iiOTESP32 Brasil
Modo de contingência: páginas públicas e downloads seguem disponíveis; login, comunidade e dados do servidor podem ficar indisponíveis.
Voltar para catálogo de tutoriais
Ilustração: placa ESP32 ligada por fios coloridos a um módulo conversor RS-485 e, por um par trançado, a um controlador industrial em uma planta
Imagem ilustrativa gerada por IA
AvançadoModbusLeitura: 15 min

Modbus RTU via RS-485 no ESP32

Ler registradores Modbus RTU no ESP32 sem biblioteca: controle de DE/RE, CRC16, o silêncio de 3,5 caracteres entre quadros e a terminação de 120 ohms.

Tecnologias:#ESP32#Modbus RTU#RS-485#MAX485#UART#CRC16

Requisitos de hardware:

  • Placa ESP32 ou ESP32-S3
  • Transceiver RS-485 (MAX3485 ou SP3485; MAX485 exige divisor no RO)
  • Dois resistores de 120 ohms para terminação
  • Par trançado, de preferência com blindagem
  • Um escravo Modbus RTU: multimedidor, inversor ou CLP

Por que Modbus RTU ainda importa

Modbus RTU é de 1979 e continua sendo o que sai de fábrica em multimedidor de energia, inversor de frequência, transmissor de pressão e controlador de temperatura. Não porque seja elegante, mas porque é simples o suficiente para caber num microcontrolador barato e estável o bastante para rodar vinte anos num painel.

Ler esses equipamentos com um ESP32 custa poucos reais em componentes e resolve o problema que motiva metade dos projetos de IIoT: o dado existe, está no painel, e não sobe para lugar nenhum.

Este tutorial implementa o protocolo direto, sem biblioteca. Não por purismo — é que as bibliotecas escondem exatamente os três detalhes que fazem a comunicação falhar, e você vai precisar entender os três quando o barramento não responder.

RS-485 não é uma porta serial comum

O primeiro erro conceitual é tratar RS-485 como se fosse UART com outro conector.

Numa serial comum, cada lado tem seu fio de transmissão. No RS-485 existe um par de fios só, compartilhado por todos os dispositivos, e a informação está na diferença de tensão entre os dois — por isso ele atravessa centenas de metros num ambiente cheio de motor e inversor, onde a serial comum não anda dez.

A consequência é que só um dispositivo pode falar por vez. Não há colisão detectável, não há arbitragem. O mestre pergunta, o escravo responde, e nesse intervalo mais ninguém abre a boca. Todo o resto deste artigo decorre disso.

O transceiver e o pino que decide tudo

O MAX485 converte a UART do ESP32 no par diferencial. Ele tem quatro pinos de controle, e dois deles fazem a mesma coisa invertida:

O DI recebe o que o ESP32 transmite, e o RO entrega o que chega do barramento. O DE habilita o transmissor em nível alto, e o RE habilita o receptor em nível baixo.

Como você nunca quer transmitir e receber ao mesmo tempo, o padrão é amarrar DE e RE no mesmo GPIO. Alto significa falar, baixo significa ouvir. Um fio a menos e nenhuma chance de deixar os dois ligados por engano.

code.cpp
cpp
1ESP32 GPIO17 (TX) -> DI
2ESP32 GPIO16 (RX) <- RO
3ESP32 GPIO4 -> DE e RE juntos
43V3 -> VCC
5GND -> GND

Use um transceiver de 3,3 V, como o MAX3485 ou o SP3485. O MAX485 clássico é de 5 V: ele até funciona alimentado em 3,3 V em muitas placas, mas o RO pode entregar nível acima do que o ESP32 tolera. Se for usar o de 5 V, coloque um divisor no RO.

O erro número um: soltar o DE cedo demais

Este é o bug que mais aparece, e o mais difícil de enxergar.

A tentação é escrever no serial e desligar o DE na linha seguinte. Só que write não espera o dado sair — ele apenas enfileira no buffer da UART. Se você baixar o DE imediatamente, o transmissor desliga no meio do quadro e o escravo recebe uma mensagem truncada, sem nunca responder.

O correto é esperar a UART esvaziar antes de soltar:

code.cpp
cpp
1void modbusEnviar(const uint8_t *quadro, size_t tamanho) {
2 digitalWrite(PINO_DE_RE, HIGH);
3 delayMicroseconds(50);
4 Serial2.write(quadro, tamanho);
5 Serial2.flush();
6 delayMicroseconds(50);
7 digitalWrite(PINO_DE_RE, LOW);
8}

O flush é a linha que importa: ele bloqueia até o último bit sair do registrador de deslocamento. As duas esperas de 50 microssegundos dão folga ao transceiver para comutar — alguns levam alguns microssegundos entre habilitar e a saída estabilizar.

Se o seu escravo nunca responde e você já conferiu fiação e endereço, é quase sempre aqui.

O silêncio de 3,5 caracteres

Modbus RTU não tem marcador de início nem de fim de quadro. O que delimita a mensagem é tempo de silêncio: uma pausa equivalente a 3,5 caracteres marca o fim, e uma pausa maior que 1,5 caractere no meio invalida o que veio.

Isso significa que o tempo depende do baud rate. Em 8N1 cada caractere leva 10 bits, então:

code.cpp
cpp
19600 bps 1 caractere = 1,042 ms silencio = 3,65 ms (calculado)
219200 bps 1 caractere = 0,521 ms silencio = 1,82 ms (calculado)
338400 bps 1 caractere = 0,260 ms silencio = 1,75 ms (fixo por norma)
4115200 bps 1 caractere = 0,087 ms silencio = 1,75 ms (fixo por norma)

Repare na virada: acima de 19200 a norma abandona o cálculo e fixa 1,750 ms. O motivo é que o valor calculado ficaria menor que o tempo de processamento típico de um escravo — em 115200 daria 0,3 ms, curto demais para muitos equipamentos reagirem. Usar o valor calculado nessas velocidades produz comunicação instável com escravos lentos.

Na prática, respeitar isso significa esperar o silêncio antes de enviar a próxima pergunta. Encadear requisições sem pausa é a segunda causa mais comum de resposta corrompida.

CRC16, e a pegadinha da ordem dos bytes

O Modbus usa CRC16 com polinômio 0xA001, calculado sobre todos os bytes do quadro. São dez linhas:

code.cpp
cpp
1uint16_t modbusCRC(const uint8_t *dados, size_t tamanho) {
2 uint16_t crc = 0xFFFF;
3 for (size_t i = 0; i < tamanho; i++) {
4 crc ^= dados[i];
5 for (uint8_t bit = 0; bit < 8; bit++) {
6 if (crc & 0x0001) {
7 crc = (crc >> 1) ^ 0xA001;
8 } else {
9 crc >>= 1;
10 }
11 }
12 }
13 return crc;
14}

A pegadinha: o CRC vai no quadro com o byte menos significativo primeiro, ao contrário de todo o resto do protocolo, que é big-endian. Endereço e quantidade de registradores vão com o byte alto na frente; o CRC vai invertido. Errar isso produz um quadro que parece perfeito e nunca é aceito.

Montando a leitura de registradores

A função 03 lê registradores de retenção, que é o que 90% dos equipamentos usam. O quadro tem oito bytes:

code.cpp
cpp
1uint8_t construirLeitura(uint8_t *quadro, uint8_t escravo,
2 uint16_t inicio, uint16_t quantidade) {
3 quadro[0] = escravo;
4 quadro[1] = 0x03;
5 quadro[2] = inicio >> 8;
6 quadro[3] = inicio & 0xFF;
7 quadro[4] = quantidade >> 8;
8 quadro[5] = quantidade & 0xFF;
9 uint16_t crc = modbusCRC(quadro, 6);
10 quadro[6] = crc & 0xFF;
11 quadro[7] = crc >> 8;
12 return 8;
13}

Repare no CRC nas duas últimas linhas: parte baixa primeiro, parte alta depois. É a inversão da seção anterior.

O endereço do manual não é o endereço do protocolo

Terceira armadilha, e a que mais consome tempo de quem está começando.

O manual do equipamento costuma dizer que a tensão da fase A está no registrador 40001. No protocolo, esse registrador é o de número 0. A numeração 4xxxx é uma convenção de documentação, não o que vai no quadro.

A regra: subtraia 40001 do número do manual. O 40001 vira 0, o 40011 vira 10, o 40100 vira 99.

Alguns fabricantes já documentam em base zero, e uns poucos misturam as duas. Se a leitura vem com valor plausível mas na grandeza errada, tente deslocar um registrador para cada lado antes de suspeitar do código.

Lendo a resposta com tempo limite

A resposta chega com o mesmo endereço, a mesma função, a contagem de bytes e os dados:

code.cpp
cpp
1bool modbusLer(uint8_t escravo, uint16_t inicio,
2 uint16_t quantidade, uint16_t *saida) {
3 uint8_t quadro[8];
4 uint8_t tamanho = construirLeitura(quadro, escravo, inicio, quantidade);
5 while (Serial2.available()) Serial2.read();
6 modbusEnviar(quadro, tamanho);
7 uint8_t esperado = 5 + quantidade * 2;
8 uint8_t resposta[64];
9 uint8_t recebidos = 0;
10 unsigned long limite = millis() + 300;
11 while (millis() < limite && recebidos < esperado) {
12 if (Serial2.available()) {
13 resposta[recebidos++] = Serial2.read();
14 }
15 }
16 if (recebidos < esperado) return false;
17 if (resposta[0] != escravo) return false;
18 if (resposta[1] & 0x80) return false;
19 uint16_t crc = modbusCRC(resposta, recebidos - 2);
20 if ((crc & 0xFF) != resposta[recebidos - 2]) return false;
21 if ((crc >> 8) != resposta[recebidos - 1]) return false;
22 for (uint16_t i = 0; i < quantidade; i++) {
23 saida[i] = (resposta[3 + i * 2] << 8) | resposta[4 + i * 2];
24 }
25 return true;
26}

Três verificações que muita implementação pula. Limpar o buffer antes de enviar descarta lixo de leituras anteriores. O teste de bit 0x80 na função detecta resposta de exceção — o escravo dizendo que o endereço não existe ou que o valor é inválido. E conferir o CRC da resposta evita aceitar dado corrompido por ruído, que num painel elétrico não é hipótese remota.

Terminação: os 120 ohms não são opcionais

Num barramento longo, o sinal que chega na ponta reflete de volta e interfere com o próximo bit. O resultado é comunicação que funciona na bancada e falha no painel, ou que funciona com um equipamento e para quando você liga o segundo.

A correção é um resistor de 120 ohms entre A e B nas duas extremidades do barramento — só nas pontas, nunca nos dispositivos do meio. Muitos módulos trazem o resistor soldado ou um jumper; se o seu ESP32 está no meio da linha, remova.

Vale também o polarização de repouso: quando ninguém transmite, a linha fica flutuando e o receptor pode interpretar ruído como dado. Resistores de 680 ohms puxando A para o positivo e B para o terra resolvem. Alguns transceivers já trazem isso internamente.

E o par tem que ser trançado. Dois fios paralelos anulam a vantagem do sinal diferencial, que é justamente captar a mesma interferência nos dois condutores.

Tutorial: da bancada à primeira leitura

Passo 1: ligar o transceiver

Com tudo desenergizado, ligue o módulo RS-485 conforme a tabela da seção do transceiver. Confirme que DE e RE estão amarrados no mesmo GPIO — na maioria dos módulos isso é um jumper ou uma trilha já pronta.

Alimente o transceiver em 3,3 V, não em 5 V, salvo se você colocou divisor no RO.

Passo 2: ligar no escravo

A e B do módulo vão para A e B do equipamento, respeitando a polaridade. Fabricantes divergem na nomenclatura: alguns chamam de D+ e D-, outros de A e B, e há quem inverta a convenção.

Se não responder, troque A e B antes de mexer no código. É gratuito e resolve boa parte dos casos.

Ligue também o terra de referência entre os dois lados. RS-485 é diferencial, mas os dois lados precisam estar dentro da mesma faixa de tensão de modo comum.

Passo 3: descobrir os parâmetros do escravo

Você precisa de quatro informações, todas no manual ou no display do equipamento: endereço do escravo (1 a 247), baud rate, paridade e o registrador que você quer ler.

O padrão mais comum é 9600 8N1, mas 19200 e paridade par aparecem bastante. Comece pelo que o manual diz; se não houver manual, 9600 8N1 no endereço 1 é o chute com maior chance.

Passo 4: configurar o platformio.ini

Crie o arquivo na raiz do projeto:

code.cpp
cpp
1[env:esp32-modbus]
2platform = espressif32
3board = esp32dev
4framework = arduino
5monitor_speed = 115200

Nenhuma biblioteca externa: o protocolo está implementado no próprio arquivo.

Passo 5: ajustar os parâmetros no código

No topo do firmware, ajuste o endereço, o baud e o registrador para os valores que você levantou no passo 3. Lembre de subtrair 40001 do número do manual.

Comece lendo um registrador só. Quantidade grande numa primeira tentativa dificulta separar erro de parâmetro de erro de comunicação.

Passo 6: gravar e observar

Compile, grave e abra o monitor:

code.cpp
cpp
1pio run -t upload
2pio device monitor -b 115200

Uma leitura bem-sucedida imprime o valor bruto. Compare com o que o display do equipamento mostra: se o número bater, ou for um múltiplo exato de 10 ou 100, você acertou o registrador — a diferença de escala está documentada no manual.

Se o valor não tiver relação nenhuma com o display, o registrador está errado. Volte ao passo 3.

Passo 7: interpretar a escala

Modbus transporta inteiros de 16 bits. Um multimedidor que mostra 127,3 V costuma devolver 1273, com o manual informando o fator.

Valores acima de 65535, como energia acumulada, ocupam dois registradores. Leia os dois e combine: o de menor endereço costuma ser a parte alta, mas há fabricante que inverte. Se o número saltar de forma absurda, tente a ordem contrária.

Erros comuns e como resolver

O escravo nunca responde. Na ordem: inverta A e B; confirme que o flush está antes de baixar o DE; confira endereço e baud; verifique o terra de referência.

Responde às vezes, e às vezes não. Falta terminação, ou você está encadeando requisições sem respeitar o silêncio de 3,5 caracteres.

CRC sempre inválido. Quase certamente a ordem dos bytes do CRC está trocada — parte baixa vai primeiro.

Recebo resposta com o bit 0x80 na função. É exceção do escravo, não erro seu: o byte seguinte diz o motivo. O código 02 significa endereço de registrador inexistente, o mais frequente.

O valor lido não bate com o display. Registrador errado ou escala. Verifique o deslocamento de 40001 e o fator no manual.

Funciona com um equipamento e para com dois. Terminação nos dois extremos apenas, e endereços diferentes em cada escravo.

Ruído quando o inversor liga. Use par trançado blindado, aterre a malha em um extremo só, e afaste o cabo dos cabos de potência. Se persistir, baixe o baud rate: 9600 é bem mais tolerante que 38400.

Ver mais tutoriais técnicosIIoT ESP32 Brasil — Documentação Prática