3.26. Magistrala CAN w kodzie

machine.CAN opakowuje sprzętowy kontroler CAN. Uruchom go, podając identyfikator magistrali oraz szybkość transmisji bitów:

from machine import CAN

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

Szybkość transmisji bitów musi dokładnie odpowiadać każdemu innemu węzłowi na magistrali – 125_000, 250_000, 500_000 i 1_000_000 to typowe wartości. set_filters() konfiguruje, które identyfikatory kontroler przepuści; None oznacza akceptowanie wszystkiego (przydatne podczas uruchamiania łącza, mniej przydatne, gdy magistrala jest już zajęta).

3.26.1. Wewnątrz kontrolera

Pomiędzy kodem Pythona kamery a magistralą znajdują się trzy elementy sprzętowe – skrzynki nadawcze TX, filtr akceptacji oraz kolejka FIFO odbioru RX. Każdy z nich odpowiada fragmentowi API CAN używanego poniżej.

Schemat blokowy kontrolera CAN. Po stronie TX (u góry) can.send() umieszcza ramki w jednej z trzech skrzynek nadawczych TX, a blok arbitrażu wybiera, która skrzynka trafia na magistralę jako następna. Po stronie RX (u dołu) przychodzące ramki przechodzą przez filtr akceptacji; zaakceptowane ramki lądują w kolejce FIFO odbioru RX, a can.recv() odczytuje FIFO od jego najstarszego końca.

Skrzynki nadawcze TX, filtr akceptacji i kolejka FIFO odbioru RX kontrolera pomiędzy oprogramowaniem a magistralą.

  • Skrzynki nadawcze TX. Niewielki zestaw sprzętowych szczelin (zwykle trzech), które przechowują wychodzące ramki przekazane przez kamerę za pomocą send(), ale które jeszcze nie dotarły na magistralę. Gdy magistrala jest bezczynna, kontroler wybiera skrzynkę o identyfikatorze o najniższym numerze (najwyższym priorytecie) i przeprowadza arbitraż o magistralę w jej imieniu. Kamera nie wybiera skrzynki; kontroler przydziela jedną i zwraca jej indeks z send().

  • Filtr akceptacji. Konfigurowalny sprzęt, który porównuje identyfikator każdej przychodzącej ramki z listą wzorców i odrzuca wszystko, co nie pasuje. Ramki, które przejdą, trafiają dalej do kolejki FIFO odbioru RX; odrzucone ramki są wyrzucane przez kontroler, nigdy nie docierając do Pythona. set_filters() konfiguruje te wzorce.

  • Kolejka FIFO odbioru RX. Kolejka first-in, first-out – pierwsza ramka, która weszła, jest też pierwszą, która wychodzi, niczym kolejka przy okienku z biletami. Kontroler dołącza odebrane ramki na koniec kolejki w miarę ich nadchodzenia, a recv() pobiera je z przodu w tej samej kolejności. Kolejka ma znaczenie, ponieważ magistrala wychwytuje ramki w tle, gdy Python jest zajęty czymś innym; kamera następnie opróżnia je pojedynczo, nie tracąc żadnej, dopóki kolejka FIFO się nie przepełni.

3.26.2. Wysyłanie ramki

send() umieszcza ramkę w kolejce do transmisji:

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

Pierwszym argumentem jest identyfikator (11-bitowa liczba całkowita dla ramki standardowej); drugim jest ładunek użyteczny (od 0 do 8 bajtów dla CAN Classic). Wywołanie zwraca małą liczbę całkowitą będącą indeksem identyfikującym sprzętową skrzynkę, do której trafiła ramka. Kontroler przeprowadza arbitraż z innymi nadajnikami na magistrali i ponawia transmisję w razie potrzeby bez dalszej pomocy ze strony oprogramowania.

Dla identyfikatorów rozszerzonych (29-bitowych) wykonaj operację OR z flagą CAN.FLAG_EXT_ID na trzecim argumencie:

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

3.26.3. Odbieranie ramek

Kontroler uruchamia się bez zainstalowanego filtra i odrzuca każdą przychodzącą ramkę. Zanim recv() cokolwiek zwróci, wywołaj raz set_filters() – najprostszą postacią jest None, która akceptuje każdy identyfikator:

can.set_filters(None)   # accept every frame

recv() zwraca wówczas następną ramkę w kolejce FIFO odbioru lub None, jeśli nic nie oczekuje:

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

Magistrala wypełnia kolejkę FIFO odbioru RX w tle, więc główna pętla po prostu opróżnia ją tak szybko, jak się wykonuje. Dopóki FIFO jest głębsza niż najdłuższa przerwa między opróżnieniami, żadne ramki nie są tracone.

3.26.4. Filtry

Prawdziwa magistrala jest zwykle zajęta ramkami, na których kamerze nie zależy. Filtry sprzętowe pozwalają kontrolerowi odrzucić niechciane identyfikatory, zanim dotrą one do FIFO. set_filters() przyjmuje listę krotek (id, mask, flags); ramka przechodzi przez filtr, jeśli jej identyfikator zamaskowany przez mask pasuje do skonfigurowanego 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)))

Niepasujące ramki są odrzucane przez kontroler i nigdy nie pojawiają się w recv(), co oszczędza zarówno miejsce w buforze, jak i czas procesora.

3.26.5. Stany błędu i odzyskiwanie

Prawdziwa magistrala CAN wychwytuje błędy transmisji – zwarcia do masy, brakujące węzły, zakłócenia elektryczne uszkadzające bity. Kontroler prowadzi dwa liczniki śledzące to zachowanie: Transmit Error Counter (TEC) i Receive Error Counter (REC), każdy zwiększany, gdy kontroler wykryje błąd, i zmniejszany po udanym przesłaniu. Wartości liczników wprowadzają kontroler w jeden z czterech stanów:

  • Error Active (TEC i REC oba poniżej 96). Normalne działanie. Gdy węzeł wykryje błąd magistrali, nadaje dominującą aktywną ramkę błędu, która zmusza każdy inny węzeł do odrzucenia trwającej ramki, aby nadawca mógł ponowić próbę.

  • Error Warning (którykolwiek licznik osiąga 96). Wciąż w pełni aktywny na magistrali – stan ostrzeżenia jest programowym sygnałem, że błędy się kumulują, a nie zmianą zachowania.

  • Error Passive (którykolwiek licznik osiąga 128). Węzeł jest nadal na magistrali, ale przestaje wysyłać dominujące ramki błędu; błędy są teraz sygnalizowane pasywnymi (recesywnymi) ramkami błędu, dzięki czemu wadliwy węzeł nie może w nieskończoność rozrywać magistrali dla wszystkich pozostałych.

  • Bus Off (TEC osiąga 256). Kontroler uznał, że ten węzeł jest zbyt niepewny, aby uczestniczyć w komunikacji. Odłącza się od magistrali, przestaje nadawać i potwierdzać oraz pozostaje wyłączony, dopóki oprogramowanie go jawnie nie zrestartuje.

Pierwsze trzy przejścia są całkowicie automatyczne – w miarę jak liczniki zmniejszają się po udanych ramkach, kontroler sam wraca w kierunku stanu Error Active bez interwencji.

Bus Off jest jedynym stanem, który wymaga działania oprogramowania. restart() resetuje kontroler i przywraca go do stanu Error Active. Typowy wzorzec polega na sprawdzaniu stanu z głównej pętli i restarcie po krótkim opóźnieniu, aby dać magistrali czas na ustabilizowanie się:

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

Bieżące wartości liczników są dostępne z get_counters() do celów diagnostycznych – TEC, który stale rośnie na skądinąd cichej magistrali, zwykle wskazuje na okablowanie, terminację lub błędnie skonfigurowaną szybkość transmisji bitów.