Ejercicio 02 — Mostrar texto en una pantalla OLED SSD1306

Dificultad: verde · Módulo 08 (Pantallas)

Enunciado

Mostrar texto en una pantalla OLED SSD1306 (128x64) por I2C y dibujar un gráfico simple (un marco y una barra que crece).

Montaje (Arduino UNO)

OLED SSD1306 0.96” SDA=A4, SCL=A5, VCC=5V, GND=GND Librerías: ‘Adafruit GFX Library’ + ‘Adafruit SSD1306’ (instalar ambas)

Cómo se resuelve

Mismo bus, misma forma de conectar (SDA a A4, SCL a A5, VCC a 5V, GND a GND), pero al otro lado ya no hay una rejilla de 32 caracteres: hay una matriz de píxeles que puedes encender uno a uno. Eso cambia la librería, cambia el sistema de coordenadas y, sobre todo, cambia cuándo se ve lo que dibujas.

Se incluyen tres librerías. Wire.h para el bus, y luego las dos de Adafruit, que hay que instalar por separado: Adafruit_GFX.h y Adafruit_SSD1306.h. Las dos son necesarias y cada una hace una mitad del trabajo. GFX es la parte genérica de dibujo: sabe qué es un rectángulo, cómo se pinta una letra, cómo se recorre una línea, pero no sabe nada de tu pantalla. SSD1306 es la parte específica: sabe arrancar ese chip concreto y mandarle la imagen por I2C. Si instalas solo una, no compila; y esa separación es lo que hace que el mismo código de dibujo sirva luego para una TFT en color cambiando únicamente la librería de abajo.

Las tres constantes describen el hardware:

#define ANCHO 128
#define ALTO   64
#define DIR_OLED 0x3C

128 por 64 píxeles es el tamaño de la OLED de 0,96 pulgadas, y 0x3C es su dirección I2C habitual (algunas unidades vienen en 0x3D). Igual que con el LCD, si la dirección no es la correcta la pantalla se queda negra sin protestar; el escáner del ejercicio 03 es el que lo resuelve.

Luego se crea el objeto de la pantalla:

Adafruit_SSD1306 oled(ANCHO, ALTO, &Wire, -1);

Los dos primeros argumentos son las dimensiones. El tercero, &Wire, es el bus por el que se va a hablar: le estás pasando a la librería el objeto del I2C del UNO, y ese & significa “toma una referencia a este objeto que ya existe”, no una copia. El cuarto, -1, es el pin de reset: muchas OLED tienen una patilla de reinicio propia, pero los módulos I2C de cuatro pines no la sacan, así que se pone -1 para decir “no hay tal pin, no intentes tocarlo”.

En setup(), oled.begin(SSD1306_SWITCHCAPVCC, DIR_OLED) arranca la pantalla en esa dirección. La constante SSD1306_SWITCHCAPVCC le dice al chip que genere internamente, a partir de los 5 V de alimentación, la tensión más alta que necesitan los píxeles para emitir luz. Es la opción normal en estos módulos. Después se limpia el buffer, se fija el tamaño de fuente con oled.setTextSize(1) (tamaño 1 son letras de 6x8 píxeles; el 2 las hace del doble en cada dirección) y el color con oled.setTextColor(SSD1306_WHITE), que en una pantalla monocroma significa simplemente “píxel encendido”.

loop() es donde está la idea importante del ejercicio, y es que cada vuelta redibuja el fotograma entero. No se retoca lo que cambió: se borra todo y se pinta de nuevo. Empieza con oled.clearDisplay(), que apaga todos los píxeles del buffer, y a partir de ahí se reconstruye la imagen.

Primero el texto: oled.setCursor(0, 0) va a la esquina superior izquierda y dos println escriben las dos líneas. Aquí las coordenadas ya no son “columna 0, fila 0” como en el LCD, sino píxeles: el (0, 0) es el píxel de arriba a la izquierda, la x crece hacia la derecha hasta 127 y la y crece hacia abajo hasta 63. Con fuente de tamaño 1 cada línea ocupa 8 píxeles de alto, así que la primera fila de texto llega hasta y=7 y la segunda ocupa de y=8 a y=15.

Después el gráfico, que son dos rectángulos:

oled.drawRect(0, 40, 102, 12, SSD1306_WHITE);     // marco
oled.fillRect(1, 41, barra, 10, SSD1306_WHITE);   // relleno

Los argumentos de ambas funciones son los mismos: esquina superior izquierda (x, y), anchura, altura y color. drawRect dibuja solo el contorno; fillRect rellena. El marco empieza en (0, 40) y mide 102 de ancho por 12 de alto, así que su borde ocupa las líneas x=0 y x=101 y las filas y=40 e y=51. El relleno arranca en (1, 41), justo un píxel dentro del marco por arriba y por la izquierda, y mide 10 de alto, que es exactamente el hueco interior. Esos desplazamientos de un píxel no son casualidad: son lo que evita que la barra pise el borde y lo borre. Y la anchura del relleno no es un número fijo, es la variable barra: ahí es donde el dibujo se vuelve dinámico.

Luego viene la línea sin la cual nada de lo anterior existe:

oled.display();

Todo lo que han hecho clearDisplay, println, drawRect y fillRect ha ocurrido en un buffer en la memoria RAM del Arduino, no en la pantalla. display() es la orden de mandar ese buffer entero por el bus I2C al chip SSD1306. Si te la olvidas, el programa compila, sube y corre sin ningún error, y la pantalla se queda en negro. Es, con diferencia, el fallo más frecuente con estas pantallas, y por eso el modelo lo lleva comentado en mayúsculas.

El movimiento lo produce el final del loop(): barra = barra + 5; y, si pasa de 100, vuelve a 0. Con delay(200) entre fotogramas, la barra crece de cinco en cinco píxeles cada 200 milisegundos, tarda 21 fotogramas en llegar de 0 a 100 y completa el ciclo en poco más de cuatro segundos. Fíjate en que el límite es 100 y el hueco interior del marco mide justo 100 píxeles de ancho: cuando la barra vale 100 el marco queda lleno exactamente, sin desbordarse.

Merece la pena que compares mentalmente este ejercicio con el anterior. En el LCD escribías espacios a mano para tapar los restos del número viejo; aquí no hace falta ningún truco de ese tipo porque el clearDisplay() inicial borra el fotograma completo. La contrapartida es el coste: cada vuelta manda a la pantalla el kilobyte entero del buffer, ocupe lo que ocupe el cambio. Y ese kilobyte también está permanentemente reservado en la RAM del UNO, que solo tiene dos: es la razón por la que un programa grande con OLED empieza a dar avisos de memoria baja.

Para practicar — cópialo y complétalo

Pega este esqueleto y completa los TODO. Es la mejor forma de aprender: inténtalo antes de mirar la solución.

/*
 * Curso de Arduino — Módulo 08: Pantallas (LCD/OLED, I2C)
 * Ejercicio 02 — PRÁCTICA (rellena los TODO)
 * Enunciado: Mostrar texto en una pantalla OLED SSD1306 (128x64) por I2C
 *            y dibujar un gráfico simple (un marco y una barra que crece).
 * Dificultad: verde
 * Placa: Arduino UNO    Montaje: OLED SSD1306 0.96" -> SDA=A4, SCL=A5, VCC=5V, GND=GND
 * Librerías: 'Adafruit GFX Library' + 'Adafruit SSD1306' (instalar ambas)
 * Correr: Arduino IDE / Wokwi (wokwi/ej02) / arduino-cli (ver README)
 */
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
 
#define ANCHO 128
#define ALTO   64
#define DIR_OLED 0x3C
 
Adafruit_SSD1306 oled(ANCHO, ALTO, &Wire, -1);
 
int barra = 0;
 
void setup() {
    // TODO: arranca la OLED -> oled.begin(SSD1306_SWITCHCAPVCC, DIR_OLED);
    // TODO: limpia el buffer -> oled.clearDisplay();
    // TODO: fija tamaño de texto 1 -> oled.setTextSize(1);
    // TODO: fija color blanco -> oled.setTextColor(SSD1306_WHITE);
}
 
void loop() {
    // TODO: borra el frame anterior -> oled.clearDisplay();
    // TODO: pon el cursor en (0,0) y escribe dos líneas de texto (oled.println)
    // TODO: dibuja el marco -> oled.drawRect(0, 40, 102, 12, SSD1306_WHITE);
    // TODO: dibuja el relleno -> oled.fillRect(1, 41, barra, 10, SSD1306_WHITE);
    // TODO: VUELCA el buffer a la pantalla -> oled.display();
    // TODO: incrementa 'barra' en 5 y reinicia a 0 si pasa de 100
    // TODO: espera 200 ms -> delay(200);
}

Solución — cópiala y ejecútala

/*
 * Curso de Arduino — Módulo 08: Pantallas (LCD/OLED, I2C)
 * Ejercicio 02 — MODELO (resuelto)
 * Enunciado: Mostrar texto en una pantalla OLED SSD1306 (128x64) por I2C
 *            y dibujar un gráfico simple (un marco y una barra que crece).
 * Dificultad: verde
 * Placa: Arduino UNO    Montaje: OLED SSD1306 0.96" -> SDA=A4, SCL=A5, VCC=5V, GND=GND
 * Librerías: 'Adafruit GFX Library' + 'Adafruit SSD1306' (instalar ambas)
 * Correr: Arduino IDE / Wokwi (wokwi/ej02) / arduino-cli (ver README)
 */
#include <Wire.h>                       // bus I2C
#include <Adafruit_GFX.h>               // primitivas gráficas (texto, líneas, rect.)
#include <Adafruit_SSD1306.h>           // driver concreto del chip SSD1306
 
#define ANCHO 128                       // píxeles en horizontal
#define ALTO   64                       // píxeles en vertical
#define DIR_OLED 0x3C                   // dirección I2C típica de la OLED
 
// pantalla sin pin de reset propio (-1 = compartido con el del UNO):
Adafruit_SSD1306 oled(ANCHO, ALTO, &Wire, -1);
 
int barra = 0;                          // ancho actual de la barra (0..100)
 
void setup() {
    oled.begin(SSD1306_SWITCHCAPVCC, DIR_OLED);  // arranca generando su propio voltaje
    oled.clearDisplay();                // limpia el buffer en memoria
    oled.setTextSize(1);                // tamaño de fuente (1 = 6x8 px)
    oled.setTextColor(SSD1306_WHITE);   // píxeles encendidos = blanco
}
 
void loop() {
    oled.clearDisplay();                // borra el frame anterior
 
    oled.setCursor(0, 0);               // esquina superior izquierda
    oled.println("Curso Arduino");
    oled.println("OLED SSD1306");
 
    oled.drawRect(0, 40, 102, 12, SSD1306_WHITE);     // marco de la barra
    oled.fillRect(1, 41, barra, 10, SSD1306_WHITE);   // relleno proporcional
 
    oled.display();                     // VUELCA el buffer a la pantalla (sin esto no se ve)
 
    barra = barra + 5;                  // la barra avanza
    if (barra > 100) barra = 0;         // y reinicia al llegar al final
    delay(200);
}

Cómo ejecutar la simulación

Sin instalar nada, en el navegador

  1. Abre wokwi.com y crea un proyecto nuevo de la placa que pide el ejercicio.
  2. Pega la solución (o tu intento) en la pestaña sketch.ino.
  3. Abre la pestaña diagram.json, borra lo que haya y pega el circuito de aquí abajo.
  4. Dale al play. Verás el circuito funcionando: LEDs, pantallas y monitor serie.
Circuito del ejercicio (diagram.json)
{
  "version": 1,
  "author": "ESIBot",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-uno", "id": "uno", "top": 0, "left": 0, "attrs": {} },
    { "type": "wokwi-ssd1306", "id": "oled1", "top": -110, "left": 180, "attrs": {} }
  ],
  "connections": [
    [ "uno:A4", "oled1:SDA", "green", [] ],
    [ "uno:A5", "oled1:SCL", "yellow", [] ],
    [ "uno:5V", "oled1:VCC", "red", [] ],
    [ "uno:GND.1", "oled1:GND", "black", [] ]
  ],
  "dependencies": {}
}

En VS Code, con el repo del curso

El paquete de simulación de este ejercicio está en practica/08-pantallas/wokwi/ej02/. Con la extensión Wokwi instalada y la licencia activada (lo cuenta el onboarding del curso):

make sim EJ=08-pantallas/ej02

y después, en VS Code, Ctrl+Shift+PWokwi: Start Simulator. La extensión no compila: simula un firmware ya compilado, y de compilarlo se encarga ese make.

Cómo usarlo

Con placa: abre el .ino en el Arduino IDE, elige placa y puerto y pulsa Subir. Sin placa: simulalo en Wokwi con cualquiera de las dos vias de arriba. Antes de mirar la solucion, intenta completar tu el esqueleto: es la mejor forma de aprender.

Conexiones