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.
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 desend().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.