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.

Blockschema över CAN-styrenheten. På TX-sidan (överst) släpper can.send() bildrutor i en av tre TX- brevlådor, och ett arbitreringsblock väljer vilken brevlåda som går till bussen härnäst. På RX-sidan (nederst) passerar inkommande bildrutor genom ett acceptansfilter; accepterade bildrutor landar i RX-FIFO:n och can.recv() läser FIFO:n från dess äldsta ände.

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

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