В заметке описывается один из вариантов реализации системы управления на базе Raspberry Pi с установленной средой исполнения Codesys и подключенной по последовательной линии платы Arduino. Программа управления использует язык программирования ST, а в качестве драйвера Modbus используется функциональный блок ModbusRequest из системной библиотеки IoDrvModbus. Со стороны Arduino используется библиотека Modbus-Master-Slave-for-Arduino.
Функциональная схема системы управления
Базовая схема управления на базе Raspberry Pi и Arduino может выглядеть следующим образом
Вариант 1. Прямое соединение через USB.
Вариант 2. Удалённое подключение Arduino с использованием интерфейса RS-485.
На обеих схемах Raspberry Pi является программируемым модулем управления, т.е. реализует функцию ПЛК. Arduino используется как удалённый универсальный модуль ввода-вывода, функция которого — преобразование логических/числовых управляющих сигналов в физические и обратное преобразование физических величин в логические/числовые.
С программной точки зрения обе эти схемы практически эквивалентны. Нужно лишь отметить, что длина линии RS-485 и наводимые на неё помехи могут потребовать снижения скорости обмена, увеличение времени ожидания ответа и повторения запросов.
Также во второй схеме желательно иметь гальванические развязки в местах соединения последовательной линии с устройствами управления. В промышленных устройствах автоматики преобразователи интерфейсов имеют отдельное питание и гальваническую развязку сигнальных линий. В бытовом применении, для обучения или в малой автоматизации такие средства защиты можно не использовать если есть уверенность в том, что наводимые помехи не будут приводить к неустойчивой связи, перезагрузке устройства или выходу из его строя.
В качестве преобразователей USB/RS-485 можно использовать устройства на базе специализированных микросхем типа FTDI, PL2303, CH340 и прочих, доступных на AliExpress. Для преобразователя RS-485/TTL также есть доступные модули.
Настройка Codesys для работы с последовательным портом
При подключении к Raspberry Pi преобразователя интерфейса USB/RS-485 или Arduino в большинстве случаев в файловой системе появляется устройство /dev/ttyUSBn, где n — это номер назначенного интерфейса для каждого подключенного по USB устройства.
Как правило, если мы выполним команду ls /dev/ | grep ttyUSB, то получим в ответ строку ttyUSB0.
Среда исполнения Codesys по умолчанию ничего не знает о внешних интерфейсах и предварительно необходимо настроить файл конфигурации. Пользовательские настройки конфигурации хранятся в файле /etc/CODESYSControl_User.cfg.
Чтобы получить доступ к файловой системе лучше воспользоваться сторонней программой WinSCP, которая позволит удобно работать с файловой системой по протоколу SFTP. Установим, запустим программу и создадим новое соединение, установив тип протокола, ip-адрес, логин и пароль (pi/raspberry по умолчанию).
Для возможности записи в расширенных настройках подключения установим:
Среда -> SFTP -> Параметры протокола:
Сервер SFTP: sudo /usr/lib/sftp-server
Далее находим файл конфигурации /etc/CODESYSControl_User.cfg и открываем его на редактирование (в программе WinSCP можно назначить внешний редактор).
Для подключения внешнего последовательного устройства в файле конфигурации добавим/изменим секцию:
1 2 |
[SysCom] Linux.Devicefile=/dev/ttyUSB |
То же можно сделать, используя инструменты ОС Raspbian, если есть прямой доступ к Raspberry Pi. Ещё можно установить панельный файловый менеджер Midnight Commander с помощью команды:
1 |
$ sudo apt-get install mc |
С помощью встроенного в него редактора можно изменить файл конфигурации. Запуск Midnight Commander выполняется командой:
1 |
$ sudo mc |
После правки конфигурации необходимо перезапустить Codesys RTS. Сделать это можно из среды разработки Codesys (Tools\Update Raspberry Pi) или при помощи команд:
1 2 |
$ sudo systemctl stop codesyscontrol $ sudo systemctl start codesyscontrol |
Программа для Arduino
В качестве программы, реализующей работу по протоколу modbus-rtu slave, возьмём пример, который идёт в составе библиотеки Modbus-Master-Slave-for-Arduino: examples\simple_slave\simple_slave.ino.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** * Modbus slave example 1: * The purpose of this example is to link a data array * from the Arduino to an external device. * * Recommended Modbus Master: QModbus * http://qmodbus.sourceforge.net/ */ #include <ModbusRtu.h> // data array for modbus network sharing uint16_t au16data[16] = { 3, 1415, 9265, 4, 2, 7182, 28182, 8, 0, 0, 0, 0, 0, 0, 1, -1 }; /** * Modbus object declaration * u8id : node id = 0 for master, = 1..247 for slave * u8serno : serial port (use 0 for Serial) * u8txenpin : 0 for RS-232 and USB-FTDI * or any pin number > 1 for RS-485 */ Modbus slave(1,0,0); // this is slave @1 and RS-232 or USB-FTDI void setup() { slave.begin( 19200 ); // baud-rate at 19200 } void loop() { slave.poll( au16data, 16 ); } |
Сначала установим саму библиотеку. Для этого нужно перейти в корень репозитория и нажать кнопку Clone or download, а в появившемся окне выбрать Download ZIP.
В окне сохранения измените имя архива на ModbusRtu.zip. Это понадобится при установке библиотеки в Arduino IDE.
После загрузки откройте Arduino IDE, вызовите пункт меню Скетч\Подключить библиотеку\Добавить .ZIP библиотеку… и выберите загруженный архив.
После установки содержимое архива ModbusRtu должно находиться в папке: C:\Users\User\Documents\Arduino\libraries. Откройте пример ModbusRtu\examples\simple_slave\simple_slave.ino и загрузите его в свою плату Arduino.
Проект для Codesys
В качестве тестового проекта предлагаю программу на языке ST, использующую библиотеки SysCom и IoDrvModbus. Первая используется для управления портом при помощи набора функций, а вторая — для реализации протокола modbus rtu master, используя функциональный блок ModbusRequest:
Проект с именем Raspberry.RTUMasterAsyncTest.ST.project находится в git репозитории codesys. Содержимое репозитория codesys доступно в виде zip архива (см. кнопку ZIP).
Также его можно клонировать стандартными средствами git, используя адрес репозитория в качестве ссылки:
1 |
git clone https://tinyco.ru/gitlist/codesys/ |
При этом репозиторий будет доступен только в режиме чтения (push запрещён).
Проект Raspberry.RTUMasterAsyncTest.ST.project сохранён в формате Codesys 3.5.12.20, используется пакет Raspberry Pi 3.5.12.30. При первом открытии возможно понадобится загрузить отсутствующие библиотеки через Library Manager в дереве проекта.
Проект может быть использован и на ранних версиях Codesys, вплоть до 3.5.10.0, но для этого его нужно вручную сохранить в нужной версии и исправить все зависимости (библиотеки, устройства), что требует некоторого опыта. Можно также скопировать текст программы, а все зависимости добавить, создав новый проект в имеющейся версии среды разработки.
Текст программы можно посмотреть в pdf файле, имеющем то же имя, что и файл проекта Codesys: Raspberry.RTUMasterAsyncTest.ST.pdf .
Имейте в виду, если Codesys RTS использует последовательный порт в проекте, то при переподключении Arduino имя устройства может измениться, т.к. текущее устройство считается занятым. Чтобы этого не происходило нужно останавливать среду исполнения перед подключениями устройств по USB.
Не забывайте перезапускать Codesys RTS, т.к. демо версия среды исполнения работает только 2 часа.
Содержимое репозитория может изменяться со временем, следите за сопровождающей темой на форуме и комментариями в репозитории.
Описание алгоритма тестового проекта Codesys
Программа имеет вид автомата состояний. Используются следующие состояния:
- MODE_CONNECT — здесь осуществляется открытие и настройка последовательного порта;
- MODE_SET_DATA — здесь происходит заполнение буфера данными;
- MODE_REQUEST — здесь в зависимости от текущего номера функции выполняется запрос к slave устройству, анализируется ответ от него и переключается номер функции;
- MODE_IDLE — здесь реализован интервал между запросами и из сброс;
- MODE_ERROR — в случае ошибки делается попытка восстановления соединения.
Алгоритм программы осуществляет циклический последовательный перебор асинхронных запросов modbus rtu. Тестируется следующий набор функций modbus:
- READ_COILS — используется для чтения значений дискретных выходов (DO),
- READ_DISCRETE_INPUTS — используется для чтения значений дискретных входов (DI),
- READ_HOLDING_REGISTERS — используется для чтения значений аналоговых выходов (AO),
- READ_INPUT_REGISTERS — используется для чтения значений аналоговых входов (AI),
- WRITE_SINGLE_COIL, — используется для записи значения одного дискретного выхода (DO)
- WRITE_SINGLE_REGISTER — используется для записи значения одного аналогового выхода (AO),
- WRITE_MULTIPLE_COILS — используется для записи значений последовательности дискретных выходов (DO),
- WRITE_MULTIPLE_REGISTERS — используется для записи значений последовательности аналоговых выходов (AO).
Тестирование
Если поставить точку останова внутри условия if xDone then, то можно, анализируя содержимое буфера tmpbuf, проверить корректность работы программы на Arduino для каждой функции modbus. Нетрудно заметить, что все запросы выполняются успешно, а содержимое буфера при первом чтении соответствует содержимому кода программы для Arduino.
Итоги
Показанные базовые схемы, тестовый проект Codesys и библиотеку Modbus RTU для Arduino можно использовать в качестве заготовки при разработке реальных проектов автоматизации с учётом имеющихся ограничений.
Приложение
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
program PLC_PRG var constant READ_COILS : byte := 16#01; READ_DISCRETE_INPUTS : byte := 16#02; READ_HOLDING_REGISTERS : byte := 16#03; READ_INPUT_REGISTERS : byte := 16#04; WRITE_SINGLE_COIL : byte := 16#05; WRITE_SINGLE_REGISTER : byte := 16#06; WRITE_MULTIPLE_COILS : byte := 16#0F; WRITE_MULTIPLE_REGISTERS : byte := 16#10; MODE_IDLE: byte := 0; MODE_CONNECT: byte := 1; MODE_SET_DATA: byte := 2; MODE_REQUEST: byte := 3; MODE_CLOSE: byte := 4; MODE_ERROR: byte := 5; // Номера портов для устройств /dev/ttyUSBn Raspberry Pi 3. TTYS0: byte := 1; TTYS1: byte := 2; TTYS2: byte := 3; TTYS3: byte := 4; TTYS4: byte := 5; end_var var xBusy, xDone, xError: bool; bvalue: bool; nfunc: byte := READ_COILS; nBytes, nRegs, ex: byte; nCoils: word; mode: uint := MODE_CONNECT; timeout: time; start, stop, t: ulint; tmpbuf: array [ 0 .. 255 ] of byte; result: RTS_IEC_RESULT; hCom: RTS_IEC_RESULT; t1: ton; ComSettings: COM_Settings; ReadCoils: ModbusRequest := ( uiFunctionCode := READ_COILS ); ReadDiscrInputs: ModbusRequest := ( uiFunctionCode := READ_DISCRETE_INPUTS ); ReadHoldRegs: ModbusRequest := ( uiFunctionCode := READ_HOLDING_REGISTERS ); ReadInputRegs: ModbusRequest := ( uiFunctionCode := READ_INPUT_REGISTERS ); WriteSingleCoil: ModbusRequest := ( uiFunctionCode := WRITE_SINGLE_COIL ); WriteSingleReg: ModbusRequest := ( uiFunctionCode := WRITE_SINGLE_REGISTER ); WriteMultCoils: ModbusRequest := ( uiFunctionCode := WRITE_MULTIPLE_COILS ); WriteMultRegs: ModbusRequest := ( uiFunctionCode := WRITE_MULTIPLE_REGISTERS ); end_var // Перед использованием необходимо: // - обновить устройство; // - обновить библиотеки; // - настроить параметры последовательного порта; case mode of MODE_CONNECT: // Настройка параметров соединения. // Таймаут ожидания ответа. timeout := t#200ms; // Порт. ComSettings.sPort := TTYS0; // Скорость. ComSettings.ulBaudrate := SYS_BR_19200; // Чётность. ComSettings.byParity := SYS_NOPARITY; // Количество стоп-битов. ComSettings.byStopBits := SYS_ONESTOPBIT; // Размер буфера приёмника. ComSettings.ulBufferSize := 255; // Открываем порт. hCom := SysComOpen( ComSettings.sPort, adr( result ) ); // Устанавливаем параметры порта. result := SysComSetSettings( hCom, adr( ComSettings ), 0 ); t1( in := false ); mode := sel( result = Errors.ERR_OK, MODE_ERROR, MODE_SET_DATA ); MODE_SET_DATA: case nfunc of // Function 01 (0x01) Read Coils READ_COILS: ; // Function 02 (0x02) Read Discrete Inputs READ_DISCRETE_INPUTS: ; // Function 03 (0x03) Read Holding Registers READ_HOLDING_REGISTERS: ; // Function 04 (0x04) Read Input Registers READ_INPUT_REGISTERS: ; // Function 05 (0x05) Write Single Coil WRITE_SINGLE_COIL: tmpbuf[0] := 1; // Function 06 (0x06) Write Single Register WRITE_SINGLE_REGISTER: tmpbuf[0] := 16#33; // Function 15 (0x0F) Write Multiple Coils WRITE_MULTIPLE_COILS: tmpbuf[0] := 16#F0; tmpbuf[1] := 16#0A; // Function 16 (0x10) Write Multiple Registers WRITE_MULTIPLE_REGISTERS: tmpbuf[0] := 16#55; tmpbuf[1] := 16#AA; tmpbuf[2] := 16#FF; tmpbuf[3] := 16#FF; end_case SysTimeRtcHighResGet( start ); mode := MODE_REQUEST; MODE_REQUEST: // Выполняем запрос (асинхронные функции). case nfunc of // Function 01 (0x01) Read Coils READ_COILS: ReadCoils( xExecute := true, usiSlaveAddr := 1, uiReadOffset := 1, uiReadLen := 8, hComPort := hCom, tTimeout := timeout, pReadBuf := adr( tmpbuf ), xBusy => xBusy, xDone => xDone, xError => xError, byModbusErrorCode => ex ); // Function 02 (0x02) Read Discrete Inputs READ_DISCRETE_INPUTS: ReadDiscrInputs( xExecute := true, usiSlaveAddr := 1, uiReadOffset := 1, uiReadLen := 8, hComPort := hCom, tTimeout := timeout, pReadBuf := adr( tmpbuf ), xBusy => xBusy, xDone => xDone, xError => xError, byModbusErrorCode => ex ); // Function 03 (0x03) Read Holding Registers READ_HOLDING_REGISTERS: ReadHoldRegs( xExecute := true, usiSlaveAddr := 1, uiReadOffset := 1, uiReadLen := 2, hComPort := hCom, tTimeout := timeout, pReadBuf := adr( tmpbuf ), xBusy => xBusy, xDone => xDone, xError => xError, byModbusErrorCode => ex ); // Function 04 (0x04) Read Input Registers READ_INPUT_REGISTERS: ReadInputRegs( xExecute := true, usiSlaveAddr := 1, uiReadOffset := 1, uiReadLen := 2, hComPort := hCom, tTimeout := timeout, pReadBuf := adr( tmpbuf ), xBusy => xBusy, xDone => xDone, xError => xError, byModbusErrorCode => ex ); // Function 05 (0x05) Write Single Coil WRITE_SINGLE_COIL: WriteSingleCoil( xExecute := true, usiSlaveAddr := 1, uiWriteOffset := 1, uiWriteLen := 1, hComPort := hCom, tTimeout := timeout, pWriteBuf := adr( tmpbuf ), xBusy => xBusy, xDone => xDone, xError => xError, byModbusErrorCode => ex ); // Function 06 (0x06) Write Single Register WRITE_SINGLE_REGISTER: WriteSingleReg( xExecute := true, usiSlaveAddr := 1, uiWriteOffset := 1, uiWriteLen := 1, hComPort := hCom, tTimeout := timeout, pWriteBuf := adr( tmpbuf ), xBusy => xBusy, xDone => xDone, xError => xError, byModbusErrorCode => ex ); // Function 15 (0x0F) Write Multiple Coils WRITE_MULTIPLE_COILS: WriteMultCoils( xExecute := true, usiSlaveAddr := 1, uiWriteOffset := 1, uiWriteLen := 8, hComPort := hCom, tTimeout := timeout, pWriteBuf := adr( tmpbuf ), xBusy => xBusy, xDone => xDone, xError => xError, byModbusErrorCode => ex ); // Function 16 (0x10) Write Multiple Registers WRITE_MULTIPLE_REGISTERS: WriteMultRegs( xExecute := true, usiSlaveAddr := 1, uiWriteOffset := 1, uiWriteLen := 2, hComPort := hCom, tTimeout := timeout, pWriteBuf := adr( tmpbuf ), xBusy => xBusy, xDone => xDone, xError => xError, byModbusErrorCode => ex ); end_case // Пришёл ответ на запрос. if xDone then SysTimeRtcHighResGet( stop ); // Фактическое время выполнения запроса, [мсек]. t := stop - start; // Переходим к следующему запросу. case nfunc of // Function 01 (0x01) Read Coils READ_COILS: nfunc := READ_DISCRETE_INPUTS; // Function 02 (0x02) Read Discrete Inputs READ_DISCRETE_INPUTS: nfunc := READ_HOLDING_REGISTERS; // Function 03 (0x03) Read Holding Registers READ_HOLDING_REGISTERS: nfunc := READ_INPUT_REGISTERS; // Function 04 (0x04) Read Input Registers READ_INPUT_REGISTERS: nfunc := WRITE_SINGLE_COIL; // Function 05 (0x05) Write Single Coil WRITE_SINGLE_COIL: nfunc := WRITE_SINGLE_REGISTER; // Function 06 (0x06) Write Single Register WRITE_SINGLE_REGISTER: nfunc := WRITE_MULTIPLE_COILS; // Function 15 (0x0F) Write Multiple Coils WRITE_MULTIPLE_COILS: nfunc := WRITE_MULTIPLE_REGISTERS; // Function 16 (0x10) Write Multiple Registers WRITE_MULTIPLE_REGISTERS: nfunc := READ_COILS; end_case // Ошибка. elsif xError then case ex of MB_ErrorCodes.RESPONSE_SUCCESS: ; MB_ErrorCodes.RESPONSE_TIMEOUT: ; MB_ErrorCodes.RESPONSE_INVALID_DATA: ; MB_ErrorCodes.REQUEST_FAILED_TO_SEND: SysComClose( hCom ) ; // Коды ошибок Modbus. else ; end_case // Запрос выполняется. elsif xBusy then return; end_if t1( in := false ); mode := sel( ex = MB_ErrorCodes.REQUEST_FAILED_TO_SEND, MODE_IDLE, MODE_ERROR ); // Интервал между запросами. MODE_IDLE: t1( in := true, pt := t#1s ); if t1.q then case nfunc of // Function 01 (0x01) Read Coils READ_COILS: ReadCoils( xExecute := false ); // Function 02 (0x02) Read Discrete Inputs READ_DISCRETE_INPUTS: ReadDiscrInputs( xExecute := false ); // Function 03 (0x03) Read Holding Registers READ_HOLDING_REGISTERS: ReadHoldRegs( xExecute := false ); // Function 04 (0x04) Read Input Registers READ_INPUT_REGISTERS: ReadInputRegs( xExecute := false ); // Function 05 (0x05) Write Single Coil WRITE_SINGLE_COIL: WriteSingleCoil( xExecute := false ); // Function 06 (0x06) Write Single Register WRITE_SINGLE_REGISTER: WriteSingleReg( xExecute := false ); // Function 15 (0x0F) Write Multiple Coils WRITE_MULTIPLE_COILS: WriteMultCoils( xExecute := false ); // Function 16 (0x10) Write Multiple Registers WRITE_MULTIPLE_REGISTERS: WriteMultRegs( xExecute := false ); end_case mode := MODE_SET_DATA; end_if // Ошибка. MODE_ERROR: t1( in := true, pt := t#1s ); if t1.q then mode := MODE_CONNECT; end_if end_case |
Вячеслав, с удовольствием прочел Ваши статьи. Также интересует тема малина и кодесис. Вопрос такой, не нашел пока информации по реализации modbus TCP на малине. Штатный конфигуратор не интересует. С ним понятно. Может есть готовая библиотека? Или лепить из например из OSCAT NET + либа Modbus ОВЕН.
Спасибо.
Существует бесплатная библиотека из состава SDK для ПЛК Agava. Её можно скачать с облака по ссылке: http://files.kb-agava.ru/index.php/s/F9Uuk5PLiMYP6Cv
В примерах есть проект ModbusTCPSlave для RP3.
Спасибо! то что нужно! Один вопрос возник — библиотека AgavaModbusEx ссылается на Util 3.5.9.0, у меня 3.5.11.0. Не компилится, ошибки выдает. Не подскажите как победить?
Библиотеки и таргет обновил. Остальные примеры которые смотрел нормально собираются.
Спасибо.
Нужно подробно пояснять:
— версия SDK (имя файла архива);
— название проекта из примера;
— версия Codesys, включая номер патча.
При работе с Codesys нужно быть очень аккуратным с зависимостями. Для RaspberryPi можно использовать Codesys 3.5.12.x и вообще не стесняться последних версий. Библиотеки из SDK собраны в Codesys 3.5.10.40 и, как правило, могут быть использованы в старших версиях.
Если требуется другая версия библиотеки, то можно попробовать принудительно установить нужную версию в менеджере библиотек (см. дерево проекта, ПКМ, свойства библиотеки).
Спасибо. Разобрался с версиями. Проекты собираются без ошибок.
А в примере CODESYS, что в статье, как работает это часть:
// Переходим к следующему запросу.
case nfunc of
// Function 01 (0x01) Read Coils
READ_COILS: nfunc := READ_DISCRETE_INPUTS;
// Function 02 (0x02) Read Discrete Inputs
READ_DISCRETE_INPUTS: nfunc := READ_HOLDING_REGISTERS;
// Function 03 (0x03) Read Holding Registers
READ_HOLDING_REGISTERS: nfunc := READ_INPUT_REGISTERS;
// Function 04 (0x04) Read Input Registers
READ_INPUT_REGISTERS: nfunc := WRITE_SINGLE_COIL;
// Function 05 (0x05) Write Single Coil
WRITE_SINGLE_COIL: nfunc := WRITE_SINGLE_REGISTER;
// Function 06 (0x06) Write Single Register
WRITE_SINGLE_REGISTER: nfunc := WRITE_MULTIPLE_COILS;
// Function 15 (0x0F) Write Multiple Coils
WRITE_MULTIPLE_COILS: nfunc := WRITE_MULTIPLE_REGISTERS;
// Function 16 (0x10) Write Multiple Registers
WRITE_MULTIPLE_REGISTERS: nfunc := READ_COILS;
end_case
Как я понял вошли в CASE после получения ответа xDone, присвоили nfunc следующее значение и вышли…. А для чего? Таким образом перебираем все функции Modbus? Это для примера? А в реальном приложении какая будет конструкция?
Перебор всех функций нужен для примера, чтобы проверить корректность работы для каждой в одном примере. Это упрощённый минимальный пример, который позволяет это сделать. Воспользовавшись монитором последовательного порта можно проверить процесс обмена данными на соответствие протоколу modbus и таблице регистров в slave устройстве.
Реальное приложение может использовать эти функции как угодно в зависимости от постановки задачи. Короче говоря, это должен определять программист, исходя из имеющейся документации на библиотеку (см. SDK).
Спасибо за внимание и развернутый ответ.