Skip to Content

Reloj digital en pantalla redonda CrowPanel 1.28″

Reloj digital en pantalla redonda CrowPanel 1.28″

En este tutorial aprenderás cómo implementar un reloj digital en un CrowPanel 1.28″ Round Display con un GC9A01 TFT usando la librería Adafruit_GC9A01A. El código de ejemplo para el reloj también te mostrará cómo usar la pantalla táctil CST816D, el zumbador, el motor de vibración, el reloj en tiempo real (RTC) BM8563, el modo de suspensión profunda y la sincronización de la hora por internet.

Partes necesarias

Obviamente, necesitarás el módulo CrowPanel ESP32 1.28-inch Round Display para este proyecto. Aparte de un cable USB-C, que normalmente viene con el módulo de pantalla, no se requieren otras piezas.

CrowPanel ESP32 1.28″ Round Display

Características del CrowPanel 1.28″ Round Display

El módulo CrowPanel 1.28″ está compuesto por una pantalla TFT redonda (GC9A01) con resolución de 240×240 píxeles, una pantalla táctil capacitiva (CST816D) y un ESP32-C3 integrado. La imagen a continuación muestra el frente del módulo:

Front of CrowPanel 1.28" Round Display
Frontal del CrowPanel 1.28″ Round Display (source)

Además del ESP32, el módulo contiene un zumbador, motor de vibración, un reloj en tiempo real (BM8563) con batería de respaldo, soporte para batería LiPo con carga y varios botones. La siguiente imagen muestra la parte trasera del módulo, donde se pueden ver la mayoría de las piezas:

Back of CrowPanel 1.28" Round Display
Parte trasera del CrowPanel 1.28″ Round Display (source)

Ten en cuenta que también hay un pequeño encoder con soporte de botón (a la izquierda), que esencialmente te permite añadir una corona para ajustar la hora como en un reloj mecánico de pulsera, siempre que escribas el código para ello. Pero como es totalmente programable, también podrías controlar todo tipo de funciones o interacciones de la interfaz de usuario con él.

Para la programación (y alimentación) hay un puerto USB-C y botones de Reset y Boot en la parte trasera. Si se conecta una batería LiPo, se cargará a través del puerto USB-C.

La siguiente tabla resume las principales características del módulo de pantalla:

Chip principalESP32-C3
ProcesadorProcesador RISC-V de 32 bits, núcleo único, hasta 160 MHz
Memoria384 KB ROM, 400 KB SRAM (16 KB para caché), 8 KB SRAM en RTC
Tamaño1.28 pulgadas
Resolución240*240
Interfaz de señalSPI
Tipo de tactoTáctil capacitivo
Tipo de panelTFT LCD, panel IPS
Profundidad de color262K
Brillo350 cd/m²
Ángulo de visión178°
BotonesBotón Reset, Botón Boot, Botón personalizado
InterfazInterfaz Tipo-C, interfaz para batería
EncoderCon función de botón, sin pin (tamaño del pin: 0.8mm*0.8mm)
AlimentaciónMódulo: DC5V, chip principal: 3.3V
Área activa32.51*32.51mm
Dimensiones42*42*9.8mm
Peso neto15g

Reloj digital en CrowPanel 1.28″ Round Display

En esta sección implementaremos un reloj digital en el CrowPanel 1.28″ Round Display. El reloj mostrará el nombre del día actual, la hora y la fecha. También podremos alternar entre formato 24h y 12h tocando un botón en la pantalla. La imagen a continuación muestra la carátula del reloj con sus características.

Clock face with features
Carátula del reloj con características

Además, el reloj puede cambiar a modo de suspensión profunda presionando el botón físico en el lado derecho (parte trasera) del módulo. Una segunda pulsación despertará el reloj. Esto es esencial si quieres que el reloj funcione con batería.

También emitiremos un tono cada hora y proporcionaremos retroalimentación táctil al presionar el botón táctil activando el motor de vibración.

Finalmente, el reloj sincronizará automáticamente el reloj interno (RTC) con un servidor de tiempo por internet (SNTP) si hay WiFi disponible.

Con esto habremos explorado la mayoría de las características técnicas del CrowPanel Display, excepto Bluetooth y el encoder.

Código para reloj digital en pantalla GC9A01

La pantalla del CrowPanel 1.28″ es una pantalla GC9A01 con una pantalla táctil CST816D. Usaremos la librería Adafruit_GC9A01A para dibujar en la pantalla, ya que es mucho más sencilla que la librería LVGL que se usa para el demo code que viene con el CrowPanel.

Sin embargo, si quieres implementar una interfaz de usuario más compleja con menús desplegables y otras funciones, la librería LVGL es la opción adecuada.

A continuación está el código para el reloj digital. Es un código extenso y te sugiero que lo revises para obtener una visión general. Luego discutiremos los detalles.

#include "WiFi.h"
#include "Adafruit_GFX.h"
#include "Adafruit_GC9A01A.h"
#include "I2C_BM8563.h"
#include "CST816D.h"

// I2C
#define SDA 4
#define SCL 5
#define PI4IO_I2C_ADDR 0x43

// TFT display
#define TFT_CS 10
#define TFT_DC 2
#define TFT_MOSI 7
#define TFT_SCLK 6
#define TFT_RST 4
#define TFT_BKL 2

#define DW 240
#define DH 240

// Touch panel
#define TP_INT 0
#define TP_RST 3


#define BUTTON 1
#define BUZZER 3
#define MOTOR 0

const char* SSID = "SSID";
const char* PWD = "PASSWORD";

bool is_24h = true;
const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";
const char* DAYSTR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
const char* MONTHSTR[] = { "Jan", "Feb", "Mar", "Apr", "May",
                           "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

// touch button
const int bx = DW / 2;
const int by = DH - 30;
const int br = 10;

Adafruit_GC9A01A tft(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK);
I2C_BM8563 rtc(I2C_BM8563_DEFAULT_ADDRESS, Wire);
CST816D touch(SDA, SCL, TP_RST, TP_INT);


void init_ioex() {
  Wire.begin(SDA, SCL);
  Wire.beginTransmission(PI4IO_I2C_ADDR);
  Wire.write(0x01);  // test register
  Wire.endTransmission();
  Wire.requestFrom(PI4IO_I2C_ADDR, 1);
  uint8_t rxdata = Wire.read();

  Wire.beginTransmission(PI4IO_I2C_ADDR);
  Wire.write(0x03);                                                  
  Wire.write((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4));  
  Wire.endTransmission();

  Wire.beginTransmission(PI4IO_I2C_ADDR);
  Wire.write(0x07);                                                     
  Wire.write(~((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4))); 
  Wire.endTransmission();
}

void write_ioex(uint8_t pin_number, bool value) {
  Wire.beginTransmission(PI4IO_I2C_ADDR);
  Wire.write(0x05);  // test register
  Wire.endTransmission();
  Wire.requestFrom(PI4IO_I2C_ADDR, 1);
  uint8_t rxdata = Wire.read();

  Wire.beginTransmission(PI4IO_I2C_ADDR);
  Wire.write(0x05);  // Output register

  if (!value)
    Wire.write((~(1 << pin_number)) & rxdata);  
  else
    Wire.write((1 << pin_number) | rxdata);  
  Wire.endTransmission();

  Wire.beginTransmission(PI4IO_I2C_ADDR);
  Wire.write(0x05);  // test register
  Wire.endTransmission();
  Wire.requestFrom(PI4IO_I2C_ADDR, 1);
  rxdata = Wire.read();
}

void set_rtc_time() {
  rtc.begin();

  struct tm t;
  getLocalTime(&t);

  I2C_BM8563_TimeTypeDef ts;
  ts.hours = t.tm_hour;
  ts.minutes = t.tm_min;
  ts.seconds = t.tm_sec;
  rtc.setTime(&ts);

  I2C_BM8563_DateTypeDef ds;
  ds.weekDay = t.tm_wday;
  ds.month = t.tm_mon + 1;
  ds.date = t.tm_mday;
  ds.year = t.tm_year + 1900;
  rtc.setDate(&ds);
}

void sync_time() {
  WiFi.begin(SSID, PWD);
  for (int i = 0; (i < 5) && (WiFi.status() != WL_CONNECTED); i++)
    delay(100);
  if (WiFi.status() == WL_CONNECTED) {
    configTzTime(TIMEZONE, "pool.ntp.org");
    set_rtc_time();
  }
}

void display_time() {
  static char buf[16];
  static I2C_BM8563_DateTypeDef ds;
  static I2C_BM8563_TimeTypeDef ts;

  rtc.getDate(&ds);
  rtc.getTime(&ts);

  tft.setTextColor(GC9A01A_LIGHTGREY, GC9A01A_BLACK);
  tft.setCursor(80, 60);
  tft.setTextSize(4);
  tft.print(DAYSTR[ds.weekDay]);

  if (is_24h) {
    sprintf(buf, "%02d:%02d:%02d", ts.hours, ts.minutes, ts.seconds);
  } else {
    int display_hours = ts.hours % 12;
    display_hours = display_hours ? display_hours : 12;
    const char* period = ts.hours >= 12 ? "pm" : "am";
    sprintf(buf, "%02d:%02d %s", display_hours, ts.minutes,  period);
  }

  tft.setTextColor(is_24h ? GC9A01A_WHITE : GC9A01A_YELLOW, GC9A01A_BLACK);
  tft.setCursor(45, 110);
  tft.setTextSize(3);
  tft.print(buf);

  sprintf(buf, "%2d %s %04d", ds.date, MONTHSTR[ds.month], ds.year);
  tft.setTextColor(GC9A01A_WHITE, GC9A01A_BLACK);
  tft.setCursor(45, 150);
  tft.setTextSize(2);
  tft.print(buf);
}

void sound_hour() {
  static I2C_BM8563_TimeTypeDef ts;
  rtc.getTime(&ts);  
  if (ts.minutes == 0 && ts.seconds == 0) {
    tone(BUZZER, 1000);
    delay(50);
    tone(BUZZER, 0);
    delay(500);
  }
}

void check_touch() {
  uint8_t gesture;
  uint16_t x, y;
  bool touched = touch.getTouch(&x, &y, &gesture);
  if (touched) {
    if (abs(x - bx) < 2*br && abs(y - by) < 2*br) {
      write_ioex(MOTOR, true);
      delay(50);
      write_ioex(MOTOR, false);      
      is_24h = !is_24h;
      display_touch();
      delay(500);
    }
  }
}

void display_touch() {
  if (is_24h) {
    tft.fillCircle(bx, by, br, GC9A01A_BLACK);
    tft.drawCircle(bx, by, br, GC9A01A_DARKGREY);
  } else {
    tft.fillCircle(bx, by, br, GC9A01A_DARKGREY);
  }
}

void check_button() {
  if (digitalRead(BUTTON) == LOW) {
    tft.fillScreen(GC9A01A_BLACK);
    write_ioex(TFT_BKL, false);  // Switch off TFT backlight
    delay(300);
    esp_deep_sleep_start();
  }
}

void init_deep_sleep() {
  pinMode(BUTTON, INPUT);
  esp_deep_sleep_enable_gpio_wakeup(1ULL << BUTTON, ESP_GPIO_WAKEUP_GPIO_LOW);
}

void init_buzzer() {
  pinMode(BUZZER, OUTPUT);
  digitalWrite(BUZZER, LOW);
}

void init_tft() {
  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(GC9A01A_BLACK);
  display_touch();
}

void set_ioex_pins() {
  write_ioex(TFT_RST, true);  // TFT Reset
  write_ioex(TP_RST, true);  // Touch panel reset
  write_ioex(TFT_BKL, true);  // TFT backlight
}

void setup() {
  Serial.begin(115200);  
  init_ioex();
  set_ioex_pins();    
  touch.begin();
  init_buzzer();
  init_deep_sleep();
  sync_time();  
  init_tft();
}

void loop() {
  static unsigned long st = millis();

  if (millis() - st > 300) {
    display_time();
    sound_hour();
    st = millis();
  }

  check_touch();
  check_button();
  yield();
}

Vamos a desglosar el código en sus componentes para una mejor comprensión.

Librerías

El código comienza incluyendo las librerías necesarias para WiFi, gráficos, control de pantalla, reloj en tiempo real (RTC) y funcionalidad del panel táctil.

#include "WiFi.h"
#include "Adafruit_GFX.h"
#include "Adafruit_GC9A01A.h"
#include "I2C_BM8563.h"
#include "CST816D.h"

Tendrás que instalar la librería Adafruit_GC9A01A. Solo abre el Library Manager, busca «Adafruit_GC9A01A» y presiona «INSTALL». La imagen a continuación muestra cómo debería verse una vez instalada:

Adafruit_GC9A01A Library installed
Librería Adafruit_GC9A01A instalada

Además, necesitarás los archivos para el reloj en tiempo real I2C_BM8563 y la pantalla táctil CST816D. Descarga el Arduino Demo Code, descomprímelo y ve a la carpeta ESP32_1.28_Arduino_Demo. Debería contener las siguientes subcarpetas:

CrowPanel 1.28" Round Display demo code folder
Carpeta de código demo para CrowPanel 1.28″ Round Display

En las subcarpetas LvglWidgets y RTC encontrarás los archivos necesarios. Cópialos en la carpeta del proyecto para tu código de reloj digital. El contenido de la carpeta del proyecto debería verse así:

Digital Clock project folder
Carpeta del proyecto Reloj Digital

Como puedes ver, nombré el sketch de Arduino «CrowPanel-Digital-Clock-1-28-Inch.ino» y como el nombre de la carpeta debe coincidir con el sketch, es «CrowPanel-Digital-Clock-1-28-Inch». Pero puedes nombrarlo diferente, solo asegúrate que el nombre del sketch y la carpeta coincidan.

Sin embargo, para simplificar, también comprimí mi proyecto y puedes descargarlo usando el siguiente enlace:

Constantes

Luego definimos constantes para la comunicación I2C, pines de la pantalla TFT y pines del panel táctil.

#define SDA 4
#define SCL 5
#define PI4IO_I2C_ADDR 0x43
#define TFT_CS 10
#define TFT_DC 2
#define TFT_MOSI 7
#define TFT_SCLK 6
#define TFT_RST 4
#define TFT_BKL 2

#define DW 240
#define DH 240

// Touch panel
#define TP_INT 0
#define TP_RST 3

#define BUTTON 1
#define BUZZER 3
#define MOTOR 0

Puedes encontrar estas definiciones de pines en el código de ejemplo y en el Schematics of the CrowPanel 1.28″ Display. Especialmente interesante es la sección que muestra el conector TFT y del panel táctil:

Schematics for TFT and Touch Panel Connector
Esquema del conector TFT y panel táctil (source)

Es importante notar que el módulo CrowPanel contiene un expansor GPIO (PI4IOE5V6408), ya que la pantalla TFT utiliza la mayoría de los pines del ESP32. Esto significa que algunos pines son GPIO del ESP32, por ejemplo (BUZZER) y otros son controlados vía el expansor GPIO. Específicamente, LCD_RESET (TFT_RST=4), TP_RESET (TP_RST=3), el LED de retroiluminación LED_P2 (TFT_BKL=2) y el motor de vibración MOTOR_P0 (MOTOR=0). Echa un vistazo al esquema del expansor GPIO a continuación:

Schematics for PI4IOE5V6408 GPIO Expander
Esquema del expansor GPIO PI4IOE5V6408 (source)

Verás más adelante en el código que los pines del ESP32 y los del expansor GPIO deben controlarse de manera diferente.

Variables globales

Declaramos varias variables globales, incluyendo credenciales WiFi, configuración de zona horaria y arrays para nombres de días y meses. También definimos una variable booleana para alternar entre formatos de 12 y 24 horas.

const char* SSID = "SSID";
const char* PWD = "PASSWORD";

bool is_24h = true;

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

const char* DAYSTR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
const char* MONTHSTR[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

Ten en cuenta que deberás reemplazar las credenciales WiFi (SSID, PWD) por las de tu red WiFi.

Probablemente también quieras cambiar la TIMEZONE. Yo uso AEST-10AEDT,M10.1.0,M4.1.0/3, que corresponde a Melbourne, Australia. Esta cadena indica el desfase horario estándar y las reglas para el horario de verano.

Las partes de esta definición de zona horaria son las siguientes

  • AEST: Hora estándar del este de Australia
  • -10: Desfase UTC de 10 horas adelantado respecto al Tiempo Universal Coordinado (UTC)
  • AEDT: Hora de verano del este de Australia
  • M10.1.0: La transición al horario de verano ocurre el primer domingo de octubre
  • M4.1.0/3: La transición de regreso al horario estándar ocurre el primer domingo de abril, con una diferencia de 3 horas respecto a UTC.

Para otras definiciones de zona horaria, consulta el Posix Timezones Database. Solo copia la cadena que encuentres allí y cambia la constante TIMEZONE en consecuencia.

Las constantes restantes son solo cadenas para los nombres de días y meses. Podrías reemplazarlas por cadenas en otro idioma, por ejemplo. Pero deberás mantener longitudes similares.

Funciones de inicialización

El código contiene varias funciones para inicializar diversos componentes. La función init_ioex() configura la comunicación I2C y el expansor de E/S.

void init_ioex() {
  Wire.begin(SDA, SCL);
  Wire.beginTransmission(PI4IO_I2C_ADDR);
  Wire.write(0x01);  // test register
  ...
}

Este código proviene del archivo demo LvglWidgets.ino. Cambié ligeramente el nombre y eliminé algunas instrucciones de impresión, pero por lo demás es el mismo código.

Lo mismo para la función relacionada write_ioex() a continuación, que permite escribir en los pines GPIO del expansor IO:

void write_ioex(uint8_t pin_number, bool value) {
  Wire.beginTransmission(PI4IO_I2C_ADDR);
  Wire.write(0x05);  // test register
  ...
}

La función init_tft() establece la configuración predeterminada para dibujar en la pantalla TFT y también dibuja el círculo blanco que representa el botón táctil llamando a display_touch():

void init_tft() {
  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(GC9A01A_BLACK);
  display_touch();
}

La función init_deep_sleep() configura el botón que despierta el ESP32 de la suspensión profunda:

void init_deep_sleep() {
  pinMode(BUTTON, INPUT);
  esp_deep_sleep_enable_gpio_wakeup(1ULL << BUTTON, ESP_GPIO_WAKEUP_GPIO_LOW);
}

Y finalmente, la función init_buzzer() configura el pin del zumbador.

void init_buzzer() {
  pinMode(BUZZER, OUTPUT);
  digitalWrite(BUZZER, LOW);
}

RTC y sincronización de hora

La función set_rtc_time() inicializa el RTC y establece la hora y fecha actuales basándose en la hora local obtenida de la conexión WiFi.

void set_rtc_time() {
  rtc.begin();
  struct tm t;
  getLocalTime(&t);
  // Set RTC time and date...
}

Esa hora local se sincroniza con un proveedor de tiempo por internet (servidor SNTP) mediante la función sync_time():

void sync_time() {
  WiFi.begin(SSID, PWD);
  for (int i = 0; (i < 5) && (WiFi.status() != WL_CONNECTED); i++)
    delay(100);
  if (WiFi.status() == WL_CONNECTED) {
    configTzTime(TIMEZONE, "pool.ntp.org");
    set_rtc_time();
  }
}

Ten en cuenta que la función intenta 5 veces conectarse al WiFi y luego se rinde. ¡Esto es intencional! Si usas el reloj fuera de tu red WiFi, no podrá conectarse ni sincronizarse al reiniciarse. Eso está bien, ya que el RTC mantendrá el seguimiento del tiempo.

Sin embargo, dado que solo sincronizamos al reiniciar el ESP32 y el RTC no tiene concepto de horario de verano, el reloj no ajustará automáticamente el cambio entre horario de verano y horario estándar y viceversa. Hay formas de solucionarlo. Consulta el tutorial Real-Time-Clock DS3231 with ESP32.

Mostrar la hora

La función display_time() obtiene la hora y fecha actuales del RTC y las muestra en la pantalla TFT en las ubicaciones apropiadas. Dependiendo del estado de la bandera is_24h, formatea la hora en 12 o 24 horas.

void display_time() {
  static char buf[16];
  static I2C_BM8563_DateTypeDef ds;
  static I2C_BM8563_TimeTypeDef ts;
  ...
  if (is_24h) {
    sprintf(buf, "%02d:%02d:%02d", ts.hours, ts.minutes, ts.seconds);
  } else {
    int display_hours = ts.hours % 12;
    display_hours = display_hours ? display_hours : 12;
    const char* period = ts.hours >= 12 ? "pm" : "am";
    sprintf(buf, "%02d:%02d %s", display_hours, ts.minutes,  period);
  }
  ...
}

Ten en cuenta que no puedes usar fácilmente una fuente diferente para mostrar la hora y la fecha. La librería Adafruit_GC9A01A permite establecer un color de fondo al imprimir texto, lo que borra la hora y fecha previamente impresas.

tft.setTextColor(GC9A01A_WHITE, GC9A01A_BLACK);
...
tft.print(buf);

Sin embargo, esto solo funciona para la fuente predeterminada, no para fuentes proporcionales. Para más información, consulta el Adafruit GFX Graphics Library Documentation. Una solución sería usar un Canvas, pero es más complejo y la actualización de la pantalla TFT podría ser lenta.

Botón táctil

La función check_touch() detecta eventos táctiles en el panel táctil. Si el toque está cerca del área definida del botón (el círculo blanco), activa el motor de vibración por un corto período para proporcionar retroalimentación táctil.

void check_touch() {
  uint8_t gesture;
  uint16_t x, y;
  bool touched = touch.getTouch(&x, &y, &gesture);
  if (touched) {
    if (abs(x - bx) < 2*br && abs(y - by) < 2*br) {
      write_ioex(MOTOR, true);
      delay(50);
      write_ioex(MOTOR, false);      
      is_24h = !is_24h;
      display_touch();
      delay(500);
    }
  }
}

Luego alterna el formato de hora y actualiza la pantalla. La siguiente imagen muestra los dos estados de la pantalla:

24h and 12h states of clock display
Estados 24h y 12h de la pantalla del reloj

El pequeño círculo blanco en la parte inferior de la pantalla representa el botón táctil y es dibujado por la función display_touch(). Dependiendo de su estado, se dibuja como un círculo relleno o vacío:

void display_touch() {
  if (is_24h) {
    tft.fillCircle(bx, by, br, GC9A01A_BLACK);
    tft.drawCircle(bx, by, br, GC9A01A_DARKGREY);
  } else {
    tft.fillCircle(bx, by, br, GC9A01A_DARKGREY);
  }
}

Botón de suspensión profunda

La función check_button() verifica si el botón físico en el lateral del módulo está presionado. Si es así, pone el dispositivo en modo de suspensión profunda. Lo más importante es que también apaga el LED de retroiluminación de la pantalla TFT para ahorrar batería.

void check_button() {
  if (digitalRead(BUTTON) == LOW) {
    tft.fillScreen(GC9A01A_BLACK);
    write_ioex(TFT_BKL, false);  // Switch off TFT backlight
    delay(300);
    esp_deep_sleep_start();
  }
}

Una segunda pulsación en el botón despierta el ESP32. Ten en cuenta que este botón tiene una resistencia pullup interna y por eso va a LOW cuando se presiona. Consulta el esquema a continuación:

Esquema del botón (source)

Funciones de configuración

La función setup() inicializa la comunicación serial, el expansor de E/S, el panel táctil, el zumbador y la pantalla TFT, y sincroniza la hora.

void setup() {
  Serial.begin(115200);  
  init_ioex();
  ...
}

Esto significa que el reloj solo se sincroniza con la hora de internet al reiniciarse. Esto ahorra batería, ya que puedes apagar el WiFi después de un reinicio. Sin embargo, podrías decidir sincronizar más frecuentemente, por ejemplo, una vez por hora para captar el cambio al horario de verano.

Función loop

La función loop() actualiza continuamente la pantalla con la hora y fecha cada 300 milisegundos y verifica eventos táctiles y el estado del botón. Podrías añadir un pequeño retardo de 50 ms en lugar de la llamada a yield().

void loop() {
  static unsigned long st = millis();

  if (millis() - st > 300) {
    display_time();
    sound_hour();
    st = millis();
  }

  check_touch();
  check_button();
  yield();
}

Demostración del reloj digital

El siguiente video corto muestra el reloj en acción. Puedes ver la hora avanzar, el cambio entre formato 24h y 12h al presionar el botón táctil, y el modo de suspensión profunda:

Demo of the Digital Clock on CrowPanel 1.28" Display
Demostración del reloj digital en CrowPanel 1.28″ Display

¡Y ahí lo tienes! Hemos explorado la mayoría de las funciones del CrowPanel 1.28″ Display y ahora deberías tener un buen punto de partida para implementar tu propio reloj con sus características únicas.

Conclusiones

En este tutorial aprendiste cómo implementar un reloj digital en un CrowPanel 1.28″ Display Module. Usamos muchas funciones del módulo de pantalla, incluyendo la pantalla táctil, el zumbador, el motor de vibración y el RTC. También aprendimos a sincronizar el RTC con un proveedor de tiempo por internet y cómo poner el reloj en modo de suspensión profunda.

Si quieres aprender más sobre sincronización de tiempo, consulta el tutorial How to synchronize ESP32 clock with SNTP server, y el tutorial Real-Time-Clock DS3231 with ESP32 te ayudará a implementar soporte para el horario de verano.

Y, si quieres implementar un reloj analógico, el Analog Clock on e-Paper Display tendrá información útil, aunque está orientado a una pantalla E-Paper y no a una TFT.

De lo contrario, hay muchas funciones que podrías añadir a tu reloj. Una obvia sería mostrar información meteorológica. Consulta el tutorial Simple ESP32 Internet Weather Station para un ejemplo. También hay información sobre cómo connect the CrowPanel el módulo a Home Assistant.

Si tienes alguna pregunta, no dudes en dejarla en la sección de comentarios.

¡Diviértete y feliz bricolaje ; )

Enlaces

A continuación algunos enlaces que encontré útiles al escribir este tutorial:

CrowPanel ESP32 1.28-inch Round Display

Hojas de datos