Início
/
Software
/
Dicas de Software
/
Comunicação Serial Avançada no Arduino: ACK, NACK e Timeout em Protocolos de Aplicação
Comunicação Serial Avançada no Arduino: ACK, NACK e Timeout em Protocolos de Aplicação
Angelo Luis Ferreira | 17/05/2026
Acessos: 69
Comunicação Serial avançada com Arduino - Validação de mensagens recebidas e executadas
Objetivo

O objetivo deste artigo é aprofundar o desenvolvimento de protocolos de aplicação na comunicação serial do Arduino, implementando mecanismos de confirmação de mensagens (ACK/NACK) e controle de timeout para tornar a comunicação mais robusta, previsível e confiável.
Nos artigos anteriores desta série, construímos progressivamente:
- Os fundamentos da comunicação serial no Arduino
- Leitura não bloqueante utilizando
char[]
- Manipulação manual de buffers
- Parsing de comandos
- Implementação de um protocolo simples de aplicação estruturado
Agora avançaremos para um novo nível:
garantir que as mensagens realmente foram recebidas, interpretadas e executadas corretamente.
Para isso, implementaremos um sistema baseado em:
-
ACK (Acknowledgment) → confirmação de recebimento
-
NACK (Negative Acknowledgment) → indicação de erro
-
Timeout → detecção de falha ou ausência de resposta
E evoluiremos para uma etapa mais próxima de sistemas embarcados profissionais, adicionando mecanismos capazes de:
- Confirmar mensagens válidas
- Detectar falhas de comunicação
- Identificar mensagens incompletas
- Tratar erros de protocolo
- Evitar espera infinita por dados
Ao final deste artigo, você será capaz de:
- Entender o funcionamento de protocolos confiáveis de comunicação
- Implementar confirmação de mensagens no Arduino
- Detectar falhas utilizando timeout
- Criar respostas automáticas ACK/NACK
- Desenvolver parsers mais robustos
- Validar mensagens recebidas
- Construir sistemas embarcados mais previsíveis e seguros
Este conteúdo representa um passo importante na evolução entre projetos didáticos e arquiteturas utilizadas em automação, IoT e sistemas embarcados profissionais.
Referências
Este artigo dá continuidade à série sobre comunicação serial no Arduino:
Recomenda-se a leitura prévia dos artigos anteriores para melhor compreensão dos conceitos abordados aqui.
Definições
ACK (Acknowledgment)
ACK é uma mensagem de confirmação enviada por um dispositivo para informar que uma mensagem foi recebida e processada corretamente.
Por exemplo:
Na prática:
- Um comando é enviado ao Arduino
- O Arduino interpreta e executa o comando
- O Arduino responde com ACK
Isso permite confirmar que a comunicação ocorreu corretamente.
NACK (Negative Acknowledgment)
NACK é uma mensagem utilizada para indicar erro na comunicação ou no processamento do comando.
Ela pode ocorrer quando:
- A mensagem possui formato inválido
- O comando não existe
- O parâmetro recebido é inválido
- O buffer foi corrompido
- O timeout foi excedido
Exemplos:
O uso de NACK torna o protocolo mais robusto e previsível.
Timeout
Timeout é o tempo máximo permitido para que uma operação seja concluída.
Na comunicação serial, ele é utilizado para detectar situações como:
- Mensagens incompletas
- Falhas de transmissão ou perda de dados
- Travamentos de comunicação
- Ausência de resposta
Por exemplo:
O Arduino começa a receber:
Mas o caractere > nunca chega.
Nesse caso, o sistema pode aguardar por um período máximo e cancelar automaticamente a mensagem incompleta.
Isso evita que o parser fique preso indefinidamente esperando o restante dos dados.
Comunicação Serial Confiável
Nos artigos anteriores, implementamos protocolos estruturados para organização das mensagens.
Agora adicionaremos mecanismos de confiabilidade ao sistema.
Isso significa que o Arduino será capaz de:
- Confirmar mensagens válidas
- Detectar erros
- Ignorar mensagens inválidas
- Cancelar recepções incompletas
- Proteger o parser contra corrupção de dados
Esse modelo é amplamente utilizado em:
- Automação industrial
- Sistemas IoT
- Comunicação entre microcontroladores
- Equipamentos eletrônicos
- Protocolos embarcados
Estrutura do Protocolo
Neste artigo continuaremos utilizando mensagens no formato:
<LED|1>
<LED|0>
<BUZZER|1>
<BUZZER|0>
Respostas do sistema:
Neste projeto utilizaremos:
- Arduino Uno
- LEDs
- Resistores
- Buzzer
- Protoboard
- Monitor Serial da Arduino IDE
O Monitor Serial será utilizado como terminal de testes para envio e recebimento das mensagens do protocolo.
O que será desenvolvido ao longo do artigo?
Ao longo deste tutorial implementaremos:
✔ Parser robusto
✔ Controle de LEDs
✔ Controle de buzzer
✔ ACK/NACK automático
✔ Timeout de recepção
✔ Validação de mensagens
✔ Tratamento de erros
✔ Comunicação serial mais segura
Resultado Final Esperado
Ao final do projeto, teremos um sistema semelhante ao fluxo abaixo:
Se ocorrer erro:
Resposta:
Ou mensagem incompleta:
Resultado:
Estratégia de Funcionamento
Nosso parser funcionará da seguinte forma:
- Aguarda o caractere
<
- Inicia captura da mensagem
- Armazena os caracteres no buffer
- Aguarda o caractere
>
- Finaliza a string com
\0
- Processa a mensagem
- Responde com ACK ou NACK
Além disso:
- Se a mensagem ultrapassar o tamanho do buffer → erro
- Se o caractere
> não chegar dentro do tempo esperado → timeout
- Se o comando for inválido → NACK
Código de Exemplo
#include <string.h>
// =======================================================
// CONFIGURAÇÕES
// =======================================================
#define TIMEOUT 3000 // 3 segundos
// =======================================================
// PINOS
// =======================================================
const byte LED = 13;
const byte BUZZER = 8;
// =======================================================
// BUFFER
// =======================================================
char buffer[40];
byte indice = 0;
// =======================================================
// CONTROLE DO PARSER
// =======================================================
bool recebendo = false;
unsigned long tempoInicial = 0;
// =======================================================
// SETUP
// =======================================================
void setup() {
Serial.begin(9600);
pinMode(LED, OUTPUT);
pinMode(BUZZER, OUTPUT);
Serial.println("Sistema pronto.");
Serial.println("Protocolo iniciado.");
Serial.println("Exemplo:");
Serial.println("<LED|1>");
Serial.println("<BUZZER|0>");
Serial.println("-------------------");
}
// =======================================================
// LOOP PRINCIPAL
// =======================================================
void loop() {
while (Serial.available() > 0) {
char c = Serial.read();
// ---------------------------------------------------
// Ignora lixo antes do caractere <
// ---------------------------------------------------
if (!recebendo && c != '<') {
continue;
}
// ---------------------------------------------------
// INÍCIO DA MENSAGEM
// ---------------------------------------------------
if (c == '<') {
recebendo = true;
indice = 0;
tempoInicial = millis();
continue;
}
// ---------------------------------------------------
// RECEBIMENTO DA MENSAGEM
// ---------------------------------------------------
if (recebendo) {
// FINAL DA MENSAGEM
if (c == '>') {
buffer[indice] = '\0';
recebendo = false;
processaMensagem();
indice = 0;
}
else {
// PROTEÇÃO CONTRA OVERFLOW
if (indice < 39) {
buffer[indice++] = c;
}
else {
recebendo = false;
indice = 0;
Serial.println("<NACK|OVERFLOW>");
}
}
}
}
// =====================================================
// CONTROLE DE TIMEOUT
// =====================================================
if (recebendo) {
if (millis() - tempoInicial > TIMEOUT) {
recebendo = false;
indice = 0;
Serial.println("<NACK|TIMEOUT>");
}
}
}
// =======================================================
// PROCESSAMENTO DA MENSAGEM
// =======================================================
void processaMensagem() {
// Divide comando e parâmetro
char* comando = strtok(buffer, "|");
char* parametro = strtok(NULL, "|");
// ---------------------------------------------------
// VALIDAÇÃO
// ---------------------------------------------------
if (comando == NULL || parametro == NULL) {
Serial.println("<NACK|FORMATO_INVALIDO>");
return;
}
// =====================================================
// COMANDO LED
// =====================================================
if (strcmp(comando, "LED") == 0) {
if (strcmp(parametro, "1") == 0) {
digitalWrite(LED, HIGH);
Serial.println("<ACK|LED_ON>");
}
else if (strcmp(parametro, "0") == 0) {
digitalWrite(LED, LOW);
Serial.println("<ACK|LED_OFF>");
}
else {
Serial.println("<NACK|PARAMETRO_LED>");
}
}
// =====================================================
// COMANDO BUZZER
// =====================================================
else if (strcmp(comando, "BUZZER") == 0) {
if (strcmp(parametro, "1") == 0) {
tone(BUZZER, 1000);
Serial.println("<ACK|BUZZER_ON>");
}
else if (strcmp(parametro, "0") == 0) {
noTone(BUZZER);
Serial.println("<ACK|BUZZER_OFF>");
}
else {
Serial.println("<NACK|PARAMETRO_BUZZER>");
}
}
// =====================================================
// COMANDO INVÁLIDO
// =====================================================
else {
Serial.println("<NACK|COMANDO_INVALIDO>");
}
}
Exemplos de resultados

Como o Código Funciona
1. Início da Mensagem
O parser aguarda o caractere:
o sistema:
- ativa o modo de recepção
- limpa o índice
- inicia o controle de timeout
2. Armazenamento no Buffer
Os caracteres recebidos são armazenados:
3. Finalização da Mensagem
Quando chega:
a string é finalizada:
Isso transforma o conteúdo em uma string válida do padrão C.
4. Parsing da Mensagem
O código utiliza:
para dividir:
- comando →
"LED"
- parâmetro →
"1"
5. ACK
Se o comando for válido:
Se ocorrer erro:
Se a mensagem começar mas não terminar:
o sistema aguardará:
Após 3 segundos:
Como o Parser Funciona Internamente
Nos exemplos anteriores utilizamos um parser capaz de receber mensagens estruturadas, validar seu formato e executar comandos. Mas o que acontece internamente desde o momento em que um caractere chega pela porta serial até a execução de uma ação?
Parser ou analisador sintático é o código que analisa a entrada de dados brutos da comunicação serial e executa os seguintes passos:
- Delimitação: Ele identifica onde uma mensagem começa e termina.
- Tokenização: Divide a mensagem recebida em partes menores ou variáveis (os tokens).
- Processamento: Executa os comandos de acordo com os parâmetros recebidos.
Compreender esse fluxo é fundamental para desenvolver protocolos mais robustos e confiáveis.
Fluxo Completo da Mensagem
Considere a seguinte mensagem enviada pelo Monitor Serial:
Os caracteres chegam ao Arduino um a um. Cada caractere é armazenado temporariamente no buffer de recepção (RX) da comunicação serial.
O loop principal verifica constantemente se existem dados disponíveis:
Quando existe pelo menos um byte disponível (após o digitar a tecla [enter]), o caractere é lido:
A partir desse momento, o parser passa a decidir o que fazer com cada caractere recebido.
Execução do Parser - Estados:
Estado 1 — Aguardando Início da Mensagem
Nesse estado:
- O sistema ignora caracteres inválidos.
- Aguarda o caractere
<.
- Nenhum dado é armazenado.
Exemplo:
Os caracteres: a b c x y z são ignorados.
Somente quando o parser encontra: <
ele muda de estado.
Estado 2 — Recebendo Mensagem
Agora cada caractere recebido passa a ser armazenado no buffer:
Exemplo:
Ao encontrar: >
a recepção termina e a mensagem é enviada para processamento.
Fluxo Visual do Parser
Esse modelo simples é extremamente eficiente e amplamente utilizado em sistemas embarcados.
Como o Buffer Evolui Durante a Recepção
Suponha a mensagem:
A evolução do buffer será:
Após receber
MOTOR|150
Quando o caractere > chega:
O conteúdo torna-se:
Agora temos uma string válida para as funções da biblioteca <string.h>, podendo ser realizada a tokenização.
Tratamento de Mensagens Corrompidas
Um protocolo robusto não assume que todas as mensagens serão válidas.
1️⃣ Por exemplo:
Neste caso, o parâmetro não existe.
Quando executamos:
teremos:
A validação detecta o problema:
Resposta:
2️⃣ Outro exemplo:
O comando existe, mas o parâmetro é inválido.
Resposta:
3️⃣ Outro caso:
O formato está correto.
Mas o comando não existe.
Resposta:
Como o Timeout Aumenta a Robustez
Imagine que a comunicação seja interrompida durante a transmissão:
O caractere > nunca chega.
Sem timeout:
❌ O parser ficaria aguardando indefinidamente.
Com timeout:
o sistema cancela automaticamente a operação:
Isso evita travamentos e mensagens presas no buffer.
Como o Controle de Overflow Protege a Memória
Suponha que alguém envie uma mensagem com mais de 40 caracteres:
Se não existisse proteção:
continuaria gravando além do tamanho do array definido.
Isso poderia:
- Corromper variáveis vizinhas
- Produzir comportamentos imprevisíveis
- Travar o Arduino
- Reiniciar o sistema
Por isso utilizamos:
Quando o limite é ultrapassado:
O que este artigo introduz de importante?
Com esse modelo, passamos a implementar conceitos reais de engenharia embarcada:
✔ Comunicação confiável
✔ Parser robusto
✔ Tratamento de falhas
✔ Timeout
✔ ACK/NACK
✔ Proteção contra overflow
✔ Estrutura de protocolo
Esses mecanismos são utilizados em:
- automação industrial
- IoT
- comunicação serial profissional
- protocolos embarcados
- sistemas críticos
Por que usar os termos ACK/NACK?
ACK e NACK não são obrigatórios — eles são uma convenção amplamente usada em protocolos de comunicação.
Vantagens:
1️⃣ Padronização: Engenheiros e sistemas embarcados reconhecem imediatamente o significado de ACK (Acknowledgment) e NACK (Negative Acknowledgment).
2️⃣ Baixo consumo de banda: Mensagens curtas ocupam menos bytes na transmissão serial.
3️⃣ Facilidade de automação: Softwares conseguem verificar rapidamente se a resposta começa com ACK ou NACK.
Pode ser mais amigável?
Sim. Em projetos educacionais, interfaces gráficas ou aplicações voltadas ao usuário final, respostas mais descritivas podem ser melhores.
Exemplos:
<SUCESSO|LED_LIGADO>
<ERRO|PARAMETRO_INVALIDO>
Conclusão
Ao longo deste artigo, evoluímos o protocolo de aplicação desenvolvido anteriormente adicionando três elementos fundamentais para a construção de sistemas embarcados mais confiáveis: ACK, NACK e Timeout.
Com o mecanismo de ACK, o Arduino passou a confirmar explicitamente que uma mensagem foi recebida e processada com sucesso. Com o NACK, o sistema tornou-se capaz de informar erros de formato, comandos inválidos, parâmetros incorretos e outras falhas de comunicação. Já o controle de Timeout permitiu detectar mensagens incompletas e evitar que o parser permanecesse aguardando dados indefinidamente.
Além disso, implementamos um parser não bloqueante baseado em char[], proteção contra overflow, tratamento de mensagens corrompidas e um fluxo de processamento mais robusto — características presentes em protocolos utilizados em automação, IoT e sistemas embarcados profissionais.
Este artigo mostrou como projetar uma comunicação serial previsível, segura e preparada para crescer. Os conceitos apresentados aqui formam a base para recursos mais avançados, como checksum, retransmissão automática, máquinas de estados e comunicação entre múltiplos dispositivos.
Dominar ACK, NACK e Timeout é um passo essencial para sair da comunicação serial puramente didática e entrar no universo dos protocolos de comunicação confiáveis utilizados no mundo real.
O anúncio abaixo ajuda a manter o Squids Arduino funcionando
Comentários