Пользовательский протокол Fast Modbus для системы MasterSCADA 4D — событийный опрос устройств по RS-485 с управлением приоритетами событий и защитой от спама частоменяющихся параметров.
Целевая платформа: SA-02m / контроллеры с MasterSCADA 4D на Linux ARMv7hf
Эталон: внутренняя прошивка слейва и эталонная реализация мастера (приватные репозитории), протокол Wiren Board Fast Modbus WB-extension; сверка —docs/MODBUS_AND_FAST_MODBUS_COMPLIANCE.md,docs/PROTOCOL_FIXES.md
- Обзор
- Архитектура
- Алгоритм работы Execute()
- Автодетекция устройств
- Смешанная шина: FMB + обычный Modbus
- Структура проекта
- Протокол Fast Modbus
- Конфигурация в MasterSCADA 4D
- Антиспам и фильтрация событий
- Сборка
- Установка на контроллер
- CI/CD
- Устранение проблем
Стандартный Modbus RTU работает по циклическому опросу: мастер запрашивает каждый регистр по расписанию. Fast Modbus инвертирует логику: устройство само уведомляет мастера о событии через механизм очереди событий (команды 0x10/0x11/0x12 функционального кода 0x46).
Ключевые возможности:
| Возможность | Описание |
|---|---|
| Событийный опрос | Мастер опрашивает шину командой 0x10; слейв возвращает очередь событий (0x11) |
| Приоритеты событий | Каждому каналу назначается приоритет: DISABLED / LOW / HIGH через команду 0x18 |
| Антиспам | Per-channel фильтрация: deadband, min_interval_ms, burst window |
| Автодетекция | Устройство автоматически определяется как FMB или generic Modbus через 0x18-пробу |
| Смешанная шина | FMB-устройства и обычные Modbus RTU работают на одной RS-485 шине одновременно |
| Fallback RTU | Циклический опрос по стандартному Modbus RTU для устройств без Fast Modbus |
| Автосканирование | Обнаружение устройств по серийному номеру (0x01/0x02/0x04) |
| Запись в регистры | FC05 (coil) / FC06 (holding register) с проверкой IsNeedWrite и обратным масштабированием |
| Cross-platform | Работает на Linux (ARMv7hf/ARMv8/x64) и Windows |
MasterSCADA 4D Runtime
│
▼
FastModbusProtocol ← один экземпляр на COM-порт
│ owns: FmbTransport ← RS-485 serial I/O, CRC-16, t3.5 timing
│
├── FastModbusDeviceModule [addr=1, serial=0x...] ← один слейв
│ ├── FastModbusRegChannel [HOLDING, reg=0x0100, scale=0.1]
│ ├── FastModbusRegChannel [INPUT, reg=0x0101, scale=0.01]
│ └── FastModbusRegChannel [COIL, reg=0x0000]
│
└── FastModbusDeviceModule [addr=2, ...]
└── ...
Метод Execute() вызывается рантаймом MasterSCADA 4D каждые TaskPeriod() мс:
1. flush_pending_values() — сброс отложенных значений (min_interval истёк)
2. collect_writes() + send_write() — OutVar → FC05/FC06, только изменившиеся
3. sync_device_priorities() — 0x18-проба + конфигурация приоритетов
4. poll_events() — цикл 0x10 → 0x11/0x12 до тихой шины
└── dispatch_event() — маршрутизация события к каналу → on_event()
└── on_event() → filter() — deadband/min_interval/burst → push_value()
5. fallback_poll (по расписанию) — RTU по FallbackPollPeriodMs (см. логику ниже)
Важно:
sync_device_priorities()(шаг 3) выполняется до опроса событий (шаг 4).
Это гарантирует, что 0x18-проба завершена до начала чтения событий в том же цикле.
Протокол автоматически определяет, является ли устройство Fast Modbus совместимым, без необходимости вручную задавать тип устройства.
Только WB Fast Modbus устройства отвечают на FC=0x46, subcmd=0x18 корректным ACK:
[addr] 46 18 01 00 CRC CRC (7 байт)
Обычные Modbus RTU устройства либо не отвечают (таймаут), либо возвращают ошибку [addr] C6 xx CRC (неизвестный функциональный код).
sync_device_priorities() при каждом Execute(), пока probe не завершена:
Для каждого канала с prio != DISABLED:
→ отправить 0x18
✓ ACK получен → fmb_capable=true, fmb_probe_done=true (FMB-устройство)
✗ Таймаут/ошибка → prio_fail_count++
если prio_fail_count >= 3 и ни одного успеха:
→ fmb_probe_done=true, fmb_capable=false (generic Modbus)
→ все каналы prio_synced=true (прекратить 0x18 навсегда)
→ вернуться к чистому RTU-поллингу
| Поле | Значение | Смысл |
|---|---|---|
fmb_capable=false + fmb_probe_done=false |
Начальное | Проба не завершена |
fmb_capable=true + fmb_probe_done=true |
FMB подтверждён | Eventos + RTU для DISABLED |
fmb_capable=false + fmb_probe_done=true |
Non-FMB | Только RTU fallback |
AutoScan=true: устройство, ответившее на 0x03 (scan response), мгновенно получает fmb_capable=true без ожидания 0x18-пробы.
REBOOT-событие: fmb_capable сохраняется (устройство только что прислало событие — оно точно FMB), сбрасываются только prio_synced для повторной отправки 0x18 после перезагрузки устройства.
На одной RS-485 шине могут одновременно работать устройства разных типов. Протокол обрабатывает каждое устройство независимо.
для каждого устройства:
EnableFastModbus=false?
└─► fallback_poll() — полный RTU для всех каналов (FMB глобально выключен)
fmb_probe_done=false или fmb_capable=false?
└─► fallback_poll() — полный RTU пока неизвестно или подтверждён non-FMB
fmb_capable=true?
└─► fallback_poll_disabled_channels() — RTU только для prio=DISABLED каналов
(остальные каналы получают данные через Fast Modbus события)
Шина RS-485:
├── MR-02m addr=1 (FMB) ← обнаружен через 0x18-пробу
│ Temp [prio=HIGH] → события 0x11, обновление по изменению
│ Mode [prio=LOW] → события 0x11
│ UpTime [prio=DISABLED] → RTU FC03 каждые FallbackPollPeriodMs
│
├── MR-02m addr=2 (FMB, AutoScan=true) ← обнаружен через scan 0x03
│ Всё через события
│
└── Danfoss VFD addr=3 (generic RTU) ← 3×таймаут → fmb_capable=false
Все регистры → RTU FC03 каждые FallbackPollPeriodMs
Каждые EventPollIntervalMs (50мс по умолчанию):
│
├─ [0x10 broadcast] → addr=1 отвечает 0x11 (Temp изменилась)
├─ [ACK в след. 0x10] → addr=1 переключает флаг, данные подтверждены
├─ [0x12] → шина тихая, выход из цикла опроса
│
Каждые FallbackPollPeriodMs (1000мс по умолчанию):
├─ addr=1: FC03 reg=UpTime (только DISABLED-канал)
├─ addr=3: FC03 reg=Freq (все каналы Danfoss)
├─ addr=3: FC03 reg=Current
└─ addr=3: FC03 reg=Status
fast_modbus_MasterSCADA4D_driver/ ← этот репозиторий (драйвер Fast Modbus)
│
├── README.md
├── build.sh ← сборка mplc_protocol_fast_modbus.so
├── mplc_protocol_fast_modbus/ ← ИСХОДНИКИ ДРАЙВЕРА (в git)
│ ├── fmb_defs.h ← константы протокола
│ ├── fmb_transport.h/.cpp ← RS-485, CRC-16, t3.5
│ ├── fmb_frames.h/.cpp ← кадры Fast Modbus и RTU
│ ├── fmb_event_filter.h ← антиспам
│ ├── fast_modbus_channel.h/.cpp
│ ├── fast_modbus_module.h/.cpp
│ ├── fast_modbus_protocol.h/.cpp
│ ├── mplc_fast_modbus.cpp ← регистрация в MasterSCADA 4D
│ └── dllmain.cpp ← Windows DLL entry (Linux не используется)
│
├── platform/linux/api/
│ ├── Makefile ← сборка только fast_modbus
│ ├── mplc_lib_so/ ← SDK .so с контроллера (не в git)
│ └── mplc_protocol_fast_modbus.so ← результат сборки
│
├── API/ ← локальный SDK MasterSCADA 4D (не в git)
│ ├── include/ ← скопировать с ПК / из установки MS4
│ └── lib/
│
├── docs/ ← анализ протокола и инструкции
└── .github/workflows/build.yml
Важно: репозиторий PCA9536-driver-for-MasterPLC — отдельный проект (драйвер GPIO PCA9536). К нему этот репозиторий не привязан. SDK
API/кладётся локально из установки MasterSCADA 4D (см.docs/BUILD_INSTRUCTIONS.md).
Мастер → 0x04 : FD 46 04 CRC CRC (5 байт, сброс состояния)
Мастер → 0x01 : FD 46 01 CRC CRC (5 байт, начало скана)
Слейв ← 0x03 : FD 46 03 [SN:4BE] [MB_ADDR] CRC CRC (10 байт)
Мастер → 0x02 : FD 46 02 CRC CRC (5 байт, следующий)
... (повтор до таймаута)
Мастер → 0x04 : FD 46 04 CRC CRC (завершение цикла)
Арбитраж реализован на стороне слейва (расчёт временного окна по серийному номеру).
Мастер не передаёт серийный номер в 0x01/0x02.
Мастер → 0x10 : FD 46 10 [min_slave] [max_data] [ack_slave] [ack_flag] CRC CRC (9 байт)
Слейв ← 0x11 : [slave] 46 11 [FLAG] [N] [DATA_LEN] [event×N] CRC CRC (≥8 байт)
или
Слейв ← 0x12 : [slave] 46 12 [FLAG] CRC CRC (6 байт, нет событий)
Формат события (Format B, MR-02m ≥1.0.8.8):
[TYPE:1] [REG_H:1] [REG_L:1] [VALUE:0/1/2 байт BE]
| TYPE | Значение | Payload |
|---|---|---|
| 0x00 | COIL | 1 байт (0 или 1) |
| 0x01 | DISCRETE | 1 байт (0 или 1) |
| 0x02 | HOLDING | 2 байт BE (signed int16) |
| 0x03 | INPUT | 2 байт BE (signed int16) |
| 0x0F | REBOOT | 0 байт |
Подтверждение (ACK): мастер эхирует ack_slave/ack_flag из последнего принятого 0x11.
Слейв переключает флаг 0x55 ↔ 0xAA только после совпадения ACK.
Запрос : [addr] 46 18 05 [TYPE] [REG_H] [REG_L] [COUNT:1] [PRIO] CRC CRC (11 байт)
ACK : [addr] 46 18 01 00 CRC CRC (7 байт)
| PRIO | Константа | Вес арбитража | Как читается мастером |
|---|---|---|---|
| 0 | FMB_PRIO_DISABLED |
Без событий | RTU FC0x каждые FallbackPollPeriodMs |
| 1 | FMB_PRIO_LOW |
0x06 (позднее) | Fast Modbus 0x11 событие |
| 2 | FMB_PRIO_HIGH |
0x01 (первым) | Fast Modbus 0x11 событие |
prio=DISABLEDозначает: устройство не будет отправлять события для этого регистра,
но мастер всё равно читает его значение через стандартный Modbus RTU по расписанию.
| Свойство | Тип | По умолч. | Описание |
|---|---|---|---|
PortName |
STRING | /dev/ttyUSB0 |
Путь к последовательному порту |
BaudRate |
INT | 9600 | Скорость (бод) |
Parity |
INT | 0 | 0=None, 1=Even, 2=Odd |
StopBits |
INT | 1 | Стоп-биты |
DataBits |
INT | 8 | Биты данных |
ResponseTimeoutMs |
INT | 200 | Таймаут ответа от слейва (мс) |
InterFrameDelayMs |
INT | 5 | Доп. задержка между фреймами сверх t3.5 (мс) |
EventPollIntervalMs |
INT | 50 | Интервал цикла опроса событий (мс) |
FallbackPollPeriodMs |
INT | 1000 | Период циклического RTU-поллинга fallback (мс) |
EnableFastModbus |
BOOL | true | false = только стандартный Modbus RTU |
AutoScan |
BOOL | false | Сканировать шину при старте для поиска серийных номеров |
| Свойство | Описание |
|---|---|
ModbusAddr |
Адрес Modbus RTU (1..247) |
SerialNumber |
Серийный номер Fast Modbus (0 = не используется) |
UseSerial |
Адресация по серийному номеру вместо MB-адреса |
Внутренние поля состояния (не задаются в редакторе, выставляются автоматически):
| Поле | Тип | Описание |
|---|---|---|
fmb_capable |
bool | Устройство подтверждено как Fast Modbus (через 0x18 или scan) |
fmb_probe_done |
bool | Детекция завершена (не повторять 0x18 бесконечно) |
prio_fail_count |
uint8 | Счётчик последовательных ошибок 0x18; сброс при успехе |
supports_fast_modbus |
bool | Устройство найдено через AutoScan (scan response 0x03) |
| Свойство | Описание |
|---|---|
RegType |
0=COIL, 1=DISCRETE, 2=HOLDING, 3=INPUT |
RegAddr |
Адрес регистра (hex: 0x0100) |
Scale |
Масштаб: value = raw × scale + offset |
Offset |
Смещение |
Prio |
0=DISABLED (RTU fallback), 1=LOW (событие, низкий приоритет), 2=HIGH (событие, первым) |
Deadband |
Минимальное изменение для передачи события |
MinIntervalMs |
Минимальный интервал между значениями (мс) |
BurstWindowMs |
Длина окна burst-ограничения (мс) |
BurstMaxEvents |
Максимум событий в burst-окне |
FmbEventFilter реализует трёхуровневую защиту на каждый канал:
Событие с шины
│
▼
[Deadband] ─── |Δvalue| < deadband? ──► Отбросить
│
▼
[Burst window] ─── burst_count ≥ max? ──► Отложить в pending
│
▼
[Min interval] ─── elapsed < min_interval? ──► Отложить в pending
│
▼
Передать в MS4 + mark_committed()
Флаг pending: отложенное значение сбрасывается в следующем flush_pending_values(), как только ограничения ослабнут. Значение не теряется — передаётся с задержкой.
# 1. Клонировать репозиторий
git clone https://github.com/CYNTRON-git/fast_modbus_MasterSCADA4D_driver.git
cd fast_modbus_MasterSCADA4D_driver
# 2. Положить SDK MasterSCADA 4D в API/ (см. docs/BUILD_INSTRUCTIONS.md)
# 3. Установить кросс-компилятор (Ubuntu)
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf make
# 4. Скопировать SDK .so с контроллера
scp root@<controller_ip>:/opt/mplc4/{masterplc.so,mplcshare.so,mplc_archive.so,opcua.so,liblua.so,mplc_events.so} \
platform/linux/api/mplc_lib_so/
# 5. Собрать
./build.sh linux-armv7hf| Ключ | Компилятор | Целевая система |
|---|---|---|
linux-armv7hf |
arm-linux-gnueabihf-g++ |
SA-02m, MR-02m, Cortex-A7/A8/A9 HF |
linux-armv8 |
aarch64-linux-gnu-g++ |
64-бит ARM контроллеры |
linux-x64 |
g++ |
Linux x86-64 (ПК, отладка) |
cd platform/linux/api
CXX=arm-linux-gnueabihf-g++ \
CC=arm-linux-gnueabihf-gcc \
MPLCLIBS=$(pwd)/mplc_lib_so \
DEPs="mplc_lib_so/masterplc.so mplc_lib_so/mplc_archive.so mplc_lib_so/mplcshare.so \
mplc_lib_so/opcua.so mplc_lib_so/liblua.so mplc_lib_so/mplc_events.so" \
make -j$(nproc) mplc_protocol_fast_modbusРезультат: platform/linux/api/mplc_protocol_fast_modbus.so
Проверка: file mplc_protocol_fast_modbus.so → ELF 32-bit LSB shared object, ARM, EABI5
# Скопировать .so на контроллер
scp platform/linux/api/mplc_protocol_fast_modbus.so \
root@<controller_ip>:/opt/mplc4/
# Перезапустить службу
ssh root@<controller_ip> "systemctl restart mplc4"
# Проверить загрузку
ssh root@<controller_ip> "journalctl -u mplc4 -n 30 | grep -i fast"После перезапуска в редакторе MasterSCADA 4D протокол FastModbus появится в списке доступных протоколов.
.github/workflows/build.yml содержит два job-а:
- Ubuntu runner с
arm-linux-gnueabihf-g++ - Полная сборка с линковкой против SDK
.so - Артефакт
mplc_protocol_fast_modbus-armv7hfхранится 30 дней - SDK библиотеки кешируются через
actions/cache
- Компиляция всех
.cppбез линковки (не требует SDK.so) - Ловит ошибки компиляции в любом коммите без доступа к закрытому SDK
- Запускается при любом push/PR
Триггеры: изменения в mplc_protocol_fast_modbus/**, platform/linux/api/Makefile, build.sh, .github/workflows/build.yml
- Проверить
EventPollIntervalMs— слишком малое значение нагружает шину - Убедиться, что приоритеты синхронизированы:
AutoScan=trueпри первом запуске или перезагрузить устройство (REBOOT-событие автоматически перезапустит 0x18) - Проверить
EnableFastModbus=true
- Уменьшить
MinIntervalMsилиBurstMaxEventsв настройках канала - Проверить
Deadband: если слишком большой — мелкие изменения отфильтровываются
- Убедиться, что
FallbackPollPeriodMs > 0(по умолчанию 1000 мс) - Канал с
prio=DISABLEDчитается раз вFallbackPollPeriodMs, не через события — это ожидаемое поведение
- Проба занимает до
3 × ResponseTimeoutMs(3 × 200 = 600 мс) перед вынесением вердикта - Если
AutoScan=true— детекция мгновенная (по ответу на scan-команду 0x03) - Во время пробы устройство уже опрашивается через RTU fallback — данные не теряются
- Убедиться, что
FallbackPollPeriodMs > 0 - Проверить, завершилась ли проба: устройство получает RTU fallback как только
fmb_probe_done=true,fmb_capable=false(≤ 3 цикла) - Если
EnableFastModbus=false— RTU работает сразу для всех устройств без ожидания пробы
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihfERROR: SDK libraries missing in platform/linux/api/mplc_lib_so/
Скопировать 6 файлов .so с работающего контроллера из /opt/mplc4/:
scp root@<ip>:/opt/mplc4/{masterplc,mplcshare,mplc_archive,opcua,liblua,mplc_events}.so \
platform/linux/api/mplc_lib_so/Скопировать каталог API из установки MasterSCADA 4D в корень проекта (API/include, API/lib). Подробно — docs/BUILD_INSTRUCTIONS.md.
- MasterSCADA 4D — документация по созданию протоколов
- Wiren Board Fast Modbus — спецификация протокола
- Репозиторий драйвера
Проприетарный — CYNTRON. Все права защищены.