banner

Ir para o Forum

STM32 F746G Automação com relé e ESP32


Um dos maiores chips da linha STM32: F746G. Com arquitetura ARM, seu módulo já vem embutido o STM32, que é um córtex M7, realmente poderoso, e ele tem muitos IOs. Vamos, portanto, programar esse “gigante” usando o Mbed para criar um controle de relés ligado a um ESP32. Ainda, vamos programar o ESP32 para se conectar ao server e receber comandos de controle dos relés.


No nosso projeto de hoje, portanto, alimentamos o módulo com USB e um RJ45, ou seja, um conector modular usado em terminações de telecomunicação. No circuito, então, o STM32 vem pela porta Ethernet e entra num switch, onde teremos um access point, o qual se comunica por WiFi com o ESP32.
Quero destacar, pessoal, que, neste projeto, não utilizei nenhuma biblioteca gráfica comprada do STM32. Sabemos que tem algumas que chegam a custar R$ 20 mil. Mas, para baratear e facilitar o acesso a todos, utilizei apenas a linguagem C.



STM32 F746G

Esse modelo possui um display que considero muito bom, assim como o touch capacitivo. Possui ainda entrada para câmera e para micros.


Neste kit do STM32 F7, da linha Discovery, a STMicroelectronics fez o seguinte: ela colocou a pinagem do Arduino Uno! Isso só mostra a importância do arduino no mundo profissional dos microcontroladores.




WiFi NodeMCU-32S ESP-WROOM-32




Montagem





Programa – ESP32

Começamos programando o ESP32!

Vamos criar um programa para se conectar a um server, neste caso, o STM32F746G, e tratar os comandos recebidos do servidor para controlar os relés.

Bibliotecas e Variáveis

Incluímos a biblioteca WiFi e definimos os pinos que controlarão os relés 1 e 2. Instanciamos o controle do temporizador, ou seja, colocamos um WatchDog, e apontamos as credenciais da rede que desejamos conectar ao ESP32. Apontamos ainda os dados relacionados ao server, como o IP e a porta.

#include <WiFi.h>

#define RELAY_1 22 //pino controla o relé 1
#define RELAY_2 23 //pino controla o relé 2

#define RELAY_ON  '1' 
#define RELAY_OFF '0'

hw_timer_t *timer = NULL; //faz o controle do temporizador (interrupção por tempo)

//credenciais da rede que desejamos conectar o ESP32
const char* ssid = "SSID_rede";
const char* password =  "Senha_rede";

//dados do server (ip, porta)
const uint16_t port = 80;
const char * host = "IP_SERVER";

Setup

Inicializamos os pinos dos relés e tentamos conectar na rede desejada.

void setup()
{
  pinMode(RELAY_1, OUTPUT);
  pinMode(RELAY_2, OUTPUT);
  
  digitalWrite(RELAY_1, LOW);
  digitalWrite(RELAY_2, LOW);
  
  Serial.begin(115200);

  //tenta conectar na rede desejada
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println("...");
  }
 
  Serial.print("WiFi connected with IP: ");
  Serial.println(WiFi.localIP());

  //hw_timer_t * timerBegin(uint8_t num, uint16_t divider, bool countUp)
  /*
     num: é a ordem do temporizador. Podemos ter quatro temporizadores, então a ordem pode ser [0,1,2,3].
    divider: É um prescaler (reduz a frequencia por fator). Para fazer um agendador de um segundo, 
    usaremos o divider como 80 (clock principal do ESP32 é 80MHz). Cada instante será T = 1/(80) = 1us
    countUp: True o contador será progressivo
  */
  timer = timerBegin(0, 80, true); //timerID 0, div 80
  //timer, callback, interrupção de borda
  timerAttachInterrupt(timer, &resetModule, true);
  //timer, tempo (us), repetição
  timerAlarmWrite(timer, 40000000, true); //40 segundos
  timerAlarmEnable(timer); //habilita a interrupção 

}

Loop

Resetamos o temporizador, o que alimenta o Watchdog. Tentamos nos conectar ao server, trabalhando suas condicionais. Fazemos o parser dos dados vindos do server, enviamos respostas e fechamos o socket.

void loop()
{
    timerWrite(timer, 0); //reseta o temporizador (alimenta o watchdog) 
    WiFiClient client;
    //tenta se conectar ao server
    if (!client.connect(host, port)) { 
        Serial.println("Connection to host failed"); 
        delay(1000);
        return;
    }
    Serial.printf("Connected to server successful! %s\n",host);

//    char bufPins[3];
//    bufPins[0] = statusRelay_1;
//    bufPins[1] = statusRelay_2;
//    bufPins[2] = '\0';
//    client.write(bufPins, sizeof(bufPins));

    //enquanto estiver conectado ao server
    while(client.connected())
    {       
//      Serial.println("client.connected()"); 
      //se temos dados vindo do server
      if(client.available()){
//      while(!client.available() && client.connected()){
//          timerWrite(timer, 0); //reseta o temporizador (alimenta o watchdog) 
//      }
          String str = "";
          // Read all the lines of the reply from server and print them to Serial
          //enquanto tiver dados para serem lidos do server
          while(client.available()) {
              String line = client.readStringUntil('\r');
              str += line;
              Serial.print(line);
              timerWrite(timer, 0); //reseta o temporizador (alimenta o watchdog) 
          }
          Serial.println();
          //faz o parser dos dados vindos do server
          parserPacket(str);
          //envia uma resposta de 'ok'
          char buf[3];
          buf[0] = 'o';
          buf[1] = 'k';
          buf[2] = '\0';
          client.write(buf, sizeof(buf)); //envia o 'ok'
      }//if client.available
      timerWrite(timer, 0); 
    }//while
    client.stop(); //fecha o socket
    Serial.println("Client Disconnected.");
}

parserPacket

Nesta etapa trabalhamos com o pacote que foi enviado pelo server, ou seja, o STM32. Esses dados devem ser do tipo #R1|0. Verifico se o primeiro byte é o que esperamos. Então, pegamos o relé que devemos aplicar o comando e apontamos o valor para o relé.

//faz o parser dos dadosvindos do servidor
//dados devem ser do tipo //ex: #R1|0 -> #R_|_ onde o primeiro _ é o número do relé (1/2) 
//                                             e o segundo _ é o valor para ele (0/1 {on/off})  
void parserPacket(String packet)
{  
  if(packet[0] == '#') //verifica se o primeiro byte é o que esperamos
  {
    String relay = packet.substring(1,3); //pega o relé que devemos aplicar o comando
    char value = packet.charAt(4); //pega o valor para o relé
    if(relay == "R1")
    {
      digitalWrite(RELAY_1, value-'0');
      statusRelay_1 = char(value-'0');
    }
    else if(relay == "R2")
    {
      digitalWrite(RELAY_2, value-'0');
      statusRelay_2 = char(value-'0');
    }
  }
}

IRAM_ATTR

Temos aqui a função que o temporizador irá chamar para reiniciar o ESP32.

//função que o temporizador irá chamar, para reiniciar o ESP32
void IRAM_ATTR resetModule(){
    ets_printf("(watchdog) reiniciar\n"); //imprime no log
    esp_restart_noos(); //reinicia o chip
}


Programa – STM32

Vamos agora criar o programa para o STM32F746G, lembrando que esse programa é feito no compilador Mbed. Ainda vamos criar um programa que transforma o STM32F746G em um controlador de relés através de sua interface touch.

Bibliotecas e Variáveis

Incluímos sete bibliotecas e definimos coordenadas dos botões de relés, bem como sua altura e largura. Declaro o objeto que controla o display e o touch screen. Temos ainda o ponteiro para um socket TCP e variáveis de controle de estado dos relés.

#include "mbed.h"
#include "TS_DISCO_F746NG.h"
#include "LCD_DISCO_F746NG.h"
#include "EthernetInterface.h"
#include "TCPServer.h"
#include "TCPSocket.h"
#include <string>

#define BTN_R1_X 60 //coordenada X do botão relé 1
#define BTN_R1_Y 130 //coordenada Y do botão relé 1

#define BTN_WIDTH  150 //largura do botão dos relés
#define BTN_HEIGHT 75 //altura do botão dos relés

#define BTN_R2_X 50 + (BTN_R1_X + BTN_WIDTH) //coordenada X do botão relé 2
#define BTN_R2_Y 130 //coordenada Y do botão relé 1

LCD_DISCO_F746NG lcd; //objeto que controla o display
TS_DISCO_F746NG ts;  //objeto que controla o touch do display

TCPSocket *clt_sock; //ponteiro para um socket TCP

//variáveis de controle de estado dos relés
bool btnRelay1 = false;
bool btnRelay2 = false;

Protótipos

Nesta parte do código temos o protótipo das funções.

/* PROTÓTIPO DAS FUNÇÕES */
//desenha um um botão na tela com uma escrita no meio
void drawButton(int x, int y, int width, int height, uint32_t color, char* title);
//verifica se ocorreu um toque na tela
void verifyTouch(int x, int y);
//verifica se o toque foi em algum dos botões
bool verifyTouchButton(int x, int y, int rectX, int rectY);
//envia um pacote de comandos para o client
bool sendPacket(char* packet);
//escreve na tela o status da conexão (client conectado ou desconectado)
void writeStatus(char* status, uint32_t color);

main

Guardamos o estado do touch e apontamos os passos de acordo com o status. Ainda tratamos de outros detalhes de impressão no display.

int main()
{
    TS_StateTypeDef TS_State; //estado do touch
    uint8_t status;

    status = ts.Init(lcd.GetXSize(), lcd.GetYSize()); //inicializa o touch na tela toda
    //se deu erro ao inicializar -> mensagem de falha e pára a execução do programa 
    if (status != TS_OK) {
        lcd.Clear(LCD_COLOR_RED);
        lcd.SetBackColor(LCD_COLOR_RED);
        lcd.SetTextColor(LCD_COLOR_WHITE);
        lcd.DisplayStringAt(0, LINE(5), (uint8_t *)"TOUCHSCREEN INIT FAIL", CENTER_MODE);
        wait(2);
        return 0;
    }

    lcd.Clear(LCD_COLOR_BLUE); //limpa a tela e pinta de azul
    lcd.SetBackColor(LCD_COLOR_BLUE); //cor de fundo de texto
    lcd.SetTextColor(LCD_COLOR_YELLOW); //cor do texto
    lcd.DisplayStringAt(0, LINE(5), (uint8_t *)"TOUCHSCREEN INIT OK", CENTER_MODE);


Prosseguimos com detalhes sobre funções do display, bem como sobre a criação de botões.

wait(1); //aguarda um segundo
    lcd.Clear(LCD_COLOR_BLUE); //limpa a tela
    lcd.SetTextColor(LCD_COLOR_WHITE); //seta cor do texto
    lcd.SetFont(&Font24); //seta tamanho da fonte
    lcd.DisplayStringAt(0, LINE(1), (uint8_t *)"AUTOMATION", CENTER_MODE);

    lcd.DisplayStringAt(BTN_R1_X, LINE(4), (uint8_t *)"RELAY 1", LEFT_MODE);
    lcd.DisplayStringAt(BTN_R2_X, LINE(4), (uint8_t *)"RELAY 2", LEFT_MODE);

    //cria os botões
    drawButton(BTN_R1_X, BTN_R1_Y, BTN_WIDTH, BTN_HEIGHT, LCD_COLOR_RED, "OFF");
    drawButton(BTN_R2_X, BTN_R2_Y, BTN_WIDTH, BTN_HEIGHT, LCD_COLOR_RED, "OFF");


Temos aqui o objeto que controla a rede Ethernet. Conectamos à rede, pegamos o IP recebido, verificamos se o mesmo é válido e o imprimimos na tela. Trabalhamos ainda nesta fase com o objeto de controle do server.

//objeto que controla a rede ethernet
    EthernetInterface eth;
    eth.connect(); //conecta à rede

    string ip = eth.get_ip_address(); //pega o IP recebido
    lcd.SetBackColor(LCD_COLOR_BLUE);
    lcd.SetFont(&Font8);
    //verifica se o IP é válido
    if(ip.length() <= 16 && ip.length() >= 7 ) {
        uint8_t text[18];
        sprintf((char*)text, "%s", eth.get_ip_address());
        lcd.DisplayStringAt(0, LINE(1), (uint8_t *)&text, LEFT_MODE); //imprime na tela o IP
    } else {
        lcd.DisplayStringAt(0, LINE(1), (uint8_t *)"IP Invalido", LEFT_MODE);
    }

   
    TCPServer srv; //objeto de controle do server

    //abre um server na rede
    srv.open(&eth);
    //configura a porta TCP 80 para o server
    srv.bind(eth.get_ip_address(), 80);
    /* Can handle 1 simultaneous connections */
    //aguarda uma única conexão
    srv.listen(1);


while(1)

Criei aqui um loop infinito, do qual vou sair só quando eu quiser. Neste processo imprimimos o status. Criamos um objeto socket, um clt_addr e, no objeto cliente socket, instancio o endereço do socket. Imprimimos novamente o status, desta vez de conectado. Enquanto o socket estiver aberto, registro o estado do touch e se algum toque for detectado na tela.

while(1) {
        writeStatus("Desconectado", LCD_COLOR_RED); //imprime o estado de desconectado
        TCPSocket socket; //objeto TCPsocket
        SocketAddress clt_addr; 
        clt_sock = &socket; 
        printf("waiting \n");
        //fica aguardando um client conectar
        srv.accept(clt_sock, &clt_addr);        
        printf("accept %s:%d\n", clt_addr.get_ip_address(), clt_addr.get_port());
        
        //char buffer[3];
        //int n = clt_sock->recv(buffer, sizeof(buffer));
        //printf("N : %d\n",n);
        //buffer[n] = '\0';
        //printf("Received message from Client : %s\n",buffer);
        
        
        writeStatus("Conectado", LCD_COLOR_GREEN); //imprime o estado de conectado
        clt_sock->set_timeout(5000); //seta o timeout para o socket
        //enquanto o socket estiver aberto
        while(clt_sock != NULL) {
            ts.GetState(&TS_State); //registra o estado do touch
            //se algum toque na tela foi detectado
            if (TS_State.touchDetected) {
                uint8_t idx;
                //ao tocar a tela, pode ser que múltiplos toques foram dados, portanto faremos uma verificação
                for (idx = 0; idx < TS_State.touchDetected; idx++) { 
                    //se o evento do toque foi PRESS
                    if(TS_State.touchEventId[idx] == TOUCH_EVENT_PRESS_DOWN) {
                        verifyTouch(TS_State.touchX[idx], TS_State.touchY[idx]);//verifica se tocou em algum botão
                        break;
                    }//if
                }//for
            }//if
        }//while NULL

    }//while 1
}

drawButton


Esta função desenha um botão na tela com uma escrita no meio.

//desenha um um botão na tela com uma escrita no meio
void drawButton(int x, int y, int width, int height, uint32_t color, char* title)
{
    lcd.SetFont(&Font24);
    lcd.SetTextColor(color);
    lcd.SetBackColor(color);
    lcd.FillRect(x, y, width, height);
    lcd.SetTextColor(LCD_COLOR_WHITE);
    uint8_t text[30];
    sprintf((char*)text, "%s", title);
    lcd.DisplayStringAt(x+50, y+(height/2)-10, (uint8_t *)&text, LEFT_MODE);
}

verifyTouch

Verificamos nesta função se ocorreu toque na tela e os comandos dos botões dos relés.

//verifica se ocorreu um toque na tela
void verifyTouch(int x, int y)
{
    bool response = false; //guarda o status do envio da mensagem para o client
    //verifica se tocou no botão do relé 1
    if( verifyTouchButton(x, y, BTN_R1_X, BTN_R1_Y) ) {
        char* text;
        uint32_t color;
        //se o relé está ligado então desliga
        if(btnRelay1) {
            text = "OFF";
            color = LCD_COLOR_RED;
            response = sendPacket("#R1|0"); //envia comando para desligar o relé
        } else {
            //se relé está desligado, então liga
            text = "ON";
            color = LCD_COLOR_GREEN;
            response = sendPacket("#R1|1");//envia comando para ligar o relé
        }
        //se o envio foi confirmado
        if(response) {
            drawButton(BTN_R1_X, BTN_R1_Y, BTN_WIDTH, BTN_HEIGHT, color, text); //atualiza o botão
            btnRelay1 = !btnRelay1;
        }

Ainda, trabalhamos com as funções que envolvem a ligação e o desligamento dos relés.

}
    //verifica se tocou no botão do relé 1 
    else if( verifyTouchButton(x,y,BTN_R2_X,BTN_R2_Y) ) {
        char* text;
        uint32_t color;
         //se o relé está ligado então desliga
        if(btnRelay2) {
            text = "OFF";
            color = LCD_COLOR_RED;
            response = sendPacket("#R2|0"); //envia comando para desligar o relé
        } else {
            //se relé está desligado, então liga
            text = "ON";
            color = LCD_COLOR_GREEN;
            response = sendPacket("#R2|1");//envia comando para ligar o relé

        }
        //se o envio foi confirmado
        if(response) {
            drawButton(BTN_R2_X, BTN_R2_Y, BTN_WIDTH, BTN_HEIGHT, color, text);//atualiza o botão
            btnRelay2 = !btnRelay2;
        }
    }
}


verifyTouchButton & writeStatus

Nesta primeira função, verifica se houve toque em algum dos botões. Na segunda, imprimimos na tela o status da conexão (client conectado ou desconectado).

//verifica se o toque foi em algum dos botões
bool verifyTouchButton(int x, int y, int rectX, int rectY)
{
    printf("tocou : %d,%d     %d,%d\n",x,y,rectX,rectY);
    if( (x >= rectX) && (x <= rectX + BTN_WIDTH) )  {
        if( (y >= rectY) && (y <= rectY + BTN_HEIGHT) )
            return true;
    }
    return false;
}

//escreve na tela o status da conexão (client conectado ou desconectado)
void writeStatus(char* status, uint32_t color)
{
    lcd.SetTextColor(color);
    lcd.SetBackColor(LCD_COLOR_BLUE);
    lcd.SetFont(&Font16);
    lcd.ClearStringLine(16); //limpa a linha que escreveremos
    uint8_t text[30];
    sprintf((char*)text, "%s", status);
    lcd.DisplayStringAtLine(16, (uint8_t *)&text);
}


sendPacket

Por fim, enviamos um pacote de comandos para o cliente e aguardamos confirmação.

//envia um pacote de comandos para o client
bool sendPacket(char* packet)
{
    char buffer[256];
    clt_sock->send(packet, strlen(packet)); //envia o comando
    int n = clt_sock->recv(buffer, sizeof(buffer)); //aguarda confirmação
    printf("N : %d\n",n);
    //se não chegou bytes então client não recebeu o pacote
    if (n <= 0) {
        clt_sock->close(); //fecha o socket
        clt_sock = NULL;
        return false;
    }
    // print received message to terminal
    buffer[n] = '\0';
    printf("Received message from Client : %s\n",buffer);
    return true;
}





Você pode fazer o download do programa através do link:



Baixe os outros arquivos:


Preparei também um passo a passo de como adquirir um STM32, por exemplo, direto da STMicroeletronics: Baixe aqui o PDF


Um comentário:

Tecnologia do Blogger.