Implementando um Protocolo de Aplicação para Comunicação Serial no Arduino

Comunicação Serial Profissional no Arduino: Protocolo de Aplicação

Objetivo

O objetivo deste artigo é apresentar os fundamentos da criação de um protocolo de aplicação simples para comunicação serial no Arduino, estruturando mensagens de forma padronizada, segura e previsível.

Nos artigos anteriores desta série, exploramos os conceitos fundamentais da comunicação serial, o funcionamento do objeto Serial e diferentes técnicas para leitura e interpretação de dados enviados pelo Monitor Serial da IDE. Agora, avançaremos um passo além: organizar essas mensagens dentro de um protocolo próprio de comunicação.

Em sistemas embarcados reais, enviar apenas textos soltos como LEDON ou NUMERO 1500 pode ser suficiente para testes simples, mas não oferece estrutura adequada para aplicações mais complexas. Para garantir que dois dispositivos consigam se comunicar corretamente, é necessário definir regras claras sobre o formato das mensagens, criando assim um protocolo de comunicação.

Ao final desta leitura, você será capaz de:

  • Entender o que é um protocolo de aplicação no contexto da comunicação serial
  • Compreender por que sistemas embarcados utilizam mensagens estruturadas
  • Definir um formato padronizado de mensagens para comunicação entre dispositivos
  • Implementar um protocolo simples utilizando delimitadores e campos de dados
  • Criar um interpretador de comandos baseado em protocolo no Arduino
  • Validar e processar mensagens recebidas pela porta serial
  • Preparar a base para integração com outras linguagens e sistemas externos, como Python, ESP ou outros microcontroladores

Ao final do artigo, você terá implementado um protocolo de comunicação simples, mas estruturado, capaz de tornar a troca de dados entre dispositivos mais robusta, organizada e adequada a aplicações reais em sistemas embarcados.

Referências

Comunicação Serial no Arduino com Monitor Serial – Fundamentos e Uso com String
Comunicação Serial Profissional no Arduino: char[], Buffer e Controle de Memória

Definições

Comunicação Serial

Comunicação serial é o processo de transmissão de dados bit a bit, de forma sequencial, entre dois dispositivos através de um canal de comunicação.

No Arduino Uno, Arduino Mega e em muitos outros microcontroladores, essa comunicação é realizada pela interface UART (Universal Asynchronous Receiver Transmitter).

Ela utiliza dois pinos principais:

  • TX (Transmit) → responsável por enviar dados (Pino 0 no Arduino Uno)
  • RX (Receive) → responsável por receber dados (Pino 1 no Arduino Uno)

Quando conectamos o Arduino ao computador via USB, um conversor USB-Serial presente na placa transforma os sinais elétricos TX/RX em dados que podem ser interpretados pelo computador.

Esses dados podem ser visualizados ou enviados através do Monitor Serial da Arduino IDE, permitindo que o computador e o microcontrolador troquem informações em tempo real.

O que é um Protocolo de Comunicação?

Um protocolo de comunicação é um conjunto de regras que define como os dados são estruturados, transmitidos e interpretados entre dois dispositivos.

Essas regras estabelecem, por exemplo:

  • Como uma mensagem começa e termina
  • Como os dados são organizados dentro da mensagem
  • Como comandos e valores devem ser interpretados

Sem um protocolo definido, os dados transmitidos podem se tornar ambíguos, inconsistentes ou difíceis de processar.

Por exemplo, considere a seguinte mensagem enviada ao Arduino: LEDON

Embora seja simples, não existe nenhuma estrutura que indique claramente as seguintes dúvidas:

  • Onde a mensagem começa e termina?
  • Existe algum parâmetro associado?
  • A mensagem foi recebida completamente?
  • Como diferenciar esse comando de outros semelhantes?

Em aplicações simples isso pode funcionar, mas em sistemas mais complexos pode gerar erros de interpretação.

Protocolo de Aplicação no Contexto do Arduino

Dentro da comunicação serial no Arduino, é importante entender que existem diferentes níveis (ou camadas) de comunicação.

De forma simplificada:

  • A UART é responsável por transmitir bits (nível físico)
  • O objeto Serial permite enviar e receber dados (nível de interface)
  • O protocolo de aplicação define o significado dos dados transmitidos

Ou seja:

A UART transporta os dados, mas é o protocolo de aplicação que dá sentido a eles.

O protocolo de aplicação portanto, é a camada responsável por definir como as mensagens devem ser organizadas e interpretadas logicamente dentro do sistema.

Ele estabelece um padrão que permite que diferentes dispositivos compreendam exatamente o que está sendo transmitido.

Para este artigo, adotaremos o seguinte formato:

<COMANDO|PARAMETRO>

Regras do Protocolo no formato acima

Nesse modelo, definimos as seguintes regras:

  • < → indica o início da mensagem
  • > → indica o fim da mensagem
  • | → separa os campos da mensagem
  • O primeiro campo representa o comando
  • O segundo campo representa o parâmetro

Por que isso é importante?

Com essa padronização:

  • O Arduino consegue identificar mensagens completas
  • O processamento se torna mais simples e previsível
  • Reduzimos ambiguidades na interpretação dos dados
  • Facilitamos a comunicação com outros sistemas (Python, ESP, etc.)

Dessa forma, deixamos de trabalhar com texto livre e passamos a utilizar uma estrutura de comunicação definida, característica essencial em sistemas embarcados mais robustos.

Exemplos de mensagens estruturadas:

<LED|1>
<LED|0>
<TEMP|?>
<MOTOR|1500>

Interpretação das mensagens:

  • <LED|1> → ligar o LED
  • <LED|0> → desligar o LED
  • <TEMP|?> → solicitar leitura de temperatura
  • <MOTOR|1500> → definir velocidade ou posição do motor

Observe que, independentemente do comando, todas as mensagens seguem exatamente o mesmo padrão estrutural.

Vantagens da Estrutura Definida

Ao utilizar delimitadores e campos bem definidos, o sistema passa a ter várias vantagens:

1️⃣ Identificação clara da mensagem: O Arduino consegue detectar facilmente quando uma mensagem começa (<) e quando termina (>).

2️⃣ Separação organizada dos dados: O caractere | permite separar o comando do parâmetro, facilitando o processamento.

3️⃣ Maior robustez na comunicação: Se uma mensagem chegar incompleta ou com formato incorreto, o sistema pode simplesmente ignorá-la.

4️⃣ Facilidade de expansão: Novos comandos podem ser adicionados sem alterar a estrutura do protocolo.

Limitações do Protocolo

Nosso protocolo é intencionalmente simples, pois o objetivo deste artigo é demonstrar os conceitos fundamentais.

Protocolos mais avançados podem incluir recursos adicionais, como:

  • checksum ou CRC para verificação de erros
  • tamanho da mensagem
  • identificação de dispositivo
  • confirmação de recebimento (ACK/NACK)

Esses recursos são comuns em sistemas profissionais, mas para este tutorial utilizaremos uma estrutura simples, suficiente para demonstrar como implementar um protocolo de aplicação no Arduino.

Implementando o Mecanismo de Recepção do Protocolo

Depois de definir a estrutura do protocolo, o próximo passo é implementar no Arduino um mecanismo capaz de receber, montar e interpretar as mensagens enviadas pela porta serial.

Como vimos anteriormente, nosso protocolo utiliza delimitadores para indicar o início e o fim de cada mensagem:

<COMANDO|PARAMETRO>

Para que o Arduino consiga interpretar corretamente essas mensagens, precisamos implementar um processo capaz de:

  • Detectar o início da mensagem
  • Armazenar os caracteres recebidos
  • Detectar o fim da mensagem
  • Finalizar a string recebida
  • Enviar a mensagem para processamento

Esse mecanismo é normalmente implementado utilizando leitura serial não bloqueante, combinada com um buffer de recepção.

Estratégia de Implementação

O funcionamento do sistema será baseado em três etapas principais.

1️⃣ Detectar o início da mensagem

Quando o Arduino receber o caractere <, significa que uma nova mensagem está começando.

Nesse momento:

  • O índice do buffer é reiniciado
  • O sistema começa a armazenar os caracteres recebidos

2️⃣ Armazenar os caracteres da mensagem

Enquanto os caracteres chegam pela porta serial, eles são armazenados em um buffer de memória.

Por exemplo, ao receber:

<LED|1>

o buffer irá armazenar gradualmente:

L
LE
LED
LED|
LED|1

3️⃣ Detectar o fim da mensagem

Quando o Arduino recebe o caractere >, significa que a mensagem foi completamente recebida.

Nesse momento:

  • O buffer é finalizado com '\0'
  • A mensagem se torna uma string válida em C
  • A função responsável pelo processamento da mensagem é chamada

Fluxo Simplificado do Processo

O funcionamento geral do sistema pode ser representado da seguinte forma:

 Serial → Detecta '<' → Armazena dados → Detecta '>' → Processa comando 

Esse modelo garante que:

  • O Arduino nunca fica aguardando dados
  • Outras tarefas do sistema continuam funcionando
  • A mensagem só é processada quando estiver completa

Implementação do Código

A seguir está um exemplo de implementação do mecanismo de recepção do protocolo.

#define TAM_BUFFER 40

char buffer[TAM_BUFFER];
byte indice = 0;
bool recebendo = false;

void setup() {

  Serial.begin(9600);
  pinMode(13, OUTPUT);

  Serial.println("Sistema pronto.");
}

void loop() {

  while (Serial.available() > 0) {

    char c = Serial.read();

    // Detecta início da mensagem
    if (c == '<') {

      indice = 0;
      recebendo = true;
    }

    // Detecta fim da mensagem
    else if (c == '>') {

      buffer[indice] = '\0';
      recebendo = false;

      processaMensagem();
    }

    // Armazena caracteres intermediários
    else if (recebendo) {

      if (indice < TAM_BUFFER - 1) {
        buffer[indice++] = c;
      }
    }
  }
}

Nesse código:

  • < inicia a captura da mensagem
  • > finaliza a captura
  • os caracteres intermediários são armazenados no buffer

Ao final da recepção, a função processaMensagem() será responsável por interpretar o conteúdo da mensagem recebida.

Conteúdo do Buffer

Se o usuário enviar a mensagem:

<LED|1>

o buffer armazenará apenas o conteúdo interno:

LED|1

Isso facilita a separação dos campos da mensagem, pois já removemos automaticamente os delimitadores < e >.

Interpretando a Mensagem: Separando Comando e Parâmetro

Após implementar o mecanismo de recepção, o Arduino já é capaz de capturar corretamente uma mensagem estruturada enviada pela comunicação serial.

Por exemplo, ao receber o conteúdo armazenado no buffer:

LED|1

Precisamos agora interpretar essa mensagem, separando:

  • COMANDOLED
  • PARÂMETRO1

Para isso, utilizaremos funções da biblioteca padrão da linguagem C: string.h.

Biblioteca <string.h>

A biblioteca <string.h> fornece funções eficientes para manipulação de strings no formato char[].

Neste artigo, utilizaremos principalmente:

  • strtok() → separar a string em partes (tokens)
  • strcmp() → comparar strings

Separando os Dados com strtok()

A função strtok() permite dividir uma string em partes menores com base em um delimitador.

Sintaxe

char* token = strtok(buffer, "|");
  • buffer → string original
  • "|" → delimitador

Observação Importante sobre strtok()

  • A função strtok() modifica o conteúdo original da string, substituindo o delimitador (|) por '\0'.

  • A função strtok() continua a leitura da string a partir da posição onde parou anteriormente, permitindo acessar as próximas partes da mensagem ao usar NULL nas chamadas seguintes.

Por que usar char* ?

No código acima (sintaxe) o * indica que a variável token é um ponteiro para char.

Isso significa que:

???? token não armazena diretamente os caracteres,
???? ele armazena o endereço na memória onde a string está localizada.

Na prática,

  • Você pode usar token como se fosse uma string normal
  • Funções como strcmp() funcionam normalmente com ele
  • Ele apenas “aponta” para uma parte do buffer original

Por exemplo:

char* comando = strtok(buffer, "|");

comando passa a apontar para o início da palavra "LED" dentro do buffer.

Vantagem disso no Arduino

  • Não duplica dados na memória
  • É muito rápido
  • Ideal para sistemas com pouca RAM (como o Arduino Uno)

Exemplo prático

Dado o conteúdo:

LED|1

Podemos fazer:

char* comando = strtok(buffer, "|");
char* parametro = strtok(NULL, "|");

Resultado:

  • comando"LED"
  • parametro"1"

O que cada linha faz:

char* comando = strtok(buffer, "|");

  • Ação: A função strtok() analisa o buffer, procura o primeiro caractere | e “divide” a string nesse ponto.
  • Resultado: A variável comando passa a apontar para o início da primeira parte da mensagem, por exemplo: "LED"
  • O “pulo do gato”: O strtok() substitui o caractere | por '\0', encerrando a primeira string diretamente na memória.

char* parametro = strtok(NULL, "|");

  • Ação: O uso de NULL indica para a função → “Continue a partir de onde você parou na chamada anterior.
  • Resultado: A função ignora a parte já processada e retorna o próximo trecho da string. A variável parametro passa a apontar para "1" (ou "ON", dependendo da mensagem enviada).

Comparando Comandos com strcmp()

Após separar os dados, utilizamos strcmp() para verificar qual comando foi recebido.

Sintaxe

strcmp(str1, str2)
  • Retorna 0 se as strings forem iguais

Exemplo

if (strcmp(comando, "LED") == 0) {
  // comando reconhecido
}

Implementação Completa da Interpretação

Agora vamos integrar tudo na função processaMensagem():

#include <string.h>

void processaMensagem() {

  // Separa comando e parâmetro
  char* comando = strtok(buffer, "|");
  char* parametro = strtok(NULL, "|");

  // Validação básica
  if (comando == NULL || parametro == NULL) {
    Serial.println("Erro: mensagem inválida");
    return;
  }

  // ===== COMANDO LED =====
  if (strcmp(comando, "LED") == 0) {

    if (strcmp(parametro, "1") == 0) {
      digitalWrite(13, HIGH);
      Serial.println("LED ligado");
    }

    else if (strcmp(parametro, "0") == 0) {
      digitalWrite(13, LOW);
      Serial.println("LED desligado");
    }

    else {
      Serial.println("Erro: parâmetro inválido");
    }
  }

  // ===== COMANDO MOTOR =====
  else if (strcmp(comando, "MOTOR") == 0) {

    int valor = atoi(parametro);

    Serial.print("Motor ajustado para: ");
    Serial.println(valor);
  }

  // ===== COMANDO TEMP =====
  else if (strcmp(comando, "TEMP") == 0) {

    Serial.println("Temperatura: 25 C (simulada)");
  }

  // ===== COMANDO DESCONHECIDO =====
  else {
    Serial.println("Erro: comando desconhecido");
  }
}

O que esse código faz?

✔ Divide a mensagem em partes
✔ Identifica o comando
✔ Interpreta o parâmetro
✔ Executa ações diferentes conforme o comando
✔ Trata erros básicos

Código Completo — Protocolo de Aplicação no Arduino

Este código implementa:

✔ Leitura serial não bloqueante
✔ Protocolo estruturado <COMANDO|PARAMETRO>
✔ Interpretação com strtok() e strcmp()
✔ Tratamento básico de erros

#include <string.h>

// =======================================================
// CONFIGURAÇÃO DO SISTEMA
// =======================================================

#define TAM_BUFFER 40

char buffer[TAM_BUFFER];
byte indice = 0;

bool recebendo = false; // controla se estamos dentro de < >

// =======================================================
// SETUP
// =======================================================

void setup() {
  Serial.begin(9600);
  pinMode(13, OUTPUT);

  Serial.println("Sistema pronto.");
  Serial.println("Envie comandos no formato:");
  Serial.println("<LED|1>  -> Liga LED");
  Serial.println("<LED|0>  -> Desliga LED");
  Serial.println("<MOTOR|1500>");
  Serial.println("<TEMP|?>");
  Serial.println("-------------------------");
}

// =======================================================
// LOOP PRINCIPAL
// =======================================================

void loop() {

  while (Serial.available() > 0) {

    char c = Serial.read();

    // INÍCIO DA MENSAGEM
    if (c == '<') {
      indice = 0;
      recebendo = true;
    }

    // FIM DA MENSAGEM
    else if (c == '>') {
      buffer[indice] = '\0'; // finaliza string
      recebendo = false;

      processaMensagem();
    }

    // ARMAZENA DADOS
    else if (recebendo) {

      buffer[indice++] = c;

      // PROTEÇÃO CONTRA OVERFLOW
      if (indice >= TAM_BUFFER - 1) {
        indice = TAM_BUFFER - 1;
      }
    }
  }

  // Aqui você pode adicionar outras tarefas sem bloqueio
}

// =======================================================
// PROCESSAMENTO DA MENSAGEM
// =======================================================

void processaMensagem() {

  Serial.print("Recebido: ");
  Serial.println(buffer);

  // SEPARA COMANDO E PARÂMETRO
  char* comando = strtok(buffer, "|");
  char* parametro = strtok(NULL, "|");

  // VALIDAÇÃO
  if (comando == NULL || parametro == NULL) {
    Serial.println("Erro: mensagem inválida");
    return;
  }

  // ===================================================
  // COMANDO LED
  // ===================================================
  if (strcmp(comando, "LED") == 0) {

    if (strcmp(parametro, "1") == 0) {
      digitalWrite(13, HIGH);
      Serial.println("OK: LED ligado");
    }

    else if (strcmp(parametro, "0") == 0) {
      digitalWrite(13, LOW);
      Serial.println("OK: LED desligado");
    }

    else {
      Serial.println("Erro: parâmetro LED inválido");
    }
  }

  // ===================================================
  // COMANDO MOTOR (SIMULADO)
  // ===================================================
  else if (strcmp(comando, "MOTOR") == 0) {

    int valor = atoi(parametro);

    Serial.print("OK: Motor ajustado para ");
    Serial.println(valor);
  }

  // ===================================================
  // COMANDO TEMPERATURA (SIMULADO)
  // ===================================================
  else if (strcmp(comando, "TEMP") == 0) {

    Serial.println("OK: Temperatura = 25 C");
  }

  // ===================================================
  // COMANDO DESCONHECIDO
  // ===================================================
  else {
    Serial.println("Erro: comando desconhecido");
  }
}

Como testar no Monitor Serial

Configure:

  • Baud rate: 9600
  • Final de linha: "Nova linha" (newline)

Exemplos de comandos

Digite no Monitor Serial:

<LED|1>
<LED|0>
<MOTOR|1500>
<TEMP|?>

Resultado

Conclusão

Com este artigo, você deu um passo importante além do uso básico da comunicação serial no Arduino.

Deixamos de trabalhar com comandos simples e passamos a implementar um protocolo de aplicação estruturado, capaz de organizar, validar e interpretar mensagens de forma clara e previsível.

Ao longo desta série, evoluímos progressivamente:

  • No primeiro artigo, entendemos os fundamentos da comunicação serial e o uso da classe String
  • No segundo, exploramos uma abordagem mais robusta com char[], buffers e leitura não bloqueante
  • Agora, no terceiro, aplicamos esses conceitos para construir um sistema de comunicação mais próximo da prática profissional

Mais do que enviar dados ao Monitor Serial, você agora compreende:

  • Como as mensagens são estruturadas
  • Como são armazenadas na memória
  • Como são interpretadas pelo microcontrolador
  • E como projetar regras de comunicação entre sistemas

Esse conhecimento é essencial para aplicações como:

  • Integração com Python
  • Comunicação entre microcontroladores
  • Sistemas IoT
  • Interfaces homem-máquina
  • Protocolos personalizados

A partir daqui, você não está apenas “programando Arduino”.

Você está projetando sistemas de comunicação embarcados.


O anúncio abaixo ajuda a manter o Squids Arduino funcionando

Comentários

×

Infomações do site / SEO








×

Adicionar Marcadores