Эмулятор устройства 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);
}
Заливаем.

Проверяем.

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