Pular para o conteúdo

Jefferson Wilhelm Meyer Soares

ATV 10 — Da UART para a rede 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.

Quadro CAN padrão como uma faixa de campos, da esquerda para a direita, com o número de bits de cada um: SOF 1, identificador 11, RTR 1, IDE 1, r0 1, DLC 4, dados de 0 a 64, CRC 15, delimitador 1, ACK 2 e EOF 7. Os campos identificador, DLC e dados estão em azul, com a legenda preenchidos pelo programa; os outros estão em cinza, com a legenda gerados pelo controlador CAN.

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.

Questão 7 de 20 Preencha a tabela

Resolvemos juntos. Um receptor CAN mostrou Recebido: id=0x2A0 dlc=3 dados=05 1F C8.

Valor
Identificador, em hexadecimal
Bytes de dados
Último byte de dados, em hexadecimal
Bits só de dados
  1. No protocolo.h, dê um identificador à leitura, por exemplo ID_LEITURA = 0x100.
  2. Grave transmissor-can no ESP32 do DHT11 e receptor-can no 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.
  3. 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.

Questão 8 de 20 Preencha a tabela

Um quadro nos dois monitores seriais.

Identificador (hex)DLCDados (hex)
Enviado pelo transmissor
Recebido pelo receptor
Questão 9 de 20 Escolha uma

Agora é com você. O transmissor manda um quadro com id=0x100. Para quem vai esse quadro?