3.26. Magistrala CAN în cod

machine.CAN încapsulează un controler CAN hardware. Inițializează-l cu id-ul magistralei și o rată de biți:

from machine import CAN

can = CAN(1, 500_000)
can.set_filters(None)        # accept all incoming IDs

Rata de biți trebuie să corespundă exact fiecărui alt nod de pe magistrală – 125_000, 250_000, 500_000 și 1_000_000 sunt valorile uzuale. set_filters() configurează care ID-uri va lăsa controlerul să treacă; None înseamnă acceptă orice (util la inițializarea unei legături, mai puțin util odată ce magistrala este ocupată).

3.26.1. În interiorul controlerului

Trei componente hardware se află între codul Python al camerei și magistrală – căsuțele poștale TX, un filtru de acceptare și FIFO-ul RX. Fiecare se mapează la o parte din API-ul CAN folosit mai jos.

Diagramă bloc a controlerului CAN. Pe partea de TX (sus), can.send() depune cadre într-una din cele trei căsuțe poștale TX, iar un bloc de arbitrare alege care căsuță merge următoarea pe magistrală. Pe partea de RX (jos), cadrele primite trec printr-un filtru de acceptare; cadrele acceptate ajung în FIFO-ul RX, iar can.recv() citește FIFO-ul de la capătul său cel mai vechi.

Căsuțele poștale TX, filtrul de acceptare și FIFO-ul RX ale controlerului dintre software și magistrală.

  • Căsuțe poștale TX. Un set mic de sloturi hardware (de obicei trei) care țin cadrele de ieșire pe care camera le-a predat prin send(), dar care nu au ajuns încă pe magistrală. Când magistrala este liberă, controlerul alege căsuța poștală cu ID-ul cel mai mic (prioritatea cea mai mare) și arbitrează pentru magistrală în numele ei. Camera nu alege căsuța poștală; controlerul atribuie una și returnează indicele acesteia din send().

  • Filtru de acceptare. Hardware configurabil care compară ID-ul fiecărui cadru primit cu o listă de tipare și elimină tot ce nu se potrivește. Cadrele care trec continuă în FIFO-ul RX; cadrele respinse sunt aruncate de controler fără a ajunge vreodată în Python. set_filters() configurează aceste tipare.

  • FIFO RX. O coadă de tip first-in, first-out – primul cadru intrat este și primul cadru ieșit, ca o coadă la o casă de bilete. Controlerul adaugă cadrele primite la capătul din spate al cozii pe măsură ce sosesc, iar recv() le extrage din față în aceeași ordine. Coada contează deoarece magistrala captează cadre în fundal în timp ce Python este ocupat în altă parte; camera le golește apoi unul câte unul fără a pierde niciunul, atâta timp cât FIFO-ul nu s-a revărsat.

3.26.2. Trimiterea unui cadru

send() pune un cadru în coadă pentru transmitere:

can.send(0x123, b"\x01\x02\x03\x04")

Primul argument este ID-ul (un întreg pe 11 biți pentru un cadru standard); al doilea este încărcătura utilă (0 până la 8 octeți pentru CAN Classic). Apelul returnează un indice întreg mic care identifică căsuța poștală hardware în care a intrat cadrul. Controlerul arbitrează cu orice alți transmițători de pe magistrală și retransmite după cum este necesar fără ajutor suplimentar din partea software-ului.

Pentru ID-uri extinse (29 de biți), aplică un OR cu fanionul CAN.FLAG_EXT_ID în al treilea argument:

can.send(0x18FF1234, b"hello", CAN.FLAG_EXT_ID)

3.26.3. Primirea cadrelor

Controlerul pornește fără niciun filtru instalat și elimină fiecare cadru primit. Înainte ca recv() să returneze ceva, apelează set_filters() o dată – forma cea mai simplă este None, care acceptă orice ID:

can.set_filters(None)   # accept every frame

recv() returnează apoi următorul cadru din FIFO-ul de recepție, sau None dacă nu așteaptă nimic:

msg = can.recv()
if msg is not None:
    can_id, data, flags, errs = msg
    print("got", hex(can_id), bytes(data))

Magistrala umple FIFO-ul RX în fundal, astfel încât bucla principală doar îl golește la fel de rapid pe cât iterează. Atâta timp cât FIFO-ul este mai adânc decât cel mai lung interval dintre goliri, nu se pierde niciun cadru.

3.26.4. Filtre

O magistrală reală este de obicei ocupată cu cadre care nu interesează camera. Filtrele hardware permit controlerului să elimine ID-urile nedorite înainte ca acestea să ajungă în FIFO. set_filters() primește o listă de tupluri (id, mask, flags); un cadru trece de filtru dacă ID-ul său, mascat cu mask, se potrivește cu id configurat:

# 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)))

Cadrele nepotrivite sunt eliminate de controler și nu apar niciodată la recv(), ceea ce economisește atât spațiu de tampon, cât și timp de CPU.

3.26.5. Stări de eroare și recuperare

O magistrală CAN reală captează erori de transmisie – scurtcircuite la masă, noduri lipsă, zgomot electric care corupe biți. Controlerul menține două contoare care urmăresc acest comportament: un Transmit Error Counter (TEC) și un Receive Error Counter (REC), fiecare incrementat când controlerul detectează o eroare și decrementat după un transfer reușit. Valorile contoarelor pun controlerul în una dintre patru stări:

  • Error Active (TEC și REC ambele sub 96). Funcționare normală. Când nodul detectează o eroare de magistrală, transmite un cadru de eroare activă dominant, care forțează fiecare alt nod să elimine cadrul în curs, astfel încât expeditorul să poată reîncerca.

  • Error Warning (oricare contor atinge 96). Încă pe deplin activ pe magistrală – starea de avertisment este un semnal software că erorile se acumulează, nu o schimbare de comportament.

  • Error Passive (oricare contor atinge 128). Nodul este încă pe magistrală, dar încetează să mai trimită cadre de eroare dominante; erorile sunt acum semnalizate cu cadre de eroare pasive (recesive), astfel încât un nod defect să nu poată continua să strice magistrala pentru toți ceilalți.

  • Bus Off (TEC atinge 256). Controlerul a decis că acest nod este prea nefiabil pentru a participa. Se deconectează de la magistrală, încetează să transmită și să confirme și rămâne în afară până când software-ul îl repornește explicit.

Primele trei tranziții sunt în întregime automate – pe măsură ce contoarele se decrementează după cadre reușite, controlerul se mută înapoi spre Error Active fără intervenție.

Bus Off este singura stare care necesită acțiune software. restart() resetează controlerul și îl readuce la Error Active. Un tipar tipic este să verifici starea din bucla principală și să repornești după o scurtă întârziere pentru a da magistralei timp să se stabilizeze:

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

Valorile curente ale contoarelor sunt disponibile din get_counters() pentru diagnostice – un TEC care urcă constant pe o magistrală altfel liniștită indică de obicei cablajul, terminația sau o rată de biți configurată greșit.