Um gateway ESP32 Modbus RS485 MQTT permite extrair dados de medidores de energia, inversores de frequência, transmissores de temperatura e outros dispositivos Modbus RTU e colocá-los no Wi-Fi sem CLP nem PC industrial. Neste tutorial você vai ligar um transceptor RS485 a um ESP32, ler registradores de um ou mais escravos com a biblioteca ModbusMaster, escalar os valores brutos e publicá-los como JSON em um broker MQTT.
O Que o Gateway Faz
O Modbus RTU é um protocolo mestre-escravo por polling: nada acontece no barramento até que um mestre peça a um escravo específico um bloco específico de registradores. O MQTT é o oposto, um protocolo publish-subscribe orientado a eventos. O ESP32 fica entre os dois:
- Ele atua como mestre Modbus no barramento RS485, lendo cada dispositivo configurado em um intervalo fixo.
- Ele converte registradores brutos de 16 bits em valores de engenharia (volts, graus, quilowatts-hora) usando um mapa de registradores.
- Ele atua como cliente MQTT, publicando uma mensagem JSON por dispositivo em um topic como
connectmq/demo/mb-4d1e/meter1.
Vários dispositivos podem compartilhar um mesmo par RS485 (cada um com seu endereço de escravo), então um único ESP32 consegue atender um pequeno painel cheio de instrumentos.
Hardware
| Peça | Observações |
|---|---|
| ESP32 DevKit | Usa a UART2 para o RS485 |
| Transceptor RS485 de 3,3 V (MAX3485, SP3485) | O clássico MAX485 é de 5 V; prefira um chip de 3,3 V |
| Dispositivo Modbus RTU | Medidor de energia, transmissor de temperatura/umidade, inversor de frequência... |
| Resistor de 120 Ω | Terminação na extremidade de barramentos longos |
| Cabo de par trançado | Par trançado blindado para qualquer coisa além de um teste de bancada |
Algumas placas de transceptor alternam a direção automaticamente e expõem apenas RX e TX. Elas também funcionam: basta deixar o pino DE/RE sem uso no código abaixo.
Ligação
| Pino do transceptor | Pino do ESP32 | Função |
|---|---|---|
| VCC | 3V3 | Alimentação |
| GND | GND | Terra; ligue também ao terra de sinal do dispositivo, se houver |
| RO | GPIO 16 (RX2) | Dados do barramento para o ESP32 |
| DI | GPIO 17 (TX2) | Dados do ESP32 para o barramento |
| DE e RE (unidos) | GPIO 4 | Nível alto para transmitir, baixo para receber |
| A / B | A / B do dispositivo | O par do barramento |
O erro de ligação mais comum é inverter as linhas A e B. Os fabricantes não concordam sobre qual delas é o "positivo", então, se você só recebe timeouts, inverta-as antes de depurar qualquer outra coisa. Os resistores de terminação ficam nas duas extremidades físicas do barramento, não em cada dispositivo.
Conheça o Seu Mapa de Registradores
Todo dispositivo Modbus documenta seus registradores em uma tabela: endereço, código de função, tipo de dado e escala. Leia com atenção, porque ela responde às perguntas que causam a maioria dos bugs de gateway:
- Código de função: holding registers são lidos com a função 03, input registers com a função 04. Pedir com a função errada retorna uma exceção ou nada.
- Offset de endereço: documentação que diz "30001" ou "40001" geralmente significa o registrador de endereço 0 com a função 04 ou 03. Alguns manuais listam endereços a partir de zero, outros a partir de um.
- Escala: muitos dispositivos enviam
235significando 23,5 graus (escala 0,1). - Tipo de dado: 16 bits sem sinal, 16 bits com sinal ou valores de 32 bits distribuídos em dois registradores, às vezes com a ordem das palavras invertida.
- Configuração serial: baud rate, paridade e stop bits precisam bater exatamente. 9600 8N1 é comum, mas 9600 8E1 é o padrão Modbus definido na norma.
O Sketch ESP32 Modbus RS485 MQTT
Instale a ModbusMaster, de Doc Walker, e a PubSubClient, de Nick O'Leary, pelo Library Manager. O mapa de registradores no início é a única parte que você precisa alterar para os seus dispositivos. O exemplo lê dois dispositivos: um transmissor de temperatura/umidade no endereço 1 e um medidor de energia no endereço 2.
#include <WiFi.h>
#include <PubSubClient.h>
#include <ModbusMaster.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* CLIENT_ID = "mb-4d1e-gateway";
const char* TOPIC_PREFIX = "connectmq/demo/mb-4d1e";
const int RS485_RX = 16;
const int RS485_TX = 17;
const int RS485_DE = 4;
const uint32_t BAUD = 9600;
const uint32_t POLL_INTERVAL_MS = 10000;
struct Point {
const char* device; // vira o nível do topic
uint8_t slave; // endereço Modbus
uint8_t fc; // 3 = holding, 4 = input
uint16_t reg; // endereço do registrador (a partir de zero)
uint8_t words; // 1 = 16 bits, 2 = 32 bits (palavra alta primeiro)
bool isSigned;
float scale;
const char* name; // chave JSON
};
// Edite esta tabela conforme a documentação dos seus dispositivos
Point points[] = {
{"climate1", 1, 4, 0x0001, 1, true, 0.1, "temperature_c"},
{"climate1", 1, 4, 0x0002, 1, false, 0.1, "humidity_pct"},
{"meter1", 2, 3, 0x0000, 1, false, 0.1, "voltage_v"},
{"meter1", 2, 3, 0x0001, 1, false, 0.01, "current_a"},
{"meter1", 2, 3, 0x0010, 2, false, 0.01, "energy_kwh"},
};
const size_t NUM_POINTS = sizeof(points) / sizeof(points[0]);
ModbusMaster node;
WiFiClient net;
PubSubClient mqtt(net);
uint32_t lastPoll = 0;
void preTx() { digitalWrite(RS485_DE, HIGH); }
void postTx() { digitalWrite(RS485_DE, LOW); }
bool readPoint(const Point& p, float& out) {
node.begin(p.slave, Serial2);
uint8_t rc = (p.fc == 4) ? node.readInputRegisters(p.reg, p.words)
: node.readHoldingRegisters(p.reg, p.words);
if (rc != node.ku8MBSuccess) {
Serial.printf("slave %u reg 0x%04X error 0x%02X\n", p.slave, p.reg, rc);
return false;
}
if (p.words == 2) {
uint32_t raw = ((uint32_t)node.getResponseBuffer(0) << 16) | node.getResponseBuffer(1);
out = p.isSigned ? (int32_t)raw * p.scale : raw * p.scale;
} else {
uint16_t raw = node.getResponseBuffer(0);
out = p.isSigned ? (int16_t)raw * p.scale : raw * p.scale;
}
return true;
}
void connectWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) delay(500);
}
void connectMqtt() {
char statusTopic[96];
snprintf(statusTopic, sizeof(statusTopic), "%s/status", TOPIC_PREFIX);
while (!mqtt.connected()) {
if (mqtt.connect(CLIENT_ID, statusTopic, 1, true, "offline")) {
mqtt.publish(statusTopic, "online", true);
} else {
delay(5000);
}
}
}
void pollAndPublish() {
// Agrupa pontos consecutivos do mesmo dispositivo em uma única mensagem JSON
size_t i = 0;
while (i < NUM_POINTS) {
const char* dev = points[i].device;
String json = "{";
int ok = 0, failed = 0;
while (i < NUM_POINTS && strcmp(points[i].device, dev) == 0) {
float v;
if (readPoint(points[i], v)) {
if (ok > 0) json += ",";
json += "\"" + String(points[i].name) + "\":" + String(v, 2);
ok++;
} else {
failed++;
}
delay(20); // intervalo entre frames, ajuda dispositivos lentos
i++;
}
if (ok > 0) json += ",";
json += "\"errors\":" + String(failed) + "}";
char topic[96];
snprintf(topic, sizeof(topic), "%s/%s", TOPIC_PREFIX, dev);
mqtt.publish(topic, json.c_str(), true);
Serial.printf("%s %s\n", topic, json.c_str());
}
}
void setup() {
Serial.begin(115200);
pinMode(RS485_DE, OUTPUT);
digitalWrite(RS485_DE, LOW);
Serial2.begin(BAUD, SERIAL_8N1, RS485_RX, RS485_TX);
node.preTransmission(preTx);
node.postTransmission(postTx);
connectWifi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setBufferSize(512);
connectMqtt();
}
void loop() {
if (WiFi.status() != WL_CONNECTED) connectWifi();
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
if (millis() - lastPoll >= POLL_INTERVAL_MS) {
lastPoll = millis();
pollAndPublish();
}
}
Alguns detalhes deste código importam mais do que parecem:
preTransmissionepostTransmissionacionam o pino DE para que o transceptor só fale enquanto o ESP32 transmite. Sem eles, o transceptor nunca libera o barramento e todas as leituras dão timeout.- Cada mensagem publicada traz uma contagem de
errors. Assim, um dashboard pode mostrar "dispositivo presente, mas alguns registradores falhando" em vez de congelar silenciosamente em valores antigos. - As mensagens são retidas, então um dashboard recém-conectado vê imediatamente os últimos valores.
setBufferSize(512)dá espaço para um JSON mais longo quando você adicionar mais pontos.
Confira a saída:
mosquitto_sub -h broker.connectmq.com -p 1883 -i mb-4d1e-viewer -t "connectmq/demo/mb-4d1e/#" -v
Interpretando os Códigos de Erro da ModbusMaster
| Código | Significado | Causa típica |
|---|---|---|
0xE2 |
Tempo de resposta esgotado | Polaridade A/B invertida, baud ou paridade errados, endereço de escravo errado, pino DE sem alternar |
0xE0 |
ID de escravo inválido na resposta | Dois dispositivos com o mesmo endereço, ou ruído |
0xE3 |
CRC inválido | Falta de terminação, cabo longo sem blindagem, diferença de potencial de terra |
0x02 |
Endereço de dado ilegal | O registrador não existe; verifique endereçamento a partir de zero ou de um |
0x01 |
Função ilegal | Usar a função 03 em um registrador que só responde à 04, ou vice-versa |
Checklist de Diagnóstico para ESP32 Modbus RS485 MQTT
- Confirme a configuração serial no menu ou no manual do dispositivo: baud, paridade, stop bits, endereço.
- Teste um dispositivo com um adaptador USB-RS485 e uma ferramenta Modbus no PC antes de envolver o ESP32. Se o PC não consegue ler, o ESP32 também não vai conseguir.
- Inverta A e B se todas as requisições derem timeout.
- Adicione terminação de 120 Ω nas duas extremidades de barramentos com mais de alguns metros e ligue o terra de sinal.
- Mantenha um intervalo de polling realista. Uma dúzia de registradores a 9600 baud leva uma fração perceptível de segundo; não leia mais rápido do que o barramento consegue responder.
- Leia registradores contíguos em uma única requisição sempre que possível. Uma requisição de dez registradores é muito mais rápida que dez requisições de um.
Publicar Menos, Não Mais
Ler a cada dez segundos e publicar tudo é o desenho mais simples, mas medidores mudam devagar e o tráfego para a nuvem se acumula. Duas melhorias cabem facilmente no sketch: publicar um ponto apenas quando ele muda mais do que uma banda morta e enviar um snapshot completo a cada poucos minutos como heartbeat. Esse padrão é explicado em report by exception. Para uma visão do mesmo problema no nível do gateway, incluindo gateways baseados em PC e redes RTU maiores, veja Modbus RTU para MQTT. Os mesmos valores escalados também podem alimentar um gêmeo digital do painel ou da linha, seja em uma planta em Joinville, em uma mineradora ou em uma estação de saneamento.
Se o próprio MQTT é novidade, comece por o que um broker MQTT faz; para montar JSON com uma biblioteca em vez de concatenar strings, o guia de ArduinoJson com MQTT é um bom complemento. O tutorial de IoT com ESP32 cobre as bases de Wi-Fi e reconexão usadas aqui.
Preparando para Produção
Um gateway na parede de uma fábrica ou de uma usina roda por anos, então trate-o como infraestrutura:
- TLS e credenciais. Use a porta 8883 com
WiFiClientSecuree credenciais emitidas para este gateway. Nunca publique dados da planta no servidor MQTT público (gratuito), que serve apenas para testes e protótipos. - Watchdog. Ative o task watchdog para que uma transação Modbus travada ou uma pilha Wi-Fi presa reinicie a placa.
- Isolação. Em painéis com muito ruído elétrico, um transceptor RS485 isolado evita que loops de terra danifiquem o ESP32.
- Somente leitura por padrão. Escrever registradores via MQTT pode ligar motores ou alterar setpoints. Se adicionar escritas, restrinja-as a uma lista de registradores permitidos e valide as faixas.
- Escolha do broker. Um broker MQTT gerenciado tira da sua lista de tarefas a aplicação de patches no broker e a renovação de certificados, e o explorador de topics da ConnectMQ é uma forma rápida de confirmar que todos os dispositivos estão reportando.
Comece a Usar a ConnectMQ
Faça o protótipo no servidor MQTT público broker.connectmq.com e depois dê ao seu gateway um broker privado. A ConnectMQ oferece endpoints TLS, credenciais por broker, um explorador de topics ao vivo e uma visão de dispositivos organizada por níveis ISA-95 para topics industriais. Crie um broker Serverless gratuito em app.connectmq.com, sem cartão de crédito, e aponte o sketch para ele.
Perguntas frequentes
Um único ESP32 consegue ler vários dispositivos Modbus?
Sim. Dê a cada dispositivo um endereço de escravo único no mesmo par RS485 e adicione seus registradores à tabela de pontos. O ESP32 lê um após o outro.
Preciso de um MAX485 ou de um MAX3485?
O ESP32 usa lógica de 3,3 V, então um transceptor de 3,3 V como o MAX3485 ou o SP3485 é a escolha mais limpa. Uma placa MAX485 de 5 V pode funcionar, mas exige cuidado com o nível na linha RO.
Por que só recebo erros de timeout?
Quase sempre é ligação ou configuração: A/B invertidos, baud ou paridade errados, endereço de escravo errado ou o pino DE/RE sem alternar. Teste o dispositivo antes com uma ferramenta no PC.
O gateway pode escrever em registradores Modbus a partir do MQTT?
A ModbusMaster suporta escritas, então é possível, mas restrinja as escritas a registradores específicos e valide os valores. Escritas remotas sem verificação em equipamentos industriais são um risco de segurança.



