Manual

do

Maker

.

com

Como fazer uma tela de login com LVGL

Como fazer uma tela de login com LVGL

Escrever muita teoria e pouco exemplo é chato, tanto para quem escreve quanto para quem lê, mas às vezes é um mal necessário. Mas dessa vez vamos ver algumas coisas interessantes. Dando sequência à série, veremos nesse artigo como fazer uma tela de login com LVGL.

Aproveitando o ensejo, vamos dar uma olhada em alguns outros detalhes interessantes para dar um charme extra ao programa, vendo algumas animações, logoff (voltando à tela de bloqueio) e no vídeo pretendo mostrar também algumas vantagens em usar o VSCode com PlatformIO para desenvolvimento rápido.

Bibliotecas utilizadas nesse artigo

Em primeiríssimo lugar, a estrela do artigo: o lvgl (por isso o título da tela de login com LVGL), que pode ser instalado tanto pela IDE do Arduino como pelo PlatformIO. Mas instale a biblioteca lv_arduino e siga as orientações dos primeiros passos com LVGL no ESP32.

Também utilizaremos biblioteca TFT_eSPI que será o driver do touchscreen, mais nada. Claro que essa biblioteca sozinha dá conta de fazer telas bacanas o suficiente para satisfazer a maioria das pessoas. Já escrevi algumas coisas sobre ela, como o artigo dedicado ao LI9341 com TFT_eSPI na AFSmartControl. Também tem o misturador de cores CMYK, no qual utilizei um código de exemplo como base, mas que migarei para LVGL por ser mais adequado para o propósito, como veremos em outro artigo.

Por alguma razão estaremos utilizando também a stdio (que é padrão), mas sem um propósito explícito.

A placa utilizada é a AFSmartControl da AFEletronica, que possui 4 relés controlados pelo programa. Por essa razão, a biblioteca EasyPCF8574está sendo usada.

Para terminar os includes, vamos habilitar o WiFi. Apenas o WiFi consumirá 50% de todo o programa; realmente, use somente se necessário.

Design da tela de login com LVGL

Poderia ter feito com usuário e senha, mas coloquei apenas numérico, por uma única razão: espaço.

Para usar o widget keyboard, a melhor posição é no estilo "widescreen", com a tela deitada. Como tenho trabalhado em aplicações com a tela "em pé", optei por fazer apenas uma senha numérica de 6 dígitos e sem usuário.

Matriz de botões e label da tela de login com LVGL

No artigo anterior discorri sobre a matriz de botões, que é bem mais leve do que botões independentes inseridos em um container. Dessa vez vou conseguir exemplificar com um pouco mais de recursos, mas previamente foi necessário desenhar o comportamento pretendido:

  • Ao ligar, pedir a senha
  • Informar que a senha será visível

Na lógica deu um pouquinho de trabalho. Sempre que tem interação humana devemos considerar o maior número de possibilidades de erro possível e implementar o tratamento. Digitar 6 números e apertar Enter, o que pode dar errado? - Vejamos na lógica:

  • Se acertou a senha e pressionou no botão de entrada, fecha a tela de login e inicia a tela da aplicação.
  • Se digitou a senha errada e apertou Enter, limpa a variável de senha e ignora a tentativa de login.
  • Se digitou além de 6 dígitos, limpa a variável de senha e ignora a tentativa de login.
  • Enquanto não completar a senha, concatena os dígitos.
  • Se errou a digitação, tem a opção de corrigir; remove tantos caracteres quanto desejado.
  • Garantir que o decremento não seja inferior a 0, senão vai invadir um endereço de memória qualquer.
  • Exibe o que está sendo digitado.

Na tela foram dispostos os 10 dígitos, além do botão para correção e o botão de entrada. O botão de correção é representado por 'x' e o botão de entrada é representado por '>'.

Todo o tratamento do que for digitado na tela é tratado por um callback.

O que fiz fora do padrão (como citei logo no início) foi criar alguns objetos globalmente, como o label que exibe a senha, de modo a modificá-lo a partir do callback, sem ter que estudar a documentação para saber qual é a maneira correta. E já que citei a documentação, aqui está (em inglês).

Para fazer a tela de login com LVGL, utilizei o widget page sobre a tela padrão. Poderia ter criado outra tela, uma janela com decoração (decoradores de janela são as bordas com botão de fechar, etc) ou outro widget. Não sei dizer ainda se existe o "correto", porém não houve nenhum impacto na escolha.

Os objetos da LVGL podem ser criados e excluídos dinamicamente, de modo a poupar RAM. Só que todos os recursos que forem implementados no código serão armazenados em algum lugar para o momento em que forem usados, certo? Daí o ponto de maior impacto acaba sendo a memória flash e por isso devemos resistir à tentação de embelezamento demasiado da interface, pois todo o background da aplicação precisa ser escrito.

Componentes da tela de login com LVGL

Para a tela de login funcional, temos 3 importantes componentes do design dessa aplicação:

  • A tela, que fará interface com o usuário
  • O callback, que atuará sobre os eventos gerados pela tela.
  • Uma task, que inicia o WiFi somente após a confirmação de login.

A task está sendo iniciada na condicional que valida o login; ela inicia o WiFi e se finaliza, portanto só ocupa processamento e memória por um momento.

tela de login com LVGL

O código da tela, com os devidos comentários:

void loginScreen(){
    //cria a tela para receber os widgets
    lv_obj_t * page = lv_page_create(lv_scr_act(), NULL); //a página é criada na janela principal
    lv_obj_set_size(page, DISPLAY_WIDTH, DISPLAY_HEIGHT); // ocupa o tamanho total da tela
    lv_page_set_scrlbar_mode(page, LV_SCRLBAR_MODE_OFF); //desabilita o scrollbar
    lv_obj_align(page, NULL, LV_ALIGN_CENTER, 0, 0); //alinha ao centro

    //Cria um array com os labels para os botões. Como explicado no artigo anterior, LF cria nova linha e "" finaliza.
    static const char * keypad[] = {"1","2","3","\n",
                                    "4","5","6","\n",
                                    "7","8","9","\n",
                                    "x","0",">",""};
    lv_obj_t * btnm1 = lv_btnmatrix_create(page, NULL); //cria um objeto do tipo lv_obj_t com o widget de matriz de botões
    lv_btnmatrix_set_map(btnm1, keypad); //configura o array de botões                               
    lv_obj_align(btnm1, NULL, LV_ALIGN_IN_BOTTOM_MID, -8, 0); //alinha o array de botões na base
    lv_obj_set_width(btnm1,230); //uma leve "apertadinha" na largura para sobrar uns pixels na tela
    lv_obj_set_event_cb(btnm1, event_handler_passwd); //define a função de callback

    //label que exibirá a senha. Veremos sobre os símbolos disponíveis também
    labelB = lv_label_create(page, NULL);
    lv_label_set_text(labelB, LV_SYMBOL_EYE_OPEN "   (senha visivel)"); //https://docs.lvgl.io/v7/en/html/overview/font.html
    lv_obj_align(labelB, NULL, LV_ALIGN_IN_TOP_MID, -5, 20); //alinhamento
    
}

Não é incrível? Praticamente não precisamos escrever código, essa biblioteca é surpreendente!

Callback da função de login

Acima está definido o callback chamado event_handler_passwd, para manipular os eventos gerados pela matriz de botões. Os callbacks não precisam ser referenciados em outra parte do código, apenas declaramos o callback dessa maneira, com o código atendendo às condicionais descritas mais acima:

static void event_handler_passwd(lv_obj_t * obj, lv_event_t event){

    if(event == LV_EVENT_VALUE_CHANGED) {
        const char * txt = lv_btnmatrix_get_active_btn_text(obj);
        //CONDICAO 1: senha bate!
        if (strcmp(txt,">") == 0 && asterisc[5] != ' '){
            if (strcmp(asterisc,"123456") == 0){
                Serial.println("login ok");  
                pwd_pos = 0;
                memset(asterisc,' ',sizeof(asterisc));  
            }
            memset(asterisc,' ',sizeof(asterisc));
            lv_obj_del(obj);
            main_app();
            //inicia o wifi se passou pelo login
            xTaskCreatePinnedToCore(taskWiFi,"taskWiFi",10000,NULL,0,NULL,1);

        }
        //CONDICAO 2: senha nao bate
        else if (strcmp(txt,">") == 0 && asterisc[5] != pwd_login[5]){
            memset(asterisc,' ',sizeof(asterisc));
            Serial.println("You should not pass");
            pwd_pos = 0;
        }
        //CONDICAO 3: digitou mais do que deveria
        else if (strcmp(txt,">") != 0 && asterisc[5] != ' ' && txt[0] != 'x'){
            memset(asterisc,' ',sizeof(asterisc));
            pwd_pos = 0;
            Serial.println("You should not pass");
        }
        //CONDICAO 4: ainda nao completou a senha, entao concatena (se nao for correcao)
        else if (asterisc[5] == ' ' && txt[0] != 'x'){
            asterisc[pwd_pos] = txt[0];
            pwd_pos += 1;
        }
        //CONDICAO 5: corrigindo senha
        else if (txt[0] == 'x'){
            pwd_pos = pwd_pos > 0 ? pwd_pos-1 : 0; //apaga só até chegar em 0
            asterisc[pwd_pos] = ' ';
            
        }
        Serial.println(asterisc);
        lv_label_set_text_fmt(labelB,"%s\0",asterisc);
    }
}

Repare na primeira condicional, quando a senha está correta e Enter foi apertado. Essa condicional termina com 3 execuções fundamentais:

  • Excluir a tela de login.
  • Inicia a tela da aplicação principal.
  • Cria uma task que inicia o WiFi e anima um ícone de WiFi na aba Dash da tela principal.

Para excluir qualquer recurso instanciado, podemos utilizar lv_obj_del(objeto), sem nenhum problema.

A task é um recurso do RTOS, mas dá pra usar em Arduino também, se instalar o RTOS para Arduino. Só não sei o quanto de recursos isso ocupa e por fim, não sei se o Arduino comportaria todo o programa descrito aqui, mas WiFi já seria um recurso que teria que ser removido.

A task contém o seguinte código:

void taskWiFi(void *pvParameters){
    //inicia o wifi
    WiFi.mode(WIFI_STA);
    WiFi.begin(SSID, PASSWD);
    while (WiFi.status() != WL_CONNECTED){
        delay(500);
        Serial.print(F("."));
    }
    vTaskDelay(pdMS_TO_TICKS(1000)); //um segundo extra para a animação do spinner que aguarda conexão
    lv_obj_set_hidden(labelWiFi,false); //o label wifi contém o simbolo de wifi. Com a flag false, ele reaparece
    lv_obj_fade_in(labelWiFi,3000,500); //faz uma animação de surgimento no ícone
    lv_spinner_set_arc_length(preload, 360); //pára a animação do spinner

    vTaskDelete(NULL); //finaliza a tarefa. O WiFi continua funcionando
}

Agora vamos ver alguns recursos interessantes da LVGL.

A fonte padrão contém uns símbolos, como o de WiFi, utilizado para mostrar que há conectividade nesse programa, ou o olho do lado esquerdo da senha. Temos uma série de símbolos que podem ser utilizados:

symbols.jpg

Não precisamos nos referenciar sempre a essa tabela quando estivermos implementando nosso código, mas isso e outras coisas importantes para desenvolvimento rápido eu vou mostrar no vídeo. Já adianto, a IDE de programação "tem" que ser o VSCode com PlatformIO, e vou mostrar no vídeo o porquê.

Enfim, a tela de login é essa. Em setup() fazemos a inicialização do display e dos drivers do display e touch básico, como mostrado no artigo de primeiros passos, mas vou dar uma reforçada no conceito:

  • Devemos calibrar o touch. Isso é feito com a biblioteca TFT_eSPI, que gerencia o touch. Se utilizar o display na posição 0, poderá usar a calibração que já está no código. De outro modo, siga os passos descritos no tutorial do display ILI9341 touch.
  • Inicializamos o buffer do display.
  • Inicializamos o display.
  • Inicializamos o driver de entrada.
  • Chamamos a função de login.

Apenas isso. O setup fica um pouco maior que o habitual, mas não tem mais nada a ser feito, exceto um ticker no loop. O setup:

void setup(){
    Serial.begin(9600);

    if (!pcfSmart.startI2C(21,22)){
        Serial.println("Not started. Check pin and address.");
        while (true);
    }

    lv_init();

    uint16_t calData[5] = {331, 3490, 384, 3477, 6};

    tft.begin(); /* TFT init */
    tft.setRotation(0); /* Landscape orientation */


    tft.setTouch(calData);

    lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10);

    /*Initialize the display*/
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = 240;
    disp_drv.ver_res = 320;
    disp_drv.flush_cb = my_disp_flush;
    disp_drv.buffer = &disp_buf;
    lv_disp_drv_register(&disp_drv);

    /*Initialize the (dummy) input device driver*/
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER; //pointer para mouse e touch
    indev_drv.read_cb = my_input_read;
    lv_indev_drv_register(&indev_drv);

    memset(asterisc,' ',sizeof(asterisc));
    loginScreen();
}

E a função loop():

void loop(){
    lv_task_handler(); /* let the GUI do its work */
    delay(5);
}

Adicionalmente, coloquei o símbolo de power sobre a tela principal da aplicação, para tornar a bloquear o dispositivo, e assim evitar que pessoas não autorizadas executem alguma funcionalidade, como acionar um relé.

lock_screen.webp

Código completo e vídeo

Esse código de exemplo da tela de login com LVGL é mostrado no vídeo que está (ou estará) disponível em nosso canal DobitaobyteBrasil no Youtube. Se não é inscrito ainda, inscreva-se e clique no sininho para receber notificações. No vídeo explico o código e os recursos, além de mostrar como programar rapidamente com uma boa IDE. Agora o código:

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <stdio.h>
#include <EasyPCF8574.h>
#include <WiFi.h>

#define DISPLAY_WIDTH  240
#define display_HEIGHT 320

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

EasyPCF8574 pcfSmart(0x27,0xFF);

TFT_eSPI tft = TFT_eSPI(); /* TFT instance */
static lv_disp_buf_t disp_buf;
static lv_color_t buf[LV_HOR_RES_MAX * 10];

uint16_t t_x = 0, t_y = 0;

unsigned long time_to_next = millis();

char asterisc[6]  = {' '};
String pwd_login = "123456";

uint8_t pwd_pos = 0;

//a maneira correta de manipular é por child. verificar isso depois
lv_obj_t * labelWiFi;
lv_obj_t * labelB;
lv_obj_t * preload;

bool has_logged_in = false;
//BOTOES DA MATRIX
uint8_t relays[4] = {7,6,5,4}; //pinos dos relés no PCF8574 da AFSmartControl
static const char * btn_labels[] = {"1","2","3","4",""}; //labels nos botões da matriz

//===================== prototipos ======================================
void lv_ex_btn_1(void);
void main_app(void);
void lv_ex_arc_1(void);
static void event_handler_matrix(lv_obj_t * obj, lv_event_t event);
void lv_ex_btnmatrix_1(void);
void taskWiFi(void *pvParameters);
void loginScreen();

//=======================================================================

//============================= task wifi ===============================
void taskWiFi(void *pvParameters){
    WiFi.mode(WIFI_STA);
    WiFi.begin(SSID, PASSWD);
    while (WiFi.status() != WL_CONNECTED){
        delay(500);
        Serial.print(F("."));
    }
    vTaskDelay(pdMS_TO_TICKS(1000));
    lv_obj_set_hidden(labelWiFi,false);
    lv_obj_fade_in(labelWiFi,3000,500);
    lv_spinner_set_arc_length(preload, 360);

    vTaskDelete(NULL);
}

//=======================================================================

//=========== callbacks =================================================
static void event_logoff(lv_obj_t * obj, lv_event_t event){
    if(event == LV_INDEV_STATE_PR) {
        memset(asterisc,' ',sizeof(asterisc));
        loginScreen();
    }
}

static void event_handler(lv_obj_t * obj, lv_event_t event){
    if(event == LV_INDEV_STATE_PR) {
        printf("Clicked\n");
    }
}

static void event_handler_matrix(lv_obj_t * obj, lv_event_t event)
{
    if(event == LV_EVENT_VALUE_CHANGED) {
        const char * txt = lv_btnmatrix_get_active_btn_text(obj);

        pcfSmart.setInvertBit(relays[String(txt).toInt()-1]);
        Serial.print("Rele assumido: ");
        Serial.println(txt);
    }
}

static void event_handler_passwd(lv_obj_t * obj, lv_event_t event){

    if(event == LV_EVENT_VALUE_CHANGED) {
        const char * txt = lv_btnmatrix_get_active_btn_text(obj);
        //CONDICAO 1: senha bate!
        if (strcmp(txt,">") == 0 && asterisc[5] != ' '){
            if (strcmp(asterisc,"123456") == 0){
                Serial.println("login ok");  
                pwd_pos = 0;
                memset(asterisc,' ',sizeof(asterisc));  
            }
            memset(asterisc,' ',sizeof(asterisc));
            lv_obj_del(obj);
            main_app();
            //inicia o wifi se passou pelo login
            xTaskCreatePinnedToCore(taskWiFi,"taskWiFi",10000,NULL,0,NULL,1);

        }
        //CONDICAO 2: senha nao bate
        else if (strcmp(txt,">") == 0 && asterisc[5] != pwd_login[5]){
            memset(asterisc,' ',sizeof(asterisc));
            Serial.println("You should not pass");
            pwd_pos = 0;
        }
        //CONDICAO 3: digitou mais do que deveria
        else if (strcmp(txt,">") != 0 && asterisc[5] != ' ' && txt[0] != 'x'){
            memset(asterisc,' ',sizeof(asterisc));
            pwd_pos = 0;
            Serial.println("You should not pass");
        }
        //CONDICAO 4: ainda nao completou a senha, entao concatena (se nao for correcao)
        else if (asterisc[5] == ' ' && txt[0] != 'x'){
            asterisc[pwd_pos] = txt[0];
            pwd_pos += 1;
        }
        //CONDICAO 5: corrigindo senha
        else if (txt[0] == 'x'){
            pwd_pos = pwd_pos > 0 ? pwd_pos-1 : 0; //apaga só até chegar em 0
            asterisc[pwd_pos] = ' ';
            
        }
        Serial.println(asterisc);
        lv_label_set_text_fmt(labelB,"%s\0",asterisc);
    }
}

//========================================================================

//======================== label child do login screen ======================================
static lv_res_t btn_ok_clicked(lv_obj_t *btn)
{
    lv_obj_t * label = lv_obj_get_child(btn, NULL); /*The label is the only child*/
    lv_label_set_text(label, asterisc);
    return LV_RES_OK;   /*The button is not deleted*/
}

//================================== 1 - tela de login ======================================
void loginScreen(){
    //cria a tela para receber os widgets
    lv_obj_t * page = lv_page_create(lv_scr_act(), NULL);
    lv_obj_set_size(page, DISPLAY_WIDTH, display_HEIGHT);
    lv_page_set_scrlbar_mode(page, LV_SCRLBAR_MODE_OFF);
    lv_obj_align(page, NULL, LV_ALIGN_CENTER, 0, 0);

    static const char * keypad[] = {"1","2","3","\n",
                                    "4","5","6","\n",
                                    "7","8","9","\n",
                                    "x","0",">",""};
    lv_obj_t * btnm1 = lv_btnmatrix_create(page, NULL);
    lv_btnmatrix_set_map(btnm1, keypad);                                
    lv_obj_align(btnm1, NULL, LV_ALIGN_IN_BOTTOM_MID, -8, 0);
    lv_obj_set_width(btnm1,230);
    lv_obj_set_event_cb(btnm1, event_handler_passwd);

    //label
    
    labelB = lv_label_create(page, NULL);
    lv_label_set_text(labelB, LV_SYMBOL_EYE_OPEN "   (senha visivel)"); //https://docs.lvgl.io/v7/en/html/overview/font.html
    lv_obj_align(labelB, NULL, LV_ALIGN_IN_TOP_MID, -5, 20);
    
}

//****************************** WIDGETS - main app ****************************************//
void main_app(void){
    /*Create a Tab view object*/
    lv_obj_t *tabview;
    tabview = lv_tabview_create(lv_scr_act(), NULL);

    /*Add 3 tabs (the tabs are page (lv_page) and can be scrolled*/
    lv_obj_t *tab1 = lv_tabview_add_tab(tabview, "Dash");
    lv_obj_t *tab2 = lv_tabview_add_tab(tabview, "Events");
    lv_obj_t *tab3 = lv_tabview_add_tab(tabview, "Setup");

    //--------------------------TAB 1: arc-------------------------------------------
    static lv_color_t needle_colors[3];
    needle_colors[0] = LV_COLOR_BLUE;
    needle_colors[1] = LV_COLOR_CYAN;

    /*Create a gauge*/
    lv_obj_t * gauge1 = lv_gauge_create(tab1, NULL);
    lv_gauge_set_needle_count(gauge1, 2, needle_colors);
    lv_obj_set_size(gauge1, 110, 110);
    lv_obj_align(gauge1, NULL, LV_ALIGN_IN_TOP_LEFT, 5, 5);
    lv_gauge_set_scale(gauge1, 360, 12, 0);
    lv_gauge_set_angle_offset(gauge1, 180);

    /*Set the values*/
    lv_gauge_set_value(gauge1, 0, -216);
    lv_gauge_set_value(gauge1, 1, -30);

    lv_gauge_set_critical_value(gauge1, 0);


    //spinner
    preload = lv_spinner_create(tab1, NULL);
    lv_obj_set_size(preload, 110, 110);
    lv_obj_align(preload, NULL, LV_ALIGN_IN_TOP_RIGHT, -5, 5);
    //lv_obj_fade_out(preload,2000,3000);

    
    labelWiFi = lv_label_create(preload, NULL);
    lv_label_set_text(labelWiFi, LV_SYMBOL_WIFI);
    //lv_btn_set_fit2(preload, LV_FIT_NONE, LV_FIT_TIGHT);
    lv_obj_align(labelWiFi,NULL,LV_ALIGN_CENTER,0,0);
    lv_obj_set_hidden(labelWiFi,true);
    
    //--------------------------------------------------------------------------------

    //TAB2 ---------------------------------------------------------------------------
    lv_obj_t * btnm1 = lv_btnmatrix_create(tab2, NULL);
    lv_btnmatrix_set_map(btnm1, btn_labels);
    //lv_btnmatrix_set_btn_width(btnm1, 4, 2);        /*Make "Action1" twice as wide as "Action2"*/
    for (uint8_t i=0;i<4;i++){
        lv_btnmatrix_set_btn_ctrl(btnm1, i, LV_BTNMATRIX_CTRL_CHECKABLE);      
    }
    lv_obj_align(btnm1, NULL, LV_ALIGN_IN_TOP_MID, 0, 10);
    lv_obj_set_height(btnm1,50);
    lv_obj_set_width(btnm1,225);
    lv_obj_set_event_cb(btnm1, event_handler_matrix);

    //--------------------------------------------------------------------------------
 
    //----------------------TAB 3: button-----------------------------------------------
    lv_obj_t * labelB;

    lv_obj_t * btn1 = lv_btn_create(tab3, NULL);

    lv_obj_set_event_cb(btn1, event_handler);
    lv_obj_align(btn1, NULL, LV_ALIGN_CENTER, 0, -25);

    labelB = lv_label_create(btn1, NULL);
    lv_label_set_text(labelB, "Button");

    lv_obj_t * btn2 = lv_btn_create(tab3, NULL);
    lv_obj_set_event_cb(btn2, event_handler);
    lv_obj_align(btn2, NULL, LV_ALIGN_CENTER, 0, 25);
    lv_btn_set_checkable(btn2, true);
    lv_btn_toggle(btn2);
    lv_btn_set_fit2(btn2, LV_FIT_NONE, LV_FIT_TIGHT);

    labelB = lv_label_create(btn2, NULL);
    lv_label_set_text(labelB, "Toggled");
    //----------------------------------------------------------------------------------
    /* Topo da screen principal */
    lv_obj_t *bb = lv_label_create(lv_scr_act(), NULL);
    lv_label_set_text(bb, "Manual do Maker - LVGL");
    lv_obj_align(bb, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, -10);

    lv_obj_t * btn_logoff = lv_btn_create(lv_scr_act(), NULL);
    lv_obj_align(btn_logoff,NULL,LV_ALIGN_IN_BOTTOM_RIGHT,0,-24);
    lv_obj_set_width(btn_logoff,30);
    lv_obj_set_height(btn_logoff,30);
    lv_obj_set_event_cb(btn_logoff, event_logoff);

    lv_obj_t *icon_logoff = lv_label_create(btn_logoff, NULL);
    lv_label_set_text(icon_logoff, LV_SYMBOL_POWER);

}

/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
    uint32_t w = (area->x2 - area->x1 + 1);
    uint32_t h = (area->y2 - area->y1 + 1);

    tft.startWrite();
    tft.setAddrWindow(area->x1, area->y1, w, h);
    tft.pushColors(&color_p->full, w * h, true);
    tft.endWrite();

    lv_disp_flush_ready(disp);
}

bool my_input_read(lv_indev_drv_t * drv, lv_indev_data_t*data)
{
    if ((millis()-time_to_next) > 1000){
        if (tft.getTouch(&t_x, &t_y)){
            data->state = LV_INDEV_STATE_PR ;
            data->point.x = t_x;
            data->point.y = t_y;
            time_to_next = millis();
        }
        else{
            data->state = LV_INDEV_STATE_REL;
            data->point.x = 0;
            data->point.y = 0;
        }
    }
    
    return false; /*No buffering now so no more data read*/
}

void setup(){
    Serial.begin(9600);

    if (!pcfSmart.startI2C(21,22)){
        Serial.println("Not started. Check pin and address.");
        while (true);
    }

    lv_init();

    uint16_t calData[5] = {331, 3490, 384, 3477, 6};

    tft.begin(); /* TFT init */
    tft.setRotation(0); /* Landscape orientation */


    tft.setTouch(calData);

    lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10);

    /*Initialize the display*/
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = 240;
    disp_drv.ver_res = 320;
    disp_drv.flush_cb = my_disp_flush;
    disp_drv.buffer = &disp_buf;
    lv_disp_drv_register(&disp_drv);

    /*Initialize the (dummy) input device driver*/
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER; //pointer para mouse e touch
    indev_drv.read_cb = my_input_read;
    lv_indev_drv_register(&indev_drv);

    memset(asterisc,' ',sizeof(asterisc));
    loginScreen();
    //lv_obj_set_hidden(labelWiFi,true);
}


void loop(){
    lv_task_handler(); /* let the GUI do its work */
    delay(5);
}

Até a próxima!

 

Revisão: Ricardo Amaral de Andrade

Inscreva-se no nosso canal Manual do Maker no YouTube.

Também estamos no Instagram.

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.