miércoles, 5 de agosto de 2026

Reloj con pantalla TFT 3.5" 8 bits y ESP32

Muy buenas a todos y todas y bienvenidos a un nuevo ejemplo de como reutilizar una vieja pantalla con interfaz de 8bits conectandola a un ESP32 DEV KIT V1.

La librería TFT_eSPI se ha convertido en el estándar de facto para manejar pantallas TFT en proyectos con microcontroladores ESP32.

La eficiencia y flexibilidad de esta librería la hacen ideal para implementar tanto relojes digitales como analógicos.

En este artículo exploraremos ambas aproximaciones, analizando sus arquitecturas y particularidades.

Antes de empezar veamos que componentes nos hacen falta para el proyecto:


🛠️ Componentes necesarios
ESP32 DEVKIT V1
ESP32 DEVKIT V1 Placa de Desarrollo Inalámbrica WiFi ESP32S esp32 DEVKIT V1.
Pantalla TFT 3,5 pulgadas
Pantalla TFT 3,5" Resolución 480x320 píxeles, interfaz paralela 8 bits.
Cables Dupont hembra hembra
Cables Dupont Kit de 40pin 10cm 2,54mm 1pin hembra a hembra .


Una vez que tenemos los componentes podemos seguir esta guía para conectar los cables, aunque no así para la configuración de la pantalla



Fundamentos del reloj digital

El reloj digital es la opción más directa y legible. La implementación de este tipo de reloj se basa en mostrar en la pantalla los dígitos que se actualizan cada segundo. Reloj digital con ESP32 y pantalla TFT paralela 8 bits

La librería TFT_eSPI permite manejar diferentes estrategias de renderizado, desde la más simple hasta técnicas avanzadas con sprites.


Enfoque para un reloj básico:

El método más sencillo consiste en dibujar directamente los dígitos sobre la pantalla usando las funciones de la propia librería tft.drawNumber() o tft.print().
En este caso, se redibuja todo el reloj cada segundo. Aunque funcional, puede producir parpadeo si la actualización no se realiza de una manera limpia.

Una mejora en el código consiste en actualizar solo los dígitos que cambian en cada momento.
Por ejemplo, si el minuto pasa de 45 a 46, solo se redibuja el dígito de las unidades. Esto reduce significativamente el parpadeo y la carga de procesamiento.
El código típico utiliza variables como byte omm = 99 para recordar el estado anterior y comparar


Extracción de la hora de compilación

Para inicializar el reloj sin necesidad de conexión WiFi, se puede extraer la hora del momento de compilación usando la macro __TIME__. Una función auxiliar llamada conv2d() convierte los caracteres en valores numéricos:


static uint8_t conv2d(const char* p) {
  uint8_t v = 0;
  if ('0' <= *p && *p <= '9') v = *p - '0';
  return 10 * v + *++p - '0';
}


Código ejemplo para un ESP32 DEVKIT V1 de un reloj digital


/*
  Reloj Digital para DOIT ESP32 DEVKIT V1 con pantalla TFT de 480x320
  Basado en el ejemplo de la librería TFT_eSPI.
  La hora se inicializa con la hora de compilación.

  - User_Setup.h -> #define ILI9341_DRIVER
  - User_Setup_Select.h -> #include <User_Setups/Setup70d_ILI9488_S3_Parallel.h>
  - User_Setups/Setup70d_ILI9488_S3_Parallel.h ->

      #define USER_SETUP_ID 146

      #define TFT_PARALLEL_8_BIT

      #define ILI9481_DRIVER

      // ESP32 S3 pins used for the parallel interface TFT
      #define TFT_CS    2
      #define TFT_DC   32  // Data Command control pin - must use a GPIO in the range 0-31
      #define TFT_RST  15

      #define TFT_WR   33  // Write strobe control pin - must use a GPIO in the range 0-31
      #define TFT_RD    4

      #define TFT_D0   12  // Must use GPIO in the range 0-31 for the data bus
      #define TFT_D1   13  // so a single register write sets/clears all bits
      #define TFT_D2   26
      #define TFT_D3   25
      #define TFT_D4   17
      #define TFT_D5   16
      #define TFT_D6   27
      #define TFT_D7   14
*/

#include <TFT_eSPI.h> // Incluye la librería para la pantalla
#include <SPI.h>

TFT_eSPI tft = TFT_eSPI(); // Crea el objeto de la pantalla

uint32_t targetTime = 0; // Variable para controlar el tiempo de actualización

// Función para extraer números de la cadena de tiempo de compilación
static uint8_t conv2d(const char* p) {
  uint8_t v = 0;
  if ('0' <= *p && *p <= '9') v = *p - '0';
  return 10 * v + *++p - '0';
}

// Obtiene la hora, minutos y segundos de la hora de compilación
uint8_t hh = conv2d(__TIME__), mm = conv2d(__TIME__ + 3), ss = conv2d(__TIME__ + 6);

// Variables para controlar el parpadeo de los dos puntos ":"
byte omm = 99, oss = 99;
int xcolon = 0, xsecs = 0;

void setup() {
  tft.init();           // Inicializa la pantalla
  tft.setRotation(1);   // Ajusta la orientación (puedes probar con 0, 1, 2, 3)
  tft.fillScreen(TFT_BLACK);

  // Configura el color y tamaño del texto
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.setTextSize(12);   // Ajusta el tamaño para tu pantalla de 480x320
  
  targetTime = millis() + 1000; // Programa la primera actualización en 1 segundo
}

void loop() {
  if (targetTime < millis()) {
    targetTime = millis() + 1000; // Programa la siguiente actualización

    // --- Lógica para incrementar la hora ---
    ss++;
    if (ss == 60) {
      ss = 0;
      omm = mm;
      mm++;
      if (mm > 59) {
        mm = 0;
        hh++;
        if (hh > 23) hh = 0;
      }
    }

    // --- Dibujar la hora en pantalla ---
    int xpos = 80; // Posición inicial X del texto (ajústalo a tu gusto)
    int ypos = 130; // Posición inicial Y del texto (ajústalo a tu gusto)

    // Redibuja horas y minutos solo cuando cambian
    if (omm != mm) {
      omm = mm;
      tft.setCursor(xpos, ypos);
      
      // Dibuja las horas con cero a la izquierda si es necesario
      if (hh < 10) tft.print("0");
      tft.print(hh);
      
      // Dibuja los dos puntos y guarda su posición para parpadear
      xcolon = tft.getCursorX();
      tft.print(":");
      
      // Dibuja los minutos con cero a la izquierda si es necesario
      if (mm < 10) tft.print("0");
      tft.print(mm);
      
      xsecs = tft.getCursorX(); // Guarda la posición para los segundos

    }

    // --- Dibujar los segundos y hacer parpadear los dos puntos cada segundo ---
    if (oss != ss) {
      oss = ss;
      
      // Borra los segundos anteriores y los vuelve a dibujar
      tft.setCursor(xsecs, ypos); // Posición más abajo para los segundos
      tft.setTextColor(TFT_WHITE, TFT_BLACK);
      tft.print(":");
      if (ss < 10) tft.print("0");
      tft.print(ss);

      // Lógica para parpadear los dos puntos ":"
      if (ss % 2 == 0) {
        tft.setTextColor(TFT_WHITE, TFT_BLACK); // Atenúa el color
        tft.setCursor(xcolon, ypos);
        tft.print(":");
      } else {
        tft.setTextColor(TFT_WHITE, TFT_BLACK); // Restaura el color
        tft.setCursor(xcolon, ypos);
        tft.print(":");
      }
    }
  }
}


Ejemplo de pantalla con reloj analógico

Reloj analógico: geometría y precisión

Crear un reloj analógico es técnicamente más exigente ya que requiere dibujar una esfera estática y dos o tres manecillas que giran con el tiempo.


La complejidad reside en el cálculo de posiciones y en la gestión del redibujado para evitar estelas.


Cálculo de ángulos y coordenadas para las manecillas del reloj

Las manecillas se posicionan mediante trigonometría básica. Partiendo del centro de la esfera, se calcula el ángulo correspondiente a la hora y luego se proyecta sobre el radio de la manecilla:

  • Segundero: 6 grados por segundo o lo que es lo mismo 360º / 60.

  • Minutero: 6 grados por minuto, más una corrección por los segundos transcurridos

  • Horario: 30 grados por hora o 360º / 12, más una corrección por los minutos transcurridos


Redibujado: el problema de las estelas

El desafío principal del reloj analógico es evitar que las manecillas dejen estelas al moverse. Si se redibuja toda la esfera cada segundo, el parpadeo es molesto. La solución óptima para este problema es redibujar solo las manecillas, de esta manera eliminamos un casi seguro parpadeo de la pantalla.


Técnica de borrado diferencial

El método más eficiente para dibujar las manecillas funciona así:

  • Se dibuja la esfera del reloj completa una sola vez en el setup().

  • En cada iteración del loop(), se calculan los nuevos ángulos para las posiciones de las manecillas.

  • Las manecillas antiguas se borran pintándolas del color de fondo (negro) en la posición anterior.

  • Se dibujan las nuevas manecillas en la posición actual.

Para que esta técnica funcione hay que guardar las coordenadas anteriores en variables como osx, osy, omx, omy, ohx, ohy.

Configuración para pantallas de 480x320

Para pantallas con resolución 480x320, las coordenadas del centro suelen ser el punto 240 y el punto 160.
El radio máximo para que quepa la esfera completa en la pantalla es de aproximadamente 150 píxeles.
Vamos a distribuir las longitudes de las manecillas de la siguiente manera:


#define CENTER_X 240
#define CENTER_Y 160
#define RADIUS 150
#define HOUR_LENGTH 67     // ~45% del radio
#define MINUTE_LENGTH 97   // ~65% del radio
#define SECOND_LENGTH 112  // ~75% del radio

Marcas horarias y minutos

La esfera incluye 12 marcas principales para las horas y 60 marcas secundarias para los minutos. Las de hora son más largas el código típico utiliza radios de 100 y 114 para los extremos.


/*
  Reloj analógico para DOIT ESP32 DEVKIT V1 con pantalla TFT de 480x320
  Basado en el ejemplo de la librería TFT_eSPI.
  La hora se inicializa con la hora de compilación.

  - User_Setup.h -> #define ILI9341_DRIVER
  - User_Setup_Select.h -> #include <User_Setups/Setup70d_ILI9488_S3_Parallel.h>
  - User_Setups/Setup70d_ILI9488_S3_Parallel.h ->

      #define USER_SETUP_ID 146

      #define TFT_PARALLEL_8_BIT

      #define ILI9481_DRIVER

      // ESP32 S3 pins used for the parallel interface TFT
      #define TFT_CS    2
      #define TFT_DC   32  // Data Command control pin - must use a GPIO in the range 0-31
      #define TFT_RST  15

      #define TFT_WR   33  // Write strobe control pin - must use a GPIO in the range 0-31
      #define TFT_RD    4

      #define TFT_D0   12  // Must use GPIO in the range 0-31 for the data bus
      #define TFT_D1   13  // so a single register write sets/clears all bits
      #define TFT_D2   26
      #define TFT_D3   25
      #define TFT_D4   17
      #define TFT_D5   16
      #define TFT_D6   27
      #define TFT_D7   14
*/

#include <TFT_eSPI.h> // Incluye la librería para la pantalla
#include <SPI.h>

TFT_eSPI tft = TFT_eSPI(); // Crea el objeto de la pantalla
// --- Configuración del reloj ---
#define CENTER_X 240    // Centro X de la pantalla (480/2)
#define CENTER_Y 160    // Centro Y de la pantalla (320/2)
#define RADIUS 150      // Radio máximo que cabe en la pantalla

// Colores de las agujas
#define COLOR_HOUR   TFT_WHITE
#define COLOR_MINUTE TFT_YELLOW
#define COLOR_SECOND TFT_RED
#define COLOR_DIAL   TFT_DARKGREY

// Grosor de las agujas (en píxeles)
#define THICKNESS_HOUR   8
#define THICKNESS_MINUTE 5
#define THICKNESS_SECOND 2

// --- Variables para la hora ---
static uint8_t conv2d(const char* p) {
  uint8_t v = 0;
  if ('0' <= *p && *p <= '9') v = *p - '0';
  return 10 * v + *++p - '0';
}

uint8_t hh = conv2d(__TIME__);
uint8_t mm = conv2d(__TIME__ + 3);
uint8_t ss = conv2d(__TIME__ + 6);

uint32_t targetTime = 0;

// --- Variables para dibujar las agujas ---
float lastHourAngle = -1;
float lastMinuteAngle = -1;
float lastSecondAngle = -1;

// --- Funciones de dibujo ---

// Dibuja la esfera del reloj (solo se dibuja una vez)
void drawDial() {
  tft.fillScreen(TFT_BLACK);
  
  // Círculo exterior
  tft.drawCircle(CENTER_X, CENTER_Y, RADIUS, COLOR_DIAL);
  tft.drawCircle(CENTER_X, CENTER_Y, RADIUS - 2, TFT_WHITE);
  
  // Marcas de las horas (12 marcas grandes)
  for (int i = 0; i < 12; i++) {
    float angle = i * 30.0 * M_PI / 180.0 - M_PI / 2;
    int x1 = CENTER_X + (RADIUS - 20) * cos(angle);
    int y1 = CENTER_Y + (RADIUS - 20) * sin(angle);
    int x2 = CENTER_X + RADIUS * cos(angle);
    int y2 = CENTER_Y + RADIUS * sin(angle);
    tft.drawLine(x1, y1, x2, y2, TFT_WHITE);
  }
  
  // Marcas de los minutos (60 marcas pequeñas)
  for (int i = 0; i < 60; i++) {
    if (i % 5 == 0) continue; // Saltar las marcas de horas
    float angle = i * 6.0 * M_PI / 180.0 - M_PI / 2;
    int x1 = CENTER_X + (RADIUS - 10) * cos(angle);
    int y1 = CENTER_Y + (RADIUS - 10) * sin(angle);
    int x2 = CENTER_X + (RADIUS - 4) * cos(angle);
    int y2 = CENTER_Y + (RADIUS - 4) * sin(angle);
    tft.drawLine(x1, y1, x2, y2, TFT_WHITE);
  }
  
  // Centro del reloj (punto central)
  tft.fillCircle(CENTER_X, CENTER_Y, 6, TFT_RED);
  tft.fillCircle(CENTER_X, CENTER_Y, 3, TFT_WHITE);
  drawNumbers();
}

// Los números 1-12
void drawNumbers() {
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextSize(2);
  tft.setTextDatum(MC_DATUM);
  
  for (int i = 1; i <= 12; i++) {
    float angle = i * 30.0 * M_PI / 180.0 - M_PI / 2;
    int x = CENTER_X + (RADIUS - 35) * cos(angle);
    int y = CENTER_Y + (RADIUS - 35) * sin(angle);
    tft.drawString(String(i), x, y);
  }
}

// Dibuja una aguja
void drawHand(float angle, int length, int thickness, uint16_t color, bool erase = false) {
  // Calcular el punto final de la aguja
  int x = CENTER_X + length * cos(angle);
  int y = CENTER_Y + length * sin(angle);
  
  // Color de borrado (negro) si estamos borrando
  uint16_t drawColor = erase ? TFT_BLACK : color;
  
  // Dibujar la aguja como una línea gruesa
  tft.drawLine(CENTER_X, CENTER_Y, x, y, drawColor);
  
  // Para hacerla más gruesa, dibujamos líneas paralelas
  if (thickness > 1) {
    for (int i = 1; i < thickness; i++) {
      // Desplazamiento perpendicular
      float perpAngle = angle + M_PI / 2;
      int offsetX = i * cos(perpAngle);
      int offsetY = i * sin(perpAngle);
      tft.drawLine(CENTER_X + offsetX, CENTER_Y + offsetY, x + offsetX, y + offsetY, drawColor);
      tft.drawLine(CENTER_X - offsetX, CENTER_Y - offsetY, x - offsetX, y - offsetY, drawColor);
    }
  }
}

// Convierte tiempo a ángulo (en radianes)
float timeToAngle(int value, int maxValue, float offset = 0) {
  return (value * 2.0 * M_PI / maxValue) - M_PI / 2 + offset;
}

// --- Setup ---
void setup() {
  tft.begin();
  tft.setRotation(1); // Ajusta la orientación (0, 1, 2, 3)
  tft.setTextSize(1);
  
  // Dibujar la esfera una sola vez
  drawDial();
  
  // Calcular los ángulos iniciales
  lastHourAngle = timeToAngle(hh % 12, 12) + timeToAngle(mm, 60) / 12.0;
  lastMinuteAngle = timeToAngle(mm, 60) + timeToAngle(ss, 60) / 60.0;
  lastSecondAngle = timeToAngle(ss, 60);
  
  // Dibujar las agujas iniciales
  drawHand(lastHourAngle, RADIUS * 0.45, THICKNESS_HOUR, COLOR_HOUR);
  drawHand(lastMinuteAngle, RADIUS * 0.65, THICKNESS_MINUTE, COLOR_MINUTE);
  drawHand(lastSecondAngle, RADIUS * 0.75, THICKNESS_SECOND, COLOR_SECOND);
  
  targetTime = millis() + 1000;
}

// --- Loop ---
void loop() {
  if (targetTime < millis()) {
    targetTime = millis() + 1000;
    
    // Incrementar tiempo
    ss++;
    if (ss == 60) {
      ss = 0;
      mm++;
      if (mm > 59) {
        mm = 0;
        hh++;
        if (hh > 23) hh = 0;
      }
    }
    
    // Calcular nuevos ángulos
    float hourAngle = timeToAngle(hh % 12, 12) + timeToAngle(mm, 60) / 12.0;
    float minuteAngle = timeToAngle(mm, 60) + timeToAngle(ss, 60) / 60.0;
    float secondAngle = timeToAngle(ss, 60);
    
    // --- Borrar agujas viejas (dibujar en negro) ---
    drawHand(lastHourAngle, RADIUS * 0.45, THICKNESS_HOUR, TFT_BLACK, true);
    drawHand(lastMinuteAngle, RADIUS * 0.65, THICKNESS_MINUTE, TFT_BLACK, true);
    drawHand(lastSecondAngle, RADIUS * 0.75, THICKNESS_SECOND, TFT_BLACK, true);
    
    // --- Dibujar agujas nuevas ---
    drawHand(hourAngle, RADIUS * 0.45, THICKNESS_HOUR, COLOR_HOUR);
    drawHand(minuteAngle, RADIUS * 0.65, THICKNESS_MINUTE, COLOR_MINUTE);
    drawHand(secondAngle, RADIUS * 0.75, THICKNESS_SECOND, COLOR_SECOND);
    
    // --- Guardar ángulos para la próxima iteración ---
    lastHourAngle = hourAngle;
    lastMinuteAngle = minuteAngle;
    lastSecondAngle = secondAngle;
  }
}


Reflexión final

La elección entre reloj digital y analógico depende del propósito.
El digital es más legible y fácil de implementar, ideal para aplicaciones donde la información debe ser clara y directa.
El analógico añade un valor estético y resulta especialmente atractivo en interfaces de usuario o pantallas de bienvenida.


Ambos enfoques se benefician de la eficiencia de TFT_eSPI, pero el analógico requiere un cuidado adicional en la gestión del redibujado para evitar estelas y ofrecer una experiencia fluida.
La técnica de borrado diferencial explicada es la solución más equilibrada en términos de rendimiento y calidad visual.

Con un par de retoques, como un calendario, puede ser un buen gadget para ponerlo en tu escritorio para saber en que hora del día estas.

Aquí tienes otros enlaces de este blog relacionados con el microcontrolador ESP32 y pantallas:


viernes, 31 de julio de 2026

Ejemplos Arduino: LEDC (LED Control) en el ESP32

Muy buenas a todos y todas a la vuelta al canal!!

El periférico LEDC está diseñado principalmente para controlar la intensidad de LEDs, aunque también pero también es muy útil para generar señales PWM para otros propósitos tales como controlar motores DC.

El ESP32-C3 cuenta con 6 canales que pueden generar formas de onda independientes, ideales para controlar LEDs o dispositivos que necesiten PWM tan solo cambiando algunos parámetros de la configuración como el temporizador,la confirmación del canal y el cambio de la señal PWM.


¿Cuando se implementó ledc?

La API de LEDC para Arduino-ESP32 está documentada y disponible como parte del núcleo (core) de ESP32 en Arduino.
La versión actual (a partir de la 3.0.0) incluye cambios importantes en la sintaxis, como la sustitución de:


ledcAttachPin() // V < 3.0.0

por


ledcAttach() // Versión > 3.0.0

Para la versión 3.0.0 del core de ESP32 para Arduino, ya no se usa la función ledcSetup() ni la función ledcAttachPin(), sino que se recomienda utilizar la función ledcAttach(pin, freq, resolution)
ESP32 C3 Mini 0.42 Pinout
ESP32 C3 mini 0.42" pinout

Funciones y definiciones relacionadas

1. ledcAttach()

Función:


bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution);

La función ledcAttach configura un pin para PWM con la frecuencia (uint32_t freq) y resolución (uint8_t resolution)indicadas.
El canal LEDC se selecciona automáticamente.

Devuelve true si la configuración es exitosa.


2. ledcAttachChannel()

Función:


bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t channel);

Con ledcAttachChannel configuramos un pin para PWM con la frecuencia, resolución y canal específicos.

Devuelve true si la configuración es exitosa.


3. ledcWrite()

Función:


void ledcWrite(uint8_t pin, uint32_t duty);

Establece el ciclo de trabajo (duty cycle) para el pin LEDC.

El valor de duty depende de la resolución: para 10 bits, el rango es de 0 a 1023.


4. ledcRead()

Función:


uint32_t ledcRead(uint8_t pin);

Lee el ciclo de trabajo configurado para el pin.


5. ledcReadFreq()

Función:


uint32_t ledcReadFreq(uint8_t pin);

Con ledcReadFreq lee la frecuencia configurada para el pin que hemos seleccionado.


Relación entre frecuencia y resolución


6. ledcDetach()

Función:


bool ledcDetach(uint8_t pin);

Desvincula el pin del periférico LEDC para usarlo como GPIO digital.


7. ledcChangeFrequency()

Función:


uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution);

Cambia la frecuencia y resolución de un pin LEDC ya configurado.


8. ledcFade()

Función:


bool ledcFade(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms);

Configura e inicia un desvanecimiento (fade) automático por hardware desde un valor inicial hasta un valor objetivo.

Devuelve true si se inicia correctamente.


9. ledcWriteTone()

Función:


uint32_t ledcWriteTone(uint8_t pin, uint32_t freq);

Genera un tono PWM al 50% en la frecuencia especificada.

10. ledcWriteNote()

Función:


uint32_t ledcWriteNote(uint8_t pin, note_t note, uint8_t octave);

Por último tenemos ledcWriteNote que genera un tono a partir de una nota musical y una octava.


LEDC ¿es mejor o peor que analogWrite?

En ESP32, analogWrite() es una capa de abstracción sobre LEDC.
La principal diferencia es que analogWrite() simplifica el uso, pero limita el control.


Característica analogWrite() LEDC nativo
Resolución Fija a 8 bits (0-255) Configurable (1-14 bits en ESP32-C3)
Frecuencia Fija (~5 kHz) Totalmente configurable
Gestión de canales Automática, puede causar conflictos Manual, 6 canales independientes
Funciones avanzadas No disponible Fade por hardware, interrupciones
Complejidad de código Muy baja Media

Conclusión: analogWrite() es perfecto para prototipos rápidos y proyectos simples.
LEDC es necesario cuando se necesita precisión, frecuencias personalizadas o múltiples canales PWM sin interferencias .


🛠️ Componentes necesarios
ESP32-C3 con OLED de 0.42, Antena Cerámica, Wifi, Bluetooth
ESP32-C3OLED de 0.42" Placa de Desarrollo ESP32-C3 con OLED de 0.42", Antena Cerámica, Wifi, Bluetooth.

Relación entre frecuencia y resolución

En el ESP32-C3, la frecuencia y la resolución están interrelacionadas. A mayor frecuencia, menor resolución máxima disponible, y viceversa.

Ejemplos prácticos:

  • 5 kHz con 13 bits: 8192 niveles de intensidad.
  • 40 MHz con 1 bit: Solo 50% fijo.

Si se intenta configurar una combinación no soportada, la API mostrará un error:


E (196) ledc: requested frequency and duty resolution cannot be achieved, try reducing freq_hz or duty_resolution. div_param=128 


Ejemplos para ESP32-C3 con pantalla OLED 0.42"

La placa "ESP32-C3 OLED 0.42" tiene la pantalla conectada por I2C: en el pin GPIO5 corresponde a SDA y el pin GPIO6 para SCL.


Ejemplo 1: Uso de analogWrite (simplificado)

Primero veamos un ejemplo clásico programa con analogWrite()



#include <Wire.h> 
#include <Adafruit_SSD1306.h>

#define OLED_SDA 5
#define OLED_SCL 6
#define OLED_RESET -1
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define LED_PIN 8

void setup() {
  Wire.begin(OLED_SDA, OLED_SCL);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  for (int brightness = 0; brightness <= 255; brightness += 5) {
    analogWrite(LED_PIN, brightness);

    display.clearDisplay();
    display.setCursor(30, 30);
    display.print("AnalogWrite: ");
    display.println(brightness);

    int porcentaje = map(brightness, 0, 255, 0, 100);
    display.setCursor(30, 40);
    display.print("Inten.: ");
    display.print(porcentaje);
    display.println("%");

    display.display();
    delay(30);
  }
  for (int brightness = 255; brightness >= 0; brightness -= 5) {
    analogWrite(LED_PIN, brightness);

    display.clearDisplay();
    display.setCursor(30, 30);
    display.print("AnalogWrite: ");
    display.println(brightness);

    int porcentaje = map(brightness, 0, 255, 0, 100);
    display.setCursor(30, 40);
	display.print("Inten.: ");
    display.print(porcentaje);
    display.println("%");

    display.display();
    delay(30);
  }
}


Ejemplo 2: Ledc PWM en LED y mostrar valor en OLED (I2C)

En este ejemplo tendremos la salida del valor Ledc en la pantalla OLED 0.42". El pin 8 del LED builtin el estado de Ledc.

NOTA: el encendido del LED builtin esta invertido, es decir cuando la señal Ledc esta en 0 el LED builtin estará a su máxima intensidad y apagado en su valor más alto.



 

#include <Wire.h> 
#include <Adafruit_SSD1306.h>

#define OLED_SDA 5
#define OLED_SCL 6
#define OLED_RESET -1
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define LED_PIN 8
#define PWM_FREQ 5000
#define PWM_RES 10
#define MAX_DUTY ((1 << PWM_RES) - 1) // 1023 para 10 bits

int duty = 0;
int step = 5;

void setup() {
  Wire.begin(OLED_SDA, OLED_SCL);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  ledcAttach(LED_PIN, PWM_FREQ, PWM_RES);
  ledcWrite(LED_PIN, 0);
}

void loop() {
  duty += step;
  if (duty >= MAX_DUTY || duty <= 0) step = -step;

  ledcWrite(LED_PIN, duty);

  display.clearDisplay();
  display.setCursor(30, 30);
  display.print("Duty: ");
  display.setCursor(30, 40);
  display.print(duty);
  display.print(" / ");
  display.println(MAX_DUTY);

  int porcentaje = map(duty, 0, MAX_DUTY, 0, 100);
  display.setCursor(30, 50);
  display.print("Intensidad: ");
  display.print(porcentaje);
  display.println("%");

  display.display();
  delay(20);
}


Ejemplo 3: Ledc fade automático por hardware y mostrar estado


 
#include <Wire.h> 
#include <Adafruit_SSD1306.h>

#define OLED_SDA 5
#define OLED_SCL 6
#define OLED_RESET -1
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define LED_PIN 8
#define PWM_CHANNEL 0
#define PWM_FREQ 5000
#define PWM_RES 10
#define MAX_DUTY ((1 << PWM_RES) - 1)

void setup() {
  Wire.begin(OLED_SDA, OLED_SCL);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  ledcAttachChannel(LED_PIN, PWM_FREQ, PWM_RES, PWM_CHANNEL);
 ledcWrite(LED_PIN, 0);

  display.display();
  delay(1000);
}

void loop() {
  display.clearDisplay();
  display.setCursor(30, 30);
  display.println("Fade IN");
  display.display();

  ledcFade(LED_PIN, 0, MAX_DUTY, 2000);
  delay(2500);

  display.clearDisplay();
  display.setCursor(30, 30);
  display.println("Fade OUT");
  display.display();

  ledcFade(LED_PIN, MAX_DUTY, 0, 2000);
  delay(2500);
}


Ejemplo 4: Ledc PWM para ESC de motor brushed

Para controlar un ESC, se necesita una señal PWM de 50 Hz con pulsos entre 1000 µs y 2000 µs.



#define ESC_PIN 8 
#define PWM_FREQ 50 
#define PWM_RES 14 
#define MAX_DUTY ((1 << PWM_RES) - 1)

// Para 14 bits: 16384 = 100% (20 ms)
// 1000 µs = 1 ms → (1/20)16384 = 819
// 2000 µs = 2 ms → (2/20)16384 = 1638

void setup() {
  ledcAttach(ESC_PIN, PWM_FREQ, PWM_RES);
  ledcWrite(ESC_PIN, 819); // Neutro
  delay(3000);
}

void loop() {
  ledcWrite(ESC_PIN, 819); // Neutro
  delay(2000);
  ledcWrite(ESC_PIN, 1638); // Máximo
  delay(2000);
}


También puedes revisar el repositorio supermini_esp32c3_sketches para ejemplos muy variados y completos.


Conclusión sobre LEDC en el ESP32-C3

El periférico LEDC del ESP32-C3 es una herramienta extremadamente versátil y potente para generar señales PWM, muy superior a la función analogWrite() que muchos conocen de Arduino.

Mientras que analogWrite() está limitada a 8 bits de resolución y frecuencias fijas, LEDC ofrece un control total con resoluciones de hasta 14 bits y frecuencias completamente configurables.
Esto hace que Ledc sea ideal para aplicaciones que requieren precisión, como el control de motores DC, servomotores, atenuación de LEDs profesionales o incluso generación de tonos y notas musicales.

Lo bueno:

El ESP32-C3 dispone de 6 canales independientes, lo que permite manejar múltiples dispositivos PWM simultáneamente sin interferencias.
Una de sus características más destacadas es la capacidad de realizar desvanecimientos (fade) automáticos por hardware, lo que descarga al procesador de esta tarea y permite transiciones suaves sin necesidad de interrupciones constantes.


Lo malo:

Sin embargo, hay que tener en cuenta algunas limitaciones importantes. El ESP32-C3 solo funciona en modo "low speed", a diferencia de otros ESP32 que también tienen modo "high speed".
Además, existe una relación de compromiso entre frecuencia y resolución: a mayor frecuencia, menor resolución disponible, y viceversa. Por ejemplo, a 5 kHz se puede alcanzar 13 bits de resolución, pero a 40 MHz apenas se obtiene 1 bit.

Aquí tienes otros enlaces de este blog relacionados con el microcontrolador ESP32 y Arduino:


jueves, 16 de julio de 2026

DCCpp Animations Controller, dale vida a tu maqueta!

DCCpp LMD Animation Controller Web

¿Qué es DCCpp Animations Controller?

Muy buenas a todos y todas!

DCCpp Animations Controller es una aplicación web que te permite controlar automáticamente las animaciones de tu maqueta de ferrocarril como luces, desvíos, fuentes, servos, etc.

Según un ciclo día/noche, configurable con parámetros personalizados, la duración del día completo o el retardo.



La web se conecta directamente a tu Arduino por USB o Bluetooth mediante Web Serial API y envía comandos DCCpp de forma automática e inteligente.

¡¡¡Sin necesidad de complicados programas para configurar accesorios o desvíos!!!

✅ DCCpp Animations Controller es ideal para:

  • Encender farolas al anochecer y apagarlas al amanecer.

  • Activar animaciones o desvíos solo durante el día.

  • Controlar luces de edificios, paso a nivel, etc.

  • Simular el comportamiento real de una maqueta.

🚀 Modo de uso

Conexión del hardware

Para utilizar el sistema automatizado de luces y accesorios solo necesitamos:

  • Central DCC Conectada por puerto serie o Bluetooth.

  • Navegador Chrome, Edge o Brave (Web Serial API).

Acceso a la web

DCCpp Animations Controller

DCCpp Animations Controller

Lógica para simulación 🌞día y 🌙noche


Ciclo completo = 8 minutos (ejemplo)
├── DÍA (4 minutos)   → Se ENCIENDEN animaciones de DÍA
│                       Se APAGAN animaciones de NOCHE
|
└── NOCHE (4 minutos) → Se ENCIENDEN animaciones de NOCHE
                        Se APAGAN animaciones de DÍA

Primeros pasos

  • 1. 🔌 Conectar → Selecciona el puerto COM de tu Arduino.

    DCCpp LMD Animation Web

  • 2. ➕ Añadir animaciones → Define qué y cuándo se activa.

  • 3. 🌙 Configurar ciclo → Duración del día/noche y retardos.

  • 4. ▶️ Dejarlo funcionar → Las animaciones se activan solas.


Funciones principales

  • Ciclo (minutos):
    Duración total de día + noche Min: 1 Max: 60 (ej: 8 min = 4 día + 4 noche).

  • Retardo (segundos):
    Retraso aleatorio máximo al encender/apagar Min: 0 Max: 300 simulando imprecisión real en el encendido/apagado.

  • Barra de progreso:
    Muestra el avance del ciclo actual.

  • Próxima animación:
    Indica cuál será la siguiente en ejecutarse.


Panel de animaciones

Panel de animaciones día y noche para DCCpp LMD

Botones del panel de animaciones:

  • ➕ Nueva animación:
    Situado en la parte izquierda, crea una nueva animación. Ver añadir animaciones.

  • 📋 Ver comandos:
    Muestra información sobre los comandos más comunes.

  • 💾 Exportar JSON:
    Guarda todas tus animaciones y configuración en un archivo formato JSON.

  • 📂 Importar JSON:
    Carga una configuración en formato JSON guardada previamente.

  • 🗑️ Limpiar:
    Elimina todas las animaciones.

Botones de cada animación

  • ✅ | ⭕: Activa | Desactiva animación

  • ✏️ Editar: Modifica la animación

  • 🗑️ Eliminar: Borra la animación

  • 🔘 Test: Envía el comando manualmente (prueba)


➕ Añadir animaciones

DCCpp Animations Controller

Es hora de crear nuestra primera animación como pueden ser las farolas de una plaza que se encenderán al empezar la noche y definidas en la central con el com la ID 15

  • Nombre:
    Identificador de la animación "Farolas plaza"

  • Momento:
    ¿Día o Noche? Noche.

  • Tipo:
    LUCES | EFECTOS | SEMAFORO | COMANDO.
    Seleccionamos LUCES.

  • Comando base:
    Comando T15 sin el 1 o 0 final.


Comunicación Serial

DCCpp Animations Controller monitor serial

Para la comunicación de la central podremos usar las conexiones seriales que tengamos disponibles en la central. Generalmente el puerto USB al que conectamos la central.

Pero Animations Controller también puede hacer uso de la comunicación bluetooth en el caso de que tengamos un módulo HC-06 y un adaptador bluetooth instalado en el ordenador.

En el caso de usar la comunicación bluetooth también podremos gestionar los comandos desde un teléfono móvil.

  • 🔗 Conectar:
    Abre una ventana para seleccionar el puerto COM o tty al que esta conectada la central DCCpp.

  • ⛔ Desconectar:
    Cierra la comunicación con la central.

  • 📤 Enviar:
    Envía le comando escrito de manera manual.

  • 🧹 Limpiar:
    Limpia el monitor de respuestas.

  • Comando manual:
    Envía cualquier comando DCCpp ejem: t1 4 80 1.

  • Monitor de respuestas:
    Muestra las respuestas seriales que proporciona la central DCCpp indicando también la hora.


Log de Transiciones

Log de transiciones DCCpp Animations Controller

En el Log de Transiciones tendremos la información interna del programa como el momento del día en el que estamos, cuando se encenderán o apagarán o apagarán entre otros datos.


Conversor de direcciones DCC

Conversor de IDs DCCpp Animations Controller

Recomendaciones Para el usuario

  • Usa Chrome o Edge, Firefox necesita el plugin externo WebSerial for Firefox.

  • Empareja primero los dispositivos Bluetooth (si usas conexión inalámbrica).

  • Prueba cada animación con el botón 🔘 antes de dejarlo automático.

  • Configura retardos largos (30-60s) para simular cambios graduales.

  • Exporta tu configuración como respaldo.


❓ Preguntas frecuentes

¿Sirve cualquier central DCC?

- Si, cualquier central DCCpp incluidas la DCC++ original o las más punteras como DCCex


¿Puedo usar Bluetooth en lugar de USB?

- , empareja tu ESP32 o HC-05 con el ordenador y aparecerá en el selector de puertos.


¿Puedo usar Firefox?

Si pero... Firefox no soporta Web Serial API, necesita el plugin WebSerial for Firefox.


¿Puedo cambiar el ciclo mientras funciona?

- , solo cambia los minutos y pulsa "Actualizar". Las animaciones se reprogramarán.


¿Qué pasa si pierdo la conexión?

- El programa sigue ejecutándose localmente en tu navegador. Al reconectar, seguirá enviando comandos.


¿Puedo usar el móvil?

- , pero... Android necesita Chrome y emparejar Bluetooth desde ajustes del sistema primero. pero la interfaz no esta adaptada para estos dispositivos.


🔗 Enlace:

🌐 Acceso a la web: https://dccppanimations.infotronikblog.com

Otros proyectos con para la central DCCpp LMD:

Saludos a todos y todas!!!

sábado, 16 de mayo de 2026

IBM 300PL 6862 Pentium II 333MHz

IBM 300PL 6862
IBM 300PL

Buenas a todos y todas a este canal de cacharreos varios!!!

ya va siendo hora de sacar a la luz a esta bestia de antes del cambio de milenio, El IBM Personal Computer 300PL.

International Business Machines Corporation fue fundada 1911 aunque con otro nombre y no fue hasta el 14 de febrero de 1924 que no adopto definitivamente el nombre de IBM

Si te preguntas el significado de las siglas PL, es Product Line o linea de producto

Esta máquina es la segunda más potente de todas las que tengo de antes de fin de milenio, la otra es un AMD dual Core.

Rescatada de una web de segunda mano, carecía de procesador, cosa que tampoco fue un problema ya que cuento con unas cuantas de estas reliquias de procesadores en formato FORMATO por casa.

Y hay que agradecer que los vendedores se tomen su tiempo para que todo este bien empaquetado.


IBM 300PL 6862
Frontal IBM 300PL 6862

IBM 300PL 6862
Trasera IBM 300PL 6862

Por lo que parece tengo bastante suerte con esto del polvo, todos los que he rescatado no venia especialmente sucio hasta que llegaron el 286 "Harris", con sus poderosos 20MHz. y el 486 con 66MHz.
Pero en este caso unicamente polvo normal del uso, una alegría..

Una vez abierta la carcasa, carcasa que por cierto tiene llave para que no lo manipulen (seguridad 2.0), nos encontramos con una maquina que esta en un muy buen estado y que por lo parece solo le falta el procesador


IBM 300PL 6862

El procesador

IBM 300PL 6862

Originalmente era un Intel Pentium II 233 MHz o por lo menos los microinterruptores de la placa estaban configurados para esa velocidad.

Para solucionar esta carencia tengo dos posibilidades, un 233 o un 333, pero para no ponerme a quitar disipadores y después de unas cuantas pruebas, me quedo con el de 333MHz

Este Pentium II a 333 MHZ además de tener más potencia tiene un disipador más grande y junto con un ventilador se calienta menos que el 233.

Una cosa curiosa y que solo he visto en ordenadores de "marca" es que en algún lugar del interior de la caja viene un diagrama con las partes más importantes de la placa.

IBM 300PL 6862

En este caso tiene la información para cambiar los micro interruptores que hacen funcionar el procesador a su frecuencia correcta como instalar los módulos RAM o el reseteo de la memoria CMOS.

La placa base tiene un chipset Intel 440BX con soporte AGPx2 al que, por el momento, no le he sacado partido, pero bueno todo se verá...


Memoria RAM


La memoria RAM es otro de los componentes que han sido actualizados:
Contaba con 192MB en 3 módulos de 64 MB SDRAM PC100 y al final le he terminado poniendo 2 de 128MB de la reserva.

Esto hace que la máquina tenga un total de 256MB de RAM, nada mal para la época.

En este ordenador, como en casi todos, no se puede poner la RAM al tun tun, tum tum? hay que seguir un orden específico que viene en el diagrama que como he comentado viene en el diagrama pegado en la carcasa.

Dependiendo de la cantidad de módulos y de su capacidad tiene que ir de una u otra manera.


Sonido

IBM 300PL 6862

* La tarjeta de sonido es una Crystal Audio Codec con chipset CS4235. Suena... con su salida de audio entrada de micrófono y entrada de linea nada que destacar.
Con Windows98 no me ha dado ningún problema. Como algo curioso que solo he visto en esta máquina, es que el speaker interno también funciona como altavoz de la tarjeta de sonido. Curioso cuanto menos.

Comunicaciones

Para la comunicación con el mundo exterior cuenta con una tarjeta de red integrada Intel PRO/100 Ethernet que será interesante probar más adelante.

Otras opciones de expansión el 300PL también tiene 3 puertos PCI y 2 puertos ISA así que tiene la posibilidad de ampliar con más cosillas como una tarjeta capturadora... sería interesante probar...
También tiene puertos USB de tipo 1.1 que van exasperantemente lentos, pero hacen su función correctamente.

Almacenamiento

Cuenta con HDD IDE de la marca Seagate con 20 GB y unidad DVD grabadora IDE. No tiene puertos SATA, esto llego más tarde.


Sistema operativo

IBM 300PL 6862

Como Sistema operativo: Pues cuando lo adquirí tenía instalado Windows ME y lo cambié a Windows 98 mucho más estable .

Drivers:

Windows 98 encontró todos los drivers sin problemas aunque los USB se resisten con algunas memorias.

Programas clave han sido el Daemons Tools para cargar archivos en formato ISO.


IBM 300PL 6862
CPU-Z

En la gráfica de CPU y FPU Si comparamos con los sistemas anteriores los Pentium 1 el salto sigue siendo más o menos gradual.

Actualmente solo tengo una máquina con generación intermedia, Pentium 3, un Dell GX1 con 450Mz que veremos próximamente.

Lo siguiente ya son el Pentium 4 que tengo por aquí, iré actualizando la gráfica con los procesadores de estas generaciones que vaya probando.


Apartado gráfico

Gráficos "On Boad"

IBM 300PL 6862
* En el apartado gráfico y ahora si, damas y caballeros le damos la bienvenida al 3D!!!

El ordenador cuenta con una tarjeta gráfica integrada en la placa, una S3 Trio3D con 4MB y también cuenta con puerto AGP en el caso que quiera ampliar a otra mejor.

Y ya he mirado por ahí peeeeero, resulta qué, por como es la caja, no se le puede poner cualquier tarjeta.

Tiene que tener la forma correcta para poder ponerla en este huequino. o al menos ponerla sin tener que modificar nada.
Pero sinceramente tampoco he buscado mucho si que le tengo el ojo echado jejeje.


Conclusión

El 300PL fue diseñado para aguantar el uso diario en una oficina, no es un PC de consumo de plástico endeble. Cuenta con una fuente de alimentación y un chasis robusto, ideal para proyectos retro.

Viene con componentes que normalmente había que comprar aparte, como la tarjeta Ethernet 10/100 integrada y chip de sonido Crystal CS4235 integrado en muchas versiones. Esto significa que esta máquina esta lista para funcionar nada más encenderla (si tiene procesador claro).

Como es el que más capacidad tiene mi intención es hacer un servidor de archivos donde tener los programas retro pudiendo sacarlos de la red y como todos los ordenadores que tengo tienen tarjeta de red puede ser lo más cómodo para hacer pruebas y configuraciones


Errores y prroblemas:

Como he comentado casi todos los drivers entrarón a la primera, los de la tarjeta de audio dierón algún problema al principio pero al final funcionó correctamente.


////////////////
Error windows98:

Error al iniciar el dispositivo VKD: error de proteccion
de windows.

Si te aparece este error solo tenemos que arrancar desde disquete o Gotek si no te funciona esa línea, teclea .


CD C:\
CD WINDOWS
CD SYSTEM
SCANREG /RESTORE


Manuales con información referente al IBM 300PL 6862


*- PC 300PL Types 6862 and 6892:

*- IBM PC300 (TYPE 6263, 6265, 6275, 6285)

*- TH99 Generated jumper manual - The Retro Web

*- BIOS IBM

martes, 12 de mayo de 2026

ESP32-C3 con pantalla Oled 0.42" con ejemplos

ESP32 C3 Oled 0.42 logo

Muy buenas a todas y todos!!!

Después de la buena experiencia programando el ESP32-C3 mini junto con el módulo de la pantalla SH1106 y el encoder, y otro proyecto que aún estoy probando.

Este pequeño no comparte los mismos pines que el ESP32C3 mini así que si ya tienes un montaje realizado para el mini no te servirá, pero eso no quita que sus pines GPIO sean igual de versátiles, veamos las características y los pines de esta pequeña placa de desarrollo:


Antes de empezar veamos que componentes nos hacen falta para el proyecto:


🛠️ Componentes necesarios
ESP32-C3 con OLED de 0.42, Antena Cerámica, Wifi, Bluetooth
ESP32-C3 con OLED de 0.42" Placa de Desarrollo ESP32-C3 con OLED de 0.42", Antena Cerámica, Wifi, Bluetooth.


Hardware e interfaces

  • 22 pines GPIO programables.
  • Comunicación vía UART, SPI, I2C, I2S y USB Serie/JTAG.
  • Funciones analógicas: dos SAR de 12 bits Convertidores analógico-digitales (ADC), sensor de temperatura integrado.

Procesador y memoria

ESP32 C3 Oled 0.42 chip
  • Procesador: ESP32-C3FN4/FH4, RISC-V de 32 bits de un solo núcleo, hasta 160 MHz.
  • Memoria flash: 4 MB.
  • Pantalla: OLED de 0,42 pulgadas para información visual directa.
  • Conectividad: Wi-Fi de modo dual (802.11b/g/n) y Bluetooth 5 (LE, malla).
  • Programación: Fácil vía USB, en este caso USB-C.
  • Antena: Antena cerámica integrada.


Funciones de Wi-Fi y Bluetooth

El ESP32 integra conectividad dual Wi-Fi y Bluetooth 5 LE, optimizada para aplicaciones que requieren velocidad y eficiencia. Estas son sus capacidades más relevantes:

  • Wi-Fi con velocidades de datos de hasta 150 Mbps, compatible con ancho de banda de 20/40 MHz.
  • Bluetooth 5 LE con alta potencia de transmisión de hasta 20 dBm y red mallada.
  • Coexistencia interna entre Wi-Fi y Bluetooth para el uso de antena compartida.
  • Compatible con múltiples modos Wi-Fi: Estación, SoftAP y modo promiscuo.

Bajo consumo de energía y seguridad

El ESP32 ha sido diseñado para ofrecer un rendimiento óptimo en aplicaciones IoT sin sacrificar la eficiencia energética ni la protección de los datos.
A continuación, se detallan sus características más destacadas en este ámbito:

  • Cuatro modos de ahorro de energía, incluyendo un modo de suspensión profunda con un consumo de corriente de tan solo 5 µA.
  • Funciones de seguridad como arranque seguro y cifrado de memoria flash.
  • Aceleración criptográfica por hardware: AES, SHA, RSA y más.

Aplicaciones

Gracias a su bajo consumo de energía y su versátil conectividad, esta placa es perfecta para numerosas aplicaciones como:

  • Dispositivos para el hogar inteligente
  • Automatización industrial
  • Atención médica
  • Electrónica de consumo
  • Agricultura inteligente
  • Robots de servicio
  • Sensores IoT de bajo consumo y registradores de datos


La placa ESP32-C3 con pantalla OLED de 0,42".


Esta placa IoT compacta se basa en el chip ESP32-C3 (RISC-V) e incorpora una pantalla OLED de 0,42 pulgadas. Como la distribución de pines y el controlador de pantalla no son iguales que la de los módulos ESP32 estándar.



A continuación os dejamos la información necesaria para el correcto funcionamiento de la placa.

Especificaciones del hardware

  • Chip: ESP32-C3FN4 con una Memoria Flash de 4 MB, Wi-Fi y Bluetooth 5.0 BLE.
  • Conexión USB: USB nativo USB-C Para Habilitar la comunicación con el monitor serial active en el IDE de Arduino "USB CDC al arrancar".
  • Pantalla OLED: 0,42 pulgadas con una resolución de 72 x 40 píxeles.
  • Dimensiones: Es muy compacta aproximadamente 20 x 25 mm.


Asignación de pines y conexiones

ESP32 C3 Oled 0.42 cubo

La documentación en línea suele ser confusa. Utilice la siguiente asignación de pines para esta placa específica:

  • I2C OLED SDA: GPIO 5 (aunque generalmente se indica el 8.
  • I2C OLED SCL: GPIO 6.
  • LED Integrado: GPIO 8.
  • Boton de reset: GPIO 9.
  • Tx; GPIO 21.
  • Rx: GPIO 20.

Configurando del IDE de Arduino

Para programar placa correctamente vamos seleccionar la siguiente configuración en el IDE de Arduino:

  • Placa: Módulo de desarrollo ESP32C3 Dev Module.
  • USB CDC al arrancar: Habilitado (Para salida de Serial.print).
  • Modo de flasheo: DIO (predeterminado).

Algunas veces podemos experimentar problemas con la carga del programa, para solucionarlo, mantenga presionado el botón BOOT, presione RESET brevemente y luego suelte BOOT. Esto fuerza a la placa al entrar modo de programación.

Códigos de ejemplo

Comprobación de pines: Parpadeo del LED integrado

Usa este sencillo bucle en el pin 8 que controla el LED integrado.

// GPIO 8 es el LED integrado (LOW = ON)
#define LED_PIN 8 

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, LOW);  // LED On
  delay(500);
  digitalWrite(LED_PIN, HIGH); // LED Off
  delay(500);
}

Control de la pantalla OLED

La pantalla utiliza un controlador SSD1306, pero tiene una resolución física es de solo 72x40 píxeles.
Sin embargo, el búfer interno es de 128x64.

Para solucionar este problema tenemos que aplicar un desplazamiento en los ejes X e Y para que el texto sea visible.

Proporción Oled 0.42 con Oled 0.96

Necesitamos tener instalada la biblioteca U8g2 (de olikraus) a través del Administrador de bibliotecas de Arduino o desde el Repositorio de GitHub.


#include <U8g2lib.h>
#include <Wire.h>

#define SCL 6
#define SDA 5

// Iniciando un ESP32-C3 con I2C en los pines 5 SDA y 6 SCL.
// Usaremos el driver estandar de 128x64 pero solo una parte de la pantalla.
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);

// Los Offsets se requieren para centrar la salida dentro del buffer 128x64.
const int VISIBLE_X0 = 28;
const int VISIBLE_Y0 = 24;

//configuramos la pantalla de 0.42 pulgadas 
const int VISIBLE_W  = 72;
const int VISIBLE_H  = 40;

void setup(void) {
  delay(1000); // Una pausa para estabilizar el ESP
  
  u8g2.begin();
  u8g2.setContrast(255);      // Máximo brillo
  u8g2.setBusClock(400000);   // 400kHz velocidad del I2C
  u8g2.setFont(u8g2_font_ncenB10_tr); // Elegimos el tipo de letra
}

void loop(void) {
  u8g2.clearBuffer(); // Limpiamos el buffer interno
  
  // Dibuja un marco para probar el área visible
  u8g2.drawFrame(VISIBLE_X0, VISIBLE_Y0, VISIBLE_W, VISIBLE_H);
  
  // Dibujar texto teniendo en cuenta el desplazamiento en x y el desplazamiento en y
  u8g2.setCursor(VISIBLE_X0 + 6, VISIBLE_Y0 + 15); 
  u8g2.print("ESP32-C3");
  u8g2.setCursor(VISIBLE_X0 + 25, VISIBLE_Y0 + 30); 
  u8g2.print("ITB");
  
  u8g2.sendBuffer(); // Enviar datos a la pantalla
  delay(1000);
}


Código: Texto con scroll lateral

El texto se mueve horizontalmente dentro de los 72 píxeles visibles y se dibuja un marco opcional para recordar los límites.
Puedes ajustar delay(30) para cambiar velocidad.

ESP32 C3 Oled 0.42 scroll lateral

#include <U8g2lib.h>

// Configuración de la pantalla (driver SSD1306, 128x64, I2C hardware)
// Pines: SDA = 6, SCL = 5 (ajusta según tu conexión)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 6, 5);

// --- Constantes de área visible real (72x40 con offset 28,24) ---
const int VISIBLE_X0 = 28;
const int VISIBLE_Y0 = 24;
const int VISIBLE_W  = 72;
const int VISIBLE_H  = 40;

// Texto a desplazar
const char* texto = "  Infotronikblog desplazamiento lateral  ";
int desplazamiento = 0;

// Función para dibujar texto teniendo en cuenta el recorte (clipping manual)
void dibujarTextoScroll(int offsetX) {
  // El área de dibujo está limitada a [VISIBLE_X0, VISIBLE_X0+VISIBLE_W]
  // Usamos setFont y drawStr con coordenadas absolutas (incluyendo offset)
  u8g2.setFont(u8g2_font_ncenB08_tr);
  int anchoTexto = u8g2.getStrWidth(texto);
  
  // Dibujamos el texto varias veces para efecto continuo
  int x = VISIBLE_X0 + offsetX;
  int y = VISIBLE_Y0 + 20;  // centrado vertical aprox.
  
  u8g2.drawStr(x, y, texto);
  // Si el texto se sale por la derecha, dibujamos continuación
  if (x + anchoTexto < VISIBLE_X0 + VISIBLE_W) {
    u8g2.drawStr(x + anchoTexto, y, texto);
  }
  // Si se sale por la izquierda, dibujamos la parte que falta
  if (x < VISIBLE_X0) {
    u8g2.drawStr(x + anchoTexto, y, texto);
  }
}

void setup() {
  u8g2.begin();
  u8g2.setFontMode(1);          // Modo transparente
  u8g2.setFontDirection(0);
}

void loop() {
  u8g2.firstPage();
  do {
    // Limpia toda la pantalla (128x64) pero solo se verá el área activa
    u8g2.clearBuffer();
    
    // Dibujar un marco alrededor del área visible (opcional, para referencia)
    u8g2.drawFrame(VISIBLE_X0 - 1, VISIBLE_Y0 - 1, VISIBLE_W + 2, VISIBLE_H + 2);
    
    // Dibujar el texto en scroll
    dibujarTextoScroll(desplazamiento);
    
  } while (u8g2.nextPage());
  
  // Actualizar desplazamiento (velocidad ajustable)
  desplazamiento--;
  if (desplazamiento < -u8g2.getStrWidth(texto)) {
    desplazamiento = VISIBLE_W;
  }
  
  delay(30);  // 30 ms entre frames
}

Código: Cubo 3D + rebotes en los bordes

Un pequeño cubo 3D que se mueve con velocidades vx y vy iniciales semi-aleatorias.
Con rebotes realistas: al tocar cualquier borde del área visible (72×40 con offsets), invierte la componente de velocidad correspondiente, quedando siempre dentro de los límites considerando el medio lado del cubo para que no se salga.

ESP32 C3 Oled 0.42 ejemplo cubo

#include <U8g2lib.h>
#include <math.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 6, 5);

// Área visible real de tu pantalla (offset 28,24)
const int X0 = 28;
const int Y0 = 24;
const int W  = 72;
const int H  = 40;

// Tamaño del cubo (lado en píxeles)
const int LADO = 14;   // Ajustado a 14 como pediste

// Centro del cubo (coordenadas absolutas) - posición inicial en el centro del área visible
float cx = X0 + W/2.0;
float cy = Y0 + H/2.0;

// Velocidades de desplazamiento (píxeles por frame)
float vx = 1.2;
float vy = 0.9;

// Ángulo de rotación (radianes)
float anguloY = 0.0;

// Límites de rebote: el cubo no debe salirse del rectángulo visible
// Como el cubo se dibuja centrado en (cx,cy), el medio lado se extiende a cada lado.
float limiteIzq  = X0 + LADO/2.0;
float limiteDer  = X0 + W - LADO/2.0;
float limiteSup  = Y0 + LADO/2.0;
float limiteInf  = Y0 + H - LADO/2.0;

// Proyección isométrica con rotación alrededor del eje Y
void projectPoint(float x, float y, float z, float angY, int &sx, int &sy) {
  // Rotación en Y
  float x1 = x * cos(angY) + z * sin(angY);
  float z1 = -x * sin(angY) + z * cos(angY);
  // Proyección simple (sin perspectiva, solo desplazamiento)
  sx = (int)(cx + x1);
  sy = (int)(cy - y + z1 * 0.3);   // ligero efecto de profundidad
}

void drawCubo(float angY) {
  // Vértices relativos al centro del cubo (coordenadas modelo)
  int vertices[8][3] = {
    {-LADO/2, -LADO/2, -LADO/2}, // 0
    { LADO/2, -LADO/2, -LADO/2}, // 1
    { LADO/2, -LADO/2,  LADO/2}, // 2
    {-LADO/2, -LADO/2,  LADO/2}, // 3
    {-LADO/2,  LADO/2, -LADO/2}, // 4
    { LADO/2,  LADO/2, -LADO/2}, // 5
    { LADO/2,  LADO/2,  LADO/2}, // 6
    {-LADO/2,  LADO/2,  LADO/2}  // 7
  };
  
  int projX[8], projY[8];
  for (int i = 0; i < 8; i++) {
    projectPoint(vertices[i][0], vertices[i][1], vertices[i][2], angY, projX[i], projY[i]);
  }
  
  // Aristas (12 líneas)
  int edges[12][2] = {
    {0,1},{1,2},{2,3},{3,0}, // base inferior
    {4,5},{5,6},{6,7},{7,4}, // base superior
    {0,4},{1,5},{2,6},{3,7}  // verticales
  };
  
  for (int i = 0; i < 12; i++) {
    u8g2.drawLine(projX[edges[i][0]], projY[edges[i][0]],
                  projX[edges[i][1]], projY[edges[i][1]]);
  }
}

void setup() {
  u8g2.begin();
  randomSeed(analogRead(0));
  
  // Velocidades iniciales aleatorias (para dar variedad)
  vx = (random(80, 160) / 100.0) * 1.5;
  vy = (random(60, 140) / 100.0) * 1.5;
  if (random(2) == 0) vx = -vx;
  if (random(2) == 0) vy = -vy;
  
  // Asegurar que la posición inicial esté dentro de los límites
  if (cx < limiteIzq) cx = limiteIzq;
  if (cx > limiteDer) cx = limiteDer;
  if (cy < limiteSup) cy = limiteSup;
  if (cy > limiteInf) cy = limiteInf;
}

void loop() {
  // 1. Actualizar posición del cubo
  cx += vx;
  cy += vy;
  
  // 2. Rebotes en los bordes
  if (cx < limiteIzq) { cx = limiteIzq; vx = -vx; }
  if (cx > limiteDer) { cx = limiteDer; vx = -vx; }
  if (cy < limiteSup) { cy = limiteSup; vy = -vy; }
  if (cy > limiteInf) { cy = limiteInf; vy = -vy; }
  
  // 3. Rotación continua
  anguloY += 0.06;   // velocidad angular (ajústala a gusto)
  if (anguloY > 2 * M_PI) anguloY -= 2 * M_PI;
  
  // 4. Dibujar en la pantalla
  u8g2.firstPage();
  do {
    u8g2.clearBuffer();
    
    // (Opcional) marco del área visible - puedes comentarlo si prefieres sin marco
    u8g2.drawFrame(X0, Y0, W, H);
    
    // Dibujar el cubo rotando
    drawCubo(anguloY);
    
  } while (u8g2.nextPage());
  
  delay(30);  // controla la velocidad de animación
}

Puedes visitar los siguiente enlaces relacionados con el microcontrolador ESP32 y Arduino: