3.26. CAN bus no código

machine.CAN encapsula um controlador CAN de hardware. Inicialize-o com o id do barramento e uma taxa de bits:

from machine import CAN

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

A taxa de bits deve corresponder exatamente à de todos os outros nós no barramento – 125_000, 250_000, 500_000 e 1_000_000 são os valores comuns. set_filters() configura quais IDs o controlador deixará passar; None significa aceitar tudo (útil ao colocar um enlace em funcionamento, menos útil quando o barramento está ocupado).

3.26.1. Dentro do controlador

Três peças de hardware ficam entre o código Python da câmera e o barramento – as mailboxes de TX, um filtro de aceitação e a FIFO de RX. Cada uma mapeia para uma parte da API CAN usada a seguir.

Block diagram of the CAN controller. On the TX side (top), can.send() drops frames into one of three TX mailboxes, and an arbitrate block picks which mailbox goes to the bus next. On the RX side (bottom), incoming frames pass through an acceptance filter; accepted frames land in the RX FIFO and can.recv() reads the FIFO from its oldest end.

As mailboxes de TX, o filtro de aceitação e a FIFO de RX do controlador entre o software e o barramento.

  • Mailboxes de TX. Um pequeno conjunto de slots de hardware (tipicamente três) que retêm os quadros de saída que a câmera entregou via send() mas que ainda não chegaram ao barramento. Quando o barramento está ocioso, o controlador escolhe a mailbox com o ID de número mais baixo (prioridade mais alta) e arbitra pelo barramento em nome dela. A câmera não escolhe a mailbox; o controlador atribui uma e retorna seu índice a partir de send().

  • Filtro de aceitação. Hardware configurável que compara o ID de cada quadro recebido com uma lista de padrões e descarta tudo que não corresponde. Os quadros que passam seguem para a FIFO de RX; os quadros rejeitados são descartados pelo controlador sem nunca chegarem ao Python. set_filters() configura esses padrões.

  • FIFO de RX. Uma fila first-in, first-out – o primeiro quadro a entrar é também o primeiro a sair, como uma fila em um guichê. O controlador acrescenta os quadros recebidos ao final da fila à medida que chegam, e recv() os retira da frente na mesma ordem. A fila importa porque o barramento captura quadros em segundo plano enquanto o Python está ocupado em outro lugar; a câmera então os esvazia um de cada vez sem perder nenhum, desde que a FIFO não tenha transbordado.

3.26.2. Enviando um quadro

send() enfileira um quadro para transmissão:

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

O primeiro argumento é o ID (um inteiro de 11 bits para um quadro padrão); o segundo é o payload (0 a 8 bytes para o CAN Classic). A chamada retorna um pequeno índice inteiro que identifica a mailbox de hardware na qual o quadro foi colocado. O controlador arbitra com quaisquer outros transmissores no barramento e retransmite conforme necessário sem mais ajuda do software.

Para IDs estendidos (29 bits), faça um OR da flag CAN.FLAG_EXT_ID no terceiro argumento:

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

3.26.3. Recebendo quadros

O controlador inicia sem nenhum filtro instalado e descarta todo quadro recebido. Antes que recv() retorne qualquer coisa, chame set_filters() uma vez – a forma mais simples é None, que aceita todos os IDs:

can.set_filters(None)   # accept every frame

recv() então retorna o próximo quadro na FIFO de recepção, ou None se nada estiver aguardando:

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

O barramento preenche a FIFO de RX em segundo plano, então o laço principal apenas a esvazia tão rápido quanto itera. Desde que a FIFO seja mais profunda do que o maior intervalo entre esvaziamentos, nenhum quadro é perdido.

3.26.4. Filtros

Um barramento real costuma estar ocupado com quadros com os quais a câmera não se importa. Os filtros de hardware permitem que o controlador descarte IDs indesejados antes que cheguem à FIFO. set_filters() recebe uma lista de tuplas (id, mask, flags); um quadro passa pelo filtro se seu ID, mascarado por mask, corresponder ao id configurado:

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

Os quadros não correspondentes são descartados pelo controlador e nunca aparecem em recv(), o que economiza tanto espaço de buffer quanto tempo de CPU.

3.26.5. Estados de erro e recuperação

Um barramento CAN real acumula erros de transmissão – curtos para o terra, nós ausentes, ruído elétrico corrompendo bits. O controlador mantém dois contadores que rastreiam esse comportamento: um Transmit Error Counter (TEC) e um Receive Error Counter (REC), cada um incrementado quando o controlador detecta um erro e decrementado após uma transferência bem-sucedida. Os valores dos contadores colocam o controlador em um de quatro estados:

  • Error Active (TEC e REC ambos abaixo de 96). Operação normal. Quando o nó detecta um erro de barramento, ele transmite um quadro de erro ativo dominante, que força todos os outros nós a descartar o quadro em andamento para que o remetente possa tentar novamente.

  • Error Warning (qualquer um dos contadores atinge 96). Ainda totalmente ativo no barramento – o estado de aviso é um sinal de software de que os erros estão se acumulando, não uma mudança de comportamento.

  • Error Passive (qualquer um dos contadores atinge 128). O nó ainda está no barramento mas para de enviar quadros de erro dominantes; os erros agora são sinalizados com quadros de erro passivos (recessivos), de modo que um nó defeituoso não possa continuar atrapalhando o barramento para todos os demais.

  • Bus Off (TEC atinge 256). O controlador decidiu que este nó é não confiável demais para participar. Ele se desconecta do barramento, para de transmitir e de confirmar, e fica fora até que o software o reinicie explicitamente.

As três primeiras transições são inteiramente automáticas – à medida que os contadores decrementam após quadros bem-sucedidos, o controlador se move de volta em direção ao Error Active sem intervenção.

O Bus Off é o único estado que requer ação do software. restart() reinicia o controlador e o retorna ao Error Active. Um padrão típico é verificar o estado a partir do laço principal e reiniciar após um curto atraso para dar tempo ao barramento de se estabilizar:

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

Os valores atuais dos contadores estão disponíveis em get_counters() para diagnóstico – um TEC que sobe de forma constante em um barramento de resto silencioso geralmente aponta para fiação, terminação ou uma taxa de bits mal configurada.