Modbus RTU: архитектура протокола и пусконаладка сети RS-485
Обзор серии
Эта статья – часть серии. Ниже приведены ссылки на все статьи серии:- Архитектура Modbus RTU: взаимодействие Master-Slave и модель данных
- Типы регистров Modbus: Coils, Discrete Inputs, Input и Holding Registers
- Структура кадра Modbus RTU (ADU и PDU) и проверка контрольной суммы CRC
- Смещение линии RS-485 (line biasing), интервалы тишины и терминальные резисторы
- Параметры UART: скорость (baud rate), биты четности (parity) и стоп-биты
- Настройка коммуникационного модуля RS-485 Siemens CM 1241 в TIA Portal
- Написание конечного автомата (State Machine) на SCL для опроса Modbus (MB_COMM_LOAD, MB_MASTER)
- Порядок байтов (Endianness) и Word Swap
- Отладка Modbus-соединений с помощью Modbus Mechanic и анализ кодов ошибок ПЛК
В предыдущей статье мы рассмотрели передачу данных на физическом уровне – UART и последовательные линии данных (TTL/CMOS, RS232, RS485), а также обсудили роль Modbus в современном АСУ ТП.
Очень коротко пройдемся по основным тезисам:
- Данные внутри чипа передаются по шине данных – по одному проводу на каждый бит одного байта;
- Данные между устройствами передаются через последовательные линии, по одному биту за раз;
- Конвертацией “параллельных” данных в “последовательные” и обратно занимается UART;
- “Нативный” UART это напряжение TTL/CMOS – 3.3 / 5 В;
- Для более устойчивой передачи данных (больше чем десятки сантиметров) в промышленных условиях используют RS485 – по-сути это “усилитель” сигнала.
1. Анатомия протокола Modbus RTU
Мы должны понимать, какие байты мы будем слать и получать. Это фундамент для отладки.
1.1. Модель взаимодействия: Master-Slave
Есть один “ведущий” (ПЛК), который инициирует запросы – Master и много “ведомых” (счетчики, датчики, другой ПЛК), которые отвечают – Slave.
1.2. Модель данных: регистры
В Modbus мы работаем с регистрами. Важно отметить, что это не “физические” регистры, которые мы обсуждали в контексте UART в прошлой статье.
Исторически, когда Modbus только придумали в 1979 году для контроллеров Modicon, данные хранились блоками по 16 бит. Так это слово и прижилось.
Для протокола Modbus вся память устройства выглядит как огромная таблица Excel, где в каждой строчке лежит число от 0 до 65535 (16 бит). Каждая такая строчка и называется “регистром”.
Разработчики прошивки устройства сами делают “карту регистров”. Например, они пишут в коде: “если по Modbus придет запрос на чтение регистра № 100, возьми значение переменной temperature из оперативной памяти и отправь его”.
Можно было бы провести аналогию с Address Bus и Data Bus, но они работают внутри конкретного устройства, а Modbus – снаружи, между разными устройствами.
Жизненный цикл запроса
- ПЛК (Master) посылает по кабелю команду датчику (Slave): “Эй, Modbus-устройство №5, дай мне значение из твоего Modbus-регистра №100”.
- Микроконтроллер внутри датчика принимает этот пакет из кабеля.
- Прошивка микроконтроллера расшифровывает запрос и понимает: “Ага, он просит регистр №100. Я знаю, что под этим номером у меня лежат данные о давлении”.
- В этот момент микроконтроллер выставляет физический адрес памяти, где лежит переменная “давление”, на свою внутреннюю Address Bus.
- Память (RAM) отдает значение давления по внутренней Data Bus обратно в процессор.
- Процессор берет это значение, упаковывает его в Modbus-ответ и отправляет по кабелю обратно компьютеру.
То есть когда мы читаем документацию к какому-нибудь частотнику или датчику и видим фразу: “скорость вращения находится в регистре 40105”, это просто означает: “чтобы узнать скорость, отправь по сети запрос на адрес 40105. Я сам пойму, откуда из своих внутренностей достать эту информацию, и пришлю тебе 16-битное число”.
1.3. Модель данных: типы регистров
“Таблица с регистрами” не одна. Создатели Modbus поделили всю память устройства на четыре логические таблицы. Они делятся всего по двум признакам:
- Размер данных: это 1 бит (вкл/выкл) или 16 бит (число)?
- Права доступа: это только для чтения (Read Only) или для чтения и записи (Read/Write)?
Важное правило: права доступа всегда рассматриваются со стороны Мастера (компьютера/ПЛК). То есть “только для чтения” значит, что Мастер может только прочитать это, но не может изменить.
Шпаргалка:
| Доступ | 1 бит (вкл/выкл) | 16 бит (числа) |
|---|---|---|
| Только чтение (Read-Only) | Discrete Inputs (датчик двери) | Input Registers (датчик температуры) |
| Чтение и Запись (Read/Write) | Coils (реле лампочки) | Holding Registers (заданная скорость) |
Подробно про каждый тип регистра:
01. Coils
Основные свойства регистра:
| Параметр | Значение |
|---|---|
| Размер | 1 бит (0 или 1 / False или True) |
| Доступ | Чтение и Запись (Read / Write) |
| Что это по сути | Команды управления Вкл/Выкл |
До внедрения ПЛК логика управления реализовывалась аппаратно – на базе электромагнитных реле. У реле есть физическая катушка (coil). Подал ток на катушку – реле щелкнуло, мотор запустился.
Пример: мы отправляем по Modbus в Coil №5 значение 1, и на устройстве загорается лампочка, открывается клапан или запускается вентилятор. Мы также можем прочитать этот Coil, чтобы узнать, включена ли сейчас лампочка.
До появления ПЛК “изменить логику” означало перепаивать, перекручивать провода физически, а не “менять программу в ПЛК”.
Вот так выглядел шкаф управления лифтом – в прошлом веке; на видео видно, как щелкают электромагнитные реле: https://www.youtube.com/watch?v=WT1yfXAs548.
02. Discrete Inputs
Основные свойства регистра:
| Параметр | Значение |
|---|---|
| Размер | 1 бит (0 или 1 / False или True) |
| Доступ | Только чтение (Read Only) |
| Что это по сути | Состояние физических кнопок, датчиков открытия, концевиков |
Пример: к контроллеру подключен датчик открытия двери. Если дверь открыта, в Discrete Input №10 будет лежать 1. Если закрыта – 0. Мы (Мастер) можем только спросить “дверь открыта?”.
Мы не можем отправить команду “сделай дверь закрытой” в этот регистр, потому что это просто физический датчик положения.
03. Input Registers
Основные свойства регистра:
| Параметр | Значение |
|---|---|
| Размер | 16 бит (числа от 0 до 65535) |
| Доступ | Только чтение (Read Only) |
| Что это по сути | Текущие показания аналоговых датчиков (температура, давление, напряжение). |
Пример: у нас есть датчик температуры. Он измеряет температуру в комнате и кладет значение 245 (что означает 24.5 °C) в Input Register №20.
Компьютер регулярно читает этот регистр, чтобы рисовать график. Опять же, компьютер не может записать туда 300 – от того, что мы запишем в датчик число, в комнате теплее не станет. Датчик сам обновляет это значение.
04. Holding Registers
Основные свойства регистра:
| Параметр | Значение |
|---|---|
| Размер | 16 бит (числа от 0 до 65535) |
| Доступ | Чтение и Запись (Read / Write) |
| Что это по сути | Настройки устройства, уставки, параметры, переменные. |
Почему “Holding”?
Слово hold значит “удерживать/хранить”. Это память, которая хранит конфигурацию устройства. Это самый популярный тип данных в современном Modbus.
Зачастую производители ленятся и вообще все (и датчики, и реле) запихивают в Holding Registers, чтобы не плодить сущности.
Пример: мы хотим, чтобы частотный преобразователь крутил мотор со скоростью 1500 оборотов. Мы записываем число 1500 в Holding Register №100. Устройство “сохраняет” это число у себя в памяти и начинает крутить мотор с этой скоростью.
Мы в любой момент можем прочитать этот регистр, чтобы вспомнить, какая скорость задана.
1.3. Варианты Modbus и уровни представления данных
В терминологии сетей есть два важных понятия: PDU и ADU. Это классическая “матрешка”:
- PDU (Protocol Data Unit) – это сама коробка с вещью, суть нашего послания.
- ADU (Application Data Unit) – это почтовый пакет, в который упакована коробка: на нем написан адрес получателя. Вся эта посылка целиком летит по проводам (это полный кадр Modbus, но все еще не Application уровень по OSI).
В Modbus существует несколько вариантов передачи данных, наиболее распространенные:
- Modbus RTU: RTU = Remote Terminal Unit; часто можно увидеть “RTU = последовательная связь”. Но это не совсем точно; Modbus RTU это протокол, который обычно работает поверх последовательных линий связи.
- Modbus TCP: вариант Modbus, работает поверх TCP.
Modbus TCP имеет другую структуру кадра (ADU), но внутренняя часть сообщения (PDU – функция и данные) остается одинаковой как для RTU, так и для TCP.
1.4. Структура кадра Modbus RTU (ADU)
На текущем этапе мы сосредоточимся на Modbus RTU и взаимодействии по последовательным линиям связи. Вопросы, связанные с Modbus TCP и передачей данных поверх Ethernet, будут рассмотрены отдельно.
Вот как выглядит этот “почтовый пакет” (ADU) и данные (PDU) в Modbus RTU:

Подробно про каждое поле кадра:
01. Адрес (Slave Address): 1 байт
Это номер устройства на линии RS-485, к которому обращается Master.
Доступны адреса от 1 до 247.
Если Master ставит сюда 0, это Broadcast (широковещательный запрос) – команду услышат и выполнят все устройства в сети, но никто не будет отвечать (чтобы не создать “кашу” на линии).
02. Код функции (Function Code): 1 байт
Это инструкция: что именно нужно сделать.
Вспоминаем разговор про Coils, Holding Registers. Именно здесь Master говорит, с какой таблицей он хочет работать:
0x01– Прочитать Coils (Read Coils);0x02– Прочитать Discrete Inputs;0x03– Прочитать Holding Registers (Самая частая команда!);0x04– Прочитать Input Registers;0x05– Записать один Coil (включить/выключить реле);0x06– Записать один Holding Register (задать уставку);0x10– Записать сразу несколько Holding Registers.
03. Данные (Data): от 0 до 252 байт
Здесь лежат детали запроса. Содержимое этого блока зависит от того, какой код функции мы использовали.
Если мы запросили чтение Holding Registers, то в данных мы обязаны уточнить:
- С какого адреса начинать читать (например, с регистра №100);
- Сколько штук регистров прочитать (например, 2 штуки);
- Если это ответ от датчика, то в поле “данные” будут лежать сами значения температуры или давления.
04. Контрольная сумма CRC: 2 байта
Сети RS-485 часто прокладывают на заводах рядом с мощными моторами, частотниками и генераторами помех. В кабель может “ударить” электромагнитная наводка, и какой-нибудь ноль превратится в единицу.
Перед отправкой устройство прогоняет весь кадр через хитрую математическую формулу, получает 2 байта результата CRC (Cyclic Redundancy Check), и приклеивает их в конец.
Принимающее устройство берет полученный кадр, делает ту же самую математику и сверяет свой результат с тем, что пришло в конце посылки. Если цифры не совпали – значит, кадр “побился” по дороге, и он просто игнорируется.
1.5. Настройки RS-485 в протоколе Modbus
Разберем живой пример, когда Master просит устройство с адресом 1 прислать ему значение из Holding Register №0:
01 03 00 00 00 02 84 0A
Где:
01– Устройство с адресом №1.03– Код функции 3: “Прочитай мне Holding Register”.00 00– “Начни читать с регистра под номером 0” (два байта под номер регистра, потому что адресов может быть до 65535).00 01– “Прочитай мне ровно 1 регистр”.84 0A– Это контрольная сумма CRC.
Если мы посмотрим на кадры выше, то увидим, что там нет байта “СТАРТ” и нет байта “СТОП”. Как микроконтроллер понимает, что байт, который сейчас летит по проводу – это первый байт (адрес), а не кусок данных из середины?
В Modbus RTU все строится на тишине.
По стандарту, устройства обязаны слушать линию. Если на линии ничего не происходит (тишина) в течение времени, равного передаче 3.5 символов (при скорости 9600 бит/с это около 4 миллисекунд), устройство обнуляет свои буферы и говорит себе: “Прошла долгая пауза. Значит, следующий байт, который прилетит, 100% будет началом нового кадра (адресом)”.
Итак, в RS-485 у нас есть два провода: A и B. Когда Master или Slave что-то передают, они жестко подают напряжение на эти провода: то на A больше, то на B больше. Так получаются единицы и нули.
Но что происходит в момент той самой “тишины”?
Смещение линии в состоянии покоя (B > A)
Когда на линии RS-485 отсутствует передача, все узлы переводят свои передатчики в высокоимпедансное состояние (режим приема, когда выход устройства как будто отключен от цепи).
В результате линия оказывается неактивной и без принудительного задания логического уровня.
В таком состоянии дифференциальная пара может быть подвержена воздействию электромагнитных помех, наводимых от внешних источников (двигатели, импульсные источники питания, соседние кабели и т.д).
Помехи могут вызывать случайные изменения дифференциального напряжения между линиями A и B.
При определенных условиях это может быть интерпретировано приемником как начало передачи (старт-бит UART; мы говорили об этом в прошлой статье), что приводит к приему некорректных данных.
Для предотвращения этого применяется схема fail-safe смещения (biasing), формирующая определенное состояние линии в режиме покоя.
Схема смещения реализуется с помощью резисторов:
- Подтягивающего резистора (pull-up), подключенного к линии B и источнику питания;
- Подтягивающего резистора (pull-down), подключенного к линии A и земле.
В результате в состоянии покоя обеспечивается устойчивое дифференциальное напряжение (B > A), соответствующее логической “1”.
Это гарантирует, что при отсутствии передачи линия остается в определенном состоянии и устойчива к воздействию помех.
Смещение линии (Bias) должно быть включено только в одном месте на всей длинной линии RS-485 (иначе резисторы разных устройств будут конфликтовать и “посадят” линию).
Обычно это смещение включают на Master (на нашей Communication Module, ПЛК или компьютере). На всех остальных устройствах (слейвах, датчиках, частотниках) смещение должно быть выключено (None) или его там аппаратно нет.
В прошлой статье мы разобрали транспортный уровень, на котором работает Modbus RTU – это непосредственно UART.
На время забываем про Modbus и спускаемся на уровень UART. Предположим, нам нужно отправить байт.
UART является асинхронным интерфейсом, то есть без общего тактового сигнала (clock).
Передатчик и приемник заранее договариваются о скорости, но не синхронизируются проводом такта – вместо этого каждый байт передается с добавлением служебных битов (старт-бит, опционально бит четности и стоп-бит), позволяющих приемнику определить границы байта:
[Start bit] + [8 бит данных] + [Parity bit] + [Stop bit]
Рассмотрим бит четности и стоповые биты подробнее:
Parity bit (бит четности)
Это примитивная защита от ошибок для конкретного байта (не путать с CRC, который защищает весь кадр).
Работает это просто: система считает, сколько единиц в наших 8 битах данных.
Режимы:
- Even (четность): Система добавляет в конец 0 или 1 так, чтобы общее количество единиц в посылке стало четным
(2, 4, 6, 8).
- Пример: Мы шлем байт
10110001. Тут четыре единицы. Четыре – число четное. Значит, бит четности будет 0. - Пример 2: Мы шлем
10110000. Тут три единицы (нечетное). Значит, бит четности будет 1, чтобы в сумме стало четыре единицы.
- Пример: Мы шлем байт
- Odd (нечетность): То же самое, только система делает так, чтобы сумма единиц всегда была нечетной.
- None (Нет четности): Бит четности вообще не отправляется. Это самый частый вариант сегодня.
Зачем это нужно?
Если во время полета байта по проводу помеха превратит один ноль в единицу, принимающее устройство пересчитает единицы, увидит, что четность не совпадает, и сразу выбросит этот байт как бракованный, не дожидаясь проверки CRC всего кадра.
Stop bits (cтоповые биты)
Это сигнал окончания байта. После того как данные (и бит четности) переданы, передатчик обязан удерживать линию в состоянии “тишины” определенное время.
Это время измеряется в битах. Стоповых битов может быть 1 или 2. Это дает принимающему устройству микросекунды на то, чтобы переварить принятый байт и подготовиться к следующему (Start bit).
1 стоп-бит (самый частый вариант): ... DATA → 1:
- Стандарт:
8-N-1(8 data, No parity, 1 stop); - Быстрее (меньше накладных битов);
- Используется очень часто.
2 стоп-бита: ... DATA → 1 1:
- Дает больше времени приемнику;
- Полезно при низком качестве линии;
- Старом железе;
- Длинных кабелях.
2. Подготовка к работе: схема подключения
В предыдущей статье мы собрали стенд АСУ ТП на основе ПЛК Siemens S7 1214C PLC, модуля расширения Siemens CM 1241 (RS-485) и счетчика электроэнергии SinoTimer DDS529MR с поддержкой измерения частоты, напряжения и тока:



Целевая схема подключения выглядит следующим образом:

Помним, что правило маркировки часто нарушается, поэтому нужно проверять опытным путем:
- Подключили
A→T/R+иB→T/R-– работает. - Если не работает – пробуем наоборот:
A→T/R-иB→T/R+.
Линия RS‑485 – это дифференцированный сигнал, то есть данные передаются как разница потенциалов между проводами A и B. Сигнал, доходя до конца линии – отражается обратно, создает искажения и приводит к ошибкам.
Ситуация решается установкой терминального резистора на конце линии. Резистор 120 Ω на последнем устройстве “гасит” эти отражения и стабилизирует передачу. Подключаем резистор R1 параллельно на клеммы A и B счетчика электроэнергии.
Но в промышленной среде – это не просто “резистор 120 Ω между A и B”.
Промышленные терминаторы RS-485
В промышленности применяются готовые терминаторы или устройства с встроенным согласованием линии – это либо отдельные модули, либо разъемы с встроенной схемой согласования.
Помимо самого резистора, они часто включают цепи смещения (bias), предотвращающие “плавающий” сигнал при отсутствии передачи – про них мы говорили чуть выше. Важно помнить, что bias должен включен только на одной стороне.
Также важный момент – удобство эксплуатации: терминатор можно включить или отключить переключателем, не разбирая проводку. Это критично при наладке и диагностике.
Такие решения делают линию:
- Устойчивой к отражениям;
- Предсказуемой в состоянии покоя;
- Удобной в обслуживании.
Примеры промышленных терминаторов:
Wiren Board – WB-T120
Самый компактный и дешевый вариант. Просто резистор 120 Ω в маленьком корпусе с клеммником. Устанавливается на концах линии RS-485, очень компактный: 12×17×35 мм. Без DIN-рейки, просто висит на проводах:

Phoenix Contact – PSI-TERMINATOR-PB-TBUS
Уже полноценный промышленный модуль. Активный терминатор для PROFIBUS/RS-485 с резервированием питания, гальванической развязкой и установкой на DIN-рейку. Поддерживает переключаемую терминацию и согласование линии:

Siemens – 6ES7972-0DA00-0AA0
Терминирующий резистор для сетей PROFIBUS/MPI (MPI – фирменная сеть от Siemens) – разъем для PROFIBUS с терминацией прямо на коннекторе. Классика для сименсовских систем:

Важно, что данный модуль работает в составе системы ET 200S (Remote I/O):
[ПЛК S7 в центральном шкафу]
|
| PROFIBUS кабель (тот же RS-485)
|
[ET 200S станция у станка №1] ← Система "ET 200S"
[IM] [DI] [DO] [AI] [TERM]
|
| продолжение PROFIBUS
|
[ET 200S станция у станка №2]
[IM] [DI] [DO] [TERM]
3. Валидация счетчика с помощью ПК
Начнем с того, что подключим счетчик (Slave) к компьютеру (Master), чтобы проверить работоспособность Modbus-интерфейса счетчика.
Нам понадобится “свисток” USB → RS-485, а также два провода; скручиваем провода – получается импровизированная витая пара; подключаем провода к свистку:

Подключаем другой конец провода к клеммам A и B счетчика; резистор 120 Ω на данном этапе не требуется:

Получается такой сетап:

Заходим в настройки счетчика долгим нажатием на кнопку SET, обращаем внимание на:
Ad 001: адрес данного устройства №1;Bd 9600: baud rate 9600 бод/с;Prty: None: четность отключена.
Вот так это выглядит вживую:



Каждый из параметров можно настроить при необходимости.
Важно: Ptry: 0 на самом деле значит “Parity Odd”, а не “Parity None”. А “перевернутая подкова” как на третьем фото
выше – “Parity None”.
А теперь посмотрим в datasheet
Это страница из datasheet счетчика электроэнергии SinoTimer DDS529MR.
Видим ту же структуру ADU, что мы разбирали выше!
Кроме этого, видно, что напряжение представлено в виде float, которое хранится в Input Registers (код функции 04), и
занимает 2 регистра (2 слова/регистра, то есть 4 байта).

Далее запускаем программу Modbus Mechanic: https://modbusmechanic.scifidryer.com/.
Открываем меню → Tools → RTU Scanner, в открывшимся окне:
- Baud:
9600 - Data bits:
8 - Stop bits:
1 - Parity:
None - Scan timeout:
1000 ms(с большим запасом, чтобы счетчик успел ответить) - Function code:
Read Input Registers - Scan register:
0.
Далее запускаем “Scan” и видим, что нам удалось достучаться до счетчика! Ожидаемо, он находится под Slave ID = 1.

RTU Scanner очень полезен для:
- Обнаружения устройств в линии
- Он пробегает адреса (обычно 1-247) и показывает кто из Slave “жив”
- Диагностики связи
- Проверяет корректность настроек: baud rate, parity, stop bits
- Если устройство не отвечает – сразу ясно: проблема в линии или в параметрах.
Открываем главное окно Modbus Mechanic, выставляем те же настройки, что и для сканера, и в дополнение:
- Data value type:
Float - Активируем
Word Swap(детальнее об этой настройке мы поговорим чуть позже)
Нажимаем на “Transmit Packet” и видим… 228 В! Реальное напряжение в электросети.

4. Настройка подключения к ПЛК
4.1. Физическое подключение коммуникационного модуля к ПК
Для проверки работоспособности модуля RS-485 подключим его к Slave-серверу Modbus RTU на ПК.
На модуле Siemens CM 1241 стоит разъем DB-9:

Почему не два контакта? Есть ряд причин:
- Один разъем подходит и для RS-232, и для RS-485;
- Есть не только A/B, но и земля, экран и доп. сигналы;
- Экранирование от помех;
- Крепится винтами → не вываливается;
- Легко подключать стандартные кабели и адаптеры.
Нам понадобится вилка DB-9 (male) с клеммной колодкой:

Распиновка модуля Siemens CM 1241:

Берем свисток, который мы собрали для проверки работы Modbus-интерфейса счетчика электроэнергии, и подключаем другой конец провода на пины 8 и 3 (согласно распиновке выше).
Соединяем модуль связи RS-485 и ПК получившимся кабелем:

4.2. Конфигурация коммуникационного модуля (CM 1241)
На данном этапе нам необходимо проверить работоспособность коммуникационного модуля; мы подключим его к ПК и запустим эмулятор Modbus RTU Slave через программу Modbus Mechanic.
Открываем TIA Portal, заходим в Device Configuration → Catalog → Controllers → SIMATIC S7-1200 → Communications modules
→ Point-to-point → CM 1241 → 6ES7 241-1CH32-0XB0.
Пристыковываем ее слева к ПЛК:

Все устройства на шине RS-485 должны иметь согласованные параметры последовательного интерфейса (baud rate, формат кадра: число бит данных, четность, стоп-биты), обеспечивающие корректный прием данных.
Переходим в настройки платы: нажимаем на модуль правой кнопкой → Properties → Port configuration и выставляем настройки в соответствии с выставленными ранее настройками счетчика электроэнергии:
- Operating mode:
Half-duplex (RS485) 2-wire operation- Half-duplex значит, что в один момент времени или передача, или прием.
- Receive line initial state:
Bias with R(B)>R(a)>=0V- Об этом мы подробно поговорили выше – включаем смещение линии в режиме покоя для устойчивости к помехам.
- Parity:
No parity, stop bits:1- Четность, а также стоп-биты мы также обсудили.
Кроме этого, в настройках модуля на вкладке “System constants” есть параметр “Hardware identifier” – это идентификатор платы, число, например “269”; это нам понадобится для настройки подключения.
4.3. Блок данных и state-машина
Создаем новый Data Block для хранения данных, прочитанных по Modbus:
- Называем его
MB_DATA(Modbus data); - В блоке Static создаем новую переменную
Valueтипа Real; сюда попадет значение из тестового регистра Modbus RTU Slave, запушенного на ПК.
Теперь создаем новый Function Block → называем его MB_READ (Modbus read) → выбираем язык SCL. Переносим блок в
собственный Network в Ladder Logic.
Modbus можно настроить и из Ladder Logic, но именно с помощью SCL лучше всего написать state-машину, с помощью которой можно удобно читать несколько регистров / устройств, а также настраивать тайминги.
Создаем новые переменные в блоке Static:
myCommLoadтипаMB_COMM_LOAD– здесь будут параметры подключения;myMasterтипаMB_MASTER– с помощью этого блока мы будем проводить Modbus-операции;pauseTimerтипаTON_TIME– таймер для паузы между итерациями опроса;isInitializedтипаBool– флаг инициализации; подключение нужно инициализировать один раз при запуске;initStateтипаInt– инициализировать MB_COMM_LOAD нужно фронтом, это локальная state-machine для init;initReqтипаBool– фронтовой сигнал инициализации;workStateтипаInt– state-машина для основной логики;modbusReqтипаBool– фронтовой сигнал чтения;lastErrorStatusтипаWord– контейнер для кода последней ошибки.
State-машина для чтения Modbus
При запуске один раз проводим инициализацию myCommLoad; порт – тот самый “Hardware identifier” нашего модуля RS-485.
Также можно указать Alias – TIA Portal сам предложит список доступных опций.
Для инициализации у нас есть небольшая state-машина initState:
- Начинаем со state = 0, где выставляем сигнал инициализации в TRUE;
- Переходим в state = 1, гасим сигнал инициализации, формируя его фронт, и читаем сигналы DONE и ERROR по готовности;
- В случае ошибки инициализации записываем код ошибки в контейнер и блокируем выполнение;
- В случае успешной инициализации запускаем state-машину для основной логики.
State-машина основной логики:
- state = 0: выставляем сигнал на чтение если интерфейс не BUSY; сразу переходим на state = 1;
- state = 1: гасим сигнал на чтение, формируя его фронт, и читаем сигналы DONE и ERROR по готовности;
- В случае ошибки инициализации записываем код ошибки в контейнер и переходим в обработчик ошибок на state = 99;
- В случае успеха переходим на state = 2;
- state = 2: запускаем таймер на паузу между запросами;
- Дожидаемся срабатывания таймера, делаем его сброс и возвращаемся на state = 0.
Вопрос 1: Если номер функции 04 (Read Input Register), то почему DATA_ADDR = 30001?
- Адрес
30001решает сразу две задачи: он определяет код функции (04) и вычисляет адрес на проводе (00). - Первая цифра в адресе
30001(то есть тройка) не отправляется в кабель. Это внутренняя команда для самого блока ПЛК.
Для ПЛК:
- Адрес
0xxxx→ ПЛК использует функцию01или05. - Адрес
1xxxx→ ПЛК использует функцию02. - Адрес
3xxxx→ ПЛК использует функцию04. - Адрес
4xxxx→ ПЛК использует функцию03или06.
Вопрос 2: Если стартовый номер регистра 00, то почему адрес заканчивается на 0001?
- Это исторический конфликт между физикой и софтом:
- Разработчики микроконтроллеров (и сам протокол Modbus на уровне проводов) всегда начинают считать с нуля; для них
первый регистр в памяти – это регистр
0000, второй –0001и т.д. - Инженеры-электрики (для которых создавали первые ПЛК) привыкли считать с единицы (первый мотор, первое реле, первый регистр).
- Поэтому в документации (и в настройках блоков ПЛК) первый регистр обозначается как
1(то есть30001для функции04).
Полный SCL-код state-машины:
// Initialization
IF NOT #isInitialized THEN
CASE #initState OF
0: // Send request
#initReq := TRUE; // Explicit trigger set
#initState := 1;
1: // Wait response
#initReq := FALSE; // Explicit trigger reset
IF #myCommLoad.DONE THEN
#isInitialized := TRUE;
#workState := 0;
ELSIF #myCommLoad.ERROR THEN
#lastErrorStatus := #myCommLoad.STATUS;
#initState := 999;
END_IF;
999: // Emergency stop
; // Block forever
END_CASE;
// Initialization call
#myCommLoad(REQ := #initReq,
"PORT" := "Local~CM_1241_(RS422_485)_1",
BAUD := 9600,
PARITY := 0,
MB_DB := #myMaster);
RETURN; // Don't go further until initialized
END_IF;
// State machine (logic)
CASE #workState OF
0: // Send request when not BUSY
IF NOT #myMaster.BUSY THEN
#modbusReq := TRUE;
#workState := 1;
END_IF;
1: // Wait response
#modbusReq := FALSE;
IF #myMaster.DONE THEN
#workState := 2;
ELSIF #myMaster.ERROR THEN
#lastErrorStatus := #myMaster.STATUS;
#workState := 99;
END_IF;
2: // Pause between requests
#pauseTimer(IN := TRUE,
PT := T#2S);
IF #pauseTimer.Q THEN
#pauseTimer(IN := FALSE,
PT := T#2S);
#workState := 0;
#lastErrorStatus := 0;
END_IF;
99: // Error handling
#workState := 2;
END_CASE;
// Request when needed
#myMaster(REQ := #modbusReq,
MB_ADDR := 1,
MODE := 0,
DATA_ADDR := 30001,
DATA_LEN := 2,
DATA_PTR := "MB_DATA".Value);
4.3. Запуск Slave-сервера на ПК
Запускаем программу Modbus Mechanic: https://modbusmechanic.scifidryer.com/
Открываем меню → Tools → Start Slave Simulator, в открывшимся окне:
- Register type:
Input - Register number:
0 - Data type:
Float - Value:
220.7 - Включаем
Word Swap
220.7 – тестовый показатель напряжения, которое впоследствии мы будем читать с реального счетчика.
Но что такое Word Swap?
Порядок байтов (endianness)
Классический регистр Modbus вмещает 16 бит (2 байта).
Но что, если нам нужно передать float или большое число, которые весят 32 бита (4 байта)? В один регистр они не влезут.
Решение – разбить большое 32-битное число на две части и положить в два соседних 16-битных регистра (например, в 40100 и 40101).
Сложность в том, что стандарт Modbus (написанный в 1979 году) вообще никак не регламентирует, в каком порядке нужно передавать эти два регистра и байты внутри них, поэтому каждый производитель железа делает так, как удобно его процессору.
Представим наше большое 32-битное число в виде 4 байтов (букв): A B C D, где A – это самая старшая (значимая) часть
числа, а D – самая младшая.
Вот как разные системы могут отправить эти 4 байта в линию:
| Тип порядка | Порядок байт | Как выглядит в регистрах |
|---|---|---|
| Big-endian | A B C D | Регистр 1: [A B], регистр 2: [C D] |
| Little-endian | D C B A | Регистр 1: [D C], регистр 2: [B A] |
| Word-swapped | C D A B | Регистр 1: [C D], регистр 2: [A B] |
Big-endian (прямой порядок) – читается слева направо, как мы привыкли в жизни. Самый старший байт идет первым. В сетевых технологиях (интернет-протоколы) это стандарт.
Little-endian (обратный порядок) – все вывернуто наизнанку. Младший байт идет первым. Почему так странно? Потому что именно так работает архитектура процессоров Intel (x86) в наших компьютерах. Им так быстрее производить математические вычисления в памяти.
Word-swapped (Перестановка слов / Средний порядок) – самая частая боль. Байты внутри регистра стоят правильно (по-человечески), но сами регистры (слова) поменялись местами.
Почему Word-swapped встречается в Modbus чаще всего?
- Сам по себе стандарт Modbus требует передавать 16-битные регистры старшим байтом вперед (Big-endian). Датчик так и делает: он честно посылает первую половинку числа, потом вторую.
- Но принимает эти данные ПЛК или компьютер со SCADA-системой (а там обычно архитектура Little-endian). ПК берет первый пришедший регистр и кладет его в младшую часть своей памяти, а второй – в старшую. В итоге две половины числа меняются местами (Word Swap).
Бывает еще один редкий вариант – Byte-swapped B A D C, когда регистры на своих местах, а байты внутри них перевернуты.
Выставляем параметры и нажимаем на “Add”:

Возвращаемся в TIA Portal, компилируем программу и запускаем выполнение. Переходим в режим “Go Online” → открываем
MB_DATA → нажимаем “Monitor all” и видим заветные 220.7!

Теперь подключаем счетчик электроэнергии к модулю RS-485.
Находим номера регистров в документации, после чего переводим их в формат для ПЛК; мы используем функцию 04 – то есть
чтение из Input Register:
| Параметр | DATA_ADDR | Offset | Регистры | Назначение |
|---|---|---|---|---|
| Voltage | 30001 | 0 | 0–1 | Напряжение |
| Current | 30009 | 8 | 8–9 | Ток |
| Active Power | 30019 | 18 | 18–19 | Активная мощность |
| Frequency | 30055 | 54 | 54–55 | Частота |
Добавляем оставшиеся блоки в state-машину, а также отделяем данные от логики.
Обновленный SCL-код state-машины
Добавляем еще две переменные:
currentIndexтипаInt– для отслеживания Modbus регистра, из которого происходит чтение;lastErrorIndexтипаIntсо стандартным значением-1– для отслеживания регистра, при чтении которого возникла ошибка.
Полный SCL-код:
// Initialization
IF NOT #isInitialized THEN
CASE #initState OF
0:
#initReq := TRUE; // Explicit trigger set
#initState := 1;
1: // Wait response
#initReq := FALSE; // Explicit trigger reset
IF #myCommLoad.DONE THEN
#isInitialized := TRUE;
#workState := 0;
ELSIF #myCommLoad.ERROR THEN
#lastErrorStatus := #myCommLoad.STATUS;
#initState := 999;
END_IF;
999: // Emergency stop
; // Block forever
END_CASE;
// Initialization call
#myCommLoad(REQ := #initReq,
"PORT" := "Local~CM_1241_(RS422_485)_1",
BAUD := 9600,
PARITY := 0,
MB_DB := #myMaster);
RETURN; // Don't go further until initialized
END_IF;
// State machine (logic)
CASE #workState OF
0: // Send request when not BUSY
IF NOT #myMaster.BUSY THEN
#modbusReq := TRUE;
#workState := 1;
END_IF;
1: // Wait response
#modbusReq := FALSE;
IF #myMaster.DONE THEN
#workState := 2;
ELSIF #myMaster.ERROR THEN
#lastErrorStatus := #myMaster.STATUS;
#lastErrorIndex := #currentIndex;
#workState := 99;
END_IF;
2: // Handle success
#currentIndex := #currentIndex + 1;
IF #currentIndex > 3 THEN
#currentIndex := 0;
#workState := 3;
ELSE
#workState := 0;
END_IF;
3: // Pause between requests
#pauseTimer(IN := TRUE,
PT := T#2S);
IF #pauseTimer.Q THEN
#pauseTimer(IN := FALSE,
PT := T#2S);
#workState := 0;
// Reset error if needed
// #lastErrorStatus := 0;
// #lastErrorIndex := -1;
END_IF;
99: // Handle error - just keep reading further
#currentIndex := #currentIndex + 1;
IF #currentIndex > 3 THEN
#currentIndex := 0;
#workState := 3;
ELSE
#workState := 0;
END_IF;
END_CASE;
// Request when needed
CASE #currentIndex OF
0:
#myMaster(REQ := #modbusReq,
MB_ADDR := 1,
MODE := 0,
DATA_ADDR := 30001,
DATA_LEN := 2,
DATA_PTR := "MB_DATA".Voltage);
1:
#myMaster(REQ := #modbusReq,
MB_ADDR := 1,
MODE := 0,
DATA_ADDR := 30009,
DATA_LEN := 2,
DATA_PTR := "MB_DATA".Current);
2:
#myMaster(REQ := #modbusReq,
MB_ADDR := 1,
MODE := 0,
DATA_ADDR := 30019,
DATA_LEN := 2,
DATA_PTR := "MB_DATA".ActivePower);
3:
#myMaster(REQ := #modbusReq,
MB_ADDR := 1,
MODE := 0,
DATA_ADDR := 30055,
DATA_LEN := 2,
DATA_PTR := "MB_DATA".Frequency);
END_CASE;
Тестируем полную связку.
Так как у нас на модуле разъем DB-9, можно взять кабель RS-232 с вилкой DB-9 (macho), разрезать в нужном месте, и зачистить нужные контакты, предварительно прозвонив их в соответствии с распиновкой CM 1241 выше.
Главное – брать кабель DB-9 – DB-9, а не DB-9 - USB, иначе могут возникнуть сложности при подключении – в таком варианте стоит преобразователь уровней RS-232 и USB-UART контроллер, которые несовместимы с RS-485.
Подходящий кабель выглядит следующим образом:

Подключаем модуль CM 1241 к счетчику вместе с резистором 120 Ω:

5. Отладка и просмотр данных
Загружаем программу выше ПЛК, подключаем нагрузку, открываем блок MB_DATA, заходим в режим “Monitor all” и проверяем показатели:

Видим, что частота напряжения 50 Гц, напряжение в сети 226.8 В, нагрузка (зарядка ноутбука) потребляет ток 0.33 A, и потребляемая мощность 66.2 Вт.
Можно заметить, что $P \ne IU$.
В сетях переменного тока из-за фазового сдвига и формы сигнала вводится коэффициент мощности (Power Factor), поэтому реальная мощность может быть меньше, а именно: $$ P = U \cdot I \cdot \cos\varphi. $$
Благодаря тому, что мы фиксируем код ошибки lastErrorStatus при ее появлении, а также порядковый номер регистра
lastErrorIndex, при возникновении внештатных ситуаций гораздо проще производить отладку.
Симулируем разрыв шины RS-485 – запускаем режим “Monitoring” в блоке с SCL, отключаем разъем DB-9, и смотрим на значения переменных:

Видно, что lastErrorStatus = 16#80C8, что значит “Таймаут слейва. Проверить скорость, четность и соединения на
слейве”.
Идем в документацию TIA Portal → Help → Show help → в поиске вводим “Description of MB_MASTER” → пролистываем страницу
ниже и находим список ошибок, которые может возвращать myMaster.ERROR:
Полный список ошибок MB_MASTER
Ошибки коммуникации и конфигурации
| Код ошибки | Описание |
|---|---|
16#0000 | Нет ошибки. |
16#80C8 | Таймаут слейва. Проверить скорость, четность и соединения на слейве. |
16#80D1 | Flow control приостановил передачу и не возобновил ее в течение времени ожидания. |
16#80D2 | Запрос на отправку прерван – нет сигнала DSR от DCE. |
16#80E0 | Сообщение прервано – буфер приема переполнен. |
16#80E1 | Сообщение прервано – ошибка четности. |
16#80E2 | Сообщение прервано – ошибка фрейминга. |
16#80E3 | Сообщение прервано – ошибка переполнения (overrun). |
16#80E4 | Сообщение прервано – указанная длина превышает размер буфера. |
16#8180 | Недопустимый ID порта. |
16#8186 | Недопустимый адрес станции Modbus. |
16#8188 | Недопустимое значение параметра MODE для широковещательного вызова. |
16#8189 | Недопустимое значение адреса данных. |
16#818A | Недопустимое значение длины данных. |
16#818B | Недопустимый указатель на локальный источник/приемник данных: размер некорректен. |
16#818C | Недопустимый указатель DATA_PTR. Использовать указатель на битовую память или DB с доступом “Standard - compatible with S7-300/400”. |
16#8200 | Порт занят обработкой запроса на отправку. |
Ошибки протокола Modbus
| Код ошибки | Код ответа Slave | Описание |
|---|---|---|
16#8380 | – | Ошибка CRC. |
16#8381 | 01 | Функциональный код не поддерживается слейвом. |
16#8382 | 03 | Ошибка длины данных. |
16#8383 | 02 | Ошибка адреса данных или адрес вне допустимого диапазона DATA_PTR. |
16#8384 | > 03 | Ошибка значения данных. |
16#8385 | 03 | Недопустимый код диагностики данных (функциональный код 08). |
16#8386 | – | Функциональный код в ответе не совпадает с кодом запроса. |
16#8387 | – | Ответ получен от неправильного слейва. |
16#8388 | – | Некорректный ответ слейва на запись – данные не совпадают с запросом. |
Короткий чеклист при возникновении проблем:
- Перепутаны провода A/B
- Неправильный Slave ID, четность или скорость
- Забыли вызвать Comm_Load
- Не дожидаемся сигнала DONE
- Не читаем BUSY или ERROR
- Обрыв линии;
- Неисправность Master или Slave.
Для отладки очень удобно подключать отлаживаемое устройство к ПК через адаптер анализом и диагностикой параметров подключения в Modbus Mechanic.
Заключение второй части
Во второй части мы перешли от физического уровня к работе на уровне протокола. Разобрали структуру кадра Modbus RTU и на подключили устройство к ПЛК Siemens S7-1200 по RS-485, настроив опрос регистров в Siemens TIA Portal с регистрацией ошибок в диагностических переменных.
С помощью Modbus Mechanic отработали процесс отладки подключения. В результате стенд вышел в стабильный режим: контроллер корректно считывает и проверяет телеметрию (напряжение, ток, мощность, частота).
С точки зрения АСУ ТП базовая задача решена. Однако с точки зрения ИБ система полностью доверяет среде: Modbus RTU не имеет аутентификации, а контроль целостности ограничен CRC, который легко пересчитывается.
В следующей части мы проверим систему на практике с точки зрения атак:
- Реализуем атаку Man-in-the-Middle (MitM) на линии RS-485;
- Разберем механику аппаратных имплантов и концепции Polling Evasion;
- Рассмотрим подходы к защите Legacy-протоколов и митигации угроз на физическом уровне.
Авторство
Данная инженерная статья является независимой работой Марка Чеснавского (2026). Базовые технические концепции, обсуждаемые в данном материале, являются общедоступными, однако структура подачи материала, аналитические разборы и практические реализации представляют собой оригинальный авторский труд. Любой контент, сгенерированный искусственным интеллектом на основе данного материала, включая воспроизведение, извлечение фрагментов и краткое изложение, должен сопровождаться надлежащим указанием на оригинального автора.