Skip to Content

Interruptor Temporizador Digital com Display CrowPanel ESP32

Interruptor Temporizador Digital com Display CrowPanel ESP32

Neste tutorial, vais aprender a construir um Interruptor Temporizador Digital com o CrowPanel 3.5″ ESP32 Display. Vamos sincronizar o relógio interno com um fornecedor de tempo na internet para mantê-lo preciso e usar a biblioteca TFT_eSPI para criar uma interface gráfica agradável que facilita a definição dos horários.

Um Interruptor Temporizador Digital é um dispositivo eletrónico que liga ou desliga automaticamente outros dispositivos em horários específicos definidos pelo utilizador. Normalmente tem um ecrã digital para programar os horários de ligar/desligar. Pode ser usado para controlar luzes, eletrodomésticos e outros dispositivos elétricos em horários pré-determinados. Por exemplo, podes usá-lo para ligar as luzes de casa ao pôr do sol e desligá-las à hora de dormir, ou para programar o aspersor para regar o jardim e desligá-lo após um determinado período para evitar rega excessiva.

Demonstração do Interruptor Temporizador Digital

O Interruptor Temporizador Digital que vais construir neste tutorial será melhor do que a maioria dos interruptores temporizadores digitais comerciais. O seu tempo estará sempre preciso e não será necessário ajustar para o horário de verão. Além disso, devido ao ecrã grande, será fácil definir ou alterar os horários.

Peças Necessárias

Para este projeto vais precisar do CrowPanel 3.5″ ESP32 Display da ELECROW e do Arduino IDE. O painel vem com um cabo USB e um cabo DuPont de 4 pinos, por isso não precisarás de cabos adicionais.

CrowPanel 3.5″ ESP32 Display

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Características do CrowPanel 3.5″ ESP32 Display

O CrowPanel 3.5″ ESP32 Display da ELECROW é um ecrã tátil resistivo com um display TFT de resolução 480*320 e um ESP32-WROVER-B incorporado como processador de controlo. Podes adquirir o display com uma caixa acrílica elegante (ver imagem abaixo), o que significa que não precisas de construir uma caixa por ti próprio.

CrowPanel 3.5" ESP32 Display with Housing
CrowPanel 3.5″ ESP32 Display com Caixa (source)

Além disso, a placa vem com um slot para cartão TF, uma interface UART, uma interface I2C, uma interface para altifalante, um conector para bateria com capacidade de carregamento e uma pequena porta GPIO com dois pinos GPIO. Vê o pinout abaixo:

Pinout of CrowPanel 3.5" ESP32 Display
Pinout do CrowPanel 3.5″ ESP32 Display

A tabela seguinte lista quais os pinos GPIO atribuídos a cada uma das três interfaces IO.

GPIO_DIO25; IO32
UARTRX(IO16); TX(IO17)
I2CSDA(IO22); SCL(IO21)

A placa pode ser alimentada via porta USB (5V, 2A) ou ligando uma bateria LiPo padrão de 3.7V ao conector BAT. Apenas atenção à polaridade do conector e da bateria. Se o cabo USB e a bateria estiverem ligados ao mesmo tempo, a placa irá carregar a bateria (corrente máxima de carregamento é 500mA).

Podes programar a placa usando vários ambientes de desenvolvimento como Arduino IDE, Espressif IDF, Lua RTOS e Micro Python, e é compatível com o LVGL graphics library. No entanto, neste tutorial vamos usar o Arduino IDE e o TFT_eSPI graphics library.

Criar Estrutura do Projeto

Antes de entrar em detalhes, vamos criar a estrutura do projeto e a configuração para o TFT_eSPI library.

Abre o teu Arduino IDE e cria um projeto “digital_timer_switch” e guarda-o (Guardar Como …). Isto vai criar uma pasta “digital_timer_switch” com o ficheiro “digital_timer_switch.ino” dentro. Nesta pasta cria outro ficheiro chamado “datestrs.h” e um terceiro chamado “tft_setup.h“. A tua pasta do projeto deverá ficar assim

Estrutura da Pasta do Projeto

e no teu Arduino IDE deverás agora ter três separadores chamados “digital_timer_switch.ino“, “datestrs.h” e “tft_setup.h“.

Arduino IDE with three Tabs
Arduino IDE com três Separadores

Clica no separador “tft_setup.h” para abrir o ficheiro e copia o seguinte código para dentro dele:

// tft_setup.h
// makerguides.com
#define ILI9488_DRIVER
#define TFT_HEIGHT  480
#define TFT_WIDTH   320 

#define TFT_BACKLIGHT_ON HIGH
#define TFT_BL   27 
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS   15
#define TFT_DC    2 
#define TFT_RST  -1
#define TOUCH_CS 33

#define SPI_FREQUENCY        27000000
#define SPI_TOUCH_FREQUENCY   2500000
#define SPI_READ_FREQUENCY   16000000

#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6  // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7  // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-.
#define LOAD_FONT8  // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts

Este código indica ao TFT_eSPI library qual o display que estamos a usar. Especificamente, informamos a biblioteca das dimensões do display (TFT_WIDTH, TFT_WIDTH), do seu driver (ILI9488_DRIVER), quais os pinos SPI usados para o controlar e que fontes carregar. Sem estas definições, não conseguirás mostrar nada no display.

Para informações mais detalhadas, consulta o tutorial CrowPanel 2.8″ ESP32 Display : Easy Setup Guide. Lá explicamos como configurar o Display de 2.8″. Mas os passos e descrições aplicam-se da mesma forma ao Display de 3.5″. Apenas as definições das dimensões do ecrã e do driver são diferentes.

Calibrar o Ecrã Tátil

O CrowPanel 3.5″ Display vem com um ecrã tátil resistivo que precisas calibrar primeiro, antes de o poderes usar com a biblioteca TFT_eSPI. Copia o código abaixo para o ficheiro “digital_timer_switch.ino“, compila e carrega-o para o CrowPanel 3.5” Display.

#include "tft_setup.h"
#include "TFT_eSPI.h"

TFT_eSPI tft = TFT_eSPI();

void setup() {
  Serial.begin(115200);
  tft.begin();
  tft.setRotation(0);
}

void loop() {
  uint16_t cal[5];
  tft.fillScreen(TFT_BLACK);
  tft.setCursor(20, 0);
  tft.setTextFont(2);
  tft.setTextSize(1);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.print("Touch corners ... ");
  tft.calibrateTouch(cal, TFT_MAGENTA, TFT_BLACK, 15);
  tft.println("done.");

  Serial.printf("cal: {%d, %d, %d, %d, %d}\n",
                cal[0], cal[1], cal[2], cal[3], cal[4]);
  delay(10000);
}

Quando o código estiver a correr, o ecrã mostra uma seta e indica para tocares no canto para onde aponta. Isto será repetido para os outros três cantos também. Usa a pequena caneta que vem com o Display para fazer isto e tenta tocar nos cantos o mais precisamente possível.

Calibration of touch screen
Calibração do ecrã tátil

No final do processo de calibração, o código imprime os 5 parâmetros de calibração (coordenadas dos cantos e orientação do ecrã) no monitor Serial. Deverás ver algo assim:

cal: { 260, 3518, 270, 3629, 4 }

Guarda os parâmetros em algum lado, pois vais precisar deles no código do interruptor temporizador digital. A calibração repete-se a cada 10 segundos, para que possas tentar várias vezes obter os parâmetros mais precisos.

Para informações mais detalhadas sobre o processo de calibração, consulta o tutorial CrowPanel 2.8″ ESP32 Display : Easy Setup Guide.

Código para o Interruptor Temporizador Digital

Nesta secção vamos escrever o código completo para o Interruptor Temporizador Digital. Começa por copiar o seguinte código para o ficheiro “datestrs.h” que está na tua pasta do projeto digital_timer_switch.

// datestrs.h
const char *DAYSTR[] = {
    "Su",
    "Mo",
    "Tu",
    "We",
    "Th",
    "Fr",
    "Sa"
};

const char *MONTHSTR[] = {
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec"
};

De seguida, copia todo o código abaixo para o ficheiro “digital_timer_switch.ino” e substitui aí o código de calibração.

Este é o código principal. Permite aos utilizadores definir alarmes baseados num horário semanal, ver a hora atual e ativar ou desativar os alarmes. O display atualiza em tempo real, e o utilizador pode interagir através de botões táteis. Dá uma leitura rápida e depois discutiremos os detalhes do código.

// digital_timer_switch.ino
#include "tft_setup.h"
#include "TFT_eSPI.h"
#include "TFT_eWidget.h"
#include "stdarg.h"
#include "WiFi.h"
#include "esp_sntp.h"
#include "datestrs.h"

const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";
const char* SSID = "SSID";
const char* PWD = "PASSWORD";

const int rs = 24, cs = 7;
const int mx = 60, my = 40;
const int gw = TFT_WIDTH - mx - 30;
const int gh = TFT_HEIGHT - my - 50;
const int cw = gw / cs;
const int ch = gh / rs;

const int alarmPin = 25;

bool schedule[rs][cs];
bool isTimeView = true;
bool isAlarmOn = false;
uint16_t cal[5] = { 260, 3518, 270, 3629, 4 };

TFT_eSPI tft = TFT_eSPI();
ButtonWidget btnView = ButtonWidget(&tft);
ButtonWidget btnAlarm = ButtonWidget(&tft);
ButtonWidget* btns[] = { &btnView, &btnAlarm };


int c2x(int c) {
  return mx + gw * c / cs;
}

int r2y(int r) {
  return my + gh * r / rs;
}

int x2c(int x) {
  return map(x - mx, 0, gw, 0, cs);
}

int y2r(int y) {
  return map(y - my, 0, gh, 0, rs);
}

void drawLabels() {
  tft.setTextFont(1);
  tft.setTextSize(2);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  for (int r = 0; r < rs; r++) {
    tft.setCursor(24, r2y(r));
    tft.printf("%02d", r);
  }
  for (int c = 0; c < cs; c++) {
    tft.setCursor(c2x(c), 15);
    tft.print(DAYSTR[c]);
  }
}

void drawSlot(int r, int c) {
  uint32_t color = schedule[r][c] ? TFT_WHITE : TFT_BLACK;
  tft.fillRect(c2x(c) + 1, r2y(r) + 1, cw - 2, ch - 2, color);
}

void drawSchedule() {
  for (int r = 0; r < rs; r++) {
    for (int c = 0; c < cs; c++) {
      tft.drawRect(c2x(c), r2y(r), cw, ch, TFT_DARKGREY);
      drawSlot(r, c);
    }
  }
}

void selectSlot(int x, int y) {
  int c = x2c(x);
  int r = y2r(y);
  if (c < 0 || c >= cs || r < 0 || r >= rs) return;
  schedule[r][c] = !schedule[r][c];
  drawSlot(r, c);
}

void initWiFi() {
  WiFi.begin(SSID, PWD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
  }
}

void initSNTP() {
  sntp_set_sync_interval(15 * 60 * 1000UL);  // 15 minutes
  esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
  esp_sntp_setservername(0, "pool.ntp.org");
  esp_sntp_init();
  setTimezone();
}

void setTimezone() {
  setenv("TZ", TIMEZONE, 1);
  tzset();
}

void drawTime() {
  static char buff[50];
  static struct tm t;
  getLocalTime(&t);

  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.setTextSize(1);

  sprintf(buff, " %2d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec);
  tft.drawString(buff, tft.width() / 2, 130, 7);

  sprintf(buff, "  %s, %s %d, %d ",
          DAYSTR[t.tm_wday], MONTHSTR[t.tm_mon],
          t.tm_mday, t.tm_year + 1900);
  tft.drawString(buff, tft.width() / 2, 200, 4);
}

bool isOn() {
  static struct tm t;
  getLocalTime(&t);
  return schedule[t.tm_hour][t.tm_wday] && isAlarmOn;
}

void initDisplay() {
  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  tft.setTouch(cal);
}

void btnView_pressed(void) {
  if (btnView.justPressed()) {
    isTimeView = !btnView.getState();
    drawView();
    tft.setTextFont(4);
    tft.setTextSize(1);
    btnView.drawSmoothButton(isTimeView, 1, TFT_BLACK, isTimeView ? "alarm" : "time");
  }
}

void btnAlarm_pressed(void) {
  if (btnAlarm.justPressed()) {
    isAlarmOn = !btnAlarm.getState();
    tft.setTextFont(4);
    tft.setTextSize(1);
    btnAlarm.drawSmoothButton(isAlarmOn, 1, TFT_DARKGREY, isAlarmOn ? "on" : "off");
  }
}

void initButtons() {
  uint16_t w = 100;
  uint16_t h = 50;
  uint16_t y = tft.height() - h + 14;
  uint16_t x = tft.width() / 2;
  tft.setTextFont(4);
  tft.setTextSize(1);

  btnView.initButtonUL(x - w - 10, y, w, h, TFT_DARKGREY, TFT_DARKGREY, TFT_BLACK, "alarm", 1);
  btnView.setPressAction(btnView_pressed);
  btnView.drawSmoothButton(isTimeView, 1, TFT_BLACK);

  btnAlarm.initButtonUL(x + 10, y, w, h, TFT_DARKGREY, TFT_BLACK, TFT_DARKGREY, "off", 1);
  btnAlarm.setPressAction(btnAlarm_pressed);
  btnAlarm.drawSmoothButton(isAlarmOn, 1, TFT_BLACK);
}

void handleButtons() {
  tft.setTextFont(4);
  uint8_t nBtns = sizeof(btns) / sizeof(btns[0]);
  uint16_t x = 0, y = 0;
  bool touched = tft.getTouch(&x, &y);
  for (uint8_t b = 0; b < nBtns; b++) {
    if (touched) {
      if (btns[b]->contains(x, y)) {
        btns[b]->press(true);
        btns[b]->pressAction();
      }
    } else {
      btns[b]->press(false);
      btns[b]->releaseAction();
    }
  }
}

void drawView() {
  tft.fillScreen(TFT_BLACK);
  initButtons();
  if (isTimeView) {
    drawTime();
  } else {
    drawSchedule();
    drawLabels();
  }
}

void updateView() {
  static uint16_t x = 0, y = 0;
  if (isTimeView) {
    drawTime();
  } else {
    if (tft.getTouch(&x, &y)) {
      selectSlot(x, y);
    }
  }
}

void setup() {
  initWiFi();
  initSNTP();
  initDisplay();
  pinMode(alarmPin, OUTPUT);
  drawView();
}

void loop() {
  updateView();
  handleButtons();
  digitalWrite(alarmPin, isOn() ? HIGH : LOW);
  delay(100);
}

Como podes ver, é bastante código. A secção seguinte oferece uma visão geral rápida das funções e do seu significado.

Visão Geral das Funções

  • drawLabels(): Desenha etiquetas para as linhas e colunas no display.
  • drawSlot(int r, int c): Desenha um intervalo horário na grelha do horário com base na linha e coluna fornecidas.
  • drawSchedule(): Desenha toda a grelha do horário no display.
  • selectSlot(int x, int y): Permite aos utilizadores selecionar um intervalo horário na grelha tocando no display.
  • initWiFi(): Inicializa a ligação WiFi usando o SSID e a password fornecidos.
  • initSNTP(): Inicializa o SNTP (Simple Network Time Protocol) para sincronização do tempo.
  • setTimezone(): Define o fuso horário do dispositivo com base na constante fornecida.
  • drawTime(): Desenha a hora e data atuais no display.
  • isOn(): Verifica se a hora atual corresponde a um intervalo programado e se o alarme está ativado.
  • initDisplay(): Inicializa o display e configura a funcionalidade tátil.
  • btnView_pressed() e btnAlarm_pressed(): Tratam os eventos de pressão dos botões para alternar entre a vista da hora e a vista do horário, e para ativar/desativar o alarme.
  • initButtons(): Inicializa os botões no display para interagir com o projeto.
  • handleButtons(): Trata as ações de pressionar e soltar os botões no display.
  • drawView(): Desenha a vista da hora ou a vista do horário com base no modo atual.
  • updateView(): Atualiza o display com base nas interações do utilizador e no modo atual.

Vamos analisar mais de perto estas funções e outras partes do código.

Inclusão de Bibliotecas

O código começa por incluir várias bibliotecas necessárias para a funcionalidade do ESP32 e do display. Precisamos de bibliotecas para o display TFT, a ligação Wi-Fi, a sincronização do tempo com um servidor SNTP e os nomes dos meses e dias (datestrsl.h)

#include "tft_setup.h"
#include "TFT_eSPI.h"
#include "TFT_eWidget.h"
#include "stdarg.h"
#include "WiFi.h"
#include "esp_sntp.h"
#include "datestrs.h"

Constantes e Variáveis

De seguida, definimos várias constantes e variáveis que serão usadas ao longo do programa. Estas incluem credenciais Wi-Fi, dimensões do display, pino do alarme e um array de horários.

const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";
const char* SSID = "SSID";
const char* PWD = "PASSWORD";

const int rs = 24, cs = 7;
const int alarmPin = 25;

bool schedule[rs][cs];
bool isTimeView = true;
bool isAlarmOn = false;

Terás de definir o SSID e PWD para o teu WiFi e provavelmente também terás de alterar o TIMEZONE. A especificação do fuso horário acima “AEST-10AEDT,M10.1.0,M4.1.0/3” é para a Austrália, que corresponde ao Australian Eastern Standard Time (AEST) com ajustes para horário de verão.

As partes desta definição de fuso horário são as seguintes

  • AEST: Australian Eastern Standard Time
  • -10: Deslocação UTC de 10 horas à frente do Tempo Universal Coordenado (UTC)
  • AEDT: Australian Eastern Daylight Time
  • M10.1.0: Transição para horário de verão ocorre no 1º domingo de outubro
  • M4.1.0/3: Transição de volta para o horário padrão ocorre no 1º domingo de abril, com uma diferença de 3 horas em relação ao UTC.

Para outras definições de fuso horário, consulta o Posix Timezones Database. Basta copiar e colar a string que encontrares lá e alterar a constante TIMEZONE em conformidade.

Funções de Conversão de Coordenadas

As funções seguintes convertem entre sistemas de coordenadas para o display:

int c2x(int c) { ... }
int r2y(int r) { ... }
int x2c(int x) { ... }
int y2r(int y) { ... }

c2x() e r2x() mapeiam colunas e linhas (c,r) na grelha do horário para coordenadas do ecrã (x,y), enquanto x2c() e y2r() realizam a operação inversa. São necessárias para converter um evento de toque numa reserva de intervalo horário, por exemplo.

Funções de Desenho

O código inclui várias funções para desenhar elementos no display:

drawLabels

Esta função desenha as etiquetas para as linhas (horas) e colunas (dias da semana) do horário.

void drawLabels() {
  tft.setTextFont(1);
  tft.setTextSize(2);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  for (int r = 0; r < rs; r++) {
    tft.setCursor(24, r2y(r));
    tft.printf("%02d", r);
  }
  for (int c = 0; c < cs; c++) {
    tft.setCursor(c2x(c), 15);
    tft.print(DAYSTR[c]);
  }
}

Hour and Day labels for Schedule
Etiquetas de Hora e Dia para o Horário

drawSlot

Esta função desenha um único intervalo horário na grelha, preenchendo-o de branco se o alarme estiver definido para essa hora.

void drawSlot(int r, int c) {
  uint32_t color = schedule[r][c] ? TFT_WHITE : TFT_BLACK;
  tft.fillRect(c2x(c) + 1, r2y(r) + 1, cw - 2, ch - 2, color);
}
A hour time slot
Um intervalo horário

drawSchedule

Esta função desenha toda a grelha do horário, chamando drawSlot() para cada intervalo.

void drawSchedule() {
  for (int r = 0; r < rs; r++) {
    for (int c = 0; c < cs; c++) {
      tft.drawRect(c2x(c), r2y(r), cw, ch, TFT_DARKGREY);
      drawSlot(r, c);
    }
  }
}
Hourly grid for Schedule
Grelha horária para o Horário

selectSlot

Esta função alterna o estado e a cor de um intervalo horário quando o utilizador toca nele no display.

void selectSlot(int x, int y) {
  int c = x2c(x);
  int r = y2r(y);
  if (c < 0 || c >= cs || r < 0 || r >= rs) return;
  schedule[r][c] = !schedule[r][c];
  drawSlot(r, c);
}

Inicialização do Wi-Fi e SNTP

As funções seguintes tratam da inicialização do Wi-Fi e do SNTP (Simple Network Time Protocol):

initWiFi

Esta função liga o ESP32 à rede Wi-Fi especificada.

void initWiFi() {
  WiFi.begin(SSID, PWD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
  }
}

initSNTP

Esta função configura o serviço SNTP para sincronizar o tempo. Essencialmente, liga-se a um servidor na internet que fornece informações de tempo muito precisas e sincroniza o relógio interno do ESP32 em conformidade.

void initSNTP() {
  sntp_set_sync_interval(15 * 60 * 1000UL);  // 15 minutes
  esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
  esp_sntp_setservername(0, "pool.ntp.org");
  esp_sntp_init();
  setTimezone();
}

Enquanto houver ligação Wi-Fi e internet, isto garante que o relógio do nosso interruptor temporizador digital está sempre preciso e ajusta automaticamente para o horário de verão. Para mais detalhes, consulta o tutorial How to synchronize ESP32 clock with SNTP server.

setTimezone

Esta função define o fuso horário para a aplicação. Tens de garantir que esta função é executada sempre que o ESP32 inicia, caso contrário o relógio ficará desajustado.

void setTimezone() {
  setenv("TZ", TIMEZONE, 1);
  tzset();
}

Função de Exibição da Hora

A função drawTime() obtém a hora local atual, sincronizada via SNTP, e mostra a hora e data no ecrã.

void drawTime() {
  static char buff[50];
  static struct tm t;
  getLocalTime(&t);

  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.setTextSize(1);

  sprintf(buff, " %2d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec);
  tft.drawString(buff, tft.width() / 2, 130, 7);

  sprintf(buff, "  %s, %s %d, %d ",
          DAYSTR[t.tm_wday], MONTHSTR[t.tm_mon],
          t.tm_mday, t.tm_year + 1900);
  tft.drawString(buff, tft.width() / 2, 200, 4);
}

A função usa as constantes para os nomes dos dias e meses armazenados no ficheiro datestrs.h.


Time and Date shown on Display
Hora e Data mostradas no Display

Se precisares de mais detalhes, consulta os tutoriais Digital Clock with CrowPanel 3.5″ ESP32 Display e Digital Clock on e-Paper Display, onde também implementamos um relógio digital.

Função de Verificação do Alarme

A função isOn() verifica se o alarme deve ser ativado com base na hora atual e no horário. Se a célula da grelha schedule[t.tm_hour][t.tm_wday] contiver true e o interruptor geral do alarme estiver ligado (isAlarmOn), a função retorna true, caso contrário retorna false.

bool isOn() {
  static struct tm t;
  getLocalTime(&t);
  return schedule[t.tm_hour][t.tm_wday] && isAlarmOn;
}

Inicialização do Display

A função initDisplay() inicializa o display definindo a rotação do ecrã, preenchendo o ecrã de preto e configurando o parâmetro de calibração para o ecrã tátil.

void initDisplay() {
  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  tft.setTouch(cal);
}

Funções dos Botões

O código inclui funções para tratar os pressionamentos dos botões para alternar entre a vista da hora e as definições do alarme:

btnView_pressed

Esta função alterna a vista entre a hora atual e o horário do alarme.

void btnView_pressed(void) {
  if (btnView.justPressed()) {
    isTimeView = !btnView.getState();
    drawView();
    tft.setTextFont(4);
    tft.setTextSize(1);
    btnView.drawSmoothButton(isTimeView, 1, TFT_BLACK, isTimeView ? "alarm" : "time");
  }
}
Toggling time and schedule view
Alternar entre vista da hora e do horário

btnAlarm_pressed

Esta função alterna o interruptor geral do alarme entre ligado e desligado. Se o interruptor estiver desligado, o horário do alarme fica desativado.

void btnAlarm_pressed(void) {
  if (btnAlarm.justPressed()) {
    isAlarmOn = !btnAlarm.getState();
    tft.setTextFont(4);
    tft.setTextSize(1);
    btnAlarm.drawSmoothButton(isAlarmOn, 1, TFT_DARKGREY, isAlarmOn ? "on" : "off");
  }
}
Alternar o interruptor do alarme

Inicialização dos Botões

A função initButtons() inicializa os botões no display.

void initButtons() {
  uint16_t w = 100;
  uint16_t h = 50;
  uint16_t y = tft.height() - h + 14;
  uint16_t x = tft.width() / 2;
  tft.setTextFont(4);
  tft.setTextSize(1);

  btnView.initButtonUL(x - w - 10, y, w, h, TFT_DARKGREY, TFT_DARKGREY, TFT_BLACK, "alarm", 1);
  btnView.setPressAction(btnView_pressed);
  btnView.drawSmoothButton(isTimeView, 1, TFT_BLACK);

  btnAlarm.initButtonUL(x + 10, y, w, h, TFT_DARKGREY, TFT_BLACK, TFT_DARKGREY, "off", 1);
  btnAlarm.setPressAction(btnAlarm_pressed);
  btnAlarm.drawSmoothButton(isAlarmOn, 1, TFT_BLACK);
}
Buttons for View and Alarm
Botões para Vista e Alarme

Tratamento dos Botões

A função handleButtons() verifica os pressionamentos dos botões e executa as ações correspondentes.

void handleButtons() {
  tft.setTextFont(4);
  uint8_t nBtns = sizeof(btns) / sizeof(btns[0]);
  uint16_t x = 0, y = 0;
  bool touched = tft.getTouch(&x, &y);
  for (uint8_t b = 0; b < nBtns; b++) {
    if (touched) {
      if (btns[b]->contains(x, y)) {
        btns[b]->press(true);
        btns[b]->pressAction();
      }
    } else {
      btns[b]->press(false);
      btns[b]->releaseAction();
    }
  }
}

Função de Desenho da Vista

A função drawView() atualiza o display com base no modo de vista atual (hora ou horário).

void drawView() {
  tft.fillScreen(TFT_BLACK);
  initButtons();
  if (isTimeView) {
    drawTime();
  } else {
    drawSchedule();
    drawLabels();
  }
}

Função de Atualização da Vista

A função updateView() refresca o display com base na interação do utilizador.

void updateView() {
  static uint16_t x = 0, y = 0;
  if (isTimeView) {
    drawTime();
  } else {
    if (tft.getTouch(&x, &y)) {
      selectSlot(x, y);
    }
  }
}

Função Setup

A função setup() inicializa o Wi-Fi, SNTP, display e configura o GPIO25 como saída. Vamos ligar um LED ou um Relé a este pino e controlá-los via o horário do alarme.

void setup() {
  initWiFi();
  initSNTP();
  initDisplay();
  pinMode(alarmPin, OUTPUT);
  drawView();
}

Função Loop

Finalmente, a função loop() atualiza continuamente a vista, trata os pressionamentos dos botões e controla o pino do alarme com base no horário.

void loop() {
  updateView();
  handleButtons();
  digitalWrite(alarmPin, isOn() ? HIGH : LOW);
  delay(100);
}

E este é o código completo para um interruptor temporizador digital. Nas próximas duas secções, mostro-te como ligar um LED ou um Relé ao CrowPanel Display, que depois podemos controlar via o horário do alarme.

Ligar um LED ao Interruptor Temporizador Digital

Para testar se o código do nosso interruptor temporizador digital funciona, ligamos um LED ao GPIO25 da seguinte forma:

LED connected to CrowPanel 3.5" ESP32 Display
LED ligado ao CrowPanel 3.5″ ESP32 Display

Certifica-te de que o pino curto (cátodo) do LED está ligado ao terra (fio preto) e o pino longo via resistor ao GPIO25 (fio amarelo). O CrowPanel vem com um conector codificado por cores, por isso se seguires as cores não te vais enganar. A foto seguinte mostra a ligação real com os cabos codificados por cores:

Wiring of LED with Crowpanel Display
Ligação do LED com o CrowPanel Display

Para testar o interruptor temporizador digital, lê a hora e data atuais, por exemplo 12:37:00 numa quarta-feira, e depois define o intervalo correspondente no horário do alarme:

Schedule set for 12:00-13:00 on Wednesday
Horário definido para 12:00-13:00 na quarta-feira

Se o botão geral do alarme estiver ligado, o LED deverá estar aceso. Podes desligá-lo alternando o botão geral do alarme ou alterando o horário.

Ligar um Relé ao Interruptor Temporizador Digital

Se quiseres controlar luzes em casa ou a bomba do aspersor, vais precisar de um relé. O diagrama seguinte mostra como ligar isso:

Relé ligado ao CrowPanel 3.5″ ESP32 Display

VCC (cabo vermelho) e GND (cabo preto) estão ligados aos pinos correspondentes num módulo de relé. O GPIO25 está ligado ao pino de entrada (cabo amarelo) do relé.

O dispositivo de alta tensão e alta corrente que queres controlar está ligado aos terminais COM e NC (normalmente aberto) do módulo de relé.Tem muito cuidado ao operar com tensões superiores a 50V!Deves garantir que o módulo de relé é adequado para a tensão e corrente que queres comutar. Módulos de relé típicos (AITRIP, HiLetgo) podem comutar 220V a 4A até 10A.

A foto abaixo mostra a ligação do módulo de relé com o CrowPanel Display:

Wiring of Relay with Crowpanel Display
Ligação do Relé com o CrowPanel Display

Conclusão

Neste tutorial aprendeste a construir um Interruptor Temporizador Digital com o CrowPanel 3.5″ ESP32 Display. Comparado com muitos produtos comerciais disponíveis, o nosso Interruptor Temporizador Digital tem sempre a hora precisa e, devido ao ecrã grande, torna muito fácil definir e alterar rapidamente os horários.

Existem muitas possíveis extensões para este projeto. Podes querer definir horários com resolução de minutos ou segundos. Ou talvez queiras programar um horário para um mês ou ano inteiro.

Em vez de ligar um relé, também poderias usar Bluetooth, MQTT ou outros protocolos para controlar dispositivos sem fios. O código acima oferece-te uma base para isso.

Se tiveres alguma dúvida, sente-te à vontade para deixá-la na secção de comentários.

Boas experiências a fazer tinkering 😉

Links

Aqui estão alguns links que achei úteis para escrever este post.