Início
/
Software
/
Dicas de Software
/
Checksum e Integridade de Dados na Comunicação Serial do Arduino
Checksum e Integridade de Dados na Comunicação Serial do Arduino
Angelo Luis Ferreira | 17/07/2026
Acessos: 172
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:
- O dispositivo emissor calcula o checksum da mensagem.
- O checksum é enviado junto com os dados.
- O dispositivo receptor calcula novamente o checksum utilizando a mensagem recebida.
- Os dois valores são comparados.
Se ambos forem iguais:
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>
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:
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:
- Percorremos todos os caracteres da mensagem.
- Obtemos o valor ASCII de cada caractere.
- Somamos todos esses valores.
- O resultado será o checksum da mensagem.
Considere a seguinte mensagem:
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:
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:
o parser irá:
- Separar os campos da mensagem.
- Extrair o comando (
LED).
- Extrair o parâmetro (
1).
- Extrair o checksum recebido (
386).
- Calcular novamente o checksum utilizando apenas
LED|1.
- Comparar os dois valores.
Se ambos forem iguais:
a mensagem será considerada válida e poderá ser executada normalmente.
Nesse caso, o Arduino responderá:
Detectando Alterações na Mensagem
Agora imagine que, durante a transmissão, um caractere seja alterado.
Mensagem enviada:
Ao recalcular o checksum, o Arduino obterá: 76 + 69 + 68 + 124 + 55 = 392
Como:
os valores serão diferentes.
Nesse caso, a mensagem será rejeitada e o sistema responderá:
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;
}
A função recebe:
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:
Depois, utilizamos:
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;
Finalmente:
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:
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:
O parser separará:
COMANDO = LED
PARAMETRO = 1
CHECKSUM = 386
Em seguida, o Arduino calcula novamente:
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:
E quando houver erro?
Imagine que a mensagem recebida seja:
Nesse caso:
Checksum recebido ≠ Checksum calculado
A mensagem será rejeitada:
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:
o cálculo será feito somente sobre:
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.
const bool DEBUG = false;
O anúncio abaixo ajuda a manter o Squids Arduino funcionando
Comentários