El firmware: Arduino y timing crítico
El Arduino Uno tiene 2 KB de RAM. Una tira de 300 LEDs son 900 bytes solo de píxeles. Cada decisión es una cuestión de espacio.
Un Arduino Uno tiene dos kilobytes de RAM. Una tira de trescientos LEDs en RGB ocupa novecientos bytes solo de buffer de píxeles. La mitad de la memoria, solo para “qué pinto”. Cada decisión del firmware es, en realidad, una decisión de espacio.
El contexto
El papel del firmware (lumware-firmware/lumware-firmware.ino)
es intencionadamente estrecho: recibir bytes por serie,
identificar frames, pintar la tira con el timing exacto que
exige WS281x y contestar con un ACK. Nada más. Ninguna
animación guardada, ninguna paleta, ningún “modo demo”.
Todo eso vive en el host.
Esta separación no es limpia por estética. Es forzada: el Uno no tiene memoria para un atlas de imágenes, ni puede leer un disco, ni tiene reloj preciso para llevar animaciones complejas con timing fiable. Lo que sí tiene es un pin de salida rápido y una biblioteca (Adafruit_NeoPixel) que respeta el protocolo eléctrico WS281x. Aprovechamos justo eso.
La decisión
El firmware es una state machine de cuatro estados:
HUNT_SOF → READ_HEADER → READ_PAYLOAD → READ_CRC → DISPATCH
- HUNT_SOF: descarta todo byte que no sea
0xA5. Cuando lo encuentra, ancla un frame nuevo y resetea el estado interno. - READ_HEADER: acumula 4 bytes (
VERSION,TYPE,LEN_HI,LEN_LO). Si la versión no es0x01, si el tipo no es conocido, o siLEN > 1024, envía un ACK de error y vuelve a HUNT_SOF. - READ_PAYLOAD: lee
LENbytes. Sin buffer entero. Si es un frameDATA, cada tres bytes se pintan directamente consetPixelColor(led_idx, r, g, b)y el firmware pasa al siguiente. Nunca se acumulan 900 bytes en memoria. - READ_CRC: 1 byte final. Se compara con el CRC8
running que se ha ido calculando desde
VERSION. Si no coincide, ACKCRC_FAILy fuera. - DISPATCH: si todo ha ido bien,
leds_strip->show()(solo paraDATA), y ACKOK.
Por qué streaming y no buffer entero. Si bufferease el payload
DATApara validar el CRC antes de pintar, tendría que reservar hasta 1024 bytes — la mitad de la RAM del Uno, solo para un frame que quizá sea inválido. Así, el “precio” es tener que revertir el frame si el CRC falla: los píxeles ya escritos quedan pintados hasta el próximoshow(). La práctica dice que es tolerable — los CRC fallan poco y la pérdida máxima es el contenido de un solo frame, no un crash.show()solo se invoca si el CRC es OK, así que en caso de error el frame no se proyecta a la tira aunque los bytes ya estén escritos en el buffer interno.
Hay cinco códigos de error de ACK bien tipados:
0x00OK0x01CRC_FAIL0x02VERSION(frame de otra versión de wire)0x03UNKNOWN_TYPE(TYPE no reconocido)0x04OVERFLOW(LEN > MAX_PAYLOAD)
¿Por qué cinco y no uno? Porque la causa importa: un
CRC_FAIL justifica retransmisión inmediata; un VERSION
justifica abortar y renegociar; un OVERFLOW es un bug del
host que hay que arreglar, no retransmitir. Distinguirlos en
el firmware permite decidir en el host.
Lo que viene
Volvemos al host y miramos por qué el protocolo tiene esta forma y no JSON. Por qué 6 bytes de overhead, por qué big-endian, por qué CRC8 y no CRC16 ni nada. ¿Era una decisión o una consecuencia inevitable?