Эмулятор устройства Modbus RTU на ESP32

  • Михаил
  • 8 мин. на прочтение
  • 15
  • 16 Sep 2025
  • 16 Sep 2025

Данный проект реализует эмулятор устройства 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);
}

Заливаем.

Проверяем.

Все, как и ожидалось. В диапазонах.