MCU & FPGA RTOS Zephyr RTOS: Como Projetar Comunicação Serial Robusta com UART, SPI e I²C

Zephyr RTOS: Como Projetar Comunicação Serial Robusta com UART, SPI e I²C


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ágioOnde fica no Zephyr
CapturaISR / UART Async callback
Bufferring_buffer, k_fifo, k_msgq
FramingThread dedicada ou workqueue
IntegridadeCódigo puro (determinístico)
ParsingThread de protocolo
AplicaçãoTasks 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.


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

FreeRTOS na Prática: Threads, Semáforos, Filas, Mutexes, Timers e Boas Práticas em Sistemas EmbarcadosFreeRTOS na Prática: Threads, Semáforos, Filas, Mutexes, Timers e Boas Práticas em Sistemas Embarcados

Este artigo faz parte de uma série técnica semanal sobre FreeRTOS e apresenta, de forma didática e rigorosa, os principais conceitos de concorrência e sincronização em sistemas embarcados. São explicados

0
Adoraria saber sua opinião, comente.x