MCU & FPGA UART (Serial) UART não é Porta Serial: Como Projetar Protocolos Robustos em Sistemas Embarcados

UART não é Porta Serial: Como Projetar Protocolos Robustos em Sistemas Embarcados


Framing: bytes sem fronteiras não são mensagens

Depois que você aceita que UART entrega apenas um stream contínuo de bytes, a próxima pergunta inevitável é:
onde começa e onde termina uma mensagem?

Esse é o ponto onde a maioria dos firmwares entra em colapso silencioso. Sem framing explícito, o sistema depende de sorte, alinhamento perfeito e ausência de ruído. Em outras palavras: depende de condições que não existem em sistemas reais.

O erro clássico: confiar em delimitadores “humanos”

Um padrão extremamente comum é tratar UART como se fosse uma linha de texto:

// ❌ Framing frágil baseado em '\n'
char line[64];

if (uart_read_line(line, sizeof(line))) {
    if (strcmp(line, "STATUS\n") == 0) {
        send_status();
    }
}

Esse modelo falha por razões fundamentais:

  • \n é apenas um byte comum, não um marcador confiável
  • Se um byte for perdido, o framing se perde junto
  • Um \n espúrio no meio do payload quebra tudo
  • Não há como distinguir lixo de dados válidos
  • Não existe recuperação de estado

Pior: quando o framing quebra, todas as mensagens seguintes também quebram, porque o parser não sabe mais onde está.

Framing é uma decisão de protocolo, não de conveniência

Sistemas robustos definem claramente:

  • Como uma mensagem começa (Start of Frame – SOF)
  • Como ela termina (End of Frame – EOF) ou qual é seu tamanho
  • O que acontece se algo der errado no meio

Um framing explícito cria uma propriedade essencial:

o parser pode se realinhar mesmo após erro ou ruído

Abordagem correta 1: marcador de início + comprimento

Um dos modelos mais robustos e usados em sistemas industriais é:

[ SOF ][ LEN ][ PAYLOAD ][ CRC ]

Exemplo de implementação:

#define SOF 0xAA
#define MAX_PAYLOAD 64

typedef enum {
    WAIT_SOF,
    WAIT_LEN,
    WAIT_PAYLOAD,
    WAIT_CRC
} parser_state_t;

static parser_state_t state = WAIT_SOF;
static uint8_t length = 0;
static uint8_t payload[MAX_PAYLOAD];
static uint8_t index = 0;

void parser_process_byte(uint8_t byte) {
    switch (state) {

    case WAIT_SOF:
        if (byte == SOF) {
            state = WAIT_LEN;
        }
        break;

    case WAIT_LEN:
        length = byte;
        if (length > MAX_PAYLOAD) {
            state = WAIT_SOF; // framing inválido
        } else {
            index = 0;
            state = WAIT_PAYLOAD;
        }
        break;

    case WAIT_PAYLOAD:
        payload[index++] = byte;
        if (index >= length) {
            state = WAIT_CRC;
        }
        break;

    case WAIT_CRC:
        if (crc_ok(payload, length, byte)) {
            handle_packet(payload, length);
        }
        state = WAIT_SOF;
        break;
    }
}

Observe algumas propriedades importantes:

  • O parser não trava
  • Um erro não compromete os próximos frames
  • É possível descartar pacotes inválidos
  • O sistema sempre consegue se realinhar ao próximo SOF

Isso é engenharia de protocolo, não “leitura de serial”.

Abordagem correta 2: delimitador com escape (SLIP-like)

Em sistemas onde o tamanho não é conhecido antecipadamente, usa-se delimitador + escape:

[ END ][ DATA ][ DATA ][ ESC + END ][ DATA ][ END ]

Exemplo simplificado:

#define END  0xC0
#define ESC  0xDB
#define ESC_END 0xDC

void parser_process_byte(uint8_t byte) {
    static bool escape = false;

    if (byte == END) {
        handle_packet(payload, index);
        index = 0;
        escape = false;
        return;
    }

    if (escape) {
        if (byte == ESC_END) {
            payload[index++] = END;
        }
        escape = false;
        return;
    }

    if (byte == ESC) {
        escape = true;
        return;
    }

    payload[index++] = byte;
}

Esse modelo é extremamente tolerante a ruído e amplamente usado em sistemas embarcados reais, inclusive sobre UART, rádio e USB CDC.

Framing define se o sistema sobrevive ao erro

Sem framing explícito:

  • Um byte perdido corrompe tudo
  • O sistema entra em estados inválidos
  • Reset vira “estratégia de recuperação”
  • Bugs aparecem apenas em campo

Com framing explícito:

  • Erros são localizados
  • O sistema se recupera sozinho
  • Logs fazem sentido
  • Atualizações remotas se tornam viáveis

Uma regra prática

Se você não consegue responder claramente:

“Como meu parser se realinha após um erro aleatório no meio do stream?”

Então não existe protocolo, apenas esperança.


0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x