3.26. CAN-bussen i kod¶
machine.CAN omsluter en CAN-styrenhet i hårdvara. Starta den med buss-id och en bithastighet:
from machine import CAN
can = CAN(1, 500_000)
can.set_filters(None) # accept all incoming IDs
Bithastigheten måste exakt matcha alla andra noder på bussen – 125_000, 250_000, 500_000 och 1_000_000 är de vanliga värdena. set_filters() konfigurerar vilka ID:n styrenheten ska släppa igenom; None betyder att allt accepteras (användbart medan en länk startas upp, mindre användbart när bussen väl är upptagen).
3.26.1. Inuti styrenheten¶
Tre hårdvarudelar sitter mellan kamerans Python-kod och bussen – TX-brevlådorna, ett acceptansfilter och RX-FIFO:n. Var och en avbildas på en del av det CAN-API som används nedan.
Styrenhetens TX-brevlådor, acceptansfilter och RX-FIFO mellan programvaran och bussen.¶
TX-brevlådor. En liten uppsättning hårdvaruplatser (vanligtvis tre) som håller utgående bildrutor som kameran lämnat över via
send()men som ännu inte nått bussen. När bussen är ledig väljer styrenheten brevlådan med det lägst numrerade ID:t (högsta prioritet) och arbitrerar för bussen å dess vägnar. Kameran väljer inte brevlådan; styrenheten tilldelar en och returnerar dess index frånsend().Acceptansfilter. Konfigurerbar hårdvara som jämför varje inkommande bildrutas ID mot en lista med mönster och förkastar allt som inte matchar. Bildrutor som passerar fortsätter in i RX-FIFO:n; avvisade bildrutor kastas av styrenheten utan att någonsin nå Python.
set_filters()konfigurerar dessa mönster.RX-FIFO. En first-in, first-out-kö – den första bildrutan in är också den första bildrutan ut, som en kö vid en biljettlucka. Styrenheten lägger till mottagna bildrutor bakerst i kön allteftersom de anländer, och
recv()plockar av dem framifrån i samma ordning. Kön är viktig eftersom bussen fångar bildrutor i bakgrunden medan Python är upptagen någon annanstans; kameran tömmer dem sedan en i taget utan att förlora någon, så länge FIFO:n inte har svämmat över.
3.26.2. Skicka en bildruta¶
send() köar en bildruta för sändning:
can.send(0x123, b"\x01\x02\x03\x04")
Det första argumentet är ID:t (ett 11-bitars heltal för en standardbildruta); det andra är nyttolasten (0 till 8 byte för CAN Classic). Anropet returnerar ett litet heltalsindex som identifierar hårdvarubrevlådan bildrutan hamnade i. Styrenheten arbitrerar med eventuella andra sändare på bussen och sänder om vid behov utan ytterligare hjälp från programvaran.
För utökade (29-bitars) ID:n, OR:a in flaggan CAN.FLAG_EXT_ID i det tredje argumentet:
can.send(0x18FF1234, b"hello", CAN.FLAG_EXT_ID)
3.26.3. Ta emot bildrutor¶
Styrenheten startar utan något filter installerat och kasserar varje inkommande bildruta. Innan recv() returnerar något måste set_filters() anropas en gång – den enklaste formen är None, som accepterar varje ID:
can.set_filters(None) # accept every frame
recv() returnerar sedan nästa bildruta i mottagnings-FIFO:n, eller None om inget väntar:
msg = can.recv()
if msg is not None:
can_id, data, flags, errs = msg
print("got", hex(can_id), bytes(data))
Bussen fyller RX-FIFO:n i bakgrunden, så huvudloopen tömmer den bara så snabbt som den itererar. Så länge FIFO:n är djupare än det längsta mellanrummet mellan tömningar går inga bildrutor förlorade.
3.26.4. Filter¶
En verklig buss är vanligtvis upptagen med bildrutor som kameran inte bryr sig om. Hårdvarufilter låter styrenheten kassera oönskade ID:n innan de når FIFO:n. set_filters() tar en lista med (id, mask, flags)-tupler; en bildruta passerar filtret om dess ID, maskerat med mask, matchar det konfigurerade 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)))
Bildrutor som inte matchar kasseras av styrenheten och syns aldrig hos recv(), vilket sparar både buffertutrymme och CPU-tid.
3.26.5. Feltillstånd och återhämtning¶
En verklig CAN-buss drar på sig sändningsfel – kortslutningar till jord, saknade noder, elektriskt brus som förvanskar bitar. Styrenheten håller två räknare som spårar detta beteende: en Transmit Error Counter (TEC) och en Receive Error Counter (REC), var och en ökas när styrenheten upptäcker ett fel och minskas efter en lyckad överföring. Räknarvärdena placerar styrenheten i ett av fyra tillstånd:
Error Active (TEC och REC båda under 96). Normal drift. När noden upptäcker ett bussfel sänder den en dominant aktiv felbildruta, som tvingar varje annan nod att kassera den pågående bildrutan så att avsändaren kan försöka igen.
Error Warning (endera räknaren når 96). Fortfarande fullt aktiv på bussen – varningstillståndet är en programvarusignal om att fel ackumuleras, inte en beteendeförändring.
Error Passive (endera räknaren når 128). Noden är fortfarande på bussen men slutar skicka dominanta felbildrutor; fel signaleras nu med passiva (recessiva) felbildrutor så att en felaktig nod inte kan fortsätta störa bussen för alla andra.
Bus Off (TEC når 256). Styrenheten har beslutat att denna nod är för opålitlig för att delta. Den kopplar bort sig från bussen, slutar sända och kvittera, och håller sig utanför tills programvaran uttryckligen startar om den.
De tre första övergångarna är helt automatiska – allteftersom räknarna minskar efter lyckade bildrutor flyttar styrenheten sig själv tillbaka mot Error Active utan ingripande.
Bus Off är det enda tillstånd som kräver programvaruåtgärd. restart() återställer styrenheten och återför den till Error Active. Ett typiskt mönster är att kontrollera tillståndet från huvudloopen och starta om efter en kort fördröjning för att ge bussen tid att stabilisera sig:
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 aktuella räknarvärdena är tillgängliga från get_counters() för diagnostik – en TEC som stiger stadigt på en i övrigt tyst buss pekar vanligtvis på kablage, terminering eller en felkonfigurerad bithastighet.