3.26. CAN-Bus im Code

machine.CAN umhüllt einen Hardware-CAN-Controller. Bringen Sie ihn mit der Bus-ID und einer Bitrate in Betrieb:

from machine import CAN

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

Die Bitrate muss exakt mit der jedes anderen Knotens am Bus übereinstimmen – 125_000, 250_000, 500_000 und 1_000_000 sind die üblichen Werte. set_filters() konfiguriert, welche IDs der Controller durchlässt; None bedeutet, alles zu akzeptieren (nützlich beim Einrichten einer Verbindung, weniger nützlich, sobald der Bus stark ausgelastet ist).

3.26.1. Im Inneren des Controllers

Drei Hardwarekomponenten sitzen zwischen dem Python-Code der Kamera und dem Bus – die TX-Mailboxen, ein Akzeptanzfilter und das RX-FIFO. Jede entspricht einem Teil der unten verwendeten CAN-API.

Blockschaltbild des CAN-Controllers. Auf der TX-Seite (oben) legt can.send() Frames in eine von drei TX- Mailboxen, und ein Arbitrierungsblock wählt, welche Mailbox als Nächstes auf den Bus geht. Auf der RX-Seite (unten) durchlaufen eingehende Frames einen Akzeptanzfilter; akzeptierte Frames landen im RX-FIFO, und can.recv() liest das FIFO an seinem ältesten Ende aus.

Die TX-Mailboxen, der Akzeptanzfilter und das RX-FIFO des Controllers zwischen der Software und dem Bus.

  • TX-Mailboxen. Ein kleiner Satz von Hardware-Slots (typischerweise drei), die ausgehende Frames aufnehmen, die die Kamera über send() übergeben hat, die aber noch nicht den Bus erreicht haben. Wenn der Bus frei ist, wählt der Controller die Mailbox mit der niedrigsten ID (höchste Priorität) und arbitriert in deren Namen um den Bus. Die Kamera wählt nicht die Mailbox; der Controller weist eine zu und gibt deren Index von send() zurück.

  • Akzeptanzfilter. Konfigurierbare Hardware, die die ID jedes eingehenden Frames mit einer Liste von Mustern vergleicht und alles verwirft, was nicht passt. Frames, die passieren, gelangen weiter ins RX-FIFO; abgelehnte Frames werden vom Controller verworfen, ohne dass sie je Python erreichen. set_filters() konfiguriert diese Muster.

  • RX-FIFO. Eine First-in-First-out-Warteschlange – das erste hineingelangte Frame ist auch das erste herauskommende, wie eine Schlange am Ticketschalter. Der Controller hängt empfangene Frames bei ihrem Eintreffen hinten an die Warteschlange an, und recv() zieht sie in derselben Reihenfolge vorne heraus. Die Warteschlange ist wichtig, weil der Bus Frames im Hintergrund auffängt, während Python anderweitig beschäftigt ist; die Kamera leert sie dann eines nach dem anderen, ohne welche zu verlieren, solange das FIFO nicht übergelaufen ist.

3.26.2. Ein Frame senden

send() stellt ein Frame zur Übertragung in die Warteschlange:

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

Das erste Argument ist die ID (eine 11-Bit-Ganzzahl für ein Standard-Frame); das zweite sind die Nutzdaten (0 bis 8 Bytes für CAN Classic). Der Aufruf gibt einen kleinen Ganzzahlindex zurück, der die Hardware-Mailbox identifiziert, in die das Frame gelangt ist. Der Controller arbitriert mit allen anderen Sendern am Bus und überträgt bei Bedarf erneut, ohne weiteres Zutun der Software.

Für erweiterte (29-Bit-)IDs verodern Sie das Flag CAN.FLAG_EXT_ID in das dritte Argument:

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

3.26.3. Frames empfangen

Der Controller startet ohne installierten Filter und verwirft jedes eingehende Frame. Bevor recv() etwas zurückgibt, rufen Sie einmal set_filters() auf – die einfachste Form ist None, was jede ID akzeptiert:

can.set_filters(None)   # accept every frame

recv() gibt dann das nächste Frame im Empfangs-FIFO zurück oder None, falls nichts wartet:

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

Der Bus füllt das RX-FIFO im Hintergrund, sodass die Hauptschleife es einfach so schnell leert, wie sie durchläuft. Solange das FIFO tiefer ist als die längste Lücke zwischen den Leerungen, gehen keine Frames verloren.

3.26.4. Filter

Ein echter Bus ist meist mit Frames beschäftigt, die die Kamera nicht interessieren. Hardwarefilter lassen den Controller unerwünschte IDs verwerfen, bevor sie das FIFO erreichen. set_filters() nimmt eine Liste von (id, mask, flags)-Tupeln entgegen; ein Frame passiert den Filter, wenn seine ID, maskiert mit mask, mit der konfigurierten id übereinstimmt:

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

Nicht übereinstimmende Frames werden vom Controller verworfen und erscheinen nie bei recv(), was sowohl Pufferplatz als auch CPU-Zeit spart.

3.26.5. Fehlerzustände und Wiederherstellung

Ein echter CAN-Bus fängt sich Übertragungsfehler ein – Kurzschlüsse nach Masse, fehlende Knoten, elektrisches Rauschen, das Bits verfälscht. Der Controller führt zwei Zähler, die dieses Verhalten verfolgen: einen Transmit Error Counter (TEC) und einen Receive Error Counter (REC), die jeweils erhöht werden, wenn der Controller einen Fehler erkennt, und nach einer erfolgreichen Übertragung verringert werden. Die Zählerwerte versetzen den Controller in einen von vier Zuständen:

  • Error Active (TEC und REC beide unter 96). Normalbetrieb. Wenn der Knoten einen Busfehler erkennt, sendet er ein dominantes aktives Fehler-Frame, das jeden anderen Knoten zwingt, das laufende Frame zu verwerfen, sodass der Sender es erneut versuchen kann.

  • Error Warning (einer der Zähler erreicht 96). Noch voll aktiv am Bus – der Warnzustand ist ein Softwaresignal, dass sich Fehler ansammeln, keine Verhaltensänderung.

  • Error Passive (einer der Zähler erreicht 128). Der Knoten ist noch am Bus, hört aber auf, dominante Fehler-Frames zu senden; Fehler werden nun mit passiven (rezessiven) Fehler-Frames signalisiert, sodass ein fehlerhafter Knoten den Bus nicht weiter für alle anderen lahmlegen kann.

  • Bus Off (TEC erreicht 256). Der Controller hat entschieden, dass dieser Knoten zu unzuverlässig zur Teilnahme ist. Er trennt sich vom Bus, stellt das Senden und Bestätigen ein und bleibt fern, bis die Software ihn explizit neu startet.

Die ersten drei Übergänge sind vollständig automatisch – während die Zähler nach erfolgreichen Frames sinken, bewegt sich der Controller ohne Eingriff von selbst wieder in Richtung Error Active.

Bus Off ist der eine Zustand, der ein Eingreifen der Software erfordert. restart() setzt den Controller zurück und versetzt ihn wieder in Error Active. Ein typisches Muster ist, den Zustand aus der Hauptschleife heraus zu prüfen und nach einer kurzen Verzögerung neu zu starten, um dem Bus Zeit zum Beruhigen zu geben:

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

Die aktuellen Zählerwerte sind über get_counters() für Diagnosezwecke abrufbar – ein TEC, der auf einem ansonsten ruhigen Bus stetig klettert, deutet meist auf die Verkabelung, den Abschluss oder eine falsch konfigurierte Bitrate hin.