ILI9341 com TFT_eSPI na AFSmartControl

ILI9341 com TFT_eSPI

Essa placa da AFEletronica me conquistou um pouco mais do que outras placas com ESP32, principalmente por ter caído como uma luva no projeto do misturador de cores CMYK, utilizando o display ILI9341 com TFT_eSPI. Nada mais prático do que ter uma placa com todos os recursos periféricos necessários a disposição, sem precisar de wiring, sem solda, sem gambiarra. Isso tudo sem contar que é uma placa nacional, com suporte, garantia e industrial! Por essa e outras sou muito simpático ao hardware desenvolvido pela AFEletronica.

Uma das coisas que essa placa possui e que pode ser interessante em diversos casos, é o RS485, muito utilizado na indústria. Trata-se de uma comunicação two-wire com alcance de até 2km. Sobre a placa estão dispostos 4 módulos relés e 4 entradas digitais, totalizando os 8 bits do PCF8574, que controla esses recursos. O display ILI9341 é um espetáculo à parte. A placa também tem slot para interface ethernet, caso desejado. Do mesmo modo, um barramento I2C exposto, para display OLED.

Programação do ILI9341 com TFT_eSPI

Para programá-la é simples, basta criar um projeto para ESP32 e escolher a placa ESP32 WROVER ou Espressif ESP32 Dev Module, no Visual Studio Code, utilizando PlatformIO.

Para controlar o PCF8574, recomendo a biblioteca que desenvolvi para exemplificar como criar uma biblioteca para Arduino. Ela está disponível no repositório oficial do Arduino e se chama EasyPCF8574.

Por enquanto estou utilizando a biblioteca TFT_eSPI para o display. Ela é bem simples de utilizar e com a interface um pouco mais elaborada ainda sobram recursos para um bom programa de backend.

Em seguida a esse artigo, estarei editando um vídeo de apresentação bem curtinho, só pra dizer o que escrevi, no intuito de trazer o pessoal do Youtube pra cá para pegar o código, mas também vocês leitores para lá, pra mostrar essa telinha em ação.

Configuração do display ILI9341

A configuração dos pinos do display deve ser feita no arquivo User_Setup.h da biblioteca TFT_eSPI. É só entrar no diretório da biblioteca e editar esse arquivo, então vá até a seção ESP32 e adicione essas linhas:

#define TOUCH_CS 33 
#define TFT_MISO 19 
#define TFT_MOSI 23 
#define TFT_SCLK 18
#define TFT_CS   12       
#define TFT_DC    2           
#define TFT_RST   4   
#define TFT_RST  -1

Calibrar o display ILI9341

Se for usar a placa AFSmartControl com a mesma disposição de tela que utilizei no ColorMixer e nesse artigo, basta utilizar o código que disponho a seguir, que já contém a devida calibração. De outro modo, siga o tutorial de calibração do artigo dedicado ao display ILI9341.

Além da biblioteca TFT_eSPI, instale também a biblioteca EasyPCF8574, utilizada para controle.

Código da imagem de destaque

O código é curtinho, parti de um exemplo de botões, mas não tem quase vestígio do sketch original porque achei o exemplo horrível e não valoriza nada a biblioteca. Enfim, com todas as implementações, o código ficou assim:

#include <Arduino.h>
#include <TFT_eSPI.h>
#include <EasyPCF8574.h>
#include <WiFi.h>

#define SSID   "SuhankoFamily"
#define PASSWD "fsjmr112"

#define KEY_X 160 // Centre of key
#define KEY_Y 50
#define KEY_W 320 // Width and height
#define KEY_H 22
#define KEY_SPACING_X 0 // X and Y gap
#define KEY_SPACING_Y 1
#define KEY_TEXTSIZE 1 // Font size multiplier
#define BUTTON_X_DELTA 22
#define NUM_KEYS 4

#define BTN_START_POS_X 36 //posição inicial do botão. O incremento é feito a partir dos valores base
#define BTN_START_POS_Y 70 //posição Y do botão
#define BTN_SIZE_X      56 //tamanho do botão em X
#define BTN_START_Y     56 //tamanho do botão em Y
#define BTN_OUTLINE      4 //outline
#define BTN_SPACER      32 //espaçador

int str_positions_x[4]    = {10,90,10,90};     //strings das inputs - posição X
int str_positions_y[4]    = {166,166,186,186}; //strings das inputs - posição Y

uint8_t relays[4]         = {7,6,5,4}; //bits do PCF relacionados aos relés (ordenando aqui)
uint8_t inputs[4]         = {3,2,1,0}; //bits do PCF relacionado aos inputs (ordenando também)
uint8_t inputs_state[4]   = {0,0,0,0}; //memória de estado para feedback no display

unsigned long timeout_to_click  = millis(); //histerese para o clique do botão
unsigned long progress_bar_time = millis(); //atualização continua baseada no intervalor de 10s

String last_relay = " ";    //memória do último acionamento de relé pelo display

EasyPCF8574 pcf(0x27,0xFF); //inicializa o barramento I2C com PCF no endereço 0x27 e todos os pinos em HIGH

TFT_eSPI tft = TFT_eSPI(); //objeto do display

TFT_eSPI_Button key[NUM_KEYS]; //4 botões para os 4 relés

void drawBoxes();                       //desenha as caixas separadoras
void WiFiInfo();                        //informação de conexão
void drawButtons();                     //desenha os botões
void drawTitles();                      //desenha os títulos das caixas separadoras
void inputStatus(uint8_t bit_to_check); //checa o estado dos pinos de entrada do PCF

void setup()
{
    //calibração do display:
    //https://www.dobitaobyte.com.br/display-ili9341-touch/
    uint16_t calData[5] = {331, 3490, 384, 3477, 6};

    Serial.begin(9600); //inicia serial

    tft.fillScreen(TFT_BLACK); //limpa tela

    tft.init(); //inicia o display

    //conexão WiFi STA
    WiFi.mode(WIFI_STA);
    WiFi.begin(SSID, PASSWD);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(F("."));
    }

    //inicialização do barramento I2C
    if (!pcf.startI2C(21,22)){
        Serial.println("Not started. Check pin and address.");
        while (true);
    }

    //Rotação do display, de 0 à 3 (de 90 em 90 graus)
    tft.setRotation(0);

    tft.setTouch(calData); //configura o touch com os parâmetros de calibragem

    // Clear screen
    tft.fillScreen(TFT_BLACK);

    //fonte utilizada no programa
    tft.setFreeFont(&FreeMono9pt7b);

    //inicia as funções anteriormente descritas
    drawButtons();
    drawBoxes();
    drawTitles();
}

void loop(){
    WiFiInfo(); //atualiza a informação do SSID de modo temporizado
    uint16_t t_x = 0, t_y = 0; // To store the touch coordinates

    // Get current touch state and coordinates
    boolean pressed = tft.getTouch(&t_x, &t_y);

    // Adjust press state of each key appropriately
    for (uint8_t b = 0; b < 4; b++)
    {
        if (pressed && key[b].contains(t_x, t_y))
            key[b].press(true); // tell the button it is pressed
        else
            key[b].press(false); // tell the button it is NOT pressed
    }

    // Check if any key has changed state
    for (uint8_t b = 0; b < 4; b++){
        inputStatus(b);
    //    // If button was just pressed, redraw inverted button
        if (key[b].justPressed())
        {
            Serial.println("Button  pressed");
            
            if ((millis()-timeout_to_click) > 1000){
                pcf.setInvertBit(relays[b]);
                timeout_to_click = millis();
                if (pcf.getBitValue(relays[b]) <1){
                    key[b].drawButton(true, "ON");
                    last_relay = String(b);
                }
                else{
                    key[b].drawButton(false, "OFF");
                    last_relay = String(b);
                }
            }
        }
        // If button was just released, redraw normal color button
        if (key[b].justReleased())
        {
            Serial.println("Button  released");
        }
    }
}

void inputStatus(uint8_t bit_to_check){
    String act = "None...";
    uint8_t bit_value = pcf.getBitValue(inputs[bit_to_check]);
    String fromRemote = bit_value < 1 ? "ON" : "OFF";
    if (bit_value < 1){ //tem que ser pulsador
        act = "R" + String(bit_to_check) + ":" + fromRemote;
        pcf.setInvertBit(inputs[bit_to_check]+4);
    }
    else{
        act = "R" + String(bit_to_check) + ":" + fromRemote;
        //pcf.setInvertBit(inputs[bit_to_check]+4);
    }
    if (inputs_state[bit_to_check] != bit_value){
        inputs_state[bit_to_check] = bit_value;
        tft.setTextColor(TFT_WHITE, TFT_BLACK);
        tft.drawString(act,str_positions_x[bit_to_check],str_positions_y[bit_to_check]);    
    }

    if ((millis()-progress_bar_time) > 9000){
        String wifi_ip = WiFi.localIP().toString();
        tft.drawCentreString(last_relay,335/2+15,255,2);
        tft.drawCentreString(wifi_ip,240/2,298,2);
    }
    
}


void WiFiInfo(){
    if ((millis()-progress_bar_time) >10000){
        tft.setTextColor(TFT_WHITE, TFT_BLACK);
        if (WiFi.status() == WL_CONNECTED){
            tft.drawCentreString(SSID,120/2-(strlen(SSID)/2)+5,255,2);
        }
        else{
            tft.drawCentreString("Offline",120/2-(strlen(SSID)/2)+5,230,4);
        }
    }
    
}

void drawBoxes(){
    tft.drawRoundRect(2,2,238,120,10,TFT_WHITE); // Acionar reles
    tft.drawRoundRect(2,128,238,90,10,TFT_WHITE); //acionador remoto
    tft.drawRoundRect(2,225,120,60,10,TFT_WHITE); //wifi
    tft.drawRoundRect(128,225,110,60,10,TFT_WHITE); //ultimo
    tft.drawRoundRect(2,294,236,26,10,TFT_WHITE); //ip
}

void drawTitles(){
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    tft.drawCentreString("Acionar Reles",240/2-(strlen("Acionar Reles")/2),5,4);
    tft.drawCentreString("Acionador Remoto",255/2-(strlen("Acionador Remoto")/2),132,4);
    tft.drawCentreString("WiFi",120/2-(strlen("WiFi")/2)+3,230,4);
    tft.drawCentreString("Atuador",335/2+15,230,4);
}

void drawButtons(){
    uint16_t cor  = tft.color565(127,127,0);
    uint16_t cor2 = tft.color565(127,0,127);
    uint16_t cor3 = tft.color565(0,127,127);
    uint16_t cor4 = tft.color565(240,100,0);

    //key[0].initButton(&tft,x,y,w,h,outline,fill,textcolor,label,textsize);
    //posicao x e y a partir do centro do botao, tamanho x e tamanho y, outline, cor, cor da fonte, texto, tamanho

    key[0].initButton(&tft, BTN_START_POS_X, BTN_START_POS_Y, BTN_SIZE_X, BTN_SIZE_X, BTN_OUTLINE, cor, TFT_WHITE, "b0", 1);
    key[1].initButton(&tft, BTN_SIZE_X+BTN_SPACER+BTN_OUTLINE, BTN_START_POS_Y, BTN_SIZE_X, BTN_SIZE_X, BTN_OUTLINE, cor2, TFT_WHITE, "b1", 1);
    key[2].initButton(&tft, BTN_SIZE_X+BTN_SIZE_X+BTN_SPACER+BTN_OUTLINE, BTN_START_POS_Y, BTN_SIZE_X, BTN_SIZE_X, BTN_OUTLINE, cor3, TFT_WHITE, "b2", 1);
    key[3].initButton(&tft, BTN_SIZE_X+BTN_SIZE_X+BTN_SIZE_X+BTN_SPACER+BTN_OUTLINE, BTN_START_POS_Y, BTN_SIZE_X, BTN_SIZE_X, BTN_OUTLINE, cor4, TFT_WHITE, "b3", 1);
    
    for (uint8_t i=0;i<4;i++){
        key[i].drawButton(false, "OFF");
    }
}

Onde comprar a AFSmartControl?

Quanto sai um módulo de 4 relés+frete, um ESP32+frete, um módulo RS485+frete, além de 2 botões, wiring, solda e tempo? Bem, com certeza custa bem mais do que essa placa “plug and play” da AFEletronica. Confira a AFSmartControl no site do fabricante.

Vídeo

O vídeo do display ILI9341 com TFT_eSPI estará disponível em breve no canal  (lembrando que é só uma apresentação da placa), mas peço encarecidamente que deixem um like para ajudar a promover nosso humilde canal. O like no vídeo tem muita importância, então, mesmo que o artigo já lhe tenha sido o suficiente, passe no canal para dar uns cliques, pode ser? Se não é inscrito e puder também contribuir dessa forma, seja bem-vindo ao canal Dobitaobytebrasil no Youtube.

 

Revisão: Ricardo Amaral de Andrade