Reloj en tiempo real con ESP32 y pantalla OLED SSD1306
Este tutorial muestra cómo implementar un reloj en tiempo real utilizando un módulo ESP32 y una pantalla OLED de 0.96 pulgadas (controlador SSD1306) en el entorno Arduino. Se presentan dos enfoques: uno básico sin conexión a internet y otro avanzado con sincronización NTP mediante WiFi.
1. Requisitos de hardware y software
Hardware necesraio:
- Placa de desarrollo ESP32 (cualquier modelo).
- Pantalla OLED de 0.96 pulgadas con interfaz I2C (SSD1306).
- Cables dupont para conexión (VCC, GND, SDA, SCL).
- Entorno WiFi disponible (opcional, solo para la versión avanzada).
Software y bibliotecas:
- Arduino IDE con soporte para ESP32 instalado.
- Bibliotecas: Adafruit SSD1306, Adafruit GFX y Wire.h.
- La biblioteca time.h ya está incluida en el entorno ESP32.
Conexiones de hardware:
| Pin OLED | Pin ESP32 | Descripción |
|---|---|---|
| VCC | 3.3V | Voltaje seguro para evitar daños en la pantalla |
| GND | GND | Conexión a tierra |
| SDA | GPIO21 | Línea de datos I2C |
| SCL | GPIO22 | Línea de reloj I2C |
2. Implementación del código
Se ofrecen dos variantes de código. La primera establece la hora manualmente y utiliza el temporizador interno del ESP32, mientras que la segunda sincroniza la hora con un servidor NTP a través de WiFi.
Versión Básica: Reloj sin conexión a red
En esta versión, la hora se configura manualmente al iniciar el dispositivo. El ESP32 actualiza la pantalla cada segundo basándose en su reloj interno.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <time.h>
#define ANCHO_PANTALLA 128
#define ALTO_PANTALLA 64
#define RESET_OLED -1
#define DIRECCION_PANTALLA 0x3C
Adafruit_SSD1306 pantalla(ANCHO_PANTALLA, ALTO_PANTALLA, &Wire, RESET_OLED);
struct tm horaActual;
unsigned long marcaTiempoAnterior = 0;
const unsigned long intervaloActualizacion = 1000;
void configurarHoraInicial() {
struct tm tiempoConfig;
tiempoConfig.tm_year = 2023 - 1900;
tiempoConfig.tm_mon = 11 - 1;
tiempoConfig.tm_mday = 15;
tiempoConfig.tm_hour = 14;
tiempoConfig.tm_min = 45;
tiempoConfig.tm_sec = 0;
tiempoConfig.tm_isdst = 0;
time_t t = mktime(&tiempoConfig);
settimeofday((const timeval*)&t, NULL);
}
void setup() {
Serial.begin(115200);
if (!pantalla.begin(SSD1306_SWITCHCAPVCC, DIRECCION_PANTALLA)) {
Serial.println(F("Error al inicializar OLED"));
while (true);
}
pantalla.clearDisplay();
pantalla.setTextColor(SSD1306_WHITE);
configurarHoraInicial();
}
void loop() {
unsigned long marcaTiempoActual = millis();
if (marcaTiempoActual - marcaTiempoAnterior >= intervaloActualizacion) {
marcaTiempoAnterior = marcaTiempoActual;
getLocalTime(&horaActual);
pantalla.clearDisplay();
pantalla.setTextSize(1);
pantalla.setCursor(10, 5);
pantalla.printf("%02d/%02d/%04d", horaActual.tm_mday, horaActual.tm_mon + 1, horaActual.tm_year + 1900);
pantalla.setTextSize(2);
pantalla.setCursor(20, 25);
pantalla.printf("%02d:%02d:%02d", horaActual.tm_hour, horaActual.tm_min, horaActual.tm_sec);
pantalla.setTextSize(1);
pantalla.setCursor(10, 55);
const char* diasSemana[] = {"Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"};
pantalla.print(diasSemana[horaActual.tm_wday]);
pantalla.display();
}
}
Versión Avanzada: Reloj con sincronización NTP por WiFi
Esta versión se conecta a una red WiFi y sincroniza la hora con un servidor NTP, prooprcionando mayor precisión.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include <time.h>
#define ANCHO_PANTALLA 128
#define ALTO_PANTALLA 64
#define RESET_OLED -1
#define DIRECCION_PANTALLA 0x3C
const char* nombreRed = "Tu_Nombre_WiFi";
const char* contrasena = "Tu_Contrasena";
const char* servidorNTP = "pool.ntp.org";
const long desplazamientoGMT = 3600 * 2; // Ajustar según zona horaria
const int ajusteHorarioVerano = 0;
Adafruit_SSD1306 pantalla(ANCHO_PANTALLA, ALTO_PANTALLA, &Wire, RESET_OLED);
struct tm horaActual;
unsigned long marcaTiempoAnterior = 0;
const unsigned long intervaloActualizacion = 1000;
void conectarWiFi() {
Serial.printf("Conectando a %s\n", nombreRed);
WiFi.begin(nombreRed, contrasena);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConexión establecida");
}
void sincronizarHoraNTP() {
configTime(desplazamientoGMT, ajusteHorarioVerano, servidorNTP);
Serial.println("Esperando sincronización NTP...");
while (!getLocalTime(&horaActual)) {
delay(1000);
}
Serial.println("Hora sincronizada");
}
void setup() {
Serial.begin(115200);
if (!pantalla.begin(SSD1306_SWITCHCAPVCC, DIRECCION_PANTALLA)) {
Serial.println(F("Error al inicializar OLED"));
while (true);
}
pantalla.clearDisplay();
pantalla.setTextColor(SSD1306_WHITE);
conectarWiFi();
sincronizarHoraNTP();
}
void loop() {
unsigned long marcaTiempoActual = millis();
if (marcaTiempoActual - marcaTiempoAnterior >= intervaloActualizacion) {
marcaTiempoAnterior = marcaTiempoActual;
getLocalTime(&horaActual);
pantalla.clearDisplay();
pantalla.setTextSize(1);
pantalla.setCursor(5, 5);
pantalla.printf("Fecha: %02d-%02d-%04d", horaActual.tm_mday, horaActual.tm_mon + 1, horaActual.tm_year + 1900);
pantalla.setTextSize(2);
pantalla.setCursor(15, 25);
pantalla.printf("%02d:%02d:%02d", horaActual.tm_hour, horaActual.tm_min, horaActual.tm_sec);
pantalla.setTextSize(1);
pantalla.setCursor(5, 55);
String diaSemana;
switch (horaActual.tm_wday) {
case 0: diaSemana = "Domingo"; break;
case 1: diaSemana = "Lunes"; break;
case 2: diaSemana = "Martes"; break;
case 3: diaSemana = "Miércoles"; break;
case 4: diaSemana = "Jueves"; break;
case 5: diaSemana = "Viernes"; break;
case 6: diaSemana = "Sábado"; break;
}
pantalla.print(diaSemana);
pantalla.display();
}
}
3. Explicación de conceptos clave
- Gestión de tiempo con time.h: La biblioteca time.h permite manejar estructuras de tiempo (struct tm) y funciones como getLocalTime() para obtener la hora actual.
- Actualización no bloqueante: Se utiliza millis() para controlar el intervalo de actualización sin usar delay(), lo que permite ejecutar otras tareas mientras tanto.
- Formato de visualización: Se aplica setTextSize() para ajustar el tamaño de los números y printf() con especificadores como %02d para garantizar que los dígitos siempre tengan dos caracteres.
- Sincronización NTP: En la versión avanzada, configTime() establece el servidor NTP y la zona horaria, permitiendo que el ESP32 obtenga la hora exacta de internet.
4. Solución de problemas comunes
- Error en conexión WiFi: Verificar que el nombre y contraseña de la red sean correctos, y que la red sea de frecuencia 2.4 GHz, ya que el ESP32 no soporta 5 GHz por defecto.
- Pantala OLED sin imagen: Comprobar la dirección I2C (usualmente 0x3C o 0x3D) y asegurar que las conexiones SDA y SCL sean correctas.
- Fecha u hora incorrecta: Recordar que en struct tm, el año se almacena como años desde 1900, y el mes desde 0 (enero = 0).
- Desfase de tiempo en versión básica: El reloj interno del ESP32 puede acumular errores; para mayor precisión, usar la versión con NTP.
5. Posibles mejoras y extensiones
- Integrar sensores de temperatura o humedad (como DHT22) para mostrar datos ambientales en la pantalla.
- Agregar funcionalidad de alarma con un zumbador o LED que se active a una hora determinada.
- Implementar control de brillo automático mediante un sensor de luz (LDR).
- Personalizar el diseño gráfico en la pantalla OLED, por ejemplo, añadiendo iconos o animaciones simples.