ESP32 ve Arduino

ESP32 Modbus RS485 - MQTT Gateway: RTU Cihazlarını Sorgulayın ve Register Değerlerini Yayınlayın

Bir ESP32'yi ve 3,3 V RS485 alıcı-vericiyi; sayaçları ve sensörleri sorgulayan, register değerlerini ölçekleyen ve bunları MQTT üzerinden JSON olarak yayınlayan küçük bir Modbus RTU master'a dönüştürün.

Bir enerji analizörüne bağlı ve register değerlerini bulut broker'ına yayınlayan ESP32 modbus rs485 mqtt gateway
Bunu kendi MQTT broker’ınızda çalıştırınFree planla özel, yönetilen bir MQTT broker alın ve hazır kod örnekleriyle başlayın.
Ücretsiz deneyin Kod örnekleri Kart gerekmez

Bir ESP32 Modbus RS485 MQTT gateway, enerji analizörlerinden, frekans invertörlerinden (VFD), sıcaklık transmitterlerinden ve diğer Modbus RTU cihazlarından veri çekip bir PLC veya endüstriyel PC'ye gerek kalmadan Wi-Fi'a aktarmanızı sağlar. Bu eğitimde bir RS485 alıcı-vericiyi ESP32'ye bağlayacak, ModbusMaster kütüphanesiyle bir veya daha fazla slave cihazdan register okuyacak, ham değerleri ölçekleyecek ve bunları JSON olarak bir MQTT broker'ına yayınlayacaksınız.

Gateway Ne Yapar?

Modbus RTU, sorgulamaya dayalı bir master-slave protokolüdür: bir master belirli bir slave'den belirli bir register bloğunu istemedikçe hatta hiçbir şey olmaz. MQTT ise tam tersidir; olay güdümlü bir publish-subscribe protokolüdür. ESP32 bu ikisinin arasında durur:

  1. RS485 hattında Modbus master olarak görev yapar ve yapılandırılmış her cihazı sabit bir aralıkla sorgular.
  2. Bir register haritası kullanarak ham 16 bitlik register'ları mühendislik değerlerine (volt, derece, kilovatsaat) dönüştürür.
  3. MQTT client olarak görev yapar ve her cihaz için connectmq/demo/mb-4d1e/meter1 gibi bir topic'e bir JSON mesajı yayınlar.

Pek çok cihaz (her biri kendi slave adresiyle) tek bir RS485 çiftini paylaşabilir; böylece tek bir ESP32 enstrümanlarla dolu küçük bir panoya hizmet edebilir.

Donanım

Parça Notlar
ESP32 DevKit RS485 için UART2 kullanılır
3,3 V RS485 alıcı-verici (MAX3485, SP3485) Klasik MAX485 5 V'luk bir parçadır; 3,3 V'luk bir çip tercih edin
Modbus RTU cihazı Enerji analizörü, sıcaklık/nem transmitteri, VFD...
120 Ω direnç Uzun hatların uzak ucunda sonlandırma
Bükümlü çift kablo Masa testinin ötesindeki her şey için ekranlı bükümlü çift

Bazı alıcı-verici kartları yönü otomatik olarak değiştirir ve yalnızca RX ile TX'i dışarı verir. Bunlar da çalışır: aşağıdaki kodda DE/RE pinini kullanılmadan bırakmanız yeterlidir.

Bağlantılar

Alıcı-verici pini ESP32 pini Amaç
VCC 3V3 Besleme
GND GND Toprak; varsa cihazın sinyal toprağına da bağlayın
RO GPIO 16 (RX2) Hattan ESP32'ye veri
DI GPIO 17 (TX2) ESP32'den hatta veri
DE ve RE (birleştirilmiş) GPIO 4 Göndermek için yüksek, almak için düşük
A / B Cihaz A / B Hat çifti

En yaygın bağlantı hatası A ve B hatlarının ters bağlanmasıdır. Üreticiler hangisinin "artı" olduğu konusunda hemfikir değildir; bu yüzden yalnızca zaman aşımı alıyorsanız başka bir şeyi incelemeden önce bunları yer değiştirin. Sonlandırma dirençleri her cihaza değil, hattın iki fiziksel ucuna takılır.

Register Haritanızı Tanıyın

Her Modbus cihazı register'larını bir tabloda belgeler: adres, fonksiyon kodu, veri tipi ve ölçek. Bu tabloyu dikkatle okuyun, çünkü gateway hatalarının çoğuna yol açan sorulara o yanıt verir:

  • Fonksiyon kodu: holding register'lar 03 fonksiyonuyla, input register'lar 04 fonksiyonuyla okunur. Yanlış fonksiyonla sormak bir istisna (exception) döndürür ya da hiçbir şey döndürmez.
  • Adres ofseti: "30001" veya "40001" diyen dokümantasyon genellikle 04 veya 03 fonksiyonuyla 0 numaralı register adresini kasteder. Bazı kılavuzlar sıfır tabanlı, bazıları bir tabanlı adresler listeler.
  • Ölçek: birçok cihaz 23,5 derece için 235 gönderir (ölçek 0,1).
  • Veri tipi: 16 bit işaretsiz, 16 bit işaretli ya da iki register'a yayılmış 32 bitlik değerler; bazen word sırası ters çevrilmiş olarak.
  • Seri port ayarları: baud hızı, parite ve stop bitleri birebir eşleşmelidir. 9600 8N1 yaygındır, ancak standartta Modbus varsayılanı 9600 8E1'dir.

ESP32 Modbus RS485 MQTT Sketch'i

Library Manager'dan Doc Walker'ın ModbusMaster ve Nick O'Leary'nin PubSubClient kütüphanelerini kurun. Cihazlarınız için değiştirmeniz gereken tek kısım en üstteki register haritasıdır. Örnek iki cihazı sorgular: 1 adresindeki bir sıcaklık/nem transmitteri ve 2 adresindeki bir enerji analizörü.

#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;    // topic seviyesi olur
  uint8_t     slave;     // Modbus adresi
  uint8_t     fc;        // 3 = holding, 4 = input
  uint16_t    reg;       // register adresi (sıfır tabanlı)
  uint8_t     words;     // 1 = 16 bit, 2 = 32 bit (önce yüksek word)
  bool        isSigned;
  float       scale;
  const char* name;      // JSON anahtarı
};

// Bu tabloyu cihazlarınızın dokümantasyonuna göre düzenleyin
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() {
  // Aynı cihaza ait ardışık noktaları tek bir JSON mesajında grupla
  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);                      // çerçeveler arası boşluk, yavaş cihazlara yardımcı olur
      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();
  }
}

Bu koddaki birkaç ayrıntı göründüğünden daha önemlidir:

  • preTransmission ve postTransmission DE pinini sürer; böylece alıcı-verici yalnızca ESP32 gönderim yaparken hatta konuşur. Bunlar olmadan alıcı-verici hattı hiç bırakmaz ve her okuma zaman aşımına uğrar.
  • Yayınlanan her mesaj bir errors sayacı taşır. Böylece bir panel eski değerlerde sessizce donup kalmak yerine "cihaz mevcut ama bazı register'lar başarısız" bilgisini gösterebilir.
  • Mesajlar retained'dır; yeni bağlanan bir panel son değerleri anında görür.
  • setBufferSize(512), daha fazla nokta eklediğinizde daha uzun JSON için yer açar.

Çıktıyı kontrol edin:

mosquitto_sub -h broker.connectmq.com -p 1883 -i mb-4d1e-viewer -t "connectmq/demo/mb-4d1e/#" -v

ModbusMaster Hata Kodlarını Okumak

Kod Anlamı Tipik neden
0xE2 Yanıt zaman aşımına uğradı Yanlış A/B polaritesi, yanlış baud veya parite, yanlış slave adresi, DE pininin değişmemesi
0xE0 Yanıtta geçersiz slave ID Aynı adrese sahip iki cihaz ya da gürültü
0xE3 Geçersiz CRC Eksik sonlandırma, uzun ekransız kablo, toprak potansiyel farkı
0x02 Geçersiz veri adresi Register mevcut değil; sıfır tabanlı ve bir tabanlı adreslemeyi kontrol edin
0x01 Geçersiz fonksiyon Yalnızca 04'e yanıt veren bir register için 03 fonksiyonunu kullanmak ya da tam tersi

ESP32 Modbus RS485 MQTT İçin Sorun Giderme Kontrol Listesi

  1. Seri port ayarlarını cihaz menüsünden veya kılavuzundan doğrulayın: baud, parite, stop bitleri, adres.
  2. ESP32'yi devreye sokmadan önce bir cihazı USB-RS485 adaptörü ve bir PC Modbus aracıyla test edin. PC okuyamıyorsa ESP32 de okuyamaz.
  3. Her istek zaman aşımına uğruyorsa A ve B'yi yer değiştirin.
  4. Birkaç metreden uzun hatlarda her iki uca 120 Ω sonlandırma ekleyin ve sinyal toprağını bağlayın.
  5. Sorgulama aralığını gerçekçi tutun. 9600 baud'da bir düzine register okumak saniyenin hissedilir bir kısmını alır; hattın yanıt verebileceğinden daha hızlı sorgulamayın.
  6. Mümkün olduğunda ardışık register'ları tek bir istekte okuyun. On register için tek bir istek, tek register için on istekten çok daha hızlıdır.

Daha Fazla Değil, Daha Az Yayınlamak

Her on saniyede bir sorgulayıp her şeyi yayınlamak en basit tasarımdır, ancak sayaçlar yavaş değişir ve bulut trafiği birikir. Sketch'e kolayca eklenebilecek iki iyileştirme vardır: bir noktayı yalnızca bir ölü banttan (deadband) fazla değiştiğinde yayınlayın ve birkaç dakikada bir heartbeat olarak tam bir anlık görüntü gönderin. Bu desen değişimde raporlama (report by exception) yazısında anlatılıyor. Aynı soruna PC tabanlı gateway'ler ve daha büyük RTU ağları dahil gateway seviyesinden bakmak için Modbus RTU'dan MQTT'ye yazısına göz atın.

MQTT sizin için yeniyse MQTT broker'ın ne yaptığını anlatan yazıyla başlayın; JSON'u string birleştirme yerine bir kütüphaneyle oluşturmak için ArduinoJson MQTT rehberi iyi bir tamamlayıcıdır. ESP32 IoT eğitimi burada kullanılan Wi-Fi ve yeniden bağlanma temellerini ele alıyor.

Üretim İçin Sağlamlaştırma

Bir fabrika duvarındaki gateway yıllarca çalışır; bu yüzden ona altyapı gibi davranın:

  • TLS ve kimlik bilgileri. WiFiClientSecure ile 8883 portunu ve bu gateway için verilmiş kimlik bilgilerini kullanın. Tesis verilerini asla herkese açık bir test topic'ine yayınlamayın.
  • Watchdog. Takılan bir Modbus işlemi veya Wi-Fi yığını kartı yeniden başlatsın diye task watchdog'u etkinleştirin.
  • İzolasyon. Elektriksel gürültünün yüksek olduğu panolarda izole bir RS485 alıcı-verici, toprak döngülerinin ESP32'ye zarar vermesini önler.
  • Varsayılan olarak salt okunur. MQTT üzerinden register yazmak motorları çalıştırabilir veya set değerlerini değiştirebilir. Yazma eklerseniz bunu izin verilen register'lardan oluşan bir listeyle sınırlayın ve değer aralıklarını doğrulayın.
  • Broker seçimi. Tam yönetilen bir MQTT hizmeti, broker yamalarını ve sertifika yenilemeyi yapılacaklar listenizden çıkarır; ConnectMQ'nun topic gezgini de her cihazın raporladığını doğrulamanın hızlı bir yoludur.

ConnectMQ ile Başlayın

Prototipinizi broker.connectmq.com'un genel alanında geliştirin, ardından gateway'inize özel bir broker verin. ConnectMQ, TLS uç noktaları, broker başına kimlik bilgileri, canlı bir topic gezgini ve endüstriyel topic'ler için ISA-95 seviyelerine göre düzenlenmiş bir cihaz görünümü sunar. app.connectmq.com adresinde kredi kartı olmadan ücretsiz bir Serverless broker oluşturun ve sketch'i ona yönlendirin.

Sık Sorulan Sorular

Tek bir ESP32 birden fazla Modbus cihazını okuyabilir mi?

Evet. Aynı RS485 çifti üzerindeki her cihaza benzersiz bir slave adresi verin ve register'larını nokta tablosuna ekleyin. ESP32 bunları sırayla sorgular.

MAX485 mi, MAX3485 mi gerekir?

ESP32 3,3 V lojik kullanır; bu nedenle MAX3485 veya SP3485 gibi 3,3 V'luk bir alıcı-verici temiz bir seçimdir. 5 V'luk bir MAX485 kartı çalışabilir, ancak RO hattında seviye uyumuna dikkat etmek gerekir.

Neden sadece zaman aşımı hataları alıyorum?

Neredeyse her zaman bağlantı veya ayarlardan kaynaklanır: ters bağlanmış A/B, yanlış baud veya parite, yanlış slave adresi ya da DE/RE pininin değişmemesi. Cihazı önce bir PC aracıyla test edin.

Gateway MQTT'den Modbus register'larına yazabilir mi?

ModbusMaster yazmayı desteklediği için bu mümkündür, ancak yazmayı belirli register'larla sınırlayın ve değerleri doğrulayın. Endüstriyel ekipmana denetimsiz uzaktan yazma bir güvenlik riskidir.

İlgili yazılar

ESP32 NTP MQTT Zaman Damgası: Her Mesaja Doğru Zaman Ekleyin

ESP32 saatini NTP ile eşitleyin, ISO 8601 ve epoch milisaniye zaman damgalarını biçimlendirin ve bunları MQTT payload'larına ekleyin; böylece ölçümler geç yayınlansa bile gerçek alınma zamanlarını korur.

6 dk okuma

Tüm yazılar →

Özel MQTT broker’ınız birkaç tık uzağınızda

Hesabınızı oluşturun, broker kimlik bilgilerinizi alın ve ilk cihazınızı bağlayın — kurulacak, yamalanacak ya da ölçeklendirilecek sunucu yok.