Skip to Content

Interruptor temporizador digital con pantalla CrowPanel ESP32

Interruptor temporizador digital con pantalla CrowPanel ESP32

En este tutorial aprenderás a construir un Interruptor Temporizador Digital con el CrowPanel 3.5″ ESP32 Display. Vamos a sincronizar el reloj interno con un proveedor de tiempo por internet para mantenerlo preciso y usaremos la biblioteca TFT_eSPI para crear una interfaz gráfica atractiva que facilite la configuración de los horarios.

Un Interruptor Temporizador Digital es un dispositivo electrónico que enciende o apaga automáticamente otros dispositivos en horarios específicos establecidos por el usuario. Normalmente cuenta con una pantalla digital para programar los tiempos de encendido/apagado. Se puede usar para controlar luces, electrodomésticos y otros dispositivos eléctricos en horarios predeterminados. Por ejemplo, puedes usarlo para encender las luces de tu casa al atardecer y apagarlas a la hora de dormir, o para programar el riego de tu jardín y apagarlo después de un tiempo específico para evitar el exceso de agua.

Demostración del Interruptor Temporizador Digital

El Interruptor Temporizador Digital que construirás en este tutorial será mejor que la mayoría de los temporizadores digitales comerciales. Su hora siempre será precisa y no será necesario ajustarlo por el horario de verano. Además, gracias a la gran pantalla, será fácil configurar o cambiar los horarios.

Partes necesarias

Para este proyecto necesitarás el CrowPanel 3.5″ ESP32 Display de ELECROW y el Arduino IDE. El panel viene con un cable USB y un cable DuPont de 4 pines, por lo que no necesitarás cables adicionales.

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 del CrowPanel 3.5″ ESP32 Display

El CrowPanel 3.5″ ESP32 Display de ELECROW es una pantalla táctil resistiva con una pantalla TFT de resolución 480*320 y un ESP32-WROVER-B como procesador de control integrado. Puedes adquirir la pantalla con una carcasa acrílica atractiva (ver imagen abajo), lo que significa que no tendrás que fabricar una carcasa tú mismo.

CrowPanel 3.5" ESP32 Display with Housing
CrowPanel 3.5″ ESP32 Display con carcasa (source)

Además, la placa incluye una ranura para tarjeta TF, una interfaz UART, una interfaz I2C, una interfaz para altavoz, un conector para batería con capacidad de carga y un pequeño puerto GPIO con dos pines GPIO. Consulta el pinout a continuación:

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

La siguiente tabla muestra qué pines GPIO están asignados a cada una de las tres interfaces IO.

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

La placa puede alimentarse a través del puerto USB (5V, 2A) o conectando una batería LiPo estándar de 3.7V al conector BAT. Solo ten cuidado con la polaridad del conector y la batería. Si el cable USB y la batería están conectados al mismo tiempo, la placa cargará la batería (la corriente máxima de carga es de 500mA).

Puedes programar la placa usando varios entornos de desarrollo como Arduino IDE, Espressif IDF, Lua RTOS y Micro Python, y es compatible con el LVGL graphics library. Sin embargo, en este tutorial usaremos Arduino IDE y la TFT_eSPI graphics library.

Crear estructura del proyecto

Antes de entrar en detalles, vamos a crear la estructura del proyecto y la configuración para el TFT_eSPI library.

Abre tu Arduino IDE y crea un proyecto llamado «digital_timer_switch» y guárdalo (Guardar como …). Esto creará una carpeta «digital_timer_switch» con el archivo «digital_timer_switch.ino» dentro. En esta carpeta crea otro archivo llamado «datestrs.h» y un tercero llamado «tft_setup.h«. La estructura de tu carpeta de proyecto debería verse así

Estructura de la carpeta del proyecto

y en tu Arduino IDE ahora deberías tener tres pestañas llamadas «digital_timer_switch.ino«, «datestrs.h» y «tft_setup.h«.

Arduino IDE with three Tabs
Arduino IDE con tres pestañas

Haz clic en la pestaña «tft_setup.h» para abrir el archivo y copia el siguiente código en él:

// 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 al TFT_eSPI library qué pantalla estamos usando. Específicamente, le indicamos a la biblioteca las dimensiones de la pantalla (TFT_WIDTH, TFT_WIDTH), su controlador de pantalla (ILI9488_DRIVER), qué pines SPI se usan para controlarla y qué fuentes cargar. Sin estas configuraciones, no podrás mostrar nada en la pantalla.

Para más información detallada, consulta el tutorial CrowPanel 2.8″ ESP32 Display : Easy Setup Guide. Allí explicamos cómo configurar la pantalla de 2.8″. Pero los pasos y descripciones aplican igual para la pantalla de 3.5″. Solo cambian los ajustes de dimensiones y controlador de pantalla.

Calibración de la pantalla táctil

El CrowPanel 3.5″ Display viene con una pantalla táctil resistiva que debes calibrar primero antes de usarla con la biblioteca TFT_eSPI. Copia el código siguiente en el archivo «digital_timer_switch.ino«, compílalo y súbelo al 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);
}

Cuando el código esté en ejecución, la pantalla mostrará una flecha y te indicará que toques la esquina a la que apunta. Esto se repetirá para las otras tres esquinas. Usa el pequeño lápiz que viene con la pantalla para hacerlo y trata de tocar las esquinas lo más precisamente posible.

Calibration of touch screen
Calibración de la pantalla táctil

Al final del proceso de calibración, el código imprime los 5 parámetros de calibración (coordenadas de las esquinas y orientación de la pantalla) en el monitor serial. Deberías ver algo así:

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

Copia estos parámetros en algún lugar, ya que los necesitarás en el código del interruptor temporizador digital. La calibración se repite cada 10 segundos, así que puedes intentarlo varias veces para obtener los parámetros más precisos.

Para más detalles sobre el proceso de calibración, consulta el tutorial CrowPanel 2.8″ ESP32 Display : Easy Setup Guide.

Código para el Interruptor Temporizador Digital

En esta sección escribiremos el código completo para el Interruptor Temporizador Digital. Comienza copiando el siguiente código en el archivo «datestrs.h» que está en la carpeta de tu proyecto 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"
};

Luego copia todo el código siguiente en el archivo «digital_timer_switch.ino» y reemplaza el código de calibración que hay allí.

Este es el código principal. Permite a los usuarios configurar alarmas basadas en un horario semanal, ver la hora actual y activar o desactivar las alarmas. La pantalla se actualiza en tiempo real y el usuario puede interactuar mediante botones táctiles. Léelo rápidamente y luego discutiremos los detalles del 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 puedes ver, es bastante código. La siguiente sección te ofrece un resumen rápido de las funciones y su significado.

Resumen de funciones

  • drawLabels(): Dibuja etiquetas para las filas y columnas en la pantalla.
  • drawSlot(int r, int c): Dibuja un intervalo horario en la cuadrícula del horario según la fila y columna proporcionadas.
  • drawSchedule(): Dibuja toda la cuadrícula del horario en la pantalla.
  • selectSlot(int x, int y): Permite a los usuarios seleccionar un intervalo horario en la cuadrícula tocando la pantalla.
  • initWiFi(): Inicializa la conexión WiFi usando el SSID y la contraseña proporcionados.
  • initSNTP(): Inicializa el SNTP (Protocolo Simple de Tiempo de Red) para la sincronización horaria.
  • setTimezone(): Establece la zona horaria del dispositivo según la constante proporcionada.
  • drawTime(): Dibuja la hora y fecha actuales en la pantalla.
  • isOn(): Verifica si la hora actual corresponde a un intervalo programado y si la alarma está activada.
  • initDisplay(): Inicializa la pantalla y configura la funcionalidad táctil.
  • btnView_pressed() y btnAlarm_pressed(): Manejan los eventos de pulsación de botones para cambiar entre vista de hora y vista de horario, y para activar o desactivar la alarma.
  • initButtons(): Inicializa los botones en la pantalla para interactuar con el proyecto.
  • handleButtons(): Maneja las acciones de pulsar y soltar botones en la pantalla.
  • drawView(): Dibuja la vista de hora o la vista de horario según el modo actual.
  • updateView(): Actualiza la pantalla según las interacciones del usuario y el modo actual.

Veamos más de cerca estas funciones y otras partes del código.

Inclusión de bibliotecas

El código comienza incluyendo varias bibliotecas necesarias para la funcionalidad del ESP32 y la pantalla. Necesitamos bibliotecas para la pantalla TFT, la conexión Wi-Fi, la sincronización horaria con un servidor SNTP y los nombres de meses y días (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 y variables

Luego definimos varias constantes y variables que se usarán en todo el programa. Estas incluyen credenciales Wi-Fi, dimensiones de la pantalla, pin de alarma y un arreglo de horarios.

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;

Deberás configurar el SSID y PWD para tu WiFi y probablemente también tendrás que cambiar el TIMEZONE. La especificación de zona horaria anterior «AEST-10AEDT,M10.1.0,M4.1.0/3» es para Australia, que corresponde a la Hora Estándar del Este Australiano (AEST) con ajustes por horario de verano.

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

  • AEST: Hora Estándar del Este Australiano
  • -10: Desfase UTC de 10 horas adelantado respecto al Tiempo Universal Coordinado (UTC)
  • AEDT: Hora de Verano del Este Australiano
  • 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 y pega la cadena que encuentres allí y cambia la constante TIMEZONE en consecuencia.

Funciones de conversión de coordenadas

Las siguientes funciones convierten entre sistemas de coordenadas para la pantalla:

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

c2x() y r2x() mapean columnas y filas (c,r) en la cuadrícula del horario a coordenadas de pantalla (x,y), mientras que x2c() y y2r() realizan la operación inversa. Son necesarias para convertir un evento táctil en la selección de un intervalo horario, por ejemplo.

Funciones de dibujo

El código incluye varias funciones para dibujar elementos en la pantalla:

drawLabels

Esta función dibuja las etiquetas para las filas (horas) y columnas (días de la semana) del horario.

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 horas y días para el horario

drawSlot

Esta función dibuja un intervalo horario individual en el horario, rellenándolo de blanco si la alarma está activada para ese tiempo.

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
Un intervalo horario

drawSchedule

Esta función dibuja toda la cuadrícula del horario, llamando a 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
Cuadrícula horaria para el horario

selectSlot

Esta función alterna el estado y color de un intervalo horario cuando el usuario lo toca en la pantalla.

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);
}

Inicialización de Wi-Fi y SNTP

Las siguientes funciones manejan la inicialización de Wi-Fi y SNTP (Protocolo Simple de Tiempo de Red):

initWiFi

Esta función conecta el ESP32 a la red Wi-Fi especificada.

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

initSNTP

Esta función configura el servicio SNTP para sincronizar la hora. Básicamente se conecta a un servidor en internet que proporciona información horaria muy precisa y sincroniza el reloj interno del ESP32 en consecuencia.

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();
}

Mientras haya conexión Wi-Fi e internet, esto asegura que el reloj de nuestro interruptor temporizador digital siempre sea preciso y se ajuste automáticamente al horario de verano. Para más detalles consulta el tutorial How to synchronize ESP32 clock with SNTP server.

setTimezone

Esta función establece la zona horaria para la aplicación. Debes asegurarte de que esta función se ejecute cada vez que el ESP32 arranque, de lo contrario el reloj estará desfasado.

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

Función de visualización de hora

La función drawTime() obtiene la hora local actual, sincronizada vía SNTP, y muestra la hora y fecha en la pantalla.

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);
}

La función usa las constantes para los nombres de días y meses almacenadas en el archivo datestrs.h.


Time and Date shown on Display
Hora y fecha mostradas en la pantalla

Si necesitas más detalles, consulta los tutoriales Digital Clock with CrowPanel 3.5″ ESP32 Display y Digital Clock on e-Paper Display, donde también implementamos un reloj digital.

Función de comprobación de alarma

La función isOn() verifica si la alarma debe activarse según la hora actual y el horario. Si la celda de la cuadrícula schedule[t.tm_hour][t.tm_wday] contiene true y el interruptor general de alarma está activado (isAlarmOn), la función devuelve true y en caso contrario false.

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

Inicialización de la pantalla

La función initDisplay() inicializa la pantalla configurando la rotación, llenando la pantalla de negro y estableciendo los parámetros de calibración para la pantalla táctil.

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

Funciones de botones

El código incluye funciones para manejar la pulsación de botones para alternar entre la vista de hora y la configuración de alarmas:

btnView_pressed

Esta función alterna la vista entre la hora actual y el horario de alarmas.

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
Alternando vista de hora y horario

btnAlarm_pressed

Esta función activa o desactiva el interruptor general de alarma. Si el interruptor está apagado, el horario de alarmas queda deshabilitado.

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");
  }
}
Alternando el interruptor de alarma

Inicialización de botones

La función initButtons() inicializa los botones en la pantalla.

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
Botones para vista y alarma

Manejo de botones

La función handleButtons() verifica las pulsaciones de botones y ejecuta las acciones correspondientes.

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();
    }
  }
}

Función de dibujo de vista

La función drawView() actualiza la pantalla según el modo de vista actual (hora o horario).

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

Función de actualización de vista

La función updateView() refresca la pantalla según la interacción del usuario.

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

Función setup

La función setup() inicializa Wi-Fi, SNTP, la pantalla y configura el GPIO25 como salida. Conectaremos un LED o un relé a este pin y los controlaremos mediante el horario de alarmas.

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

Función loop

Finalmente, la función loop() actualiza continuamente la vista, maneja las pulsaciones de botones y controla el pin de alarma según el horario.

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

Y este es el código completo para un interruptor temporizador digital. En las siguientes dos secciones te mostraré cómo conectar un LED o un relé al CrowPanel Display, que luego podremos controlar mediante el horario de alarmas.

Conectando un LED al Interruptor Temporizador Digital

Para probar que el código de nuestro interruptor temporizador digital funciona, conectamos un LED al GPIO25 de la siguiente manera:

LED connected to CrowPanel 3.5" ESP32 Display
LED conectado al CrowPanel 3.5″ ESP32 Display

Asegúrate de que el pin corto (cátodo) del LED esté conectado a tierra (cable negro) y el pin largo a través de una resistencia al GPIO25 (cable amarillo). El CrowPanel viene con un conector codificado por colores, así que si sigues los colores no te equivocarás. La foto siguiente muestra el cableado real con los cables codificados por colores:

Wiring of LED with Crowpanel Display
Cableado del LED con CrowPanel Display

Para probar el interruptor temporizador digital, lee la hora y fecha actuales, por ejemplo 12:37:00 un miércoles, y luego configura el intervalo correspondiente en el horario de alarmas:

Schedule set for 12:00-13:00 on Wednesday
Horario configurado para 12:00-13:00 el miércoles

Si el botón general de alarma está activado, el LED debería encenderse. Luego puedes apagarlo alternando el botón general de alarma o cambiando el horario.

Conectando un relé al Interruptor Temporizador Digital

Si quieres controlar las luces de tu casa o la bomba del riego, necesitarás un relé. El siguiente diagrama muestra cómo conectarlo:

Relé conectado al CrowPanel 3.5″ ESP32 Display

VCC (cable rojo) y GND (cable negro) están conectados a los pines correspondientes en un módulo de relé. GPIO25 está conectado al pin de entrada (cable amarillo) del relé.

El dispositivo de alta tensión y alta corriente que quieres controlar se conecta a los terminales COM y NC (normalmente cerrado) del módulo de relé.¡Ten mucho cuidado al operar con voltajes superiores a 50V! Debes asegurarte de que el módulo de relé esté clasificado para el voltaje y corriente que deseas conmutar. Los módulos de relé típicos (AITRIP, HiLetgo) pueden conmutar 220V a 4A hasta 10A.

La foto a continuación muestra el cableado del módulo de relé con el CrowPanel Display:

Wiring of Relay with Crowpanel Display
Cableado del relé con CrowPanel Display

Conclusión

En este tutorial aprendiste a construir un Interruptor Temporizador Digital con el CrowPanel 3.5″ ESP32 Display. En comparación con muchos productos comerciales, nuestro Interruptor Temporizador Digital tiene siempre una hora precisa y, gracias a la gran pantalla, es muy fácil configurar y cambiar los horarios rápidamente.

Hay muchas posibles extensiones para este proyecto. Puede que quieras poder configurar horarios con resolución de minutos o segundos. O tal vez quieras programar un horario para un mes o un año entero.

En lugar de conectar un relé, también podrías usar Bluetooth, MQTT u otros protocolos para controlar dispositivos de forma inalámbrica. El código anterior te ofrece una base para ello.

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

¡Feliz bricolaje! 😉

Enlaces

Aquí algunos enlaces que encontré útiles para escribir este post.