3.26. Le bus CAN dans le code

machine.CAN enveloppe un contrôleur CAN matériel. Initialisez-le avec l’identifiant du bus et un débit binaire :

from machine import CAN

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

Le débit binaire doit correspondre exactement à celui de tous les autres nœuds du bus – 125_000, 250_000, 500_000 et 1_000_000 sont les valeurs courantes. set_filters() configure quels ID le contrôleur laissera passer ; None signifie tout accepter (utile lors de la mise en service d’une liaison, moins utile une fois le bus chargé).

3.26.1. À l’intérieur du contrôleur

Trois éléments matériels se placent entre le code Python de la caméra et le bus – les boîtes aux lettres TX, un filtre d’acceptation et la FIFO RX. Chacun correspond à une partie de l’API CAN utilisée ci-dessous.

Schéma fonctionnel du contrôleur CAN. Côté TX (en haut), can.send() dépose les trames dans l'une des trois boîtes aux lettres TX, et un bloc d'arbitrage choisit quelle boîte aux lettres passe ensuite sur le bus. Côté RX (en bas), les trames entrantes traversent un filtre d'acceptation ; les trames acceptées atterrissent dans la FIFO RX et can.recv() lit la FIFO par son extrémité la plus ancienne.

Les boîtes aux lettres TX, le filtre d’acceptation et la FIFO RX du contrôleur, entre le logiciel et le bus.

  • Boîtes aux lettres TX. Un petit ensemble d’emplacements matériels (généralement trois) qui contiennent les trames sortantes que la caméra a transmises via send() mais qui n’ont pas encore atteint le bus. Lorsque le bus est inactif, le contrôleur choisit la boîte aux lettres dont l’ID a le numéro le plus faible (priorité la plus élevée) et arbitre le bus en son nom. La caméra ne choisit pas la boîte aux lettres ; le contrôleur en assigne une et renvoie son indice depuis send().

  • Filtre d’acceptation. Un dispositif matériel configurable qui compare l’ID de chaque trame entrante à une liste de motifs et rejette tout ce qui ne correspond pas. Les trames qui passent poursuivent vers la FIFO RX ; les trames rejetées sont écartées par le contrôleur sans jamais atteindre Python. set_filters() configure ces motifs.

  • FIFO RX. Une file premier entré, premier sorti – la première trame entrée est aussi la première trame sortie, comme une file d’attente à un guichet. Le contrôleur ajoute les trames reçues à l’arrière de la file au fur et à mesure de leur arrivée, et recv() les retire par l’avant dans le même ordre. La file est importante parce que le bus capte les trames en arrière-plan pendant que Python est occupé ailleurs ; la caméra les vide ensuite une à une sans en perdre aucune, tant que la FIFO n’a pas débordé.

3.26.2. Envoyer une trame

send() met une trame en file d’attente pour transmission :

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

Le premier argument est l’ID (un entier de 11 bits pour une trame standard) ; le second est la charge utile (de 0 à 8 octets pour CAN Classic). L’appel renvoie un petit indice entier identifiant la boîte aux lettres matérielle dans laquelle la trame est entrée. Le contrôleur arbitre avec tout autre émetteur présent sur le bus et retransmet au besoin sans autre intervention du logiciel.

Pour les ID étendus (29 bits), combinez par OU le drapeau CAN.FLAG_EXT_ID dans le troisième argument :

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

3.26.3. Recevoir des trames

Le contrôleur démarre sans aucun filtre installé et rejette chaque trame entrante. Avant que recv() ne renvoie quoi que ce soit, appelez set_filters() une fois – la forme la plus simple est None, qui accepte tous les ID :

can.set_filters(None)   # accept every frame

recv() renvoie alors la trame suivante de la FIFO de réception, ou None si rien n’est en attente :

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

Le bus remplit la FIFO RX en arrière-plan, de sorte que la boucle principale n’a qu’à la vider aussi vite qu’elle itère. Tant que la FIFO est plus profonde que le plus long intervalle entre deux vidages, aucune trame n’est perdue.

3.26.4. Filtres

Un bus réel est généralement occupé par des trames dont la caméra n’a que faire. Les filtres matériels permettent au contrôleur de rejeter les ID indésirables avant qu’ils n’atteignent la FIFO. set_filters() prend une liste de tuples (id, mask, flags) ; une trame passe le filtre si son ID, masqué par mask, correspond à l”id configuré :

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

Les trames non correspondantes sont écartées par le contrôleur et n’apparaissent jamais à recv(), ce qui économise à la fois de l’espace de tampon et du temps processeur.

3.26.5. États d’erreur et récupération

Un bus CAN réel accumule des erreurs de transmission – courts-circuits à la masse, nœuds manquants, bruit électrique corrompant des bits. Le contrôleur tient deux compteurs qui suivent ce comportement : un compteur d’erreurs de transmission (TEC) et un compteur d’erreurs de réception (REC), chacun incrémenté lorsque le contrôleur détecte une erreur et décrémenté après un transfert réussi. Les valeurs des compteurs placent le contrôleur dans l’un de quatre états :

  • Error Active (TEC et REC tous deux inférieurs à 96). Fonctionnement normal. Lorsque le nœud détecte une erreur de bus, il transmet une trame d’erreur active dominante, qui force chaque autre nœud à écarter la trame en cours afin que l’émetteur puisse réessayer.

  • Error Warning (l’un des compteurs atteint 96). Toujours pleinement actif sur le bus – l’état d’avertissement est un signal logiciel indiquant que les erreurs s’accumulent, et non un changement de comportement.

  • Error Passive (l’un des compteurs atteint 128). Le nœud est toujours sur le bus mais cesse d’envoyer des trames d’erreur dominantes ; les erreurs sont désormais signalées par des trames d’erreur passives (récessives) afin qu’un nœud défaillant ne puisse pas continuer à perturber le bus pour tous les autres.

  • Bus Off (le TEC atteint 256). Le contrôleur a décidé que ce nœud est trop peu fiable pour participer. Il se déconnecte du bus, cesse d’émettre et d’acquitter, et reste à l’écart jusqu’à ce que le logiciel le redémarre explicitement.

Les trois premières transitions sont entièrement automatiques – à mesure que les compteurs décroissent après des trames réussies, le contrôleur revient de lui-même vers Error Active sans intervention.

Bus Off est le seul état qui nécessite une action logicielle. restart() réinitialise le contrôleur et le ramène à Error Active. Un schéma typique consiste à vérifier l’état depuis la boucle principale et à redémarrer après un court délai pour laisser au bus le temps de se stabiliser :

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

Les valeurs actuelles des compteurs sont disponibles via get_counters() à des fins de diagnostic – un TEC qui grimpe régulièrement sur un bus par ailleurs calme pointe généralement vers le câblage, la terminaison ou un débit binaire mal configuré.