• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Мост WiFi-UART: ModbusTCP-server <-> ModbusRTU-master

WebMiCo

New member
Код:
/*
Мост ModbusTCP-server <-> ModbusRTU-master.

При старте скетча слушается 502 порт. Поступающие ModbusTCP запросы
транслируются в Setial с добавлением контрольной суммы и отсечением заголовка MBAP Header.
Адрес ведомого берется из ModbusTCP заголовка (UnitID). Полученные ответы транслируются
в обратном направлении.

Для функционирования необходимо задать:
ssid и password своей сети WiFi;
RS_Speed в соответствии со своими потребностями;
RS_pin - выход для переключения направления приём/передача(используется в микросхемах типа ADM485)
         или индикации обмена (не обязателен).

Описание ModBusTCP:
http://www.simplymodbus.ca/TCP.htm
Описание МodBusRTU:
http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf


Примеры использования http://www.webmico.ru
*/

#include <ESP8266WiFi.h>

//----- Задай свои настройки здесь: -----
const char* ssid = "ssid";
const char* password = "password";
#define RS_Speed 9600    //скорость соединения по Serial
#define RS_pin 2        //выход для переключения направления передачи
//---------------------------------------

WiFiServer server(502);
WiFiClient serverClient;
uint8_t wifiCondition=0;
uint8_t buf[512];
uint8_t rsCondition=0;

//----- Таблица для вычисления CRC: -----
const uint16_t Crc16Table[] PROGMEM = {
    0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
    0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
    0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
    0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
    0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
    0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
    0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
    0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
    0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
    0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
    0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
    0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
    0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
    0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
    0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
    0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
    0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
    0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
    0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
    0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
    0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
    0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
    0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
    0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
    0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
    0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
    0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
    0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
    0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
    0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
    0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
    0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
    };
uint16_t CRC_modbus;
void CRC_16(uint8_t *b, uint16_t count){
    CRC_modbus = 0xffff;
    for(uint16_t i=0;i<count;i++){
        uint8_t ptr = (CRC_modbus&0x00ff) ^ ((uint8_t)b[i]&0x00ff);
        CRC_modbus = pgm_read_word_near(Crc16Table+ptr) ^ (CRC_modbus>>8);
    }
    b[count] = (uint8_t)(CRC_modbus&0xff);
    b[count+1] = (uint8_t)(CRC_modbus>>8);
}
   
void setup() {
    Serial.setTimeout(100);
    Serial.begin(RS_Speed);

    pinMode(RS_pin, OUTPUT);
    digitalWrite(RS_pin, 0);
}

void loop() {
   
    switch (wifiCondition) {
    case 0:
        //Подключение к WiFi точке доступа и старт сервера
        WiFi.begin(ssid, password);
        WiFi.waitForConnectResult();
        wifiCondition = 1;
        server.begin();
        server.setNoDelay(true);
        Serial.println(WiFi.localIP());
        break;
    case 1:
        //Ожидание подключения и обработка
        switch (WiFi.status()) {
        case WL_CONNECTED:
            if (server.hasClient()){
                //find free/disconnected spot
                if (!serverClient || !serverClient.connected()){
                    if(serverClient) serverClient.stop();
                    serverClient = server.available();
                    return;
                }
                WiFiClient sClient = server.available();
                sClient.stop();
            }
           
            /*Если сервер запущен и поступили данные*/
            if (serverClient && serverClient.connected()){
                if(serverClient.available()){
                    //Сперва считывается заголовок ModbusTCP, затем считывается остаток кадра в зависимости от
                    //заданной в заголовке длины (-1 ,тк адрес ведомого передается в заголовке после длины):
                    if (serverClient.readBytes(buf,7)==7){serverClient.readBytes(&buf[7],((buf[4]<<8)+buf[5]-1));}
                    serverClient.flush();
                    rsCondition = 1;
                }
            }
            break;
        default: //WiFi отключен
            wifiCondition = 0;
            rsCondition = 0;
            serverClient.stop();
            break;
        }
        break;
    }
   
    uint16 c;//длина сообщения
    switch (rsCondition) {
    case 0:
        break;
    case 1:
        //Поступило сообщение:
        c = (buf[4]<<8)+buf[5];//выделяем его длину
        CRC_16(&buf[6],c);//вычиляем контрольную сумму и добавляем ее в конец
        Serial.write(&buf[6],c+2);//Отправляем RTU запрос
        digitalWrite(RS_pin, 1);//переключение на приём
        rsCondition = 2;
        break;
    case 2:
        //Получение ответа:
        switch(buf[7]){//вычисление ожидаемой длины ответа в зависимости от функции в запросе
        case 1://01 (0x01) Read Coils
        case 2://02 (0x02) Read Discrete Inputs
            c = 3 + 2 + ((buf[10]<<8)+buf[11])/8 + (((buf[10]<<8)+buf[11])%8>0?1:0);
            break;
        case 3://03 (0x03) Read Holding Registers
        case 4://04 (0x04) Read Input Registers
            c = 3 + 2 + ((buf[10]<<8)+buf[11])*2;
            break;
        case 5://05 (0x05) Write Single Coil
        case 6://06 (0x06) Write Single Register
        case 15://15 (0x0F) Write Multiple Coils
        case 16://16 (0x10) Write Multiple registers
            c = 3 + 5;
            break;
        case 22://22 (0x16) Mask Write Register
            c = 3 + 7;
            break;   
        case 23://23 (0x17) Read/Write Multiple registers
            c = 3 + 2 + ((buf[14]<<8)+buf[15])*2;
            break;   
        }
        c = Serial.readBytes(&buf[6], c);//считывание ответа.
        CRC_16(&buf[6], c);//проверка контрольной суммы
        if(CRC_modbus==0 && c>=7){//контрольная сумма верна и ответ был
            buf[4] = ((c-2)>>8);
            buf[5] = ((c-2)&0xff);
            serverClient.write(&buf[0],c+6-2);//отсылаем ответ ModbusTCP-клиенту
        }
        rsCondition = 0;
        digitalWrite(RS_pin, 0);//переключение на передачу
        break;
    }
}
 

Kaspiysk

New member
А в связке с каким железом вы использовали этот скетч. У вас был преобразователь TTL <->RS-232 3.3v или же сразу в RS-485.
 

WebMiCo

New member
Напрямую к ардуине подключал без преобразователя. Что-то не заработало?
 

genek2882

New member
Добрый день , нужна помощь esp подключен к uno по rs232 через max485 все работает а через esp не читается coils.Направьте в нужном направление.
 
Код:
/*
Мост ModbusTCP-server <-> ModbusRTU-master.

При старте скетча слушается 502 порт. Поступающие ModbusTCP запросы
транслируются в Setial с добавлением контрольной суммы и отсечением заголовка MBAP Header.
Адрес ведомого берется из ModbusTCP заголовка (UnitID). Полученные ответы транслируются
в обратном направлении.

Для функционирования необходимо задать:
ssid и password своей сети WiFi;
RS_Speed в соответствии со своими потребностями;
RS_pin - выход для переключения направления приём/передача(используется в микросхемах типа ADM485)
         или индикации обмена (не обязателен).

Описание ModBusTCP:
http://www.simplymodbus.ca/TCP.htm
Описание МodBusRTU:
http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf


Примеры использования http://www.webmico.ru
*/

#include <ESP8266WiFi.h>

//----- Задай свои настройки здесь: -----
const char* ssid = "ssid";
const char* password = "password";
#define RS_Speed 9600    //скорость соединения по Serial
#define RS_pin 2        //выход для переключения направления передачи
//---------------------------------------

WiFiServer server(502);
WiFiClient serverClient;
uint8_t wifiCondition=0;
uint8_t buf[512];
uint8_t rsCondition=0;

//----- Таблица для вычисления CRC: -----
const uint16_t Crc16Table[] PROGMEM = {
    0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
    0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
    0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
    0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
    0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
    0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
    0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
    0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
    0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
    0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
    0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
    0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
    0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
    0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
    0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
    0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
    0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
    0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
    0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
    0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
    0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
    0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
    0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
    0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
    0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
    0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
    0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
    0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
    0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
    0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
    0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
    0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
    };
uint16_t CRC_modbus;
void CRC_16(uint8_t *b, uint16_t count){
    CRC_modbus = 0xffff;
    for(uint16_t i=0;i<count;i++){
        uint8_t ptr = (CRC_modbus&0x00ff) ^ ((uint8_t)b[i]&0x00ff);
        CRC_modbus = pgm_read_word_near(Crc16Table+ptr) ^ (CRC_modbus>>8);
    }
    b[count] = (uint8_t)(CRC_modbus&0xff);
    b[count+1] = (uint8_t)(CRC_modbus>>8);
}
  
void setup() {
    Serial.setTimeout(100);
    Serial.begin(RS_Speed);

    pinMode(RS_pin, OUTPUT);
    digitalWrite(RS_pin, 0);
}

void loop() {
  
    switch (wifiCondition) {
    case 0:
        //Подключение к WiFi точке доступа и старт сервера
        WiFi.begin(ssid, password);
        WiFi.waitForConnectResult();
        wifiCondition = 1;
        server.begin();
        server.setNoDelay(true);
        Serial.println(WiFi.localIP());
        break;
    case 1:
        //Ожидание подключения и обработка
        switch (WiFi.status()) {
        case WL_CONNECTED:
            if (server.hasClient()){
                //find free/disconnected spot
                if (!serverClient || !serverClient.connected()){
                    if(serverClient) serverClient.stop();
                    serverClient = server.available();
                    return;
                }
                WiFiClient sClient = server.available();
                sClient.stop();
            }
          
            /*Если сервер запущен и поступили данные*/
            if (serverClient && serverClient.connected()){
                if(serverClient.available()){
                    //Сперва считывается заголовок ModbusTCP, затем считывается остаток кадра в зависимости от
                    //заданной в заголовке длины (-1 ,тк адрес ведомого передается в заголовке после длины):
                    if (serverClient.readBytes(buf,7)==7){serverClient.readBytes(&buf[7],((buf[4]<<8)+buf[5]-1));}
                    serverClient.flush();
                    rsCondition = 1;
                }
            }
            break;
        default: //WiFi отключен
            wifiCondition = 0;
            rsCondition = 0;
            serverClient.stop();
            break;
        }
        break;
    }
  
    uint16 c;//длина сообщения
    switch (rsCondition) {
    case 0:
        break;
    case 1:
        //Поступило сообщение:
        c = (buf[4]<<8)+buf[5];//выделяем его длину
        CRC_16(&buf[6],c);//вычиляем контрольную сумму и добавляем ее в конец
        Serial.write(&buf[6],c+2);//Отправляем RTU запрос
        digitalWrite(RS_pin, 1);//переключение на приём
        rsCondition = 2;
        break;
    case 2:
        //Получение ответа:
        switch(buf[7]){//вычисление ожидаемой длины ответа в зависимости от функции в запросе
        case 1://01 (0x01) Read Coils
        case 2://02 (0x02) Read Discrete Inputs
            c = 3 + 2 + ((buf[10]<<8)+buf[11])/8 + (((buf[10]<<8)+buf[11])%8>0?1:0);
            break;
        case 3://03 (0x03) Read Holding Registers
        case 4://04 (0x04) Read Input Registers
            c = 3 + 2 + ((buf[10]<<8)+buf[11])*2;
            break;
        case 5://05 (0x05) Write Single Coil
        case 6://06 (0x06) Write Single Register
        case 15://15 (0x0F) Write Multiple Coils
        case 16://16 (0x10) Write Multiple registers
            c = 3 + 5;
            break;
        case 22://22 (0x16) Mask Write Register
            c = 3 + 7;
            break;  
        case 23://23 (0x17) Read/Write Multiple registers
            c = 3 + 2 + ((buf[14]<<8)+buf[15])*2;
            break;  
        }
        c = Serial.readBytes(&buf[6], c);//считывание ответа.
        CRC_16(&buf[6], c);//проверка контрольной суммы
        if(CRC_modbus==0 && c>=7){//контрольная сумма верна и ответ был
            buf[4] = ((c-2)>>8);
            buf[5] = ((c-2)&0xff);
            serverClient.write(&buf[0],c+6-2);//отсылаем ответ ModbusTCP-клиенту
        }
        rsCondition = 0;
        digitalWrite(RS_pin, 0);//переключение на передачу
        break;
    }
}
Приветствую!

Искал простой скетч для проксирования ModBus TCP в ModBus RTU. Наткнулся на этот. Решил попробовать.
В связке со стороны TCP - ESP8266 в STA режиме WiFi, со стороны RTU - Arduino Nano с датчиками BME280 и DS18B20.
Arduino Nano - рабочий скетч, уже около года работает в связке с OpenHab. Через программу диагностики QModBus можно напрямую забирать данные с Arduino, все работает.
Решил установить эту связку (Arduino+BME+18B20) без проводов удаленно, для этого решил найти скетч, который бы использовал возможности ESP работать по WiFi и проксировать TCP в RTU.
Собрал стенд, залил скетч в ESP. Через QModBus формирую ModBus TCP запрос к ESP, по сниферу вижу, что запрос уходит с нужным UnitID, функцией, адресами и длинной данных. Но на Arduine в Serial порте вижу, что после функции приходят неверные значения адресов с данными и длинна. SlaveID и функция - корректные.
Вы проверяли свой скетч? Он рабочий?
 

WebMiCo

New member
Когда-то был рабочий. Проверил сейчас - нет. Доработаю на днях.
 
Когда-то был рабочий. Проверил сейчас - нет. Доработаю на днях.
Приветствую!
Разобрался со своей схемой. Скетч работает.
Дело было в дешевом конверторе RS485 - TTL
Заменил на дорогой, заработало на стенде.

Попробую собрать схему полностью и включить электросчетчик в openhab.

Спасибо за скетч.
 
Сверху Снизу