← Índice del curso

Comunicación entre Dispositivos · Tema 20 · Taller

Taller: bus I²C con varios dispositivos

Cierre del bloque de I²C con un proyecto completo: una estación de registro en la que un ESP32 lee un sensor BME280, pone fecha y hora con un reloj DS3231, guarda las mediciones en una memoria EEPROM y las muestra en una pantalla OLED, con los cuatro dispositivos sobre los mismos dos hilos. Vamos a repartir direcciones, calcular las pull-ups, leer un reloj en BCD, escribir una memoria por páginas, proteger los registros con un CRC, medir cuánto tiempo ocupa cada cosa en el bus y probar todo por etapas.

01 · Objetivo

Qué vamos a construir

En los cuatro temas anteriores estudiamos I²C por partes: las dos líneas de colector abierto, el protocolo con sus direcciones y confirmaciones, los registros y la biblioteca Wire, y los problemas reales. En este taller las juntamos. Un solo bus con cuatro dispositivos y un programa que cumple una tarea completa: medir, fechar, guardar y mostrar.

  • Un BME280 (dirección 0x76): temperatura, humedad y presión.
  • Un reloj de tiempo real DS3231 (0x68): dice qué fecha y hora son, con una pila que lo mantiene en marcha sin alimentación. Sus registros están en BCD.
  • Una memoria EEPROM 24C32 (0x57), de 4 KB, que guarda un registro de 8 bytes por medición y conserva los datos sin energía. Se escribe por páginas y tarda unos milisegundos en cada escritura. Los módulos del DS3231 la traen en la misma placa.
  • Una pantalla OLED SSD1306 de 128 × 64 (0x3C), que muestra la última medición y es la que más tiempo de bus consume.

Cada uno enseña algo distinto. El reloj, la conversión de BCD; la memoria, las reglas de escritura (página, ciclo interno, polling de confirmación) que ya vimos en la flash de SPI, pero con direcciones de dos bytes; la pantalla, que un bus compartido también es un presupuesto de tiempo; y el conjunto, cómo se reparten direcciones y pull-ups.

02 · Materiales, cableado y direcciones

Materiales, cableado y direcciones

Hace falta lo siguiente (el código usa un ESP32; con otra placa solo cambian los pines y los tiempos máximos de Wire):

  • Una placa con I²C a 3,3 V. Con un Arduino de 5 V, los módulos de 3,3 V necesitan un desplazador de nivel (tema 16).
  • Un módulo BME280 de 3,3 V (no confundir con el BMP280, que mide temperatura y presión pero no humedad; su identificación es 0x58 y no 0x60).
  • Un módulo DS3231 con su pila. Casi todos incluyen también una EEPROM 24C32 (en 0x57) y ya traen las pull-ups.
  • Una pantalla OLED SSD1306 de 128 × 64 con interfaz I²C.
  • Protoboard y cables cortos, y si es posible un analizador lógico.
Cableado del taller I²C Un ESP32 maneja dos líneas, SDA y SCL, a las que se conectan en paralelo un sensor BME280 en 0x76, una pantalla OLED SSD1306 en 0x3C y un módulo DS3231 que contiene dos chips: el reloj en 0x68 y la memoria EEPROM 24C32 en 0x57. Todos comparten alimentación de 3,3 V y masa. ESP32 GPIO 21 = SDA GPIO 22 = SCL 3,3 V BME280 sensor 0x76 SSD1306 pantalla OLED 0x3C DS3231 reloj (RTC) 0x68 24C32 EEPROM 4 KB 0x57 un solo módulo con dos chips SDASCL Las pull-ups de cada módulo quedan en paralelo. Todo comparte 3,3 V y masa.

Los cuatro chips se conectan en paralelo a SDA y SCL, y comparten 3,3 V y masa. Antes de armar nada, lo primero es la tabla de direcciones, porque es lo que decide si el bus funciona:

Direcciones del taller y bytes que viajan por el bus.
DispositivoDirección de 7 bitsByte de escrituraByte de lecturaCómo se cambia
BME2800x760xEC0xEDPin SDO: a masa 0x76, a VDD 0x77
SSD13060x3C0x780x79Puente SA0 o resistencia: 0x3C o 0x3D
DS3231 (reloj)0x680xD00xD1Fija
24C32 (EEPROM)0x570xAE0xAFPads A0 a A2 del módulo: 0x50 a 0x57

Las cuatro direcciones son distintas, de modo que no hace falta un multiplexor. Si se quisiera agregar un MPU6050 (también 0x68) habría que cambiar su pin AD0 o poner el reloj en otro bus (tema 19). El módulo del DS3231 ya ocupa dos direcciones con una sola placa, algo a tener presente en el barrido.

Las pull-ups son el segundo asunto. Cada módulo trae las suyas, y todas quedan en paralelo. Supongamos 4,7 kΩ en el módulo del reloj y 10 kΩ en cada uno de los otros dos:

Req = 1 / (1/4700 + 1/10 000 + 1/10 000) ≈ 2,42 kΩ

La capacidad del bus: cuatro chips de 8 pF, 10 pF de pistas y 0,3 m de cable de 100 pF/m dan 10 + 32 + 30 = 72 pF. El tiempo de subida es 0,8473 · 2420 · 72 pF ≈ 148 ns, bien por debajo de los 300 ns del modo rápido; y 3,3 V sobre 2,42 kΩ son 1,2 mA en el nivel bajo, lejos de los 3 mA que garantiza cada chip. El bus soporta 400 kHz con margen. Si se midieran valores muy distintos (una resistencia equivalente por debajo de 967 Ω, o una subida de más de 300 ns), habría que quitar pull-ups de algún módulo o acortar el cable.

03 · Parte A

El reloj DS3231

El DS3231 es un reloj con oscilador de cristal compensado en temperatura, de una exactitud cercana a una parte por cien millones (unos ±2 ppm: alrededor de un minuto por año). Su mapa de registros comienza con la fecha y la hora, en BCD, siete registros consecutivos desde la dirección 0:

Registros de hora y fecha del DS3231 y su codificación.
RegistroContenidoBits
0x00Segundos (00 a 59)bits 6:4 decenas, 3:0 unidades
0x01Minutos (00 a 59)bits 6:4 decenas, 3:0 unidades
0x02Horasbit 6 = 1: modo de 12 h (bit 5 = PM); bit 6 = 0: modo de 24 h (bits 5:4 decenas); 3:0 unidades
0x03Día de la semana (1 a 7)lo define el usuario; el chip solo lo incrementa
0x04Día del mes (01 a 31)bits 5:4 decenas, 3:0 unidades
0x05Mes (01 a 12)bit 7 = siglo; bit 4 decenas, 3:0 unidades
0x06Año (00 a 99)bits 7:4 decenas, 3:0 unidades
0x0FEstadobit 7 = OSF: el oscilador se detuvo, la hora no es confiable

La conversión desde y hacia BCD es corta, y se aplica después de enmascarar los bits de control del registro (por ejemplo, el bit 6 de las horas):

decimal = (b >> 4) × 10 + (b & 0x0F)  b = ((decimal / 10) << 4) | (decimal % 10)

Una lectura de la hora es el mensaje de registro y ráfaga del tema 18: S 0xD0(W) A 0x00 A Sr 0xD1(R) A 7 bytes N P. Se leen los siete registros juntos y no uno por uno, por la misma razón de coherencia que vimos: si entre dos lecturas el reloj pasa de las 12:59:59 a las 13:00:00, se obtendría 12:00:59 o 13:59:00. El DS3231 además copia la hora a registros de lectura al iniciar la transferencia, justamente para evitarlo, pero solo en una única ráfaga.

Dos reglas más. El día de la semana no lo calcula el chip: es un número de 1 a 7 que se escribe al poner la hora y el reloj incrementa a medianoche; si se escribe uno incoherente con la fecha, así queda. Y el bit OSF del registro de estado (0x0F) vale 1 si el oscilador se detuvo alguna vez desde que se lo borró (típicamente por haberse quedado sin pila); mientras vale 1 la hora no es confiable. El programa lo consulta al arrancar, y si está activo pone la hora (en el taller, la de compilación) y lo borra escribiendo 0 en ese bit.

Para guardar una hora en la memoria conviene un número en lugar de seis campos: los segundos desde el 1.º de enero de 2000, en 4 bytes (alcanzan para 136 años). La cuenta usa los días de cada mes y los años bisiestos (todo año divisible por 4 desde 2000 hasta 2099 lo es):

días = 365 · (año − 2000) + (año − 2000 + 3) / 4 + acumulado[mes] + (día − 1) + (1 si es bisiesto y mes > 2)  segundos = días · 86 400 + hora · 3600 + min · 60 + seg

Como referencia, el 1.º de enero de 2025 a las 00:00:00 son 789 004 800 segundos desde 2000.

04 · Parte B

La memoria EEPROM

La 24C32 guarda 32 Kbit: 4096 bytes. Es una memoria I²C típica de la familia 24Cxx. Se maneja como una versión pequeña de lo que vimos en la flash del tema 15, con otro protocolo:

  • Direcciones de dos bytes. A diferencia de un sensor, el «número de registro» es una dirección de 12 bits (0 a 4095) que viaja en dos bytes, el alto primero. Una escritura es S 0xAE(W) A dirH A dirL A dato … P y una lectura es S 0xAE(W) A dirH A dirL A Sr 0xAF(R) A dato … N P.
  • Escritura por páginas de 32 bytes. Se pueden mandar hasta 32 bytes en una operación, pero no deben cruzar el final de una página: si se llega al último byte de la página, la dirección interna vuelve al principio de la misma página y pisa lo que había. La misma trampa de la flash.
  • Ciclo interno de escritura. Después del STOP, la memoria tarda hasta unos 5 ms en grabar. Durante ese lapso no contesta a su dirección (NACK). El bus queda libre para los otros dispositivos, pero no para la memoria.
  • Polling de ACK. En lugar de esperar un tiempo fijo, se repite el mensaje de solo la dirección hasta que la memoria vuelva a confirmar. Es la manera correcta y la más rápida de saber que terminó.
  • Lectura. Se puede leer cualquier cantidad de bytes de corrido: la dirección avanza sola y, al llegar al final de la memoria, vuelve al principio.
  • Desgaste. Cada celda soporta del orden de un millón de ciclos de escritura. En un registro de datos conviene repartir las escrituras en toda la memoria, no usar siempre la misma posición.
Registros de 8 bytes dentro de las páginas de la EEPROM La EEPROM se divide en páginas de 32 bytes. Cada página contiene exactamente cuatro registros de 8 bytes, de modo que ningún registro cruza el límite de una página. Cada registro tiene cuatro bytes de segundos, dos de temperatura, uno de humedad y uno de CRC-8. página 0 0 1 2 3 0x0000 a 0x001F página 1 4 5 6 7 0x0020 a 0x003F página 2 8 9 10 11 0x0040 a 0x005F página 3 12 13 14 15 0x0060 a 0x007F el registro 0 (en naranja) abierto en sus campos segundos 4 bytes temperatura 2 bytes humedad 1 CRC-8 1 byte 0 … 3byte 4 … 5byte 6byte 7

El diseño del registro aprovecha estas reglas. Cada medición ocupa 8 bytes: 4 de segundos desde 2000 (de menor a mayor peso), 2 de temperatura en centésimas de grado (entero con signo, también de menor a mayor peso), 1 de humedad en porcentaje y 1 de CRC-8 calculado sobre los 7 anteriores. Como 8 divide a 32, cada página contiene exactamente 4 registros y ninguno cruza un límite de página: cada escritura de un registro es una sola operación de 8 bytes, sin partir. Con 4096 bytes caben 512 registros, que se usan como un buffer circular: se escribe en el siguiente lugar y al llegar al final se vuelve al principio.

El CRC-8 (polinomio 0x07, valor inicial 0) detecta que un registro está dañado: por un corte de energía a mitad de una escritura, por un bit invertido o por una celda desgastada. También marca los lugares vacíos: una memoria borrada tiene todos los bytes en 0xFF, y el CRC-8 de siete 0xFF es 0x0C, distinto del 0xFF del octavo byte, por lo que un registro vacío no valida. Con esto, el programa distingue un registro bueno de uno vacío o corrupto.

Resta el problema del puntero: ¿dónde se escribe el próximo registro después de un reinicio? Guardar el puntero en otro lugar de la EEPROM lo desgastaría muy rápido. La solución es reconstruirlo al arrancar: se recorre la memoria (en 128 lecturas de 32 bytes) y se busca el registro válido con el mayor tiempo; el siguiente lugar es el que sigue. Si no hay ninguno, se empieza en 0. Es lento (unos 100 ms) pero se hace una sola vez, y sobrevive a cualquier reinicio.

El desgaste del esquema es bajo. Con un registro por minuto, el buffer da una vuelta cada 512 minutos (8 h 32 min), y cada página recibe 4 escrituras por vuelta: unas 4100 por año, es decir, unos 240 años hasta el millón de ciclos.

05 · Parte C

Sensor, pantalla y tiempo del bus

Para el BME280 y la pantalla se usan las bibliotecas habituales (Adafruit_BME280, Adafruit_SSD1306 y Adafruit_GFX). La compensación del BME280 son unas sesenta líneas de matemática de la hoja de datos que no aportan a este tema, y el controlador de la pantalla es una larga lista de comandos de inicialización. Lo que sí importa es lo que esas bibliotecas hacen sobre el bus, que se ve con el analizador:

  • El BME280 se identifica por el registro 0xD0 (valor 0x60), se configura con unos pocos registros, y cada medición se lee en una ráfaga de 8 bytes (presión, temperatura y humedad) desde 0xF7.
  • La pantalla recibe comandos con un byte de control 0x00 y datos de píxeles con el byte de control 0x40. Un cuadro completo son 1024 bytes (128 × 64 / 8), que se envían en trozos porque el buffer de Wire es limitado: con trozos de 16 bytes de imagen, cada uno viaja con su dirección y su byte de control.

Ese cuadro completo es el gran consumidor del bus. Un presupuesto de lo que cuesta cada operación (9 pulsos por byte):

Costo en el bus de cada operación del taller.
OperaciónBytes en el cablePulsosA 100 kHzA 400 kHz
Leer la hora (7 registros)3 + 7 = 1090900 µs225 µs
Leer el BME280 (8 bytes)3 + 8 = 1199990 µs247,5 µs
Escribir un registro en la EEPROM1 + 2 + 8 = 1199990 µs247,5 µs
Leer una página de la EEPROM (32 bytes)1 + 2 + 1 + 32 = 363243,24 ms810 µs
Redibujar la pantalla (1024 bytes en 64 trozos de 16)1024 + 2 × 64 = 115210 368103,7 ms25,9 ms

Una actualización por segundo (hora + sensor + pantalla) son 10 557 pulsos: 105,6 ms a 100 kHz (10,6 % del tiempo) y 26,4 ms a 400 kHz (2,6 %). Casi todo es la pantalla. Si se quisiera refrescarla diez veces por segundo, a 100 kHz haría falta más de un segundo por segundo: no alcanza. Por eso se sube el reloj a 400 kHz (con las pull-ups verificadas) y se refresca la pantalla solo cuando algo cambia.

Este presupuesto pesa más cuanto más cosas hay en el bus: un bus compartido es un recurso con tiempo limitado, y una pantalla que se redibuja sin pausa puede retrasar la lectura de un sensor crítico. Cuando eso importa, se pasa la pantalla a un segundo bus (el ESP32 tiene dos controladores) o se la actualiza por partes.

06 · Poner a andar el enlace

Plan de pruebas

Como en los otros talleres, el montaje se prueba por etapas, de lo más simple a lo más completo. Cada etapa descarta un grupo de causas:

Plan de pruebas del taller, en orden.
EtapaCómo se haceResultado esperadoSi falla, sospechá de…
1. ReposoCon todo conectado y sin tráfico, leer SDA y SCL (tema 16)Las dos en altoFalta de pull-ups, un módulo en cortocircuito, una línea retenida
2. BarridoProbar las direcciones 0x08 a 0x77Contestan 0x3C, 0x57, 0x68 y 0x76Cableado, alimentación, direcciones distintas de las supuestas
3. IdentificaciónLeer el registro 0xD0 del 0x760x60 (BME280)Es un BMP280 (0x58) u otro chip
4. RelojLeer los 7 registros y el estadoFecha y hora razonables, OSF = 0Pila agotada, hora sin poner, BCD mal convertido
5. EEPROMEscribir 8 bytes en una dirección, esperar con polling y leerlosLa lectura devuelve lo escritoDirección de un solo byte, escribir sin esperar, cruce de página
6. PantallaMostrar un texto fijoEl texto apareceDirección 0x3D en lugar de 0x3C, alimentación, inicialización
7. Todo junto a 100 kHzEjecutar el programa completoPantalla y registro funcionanDirecciones repetidas, pull-ups en paralelo muy bajas, buffer de Wire
8. A 400 kHzSubir Wire.setClock(400000) y repetirTodo correctoSubida lenta (resistencia alta o cable largo), capacidad excesiva

La regla de siempre: cada etapa se supera antes de pasar a la siguiente. Una falla en la etapa 7 que no existía en la 5 casi siempre es una interacción entre dispositivos: direcciones, pull-ups o tiempo.

07 · Con el microcontrolador

El código completo

El programa maneja el reloj y la memoria a mano, con las funciones del tema 18, y deja el sensor y la pantalla a sus bibliotecas. Tiene tres partes: las funciones de cada dispositivo, un setup que verifica el bus antes de usarlo y un loop que cada segundo lee, muestra y, cada minuto, registra. Para ahorrar espacio, el dibujo de la pantalla se reduce a cuatro líneas de texto.

#include <Wire.h>
#include <math.h>
#include <string.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_BME280.h>

const byte DIR_BME = 0x76, DIR_OLED = 0x3C, DIR_RTC = 0x68, DIR_EEP = 0x57;
const uint16_t TAM_EEPROM = 4096;
const byte TAM_REG = 8;                                   // 8 bytes: 4 por segundos, 2 de temperatura, 1 de humedad, 1 de CRC
const uint16_t N_REG = TAM_EEPROM / TAM_REG;              // 512 registros, 4 por página de 32 bytes
const unsigned long PERIODO_PANTALLA_MS = 1000;
const unsigned long PERIODO_LOG_MS = 60000;

Adafruit_SSD1306 oled(128, 64, &Wire, -1);
Adafruit_BME280 bme;
uint16_t siguiente = 0;                                   // próximo registro a escribir

struct Hora { byte seg, min, hora, dia, mes; uint16_t anio; };

// ---------- Utilidades ----------
bool ping(byte dir) { Wire.beginTransmission(dir); return Wire.endTransmission() == 0; }

byte crc8(const byte *d, byte n) {                        // polinomio 0x07, valor inicial 0
    byte crc = 0;
    for (byte i = 0; i < n; i++) {
        crc ^= d[i];
        for (byte b = 0; b < 8; b++) crc = (crc & 0x80) ? (crc << 1) ^ 0x07 : (crc << 1);
    }
    return crc;
}

byte bcdADec(byte b) { return (b >> 4) * 10 + (b & 0x0F); }
byte decABcd(byte d) { return ((d / 10) << 4) | (d % 10); }

uint32_t segundosDesde2000(uint16_t anio, byte mes, byte dia, byte h, byte mi, byte s) {
    static const uint16_t acum[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
    uint32_t dias = 365UL * (anio - 2000) + (anio - 2000 + 3) / 4 + acum[mes - 1] + (dia - 1);
    if (anio % 4 == 0 && mes > 2) dias++;
    return dias * 86400UL + h * 3600UL + mi * 60UL + s;
}

// ---------- Reloj DS3231 ----------
bool rtcLeer(Hora &h) {                                    // S 0xD0(W) A 0x00 A Sr 0xD1(R) A 7 datos N P
    byte b[7];
    Wire.beginTransmission(DIR_RTC);
    Wire.write((uint8_t)0x00);
    if (Wire.endTransmission(false) != 0) return false;
    if (Wire.requestFrom(DIR_RTC, (uint8_t)7) != 7) return false;
    for (byte i = 0; i < 7; i++) b[i] = Wire.read();
    h.seg = bcdADec(b[0] & 0x7F);
    h.min = bcdADec(b[1] & 0x7F);
    h.hora = bcdADec(b[2] & 0x3F);                         // modo de 24 horas
    h.dia = bcdADec(b[4] & 0x3F);
    h.mes = bcdADec(b[5] & 0x1F);
    h.anio = 2000 + bcdADec(b[6]);
    return true;
}

bool rtcEscribir(const Hora &h) {
    Wire.beginTransmission(DIR_RTC);
    Wire.write((uint8_t)0x00);
    Wire.write(decABcd(h.seg)); Wire.write(decABcd(h.min)); Wire.write(decABcd(h.hora));
    Wire.write((uint8_t)1);                               // día de la semana: lo define quien escribe
    Wire.write(decABcd(h.dia)); Wire.write(decABcd(h.mes)); Wire.write(decABcd(h.anio - 2000));
    if (Wire.endTransmission() != 0) return false;
    Wire.beginTransmission(DIR_RTC);                      // borrar OSF (bit 7 del registro de estado)
    Wire.write((uint8_t)0x0F);
    Wire.write((uint8_t)0x00);
    return Wire.endTransmission() == 0;
}

bool rtcOsciladorDetenido() {
    Wire.beginTransmission(DIR_RTC);
    Wire.write((uint8_t)0x0F);
    if (Wire.endTransmission(false) != 0) return true;
    if (Wire.requestFrom(DIR_RTC, (uint8_t)1) != 1) return true;
    return Wire.read() & 0x80;
}

void ponerHoraDeCompilacion() {                           // "Jun 15 2025" y "14:30:00"
    char m[4]; int d, y, hh, mm, ss;
    sscanf(__DATE__, "%3s %d %d", m, &d, &y);
    sscanf(__TIME__, "%d:%d:%d", &hh, &mm, &ss);
    Hora h = { (byte)ss, (byte)mm, (byte)hh, (byte)d, (byte)((strstr("JanFebMarAprMayJunJulAugSepOctNovDec", m) - "JanFebMarAprMayJunJulAugSepOctNovDec") / 3 + 1), (uint16_t)y };
    rtcEscribir(h);
}

// ---------- EEPROM 24C32 ----------
bool eepromEsperarListo() {                               // polling de ACK: hasta 50 intentos de 1 ms
    for (byte i = 0; i < 50; i++) {
        if (ping(DIR_EEP)) return true;
        delay(1);
    }
    return false;
}

bool eepromEscribir(uint16_t dir, const byte *datos, byte n) {   // n no debe cruzar el final de la página
    Wire.beginTransmission(DIR_EEP);
    Wire.write((uint8_t)(dir >> 8));
    Wire.write((uint8_t)(dir & 0xFF));
    Wire.write(datos, n);
    if (Wire.endTransmission() != 0) return false;
    return eepromEsperarListo();
}

bool eepromLeer(uint16_t dir, byte *destino, byte n) {
    Wire.beginTransmission(DIR_EEP);
    Wire.write((uint8_t)(dir >> 8));
    Wire.write((uint8_t)(dir & 0xFF));
    if (Wire.endTransmission(false) != 0) return false;
    if (Wire.requestFrom(DIR_EEP, n) != n) return false;
    for (byte i = 0; i < n; i++) destino[i] = Wire.read();
    return true;
}

bool registrar(uint32_t seg, int16_t centi, byte hum) {
    byte r[TAM_REG];
    r[0] = seg; r[1] = seg >> 8; r[2] = seg >> 16; r[3] = seg >> 24;
    r[4] = centi; r[5] = centi >> 8;
    r[6] = hum;
    r[7] = crc8(r, 7);
    if (!eepromEscribir(siguiente * TAM_REG, r, TAM_REG)) return false;
    siguiente = (siguiente + 1) % N_REG;
    return true;
}

uint16_t buscarSiguiente() {                              // el registro válido más reciente + 1
    byte pagina[32];
    uint32_t mejor = 0;
    int32_t indice = -1;
    for (uint16_t dir = 0; dir < TAM_EEPROM; dir += 32) {
        if (!eepromLeer(dir, pagina, 32)) return 0;
        for (byte k = 0; k < 4; k++) {
            const byte *r = pagina + k * TAM_REG;
            if (crc8(r, 7) != r[7]) continue;             // vacío o corrupto
            uint32_t t = (uint32_t)r[0] | ((uint32_t)r[1] << 8) | ((uint32_t)r[2] << 16) | ((uint32_t)r[3] << 24);
            if (indice < 0 || t > mejor) { mejor = t; indice = dir / TAM_REG + k; }
        }
    }
    return indice < 0 ? 0 : (indice + 1) % N_REG;
}

// ---------- Pantalla ----------
void dos(Print &p, int n) { if (n < 10) p.print('0'); p.print(n); }

void mostrar(const Hora &h, float t, float hr, float pa) {
    oled.clearDisplay();
    oled.setTextSize(1);
    oled.setTextColor(SSD1306_WHITE);
    oled.setCursor(0, 0);
    dos(oled, h.dia); oled.print('/'); dos(oled, h.mes); oled.print('/'); oled.print(h.anio);
    oled.print(' '); dos(oled, h.hora); oled.print(':'); dos(oled, h.min); oled.print(':'); dos(oled, h.seg);
    oled.setCursor(0, 16); oled.print("T: ");  oled.print(t, 1);  oled.print(" C");
    oled.setCursor(0, 28); oled.print("H: ");  oled.print(hr, 0); oled.print(" %");
    oled.setCursor(0, 40); oled.print("P: ");  oled.print(pa, 0); oled.print(" hPa");
    oled.setCursor(0, 54); oled.print("Registro "); oled.print(siguiente);
    oled.display();
}

void setup() {
    Serial.begin(115200);
    Wire.begin(21, 22);
    Wire.setClock(100000);                                // primero lento; se sube cuando todo funciona
#if defined(ESP32)
    Wire.setTimeOut(100);                                 // tiempo máximo de espera (tema 19)
#endif
    // aquí iría recuperarBus() del tema 19, antes de Wire.begin()

    const byte dispositivos[] = { DIR_BME, DIR_OLED, DIR_RTC, DIR_EEP };
    bool todos = true;
    for (byte d : dispositivos) {
        bool ok = ping(d);
        Serial.print("0x"); Serial.print(d, HEX); Serial.println(ok ? ": ACK" : ": NACK");
        todos &= ok;
    }
    if (!todos) { Serial.println("Falta algún dispositivo: revisar cableado y direcciones"); while (true) delay(1000); }

    if (!bme.begin(DIR_BME)) { Serial.println("El BME280 no se identifica"); while (true) delay(1000); }
    if (!oled.begin(SSD1306_SWITCHCAPVCC, DIR_OLED)) { Serial.println("La pantalla no inicia"); while (true) delay(1000); }
    if (rtcOsciladorDetenido()) { Serial.println("RTC sin hora confiable: se pone la de compilación"); ponerHoraDeCompilacion(); }

    siguiente = buscarSiguiente();
    Serial.print("Próximo registro de la EEPROM: "); Serial.println(siguiente);
    Wire.setClock(400000);
}

void loop() {
    static unsigned long ultimaPantalla = 0, ultimoLog = 0;
    unsigned long ahora = millis();
    if (ahora - ultimaPantalla < PERIODO_PANTALLA_MS) return;
    ultimaPantalla = ahora;

    Hora h;
    if (!rtcLeer(h)) { Serial.println("El reloj no responde"); return; }
    float t = bme.readTemperature(), hr = bme.readHumidity(), pa = bme.readPressure() / 100.0;
    mostrar(h, t, hr, pa);

    if (ahora - ultimoLog >= PERIODO_LOG_MS) {
        ultimoLog = ahora;
        uint32_t seg = segundosDesde2000(h.anio, h.mes, h.dia, h.hora, h.min, h.seg);
        if (!registrar(seg, (int16_t)lroundf(t * 100), (byte)lroundf(hr))) Serial.println("Falló la escritura en la EEPROM");
    }
}

Cinco decisiones del código. El setup comprueba el bus antes de usarlo: un ping a cada dirección y el resultado impreso, de modo que un cable flojo se ve de inmediato. El reloj arranca con 100 kHz y se sube a 400 kHz recién cuando todo contestó. La hora se lee en una sola ráfaga y se enmascaran los bits de control antes de convertir de BCD. Cada registro de la EEPROM es una sola escritura de 8 bytes que no cruza una página, seguida de la espera por polling. Y el puntero no se guarda: se reconstruye buscando el registro válido más reciente.

El loop no usa delay sino la diferencia con millis(), de modo que el programa pueda hacer otras cosas y los tiempos no se acumulen con la duración de cada operación. Y la pantalla se refresca solo una vez por segundo, que es lo que el presupuesto del bus aconsejaba.

08 · Ver lo que pasa en el cable

Verificar con el analizador

Un analizador lógico conectado a SDA y SCL, con el decodificador de I²C, muestra los mensajes que armó el programa. Estas son las capturas correctas de cada operación (a 400 kHz):

Qué debe mostrar una captura correcta de cada operación.
OperaciónMensajePulsosDuración
Ping a una direcciónS 0xD0(W) A P922,5 µs
Leer la horaS 0xD0(W) A 0x00 A Sr 0xD1(R) A 7 datos (A… N) P90225 µs
Leer el BME280S 0xEC(W) A 0xF7 A Sr 0xED(R) A 8 datos N P99247,5 µs
Escribir un registroS 0xAE(W) A dirH A dirL A 8 datos A P99247,5 µs
Polling de la EEPROMS 0xAE(W) N P repetido hasta A9 por intento22,5 µs cada uno
Leer una páginaS 0xAE(W) A dirH A dirL A Sr 0xAF(R) A 32 datos N P324810 µs
Trozo de la pantallaS 0x78(W) A 0x40 A 16 datos A P162405 µs

Cuatro cosas para revisar en la captura, en este orden: 1) que cada mensaje tenga su S y su P (o un Sr donde corresponde) y que no haya N inesperados; 2) que las direcciones sean las de la tabla de la sección 02; 3) que después de cada escritura a la EEPROM aparezcan los intentos N del polling, entre 1 y unos 5 ms, hasta un A (cuántos depende del chip); 4) que la hora se lea en una ráfaga y no como siete mensajes. Con la captura de un cuadro de pantalla se puede medir directamente la ocupación real del bus contra el presupuesto de la sección 05.

Una pista útil para el polling: si los intentos de la EEPROM no terminan nunca, el chip no recibió la operación (se subió el bus antes de tiempo, la escritura cruzó la página, o la dirección es errónea) o el cable de alimentación cae durante la escritura.

09 · Analogía con programación

El registro de datos en Python

Como en los otros talleres, conviene tener un modelo del software antes del hardware. El programa siguiente implementa las piezas con lógica propia: la cuenta de segundos desde 2000, el CRC-8, el empaquetado y desempaquetado del registro, una EEPROM que reproduce el comportamiento por páginas, la reconstrucción del puntero tras un reinicio y el presupuesto de tiempo del bus. Con él se prueba la lógica sin tocar ningún cable.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

ACUM = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
hexa = lambda b: f"{b:#04x}"


def segundos_desde_2000(a, m, d, h, mi, s):
    dias = 365 * (a - 2000) + (a - 2000 + 3) // 4 + ACUM[m - 1] + (d - 1) + (1 if a % 4 == 0 and m > 2 else 0)
    return dias * 86400 + h * 3600 + mi * 60 + s


def crc8(datos, poly=0x07):
    crc = 0
    for b in datos:
        crc ^= b
        for _ in range(8):
            crc = ((crc << 1) ^ poly) & 0xFF if crc & 0x80 else (crc << 1) & 0xFF
    return crc


def empaquetar(segundos, centi, humedad):
    cuerpo = segundos.to_bytes(4, "little") + centi.to_bytes(2, "little", signed=True) + bytes([humedad])
    return cuerpo + bytes([crc8(cuerpo)])


def desempaquetar(r):
    if len(r) != 8 or crc8(r[:7]) != r[7]:
        return None                                   # vacío o corrupto
    return int.from_bytes(r[:4], "little"), int.from_bytes(r[4:6], "little", signed=True), r[6]


class Eeprom24C32:
    TAM, PAGINA = 4096, 32

    def __init__(self):
        self.mem = bytearray([0xFF]) * self.TAM

    def escribir(self, direccion, datos):
        for i, b in enumerate(datos):                 # al llegar al final de la página, vuelve al principio de la misma
            self.mem[(direccion & ~(self.PAGINA - 1)) | ((direccion + i) & (self.PAGINA - 1))] = b

    def leer(self, direccion, n):
        return bytes(self.mem[(direccion + i) % self.TAM] for i in range(n))


def siguiente_slot(eeprom):
    mejor, indice = -1, -1
    for i in range(512):
        reg = desempaquetar(eeprom.leer(i * 8, 8))
        if reg and reg[0] > mejor:
            mejor, indice = reg[0], i
    return 0 if indice < 0 else (indice + 1) % 512


print("1 de enero de 2025:", segundos_desde_2000(2025, 1, 1, 0, 0, 0), "s desde 2000")
t0 = segundos_desde_2000(2025, 6, 15, 14, 30, 0)
print("15/06/2025 14:30:00:", t0)

r = empaquetar(t0, 2537, 48)                          # 25,37 °C y 48 %
print("Registro:", " ".join(hexa(b) for b in r), "| CRC:", hexa(r[7]))
print("Desempaquetado:", desempaquetar(r))
mal = bytearray(r)
mal[4] ^= 0x08                                        # se invierte un bit de la temperatura
print("Con un bit invertido:", desempaquetar(bytes(mal)))
print("CRC de siete 0xFF:", hexa(crc8([0xFF] * 7)), "(distinto de 0xFF: un lugar vacío no valida)")
neg = empaquetar(t0, -512, 100)
print("-5,12 °C y 100 %:", " ".join(hexa(b) for b in neg), "->", desempaquetar(neg))

print()
e = Eeprom24C32()
for k in range(4):
    e.escribir(k * 8, empaquetar(t0 + 10 * k, 2537 + k, 48))
print("Cuatro registros caben en la página 0:", all(b != 0xFF for b in e.mem[:32]), "| página 1 vacía:", all(b == 0xFF for b in e.mem[32:64]))
e2 = Eeprom24C32()
e2.escribir(28, bytes(range(1, 11)))
print("10 bytes desde la dirección 28 -> 28 a 31:", list(e2.mem[28:32]), "| 0 a 5:", list(e2.mem[0:6]), "| 32 y 33 intactos:", hexa(e2.mem[32]), hexa(e2.mem[33]))

print()
print("Puntero tras un reinicio:")
print("  EEPROM vacía:", siguiente_slot(Eeprom24C32()))
e3 = Eeprom24C32()
for k in range(7):
    e3.escribir(k * 8, empaquetar(t0 + 60 * k, 2537, 48))
print("  7 registros escritos:", siguiente_slot(e3))
for k in range(3):
    e3.escribir(((509 + k) % 512) * 8, empaquetar(t0 + 60 * (600 + k), 2000, 50))
print("  después de dar la vuelta (último en 511):", siguiente_slot(e3))

print()
print("Presupuesto del bus (9 pulsos por byte):")
operaciones = {"leer el reloj": 3 + 7, "leer el BME280": 3 + 8, "pantalla (1024 + 2 por trozo de 16)": 1024 + 2 * 64, "escribir un registro": 1 + 2 + 8}
for nombre, n in operaciones.items():
    p = 9 * n
    print(f"  {nombre:36} {n:5} bytes {p:6} pulsos {p / 100e3 * 1e3:8.2f} ms a 100 kHz {p / 400e3 * 1e3:7.3f} ms a 400 kHz")
ciclo = 9 * (operaciones["leer el reloj"] + operaciones["leer el BME280"] + operaciones["pantalla (1024 + 2 por trozo de 16)"])
print(f"  una actualización por segundo: {ciclo} pulsos -> {ciclo / 100e3 * 1e3:.2f} ms ({ciclo / 100e3 * 100:.1f} %) a 100 kHz; {ciclo / 400e3 * 1e3:.2f} ms ({ciclo / 400e3 * 100:.1f} %) a 400 kHz")

Resultado: el 1.º de enero de 2025 son 789 004 800 s desde 2000, y las 14:30:00 del 15/06/2025, 803 313 000 s. El registro de 25,37 °C y 48 % es 0x68 0x95 0xe1 0x2f 0xe9 0x09 0x30 0x6d: los cuatro primeros son los segundos (menor peso primero), 0xe9 0x09 son 2537, 0x30 son 48 y 0x6d el CRC; se desempaqueta sin problemas. Con un solo bit invertido en la temperatura el CRC no coincide y el resultado es None. El CRC de siete 0xFF es 0x0c, distinto de 0xFF, de modo que un lugar vacío no valida. −5,12 °C se guarda como 0x00 0xfe (−512 en complemento a dos) y se recupera con su signo. Los cuatro registros llenan la página 0 sin invadir la 1. Diez bytes desde la dirección 28 hacen lo que se esperaba de una página: los cuatro primeros quedan en 28 a 31 y los seis restantes vuelven a 0 a 5 ([5, 6, 7, 8, 9, 10]), sin tocar las direcciones 32 y 33. El puntero tras un reinicio es 0 con la memoria vacía, 7 después de siete registros y 0 cuando el último registro está en el 511. El presupuesto: leer el reloj cuesta 90 pulsos, el BME280 99, el registro de la EEPROM 99 y la pantalla 10 368; la actualización por segundo son 10 557 pulsos, 105,57 ms (10,6 %) a 100 kHz y 26,39 ms (2,6 %) a 400 kHz.

Observá cómo los dos riesgos de la EEPROM, el cruce de página y los datos corruptos, quedan expuestos en un modelo de veinte líneas. Eso es lo que se prueba antes de llevar el código al hardware.

10 · Exploración interactiva

Laboratorio: tiempo, reloj, registros y direcciones

El laboratorio tiene cuatro herramientas. La primera es un presupuesto del bus: elegís la velocidad, cada cuánto se refresca la pantalla y cada cuánto se registra, y ves cuánto del bus se ocupa. La segunda decodifica los registros del DS3231 en BCD. La tercera arma un registro de la EEPROM con su CRC y detecta la corrupción. La cuarta es un mapa de direcciones donde se agrega un quinto dispositivo y se mira el efecto en las direcciones y en las pull-ups.

1. Presupuesto del bus

Redibujar la pantalla—
Una actualización completa (reloj + sensor + pantalla)—
Bus ocupado por cada segundo—
Veredicto—

Elegí la velocidad y los períodos.

2. Registros del DS3231 en BCD

Fecha y hora—
Segundos desde 2000—
Modo de horas · día de la semana—
Estado—

Escribí los siete registros en hexadecimal.

3. Registro de la EEPROM

Los 8 bytes del registro—
CRC-8 de los 7 primeros—
Dirección en la EEPROM—
Costo de la escritura (400 kHz)—
Lectura de vuelta—
Veredicto—

Elegí los datos del registro.

4. Mapa de direcciones y pull-ups

Direcciones que contestan—
Direcciones repetidas—
Pull-up equivalente—
Capacidad del bus—
Subida · límite a 400 kHz—
Veredicto—

Elegí un dispositivo adicional y el largo del cable.

En la herramienta 1, la pantalla son 1024 bytes de imagen más 2 bytes (dirección y control) por trozo; el reloj y el sensor suman 190 pulsos por actualización, y cada registro de la EEPROM, 99 más. En la 2, el día de la semana se muestra tal cual, porque el chip no lo calcula. En la 3, la dirección se calcula como número de registro × 8, y la lectura de vuelta valida el CRC. En la 4, los módulos se suponen con las pull-ups indicadas, 8 pF por chip y 10 pF fijos, con cable de 100 pF/m.

Recorrido guiado

  1. Con los valores iniciales (400 kHz, pantalla cada 1 s, registro cada 60 s, trozos de 16), redibujar la pantalla cuesta 25,92 ms y una actualización completa, 26,39 ms: el bus se ocupa un 2,6 % del tiempo. «Holgado».
  2. Cambiá a 100 kHz: la pantalla pasa a 103,68 ms y el bus a un 10,6 %. Pasá la actualización a 0,1 s: 105,6 ms por actualización diez veces por segundo no entran en un segundo (106 %): «No alcanza». A 400 kHz esa misma actualización ocupa un 26,4 %.
  3. Con trozos de 31 bytes la pantalla baja a 24,6 ms a 400 kHz: menos encabezados, pero depende del buffer de Wire de cada placa.
  4. En la herramienta 2, con los valores iniciales: 15/06/2025 14:30:45, 803 313 045 segundos desde 2000, modo de 24 h y día de la semana 3 (un valor que el usuario puso; el 15/06/2025 fue domingo).
  5. Cambiá las horas a 1A: «no es BCD», porque la A no es una cifra decimal. Probá 66: el bit 6 está en 1 y el reloj está en modo de 12 horas, con el bit PM: 6 PM son 18:30:45.
  6. Poné el estado en 80: el bit OSF indica que el oscilador se detuvo y la hora se marca como «no confiable» aunque los números sean razonables.
  7. Probá año 99, mes 02 y día 29 (registros 45 30 14 03 29 02 99): el 29 de febrero de 2099 no existe porque 2099 no es bisiesto.
  8. En la herramienta 3, con los valores iniciales, el registro es 68 95 E1 2F E9 09 30 6D, con CRC 0x6D, dirección 0x0028 (página 1, posición 8 de la página). La lectura de vuelta valida y recupera 25,37 °C y 48 %.
  9. Elegí «se invierte un bit de la temperatura»: el CRC de la lectura no coincide y el registro se descarta como corrupto. Con el bit del CRC invertido pasa lo mismo. Poné −5,12 °C: los bytes de temperatura son 00 FE (complemento a dos). Poné el registro 511: la dirección es 0x0FF8, el último registro de la última página.
  10. En la herramienta 4, con los valores iniciales hay 4 direcciones (0x3C, 0x57, 0x68, 0x76), sin repetidas; la pull-up equivalente es de 2,42 kΩ, el bus tiene 72 pF y sube en 148 ns (límite 300 ns). Agregá un MPU6050 en 0x68: «Conflicto» con el reloj. Con el MPU6050 en 0x69 o el BH1750, sin conflicto. Un segundo módulo DS3231 repite dos direcciones (0x68 y 0x57).
  11. Con el cable de 2 m el bus suma 242 pF y la subida pasa a 497 ns: no cumple a 400 kHz (sí a 100 kHz, que admite 1000 ns). La solución sería bajar la pull-up o acortar el cable.
Desafío: cuánto tarda en reconstruir el puntero

Buscar el puntero tras un reinicio lee los 4096 bytes en 128 lecturas de 32 bytes. Cada lectura son 1 + 2 + 1 + 32 = 36 bytes en el bus (324 pulsos). ¿Cuánto tarda todo a 100 kHz y a 400 kHz? 128 × 324 = 41 472 pulsos: 415 ms a 100 kHz y 104 ms a 400 kHz. Se hace una sola vez al arrancar, y a 100 kHz casi medio segundo es perceptible: otra razón para subir el reloj apenas se verificó el bus.

11 · Qué viene después

Ampliaciones y buenas prácticas

El taller deja un sistema completo y modesto. Se puede extender en varias direcciones:

  • Más historia. Una EEPROM 24C256 (32 KB, páginas de 64 bytes, también con dirección de dos bytes) guarda 4096 registros: casi tres días a un registro por minuto. El registro de 8 bytes sigue alineado a la página.
  • Descargar los datos. Un comando por el puerto serie que lea todos los registros válidos, los ordene por tiempo y los imprima (con la fecha convertida). Es la mejor prueba de que el registro y el CRC funcionan.
  • Un CRC más fuerte. Con un CRC-16 en lugar de uno de 8 bits se reduce la probabilidad de que un registro corrupto pase por válido (temas 43 a 45).
  • Despertarse con una alarma del reloj. El DS3231 tiene una salida de interrupción (SQW/INT) que baja a la hora configurada. El ESP32 puede dormir y despertar con ella, en lugar de leer la hora cada segundo: baja el consumo y el tráfico del bus.
  • Un segundo bus. Pasar la pantalla a Wire1 libera al bus principal del tráfico más pesado y permite que los dos sigan trabajando a la vez.
  • Más sensores. Agregar dispositivos repitiendo el procedimiento: dirección, barrido, identificación, presupuesto de pull-up y de tiempo; y un multiplexor si hay direcciones repetidas.
  • Robustez. La rutina de recuperación del bus del tema 19 en el arranque, un tiempo máximo en todas las llamadas y un contador de errores consecutivos que dispare un reinicio de los dispositivos.

Y cuatro hábitos que valen para cualquier bus compartido: asignar las direcciones en papel antes de soldar, calcular las pull-ups equivalentes y la capacidad, verificar cada dispositivo por su identificación y medir cuánto tiempo del bus consume cada tarea antes de agregar otra.

12 · Comprensión

Confusiones frecuentes

«Cada módulo trae sus pull-ups, así que cuantos más módulos, más seguro»

Las pull-ups de todos los módulos se suman en paralelo y la resistencia equivalente baja con cada uno. Con muchos módulos puede quedar por debajo del mínimo y los dispositivos no llegan a bajar la línea. Hay que calcular el conjunto y, si hace falta, quitar resistencias de algún módulo.

«Una EEPROM se escribe como una RAM»

Se escribe por páginas, la escritura que cruza el final de una página vuelve al principio de la misma, y después de cada escritura la memoria no contesta durante unos milisegundos. Hay que partir las escrituras por páginas y esperar con polling.

«Mientras la EEPROM escribe, el bus está ocupado»

No: el bus está libre y se puede leer el reloj o el sensor. Solo la EEPROM no contesta a su dirección durante el ciclo interno. El polling consiste justamente en preguntarle hasta que conteste.

«El DS3231 guarda la hora como un número»

La guarda como campos separados en BCD (una cifra decimal por nibble), con bits de control mezclados en algunos registros. Hay que enmascarar y convertir; leer 0x59 como binario daría 89.

«El día de la semana lo calcula el reloj a partir de la fecha»

No: es un contador de 1 a 7 que el usuario inicializa y el chip incrementa cada día. Si se escribe uno incoherente con la fecha, así queda.

«Si la hora se lee como 00:00:00 del 01/01/2000, el reloj anda»

Eso suele ser un reloj que perdió la pila y arrancó de cero. El bit OSF del registro de estado es la señal de que la hora no es confiable, y el programa debe consultarlo.

«La pantalla es chica, así que casi no ocupa el bus»

Un cuadro son 1024 bytes de imagen, unas cien veces más que una lectura de sensor. En este taller es el 98 % del tiempo del bus. Se refresca solo cuando cambia algo, y a 400 kHz.

«Un CRC en un registro de 8 bytes es exagerado»

Es lo que permite distinguir un registro bueno de uno vacío o dañado, y reconstruir el puntero después de un corte de energía a mitad de una escritura. Cuesta un byte y unas pocas líneas.

13 · Práctica guiada

Predecí antes de comprobar

1. Los bytes del bus

¿Qué bytes viajan por el bus para escribir y para leer cada dispositivo del taller (0x76, 0x3C, 0x68 y 0x57)?

Ver solución

Dirección << 1 y dirección << 1 | 1: BME280 0xEC / 0xED; pantalla 0x78 / 0x79; reloj 0xD0 / 0xD1; EEPROM 0xAE / 0xAF.

2. Pull-ups del conjunto

El módulo del reloj trae pull-ups de 4,7 kΩ, y el BME280 y la pantalla, de 10 kΩ cada uno. ¿Cuál es la resistencia equivalente? Con 72 pF, ¿sirve para el modo rápido?

Ver solución

1 / (1/4700 + 1/10 000 + 1/10 000) ≈ 2423 Ω. La subida es 0,8473 · 2423 · 72 pF ≈ 148 ns, menor que 300 ns; y está por encima de los 967 Ω mínimos a 3,3 V. Sirve, con margen.

3. Dónde cae un registro

El registro número 100 de la EEPROM, ¿en qué dirección está, en qué página y con qué bytes de dirección se lo lee?

Ver solución

100 × 8 = 800 = 0x0320. Página 800 / 32 = 25, posición 0 dentro de la página (es el primero de sus cuatro registros). Los bytes de dirección son 0x03 (alto) y 0x20 (bajo).

4. Tiempo de una escritura

Escribir un registro son 1 byte de dirección del dispositivo, 2 de dirección interna y 8 de datos. ¿Cuántos pulsos y cuánto tiempo a 100 y a 400 kHz? ¿Cuánto más tarda la memoria en grabar, y qué pasa mientras tanto con el bus?

Ver solución

11 bytes × 9 = 99 pulsos: 990 µs a 100 kHz y 247,5 µs a 400 kHz. Después, hasta unos 5 ms de ciclo interno en los que la memoria no contesta (NACK); el bus queda libre para el reloj y el sensor. El polling repite S 0xAE(W) hasta recibir ACK.

5. Una escritura que cruza la página

Por error, un programa escribe 10 bytes seguidos desde la dirección 28. ¿Dónde caen?

Ver solución

Los primeros 4 bytes caen en 28 a 31 (el final de la página 0). Los 6 restantes vuelven al principio de la misma página y caen en 0 a 5, pisando lo que había: no pasan a 32 en adelante. Se evita partiendo en dos escrituras (4 bytes en 28 y 6 en 32) o, como en el taller, alineando los registros para que nunca crucen.

6. Historia y desgaste

Con un registro por minuto y 512 lugares, ¿cuánto tiempo de historia guarda la memoria? ¿Y cuántos años dura la memoria si cada página soporta un millón de ciclos?

Ver solución

512 minutos = 8 h 32 min. Cada vuelta escribe cada página 4 veces; hay 365 · 24 · 60 / 512 ≈ 1027 vueltas por año, o sea unas 4100 escrituras por página por año. 1 000 000 / 4100 ≈ 240 años.

7. Refrescar la pantalla diez veces por segundo

Cada actualización completa son 10 557 pulsos. ¿Qué porcentaje del bus ocupan diez actualizaciones por segundo a 400 kHz y a 100 kHz?

Ver solución

A 400 kHz: 10 557 / 400 000 = 26,4 ms por actualización; diez son 264 ms por segundo: 26 %. A 100 kHz: 105,6 ms por actualización; diez son 1056 ms: 106 %, es decir, imposible, no entran en un segundo.

14 · Síntesis

Ideas para recordar

  • Un bus I²C con varios dispositivos se diseña primero en papel: una tabla de direcciones sin choques, la resistencia de pull-up equivalente (las de los módulos están en paralelo) y la capacidad total.
  • El DS3231 guarda la hora en BCD, en siete registros consecutivos que se leen en una sola ráfaga; hay que enmascarar los bits de control y consultar el bit OSF para saber si la hora es confiable. El día de la semana no lo calcula el chip.
  • La EEPROM 24C32 usa direcciones internas de dos bytes, escribe por páginas de 32 bytes (una escritura que cruza la página vuelve al principio de la misma), no contesta durante el ciclo interno y se espera con polling de ACK.
  • Un registro de 8 bytes con CRC-8, alineado a las páginas, permite escrituras de una sola operación y detectar registros vacíos o corruptos.
  • El puntero de un buffer circular no se guarda: se reconstruye al arrancar buscando el registro válido más reciente, y el mismo esquema reparte el desgaste.
  • Un bus compartido es un presupuesto de tiempo: cada byte cuesta 9 pulsos, y una pantalla de 1024 bytes por cuadro domina el uso del bus. Se sube la velocidad (con las pull-ups verificadas), se refresca solo lo necesario y, si hace falta, se usa un segundo bus.
  • Se prueba por etapas: reposo, barrido, identificación, cada dispositivo por separado, todo junto a 100 kHz y recién después a 400 kHz.
  • Con el analizador se verifica cada mensaje: direcciones correctas, S/Sr/P donde corresponde, intentos N del polling hasta un A, y la hora leída en una sola ráfaga.

¿Listo para el siguiente bloque?

Antes de avanzar, decidí qué dirección y qué bytes de dirección interna usarías para leer el registro número 37 de la EEPROM; calculá el CRC-8 de un registro dañado y explicá por qué el programa lo descarta; y explicá por qué conviene subir el reloj de 100 a 400 kHz recién después de que el barrido y las identificaciones salieron bien. (Para la primera: 37 × 8 = 296 = 0x0128, página 9, bytes 0x01 y 0x28, con la dirección de dispositivo 0x57.) Si podés justificar las tres respuestas, tenés el bloque de I²C completo.

En el próximo tema empezamos el bloque de 1-Wire: un solo cable de datos (más masa) para hablar con muchos sensores, con pulsos de reinicio y presencia, intervalos de tiempo y una identificación única de 64 bits por dispositivo.