3.26. CAN-bus in code¶
machine.CAN omhult een hardware CAN-controller. Breng hem in werking met de bus-id en een bitrate:
from machine import CAN
can = CAN(1, 500_000)
can.set_filters(None) # accept all incoming IDs
De bitrate moet exact overeenkomen met die van elk ander knooppunt op de bus – 125_000, 250_000, 500_000 en 1_000_000 zijn de gangbare waarden. set_filters() configureert welke ID’s de controller zal doorlaten; None betekent alles accepteren (handig tijdens het in werking brengen van een verbinding, minder handig zodra de bus druk bezet is).
3.26.1. Binnen in de controller¶
Drie stukken hardware bevinden zich tussen de Python-code van de camera en de bus – de TX-mailboxen, een acceptatiefilter en de RX-FIFO. Elk daarvan komt overeen met een onderdeel van de CAN-API die hieronder wordt gebruikt.
De TX-mailboxen, het acceptatiefilter en de RX-FIFO van de controller tussen de software en de bus.¶
TX-mailboxen. Een kleine reeks hardwareslots (meestal drie) die uitgaande frames bevatten die de camera via
send()heeft overgedragen maar die de bus nog niet hebben bereikt. Wanneer de bus inactief is, kiest de controller de mailbox met de ID met het laagste nummer (hoogste prioriteit) en arbitreert hij namens deze om de bus. De camera kiest de mailbox niet; de controller wijst er een toe en geeft de index ervan terug uitsend().Acceptatiefilter. Configureerbare hardware die de ID van elk binnenkomend frame vergelijkt met een lijst van patronen en alles weggooit wat niet overeenkomt. Frames die slagen, gaan verder naar de RX-FIFO; afgewezen frames worden door de controller weggegooid zonder Python ooit te bereiken.
set_filters()configureert deze patronen.RX-FIFO. Een first-in, first-out-wachtrij – het eerste frame dat erin gaat, is ook het eerste frame dat eruit komt, zoals een rij bij een loket. De controller voegt ontvangen frames toe aan de achterkant van de wachtrij zodra ze binnenkomen, en
recv()haalt ze er aan de voorkant in dezelfde volgorde af. De wachtrij is van belang omdat de bus frames op de achtergrond opvangt terwijl Python elders bezig is; de camera leegt ze vervolgens één voor één zonder er een te verliezen, zolang de FIFO niet is overgelopen.
3.26.2. Een frame verzenden¶
send() zet een frame in de wachtrij voor verzending:
can.send(0x123, b"\x01\x02\x03\x04")
Het eerste argument is de ID (een 11-bits integer voor een standaardframe); het tweede is de payload (0 tot 8 bytes voor CAN Classic). De aanroep geeft een kleine integer-index terug die de hardware-mailbox identificeert waar het frame in terecht is gekomen. De controller arbitreert met eventuele andere zenders op de bus en zendt zo nodig opnieuw zonder verdere hulp van software.
Voor uitgebreide (29-bits) ID’s, OR de CAN.FLAG_EXT_ID-vlag in het derde argument:
can.send(0x18FF1234, b"hello", CAN.FLAG_EXT_ID)
3.26.3. Frames ontvangen¶
De controller start op zonder geïnstalleerd filter en laat elk binnenkomend frame vallen. Voordat recv() iets zal teruggeven, roep je eenmaal set_filters() aan – de eenvoudigste vorm is None, die elke ID accepteert:
can.set_filters(None) # accept every frame
recv() geeft vervolgens het volgende frame in de ontvangst-FIFO terug, of None als er niets wacht:
msg = can.recv()
if msg is not None:
can_id, data, flags, errs = msg
print("got", hex(can_id), bytes(data))
De bus vult de RX-FIFO op de achtergrond, dus de hoofdlus leegt hem gewoon zo snel als hij itereert. Zolang de FIFO dieper is dan het langste gat tussen het legen, gaan er geen frames verloren.
3.26.4. Filters¶
Een echte bus is meestal druk met frames waar de camera niet om geeft. Hardwarefilters laten de controller ongewenste ID’s verwijderen voordat ze de FIFO bereiken. set_filters() neemt een lijst van (id, mask, flags)-tupels; een frame slaagt voor het filter als zijn ID, gemaskeerd door mask, overeenkomt met de geconfigureerde id:
# 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)))
Niet-overeenkomende frames worden door de controller weggegooid en verschijnen nooit bij recv(), wat zowel bufferruimte als CPU-tijd bespaart.
3.26.5. Fouttoestanden en herstel¶
Een echte CAN-bus pikt transmissiefouten op – kortsluitingen naar massa, ontbrekende knooppunten, elektrische ruis die bits beschadigt. De controller houdt twee tellers bij die dit gedrag volgen: een Transmit Error Counter (TEC) en een Receive Error Counter (REC), die elk worden verhoogd wanneer de controller een fout detecteert en verlaagd na een geslaagde overdracht. De tellerwaarden brengen de controller in een van vier toestanden:
Error Active (TEC en REC beide onder 96). Normale werking. Wanneer het knooppunt een busfout detecteert, zendt het een dominant active error frame, dat elk ander knooppunt dwingt om het lopende frame te verwerpen zodat de zender het opnieuw kan proberen.
Error Warning (een van beide tellers bereikt 96). Nog volledig actief op de bus – de waarschuwingstoestand is een softwaresignaal dat fouten zich opstapelen, geen gedragsverandering.
Error Passive (een van beide tellers bereikt 128). Het knooppunt staat nog op de bus maar stopt met het verzenden van dominante error frames; fouten worden nu gesignaleerd met passieve (recessieve) error frames zodat een defect knooppunt niet de bus voor iedereen kan blijven verstoren.
Bus Off (TEC bereikt 256). De controller heeft besloten dat dit knooppunt te onbetrouwbaar is om deel te nemen. Het verbreekt de verbinding met de bus, stopt met zenden en bevestigen, en blijft eruit totdat software het expliciet herstart.
De eerste drie overgangen zijn volledig automatisch – naarmate de tellers na geslaagde frames verlagen, beweegt de controller zichzelf zonder tussenkomst weer terug richting Error Active.
Bus Off is de enige toestand die softwareactie vereist. restart() reset de controller en brengt hem terug naar Error Active. Een typisch patroon is om de toestand vanuit de hoofdlus te controleren en na een korte vertraging te herstarten om de bus tijd te geven om tot rust te komen:
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
De huidige tellerwaarden zijn beschikbaar via get_counters() voor diagnostiek – een TEC die gestaag oploopt op een verder rustige bus wijst meestal op bekabeling, afsluiting of een verkeerd geconfigureerde bitrate.