Checksum e Integridade de Dados na Comunicação Serial do Arduino

Comunicação Serial avançada com Arduino - Checksum e Integridade de Dados

Objetivo

O objetivo deste artigo é apresentar o conceito de checksum como mecanismo de verificação da integridade dos dados na comunicação serial do Arduino.

Nos artigos anteriores desta série, desenvolvemos um protocolo de aplicação capaz de interpretar comandos estruturados, validar mensagens e responder automaticamente utilizando ACK, NACK e Timeout. Entretanto, ainda existe uma situação importante que não foi tratada: como garantir que uma mensagem recebida não foi alterada durante a transmissão.

Para resolver esse problema, implementaremos um algoritmo simples de checksum, permitindo que o Arduino verifique a integridade de cada mensagem antes de processá-la.

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

  • Compreender o conceito de integridade de dados na comunicação serial.
  • Entender a diferença entre erros de protocolo e erros de transmissão.
  • Implementar um algoritmo simples de checksum utilizando a linguagem C++ do Arduino.
  • Adicionar um campo de verificação às mensagens do protocolo de aplicação.
  • Validar automaticamente mensagens recebidas antes de executá-las.
  • Detectar mensagens corrompidas durante a transmissão.
  • Responder adequadamente utilizando ACK ou NACK de acordo com o resultado da validação.
  • Desenvolver protocolos de comunicação mais confiáveis e preparados para aplicações embarcadas.

Este artigo representa mais um passo na evolução da série sobre comunicação serial, aproximando o protocolo desenvolvido de técnicas utilizadas em equipamentos industriais, sistemas de automação, dispositivos IoT e aplicações embarcadas profissionais, onde a confiabilidade da transmissão é tão importante quanto o próprio conteúdo da mensagem.

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

Integridade de Dados

Na comunicação serial, não basta apenas transmitir uma mensagem entre dois dispositivos. Também é importante garantir que todos os dados recebidos sejam exatamente os mesmos que foram enviados.

Esse conceito é conhecido como integridade de dados.

Quando ocorre qualquer alteração durante a transmissão — seja por interferência elétrica, ruído, falhas de comunicação ou erros de software — a mensagem recebida pode deixar de representar a informação original.

Por exemplo, suponha que o computador envie o comando:

<LED|1>

Mas, durante a transmissão, um dos caracteres seja alterado:

<LED|7>
Embora o protocolo continue obedecendo ao formato esperado, o conteúdo da mensagem foi modificado.

Sem algum mecanismo de verificação, o Arduino não tem como saber que ocorreu essa alteração e poderá executar um comando incorreto.

O que é Checksum?

Checksum é uma técnica utilizada para verificar se uma mensagem foi recebida corretamente.

Seu funcionamento baseia-se na geração de um valor numérico calculado a partir dos próprios dados da mensagem.

Esse valor é enviado juntamente com a mensagem e recalculado pelo dispositivo receptor.

Se os dois valores forem iguais, considera-se que a mensagem foi recebida corretamente.

Caso sejam diferentes, presume-se que ocorreu alguma alteração durante a transmissão.

Em outras palavras, o checksum funciona como uma assinatura numérica da mensagem.

Como o Checksum Funciona?

O funcionamento do checksum pode ser dividido em quatro etapas:

  1. O dispositivo emissor calcula o checksum da mensagem.
  2. O checksum é enviado junto com os dados.
  3. O dispositivo receptor calcula novamente o checksum utilizando a mensagem recebida.
  4. Os dois valores são comparados.

Se ambos forem iguais:

Mensagem válida

Se forem diferentes:

Mensagem corrompida

Essa verificação ocorre antes que o comando seja executado.

Estrutura do Novo Protocolo

Nos artigos anteriores utilizamos mensagens no formato:

<COMANDO|PARAMETRO>

Por exemplo:

<LED|1>
<MOTOR|150>
<TEMP|?>

A partir deste artigo, adicionaremos um terceiro campo contendo o checksum:

<COMANDO|PARAMETRO|CHECKSUM>
Exemplos
<LED|1|113>
<MOTOR|150|225>
<TEMP|?|184>
O último campo representa o valor utilizado para verificar a integridade da mensagem.

Antes de executar qualquer comando, o Arduino recalculará o checksum da mensagem recebida e comparará o resultado com o valor informado.

Erro de Protocolo × Erro de Integridade

É importante distinguir dois tipos de falhas que podem ocorrer durante a comunicação.

Erro de protocolo ocorre quando a mensagem não segue a estrutura definida pelo protocolo.

Exemplos:

<LED>
<LED|>
<LED|1
LED|1>

Nesses casos, o parser não consegue interpretar corretamente a mensagem.

Já o erro de integridade ocorre quando a estrutura da mensagem está correta, mas algum caractere foi alterado durante a transmissão.

Exemplo:

Mensagem enviada:

<LED|1|113>

Mensagem recebida:

<LED|7|113>

O protocolo continua válido, porém o conteúdo foi modificado.

Esse tipo de problema somente pode ser detectado por mecanismos de verificação, como o checksum.

Checksum não Corrige Erros

É importante observar que o checksum não corrige mensagens corrompidas.

Sua função é apenas detectar que houve alguma alteração nos dados transmitidos.

Ao identificar uma inconsistência, o Arduino poderá:

  • rejeitar a mensagem;
  • responder com um NACK indicando erro de checksum;
  • aguardar o reenvio da mensagem.

Esse comportamento aumenta significativamente a confiabilidade do protocolo.

Checksum e Protocolos Profissionais

O algoritmo que implementaremos neste artigo possui finalidade didática e é ideal para compreender os princípios da verificação de integridade.

Em aplicações profissionais, normalmente são utilizados algoritmos mais robustos, como:

  • CRC (Cyclic Redundancy Check)
  • Fletcher Checksum
  • Adler-32
  • SHA (para aplicações criptográficas)

Apesar de serem mais sofisticados, todos seguem a mesma ideia fundamental: gerar um valor de verificação a partir da mensagem e utilizá-lo para confirmar sua integridade durante a comunicação.

Implementando o Checksum na Comunicação Serial

Agora que entendemos o conceito de integridade de dados, vamos implementar um mecanismo simples de checksum em nosso protocolo de comunicação serial.

O objetivo é permitir que o Arduino verifique automaticamente se a mensagem recebida permaneceu íntegra durante toda a transmissão.

Para isso, utilizaremos um algoritmo bastante simples, ideal para fins didáticos e para pequenos sistemas embarcados.

O Algoritmo Utilizado

Existem diversos algoritmos de checksum, alguns extremamente sofisticados. Neste artigo utilizaremos um dos mais simples: a soma dos códigos ASCII de todos os caracteres da mensagem.

O funcionamento é bastante direto:

  1. Percorremos todos os caracteres da mensagem.
  2. Obtemos o valor ASCII de cada caractere.
  3. Somamos todos esses valores.
  4. O resultado será o checksum da mensagem.

Considere a seguinte mensagem:

LED|1

Os códigos ASCII de cada caractere são:

 

Somando todos os valores: 76 + 69 + 68 + 124 + 49 = 386

Logo, o checksum da mensagem será: 386

No nosso exemplo o usuário não precisará calcular esse valor manualmente. Basta enviar ao Arduino a mensagem:

<LED|1>

O próprio Arduino calculará o checksum e montará internamente a mensagem completa, e o nosso protocolo passa então a transmitir:

<LED|1|386>

Observe que o checksum é calculado apenas sobre o conteúdo da mensagem (LED|1), sem considerar os delimitadores < e > nem o próprio valor do checksum.

Essa decisão simplifica o algoritmo e facilita sua implementação.

Observação importante: 

Validação da Mensagem

Quando o Arduino receber uma mensagem, ele executará novamente o mesmo cálculo.

Por exemplo, ao receber:

<LED|1|386>

o parser irá:

  1. Separar os campos da mensagem.
  2. Extrair o comando (LED).
  3. Extrair o parâmetro (1).
  4. Extrair o checksum recebido (386).
  5. Calcular novamente o checksum utilizando apenas LED|1.
  6. Comparar os dois valores.

Se ambos forem iguais:

Checksum recebido = 386
Checksum calculado = 386

a mensagem será considerada válida e poderá ser executada normalmente.

Nesse caso, o Arduino responderá:

<ACK|LED_ON>

Detectando Alterações na Mensagem

Agora imagine que, durante a transmissão, um caractere seja alterado.

Mensagem enviada:

<LED|1|386>

Mensagem recebida:

<LED|7|386>

Embora a estrutura do protocolo continue correta, o conteúdo da mensagem foi modificado.

Ao recalcular o checksum, o Arduino obterá: 76 + 69 + 68 + 124 + 55 = 392

Como:

Checksum recebido  = 386
Checksum calculado = 392

os valores serão diferentes.

Nesse caso, a mensagem será rejeitada e o sistema responderá:

<NACK|CHECKSUM_INVALIDO>

Observe que o parser continua funcionando normalmente. O que impede a execução do comando é justamente a verificação de integridade.

Por que Utilizar um Checksum Simples?

O algoritmo apresentado neste artigo foi escolhido por três motivos principais:

  • É extremamente fácil de implementar.
  • Possui baixo custo computacional.
  • Permite compreender claramente o conceito de verificação de integridade.

Embora existam algoritmos muito mais robustos, como CRC-16 e CRC-32, entender primeiro um checksum baseado na soma dos códigos ASCII facilita a aprendizagem e cria uma base sólida para estudar técnicas mais avançadas.

Nos próximos blocos implementaremos esse algoritmo em C++ utilizando a IDE do Arduino e o integraremos ao protocolo de aplicação desenvolvido ao longo desta série.

Implementando a Função de Checksum

A primeira etapa é criar uma função responsável exclusivamente por calcular o checksum de uma mensagem.

Essa separação é importante porque mantém o código organizado: o parser recebe e interpreta a mensagem, enquanto a função de checksum fica responsável apenas pela verificação de integridade.

Função calculaChecksum()

Utilizaremos a seguinte função:

unsigned int calculaChecksum(const char* texto) {

  unsigned int soma = 0;

  while (*texto) {
    soma += (unsigned char)*texto;
    texto++;
  }

  return soma;
}

Como a função funciona?

A função recebe: 

const char* texto

Esse parâmetro representa o endereço inicial da string que será analisada.

Observação:

char* texto significa que texto é um ponteiro para char.

Em vez de receber uma cópia da string inteira, a função recebe o endereço de memória onde começa a sequência de caracteres.

A variável:

unsigned int soma = 0;

 armazena o resultado da soma na variável unsigned int.

Depois, utilizamos:

while (*texto)

para percorrer a string caractere por caractere. A leitura termina quando encontramos o terminador nulo: '\0'

Observação:

O * utilizado antes de um ponteiro significa acessar o conteúdo do endereço para o qual ele aponta e não o endereço de memória pelo qual é apontado.

Em cada posição:

soma += (unsigned char)*texto;

o valor numérico do caractere é adicionado à soma.

Observação:

A variável char ocupa 1 byte de memória. No Arduino Uno, o char é normalmente signed, e portanto assume valores de -128 a 127.

A conversão de tipo (unsigned char) faz com que o valor obtido em *texto seja tratado como um unsigned char antes da soma, utilizando valores de 0 a 255, sem sinal negativo. Isso evita que um char com sinal seja interpretado como um valor negativo durante o cálculo do checksum.

Na memória, caracteres são representados por valores numéricos. A forma como esse valor é interpretado depende do tipo e da operação realizada pelo programa:

    • Se você o usa em uma soma, ele age como número, de acordo com a tabela ASCII.
    • Se você o usa em um print, como por exemplo Serial.print(*texto), ele age como caractere. 

Finalmente:

texto++;

faz o ponteiro avançar para o próximo caractere. Isso se repete até encontrarmos o valor nulo (\0), resultando a soma de todos os caracteres.

Integrando o Checksum ao Protocolo

Agora que já temos uma função capaz de calcular o checksum, precisamos incorporá-la ao protocolo desenvolvido nos artigos anteriores.

A estrutura da mensagem, que anteriormente era:

<COMANDO|PARAMETRO>

passará a ser:

<COMANDO|PARAMETRO|CHECKSUM>

Por exemplo:

<LED|1|386>

Nesse caso:

  • LED → comando
  • 1 → parâmetro
  • 386 → checksum da sequência LED|1

Fluxo de Validação

Quando o Arduino receber uma mensagem, o processamento seguirá esta sequência:

O comando somente será executado depois que o checksum for validado.

Isso é importante porque uma mensagem com estrutura correta não significa necessariamente que seu conteúdo está correto.

Exemplo de Validação

Suponha que o Arduino receba:

<LED|1|386>

O parser separará:

COMANDO = LED
PARAMETRO = 1
CHECKSUM = 386

Em seguida, o Arduino calcula novamente:

LED|1

e obtém: 386 

Como os valores são iguais:

Checksum recebido = 386
Checksum calculado = 386

a mensagem é considerada íntegra e o comando pode ser executado.

A resposta poderá ser:

<ACK|LED_ON>

E quando houver erro?

Imagine que a mensagem recebida seja:

Nesse caso:

Checksum recebido ≠ Checksum calculado

A mensagem será rejeitada:

<NACK|CHECKSUM_INVALIDO>

O LED não será acionado.

Essa é a principal função do checksum: detectar alterações no conteúdo antes que a mensagem seja processada.

Uma decisão importante do nosso protocolo

Neste artigo, o checksum será calculado sobre: COMANDO|PARAMETRO

e não incluirá: < > e nem o próprio campo do checksum.

Assim, para:

<LED|1|386>

o cálculo será feito somente sobre:

LED|1

Essa regra precisa ser exatamente igual no transmissor e no receptor. Um protocolo só funciona corretamente quando ambos os lados seguem as mesmas regras.

No próximo bloco, podemos então partir para o código completo, incorporando recepção, parsing, checksum, ACK/NACK, timeout e os comandos LED, MOTOR e BUZZER em um único exemplo.

Código — Protocolo com Checksum, ACK/NACK e Timeout

Agora chegamos ao núcleo do quinto artigo da série Comunicação Serial. Abaixo está um código único, completo e testável, mantendo o protocolo dos artigos anteriores e incorporando o checksum, ACK, NACK, Timeout, LED, BUZZER e o comando MOTOR como simulação.

#include <string.h>
#include <stdlib.h>

// =======================================================
// CONFIGURAÇÕES
// =======================================================

const unsigned long TIMEOUT = 3000;
const byte TAM_BUFFER = 50;
const bool DEBUG = true;

// =======================================================
// PINOS
// =======================================================

const byte LED = 13;
const byte BUZZER = 8;

// =======================================================
// BUFFER E CONTROLE DO PARSER
// =======================================================

char buffer[TAM_BUFFER];
char mensagem[TAM_BUFFER + 7];

byte indice = 0;
bool recebendo = false;
unsigned long tempoInicial = 0;

// =======================================================
// CALCULA CHECKSUM
// Soma os valores ASCII dos caracteres da mensagem
// =======================================================

unsigned int calculaChecksum(const char* texto) {

  unsigned int soma = 0;

  while (*texto) {
    soma += (unsigned char)*texto;
    texto++;
  }

  return soma;
}

// =======================================================
// SEPARADOR VISUAL DO MODO DEBUG
// =======================================================

void separadorDebug() {

  if (DEBUG) {
    Serial.println("--------------------");
  }
}

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

void setup() {

  Serial.begin(9600);

  pinMode(LED, OUTPUT);
  pinMode(BUZZER, OUTPUT);

  Serial.println("Sistema iniciado.");
  Serial.println("Protocolo com Checksum.");
  Serial.println();
  Serial.println("Digite:");
  Serial.println("<LED|1>");
  Serial.println("<LED|0>");
  Serial.println("<BUZZER|1>");
  Serial.println("<BUZZER|0>");
  Serial.println("<MOTOR|150>");
  Serial.println("<TEMP|?>");
  Serial.println("--------------------");
}

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

void loop() {

  while (Serial.available() > 0) {

    char c = Serial.read();

    // ---------------------------------------------------
    // Ignora caracteres antes do início da mensagem
    // ---------------------------------------------------

    if (!recebendo && c != '<') {
      continue;
    }

    // ---------------------------------------------------
    // INÍCIO DA MENSAGEM
    // ---------------------------------------------------

    if (c == '<') {

      recebendo = true;
      indice = 0;
      tempoInicial = millis();

      continue;
    }

    // ---------------------------------------------------
    // RECEBIMENTO DA MENSAGEM
    // ---------------------------------------------------

    if (recebendo) {

      // -------------------------------------------------
      // FIM DA MENSAGEM
      // -------------------------------------------------

      if (c == '>') {

        buffer[indice] = '\0';

        recebendo = false;

        processaMensagem();

        indice = 0;
      }

      // -------------------------------------------------
      // ARMAZENAMENTO
      // -------------------------------------------------

      else {

        if (indice < TAM_BUFFER - 1) {

          buffer[indice++] = c;
        }

        else {

          recebendo = false;
          indice = 0;

          Serial.println("<NACK|OVERFLOW>");
          separadorDebug();
        }
      }
    }
  }

  // =====================================================
  // TIMEOUT
  // =====================================================

  if (recebendo && millis() - tempoInicial > TIMEOUT) {

    recebendo = false;
    indice = 0;

    Serial.println("<NACK|TIMEOUT>");
    separadorDebug();
  }
}

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

void processaMensagem() {

  // ---------------------------------------------------
  // 1. CALCULA O CHECKSUM DA MENSAGEM RECEBIDA
  // ---------------------------------------------------

  unsigned int checksumInicial = calculaChecksum(buffer);

  if (DEBUG) {

    Serial.print("Mensagem recebida: ");
    Serial.println(buffer);

    Serial.print("Checksum calculado: ");
    Serial.println(checksumInicial);
  }

  // ---------------------------------------------------
  // 2. MONTA INTERNAMENTE A MENSAGEM COM CHECKSUM
  // ---------------------------------------------------

  strcpy(mensagem, buffer);
  strcat(mensagem, "|");

  char checksumTexto[6];

  itoa(checksumInicial, checksumTexto, 10);

  strcat(mensagem, checksumTexto);

  if (DEBUG) {

    Serial.print("Mensagem com checksum: <");
    Serial.print(mensagem);
    Serial.println(">");
  }

  // ---------------------------------------------------
  // 3. LOCALIZA O SEPARADOR DO CHECKSUM
  // ---------------------------------------------------

  char* separadorChecksum = strrchr(mensagem, '|');

  if (separadorChecksum == NULL) {

    Serial.println("<NACK|FORMATO_INVALIDO>");
    separadorDebug();
    return;
  }

  // ---------------------------------------------------
  // 4. SEPARA O CHECKSUM DO CONTEÚDO
  // ---------------------------------------------------

  *separadorChecksum = '\0';

  char* textoChecksum = separadorChecksum + 1;

  // ---------------------------------------------------
  // 5. CONVERTE O CHECKSUM PARA NÚMERO
  // ---------------------------------------------------

  unsigned int checksumRecebido = atoi(textoChecksum);

  // ---------------------------------------------------
  // 6. CALCULA NOVAMENTE O CHECKSUM
  // ---------------------------------------------------

  unsigned int checksumCalculado = calculaChecksum(mensagem);

  if (DEBUG) {

    Serial.print("Checksum recebido: ");
    Serial.println(checksumRecebido);

    Serial.print("Checksum recalculado: ");
    Serial.println(checksumCalculado);
  }

  // ---------------------------------------------------
  // 7. COMPARA OS CHECKSUMS
  // ---------------------------------------------------

  if (checksumRecebido != checksumCalculado) {

    Serial.println("<NACK|CHECKSUM_INVALIDO>");
    separadorDebug();
    return;
  }

  if (DEBUG) {
    Serial.println("Checksum validado.");
  }

  // ---------------------------------------------------
  // 8. SEPARA COMANDO E PARÂMETRO
  // ---------------------------------------------------

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

  if (comando == NULL || parametro == NULL || campoExtra != NULL) {

    Serial.println("<NACK|FORMATO_INVALIDO>");
    separadorDebug();
    return;
  }

  // ===================================================
  // COMANDO LED
  // ===================================================

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

    if (strcmp(parametro, "1") == 0) {

      digitalWrite(LED, HIGH);

      Serial.println("<ACK|LED_ON>");
      separadorDebug();
    }

    else if (strcmp(parametro, "0") == 0) {

      digitalWrite(LED, LOW);

      Serial.println("<ACK|LED_OFF>");
      separadorDebug();
    }

    else {

      Serial.println("<NACK|PARAMETRO_LED>");
      separadorDebug();
    }
  }

  // ===================================================
  // COMANDO BUZZER
  // ===================================================

  else if (strcmp(comando, "BUZZER") == 0) {

    if (strcmp(parametro, "1") == 0) {

      tone(BUZZER, 1000);

      Serial.println("<ACK|BUZZER_ON>");
      separadorDebug();
    }

    else if (strcmp(parametro, "0") == 0) {

      noTone(BUZZER);

      Serial.println("<ACK|BUZZER_OFF>");
      separadorDebug();
    }

    else {

      Serial.println("<NACK|PARAMETRO_BUZZER>");
      separadorDebug();
    }
  }

  // ===================================================
  // COMANDO MOTOR
  // Simulado
  // ===================================================

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

    int valor = atoi(parametro);

    Serial.print("<ACK|MOTOR=");
    Serial.print(valor);
    Serial.println(">");

    separadorDebug();
  }

  // ===================================================
  // COMANDO TEMPERATURA
  // Simulado
  // ===================================================

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

    if (strcmp(parametro, "?") != 0) {

      Serial.println("<NACK|PARAMETRO_TEMP>");
      separadorDebug();
      return;
    }

    Serial.println("<ACK|TEMP=25>");
    separadorDebug();
  }

  // ===================================================
  // COMANDO DESCONHECIDO
  // ===================================================

  else {

    Serial.println("<NACK|COMANDO_INVALIDO>");
    separadorDebug();
  }
}

Exemplo de resultados

 

Como o Código Funciona

O código reúne os principais conceitos desenvolvidos ao longo desta série: leitura não bloqueante, buffer, parser, checksum, ACK, NACK e timeout.

1. Recepção da mensagem

O Arduino verifica continuamente se existem dados disponíveis:

while (Serial.available() > 0)

Cada caractere é lido com:

char c = Serial.read();

O parser ignora qualquer caractere que apareça antes do delimitador <.

Quando encontra <, inicia uma nova mensagem.

2. Armazenamento no buffer

Os caracteres recebidos são armazenados em:

char buffer[TAM_BUFFER];

Até encontrar o delimitador final: >

O caractere '\0' então marca o final da string.

O código também controla o tamanho do buffer para evitar overflow.

3. Cálculo do checksum

Depois que a mensagem é recebida, a função:

calculaChecksum(buffer);

soma os valores dos caracteres.

Por exemplo:

LED|1

resulta em:

386

O código então monta internamente:

LED|1|386

4. Validação

O checksum é separado da mensagem e calculado novamente.

Se:

checksum recebido = checksum recalculado

a mensagem é considerada válida.

Caso contrário:

<NACK|CHECKSUM_INVALIDO>

5. Processamento do comando

Somente depois da validação o código utiliza strtok() e strcmp() para separar e interpretar:

comando → LED
parâmetro → 1

O Arduino então executa a ação correspondente.

6. Resposta

Se tudo estiver correto:

<ACK|LED_ON>

Se ocorrer algum problema:

<NACK|PARAMETRO_LED>

ou:

<NACK|COMANDO_INVALIDO>

ou ainda:

<NACK|TIMEOUT>

7. Controle de Timeout

Se uma mensagem começar com < mas o > não chegar dentro do tempo definido:

const unsigned long TIMEOUT = 3000;

o Arduino cancela a recepção e responde:

<NACK|TIMEOUT>

Assim, o parser não fica indefinidamente aguardando uma mensagem incompleta.

8. Modo de depuração (DEBUG)

O código possui a constante:

const bool DEBUG = true;

Quando DEBUG está true, o Arduino exibe informações adicionais no Monitor Serial durante o processamento, como:

Mensagem recebida: LED|1
Checksum calculado: 386
Mensagem com checksum: <LED|1|386>
Checksum recebido: 386
Checksum recalculado: 386
Checksum validado.
<ACK|LED_ON>
--------------------

Essas informações não fazem parte do protocolo. Elas servem apenas para acompanhar e depurar o funcionamento interno do programa durante o desenvolvimento e os testes.

Quando o projeto estiver pronto para uma utilização real, podemos desativar o modo de depuração:

const bool DEBUG = false;

Nesse caso, serão mantidas apenas as respostas do protocolo, como:

<ACK|LED_ON>

ou:

<NACK|CHECKSUM_INVALIDO>

Resumindo o funcionamento completo

O código segue uma regra simples:

Receber
   ↓
Armazenar
   ↓
Calcular checksum
   ↓
Montar mensagem
   ↓
Recalcular e validar
   ↓
DEBUG → mostrar informações
   ↓
Interpretar comando
   ↓
Executar
   ↓
ACK ou NACK

Essa sequência é o núcleo do protocolo desenvolvido neste artigo e estabelece uma base importante para sistemas de comunicação serial mais confiáveis.

A distinção entre dados do protocolo e informações de debug é importante em sistemas embarcados, porque permite desenvolver e testar o sistema com bastante informação sem poluir a comunicação definitiva.

Depois que o protocolo estiver funcionando corretamente, podemos alterar para:

const bool DEBUG = false;

Com isso, as mensagens de depuração deixam de ser exibidas e o Monitor Serial apresenta somente as respostas efetivas do protocolo, deixando a comunicação mais limpa e fácil de interpretar.

O que há de novo neste artigo?

Além do conceito de checksum e integridade de dados, este artigo introduziu alguns recursos importantes para a construção de protocolos mais robustos.

1. strrchr() – Encontrando o último separador

A função strrchr() pertence à biblioteca <string.h> e localiza a última ocorrência de um determinado caractere dentro de uma string.

No nosso código:

char* separadorChecksum = strrchr(mensagem, '|');

Considerando:

LED|1|386

existem dois caracteres |.

A função strrchr() encontra justamente o último:

Isso é útil porque queremos separar o conteúdo da mensagem:

LED|1

do checksum: 386

Depois fazemos:

*separadorChecksum = '\0';

Obs.: Relembrando que quando o * é utilizado antes de um ponteiro significa acessar o conteúdo do endereço para o qual ele aponta e não o endereço de memória pelo qual é apontado.

e a memória passa a representar:

 LED|1\0386

Assim, mensagem passa a representar apenas LED|1, enquanto:

char* textoChecksum = separadorChecksum + 1;

aponta para 386.

2. atoi() – Convertendo o checksum de texto para número

Como o checksum recebido é armazenado como texto: "386"

precisamos convertê-lo para um valor numérico antes de compará-lo:

unsigned int checksumRecebido = atoi(textoChecksum);

A função atoi() significa ASCII to Integer e converte uma string no estilo C (char[] ou char*) para um valor inteiro.

Assim: 

"386" → 386

Isso permite fazer:

if (checksumRecebido != checksumCalculado)

3. unsigned int – Armazenando o resultado do checksum

O checksum é armazenado como:

 

unsigned int

porque o resultado da soma pode ultrapassar o intervalo de um byte.

No nosso algoritmo, estamos somando os valores numéricos dos caracteres da mensagem. Por isso, precisamos de um tipo capaz de armazenar um resultado maior que 255.

4. Modo DEBUG

Outra prática introduzida neste artigo foi separar as mensagens de depuração das mensagens do protocolo:

const bool DEBUG = true;

Quando DEBUG está habilitado, o programa mostra informações como:

Checksum calculado: 386
Checksum recebido: 386
Checksum recalculado: 386
Checksum validado.

Essas informações ajudam durante o desenvolvimento, mas não fazem parte do protocolo.

Quando o programa estiver pronto para operação:

const bool DEBUG = false;

e o Monitor Serial passa a apresentar somente as respostas do protocolo.

Conclusão

Neste artigo, demos mais um passo na evolução da comunicação serial no Arduino: passamos da simples estruturação e interpretação das mensagens para a verificação de sua integridade.

Ao implementar o checksum, aprendemos que receber uma mensagem corretamente formatada não é necessariamente suficiente. Também precisamos verificar se o conteúdo recebido corresponde ao conteúdo esperado.

Ao longo da série, nossa evolução foi gradual:

Comunicação serial → char[] → protocolo → ACK/NACK → Timeout → integridade de dados.

No presente artigo, acrescentamos ao protocolo um mecanismo de verificação baseado na soma dos valores dos caracteres da mensagem. Embora simples, esse algoritmo permite compreender um princípio fundamental dos sistemas embarcados:

não basta receber uma mensagem; é necessário verificar se ela pode ser considerada confiável antes de processá-la.

Também aprofundamos o uso de char*, strrchr(), atoi(), unsigned int, controle de buffer e modo DEBUG, integrando esses conceitos em um único sistema de comunicação.

É importante, entretanto, compreender a limitação da implementação apresentada: o checksum utilizado é didático e simples, e a versão desenvolvida neste artigo calcula o valor a partir da mensagem recebida. Em uma comunicação real entre dois dispositivos, o transmissor deve calcular e enviar o checksum junto com a mensagem, enquanto o receptor recalcula o valor e faz a comparação.

A partir desse ponto, o protocolo que estamos construindo deixa de ser apenas um mecanismo para enviar comandos ao Arduino e passa a incorporar conceitos fundamentais de confiabilidade, validação e integridade de dados.

Esse conhecimento prepara o caminho para os próximos níveis da série, como CRC, máquinas de estados, retransmissão de mensagens e comunicação entre dispositivos, aproximando cada vez mais o desenvolvimento no Arduino das técnicas utilizadas em sistemas embarcados reais.

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

Comentários

×

Infomações do site / SEO








×

Adicionar Marcadores