Эмулятор устройства Modbus RTU на ESP32
Данный проект реализует эмулятор устройства Modbus RTU на базе микроконтроллера ESP32. Программа позволяет симулировать работу датчиков, передающих данные по протоколу Modbus RTU в формате Holding Registers. Эмулятор генерирует случайные значения для пяти физических величин (температура, давление, влажность, уровень, расход), преобразует их в формат, совместимый с Modbus, и предоставляет доступ к этим данным по запросам от Modbus Master-устройств.
Проект может быть полезен для тестирования и отладки систем автоматизации, SCADA-систем, а также для обучения работе с протоколом Modbus RTU.
Описание протокола Modbus RTU
Modbus RTU — это последовательный протокол обмена данными, широко используемый в промышленной автоматизации. Он работает на физическом уровне RS-485 (или RS-232) и обеспечивает связь между Master-устройством (например, ПЛК, компьютер) и Slave-устройствами (датчики, исполнительные механизмы).
Основные характеристики Modbus RTU:
- Адресация устройств: Каждое устройство имеет уникальный Slave ID (от 1 до 247).
- Формат данных: Данные передаются в виде регистров (16-битных значений).
- Типы регистров:- Holding Registers — для хранения данных, которые могут быть записаны и прочитаны Master-устройством.
- Input Registers — только для чтения.
 
- Формат сообщения:- Адрес устройства (1 байт)
- Код функции (1 байт)
- Данные (N байт)
- Контрольная сумма (CRC, 2 байта)
 
- Скорость обмена (baud rate): Обычно 9600, 19200, 38400 бод.
Описание кода
1. Конфигурация Modbus
const uint8_t slaveID = 1;          // Идентификатор устройства (Slave ID)
const uint32_t baudRate = 9600;     // Скорость обмена данными
const uint8_t registerCount = 10;   // Количество регистров (5 float × 2 регистра = 10)- Устройство настроено как Slave с ID = 1.
- Скорость обмена: 9600 бод.
- Используется 10 регистров для хранения 5 значений типа float(каждоеfloatзанимает 2 регистра).
2. Буфер для регистров и объект Modbus
uint16_t holdingRegisters[registerCount];  // Буфер для хранения значений
ModbusRTUSlave modbus(Serial);             // Объект для работы с Modbus RTU- holdingRegisters— массив для хранения значений, доступных по Modbus.
- modbus— объект библиотеки- ModbusRTUSlave, отвечающий за обработку запросов.
3. Преобразование float в регистры
void floatToRegisters(float value, uint16_t ®1, uint16_t ®2) {
 union { float f; uint32_t i; } converter;
 converter.f = value;
 reg1 = (converter.i >> 16) & 0xFFFF;  // Старшие 2 байта
 reg2 = converter.i & 0xFFFF;          // Младшие 2 байта
}Функция преобразует значение float в два 16-битных регистра (uint16_t).
- Используется union для интерпретации байтов floatкакuint32_t.
4. Генерация случайных значений
float randomFloat(float min, float max) {
 return min + static_cast<float>(rand()) / (static_cast<float>(RAND_MAX / (max - min)));
}- Генерирует случайное значение floatв заданном диапазоне.
5. Обновление значений регистров
void updateRandomValues() {
 float values[5];
 values[0] = randomFloat(20.0, 30.0);    // Температура, °C
 values[1] = randomFloat(50.0, 100.0);   // Давление, kPa
 values[2] = randomFloat(30.0, 80.0);    // Влажность, %
 values[3] = randomFloat(0.0, 10.0);     // Уровень, m
 values[4] = randomFloat(100.0, 500.0);  // Расход, l/min
 for (int i = 0; i < 5; i++) {
   floatToRegisters(values[i], holdingRegisters[i * 2], holdingRegisters[i * 2 + 1]);
 }
}- Генерирует 5 случайных значений для физических величин.
- Преобразует их в регистры и сохраняет в holdingRegisters.
6. Вывод текущих значений в Serial
void printCurrentValues() {
 // ... вывод значений в Serial для отладки
}- Выводит текущие значения в Serial Monitor для отладки.
7. Инициализация и основной цикл
void setup() {
 Serial.begin(baudRate, SERIAL_8N1);
 randomSeed(analogRead(0));
 updateRandomValues();
 modbus.configureHoldingRegisters(holdingRegisters, registerCount);
 modbus.begin(slaveID, baudRate, SERIAL_8N1);
 // ... вывод информации о конфигурации
}
void loop() {
 updateRandomValues();
 modbus.poll();
 delay(1);
}- В setup():- Инициализируется Serial и Modbus.
- Генерируются начальные случайные значения.
 
- В loop():- Обновляются значения перед каждым опросом.
- Обрабатываются запросы от Master-устройств (modbus.poll()).
 
Заключение
Этот проект позволяет эмулировать работу устройства Modbus RTU на ESP32, генерируя случайные данные для пяти физических величин. Код может быть легко адаптирован для работы с реальными датчиками или для тестирования SCADA-систем.
Возможные улучшения:
- Добавление поддержки Input Registers.
- Реализация дискретных входов/выходов (Coils).
- Поддержка дополнительных функций Modbus (например, чтение/запись нескольких регистров).
Полный код
#include 
// Конфигурация Modbus
const uint8_t slaveID = 1;
const uint32_t baudRate = 9600;
const uint8_t registerCount = 10; // 5 float значений × 2 регистра = 10 регистров
// Буфер для holding registers
uint16_t holdingRegisters[registerCount];
// Создаем объект Modbus
ModbusRTUSlave modbus(Serial);
// Функция для преобразования float в два uint16_t
void floatToRegisters(float value, uint16_t ®1, uint16_t ®2) {
  union {
    float f;
    uint32_t i;
  } converter;
 
  converter.f = value;
  reg1 = (converter.i >> 16) & 0xFFFF; // Старшие 2 байта
  reg2 = converter.i & 0xFFFF;         // Младшие 2 байта
}
// Функция для генерации случайного float в заданном диапазоне
float randomFloat(float min, float max) {
  return min + static_cast(rand()) / (static_cast(RAND_MAX / (max - min)));
}
// Функция для обновления значений регистров случайными float
void updateRandomValues() {
  // Генерируем 5 случайных float значений с разными диапазонами
  float values[5];
 
  // Задаем разные диапазоны для каждого значения
  values[0] = randomFloat(20.0, 30.0);    // Температура, °C
  values[1] = randomFloat(50.0, 100.0);   // Давление, kPa
  values[2] = randomFloat(30.0, 80.0);    // Влажность, %
  values[3] = randomFloat(0.0, 10.0);     // Уровень, m
  values[4] = randomFloat(100.0, 500.0);  // Расход, l/min
 
  // Преобразуем float в регистры
  for (int i = 0; i < 5; i++) {
    floatToRegisters(values[i], holdingRegisters[i * 2], holdingRegisters[i * 2 + 1]);
  }
}
// Функция для вывода текущих значений в Serial
void printCurrentValues() {
  Serial.println("\n=== Current Values ===");
  Serial.println("Float values in holding registers:");
 
  for (int i = 0; i < 5; i++) {
    // Преобразуем обратно из регистров в float для отладки
    union {
      float f;
      uint32_t i;
    } converter;
   
    converter.i = (static_cast(holdingRegisters[i * 2]) << 16) | holdingRegisters[i * 2 + 1];
   
    Serial.print("Value ");
    Serial.print(i);
    Serial.print(": ");
    Serial.print(converter.f, 2);
   
    // Подписи для разных значений
    switch(i) {
      case 0: Serial.println(" °C (Temperature)"); break;
      case 1: Serial.println(" kPa (Pressure)"); break;
      case 2: Serial.println(" % (Humidity)"); break;
      case 3: Serial.println(" m (Level)"); break;
      case 4: Serial.println(" l/min (Flow)"); break;
    }
  }
 
  Serial.println("Raw registers (HEX):");
  for (int i = 0; i < registerCount; i++) {
    if (holdingRegisters[i] < 0x10) Serial.print("0");
    Serial.print(holdingRegisters[i], HEX);
    Serial.print(" ");
  }
  Serial.println("\n======================");
}
void setup() {
  // Инициализация Serial
  Serial.begin(baudRate, SERIAL_8N1);
  while (!Serial) {
    delay(10);
  }
 
  // Инициализация генератора случайных чисел
  randomSeed(analogRead(0));
 
  // Генерируем начальные значения
  updateRandomValues();
 
  // Инициализация Modbus
  modbus.configureHoldingRegisters(holdingRegisters, registerCount);
  modbus.begin(slaveID, baudRate, SERIAL_8N1);
 
  Serial.println("====================================");
  Serial.println("Modbus RTU Slave with Random Float Values");
  Serial.println("====================================");
  Serial.print("Slave ID: "); Serial.println(slaveID);
  Serial.print("Baud rate: "); Serial.println(baudRate);
  Serial.print("Holding registers: "); Serial.println(registerCount);
  Serial.println("Storing 5 float values (2 registers each)");
  Serial.println("Value ranges:");
  Serial.println("  0: Temperature - 20.0 to 30.0 °C");
  Serial.println("  1: Pressure - 50.0 to 100.0 kPa");
  Serial.println("  2: Humidity - 30.0 to 80.0 %");
  Serial.println("  3: Level - 0.0 to 10.0 m");
  Serial.println("  4: Flow - 100.0 to 500.0 l/min");
  Serial.println("====================================");
 
  // Выводим начальные значения
  printCurrentValues();
  Serial.println("Waiting for Modbus requests...");
  Serial.println("Values will be updated on each Modbus request");
}
void loop() {
  // Обновляем значения перед обработкой каждого запроса
  updateRandomValues();
 
  // Обработка Modbus запросов
  modbus.poll();
 
  // Небольшая задержка для стабильности
  delay(1);
}Заливаем.

Проверяем.

Все, как и ожидалось. В диапазонах.
 
                                
Только полноправные пользователи могут оставлять комментарии. Аутентифицируйтесь пожалуйста, используя сервисы.