El firmware: Arduino i timing crític
L'Arduino Uno té 2 KB de RAM. Una tira de 300 LEDs són 900 bytes només de pixels. Cada decisió és una qüestió d'espai.
Un Arduino Uno té dos kilobytes de RAM. Una tira de tres-cents LEDs pintats en RGB ocupa nou-cents bytes només pel buffer de pixels. La meitat de la memòria, només per “què pinto”. Cada decisió del firmware és, en realitat, una decisió d’espai.
El context
El paper del firmware (lumware-firmware/lumware-firmware.ino)
és intencionalment estret: rebre bytes per serial,
identificar frames, pintar la tira amb el timing exacte que
demana WS281x i contestar amb un ACK. Res més. Cap
animació guardada, cap paleta, cap “mode demo”. Tot això
viu al host.
Aquesta separació no és neta per estètica. És forçada: l’Uno no té memòria per a un atlas d’imatges, ni té com llegir un disc, ni té rellotge precís per portar animacions complexes amb timing fiable. El que sí té és un pin de sortida ràpid i una biblioteca (Adafruit_NeoPixel) que respecta el protocol elèctric WS281x. Aprofitem just això.
La decisió
El firmware és una state machine de quatre estats:
HUNT_SOF → READ_HEADER → READ_PAYLOAD → READ_CRC → DISPATCH
- HUNT_SOF: descarta tot byte que no sigui
0xA5. Quan el troba, ancora un frame nou i reseteja l’estat intern. - READ_HEADER: acumula 4 bytes (
VERSION,TYPE,LEN_HI,LEN_LO). Si la versió no és0x01, si el tipus no és conegut, o siLEN > 1024, envia un ACK d’error i torna a HUNT_SOF. - READ_PAYLOAD: llegeix
LENbytes. Cap buffer sencer. Si és un frameDATA, cada tres bytes es pinten directament ambsetPixelColor(led_idx, r, g, b)i el firmware passa al següent. Mai s’acumulen 900 bytes a memòria. - READ_CRC: 1 byte final. Es compara amb el CRC8
running que s’ha anat calculant des de
VERSION. Si no coincideix, ACKCRC_FAILi fora. - DISPATCH: si tot ha anat bé,
leds_strip->show()(només perDATA), i ACKOK.
Per què streaming i no buffer sencer. Si bufferejés el payload
DATAper validar el CRC abans de pintar, hauria de reservar fins a 1024 bytes — la meitat de la RAM de l’Uno, només per a un frame que potser és invàlid. Així, el “preu” és haver de revertir el frame si el CRC falla: els pixels ja escrits queden pintats fins al pròximshow(). La pràctica diu que és tolerable — els CRC fallen poc i la pèrdua màxima és el contingut d’un sol frame, no un crash. Elshow()només s’invoca si el CRC és OK, així que en cas d’error el frame no es projecta a la tira encara que els bytes ja estiguin escrits al buffer intern.
Hi ha cinc codis d’error d’ACK ben tipats:
0x00OK0x01CRC_FAIL0x02VERSION(frame d’una altra versió de wire)0x03UNKNOWN_TYPE(TYPE no reconegut)0x04OVERFLOW(LEN > MAX_PAYLOAD)
Per què cinc i no un? Perquè la causa importa: un
CRC_FAIL justifica retransmissió immediata; un VERSION
justifica abortar i renegociar; un OVERFLOW és un bug del
host que cal arreglar, no retransmetre. Distingir-los al
firmware permet decidir al host.
El que ve
Tornem al host i mirem per què el protocol té aquesta forma i no JSON. Per què 6 bytes d’overhead, per què big-endian, per què CRC8 i no CRC16 ni res. Era una decisió o una conseqüència inevitable?