Работа с шиной RS-485: основы Modbus RTU
Введение в RS-485 и Modbus RTU: Физический и Протокольный Уровни
Промышленная автоматизация и системы "умного дома" часто требуют подключения устройств, расположенных на значительном удалении друг от друга. Стандартные интерфейсы, такие как I2C или SPI, не подходят для этих задач из-за их малой дальности. Здесь на сцену выходит RS-485 — надежный стандарт физического уровня для последовательной передачи данных.
> ℹ️ Информация: RS-485 определяет только физический уровень (как передавать биты по проводам), а Modbus RTU — протокольный (что означают эти биты). Они работают в паре, как конверт и письмо: RS-485 — это почтовая служба, доставляющая конверт, а Modbus — это язык, на котором написано письмо внутри.
Что такое RS-485?
RS-485 (Recommended Standard 485) — это стандарт, описывающий электрические характеристики драйверов и приёмников для использования в сбалансированных цифровых многоточечных системах.📋 Ключевые понятия:
- Дифференциальный сигнал: В отличие от интерфейсов, использующих один сигнальный провод относительно земли (GND), RS-485 использует два провода, обычно обозначаемые как A и B. Данные передаются разностью потенциалов между этими двумя линиями. Если на линии A напряжение выше, чем на B, это логическая "1", если наоборот — логический "0".
- Высокая помехоустойчивость: Любая внешняя электромагнитная помеха (например, от силового кабеля) воздействует на оба провода A и B практически одинаково. Поскольку приемник анализирует разницу напряжений между ними, помеха "вычитается" и не влияет на полезный сигнал. Это позволяет прокладывать линии RS-485 на большие расстояния в условиях промышленных помех.
- Двухпроводная линия: Для полудуплексной связи (когда устройства говорят по очереди) достаточно одной витой пары проводов. Наш контроллер HI использует именно этот режим.
- Топология "Шина": Все устройства подключаются к одной общей паре проводов параллельно друг другу. Эта линия называется шиной. Максимальная длина шины может достигать 1200 метров, а количество устройств на одной шине — до 32 (стандартно) или до 256 с использованием специальных повторителей или приемопередатчиков.
Что такое Modbus?
Если RS-485 — это "дорога", то Modbus — это "правила дорожного движения" для устройств. Modbus — это открытый коммуникационный протокол, работающий по принципу "Master-Slave" (Ведущий-Ведомый).
- Master (Ведущий): Это устройство, которое инициирует общение. Оно отправляет запросы на шину. В нашей системе роль Master всегда выполняет контроллер HI. На одной шине Modbus RTU может быть только один Master.
- Slave (Ведомый): Это оконечное устройство (датчик, релейный модуль, счетчик), которое слушает шину, ожидая адресованного ему запроса. Получив запрос, Slave-устройство выполняет действие (например, считывает температуру) и отправляет ответ обратно Master'у. Slave-устройства никогда не инициируют передачу данных сами.
Для того чтобы Master мог обратиться к конкретному Slave-устройству, каждое из них должно иметь уникальный адрес — Slave ID. Это число в диапазоне от 1 до 247. Адрес устанавливается на самом устройстве (с помощью DIP-переключателей или программно).
Структура сообщения (кадра) в Modbus RTU предельно проста и эффективна:
Эта простая и надежная архитектура сделала Modbus RTU стандартом де-факто в промышленной автоматизации.
---
Настройка порта RS-485 в контроллере HI
Правильная настройка физического уровня — залог стабильной работы всей системы. Ошибки, допущенные на этом этапе, являются самой распространенной причиной проблем с Modbus.
> ⚠️ Внимание: Неправильные параметры порта (скорость, четность) — самая частая причина, почему Modbus-устройства не отвечают. Все устройства на одной шине должны иметь одинаковые настройки.
Физическое подключение
На корпусе контроллера HI присутствуют клеммы для подключения шины RS-485, обозначенные как `RS485-A` и `RS485-B`.
Настройка параметров порта
Перед тем как начать обмен данными, необходимо сконфигурировать последовательный порт контроллера. Эти настройки должны быть идентичны настройкам на всех подключенных Modbus-устройствах.
| Параметр | Описание | Типовые значения |
| ------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| Скорость (Baud Rate) | Скорость передачи данных, измеряется в битах в секунду. | 9600, 19200, 38400, 57600, 115200 |
| Биты данных (Data Bits) | Количество бит в "полезной" части каждого байта. Для Modbus RTU это всегда 8. | 8 |
| Четность (Parity) | Механизм проверки на наличие ошибок в одном байте. Может быть 'None' (нет), 'Even' (чет), 'Odd' (нечет). | None (N), Even (E), Odd (O) |
| Стоп-биты (Stop Bits) | Количество бит, указывающих на окончание передачи одного байта. | 1, 2 |
Самой распространенной конфигурацией, поддерживаемой большинством устройств по умолчанию, является "9600 8N1":
- 9600 — скорость 9600 бит/с.
- 8 — 8 бит данных.
- N — без контроля четности (None).
- 1 — 1 стоп-бит.
Терминирующий резистор (120 Ом)
На высоких скоростях и длинных линиях (более 50 метров) возникает физический эффект отражения сигнала от концов кабеля. Отраженный сигнал накладывается на основной и искажает его, что приводит к ошибкам передачи данных (ошибки CRC).
Для подавления этого эффекта на двух физически крайних устройствах шины устанавливается терминирующий резистор номиналом 120 Ом. Он подключается между клеммами A и B.
- Если контроллер HI — одно из крайних устройств, терминатор нужно установить на нем (часто это делается программно или с помощью перемычки).
- Второе крайнее устройство также должно быть терминировано.
- Устройства, находящиеся в середине шины, терминировать не нужно.
> 💡 Подсказка: Если у вас короткая шина (до 10-20 метров) и низкая скорость (9600), система может работать и без терминаторов. Однако их установка является правилом хорошего тона и гарантирует стабильность в будущем.
---
Обзор узлов Node-RED для работы с Modbus
Для взаимодействия с Modbus-устройствами из Node-RED мы будем использовать популярную и мощную палитру `node-red-contrib-modbus`. Предполагается, что она уже установлена в вашей системе. Если нет, ее можно установить через `Manage Palette` в интерфейсе Node-RED.
Эта палитра предоставляет набор узлов для выполнения всех необходимых операций.
Узел `Modbus-Client` (конфигурационный)
Это не узел потока, а узел конфигурации. Он определяет параметры подключения к шине или устройству и используется затем во всех остальных Modbus-узлах.
* Type: Тип подключения. Для работы по RS-485 необходимо выбрать `Serial`.
* Serial Port: Путь к последовательному порту контроллера. В системе HI это обычно `/dev/ttyS1` или `/dev/ttyS2` (зависит от модели контроллера).
* Serial Type: Тип протокола. Мы выбираем `RTU`.
* Baud Rate, Data Bits, Parity, Stop Bits: Здесь задаются параметры порта, которые мы обсуждали в предыдущем разделе (например, `9600`, `8`, `None`, `1`).
* Unit-ID: Это не адрес Slave-устройства. Это поле в клиенте можно оставить пустым, так как ID конкретного устройства мы будем указывать в каждом узле-запросе.
Узлы для чтения данных: `Modbus-Read` и `Modbus-Getter`
Эти узлы отправляют Master-запрос на чтение данных из Slave-устройства.
- `Modbus-Read`: Более гибкий узел, параметры которого (Slave ID, адрес, количество регистров) можно задавать динамически через входящее `msg`.
- `Modbus-Getter`: Упрощенный узел, где все параметры задаются статически в его настройках. Он идеально подходит для периодического опроса датчиков.
- FC 3 (Read Holding Registers): Чтение 16-битных регистров, доступных для чтения и записи (например, уставка температуры на термостате).
- FC 4 (Read Input Registers): Чтение 16-битных регистров, доступных только для чтения (например, показания датчика температуры или влажности).
Узлы для записи данных: `Modbus-Write`
Этот узел отправляет Master-запрос на запись данных в Slave-устройство.
- Настройка: В узле указывается Slave ID, адрес регистра, а сами данные для записи передаются через `msg.payload`.
- Коды функций для записи:
* FC 6 (Write Single Register): Запись значения в один 16-битный регистр.
* FC 15 (Write Multiple Coils): Запись состояний сразу нескольких реле.
* FC 16 (Write Multiple Registers): Запись значений в несколько регистров подряд.
Выбор узла и кода функции зависит от задачи и от того, что поддерживает конкретное оконечное устройство. Вся эта информация содержится в его документации.
---
Практика: Чтение температуры с Modbus-датчика
Рассмотрим самый распространенный сценарий: периодическое получение данных с датчика.
Задача: Каждые 10 секунд считывать значение температуры с комнатного датчика, у которого `Slave ID = 15`. Температура хранится в регистре типа "Input Register" с адресом `0`. Устройство отдает значение в виде целого числа, умноженного на 10 (т.е. значение `253` означает `25.3 °C`).> 💡 Подсказка: Всегда изучайте карту регистров (Register Map) вашего устройства. Без нее невозможно понять, по какому адресу находится нужная информация и как ее интерпретировать. Ошибка на единицу в адресе (`off-by-one error`) — очень частое явление. Если в документации указан регистр 30001, его адрес в запросе будет `0`.
Создание потока
* `Payload`: `timestamp`
* `Repeat`: `interval`
* `every`: `10` seconds
* `Name`: `Read Room Temperature`
* `Server`: Выберите или создайте `Modbus-Client` с параметрами вашего порта RS-485 (например, `/dev/ttyS1`, `9600`, `8N1`).
* `Unit-ID`: `15` (адрес нашего датчика).
* `FC`: `FC 4: Read Input Registers`.
* `Address`: `0` (адрес регистра температуры).
* `Quantity`: `1` (мы читаем один 16-битный регистр).
Анализ и обработка ответа
После успешного опроса `Modbus-Getter` вернет `msg` со сложной структурой. Нас интересует `msg.payload`. Он будет выглядеть примерно так:
{
"data": [253],
"buffer": ""
}
`data` — это массив полученных значений. В нашем случае это одно значение `253`. Это "сырое" значение (raw value).
Теперь наша задача — превратить его в понятные градусы Цельсия. Для этого используем узел `Function`.
* `Name`: `Format Temperature`
* Добавьте следующий код:
// Получаем сырое значение из массива
const rawValue = msg.payload.data[0];
// Согласно документации, делим значение на 10
const temperature = rawValue / 10.0;
// Формируем новый, чистый msg.payload в соответствии
// с "Контрактом сообщения", принятым в нашей академии.
msg.payload = {
"value": temperature,
"unit": "°C",
"source": "modbus-sensor-room1",
"ts": Date.now()
};
// Для наглядной диагностики выводим статус на узле
node.status({fill:"green", shape:"dot", text: temperature + " °C"});
return msg;
Теперь, если вы подключите выход `Function` к узлу `Debug`, вы будете получать аккуратные JSON-объекты, готовые для дальнейшей обработки — отображения на дашборде, отправки в базу данных или использования в сценариях автоматизации.
---
Практика: Управление Modbus-реле
Теперь рассмотрим обратную задачу — отправку команды на исполнительное устройство.
Задача: Включить свет, подключенный к Modbus-релейному модулю с `Slave ID = 20`. Управление происходит через регистр типа "Coil" с адресом `2`. Для включения в него нужно записать `1` (true), для выключения — `0` (false).> 🔗 Связанный материал: Принципы формирования объекта `msg` и работы с JSON были подробно рассмотрены в модуле `COURSE-06-M03`. Здесь мы активно их используем для формирования запросов к Modbus-устройствам.
Создание потока
* Первый `Inject` с `msg.payload` (string) = `ON`.
* Второй `Inject` с `msg.payload` (string) = `OFF`.
* `Name`: `Control Light Relay`
* `Server`: Выберите тот же `Modbus-Client`, что и в предыдущем примере.
* `Unit-ID`: `20`.
* `FC`: `FC 5: Force Single Coil`.
* `Address`: `2` (адрес нашего реле).
* Поле `Value` оставьте пустым — узел будет брать значение из `msg.payload`.
Формирование команды
Узел `Modbus-Write` ожидает получить данные для записи в определенном формате. Для записи одного бита (Coil) ему нужен `msg.payload` равный `true` или `false`. Наша задача — преобразовать строки "ON" и "OFF" в булевы значения.
Можно использовать для этого `Switch` + `Change` узлы, но для простоты сделаем это в одном узле `Function`.
* `Name`: `Prepare ON/OFF Command`
* Добавьте код:
// Команда для записи передается в msg.payload
// Для FC5 это должно быть true или false.
if (msg.payload === "ON") {
msg.payload = true;
} else if (msg.payload === "OFF") {
msg.payload = false;
} else {
// Если пришла неизвестная команда, останавливаем поток.
node.warn("Неизвестная команда: " + msg.payload);
return null;
}
// Узел Modbus-Write также можно сконфигурировать динамически,
// передав ему объект с параметрами.
// Например, так можно управлять разными реле из одного потока.
/*
msg.payload = {
'value': true, // Что пишем
'fc': 5, // Код функции
'unitid': 20, // Адрес устройства
'address': 2, // Адрес регистра
'quantity': 1 // Количество
};
*/
// В нашем простом случае достаточно передать только value.
return msg;
Теперь, нажимая на узлы `Inject`, вы будете отправлять команды `true` или `false` на `Modbus-Write`, который, в свою очередь, будет включать или выключать реле. Узел `Debug`, подключенный к выходу `Modbus-Write`, покажет вам ответ от устройства после успешной записи.
---
Итоги и решение типичных проблем
На этом уроке мы сделали большой шаг от простых дискретных сигналов к полноценной промышленной шине данных. Мы научились подключать, настраивать и взаимодействовать с Modbus RTU устройствами.
> ⚠️ Внимание: На одной шине RS-485 может быть только один Master. В нашем случае Master — это контроллер HI. Одновременное подключение второго Master (например, ПК с конфигурационной утилитой для настройки устройств) к работающей шине вызовет коллизии (конфликты) и отказ всей шины. Для настройки устройств отключайте их от основной шины или останавливайте опрос в Node-RED.
Краткое повторение:- RS-485 — это надежная физическая среда передачи данных на большие расстояния.
- Modbus RTU — это протокол типа "Master-Slave", где наш контроллер — Master.
- Каждое устройство на шине имеет уникальный Slave ID.
- Ключевые узлы в Node-RED:
* `Modbus-Getter` — для простого чтения данных.
* `Modbus-Write` — для отправки команд.
Чек-лист для отладки "Modbus не работает"
Если устройства не отвечают, пройдитесь по этому списку:
В следующем уроке мы рассмотрим более сложные сценарии работы с Modbus, включая чтение нескольких регистров за раз, обработку 32-битных чисел и float-значений.