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