3.26. Barramento CAN em código

machine.CAN envolve 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 tem de corresponder exatamente a 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 os IDs que o controlador deixará passar; None significa aceitar tudo (útil ao estabelecer uma ligação, menos útil quando o barramento está ocupado).

3.26.1. Dentro do controlador

Três partes de hardware ficam entre o código Python da câmara e o barramento – as caixas de correio TX, um filtro de aceitação e o FIFO RX. Cada uma mapeia para uma parte da API CAN usada abaixo.

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 caixas de correio TX, o filtro de aceitação e o FIFO RX do controlador entre o software e o barramento.

  • Caixas de correio TX. Um pequeno conjunto de slots de hardware (tipicamente três) que guardam as tramas de saída que a câmara entregou via send() mas que ainda não chegaram ao barramento. Quando o barramento está inativo, o controlador escolhe a caixa de correio com o ID de número mais baixo (prioridade mais alta) e arbitra pelo barramento em seu nome. A câmara não escolhe a caixa de correio; o controlador atribui uma e devolve o seu índice de send().

  • Filtro de aceitação. Hardware configurável que compara o ID de cada trama recebida com uma lista de padrões e descarta tudo o que não corresponda. As tramas que passam continuam para o FIFO RX; as tramas rejeitadas são descartadas pelo controlador sem nunca chegarem ao Python. set_filters() configura estes padrões.

  • FIFO RX. Uma fila primeiro a entrar, primeiro a sair – a primeira trama a entrar é também a primeira a sair, como uma fila numa bilheteira. O controlador acrescenta tramas recebidas ao final da fila à medida que chegam, e recv() retira-as do início na mesma ordem. A fila é importante porque o barramento capta tramas em segundo plano enquanto o Python está ocupado noutro sítio; a câmara drena-as uma de cada vez sem perder nenhuma, desde que o FIFO não tenha transbordado.

3.26.2. Enviar uma trama

send() coloca uma trama na fila de transmissão:

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

O primeiro argumento é o ID (um inteiro de 11 bits para uma trama padrão); o segundo é a carga útil (de 0 a 8 bytes para CAN Clássico). A chamada devolve um pequeno índice inteiro que identifica a caixa de correio de hardware para onde a trama foi. O controlador arbitra com quaisquer outros transmissores no barramento e retransmite conforme necessário sem mais ajuda do software.

Para IDs estendidos (29 bits), aplique OR com o sinalizador CAN.FLAG_EXT_ID no terceiro argumento:

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

3.26.3. Receber tramas

O controlador arranca sem filtro instalado e descarta todas as tramas recebidas. Antes de recv() devolver 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() devolve então a próxima trama no FIFO de receção, ou None se nada estiver à espera:

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

O barramento preenche o FIFO RX em segundo plano, pelo que o ciclo principal apenas o drena tão rapidamente quanto itera. Desde que o FIFO seja mais profundo do que o intervalo mais longo entre drenagens, nenhuma trama se perde.

3.26.4. Filtros

Um barramento real está normalmente ocupado com tramas que a câmara não interessa. Os filtros de hardware permitem ao controlador descartar IDs indesejados antes de chegarem ao FIFO. set_filters() aceita uma lista de tuplos (id, mask, flags); uma trama passa no filtro se o 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)))

As tramas não correspondentes são descartadas pelo controlador e nunca aparecem em recv(), o que poupa espaço de buffer e tempo de CPU.

3.26.5. Estados de erro e recuperação

Um barramento CAN real acumula erros de transmissão – curto-circuitos à massa, nós em falta, ruído elétrico a corromper bits. O controlador mantém dois contadores que rastreiam este comportamento: um Contador de Erros de Transmissão (TEC) e um Contador de Erros de Receção (REC), cada um incrementado quando o controlador deteta um erro e decrementado após uma transferência bem-sucedida. Os valores do contador colocam o controlador num de quatro estados:

  • Error Active (TEC e REC ambos abaixo de 96). Operação normal. Quando o nó deteta um erro no barramento transmite uma trama de erro ativo dominante, que força todos os outros nós a descartar a trama em progresso para que o emissor possa tentar novamente.

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

  • Error Passive (qualquer contador atinge 128). O nó ainda está no barramento mas para de enviar tramas de erro dominantes; os erros são agora sinalizados com tramas de erro passivas (recessivas) para que um nó com falha não possa continuar a perturbar o barramento para todos os outros.

  • Bus Off (TEC atinge 256). O controlador decidiu que este nó é demasiado pouco fiável para participar. Desliga-se do barramento, para de transmitir e de reconhecer, e mantém-se fora até que o software o reinicie explicitamente.

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

Bus Off é o único estado que requer ação de software. restart() reinicia o controlador e devolve-o a Error Active. Um padrão típico é verificar o estado a partir do ciclo 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 constantemente num barramento de outra forma quieto geralmente aponta para cablagem, terminação ou uma taxa de bits mal configurada.