Conexión rápida de una pantalla OLED a un ESP32


Conectar una pantalla TFT pequeña a un ESP32 parece, sobre el papel, una tarea sencilla: cuatro cables SPI, una librería y listo. La realidad, como suele pasar en electrónica, es bastante distinta. En este artículo quiero contar el proceso completo, incluyendo los errores, las dudas y la solución final estable, porque estoy seguro de que le ahorrará tiempo a más de uno.

La pantalla en cuestión es una TFT IPS de 0,96 pulgadas, resolución 80×160, (exactamente este modelo https://amzn.to/4p0DjKF ) basada en el controlador ST7735S, muy similar (y compatible) con la conocida Adafruit Mini TFT 0.96″ 160×80. El microcontrolador es un ESP32 DevKit con módulo ESP-WROOM-32.

1. El hardware de partida

El montaje inicial estaba compuesto por:

  • ESP32 DevKit (ESP-WROOM-32)–> comprado en Amazon por unos 11.99€
  • Pantalla TFT IPS 0,96” 80×160 SPI (ST7735S)–>comprada en Amazon por unos 6.99€
  • Comunicación SPI
  • Alimentación desde el propio ESP32

Un punto importante: aunque el módulo TFT permite alimentación a 5 V porque incorpora un regulador en la propia placa, finalmente decidí alimentarlo a 3,3 V directamente desde el ESP32. Es más limpio, más seguro y suficiente para un funcionamiento correcto.

2. Primer obstáculo: el ESP32 no programaba

Antes incluso de ver algo en pantalla, apareció el primer problema serio:el ESP32 no se dejaba programar. El Arduino IDE devolvía errores del tipo:

Failed to connect / no serial data received / exit status 2

En Windows, el chip CP2102 aparecía con el clásico triángulo amarillo en el Administrador de dispositivos. La solución fue sencilla, pero nada obvia para quien empieza con un PC sin todo bien configurado:

  • Instalar el driver oficial CP210x USB-to-UART de Silicon Labs
  • Seleccionar el puerto COM correcto en el Arduino IDE
  • Bajar la velocidad de carga a 115200 baudios
  • Desconectar cualquier periférico hasta conseguir un programa mínimo estable (un Blink)

Una vez superado esto, el ESP32 empezó a comportarse como debía.

3. Pantalla encendida… pero en negro

El siguiente síntoma fue frustrante: la pantalla encendía la retroiluminación, pero no mostraba absolutamente nada.

El primer intento fue con la popular librería TFT_eSPI, que suele funcionar muy bien con ESP32. Sin embargo, aquí empezaron los problemas:

  • El archivo User_Setup_Select.h estaba prácticamente todo comentado
  • No había un controlador ST7735 correctamente definido
  • Los pines SPI no coincidían con los que estaba usando

Incluso después de configurar explícitamente ST7735 y definir pines personalizados (MOSI, SCLK, CS, DC, RST), ningún ejemplo funcionaba, ni siquiera los más básicos.

4. ¿La habré dañado alimentándola a 5 V?

En ese punto surgió la duda clásica:“¿Habré quemado la pantalla?”

Investigando el modelo exacto, encontré que este tipo de módulos incluye un regulador de tensión, exactamente igual que los de Adafruit, diseñados para:

  • VCC = 5 V
  • Lógica SPI a 3,3 V

Conclusión: la probabilidad de daño era baja. El problema no era eléctrico, sino de driver y configuración.

5. El verdadero lío: los buses SPI del ESP32

Aquí estaba una de las claves del problema.

El ESP32 dispone de dos buses SPI por hardware:

  • VSPI (por defecto):
    • SCK = GPIO18
    • MOSI = GPIO23
    • MISO = GPIO19
  • HSPI (alternativo):
    • SCK = GPIO14
    • MOSI = GPIO13
    • MISO = GPIO12 / 19 (configurable)

Yo ya tenía un lector de tarjeta SD funcionando en VSPI, y no quería tocarlo. Intentar colgar la TFT del mismo bus solo complicaba la configuración.

La solución fue clara: 👉 dedicar HSPI exclusivamente a la pantalla TFT.

6. Cambio de estrategia: librerías de Adafruit

La pista definitiva llegó revisando una reseña del propio módulo TFT, donde alguien lo hacía funcionar como si fuera una Adafruit Mini TFT 0.96″ 160×80.

Eso implicaba dos cosas importantes:

  • Usar las librerías:
    • Adafruit_GFX
    • Adafruit_ST7735
  • Inicializar la pantalla con: tft.initR(INITR_MINI160x80);

Este detalle es crítico: no todas las pantallas ST7735 se inicializan igual.

Tras instalar ambas librerías desde el gestor del Arduino IDE, todo empezó a encajar.

7. Conexión definitiva de la pantalla al ESP32

Separando claramente los buses SPI, el esquema final quedó así:

SPI

  • VSPI → Tarjeta SD
  • HSPI → Pantalla TFT

Conexiones TFT → ESP32

  • GND → GND
  • VCC → 3V3
  • SCL (SCK) → GPIO14
  • SDA (MOSI) → GPIO13
  • CS → GPIO26
  • DC → GPIO27
  • RES → GPIO12
  • BLK → 3V3 (retroiluminación permanente)

De este modo, una TFT y un futuro lector/grabador de SD pueden funcionar simultáneamente sin interferencias.

8. Código final que funciona (por fin)

Una prueba basica una vez instaladas desde el propio IDE de Arduino las librerías de Adafruit (Adafruit_GFX.h y Adafruit_ST7735) es el siguiente código funcional:

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>

#define TFT_CS   26
#define TFT_DC   27
#define TFT_RST  12

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

void setup() {
  // Usar HSPI con 14 (SCK) y 13 (MOSI)
  SPI.begin(14, 19, 13);  // SCK=14, MISO=19 (no usado), MOSI=13

  tft.initR(INITR_MINI160x80);  // 0.96" 160x80
  tft.setRotation(3);

  tft.fillScreen(ST77XX_RED);
  delay(1000);
  tft.fillScreen(ST77XX_GREEN);
  delay(1000);
  tft.fillScreen(ST77XX_BLUE);
  delay(1000);
  tft.fillScreen(ST77XX_BLACK);
  tft.setTextColor(ST77XX_WHITE);
  tft.setCursor(0, 0);
  tft.println("Hola");
}

void loop() {}

!Funciona !, ya se ven colores al prinicpio a pantalla completa y al final de la secuencia un texto, pero como inconveniente del código anterior es que el texto que se muestra es demasiado pequeño para poderse leer.

Esta claro que el codigo anterior se puede mejorar. Este es el sketch de prueba definitivo, limpio y funcional:

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>

#define TFT_CS   26
#define TFT_DC   27
#define TFT_RST  12

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

void setup() {
  // HSPI: SCK=14, MISO=19, MOSI=13
  SPI.begin(14, 19, 13);

  tft.initR(INITR_MINI160x80);
  tft.setRotation(3);
  tft.fillScreen(ST77XX_BLACK);
  tft.setTextSize(2);

  tft.setTextColor(ST77XX_RED, ST77XX_BLACK);
  tft.setCursor(5, 10);
  tft.print("0123456789");

  tft.setTextColor(ST77XX_GREEN, ST77XX_BLACK);
  tft.setCursor(5, 35);
  tft.print("0123456789");

  tft.setTextColor(ST77XX_BLUE, ST77XX_BLACK);
  tft.setCursor(5, 60);
  tft.print("0123456789");
}

void loop() {
}

El resultado: imagen estable, colores correctos y orientación perfecta.

9. Conclusiones importantes

Si estás intentando conectar una pantalla TFT pequeña a un ESP32, quédate con estas ideas clave:

  • El ESP32 tiene dos buses SPI: aprovéchalos
  • No todas las pantallas ST7735 usan la misma inicialización
  • La librería Adafruit_ST7735 es a veces más fiable que TFT_eSPI
  • Que la pantalla se ilumine no significa que esté bien configurada
  • Alimentar a 3,3 V suele ser la opción más segura

En el próximo proyecto, esta TFT ya no será un problema… pero este “viaje” merecía ser contado.

Emulación Zumo de sonidos con ATmega


Como adelantábamos en un post anterior sobre la emulación del robot Zumo32 con un ATMEGA, en este post vamos a tratar un tema tana atractivo con el sonido usando para ello solo el buzzer emulado (ojo porqeu solo disponemos de un unico bit para obtener toda la funcionalidad).

Para los amigos lectores que se pregunten si es equivalente en ambiente Arduino la instrucción tone (puzzer_pin,nota) por buzzer.playNote(nota,1000,15),  NO son equivalentes exactamente, dado que la primera instrucción corresponde al código nativo de Arduino , y el segundo requiere la librería de Zumo32 y por tanto va condicionada a usar un Zumo32 o en su defecto un Arduino Leonardo ( de modo que en nuestro Atmega32 no podremos usar el segundo a no ser que modifiquemos la librería original.

tone(BUZZER_PIN, nota) y buzzer.playNote(nota, 1000, 15) tienen diferencias funcionales críticas:

Diferencias Técnicas

Aspectotone(pin, freq)buzzer.playNote(freq, duration, octave)
Librería✅ Nativa Arduino❌ Zumo32U4Buzzer (solo 32U4)
DuraciónInfinita (hasta noTone())1000ms (automática)
OctavaIgnora parámetroOctava 15 (frecuencia alterada)
VolumenFijoVariable (polifonía Zumo)
Blocking✅ No bloquea❌ Puede bloquear

Por tanto el código  CORRECTO

tone(BUZZER_PIN, notas[notaActual]);  // Reproduce INDEFINIDAMENTE
// ...
noTone(BUZZER_PIN);  // Para manualmente

Funciona perfecto en ATmega328P sin librerías externas.

buzzer.playNote() Sintaxis Completa

cpp// Zumo32U4Buzzer (SOLO ATmega32U4)
buzzer.playNote(NOTE_C(4), 1000);  // Frecuencia C4, 1000ms

Parámetros:
  • notaNOTE_C(4)NOTE_G(5), etc. (predefinidas)
  • duration: ms automático
  • octave: 0-8 (no 15, error en tu ejemplo)

Equivalencia Aproximada (No Exacta)

// ANTES (tu código - CORRECTO)
tone(BUZZER_PIN, 262); // C4 infinito
delay(1000);
noTone(BUZZER_PIN);

// "Similar" a:
buzzer.playNote(NOTE_C(4), 1000); // C4 x 1s automático

Recomendación para la emulacion de Zumo32 con ATmega328P:

Mantén tone() + noTone() – es más preciso y flexible:

// Tu código actual es ÓPTIMO
if (millis() - tiempoInicio >= 1000) {
tone(BUZZER_PIN, notas[notaActual]);
// ... display
// noTone() en pausas
}

A cambio buzzer.playNote() solo para Zumo32U4 real (no para una emulación con ATmega328P pero si funcional para Arduino Leornardo).

​EJEMPLO PRACTICO CON LA EMULACION DEL ZUMO 32 EN UNA PLACA ARDUINO ATMEGA32

Para comprender la potencia de no trabajar con librerías externas donde se supone que esta todo hecho , pero que a cambio no dejan apenas flexibilidad al programador , vamos a ver un ejemplo de como reproducir tres escalas musicales con la emulación de Zumo usando un ATmega328P o en la práctica cualquier Arduino:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x3F, 16, 2);

#define BUZZER_PIN 6
#define BUTTON_A_PIN A0
#define BUTTON_B_PIN 4
#define BUTTON_C_PIN A3
#define LED_YELLOW_PIN 13
#define LED_RED_PIN 12

bool ledYellowState = false;
bool lastBtnA = false, lastBtnB = false;

// Escala completa C3 (130Hz) → B5 (1976Hz) - 3 octavas
int notas[] = {
  // C3 (65*2=130), D3(73*2=146), E3(82*2=164), F3(87*2=174), G3(98*2=196), A3(110*2=220), B3(123*2=246)
  130, 146, 164, 174, 196, 220, 246,
  // C4 (262), D4(294), E4(330), F4(349), G4(392), A4(440), B4(494)
  262, 294, 330, 349, 392, 440, 494,
  // C5 (523), D5(587), E5(659), F5(698), G5(784), A5(880), B5(988)
  523, 587, 659, 698, 784, 880, 988
};

String nombres[] = {
  "C3","D3","E3","F3","G3","A3","B3",
  "C4","D4","E4","F4","G4","A4","B4",
  "C5","D5","E5","F5","G5","A5","B5"
};

int numNotas = 21;  // 3 octavas × 7 notas
int escalaActual = 0;  // 0,1,2 (3 escalas)

void setup() {
  Wire.begin();
  lcd.init(); lcd.backlight(); lcd.clear();
  lcd.setCursor(0,0); lcd.print("3 Escalas C3-B5");
  lcd.setCursor(0,1); lcd.print("Iniciando...");
  delay(2000);
  
  pinMode(BUTTON_A_PIN, INPUT_PULLUP);
  pinMode(BUTTON_B_PIN, INPUT_PULLUP);
  pinMode(BUTTON_C_PIN, INPUT_PULLUP);
  pinMode(LED_YELLOW_PIN, OUTPUT);
  pinMode(LED_RED_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
}

void loop() {
  bool btnA = digitalRead(BUTTON_A_PIN) == LOW;
  bool btnB = digitalRead(BUTTON_B_PIN) == LOW;
  bool btnC = digitalRead(BUTTON_C_PIN) == LOW;
  
  // Toggle LED Amarillo
  bool btnA_pressed = btnA && !lastBtnA;
  bool btnB_pressed = btnB && !lastBtnB;
  if (btnA_pressed) ledYellowState = true;
  if (btnB_pressed) ledYellowState = false;
  digitalWrite(LED_YELLOW_PIN, ledYellowState);
  digitalWrite(LED_RED_PIN, !btnA);
  
  // REPRODUCCIÓN 3 ESCALAS COMPLETAS C3→B5
  static int notaActual = 0;
  static unsigned long tiempoInicio = 0;
  static bool reproduciendo = true;
  
  if (reproduciendo && millis() - tiempoInicio >= 1000) {
    tone(BUZZER_PIN, notas[notaActual]);
    
    // Mostrar nota actual
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Escala "); lcd.print(escalaActual + 1);
    lcd.print("/3 Nota:");
    
    lcd.setCursor(0, 1);
    lcd.print(nombres[notaActual]);
    lcd.print(" "); lcd.print(notas[notaActual]);
    lcd.print("Hz ");
    
    notaActual++;
    
    if (notaActual >= 7) {  // Fin de escala (7 notas)
      escalaActual++;
      notaActual = 0;
      noTone(BUZZER_PIN);
      delay(500);  // Pausa entre escalas
    }
    
    if (escalaActual >= 3) {  // 3 escalas completas
      reproduciendo = false;
      lcd.clear();
      lcd.setCursor(0,0); lcd.print("3 ESCALAS OK!");
      lcd.setCursor(0,1); lcd.print("C3-B5 Fin");
      noTone(BUZZER_PIN);
    }
    
    tiempoInicio = millis();
  }
  
  lastBtnA = btnA;
  lastBtnB = btnB;
  delay(10);
}

Este código extiende la emulación del Zumo32U4 añadiendo un buzzer musical que reproduce automáticamente 3 escalas completas (C3-B5) usando la función tone(), mientras mantiene el control de LEDs y pulsadores del ejemplo anterior. En setup() inicializa el LCD I2C (0x3F), configura los pines (A0=A, 4=B, A3=C para botones; 13=amarillo, 12=rojo, 6=buzzer) y muestra «3 Escalas C3-B5». Define dos arrays paralelos: notas[] con 21 frecuencias (130Hz C3 hasta 988Hz B5, 3 octavas × 7 notas diatónicas) y nombres[] con sus identificadores («C3″,»D3″,…,»B5»).​

En loop(), primero gestiona los botones con detección de flanco (anti-rebote): A enciende LED amarillo (latch), B apaga LED amarillo (latch), LED rojo sigue inverso de A instantáneamente. Luego, una máquina de estados automática con variables static (notaActual, tiempoInicio, reproduciendo) reproduce las escalas: cada 1000ms llama tone(6, frecuencia) de la nota actual, actualiza el LCD mostrando «Escala X/3 Nota: Nombre frecuenciaHz», avanza notaActual. Al llegar a la 7ª nota (fin escala), pasa a la siguiente escalaActual, pausa 500ms con noTone() y reinicia notaActual=0. Tras 3 escalas (21 notas total), para con reproduciendo=false y mensaje «3 ESCALAS OK!».

La reproducción es no bloqueante (usa millis() para timing), permitiendo que los botones respondan simultáneamente. El delay(10) final estabiliza lecturas sin interferir con los 1000ms de nota. Emula perfectamente el Zumo32U4Buzzer de Pololu, que usa PWM en pin 6 (PD7) para tonos precisos, demostrando control musical preciso en ATmega328P con pines mapeados equivalentes (pin 6 Uno → pin 6/PD7 Zumo).

En el siguiente video podemos ver el código anterior explicado por el autor que escribe estas líneas https://youtube.com/shorts/QYoSJSKwREc?feature=share

En el siguiente ejemplo ahora podemos ver para la misma funcionalidad (a excepción del LCD y los leds) las grandes diferencias del código anterior con el código para el propio robot usando su librería Zumo32U4 en lugar de código nativo.

#include <Wire.h>
#include <Zumo32U4.h>

Zumo32U4Buzzer buzzer;

void setup() {
}

void loop() {
  buzzer.playFrequency(440, 200, 12);
  delay(1000);

  for (int i = 3; i <= 5; i++) {

    buzzer.playFrequency(NOTE_C(i), 1000, 15);
    while (buzzer.isPlaying()) {}
    delay(1000);

    buzzer.playFrequency(NOTE_D(i), 1000, 15);
    while (buzzer.isPlaying()) {}
    delay(1000);

    buzzer.playFrequency(NOTE_E(i), 1000, 15);
    while (buzzer.isPlaying()) {}
    delay(1000);

    buzzer.playFrequency(NOTE_F(i), 1000, 15);
    while (buzzer.isPlaying()) {}
    delay(1000);

    buzzer.playFrequency(NOTE_G(i), 1000, 15);
    while (buzzer.isPlaying()) {}
    delay(1000);

    buzzer.playFrequency(NOTE_A(i), 1000, 15);
    while (buzzer.isPlaying()) {}
    delay(1000);

    buzzer.playFrequency(NOTE_B(i), 1000, 15);
    while (buzzer.isPlaying()) {}
    delay(1000);
  }
}

Este programa como ya adelantábamos usa la librería del Zumo32U4 para que el robot reproduzca tonos musicales con su zumbador integrado. Primero crea un objeto Zumo32U4Buzzer y, en el loop(), genera un pitido inicial de 440 Hz durante 200 ms con volumen 12 y espera 1 segundo. Después entra en un bucle for que recorre las octavas 3, 4 y 5; en cada octava va llamando a buzzer.playFrequency(NOTE_C(i) ... NOTE_B(i)) para reproducir las siete notas de la escala (Do, Re, Mi, Fa, Sol, La, Si) con duración de 1 segundo y volumen 15, esperando a que termine cada nota con while (buzzer.isPlaying()) {} y añadiendo un segundo de pausa entre ellas, de forma que se escuchan tres escalas ascendentes completas en distintas octavas usando las constantes de nota que proporciona la propia librería del Zumo32U4​.