3.26. CAN busz a kódban

A machine.CAN egy hardveres CAN vezérlőt csomagol be. Hozd létre a busz azonosítójával és egy bitsebességgel:

from machine import CAN

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

A bitsebességnek pontosan egyeznie kell a buszon lévő összes többi csomóponttal – a 125_000, 250_000, 500_000 és 1_000_000 a szokásos értékek. A set_filters() beállítja, hogy a vezérlő mely azonosítókat engedi át; a None azt jelenti, hogy mindent elfogad (hasznos egy kapcsolat felállításakor, kevésbé hasznos, ha a busz már forgalmas).

3.26.1. A vezérlő belsejében

Három hardverelem helyezkedik el a kamera Python kódja és a busz között – a TX postaládák, egy elfogadási szűrő és az RX FIFO. Mindegyik megfelel a CAN API alább használt egy-egy darabjának.

A CAN vezérlő blokkdiagramja. A TX oldalon (felül) a can.send() a képkockákat a három TX postaláda egyikébe ejti, és egy arbitrációs blokk választja ki, melyik postaláda kerül legközelebb a buszra. Az RX oldalon (alul) a beérkező képkockák áthaladnak egy elfogadási szűrőn; az elfogadott képkockák az RX FIFO-ba kerülnek, és a can.recv() a FIFO-t a legrégebbi végéről olvassa.

A vezérlő TX postaládái, elfogadási szűrője és RX FIFO-ja a szoftver és a busz között.

  • TX postaládák. Hardveres rekeszek kis halmaza (jellemzően három), amelyek a kimenő képkockákat tartják, amelyeket a kamera a send() segítségével adott át, de amelyek még nem jutottak el a buszra. Amikor a busz tétlen, a vezérlő kiválasztja a legalacsonyabb számú azonosítóval (legmagasabb prioritással) rendelkező postaládát, és a nevében arbitrál a buszért. A kamera nem választja ki a postaládát; a vezérlő rendel hozzá egyet, és visszaadja annak indexét a send()-ből.

  • Elfogadási szűrő. Konfigurálható hardver, amely minden beérkező képkocka azonosítóját összehasonlítja egy mintalistával, és eldob mindent, ami nem illeszkedik. Az átjutó képkockák tovább haladnak az RX FIFO-ba; az elutasított képkockákat a vezérlő eldobja, anélkül hogy valaha is elérnék a Pythont. A set_filters() konfigurálja ezeket a mintákat.

  • RX FIFO. Egy first-in, first-out (először be, először ki) sor – az elsőként bekerült képkocka egyben az elsőként kikerülő is, mint egy sor a jegypénztárnál. A vezérlő a beérkező képkockákat a sor végéhez fűzi, ahogy megérkeznek, a recv() pedig az elejéről húzza le őket ugyanabban a sorrendben. A sor azért számít, mert a busz a háttérben kapja el a képkockákat, miközben a Python máshol van elfoglalva; a kamera ezután egyesével lecsapolja őket anélkül, hogy bármelyik elveszne, amíg a FIFO nem csordul túl.

3.26.2. Egy képkocka küldése

A send() adásra sorba állít egy képkockát:

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

Az első argumentum az azonosító (egy 11 bites egész szám szabványos képkocka esetén); a második a hasznos adat (0–8 bájt CAN Classic esetén). A hívás egy kis egész szám indexet ad vissza, amely azonosítja azt a hardveres postaládát, amelybe a képkocka került. A vezérlő arbitrál a buszon lévő bármely más adóval, és szükség szerint újraküld, a szoftver további segítsége nélkül.

Kiterjesztett (29 bites) azonosítók esetén OR-old a CAN.FLAG_EXT_ID jelzőt a harmadik argumentumba:

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

3.26.3. Képkockák fogadása

A vezérlő telepített szűrő nélkül indul, és minden beérkező képkockát eldob. Mielőtt a recv() bármit visszaadna, hívd meg egyszer a set_filters()-t – a legegyszerűbb forma a None, amely minden azonosítót elfogad:

can.set_filters(None)   # accept every frame

A recv() ezután a fogadási FIFO következő képkockáját adja vissza, vagy None-t, ha semmi sem vár:

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

A busz a háttérben tölti fel az RX FIFO-t, így a fő ciklus csak olyan gyorsan csapolja le, ahogy iterál. Amíg a FIFO mélyebb, mint a lecsapolások közötti leghosszabb szünet, egyetlen képkocka sem vész el.

3.26.4. Szűrők

Egy valódi busz általában tele van olyan képkockákkal, amelyekkel a kamera nem törődik. A hardveres szűrők lehetővé teszik, hogy a vezérlő eldobja a nem kívánt azonosítókat, mielőtt elérnék a FIFO-t. A set_filters() egy (id, mask, flags) tuple-ökből álló listát vár; egy képkocka átjut a szűrőn, ha az azonosítója a mask-kal maszkolva megegyezik a beállított id-vel:

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

A nem illeszkedő képkockákat a vezérlő eldobja, és soha nem jelennek meg a recv()-nél, ami pufferhelyet és CPU-időt is megtakarít.

3.26.5. Hibaállapotok és helyreállítás

Egy valódi CAN busz adási hibákat szed fel – földzárlatokat, hiányzó csomópontokat, biteket megrontó elektromos zajt. A vezérlő két számlálót tart, amelyek ezt a viselkedést követik: egy adáshiba-számlálót (Transmit Error Counter, TEC) és egy vételihiba-számlálót (Receive Error Counter, REC), mindkettő növekszik, amikor a vezérlő hibát észlel, és csökken egy sikeres átvitel után. A számlálók értékei a vezérlőt négy állapot egyikébe helyezik:

  • Error Active (a TEC és a REC is 96 alatt van). Normál működés. Amikor a csomópont busz-hibát észlel, egy dominant aktív hibakockát ad, ami minden más csomópontot arra kényszerít, hogy eldobja a folyamatban lévő képkockát, így a küldő újra próbálkozhat.

  • Error Warning (valamelyik számláló eléri a 96-ot). Még teljesen aktív a buszon – a figyelmeztető állapot egy szoftveres jelzés, hogy a hibák halmozódnak, nem pedig viselkedésbeli változás.

  • Error Passive (valamelyik számláló eléri a 128-at). A csomópont még a buszon van, de abbahagyja a dominant hibakockák küldését; a hibákat most passzív (recessive) hibakockákkal jelzi, így egy hibás csomópont nem tudja folyamatosan szétzilálni a buszt mindenki más számára.

  • Bus Off (a TEC eléri a 256-ot). A vezérlő úgy döntött, hogy ez a csomópont túl megbízhatatlan a részvételhez. Lecsatlakozik a buszról, abbahagyja az adást és a nyugtázást, és addig kívül marad, amíg a szoftver kifejezetten újra nem indítja.

Az első három átmenet teljesen automatikus – ahogy a számlálók a sikeres képkockák után csökkennek, a vezérlő beavatkozás nélkül halad vissza az Error Active felé.

A Bus Off az egyetlen állapot, amely szoftveres beavatkozást igényel. A restart() visszaállítja a vezérlőt, és visszaadja Error Active állapotba. Tipikus minta, hogy a fő ciklusból ellenőrzöd az állapotot, és egy rövid késleltetés után újraindítod, hogy a busznak legyen ideje lecsillapodni:

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

Az aktuális számlálóértékek diagnosztikai célból a get_counters()-ből érhetők el – egy egyébként csendes buszon egyenletesen kúszó TEC általában a kábelezésre, a lezárásra vagy egy rosszul beállított bitsebességre utal.