ESP32 e Arduino

Gateway ESP32 Modbus RS485 para MQTT: Leia Dispositivos RTU e Publique Valores de Registradores

Transforme um ESP32 e um transceptor RS485 de 3,3 V em um pequeno mestre Modbus RTU que lê medidores e sensores, escala os valores dos registradores e os publica como JSON via MQTT.

Gateway ESP32 Modbus RS485 MQTT ligado a um medidor de energia e publicando valores de registradores em um broker na nuvem
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

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:

  1. Ele atua como mestre Modbus no barramento RS485, lendo cada dispositivo configurado em um intervalo fixo.
  2. Ele converte registradores brutos de 16 bits em valores de engenharia (volts, graus, quilowatts-hora) usando um mapa de registradores.
  3. 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 235 significando 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:

  • preTransmission e postTransmission acionam 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

  1. Confirme a configuração serial no menu ou no manual do dispositivo: baud, paridade, stop bits, endereço.
  2. 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.
  3. Inverta A e B se todas as requisições derem timeout.
  4. Adicione terminação de 120 Ω nas duas extremidades de barramentos com mais de alguns metros e ligue o terra de sinal.
  5. 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.
  6. 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 WiFiClientSecure e 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.

Artigos relacionados

Exemplo de MQTT com ESP-IDF: Monte um Cliente esp-mqtt do Zero

Monte um projeto ESP-IDF a partir de uma pasta vazia com o componente esp-mqtt: configuração do Wi-Fi em modo station, o handler de eventos MQTT, publicação e inscrição, propriedades do MQTT 5 e TLS com o bundle de certificados.

8 min de leitura

Usando os Recursos do MQTT 5 do esp-mqtt no ESP32 com ESP-IDF

Um projeto ESP-IDF prático que ativa o MQTT 5.0 no esp-mqtt e usa user properties, expiração de mensagens, topic aliases, expiração de sessão e response topics com um broker MQTT 5 real.

8 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.