Skip to Content

Relógio Analógico em Ecrã e-Paper

Relógio Analógico em Ecrã e-Paper

Neste tutorial vamos construir um relógio analógico usando um ecrã e-Paper de 4,2″ e um ESP32. O relógio irá sincronizar a hora com um servidor de tempo na internet (servidor SNTP). Além disso, o ESP32 e o ecrã vão entrar em modo de suspensão entre atualizações do ecrã para aumentar o tempo de funcionamento do relógio com alimentação por bateria.

Vamos começar com os componentes necessários.

Componentes Necessários

Estou a usar o ES32 lite como microprocessador, pois é barato e tem uma interface de carregamento de bateria, o que permite alimentar o relógio com uma bateria LiPo. Qualquer outro ESP32 ou ESP8266 com memória suficiente também serve, mas de preferência escolha um com interface de carregamento de bateria.

Ecrã e-Paper de 4,2″

ESP32 lite Lolin32

ESP32 lite

USB data cable

Cabo USB de Dados

Dupont wire set

Conjunto de fios Dupont

Half_breadboard56a

Breadboard

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.

Ecrã e-Paper

O ecrã e-Paper utilizado neste projeto é um módulo de 4,2″ com resolução de 400×300 píxeis, 4 níveis de cinzento, tempo de atualização parcial de 0,4 segundos e um controlador integrado com interface SPI.

Frente e verso do módulo e-Paper de 4,2″

Repare que o módulo tem um pequeno jumper/switch na parte de trás para alternar entre SPI de 4 fios e SPI de 3 fios. Vamos usar o SPI de 4 fios por defeito. Por isso, não é necessário alterar nada.

Parte de trás do módulo do ecrã com interface SPI e controlador

O módulo do ecrã funciona a 3,3V ou 5V, tem um consumo em modo de suspensão muito baixo de 0,01µA e consome apenas cerca de 26,4mW durante a atualização. Para mais informações sobre ecrãs e-Paper em geral, veja os seguintes dois tutoriais: Interfacing Arduino To An E-ink Display e Weather Station on e-Paper Display .

Ligar e Testar o e-Paper

Primeiro, vamos ligar e testar o funcionamento do e-Paper. A imagem seguinte mostra toda a cablagem para alimentação e SPI.

Connecting 4.2" e-Paper to ESP32 via SPI
Ligar o e-Paper de 4,2″ ao ESP32 via SPI

Abaixo está uma tabela com todas as ligações para sua conveniência. Note que pode alimentar o ecrã com 3,3V ou 5V, mas o ESP32-lite só tem saída de 3,3V e as linhas de dados SPI devem ser 3,3V!

Ecrã e-Paper ESP32 lite
CS/SS 5
SCL/SCK 18
SDA/DIN/MOSI 23
BUSY 15
RES/RST 2
DC 0
VCC 3.3V
GND G

Pode usar uma breadboard para fazer todas as ligações. Mas eu liguei o ecrã diretamente ao ESP32 com fios Dupont e também adicionei uma bateria LiPo para testar o funcionamento do relógio a bateria. A imagem abaixo mostra como ficou a minha montagem.

ESP32 wired with e-Paper and LiPo battery
ESP32 ligado ao e-Paper e bateria LiPo

Instalar a biblioteca GxEPD2

Antes de podermos desenhar ou escrever no ecrã e-Paper, precisamos de instalar duas bibliotecas. A Adafruit_GFX biblioteca gráfica, que fornece um conjunto comum de primitivas gráficas (texto, pontos, linhas, círculos, etc.). E a GxEPD2 biblioteca, que fornece o driver gráfico para o ecrã e-Paper.

Basta instalar as bibliotecas da forma habitual. Depois de instaladas, devem aparecer no Library Manager como mostrado abaixo.

Adafruit_GFX and GxEPD2 libraries in Library Manager
Bibliotecas Adafruit_GFX e GxEPD2 no Library Manager

Código de teste

Depois de instalar a biblioteca, execute o seguinte código de teste para garantir que tudo está a funcionar.

#include "GxEPD2_BW.h"

//CS(SS)=5, SCL(SCK)=18, SDA(MOSI)=23, BUSY=15, RES(RST)=2, DC=0
GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> epd(GxEPD2_420_GDEY042T81(5, 0, 2, 15));

void setup() {
  epd.init(115200, true, 50, false);
  epd.setRotation(1);
  epd.setTextColor(GxEPD_BLACK);   
  epd.setTextSize(2);
  epd.setFullWindow();

  epd.fillScreen(GxEPD_WHITE);     
  epd.setCursor(90, 190);
  epd.print("Makerguides");  
  epd.display();
  epd.hibernate();
}

void loop() {}

O código de teste imprime o texto “Makerguides” e, após algum piscar do ecrã (atualização completa), deve vê-lo no seu ecrã como mostrado abaixo:

Test output on 4.2" e-Paper display
Resultado do teste no ecrã e-Paper de 4,2″

Se não aparecer, algo está errado. Provavelmente o ecrã não está corretamente ligado ou foi escolhido o driver errado. A linha de código crítica é esta, onde especificamos o driver do ecrã de 4,2″:

GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> epd(GxEPD2_420_GDEY042T81(5, 0, 2, 15));

O Readme da GxEPD2 biblioteca lista todos os ecrãs suportados e pode encontrar os detalhes nos ficheiros header, por exemplo GxEPD2.h . Procure o driver específico para o seu ecrã. Pode ser necessário algum teste e erro.

Código para um Relógio Analógico em e-Paper

O código seguinte é o código completo para um relógio analógico que sincroniza automaticamente a hora com um servidor de tempo na internet (SNTP) e mostra a hora e a data num ecrã e-Paper de 4,2″. Entre atualizações, o ecrã e o ESP32 entram em modo de suspensão para poupar bateria. Dê uma vista de olhos rápida ao código e depois vamos analisar os detalhes.

#include "GxEPD2_BW.h"
#include "Fonts/FreeSans9pt7b.h"
#include "Fonts/FreeSansBold9pt7b.h"
#include "WiFi.h"
#include "esp_sntp.h"

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

const char* DAYSTR[] = { 
  "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 
};

// W, H flipped due to setRotation(1)
const int H = GxEPD2_420_GDEY042T81::WIDTH;
const int W = GxEPD2_420_GDEY042T81::HEIGHT;

const int CW = W / 2;
const int CH = H / 2;
const int R = min(W, H) / 2;

const uint16_t WHITE = GxEPD_WHITE;
const uint16_t BLACK = GxEPD_BLACK;

RTC_DATA_ATTR uint16_t wakeups = 0;
GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> epd(GxEPD2_420_GDEY042T81(5, 0, 2, 15));

void initDisplay() {
  bool initial = wakeups == 0;
  epd.init(115200, initial, 50, false);
  epd.setRotation(1);
  epd.setTextSize(1);
  epd.setTextColor(BLACK);
}

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

void syncTime() {
  if (wakeups % 50 == 0) {
    WiFi.begin(SSID, PWD);
    while (WiFi.status() != WL_CONNECTED)
      ;
    configTzTime(TIMEZONE, "pool.ntp.org");
  }
}

void printAt(int16_t x, int16_t y, const char* text) {
  int16_t x1, y1;
  uint16_t w, h;
  epd.getTextBounds(text, x, y, &x1, &y1, &w, &h);
  epd.setCursor(x - w / 2, y + h / 2);
  epd.print(text);
}

void printfAt(int16_t x, int16_t y, const char* format, ...) {
  static char buff[64];
  va_list args;
  va_start(args, format);
  vsnprintf(buff, 64, format, args);
  printAt(x, y, buff);
}

void polar2cart(float x, float y, float r, float alpha, int& cx, int& cy) {
  alpha = alpha * TWO_PI / 360;
  cx = int(x + r * sin(alpha));
  cy = int(y - r * cos(alpha));
}

void drawClockFace() {
  int cx, cy;
  epd.setFont(&FreeSansBold9pt7b);
  epd.drawCircle(CW, CH, R - 2, BLACK);
  epd.fillCircle(CW, CH, 8, BLACK);

  for (int h = 1; h <= 12; h++) {
    float alpha = 360.0 * h / 12;
    polar2cart(CW, CH, R - 20, alpha, cx, cy);
    printfAt(cx, cy, "%d", h);
    polar2cart(CW, CH, R - 40, alpha, cx, cy);
    epd.fillCircle(cx, cy, 3, BLACK);
  }

  for (int m = 1; m <= 12 * 5; m++) {
    float alpha = 360.0 * m / (12 * 5);
    polar2cart(CW, CH, R - 40, alpha, cx, cy);
    epd.fillCircle(cx, cy, 2, BLACK);
  }
}

void drawTriangle(float alpha, int width, int len) {
  int x0, y0, x1, y1, x2, y2;
  polar2cart(CW, CH, len, alpha, x2, y2);
  polar2cart(CW, CH, width, alpha - 90, x1, y1);
  polar2cart(CW, CH, width, alpha + 90, x0, y0);
  epd.drawTriangle(x0, y0, x1, y1, x2, y2, BLACK);
}

void drawClockHands() {
  struct tm t;
  getLocalTime(&t);

  float alphaM = 360 * (t.tm_min / 60.0);
  float alphaH = 30 * (t.tm_hour % 12);

  drawTriangle(alphaM, 8, R - 50);
  drawTriangle(alphaH, 8, R - 65);
}

void drawDateDay() {
  struct tm t;
  getLocalTime(&t);
  
  epd.setFont(&FreeSans9pt7b);
  printfAt(CW, CH+R/3, "%02d-%02d-%02d",          
          t.tm_mday, t.tm_mon + 1, t.tm_year -100);
  printfAt(CW, CH-R/3, "%s", DAYSTR[t.tm_wday]);           
}

void drawClock(const void* pv) {
  if (wakeups % 120 == 0) {
    epd.setFullWindow();
  } else {
    epd.setPartialWindow(0, 0, W, H);
  }
  epd.fillScreen(WHITE);
  drawClockFace();
  drawClockHands();
  drawDateDay();
}

void setup() {
  initDisplay();
  setTimezone();
  syncTime();

  epd.drawPaged(drawClock, 0);
  epd.hibernate();

  wakeups = (wakeups + 1) % 1000;

  esp_sleep_enable_timer_wakeup(30 * 1000 * 1000);
  esp_deep_sleep_start();
}

void loop() {
}

Se carregar o código no seu ESP32, deve ver o seguinte relógio apresentado:

Analog Clock on a 4.2" e-Paper Display
Relógio Analógico num ecrã e-Paper de 4,2″

Mostra o mostrador do relógio, os dois ponteiros e o dia e data atuais.

Bibliotecas

Começamos por incluir o ficheiro header GxEPD2_BW.h para o ecrã e-Paper preto e branco (BW). Se tiver um ecrã de 3 cores, deve incluir GxEPD2_3C.h , ou GxEPD2_4C.h para um ecrã de 4 cores, e GxEPD2_7C.h para um ecrã de 7 cores.

#include "GxEPD2_BW.h"
#include "Fonts/FreeSans9pt7b.h"
#include "Fonts/FreeSansBold9pt7b.h"

Incluímos também dois ficheiros para as fontes usadas para mostrar os rótulos do relógio e a data. Os rótulos do relógio estão numa fonte negrito ( FreeSansBold9pt7b ) e a data está numa fonte mais fina ( FreeSans9pt7b ) – veja a imagem do relógio acima. Pode encontrar uma visão geral das AdaFruit GFX fonts aqui.

Por fim, incluímos as bibliotecas WiFi.h e esp_snt.h , que vamos precisar para sincronizar o relógio com um servidor de tempo SNTP via Wi-Fi.

#include "WiFi.h"
#include "esp_sntp.h"

Constantes

De seguida, definimos algumas constantes. Mais importante, terá de definir o SSID e PASSWORD da sua rede WiFi, e o TIMEZONE onde vive.

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

A especificação de fuso horário acima ” AEST-10AEDT,M10.1.0,M4.1.0/3 ” é para a Austrália, que corresponde ao horário padrão da Austrália Oriental (AEST) com ajustes para horário de verão.

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

  • AEST: Horário Padrão da Austrália Oriental
  • -10: Desvio UTC de 10 horas à frente do Tempo Universal Coordenado (UTC)
  • AEDT: Horário de Verão da Austrália Oriental
  • M10.1.0: Mudança para horário de verão ocorre no 1º domingo de outubro
  • M4.1.0/3: Mudança 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, consulte o Posix Timezones Database . Basta copiar e colar a string que encontrar lá e alterar a constante TIMEZONE em conformidade.

Além da data, também queremos mostrar o nome do dia atual, por exemplo, quinta-feira. A constante DAYSTR define os nomes dos dias. Sinta-se à vontade para mudar o idioma ou escolher nomes mais curtos, mas mantenha a ordem.

const char* DAYSTR[] = { 
  "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 
};

A seguir, redefinimos constantes para a largura W e altura H do ecrã. Isto é apenas por conveniência. Note que a largura e altura estão trocadas, pois rodamos o ecrã ( setRotation(1) ) na função initDisplay .

const int H = GxEPD2_420_GDEY042T81::WIDTH;
const int W = GxEPD2_420_GDEY042T81::HEIGHT;

Por fim, definimos constantes para a posição central ( CW , CH ) no ecrã, o raio máximo R de um círculo no ecrã, e nomes mais curtos para as cores preto e branco.

const int CW = W / 2;
const int CH = H / 2;
const int R = min(W, H) / 2;

const uint16_t WHITE = GxEPD_WHITE;
const uint16_t BLACK = GxEPD_BLACK;

Variáveis e Objetos

Depois das constantes, definimos uma variável global para contar os wakeups e o objeto do ecrã.

RTC_DATA_ATTR uint16_t wakeups = 0;
GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> epd(GxEPD2_420_GDEY042T81(5, 0, 2, 15));

A variável wakeups é incrementada sempre que o ESP32 acorda do deep-sleep. É armazenada em RTC memory para que mantenha o valor mesmo quando o ESP32 entra em modo deep-sleep. Usamo-la para decidir quando fazer uma atualização total ou parcial do ecrã, e com que frequência sincronizar a hora.

O objeto epd representa o ecrã ( e p aper d isplay). Esta definição de objeto deve corresponder ao tipo de ecrã e-Paper que tem. Aqui temos um ecrã de 4,2 polegadas (= _420). Veja o Readme da GxEPD2 biblioteca e o ficheiro GxEPD2.h para os ecrãs suportados.

Função initDisplay

A função initDisplay inicializa o ecrã, define a orientação para horizontal, o tamanho do texto para 1 e a cor do texto para preto.

void initDisplay() {
  bool initial = wakeups == 0;
  epd.init(115200, initial, 50, false);
  epd.setRotation(1);
  epd.setTextSize(1);
  epd.setTextColor(BLACK);
}

epd.init() normalmente provoca uma atualização total do ecrã quando executada. No entanto, queremos uma atualização total apenas após reiniciar o ESP32 e uma atualização parcial ao acordar do deep sleep. Para isso, definimos o parâmetro initial da função epd.init() para true quando wakeups == 0 , ou seja, após um reset, e caso contrário initial é false, para uma atualização parcial.

Função setTimezone

A função setTimezone define o TIMEZONE . Temos de a chamar sempre na função setup, pois após deep-sleep ou reset a informação do fuso horário é perdida.

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

Função syncTime

A função syncTime cria uma ligação WiFi e depois sincroniza o relógio interno do ESP32 com o servidor SNTP ” pool.ntp.org ” chamando configTzTime() .

Pode especificar outros servidores SNTP, ou até vários, para se ligar. Veja o tutorial How to synchronize ESP32 clock with SNTP server para mais informações.

void syncTime() {
  if (wakeups % 50 == 0) {
    WiFi.begin(SSID, PWD);
    while (WiFi.status() != WL_CONNECTED)
      ;
    configTzTime(TIMEZONE, "pool.ntp.org");
  }
}

No entanto, queremos sincronizar a hora apenas de vez em quando. Como o ESP32 está configurado para dormir durante 30 segundos (ver função loop ), a expressão wakeups % 50 == 0 garante que a hora só é sincronizada a cada 25 minutos (30seg * 50 / 60seg).

Pode alterar isto, mas tenha em conta que sincronizações mais frequentes consomem mais bateria (o Wi-Fi consome muita energia). Sincronizações menos frequentes, por outro lado, fazem com que o relógio reaja mais lentamente à mudança para o horário de verão.

Funções printAt

As funções printAt mostram texto centrado nas coordenadas especificadas do ecrã.

void printAt(int16_t x, int16_t y, const char* text) {
  int16_t x1, y1;
  uint16_t w, h;
  epd.getTextBounds(text, x, y, &x1, &y1, &w, &h);
  epd.setCursor(x - w / 2, y + h / 2);
  epd.print(text);
}

void printfAt(int16_t x, int16_t y, const char* format, ...) {
  static char buff[64];
  va_list args;
  va_start(args, format);
  vsnprintf(buff, 64, format, args);
  printAt(x, y, buff);
}

Enquanto printAt() apenas imprime texto simples, a função printfAt() funciona como printf e permite-lhe fornecer um especificador de formato, por exemplo printfAt(x, y, "%02d-%02d-%02d", m, h, y);

Função polar2cart

A função polar2cart converte polar coordinates dados em coordenadas polares, dados por um raio r e um ângulo alpha em cartesian coordinates cx, cy .

void polar2cart(float x, float y, float r, float alpha, int& cx, int& cy) {
  alpha = alpha * TWO_PI / 360;
  cx = int(x + r * sin(alpha));
  cy = int(y - r * cos(alpha));
}

Esta função é muito útil, pois dados de tempo como 8:15 são essencialmente coordenadas polares no relógio e temos de as converter para o sistema de coordenadas cartesianas usado pelo ecrã.

Função drawClockFace

A função drawClockFace() , como o nome indica, desenha o mostrador do relógio. Isto inclui a moldura, as marcas dos minutos e os rótulos e marcas das horas. Como pode ver, faz bom uso da função polar2cart() que discutimos antes.

void drawClockFace() {
  int cx, cy;
  epd.setFont(&FreeSansBold9pt7b);

  // Frame
  epd.drawCircle(CW, CH, R - 2, BLACK);
  epd.fillCircle(CW, CH, 8, BLACK);

  // Hour ticks and labels
  for (int h = 1; h <= 12; h++) {
    float alpha = 360.0 * h / 12;
    polar2cart(CW, CH, R - 20, alpha, cx, cy);
    printfAt(cx, cy, "%d", h);
    polar2cart(CW, CH, R - 40, alpha, cx, cy);
    epd.fillCircle(cx, cy, 3, BLACK);
  }

  // Minute ticks
  for (int m = 1; m <= 12 * 5; m++) {
    float alpha = 360.0 * m / (12 * 5);
    polar2cart(CW, CH, R - 40, alpha, cx, cy);
    epd.fillCircle(cx, cy, 2, BLACK);
  }
}

Função drawTriangle

A função drawTriangle() desenha um triângulo, mas em coordenadas polares. A base do triângulo está no centro do relógio e o triângulo está inclinado com o ângulo alpha . width é a largura da base e len é o comprimento do triângulo. Esta função é usada para desenhar os ponteiros do relógio.

void drawTriangle(float alpha, int width, int len) {
  int x0, y0, x1, y1, x2, y2;
  polar2cart(CW, CH, len, alpha, x2, y2);
  polar2cart(CW, CH, width, alpha - 90, x1, y1);
  polar2cart(CW, CH, width, alpha + 90, x0, y0);
  epd.drawTriangle(x0, y0, x1, y1, x2, y2, BLACK);
}

Pode mudar de epd.drawTriangle() para epd.fillTriangle() , se preferir ponteiros preenchidos. São mais fáceis de ver, mas vão tapar o dia e a data atuais.

Função drawClockHands

O desenho real dos ponteiros do relógio é feito pela função drawClockHands() . Obtém a hora local atual, converte minutos e horas em ângulos e depois usa drawTriangle() para desenhar os ponteiros.

void drawClockHands() {
  struct tm t;
  getLocalTime(&t);

  float alphaM = 360 * (t.tm_min / 60.0);
  float alphaH = 30 * (t.tm_hour % 12);

  drawTriangle(alphaM, 8, R - 50);
  drawTriangle(alphaH, 8, R - 65);
}

Função drawDateDay

Além da hora mostrada pelos ponteiros, também quis mostrar a data e o dia da semana atuais. A função drawDateDay() obtém a hora e data locais e depois imprime o nome do dia, por exemplo quinta-feira, no topo do mostrador e a data, por exemplo 05-09-24, em baixo. Pode facilmente mudar o especificador de formato para adaptar a data ao seu país.

void drawDateDay() {
  struct tm t;
  getLocalTime(&t);
  
  epd.setFont(&FreeSans9pt7b);
  printfAt(CW, CH+R/3, "%02d-%02d-%02d",          
          t.tm_mday, t.tm_mon + 1, t.tm_year -100);
  printfAt(CW, CH-R/3, "%s", DAYSTR[t.tm_wday]);           
}

Função drawClock

A função drawClock() junta tudo. Limpa o ecrã e depois desenha o mostrador, os ponteiros e a data.

void drawClock(const void* pv) {
  if (wakeups % 120 == 0) {
    epd.setFullWindow();
  } else {
    epd.setPartialWindow(0, 0, W, H);
  }
  epd.fillScreen(WHITE);
  drawClockFace();
  drawClockHands();
  drawDateDay();
}

Dependendo do contador wakeups é feita uma atualização total ou parcial do ecrã. Uma atualização total é lenta (2-4 segundos) e provoca muito piscar do ecrã. Uma atualização parcial é muito mais rápida (<0,5 seg) e sem piscar.

No entanto, uma atualização parcial deixa uma imagem residual ténue do conteúdo antigo. Se olhar com atenção para a imagem abaixo, pode ver as imagens residuais do ponteiro dos minutos enquanto se moveu do 5 para o 10.

After-image on e-Paper Display
Imagem residual no ecrã e-Paper

Para remover as imagens residuais, ocasionalmente queremos fazer uma atualização total do ecrã. Na função drawClock() fazemos isto quando o contador wakeup atinge 120: wakeups % 120 == 0 . Como o tempo de deep-sleep está definido para 30 segundos, isto significa uma atualização total a cada 30seg*120/60seg = 60 minutos.

Funções setup e loop

Por fim, temos as habituais funções setup e loop . A função loop está vazia, pois o ESP32 entra em deep-sleep no final da função setup e a função loop nunca é executada.

void setup() {
  initDisplay();
  setTimezone();
  syncTime();

  epd.drawPaged(drawClock, 0);
  epd.hibernate();

  wakeups = (wakeups + 1) % 1000;

  esp_sleep_enable_timer_wakeup(30 * 1000 * 1000);
  esp_deep_sleep_start();
}

void loop() {
}

A função setup começa por inicializar o ecrã, definir o fuso horário e (talvez) sincronizar a hora (dependendo do contador de wakeup).

Depois chamamos drawPaged para executar as funções drawClock e depois hibernamos o ecrã para poupar energia. Se precisar de mais detalhes, veja o tutorial Partial Refresh of e-Paper Display , onde explicamos a função drawPaged em mais detalhe.

Antes de colocar o ESP32 durante 30 segundos (30 * 1000 * 1000 microssegundos) em deep-sleep, incrementamos o contador wakeup e calculamos o módulo 1000 para evitar overflow do contador.

E é isto! Com este código tem um relógio analógico sempre preciso e que pode funcionar a bateria.

Conclusão

Neste tutorial aprendeu a construir um relógio analógico num ecrã e-Paper de 4,2″ que sincroniza a hora com um servidor SNTP e mostra sempre a hora e data corretas. O relógio utiliza o modo deep-sleep do ESP32 para reduzir o consumo de energia e aumentar o tempo de funcionamento com bateria.

Se usar o ESP32 LOLIN Lite sugerido, ligar uma bateria LiPo recarregável é simples e pode usar um MAX1704X para monitorizar os níveis de carga.

Por fim, se preferir um relógio digital em vez de um analógico, veja o nosso tutorial Digital Clock on e-Paper Display .

Boas experiências ; )