ATV 10 — Da UART para a rede CAN
Seções O quadro CAN
O quadro CAN
No CAN, a informação viaja em quadros. Cada quadro leva um identificador de 11 bits, que diz o que é aquela mensagem, um DLC, que diz quantos bytes de dados vêm, de 0 a 8, e os dados. Não há endereço de destino: o quadro vai para o barramento todo, e cada nó decide pelo identificador se aquela mensagem interessa.
O identificador também é a prioridade. Na disputa pelo barramento, os nós transmitem o identificador bit a bit; quem manda um recessivo e lê um dominante perde e para. Por isso, quanto menor o identificador, maior a prioridade.
O resto do quadro o controlador CAN monta sozinho: o início (SOF), o CRC para conferir os bits, o campo de ACK e o fim (EOF). A configuração só escolhe o identificador e onde vai cada informação nos dados.
O campo de ACK é a confirmação: o transmissor deixa esse bit recessivo, e qualquer nó que recebeu o quadro sem erro o puxa para dominante enquanto ele passa. Se ninguém puxa, o transmissor sabe que ninguém recebeu.
O gráfico mostra quanto tempo um quadro ocupa o barramento, pela taxa e pelo número de bytes de dados.
Ver código
import matplotlib.pyplot as plt
def br(valor, casas=0):
return f"{valor:.{casas}f}".replace(".", ",")
campos = [("SOF", 1), ("identificador", 11), ("RTR, IDE, r0", 3), ("DLC", 4), ("dados", 8 * int(dlc)), ("CRC", 16), ("ACK", 2), ("EOF", 7), ("intervalo", 3)]
programa = {"identificador", "DLC", "dados"}
bit_us = 1000 / taxa
fig, eixo = plt.subplots(figsize=(11, 2.6))
inicio = 0
for nome, bits in campos:
if bits == 0:
continue
eixo.barh(0, bits * bit_us, left=inicio * bit_us, color="C0" if nome in programa else "C7", edgecolor="white")
if bits >= 4:
eixo.text((inicio + bits / 2) * bit_us, 0, nome, ha="center", va="center", color="white", fontsize=9)
inicio += bits
eixo.set_yticks([])
eixo.set_xlabel("tempo (µs)")
eixo.set_title(f"um quadro com {int(dlc)} byte(s) de dados a {int(taxa)} kbit/s; em azul, o que o programa preenche")
fig.tight_layout()
print(f"Duração de um bit: {br(bit_us, 2)} µs")
print(f"Quadro: {inicio} bits, {br(inicio * bit_us)} µs, sem contar os bits de enchimento")
print(f"Dados: {8 * int(dlc)} de {inicio} bits ({br(100 * 8 * int(dlc) / inicio)} %)")
O arquivo protocolo.h é a configuração da rede:
const uint32_t ID_LEITURA = 0x000; // identificador da mensagem com a leitura do DHT11
const uint32_t ID_FALHA = 0x000; // identificador da mensagem de falha do DHT11
const int BYTE_TEMPERATURA = 0; // posição da temperatura nos dados do quadro, de 0 a 7
const int BYTE_UMIDADE = 1; // posição da umidade nos dados do quadro, de 0 a 7
const int TAXA_KBPS = 500; // 125, 250, 500 ou 1000
const uint32_t FILTRO_ID = 0; // 0 aceita todos os identificadores
Transmissor e receptor usam o mesmo arquivo. O transmissor mostra no monitor serial cada quadro que envia, e o receptor, cada quadro que recebe, os dois no mesmo formato: id=0x100 dlc=2 dados=1B 58.
- No
protocolo.h, dê um identificador à leitura, por exemploID_LEITURA = 0x100. - Grave
transmissor-canno ESP32 do DHT11 ereceptor-canno outro. O DHT11 precisa estar ligado: se a leitura dele falha, o transmissor manda outro quadro, o de falha, que fica para a próxima parte. - Abra o monitor serial dos dois e anote um quadro de cada lado.
Logo depois de gravar, o erros_tx do transmissor pode aparecer alto e ir caindo de 1 em 1. Enquanto um ESP32 está sendo gravado, ninguém confirma os quadros do outro, e cada quadro sem confirmação conta como erro. A última parte explica esse contador.