O “melhor algoritmo” no Zephyr para UART (e por que ele também serve para SPI, I²C e afins)
Aqui é onde o artigo anterior do mcu.tec.br realmente se conecta com o Zephyr:
UART não é porta serial, é transporte bruto.
Logo, o “melhor algoritmo” não é uma API específica, mas um pipeline arquitetural que separa responsabilidades de forma rígida.
Se você errar essa separação, nenhuma RTOS, driver ou DMA vai te salvar.
2.1 O algoritmo certo não começa na UART — começa na arquitetura
Vamos deixar isso explícito:
O algoritmo correto é este:
[ Driver / DMA / ISR ]
↓
[ Buffer de Captura (Ring Buffer / FIFO) ]
↓
[ Framing (máquina de estados) ]
↓
[ Integridade (CRC / checksum / tamanho) ]
↓
[ Parsing semântico ]
↓
[ Lógica de aplicação ]
Esse pipeline não é opcional.
Ele é o mínimo para sair do nível demo e entrar no nível sistema embarcado real.
Erro clássico (muito comum):
UART RX callback
↓
if (strcmp(cmd, "ON") == 0)
liga_motor();
Isso não é firmware.
Isso é um acidente esperando acontecer.
2.2 O papel do Zephyr nesse algoritmo
O Zephyr não decide o protocolo por você.
O que ele faz é te dar pontos de encaixe corretos para cada estágio:
| Estágio | Onde fica no Zephyr |
|---|---|
| Captura | ISR / UART Async callback |
| Buffer | ring_buffer, k_fifo, k_msgq |
| Framing | Thread dedicada ou workqueue |
| Integridade | Código puro (determinístico) |
| Parsing | Thread de protocolo |
| Aplicação | Tasks normais |
Certo: cada camada isolada.
Errado: misturar ISR, parsing e lógica de negócio na mesma função.
2.3 Framing: o coração do algoritmo (onde tudo dá errado)
Sem framing, não existe protocolo, só ruído.
Existem três estratégias sérias:
1️⃣ Delimitadores explícitos (STX / ETX)
Exemplo:
0x02 <payload> 0x03
✔ Simples
❌ Frágil se payload não for escapado
2️⃣ Tamanho explícito (Length-prefixed)
Exemplo:
[SOF][LEN][PAYLOAD][CRC]
✔ Robusto
✔ Muito usado em produção
✔ Ideal para UART, SPI, I²C
3️⃣ Protocolos auto-sincronizáveis (CBOR, SLIP, COBS)
✔ Muito robustos
❌ Mais complexos
❌ Overkill para MCU pequeno (às vezes)
👉 Para Zephyr + MCU, o melhor custo-benefício é Length + CRC.
2.4 Implementando framing com máquina de estados (do jeito certo)
Aqui entra o algoritmo real, não a API.
Estados típicos:
typedef enum {
RX_WAIT_SOF,
RX_WAIT_LEN,
RX_WAIT_PAYLOAD,
RX_WAIT_CRC
} rx_state_t;
Contexto do parser:
#define MAX_PAYLOAD 256
typedef struct {
rx_state_t state;
uint8_t len;
uint8_t payload[MAX_PAYLOAD];
uint8_t index;
uint16_t crc;
} rx_parser_t;
Função de parsing byte-a-byte (independente da UART):
void protocol_parse_byte(rx_parser_t *p, uint8_t b)
{
switch (p->state) {
case RX_WAIT_SOF:
if (b == 0xAA) {
p->state = RX_WAIT_LEN;
p->index = 0;
}
break;
case RX_WAIT_LEN:
p->len = b;
if (p->len > MAX_PAYLOAD) {
p->state = RX_WAIT_SOF;
} else {
p->state = RX_WAIT_PAYLOAD;
}
break;
case RX_WAIT_PAYLOAD:
p->payload[p->index++] = b;
if (p->index >= p->len) {
p->state = RX_WAIT_CRC;
}
break;
case RX_WAIT_CRC:
/* aqui você validaria CRC real */
if (b == 0x55) { /* exemplo simplificado */
/* pacote válido */
on_packet_received(p->payload, p->len);
}
p->state = RX_WAIT_SOF;
break;
}
}
⚠️ Observação crítica:
Essa função não sabe se o byte veio da UART, SPI, I²C ou rádio.
Isso é engenharia correta.
2.5 Ligando o ring buffer ao parser (Zephyr-style)
Agora o encaixe limpo com o Zephyr:
void protocol_task(void)
{
static rx_parser_t parser = {
.state = RX_WAIT_SOF
};
uint8_t buf[32];
while (1) {
uint32_t n = ring_buf_get(&rx_rb, buf, sizeof(buf));
for (uint32_t i = 0; i < n; i++) {
protocol_parse_byte(&parser, buf[i]);
}
k_sleep(K_MSEC(1));
}
}
Certo:
- ISR → ring buffer
- Thread → parser
- Parser → evento / callback
Errado:
- ISR → parser
- Parser → lógica de aplicação
- Lógica →
printf
2.6 Por que esse algoritmo é superior no Zephyr
Porque ele resolve todos os problemas reais:
✔ Ruído na linha
✔ Bytes perdidos
✔ Pacotes truncados
✔ Re-sincronização automática
✔ Portabilidade para SPI / I²C / USB / BLE
✔ Testabilidade (parser testável em PC)
E, principalmente:
Ele força você a pensar em comunicação como sistema, não como string.
Isso é exatamente a filosofia defendida no artigo anterior do mcu.tec.br.
2.7 O erro mais comum de quem “usa Zephyr errado”
“O Zephyr é pesado / complicado / lento.”
Na prática, quase sempre significa:
- parsing no callback
- lógica de negócio na UART
- nenhuma estratégia de framing
- nenhum plano de recuperação
O RTOS vira bode expiatório de erro arquitetural.