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.

Block diagram of the CAN controller. On the TX side (top), can.send() drops frames into one of three TX mailboxes, and an arbitrate block picks which mailbox goes to the bus next. On the RX side (bottom), incoming frames pass through an acceptance filter; accepted frames land in the RX FIFO and can.recv() reads the FIFO from its oldest end.

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 da send().

  • 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.