Introduction: Automation With the Giant STM32F746G and ESP32
Today, we’re discussing one of the largest chips in the STM32 line: F746G. With ARM architecture, its module has STM32 already built-in, which provides a really powerful M7 cortex. It also has many OIs. So let’s program this "giant" using Mbed to create a relay control connected to an ESP32. Also, let's program the ESP32 to connect to the server and receive control commands from the relays.
In our project today, we fed the module with USB and an RJ45, which is a modular connector used in telecommunication terminations. So in the circuit, the STM32 comes through the Ethernet port and enters a suit, where we will have an access point. This communicates over WiFi with the ESP32.
I want to point out that in this project, I did not use any purchased graphic library for STM32. Some of these cost around $5,000. For an easy and cheap option, I used the C language.
Step 1: STM32 F746G
This model has a display that I consider to be very good, as it has capacitive touch. It also has camera and microphone inputs.
In this STM32 F7 kit from the Discovery line, STMicroelectronics put in the Arduino One pinout! This shows the importance of Arduino in the professional world of microcontrollers.
Step 2: WiFi NodeMCU-32S ESP-WROOM-32
Step 3: Assembly
Step 4: Program - ESP32
We start programming the ESP32!
Let's create a program to connect to a server (in this case, the STM32F746G), and then we’ll handle the commands received from the server to control the relays.
ESP32 source code
Libraries and Variables
We have included the WiFi library and defined the pins that will control relays 1 and 2. We instantiated the control of the timer, meaning we placed a WatchDog and pointed out the credentials of the network that we wish to connect to the ESP32. We also point out data related to the server, such as IP and port.
#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
We initialize the relay pins and try to connect to the desired network.
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
Let's reset the timer, which powers the Watchdog. We try to connect to the server, working its conditionals. Then, with the parser of the data coming from the server, we gave answers and closed the 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
In this step, we work with the package that was sent by the server (the STM32). This data must be of type # R1 | 0. I check if the first byte is what we expect. Then, we take the relay that we must apply the command to and point the value to the relay.
//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
Here is the function the timer will call to restart 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 }
Step 5: Program - STM32
Let's now create the program for the STM32F746G, remembering that this program is done in the Mbed compiler. We will also create a program that turns the STM32F746G into a relay controller through its touch interface.
Libraries and Variables
We have included seven libraries and defined coordinates of relay buttons, as well as their height and width. I declare the object that controls the display and the touch screen. We also have the pointer going to a TCP socket and relay state control variables.
#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;
Prototypes
In this part of the code, we have the prototype of the functions.
/* 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
We save the touch state and point out the steps according to the status. We are still dealing with other printing details on the 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);
We continue with details on display functions, as well as button creation.
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");
Here, we have the object that controls the Ethernet network. We connect to the network, and then we get the received IP. We check if it is valid and print it on the screen. We work on this phase with the control object of the 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(ð); //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)
I created an infinite loop here, of which I will leave only when I want to. In this process, we print the status. We create a socket object, a clt_addr, and (on the client socket object) instantiate the address of the socket. We re-printed the status; this time connected. While the socket is open, record the status of the touch. Also, record if any touch is detected on the screen.
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
This function draws a button on the screen with writing in the middle.
//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
We verified in this function if touch occurred on the screen and the commands of the relay buttons.
//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; }
Also, we work with the functions that involve the connection and the disconnection of the relays.
}
//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
In this first function, it checks if there is a touch involving any of the buttons. In the second function, we print on the screen the status of the connection (client connected or disconnected).
//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
Finally, we send a package of commands to the client, and we wait for confirmation.
//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; }
Step 6: Files
You can download the program through the link:
https://os.mbed.com/users/FernandoKoyanagi/code/DI...
Download the other files:
I also prepared a step by step how to acquire an STM32, for example, direct from STMicroelectronics: