Ejercicio 03 — Escáner I2C

Dificultad: amarillo · Módulo 08 (Pantallas)

Enunciado

Escáner I2C. Recorre todas las direcciones del bus (1..126) y muestra por el Monitor Serie cuáles responden. Útil para saber qué dirección tiene tu LCD u OLED (0x27, 0x3F, 0x3C…).

Montaje (Arduino UNO)

Cualquier dispositivo I2C SDA=A4, SCL=A5, VCC=5V, GND=GND Librería: ninguna extra (Wire viene con el IDE). Monitor Serie a 9600 baudios.

Cómo se resuelve

Este ejercicio no dibuja nada: construye una herramienta. Es el programa que vas a copiar en todos tus proyectos futuros con I2C, porque responde en treinta segundos a la pregunta que más tiempo hace perder, que es “¿por qué no responde mi pantalla?“.

El montaje del simulador conecta a la vez el LCD 1602 y la OLED al mismo bus: SDA de las dos al pin A4, SCL de las dos al A5, VCC a 5V y GND a GND. Ver dos dispositivos distintos colgando de los mismos dos cables de señal es la demostración física de todo lo que dice la teoría del módulo. Con un dispositivo suelto también funciona, claro.

Aquí solo se incluye Wire.h, que viene con el IDE: ninguna librería de pantalla, porque no vamos a hablar con ninguna pantalla en particular. Vamos a hablar con el bus.

setup() hace tres cosas. Wire.begin() sin argumentos entra al bus como maestro, que es el papel del Arduino: el que inicia todas las conversaciones. Serial.begin(9600) abre el puerto serie, porque en este ejercicio la salida no es una pantalla sino el Monitor Serie del ordenador, y hay que abrirlo a esa misma velocidad de 9600 baudios o solo verás caracteres raros. Y un println inicial que avisa de que el escáner ha arrancado; ese mensaje también sirve para confirmar que la placa se ha reiniciado y que el monitor está bien configurado.

El corazón del programa es el bucle de loop():

for (byte dir = 1; dir < 127; dir++) {
    Wire.beginTransmission(dir);
    byte error = Wire.endTransmission();
    if (error == 0) { ... }
}

El rango va de 1 a 126 porque una dirección I2C tiene 7 bits, es decir, 128 valores posibles de 0 a 127, y los extremos están reservados por el protocolo (la 0 es la llamada general y el tramo alto está reservado también). Recorrerlas todas es viable porque son poquísimas y cada intento tarda microsegundos: el barrido completo es prácticamente instantáneo.

El truco está en la pareja beginTransmission / endTransmission. beginTransmission(dir) no manda nada todavía: solo prepara una transmisión dirigida a esa dirección. Como en medio no se escribe ningún dato, lo que hace endTransmission() es poner en el bus la dirección a secas y ver qué pasa. Es el equivalente a llamar a una puerta y colgar sin decir nada. El valor que devuelve es la respuesta: 0 significa que alguien contestó con un ACK, o sea, que en esa dirección hay un dispositivo vivo y escuchando. Cualquier otro valor significa que nadie respondió, que es lo normal en 125 de las 126 direcciones.

Cuando hay respuesta, se imprime la dirección con un detalle de formato que conviene mirar:

Serial.print("  Dispositivo en 0x");
if (dir < 16) Serial.print("0");
Serial.println(dir, HEX);

El segundo argumento HEX en Serial.println es lo que hace que el número salga en hexadecimal en vez de en decimal. Es importante, porque las direcciones I2C siempre se documentan así: la del LCD es 0x27, no 39, aunque sean el mismo número. Y el if de en medio arregla una fea costumbre de Serial.print: los valores menores que 16 se imprimen con un solo dígito (0xA en vez de 0x0A), así que se añade un cero delante a mano para que todas las direcciones salgan alineadas con dos dígitos. Es cosmético, pero cuando estás comparando una lista de direcciones con la constante de tu código, que estén escritas igual ayuda.

La variable encontrados se pone a cero al principio de cada barrido y suma uno por cada respuesta. Sirve para el caso importante: si al terminar el recorrido sigue valiendo 0, se imprime el aviso de que no hay ninguno. Y esa distinción es justamente lo que convierte el escáner en una herramienta de diagnóstico en vez de en una curiosidad. Léelo así:

Si no aparece nada, el problema no es de software: o los cables están mal (SDA y SCL cruzados es el clásico, porque A4 y A5 están uno al lado del otro), o falta la alimentación, o falta GND, o no hay pull-ups en el bus. Si aparece una dirección, el bus funciona y el cableado es correcto; entonces lo único que puede fallar es que esa dirección no coincida con la que tienes escrita en tu sketch, y la solución es copiar en el código la que salga. Poder separar esos dos mundos de un vistazo es la mitad de la depuración de cualquier montaje I2C.

El delay(3000) del final hace que el barrido se repita cada tres segundos en lugar de una sola vez. Podría haberse escrito todo en setup() y ejecutarse una única vez, pero repetir tiene una ventaja práctica muy real: puedes ir enchufando y desenchufando módulos con el programa corriendo y ver aparecer y desaparecer sus direcciones en tiempo real. Es la forma más rápida de identificar cuál es cuál cuando tienes varios conectados a la vez.

Con el montaje de dos pantallas deberías ver dos líneas en cada barrido, típicamente 0x27 (el LCD) y 0x3C (la OLED). Ahí está la respuesta a la pregunta de la teoría: dos dispositivos, dos cables, dos nombres distintos. Si los dos tuvieran la misma dirección, el escáner solo vería una y el bus no serviría para nada, y por eso muchos módulos traen un puente soldable para cambiarla.

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 03 — PRÁCTICA (rellena los TODO)
 * Enunciado: Escáner I2C. Recorre todas las direcciones del bus (1..126) y
 *            muestra por el Monitor Serie cuáles responden. Útil para saber
 *            qué dirección tiene tu LCD u OLED (0x27, 0x3F, 0x3C...).
 * Dificultad: amarillo
 * Placa: Arduino UNO    Montaje: cualquier dispositivo I2C -> SDA=A4, SCL=A5, VCC=5V, GND=GND
 * Librería: ninguna extra (Wire viene con el IDE). Monitor Serie a 9600 baudios.
 * Correr: Arduino IDE / Wokwi (wokwi/ej03) / arduino-cli (ver README)
 */
#include <Wire.h>
 
void setup() {
    // TODO: entra al bus como maestro -> Wire.begin();
    // TODO: abre el serie a 9600 -> Serial.begin(9600);
    // TODO: imprime un mensaje inicial
}
 
void loop() {
    int encontrados = 0;
 
    // TODO: recorre dir de 1 a 126 con un for
    //   - Wire.beginTransmission(dir);
    //   - byte error = Wire.endTransmission();
    //   - si error == 0: imprime "  Dispositivo en 0x" + Serial.println(dir, HEX) y suma 1 a 'encontrados'
 
    // TODO: si 'encontrados' == 0, avisa de que no hay ninguno
    // TODO: imprime un separador "---"
    // TODO: espera 3 segundos -> delay(3000);
}

Solución — cópiala y ejecútala

/*
 * Curso de Arduino — Módulo 08: Pantallas (LCD/OLED, I2C)
 * Ejercicio 03 — MODELO (resuelto)
 * Enunciado: Escáner I2C. Recorre todas las direcciones del bus (1..126) y
 *            muestra por el Monitor Serie cuáles responden. Útil para saber
 *            qué dirección tiene tu LCD u OLED (0x27, 0x3F, 0x3C...).
 * Dificultad: amarillo
 * Placa: Arduino UNO    Montaje: cualquier dispositivo I2C -> SDA=A4, SCL=A5, VCC=5V, GND=GND
 * Librería: ninguna extra (Wire viene con el IDE). Monitor Serie a 9600 baudios.
 * Correr: Arduino IDE / Wokwi (wokwi/ej03) / arduino-cli (ver README)
 */
#include <Wire.h>                       // librería del bus I2C
 
void setup() {
    Wire.begin();                       // entra al bus como maestro
    Serial.begin(9600);
    Serial.println("Escaner I2C iniciado.");
}
 
void loop() {
    int encontrados = 0;
    Serial.println("Buscando dispositivos...");
 
    for (byte dir = 1; dir < 127; dir++) {   // 7 bits útiles -> direcciones 1..126
        Wire.beginTransmission(dir);          // intenta hablar con esa dirección
        byte error = Wire.endTransmission();  // 0 = alguien respondió (ACK)
 
        if (error == 0) {
            Serial.print("  Dispositivo en 0x");
            if (dir < 16) Serial.print("0");  // un cero a la izquierda para que quede 0x0A, etc.
            Serial.println(dir, HEX);         // imprime la dirección en hexadecimal
            encontrados++;
        }
    }
 
    if (encontrados == 0) {
        Serial.println("  Ninguno (revisa cableado y pull-ups).");
    }
    Serial.println("---");
    delay(3000);                        // repite el barrido cada 3 s
}

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-lcd1602", "id": "lcd1", "top": -120, "left": 160, "attrs": { "pins": "i2c" } },
    { "type": "wokwi-ssd1306", "id": "oled1", "top": 40, "left": 200, "attrs": {} }
  ],
  "connections": [
    [ "uno:A4", "lcd1:SDA", "green", [] ],
    [ "uno:A5", "lcd1:SCL", "yellow", [] ],
    [ "uno:5V", "lcd1:VCC", "red", [] ],
    [ "uno:GND.1", "lcd1:GND", "black", [] ],
    [ "uno:A4", "oled1:SDA", "green", [] ],
    [ "uno:A5", "oled1:SCL", "yellow", [] ],
    [ "uno:5V", "oled1:VCC", "red", [] ],
    [ "uno:GND.2", "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/ej03/. Con la extensión Wokwi instalada y la licencia activada (lo cuenta el onboarding del curso):

make sim EJ=08-pantallas/ej03

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