3.26. Il bus CAN nel codice¶
machine.CAN racchiude un controller CAN hardware. Avvialo con l’id del bus e una velocità di trasmissione:
from machine import CAN
can = CAN(1, 500_000)
can.set_filters(None) # accept all incoming IDs
La velocità di trasmissione deve corrispondere esattamente a quella di ogni altro nodo sul bus – 125_000, 250_000, 500_000 e 1_000_000 sono i valori comuni. set_filters() configura quali ID il controller lascerà passare; None significa accettare tutto (utile durante l’avvio di un collegamento, meno utile una volta che il bus è impegnato).
3.26.1. All’interno del controller¶
Tre componenti hardware si trovano tra il codice Python della camera e il bus – le mailbox TX, un filtro di accettazione e la FIFO RX. Ciascuno corrisponde a un elemento dell’API CAN utilizzata di seguito.
Le mailbox TX, il filtro di accettazione e la FIFO RX del controller tra il software e il bus.¶
Mailbox TX. Un piccolo insieme di slot hardware (tipicamente tre) che contengono i frame in uscita che la camera ha consegnato tramite
send()ma che non hanno ancora raggiunto il bus. Quando il bus è inattivo, il controller sceglie la mailbox con l’ID di numero inferiore (priorità più alta) e arbitra per il bus per suo conto. La camera non sceglie la mailbox; il controller ne assegna una e ne restituisce l’indice dasend().Filtro di accettazione. Hardware configurabile che confronta l’ID di ogni frame in arrivo con un elenco di pattern e scarta tutto ciò che non corrisponde. I frame che passano proseguono nella FIFO RX; i frame rifiutati vengono scartati dal controller senza mai raggiungere Python.
set_filters()configura questi pattern.FIFO RX. Una coda first-in, first-out – il primo frame entrato è anche il primo a uscire, come una fila a uno sportello. Il controller aggiunge i frame ricevuti in coda man mano che arrivano, e
recv()li preleva dalla testa nello stesso ordine. La coda è importante perché il bus cattura i frame in background mentre Python è occupato altrove; la camera li smaltisce poi uno alla volta senza perderne nessuno, a patto che la FIFO non sia andata in overflow.
3.26.2. Invio di un frame¶
send() accoda un frame per la trasmissione:
can.send(0x123, b"\x01\x02\x03\x04")
Il primo argomento è l’ID (un intero a 11 bit per un frame standard); il secondo è il payload (da 0 a 8 byte per il CAN Classic). La chiamata restituisce un piccolo indice intero che identifica la mailbox hardware in cui è finito il frame. Il controller arbitra con gli altri trasmittenti sul bus e ritrasmette quando necessario senza ulteriore aiuto da parte del software.
Per gli ID estesi (a 29 bit), applica l’OR del flag CAN.FLAG_EXT_ID al terzo argomento:
can.send(0x18FF1234, b"hello", CAN.FLAG_EXT_ID)
3.26.3. Ricezione dei frame¶
Il controller si avvia senza alcun filtro installato e scarta ogni frame in arrivo. Prima che recv() restituisca qualcosa, chiama set_filters() una volta – la forma più semplice è None, che accetta ogni ID:
can.set_filters(None) # accept every frame
recv() restituisce quindi il frame successivo nella FIFO di ricezione, oppure None se non c’è nulla in attesa:
msg = can.recv()
if msg is not None:
can_id, data, flags, errs = msg
print("got", hex(can_id), bytes(data))
Il bus riempie la FIFO RX in background, quindi il loop principale si limita a svuotarla con la stessa rapidità con cui itera. Finché la FIFO è più profonda dell’intervallo più lungo tra uno svuotamento e l’altro, non si perde alcun frame.
3.26.4. Filtri¶
Un bus reale è di solito occupato da frame che non interessano alla camera. I filtri hardware consentono al controller di scartare gli ID indesiderati prima che raggiungano la FIFO. set_filters() accetta un elenco di tuple (id, mask, flags); un frame supera il filtro se il suo ID, mascherato da mask, corrisponde all”id configurato:
# Accept only IDs 0x100 - 0x10F (mask off the bottom 4 bits)
can.set_filters(((0x100, 0x7F0, 0),))
# Accept IDs 0x300 and 0x700 exactly
can.set_filters(((0x300, 0x7FF, 0),
(0x700, 0x7FF, 0)))
I frame che non corrispondono vengono scartati dal controller e non compaiono mai presso recv(), il che fa risparmiare sia spazio di buffer sia tempo di CPU.
3.26.5. Stati di errore e ripristino¶
Un bus CAN reale rileva errori di trasmissione – cortocircuiti verso massa, nodi mancanti, rumore elettrico che corrompe i bit. Il controller mantiene due contatori che tracciano questo comportamento: un Transmit Error Counter (TEC) e un Receive Error Counter (REC), ciascuno incrementato quando il controller rileva un errore e decrementato dopo un trasferimento riuscito. I valori dei contatori pongono il controller in uno di quattro stati:
Error Active (TEC e REC entrambi sotto 96). Funzionamento normale. Quando il nodo rileva un errore sul bus trasmette un active error frame dominante, che costringe ogni altro nodo a scartare il frame in corso così che il mittente possa ritentare.
Error Warning (uno dei due contatori raggiunge 96). Ancora pienamente attivo sul bus – lo stato di warning è un segnale software che indica un accumulo di errori, non un cambiamento di comportamento.
Error Passive (uno dei due contatori raggiunge 128). Il nodo è ancora sul bus ma smette di inviare error frame dominanti; gli errori vengono ora segnalati con error frame passivi (recessivi), così che un nodo difettoso non possa continuare a disturbare il bus per tutti gli altri.
Bus Off (il TEC raggiunge 256). Il controller ha deciso che questo nodo è troppo inaffidabile per partecipare. Si disconnette dal bus, smette di trasmettere e di confermare, e resta fuori finché il software non lo riavvia esplicitamente.
Le prime tre transizioni sono interamente automatiche – man mano che i contatori si decrementano dopo i frame riusciti, il controller torna da solo verso Error Active senza intervento.
Bus Off è l’unico stato che richiede un intervento software. restart() reimposta il controller e lo riporta a Error Active. Uno schema tipico è verificare lo stato dal loop principale e riavviare dopo un breve ritardo per dare al bus il tempo di stabilizzarsi:
import time
from machine import CAN
can = CAN(1, 500_000)
can.set_filters(None)
while True:
if can.state() == CAN.STATE_BUS_OFF:
time.sleep_ms(100)
can.restart()
# ... rest of the loop
I valori correnti dei contatori sono disponibili da get_counters() a fini diagnostici – un TEC che sale costantemente su un bus altrimenti tranquillo di solito indica problemi di cablaggio, terminazione o una velocità di trasmissione mal configurata.