Руководство по протоколу modbus

Время на прочтение
7 мин

Количество просмотров 190K

Протокол Modbus — самый распространенный промышленный протокол для M2M-взаимодействия. Является стандартом де-факто и поддерживается почти всеми производителями промышленного оборудования.

Благодаря универсальности и открытости, стандарт позволяет интегрировать оборудование разных производителей. Modbus используется для сбора показания с датчиков, управления реле и контроллерами, мониторинга, и т.д.

В статье разберем реализации протокола Modbus, форматы данных, программное обеспечение для работы с протоколом. Попробуем на практике прочитать данные из устройства.

История Modbus

Modbus был представлен в 1979 году компанией Modicon (ныне Schneider Electric). Это был открытый стандарт, работающий по интерфейсу RS-232. Позже появилась реализации протокола для интерфейсов RS-485 и Modbus TCP. Протокол быстро набрал популярность, и многие производители стали внедрять его в своих устройствах.

Позже права на протокол были переданы некоммерческой организации Modbus Organization, которая до сегодняшнего дня владеет стандартом.

В описании стандарта Modbus используются терминология, унаследованная от языков релейной логики. Так, например, некоторые регистры называются катушками (англ. coil).

Физический уровень

  • RS-232/422/485 — последовательные интерфейсы, широко распространенные в промышленности. Интерфейсы RS-422/485 обеспечивают дальность сигнала до 1200 метров. Используются протоколы Modbus RTU/ASCII
  • Сети TCP/IP — физическим каналом передачи данных могут любые ethernet-интерфейсы. Используется протокол Modbus TCP

Логический уровень


Различия протоколов Modbus

Modbus ASCII

Данные кодируются символами из таблицы ASCII и передаются в шестнадцатеричном формате. Начало каждого пакета обозначается символом двоеточия, а конец — символами возврата каретки и переноса строки. Это позволяет использовать протокол на линиях с большими задержками и оборудовании с менее точными таймерами.

Modbus RTU

В протоколе Modbus RTU данные кодируются в двоичный формат, и разделителем пакетов служит временной интервал. Этот протокол критичен к задержкам и не может работать, например, на модемных линиях. При этом, накладные расходы на передачу данных меньше, чем в Modbus ASCII, так как длина сообщений меньше.

Modbus TCP

Структура пакетов схожа с Modbus RTU, данные также кодируются в двоичный формат, и упаковываются в обычный TCP-пакет, для передачи по IP-сетям. Проверка целостности, используемая в Modbus RTU, не применяется, так как TCP уже имеет собственный механизм контроля целостности.

Формат пакета


Форматы пакета разных реализаций Modbus

Все устройства Modbus взаимодействуют, следуя модели master-slave. Запросы может инициировать только master-устройство, slave-устройства могут только отвечать на запросы, и не могут самостоятельно начинать передачу данных. В зависимости от реализации протокола, заголовки пакета различаются. Вот основные составляющие пакета, которые важно знать:

ADU (Application Data Unit) — пакет Modbus целиком, со всеми заголовками, PDU, контрольной суммой, адресом и маркерами. Отличается, в зависимости от реализации протокола.

PDU (protocol data unit) — основная часть пакета, одинаковая для всех реализаций протокола. Содержит сам payload.

Адрес устройства — адрес получателя, то есть slave-устройства. В одном сегменте Modbus-сети могут находится до 247 устройств. Только slave-устройства имеют различающиеся адреса, master-устройство не имеет адреса. Адрес «0» используется для широковещательных запросов от master, при этом, slave-устройства не могут отвечать на эти широковещательные пакеты.

Контрольная сумма — алгоритмы проверки целостности пакетов. В Мodbus RTU и ASCII используется 2 байта контрольной суммы. В Modbus RTU применяется алгоритм CRC16, в Modbus ASCII — более простой и менее надежный LRC8. В Modbus TCP контрольная сумма не добавляется в ADU, так как целостность проверяется на уровне TCP.

Мы не будем разбирать дополнительные заголовки, специфичные для каждой отдельной реализации протокола, так как это не имеет существенного значения при работе с протоколом на прикладном уровне.

Регистры и функции Modbus

В упрощенном виде, структура запросов Modbus состоит из кода функции (чтение/запись), и данных, которые нужно считать или записать. При этом, коды функции различаются для разных типов данных. Разберем, какие бывают регистры, и функции для работы с ними.

  • Discrete Inputs — дискретные входы устройства, доступны только для чтения. Диапазон адресов регистров: с 10001 по 19999. Имеют функцию «02» — чтение группы регистров
  • Coils — дискретные выходы устройства, или внутренние значения. Доступны для чтения и записи. Диапазон адресов регистров: с 20001 по 29999. Имеет функции: «01» — чтения группы регистров, «05» — запись одного регистра, «15» — запись группы регистров
  • Input Registers — 16-битные входы устройства. Доступны только для чтения. Диапазон адресов регистров: с 30001 по 39999. Имеют функцию: «04» — чтение группы регистров
  • Holding Registers — 16-битные выходы устройства, либо внутренние значения. Доступны для чтения и записи. Диапазон адресов регистров: с 40001 по 49999. Имеют

Несмотря на названия, входы и выходы могут на самом деле являться внутренними переменными, хранить счетчики, флаги, или быть управляющими триггерами. Существуют также и другие диапазоны регистров, но в подавляющем большинстве устройств они не используется, поэтому мы рассмотрим четыре основных типа регистров. В разных устройствах могут быть задействованы разные диапазоны регистров, или же все сразу.

Примеры работы

Для примера работы с протоколом Modbus TCP воспользуемся максимально простой консольной утилитой modbus-cli, написанной на языке Ruby. Она позволяет легко читать и писать данные в регистры Modbus.

Попробуем прочесть состояние счетчиков переданных пакетов на промышленном коммутаторе Advantech EKI-5524SSI. Для начала необходимо определить адреса регистров, хранящие нужную информацию, для этого заглянем в документацию устройства. Описание регистров находятся в разделе «Modbus Mapping Table»:

Описание значений регистров в документации коммутаторов EKI

Видно, что значение переданных пакетов для одного порта хранится в четырех регистрах, и для первого порта это регистры с 38193 по 38197. Также дано описание формата хранения данных, из которого следует, что целое число переданных пакетов хранится шестнадцатеричном формате, и значение 11223344 пакетов будет записано как 0xAB4130, справа налево.

Составим запрос:

$ modbus read 192.168.0.17 38193 4
38193   0x0000
38194   0x0000
38195   0x0000
38196   0x3459

read — команда чтения. Программа сама понимает, какую конкретно команду чтения использовать в зависимости от адреса регистра, в нашем случае будет использована команда «04», для чтения 16-битных регистров.

192.168.0.17 — IP-адрес устройства.

38193 — начальный адрес регистра.

4 — смещение относительно начального адреса. Мы читаем четыре регистра для порта 1, как следует из даташита.

Получаем ответ, содержащий значения четырех регистров. Видим, что число пакетов невелико: 0x3459, то есть 13401, — коммутатор был включен недавно.

Недостатки протокола Modbus

Справедливости ради, стоит упомянуть и о недостатках протокола. Так как он разрабатывался более 40 лет назад, когда производительность процессоров была существенно ниже и протоколы разрабатывались без учета защиты данных, он имеет рад минусов:

  • Протокол не предусматривает аутентификацию и шифрование передаваемых данных. Поэтому, при использовании Modbus TCP необходимо использовать дополнительные VPN-тоннели.
  • Slave-устройство не может инициировать передачу данных, поэтому master должен постоянно опрашивать ведомые устройства
  • Slave-устройство не может обнаружить потерю связи с Master. Эта проблема напрямую следует из предыдущей.

Однако, несмотря на все недостатки, Modbus по-прежнему остается самым распространенным промышленным протоколом, и благодаря открытости, позволяет легко объединять устройства разных производителей. Нетребовательность к ресурсам позволяет интегрировать протокол в самые маломощные устройства.

Оборудование с поддержкой Modbus

Advantech предлагает широкий спектр промышленного оборудования с поддержкой протокола Modbus для любых задач: автоматизации, управления, сбора и передачи данных.

ADAM-6000 и WISE-4000 — модули удаленного ввода-вывода

Модули серии ADAM-6000 и WISE-4000 позволяют удаленно управлять цифровыми и аналоговыми входами/выходами по протоколу Modbus TCP. Используются для управления периферийными устройствами и сбора данных в режиме slave. Могут работать в паре с программируемым логическим контроллером, или подключаться напрямую к SCADA-серверу.⠀⠀⠀ ⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

EKI-1200 — Modbus-шлюзы для преобразования интерфейсов


Для преобразования протоколов Modbus RTU/ASCII в Modbus TCP, используются Modbus шлюзы. Устройства серии EKI-1200 имеют на борту до четырех последовательных интерфейсов RS-232/422/485, и два Ethernet-порта. Они позволяют объединить в одну сеть устройства с разными протоколами. Например, подключить slave устройство, поддерживающее только Modbus RTU, по интерфейсу RS-485 к сегменту сети Modbus TCP.

APAX-5000, ADAM-3600, WISE-5000 — контроллеры автоматизации

Контроллеры поддерживают функции Modbus RTU в качестве slave/master и клиента/сервера Modbus TCP.

Примеры применения

Система мониторинга теплиц

Решение Advantech для мониторинга интегрирует устройства TPC-1070H, ADAM-6024, ADAM-6050, ADAM-6060 и программное обеспечение WebAccess в машинном шкафу рядом с сельскохозяйственными угодьями. Соединяясь с различными чувствительными устройствами, модули ADAM-6000 могут в режиме реального времени получать данные об окружающей среде и контролировать переключение оборудования, чтобы гарантировать, что теплица находится в оптимальной среде для роста растений. Благодаря особой функции Advantech — графической логике условий (GCL), пользователи могут определять свои собственные правила логики управления и загружать эти правила в модули ввода / вывода Ethernet ADAM-6000, а затем модули автоматически выполняют логические правила, как автономные модули. контроллер. Еще одна особенность — Peer-to-Peer (P2P) использует наиболее открытую и гибкую сеть Ethernet, чтобы не только упростить процесс внедрения без контроллера, но и сэкономить затраты на аппаратное оборудование.

Все полученные данные затем передаются через Ethernet на компьютер с сенсорной панелью TPC-1070H. Благодаря системе охлаждения без вентилятора и передней панели, соответствующей стандарту IP65, TPC-1070H представляет собой прочную и компактную конструкцию, подходящую для изменяемой операционной среды, а его мощные вычислительные возможности способны обрабатывать большие объемы данных. Для управления устройствами Advantech WebAccess позволяет инженерам или менеджерам просматривать, контролировать и настраивать систему мониторинга через интрасеть или Интернет с помощью обычного веб-браузера с любого устройства, включая планшеты и смартфоны.

Мониторинг системы нагрева воды солнечной энергией

Инжиниринговая компания должна была иметь возможность контролировать количество солнечной энергии, температуры и расход воды в системе нагрева воды на солнечной энергии для бассейна олимпийских размеров, обеспечиваемого их недавно разработанной солнечной панелью. Они также должны были иметь возможность непосредственно отслеживать эти значения и их аварийные сигналы на ЖК-панелях и сохранять эти значения для дальнейшего использования.

Модули Adam от Advantech предоставили заказчику решение, в котором использовались модули сбора данных, подключенные через RS485, и двухпроводная шина для передачи данных со всех датчиков. Эта системная архитектура имеет два основных преимущества: во-первых, она позволяет в любое время добавлять в систему большее количество датчиков модулей сбора данных, и, во-вторых, очень легко добавлять дополнительные метки в программное обеспечение для мониторинга и записи этих значений на ПК.

Описание протокола Modbus:

Перед тем как приступить к описанию протокола, давайте разберёмся с терминами.

Шина – канал связи, служащий для передачи данных, используя предписанные электрические (физические) и логические (управляющие) уровни.

Шина это нечно физическое (дорожки на плате, провода, шлейфы и т.д.). Шина определяет граничные значения скорости, максимальную дальность, напряжения логических уровней, допустимые емкости между линиями, форму сигналов на линиях и т.д.

Протокол – набор правил и действий (очерёдности действий), позволяющий осуществлять соединение и обмен данными.

Протокол это правила по которым осуществляется передача данных. Данные между двумя устройствами передаются по шине в соответствии с протоколом.

Интерфейс – совокупность средств и правил, обеспечивающих логическое и физическое взаимодействие устройств и (или) программ системы.

Интерфейс это шина + протокол + устройство или программа отвечающая за передачу данных.

Протокол Modbus – коммуникационный протокол, основанный на архитектуре ведущий-ведомый.

Протокол Modbus разработан для шин: RS-485, RS-422, RS-232, и интерфейса Ethernet сети TCP/IP, но при желании его можно использовать для связи и по другим шинам или даже радиоканалам.

Протокол Modbus предусматривает наличие одного ведущего и до 247 ведомых устройств. Количество ведомых устройств может быть ограничено физическими возможностями шины.

Для ведущего устройства (master) в протоколе Modbus используется термин — client. Для ведомого устройства (slave) в протоколе Modbus используется термин — server. Да, это не опечатка, ведущий – клиент, ведомый – сервер. Думаю не будет ошибкой, если далее в статье мы будем пользоваться более привычными терминами: ведущий (master), ведомый (slave).

В соответствии с протоколом Modbus связь всегда начинает мастер, а ведомые устройства могут только отвечать на запросы мастера. Связь осуществляется посылкой пакета запроса (от мастера к ведомому) и посылкой ответного пакета (от ведомого к мастеру).

Типы протоколов Modbus:

  • Modbus RTU:
    Данные передаются в двоичном формате, разделителем пакетов служит отсутствие данных в течении времени при котором можно передать более 3,5 байт.
    Протокол предназначен для шин: RS-485, RS-422, RS-232.
  • Modbus ASCII:
    Данные передаются символами из таблицы ASCII в шестнадцатеричном формате, разделителями пакетов служат символы начала и конца пакета.
    Каждый пакет начинается символом двоеточия, в кодировке ASCII и заканчивается символами возврата каретки ‘r’, и переноса строки ‘n’.
    Например, для передачи адреса 10 в протоколе Modbus RTU будет передан байт 0x0A, а в протоколе Modbus ASCII будет передан байт символа ‘0’ и байт символа ‘A’, в кодировке ASCII.
    Протокол предназначен для шин: RS-485, RS-422, RS-232.
  • Modbus TCP:
    Данные передаются в двоичном формате и упаковываются в обычный TCP-пакет, для передачи по IP-сетям. Отличием данного протокола является отсутствие проверки целостности (CRC-16), так как TCP уже имеет собственный механизм контроля целостности.
    Протокол предназначен для сетей TCP/IP.

Состав пакета Modbus RTU:

Состав пакета запроса от мастера к ведомому, аналогичен составу ответного пакета ведомого.

ADU (до 256 байт)
АДРЕС
(1 байт)
PDU (до 253 байт) CRC-16
(2 байта)
КОМАНДА
(1 байт)
ДАННЫЕ (до 252 байт)
количество байт зависит от команды
0…247 1…127 0…65535
  • ADU (Application Data Unit) – пакет Modbus целиком.
  • PDU (Protocol Data Unit) – основная часть пакета, состоит из команды и данных.
  • АДРЕС – адрес ведомого устройства которому адресован пакет запроса, или от которого отправлен пакет ответа. Ведомые устройства могут иметь адреса от 1 до 247. Пакет запроса отправленный с адресом 0 является широковещательным, он адресован всем ведомым на шине, они обязаны выполнить запрос, но не должны на него отвечать.
  • КОМАНДА – указывает какие действия должен выполнить ведомый. Команда в запросе может иметь значение от 1 до 127. Этот диапазон соответствует байту у которого сброшен старший бит. Если ведомый выполнил запрос мастера, то в ответном пакете он указывает тот же номер команды, без изменений. Если ведомый обнаружил ошибку в запросе, или не может его выполнить, то в ответном пакете он указывает номер команды с установленным старшим битом. То есть номер команды будет лежать в диапазоне от 129 до 255.
  • ДАННЫЕ – состав и количество данных в запросе и ответе зависят от номера команды. Если ведомый обнаружил ошибку в запросе, или не может его выполнить, то в ответном пакете, в поле данных, он указывает код ошибки.
  • CRC-16 – проверка целостности пакета, два байта передаются младшим байтом вперёд.
    Для протокола Modbus значение CRC-16 рассчитывается используя реверсивный сдвиг, начальное значение равно 0xFFFF, порождающий полином равен 0xA001.

Регистры ведомых устройств:

Данные ведомых устройств хранятся в регистрах. Существует 4 типа регистров:

Адрес: Название: Назначение:
0…0x270E (DI) Discrete Input 1 бит Цифровые входы R
0…0x270E (DO) Discrete Output Coils 1 бит Цифровые выходы
(регистры флагов)
RW
0…0x270E (AI) Analog Input Registers 16 бит Аналоговые входы R
0…0x270E (AO) Analog Output Holding Registers 16 бит Аналоговые выходы
(регистры хранения)
RW

Данные в регистрах не обязательно связаны со значениями на входах и выходах ведомых устройств. Каждый регистр DI/DO хранит 1 бит. Каждый регистр AI/AO хранит 16 бит (2 байта).

Например, регистры AI и AO могут хранить входные данные и результаты вычислений, а регистры DI и DO флаги настроек, и биты состояний.

Команды протокола Modbus:

Номер: Название: Назначение:
hex dec
0x01 1 Read Coil Status Чтение значений из нескольких регистров «DO»
0x02 2 Read Discrete Inputs. Чтение значений из нескольких регистров «DI».
0x03 3 Read Holding Registers. Чтение значений из нескольких регистров «AO».
0x04 4 Read Input Registers. Чтение значений из нескольких регистров «AI».
0x05 5 Force Single Coil. Запись значения в один регистр «DO».
0x06 6 Preset Single Register. Запись значения в один регистр «AO».
0x07 7 Read Exception Status. Чтение сигналов состояния статусных выходов.
0x08 8 Diagnostic. Диагностика.
0x09 9
Команда не стандартизирована,
но уже используется в различных устройствах.
0x0A 10
0x0B 11 Get Com Event Counter. Чтение счетчика событий.
0x0C 12 Get Com Event Log. Чтение журнала событий.
0x0D 13
Команда не стандартизирована,
но уже используется в различных устройствах.
0x0E 14
0x0F 15 Force Multiple Coils. Запись значений в несколько регистров «DO».
0x10 16 Preset Multiple Registers. Запись значений в несколько регистров «AO».
0x11 17 Report Slave ID. Чтение информации об устройстве.
0x12 18

0x13 19
0x14 20 Read File Record. Чтение из файла.
0x15 21 Write File Record. Запись в файл.
0x16 22 Mask Write Register. Запись значения в регистр «AO» с масками И и ИЛИ.
0x17 23 Read/Write Multiple Registers. Чтение и запись нескольких регистров «AO».
0x18 24 Read FIFO Queue. Чтение данных из буфера FIFO.

Все перечисленные команды (кроме работы с файлами и буфером FIFO) реализованы в библиотеке iarduino_Modbus.

Библиотека iarduino_Modbus:

Библиотека iarduino_Modbus позволяет работать с устройствами по шине RS485 используя протокол Modbus RTU или Modbus ASCII. Так как у большинства плат Arduino и ESP нет шины RS485, библиотека использует аппаратную или программную шину UART. Сигналы шины UART необходимо преобразовать в сигналы шины RS485 при помощи конвертирующего модуля UART-RS485, например, на базе микросхемы на MAX485.

Конвертер подключается к выводам TX и RX шины UART, а так же назначается вывод разрешающий работу передатчика DE.

Подключение к шине RS485:

  • Для подключения конвертера к микроконтроллеру используется вывод DE и выводы UART.
    • TX — вывод шины UART микроконтроллера для передачи данных к конвертеру.
    • RX — вывод шины UART микроконтроллера для получения данных от конвертера.
    • DI — вывод шины UART конвертера для получения данных от микроконтроллера.
    • RO — вывод шины UART конвертера для передачи данных к микроконтроллеру.
    • DE — вывод конвертера разрешающий работу его передатчика на шине RS485.
    • ~RE — вывод конвертера разрешающий работу его приёмника на шине RS485.
  • Для подключения устройств к шине RS485 используются выводы A, B и GND.
    • A, B — витая пара для передачи/приёма данных.
    • GND — линия шины RS485 для выравнивания потенциалов.
  • Источники питания могут быть разные для каждого устройства, или один на все, если напряжение питания устройств совпадают.

Подключение к программной шине Piranha UNO:

Вместо выводов 2, 8 и 9 платы Piranha UNO можно использовать любые выводы, указав их номера в скетче.

SoftwareSerial rs485(8, 9);    // Создаём объект для работы с программной шиной UART указывая выводы RX, TX.
ModbusClient modbus(rs485, 2); // Создаём объект для работы по протоколу Modbus указав объект программной шины UART и номер вывода DE конвертера UART-RS485.

Подключение к аппаратной шине Piranha ULTRA:

В примере используется аппаратная шина UART1 использующая выводы 8 и 9. Вместо вывода 2 можно использовать любой вывод платы Piranha ULTRA, указав его номер в скетче.

ModbusClient modbus(Serial1, 2); // Создаём объект для работы по протоколу Modbus указав класс Serial1 и номер вывода DE конвертера UART-RS485.

Подключение к аппаратной шине Piranha ESP32:

В примере используется аппаратная шина UART2 работающая на выводах 16 и 17. Вместо вывода 22 можно использовать любой вывод платы Piranha ESP32, указав его номер в скетче.

ModbusClient modbus(Serial2, 22); // Создаём объект для работы по протоколу Modbus указав класс Serial2 и номер вывода DE конвертера UART-RS485.

Описание функций библиотеки:

В данном разделе описаны функции библиотеки iarduino_Modbus для работы с шиной RS485 по протоколу Modbus.

Синтаксис функций библиотеки iarduino_Modbus совместим с библиотекой ArduinoModbus.

Подключение библиотеки:

В примерах вход DE конвертера подключён к выводу D2 Arduino. Вместо вывода D2 можно использовать любой выход Arduino.

  • Если используется аппаратная шина UART:
#include <iarduino_Modbus.h>    // Подключаем библиотеку для работы по протоколу Modbus.
ModbusClient modbus(Serial, 2); // Создаём объект для работы по протоколу Modbus указав класс Serial и номер вывода DE конвертера UART-RS485.

Если используется аппаратная шина UART-1, то вместо класса Serial указываем Serial1, для шины UART2 указываем Serial2 и т.д.

  • Если используется программная реализация шины UART:
#include <SoftwareSerial.h>     // Подключаем библиотеку для работы с программной шиной UART.
#include <iarduino_Modbus.h>    // Подключаем библиотеку для работы по протоколу Modbus.
                                //
SoftwareSerial rs485(8, 9);     // Создаём объект для работы с программной шиной UART указывая выводы RX, TX.
ModbusClient modbus(rs485, 2);  // Создаём объект для работы по протоколу Modbus указав объект программной шины UART и номер вывода DE конвертера UART-RS485.

Для программной реализации шины UART необходимо указать выводы которые будут использоваться как RX и TX, в примере указаны выводы 8, и 9 соответственно. При создании объекта modbus указывается не класс Serial, а объект для работы с программной шиной UART.

  • Если используются две и более шины:
#include <iarduino_Modbus.h>       // Подключаем библиотеку для работы по протоколу Modbus.
ModbusClient modbus1(Serial1, 2);  // Создаём объект для работы по протоколу Modbus указав класс Serial1 и номер вывода DE конвертера UART1-RS485_1.
ModbusClient modbus2(Serial2, 3);  // Создаём объект для работы по протоколу Modbus указав класс Serial2 и номер вывода DE конвертера UART2-RS485_2.

В примере созданы два объекта для работы по двум аппаратным шинам UART. Количество создаваемых объектов ограничено количеством шин и памятью микроконтроллера. Можно добавить еще и программную шину, подключив библиотеку SoftwareSerial как в примере выше.

Скорость передачи данных по шине Modbus равна скорости шины UART.

Функция begin();

  • Назначение: Инициализация работы по протоколу Modbus.
  • Синтаксис: begin();
  • Параметры: Нет.
  • Возвращаемое значение: Нет.
  • Примечание:
    • Протокол будет инициирован на той шине UART, которая указана при создании объекта.
    • Функцию достаточно вызвать 1 раз в коде Setup(), до обращения к остальным функциям библиотеки.
  • Пример:
modbus.begin(); // Инициируем работу по протоколу Modbus.

Функция setTypeMB();

  • Назначение: Указание типа протокола Modbus.
  • Синтаксис: setTypeMB( ТИП );
  • Параметр: ТИП — может принимать значение MODBUS_RTU или MODBUS_ASCII.
  • Возвращаемое значение: Нет.
  • Примечание: Тип протокола по умолчанию MODBUS_RTU.
  • Пример:
modbus.setTypeMB( MODBUS_RTU ); // Указываем тип протокола Modbus: MODBUS_RTU (по умолчанию), или MODBUS_ASCII.

Функция setTimeout();

  • Назначение: Указание максимального времени ожидания ответа от ведомых устройств.
  • Синтаксис: setTimeout( ВРЕМЯ );
  • Параметр: ВРЕМЯ uint32_t — количество миллисекунд.
  • Возвращаемое значение: Нет.
  • Примечание:
    • Максимальное время ожидания устанавливается, как для типа RTU, так и для ASCII.
    • Максимальное время ожидания по умолчанию 10 мс.
    • Библиотека не ждёт завершение времени ожидания, если модуль ответил раньше.
  • Пример:
modbus.setTimeout( 15 ); // Указываем жать ответ от модулей не более 15 мс.

Функция setDelay();

  • Назначение: Указание паузы до отправки сообщения.
  • Синтаксис: setDelay( ВРЕМЯ );
  • Параметр: ВРЕМЯ uint32_t — количество миллисекунд.
  • Возвращаемое значение: Нет.
  • Примечание:
    • Пауза до отправки сообщения устанавливается только для протокола Modbus RTU.
    • Пауза до отправки сообщения по умолчанию 3 мс.
    • Пауза устанавливается между пакетами. Если после получения/отправки последнего пакета прошло больше времени, то новый пакет будет отправлен без паузы.
  • Пример:
modbus.setDelay( 5 ); // Указываем выдерживать паузу между пакетами в 5 мс.

Функция coilRead();

  • Назначение: Чтение одного регистра «Coil» (он же «Discrete Output»).
  • Синтаксис: coilRead( [ АДРЕС_МОДУЛЯ ] , АДРЕС_РЕГИСТРА );
  • Параметры:
    • АДРЕС_МОДУЛЯ: uint8_t — значение от 1 до 247.
    • АДРЕС_РЕГИСТРА: uint16_t — значение от 0 до 9999.
  • Возвращаемое значение: int8_t — прочитанное значение (0/1), или -1 при неудаче.
  • Примечание: Если адрес модуля не указан, команда будет отправлена всем устройствам.
  • Пример без проверки чтения:
bool i = modbus.coilRead(9, 0); // Читаем из модуля с адресом 9, значение регистра "Coil" с адресом 0.
  • Пример с проверкой:
bool i;
int8_t j = modbus.coilRead(9, 0);      // Читаем из модуля с адресом 9, значение регистра "Coil" с адресом 0.
if( j>=0 ){ i=j; }                     // Сохраняем прочитанное значение в переменную i.
else      { Serial.println("Error"); } // Выводим сообщение о ошибке чтения.

Функция discreteInputRead();

  • Назначение: Чтение одного регистра «Discrete Input».
  • Синтаксис: discreteInputRead( [ АДРЕС_МОДУЛЯ ] , АДРЕС_РЕГИСТРА );
  • Параметры:
    • АДРЕС_МОДУЛЯ: uint8_t — значение от 1 до 247.
    • АДРЕС_РЕГИСТРА: uint16_t — значение от 0 до 9999.
  • Возвращаемое значение: int8_t — прочитанное значение (0/1), или -1 при неудаче.
  • Примечание: Если адрес модуля не указан, команда будет отправлена всем устройствам.
  • Пример без проверки чтения:
bool i = modbus.discreteInputRead(9, 0); // Читаем из модуля с адресом 9, значение регистра "Discrete Input" с адресом 0.
  • Пример с проверкой:
bool i;
int8_t j = modbus.discreteInputRead(9, 0); // Читаем из модуля с адресом 9, значение регистра "Discrete Input" с адресом 0.
if( j>=0 ){ i=j; }                         // Сохраняем прочитанное значение в переменную i.
else      { Serial.println("Error"); }     // Выводим сообщение о ошибке чтения.

Функция holdingRegisterRead();

  • Назначение: Чтение одного регистра «Holding Register» (он же «Analog Output»).
  • Синтаксис: holdingRegisterRead( [ АДРЕС_МОДУЛЯ ] , АДРЕС_РЕГИСТРА );
  • Параметры:
    • АДРЕС_МОДУЛЯ: uint8_t — значение от 1 до 247.
    • АДРЕС_РЕГИСТРА: uint16_t — значение от 0 до 9999.
  • Возвращаемое значение: int32_t — прочитанное значение (0…65535), или -1 при неудаче.
  • Примечание: Если адрес модуля не указан, команда будет отправлена всем устройствам.
  • Пример без проверки чтения:
uint16_t i = modbus.holdingRegisterRead(9, 0); // Читаем из модуля с адресом 9, значение регистра "Holding Register" с адресом 0.
  • Пример с проверкой:
uint16_t i;
int32_t j = modbus.holdingRegisterRead(9, 0); // Читаем из модуля с адресом 9, значение регистра "Holding Register" с адресом 0.
if( j>=0 ){ i=j; }                            // Сохраняем прочитанное значение в переменную i.
else      { Serial.println("Error"); }        // Выводим сообщение о ошибке чтения.

Функция inputRegisterRead();

  • Назначение: Чтение одного регистра «Input Register» (он же «Analog Input»).
  • Синтаксис: inputRegisterRead( [ АДРЕС_МОДУЛЯ ] , АДРЕС_РЕГИСТРА );
  • Параметры:
    • АДРЕС_МОДУЛЯ: uint8_t — значение от 1 до 247.
    • АДРЕС_РЕГИСТРА: uint16_t — значение от 0 до 9999.
  • Возвращаемое значение: int32_t — прочитанное значение (0…65535), или -1 при неудаче.
  • Примечание: Если адрес модуля не указан, команда будет отправлена всем устройствам.
  • Пример без проверки чтения:
uint16_t i = modbus.inputRegisterRead(9, 0); // Читаем из модуля с адресом 9, значение регистра "Input Register" с адресом 0.
  • Пример с проверкой:
uint16_t i;
int32_t j = modbus.inputRegisterRead(9, 0); // Читаем из модуля с адресом 9, значение регистра "Input Register" с адресом 0.
if( j>=0 ){ i=j; }                          // Сохраняем прочитанное значение в переменную i.
else      { Serial.println("Error"); }      // Выводим сообщение о ошибке чтения.

Функция coilWrite();

  • Назначение: Запись в один регистр «Coil» (он же «Discrete Output»).
  • Синтаксис: coilWrite( [ АДРЕС_МОДУЛЯ ] , АДРЕС_РЕГИСТРА , ДАННЫЕ );
  • Параметры:
    • АДРЕС_МОДУЛЯ: uint8_t — значение от 1 до 247.
    • АДРЕС_РЕГИСТРА: uint16_t — значение от 0 до 9999.
    • ДАННЫЕ: bool — значение для записи 0 или 1.
  • Возвращаемое значение: bool — результат записи значения в регистр (true или false).
  • Примечание: Если адрес модуля не указан, команда будет отправлена всем устройствам.
  • Пример без проверки записи:
modbus.coilWrite(9, 0, 1); // Для модуля с адресом 9, в регистр "Coil" с адресом 0, записываем значение 1.
  • Пример с проверкой:
bool i = modbus.coilWrite(9, 0, 1); // Для модуля с адресом 9, в регистр "Coil" с адресом 0, записываем значение 1.
if(i){ Serial.println( "Ok"  ); }   // Выводим сообщение о успешной записи.
else { Serial.println("Error"); }   // Выводим сообщение о ошибке записи.

Функция holdingRegisterWrite();

  • Назначение: Запись в один регистр «Holding Register» (он же «Analog Output»).
  • Синтаксис: holdingRegisterWrite( [ АДРЕС_МОДУЛЯ ] , АДРЕС_РЕГИСТРА , ДАННЫЕ );
  • Параметры:
    • АДРЕС_МОДУЛЯ: uint8_t — значение от 1 до 247.
    • АДРЕС_РЕГИСТРА: uint16_t — значение от 0 до 9999.
    • ДАННЫЕ: uint16_t — значение для записи от 0 до 65535.
  • Возвращаемое значение: bool — результат записи значения в регистр (true или false).
  • Примечание: Если адрес модуля не указан, команда будет отправлена всем устройствам.
  • Пример без проверки записи:
modbus.holdingRegisterWrite(9, 0, 1000); // Для модуля с адресом 9, в регистр "Holding Register" с адресом 0, записываем значение 1000.
  • Пример с проверкой:
bool i = modbus.holdingRegisterWrite(9, 0, 1000); // Для модуля с адресом 9, в регистр "Holding Register" с адресом 0, записываем значение 1000.
if(i){ Serial.println( "Ok"  ); }                 // Выводим сообщение о успешной записи.
else { Serial.println("Error"); }                 // Выводим сообщение о ошибке записи.

Функция registerMaskWrite();

  • Назначение: Запись масок в один регистр «Holding Register» (он же «Analog Output»).
  • Синтаксис: registerMaskWrite( [ АДРЕС_МОДУЛЯ ] , АДРЕС_РЕГИСТРА , AND , OR );
  • Параметры:
    • АДРЕС_МОДУЛЯ: uint8_t — значение от 1 до 247.
    • АДРЕС_РЕГИСТРА: uint16_t — значение от 0 до 9999.
    • AND: uint16_t — значение маски AND от 0 до 65535.
    • OR: uint16_t — значение маски OR от 0 до 65535.
  • Возвращаемое значение: bool — результат записи масок в регистр (true или false).
  • Примечание:
    • Если адрес модуля не указан, команда будет отправлена всем устройствам.
    • Результат записи вычисляется по формуле: REG = ( REG & AND ) | ( OR & ~AND ).
    • Каждый бит маски AND запрещает менять значение того же бита в регистре.
    • Маска OR содержит биты для записи в регистр, если это не запрещено маской AND.
    • Пример:
      • Допустим сейчас значение регистра = ( 0101 0101 0101 0101 )2.
      • Если маска AND = ( 0000 0000 1111 1111 )2, то она запрещает менять 8 младших битов.
      • Для маски OR возьмем значение = ( 0111 0111 0111 0111 )2.
      • После записи, значение регистра будет = ( 0111 0111 0101 0101 )2.
      • 8 старших битов взяли значение из OR, а 8 младших остались без изменений.
      • Так можно менять отдельные биты, без предварительного чтения регистра.
  • Пример без проверки записи:
modbus.registerMaskWrite(9, 0, 0x00FF, 0x7777); // Для модуля с адресом 9, в регистр "Holding Register" с адресом 0, записываем маски AND=0x00FF и OR=0x7777.
  • Пример с проверкой:
bool i = modbus.registerMaskWrite(9, 0, 0x00FF, 0x7777); // Для модуля с адресом 9, в регистр "Holding Register" с адресом 0, записываем маски AND=0x00FF и OR=0x7777.
if(i){ Serial.println( "Ok"  ); }                        // Выводим сообщение о успешной записи.
else { Serial.println("Error"); }                        // Выводим сообщение о ошибке записи.

Функция beginTransmission();

  • Назначение: Инициализация записи в несколько регистров «Coils» или «Holding Registers».
  • Синтаксис: beginTransmission( [ АДРЕС_МОДУЛЯ ], ТИП, АДРЕС_РЕГИСТРА, КОЛИЧЕСТВО);
  • Параметры:
    • АДРЕС_МОДУЛЯ: uint8_t — значение от 1 до 247.
    • ТИП — может принимать значение COILS или HOLDING_REGISTERS.
    • АДРЕС_РЕГИСТРА: uint16_t — адрес первого регистра для записи от 0 до 9999.
    • КОЛИЧЕСТВО: uint16_t — количество записываемых регистров.
  • Возвращаемое значение: bool — результат инициализации записи (true или false).
  • Примечание:
    • Если адрес модуля не указан, команда будет отправлена всем устройствам.
    • Если задан некорректный диапазон адресов регистров, не будет записан ни один регистр.
    • Данные для записи необходимо ставить в очередь функцией write().
    • Запись данных поставленных в очередь осуществляется функцией endTransmission().
  • Пример для функций beginTransmission(), write() и endTransmission(), указан ниже.

Функция write();

  • Назначение: Постановка значения в очередь на запись, после beginTransmission().
  • Синтаксис: write( ДАННЫЕ );
  • Параметр: ДАННЫЕ uint16_t — значение 0/1 для «Coils», или 0…65535 для «Holding Registers».
  • Возвращаемое значение: bool — результат постановки значения в очередь (true или false).
  • Пример для функций beginTransmission(), write() и endTransmission(), указан ниже.

Функция endTransmission();

  • Назначение: Выполнение инициированной ранее записи.
  • Синтаксис: endTransmission();
  • Параметр: Нет.
  • Возвращаемое значение: bool — результат записи (true или false).
  • Пример без проверки записи:
modbus.beginTransmission(9, COILS, 0, 3);             // Инициируем запись для модуля с адресом 9, в регистры "Coils", начиная с адреса 0, всего 3 значения (3 регистра).
modbus.write( 0 );                                    // Ставим значение в очередь на запись. Это значение будет записано в регистр, адрес которого был указан в modbus.beginTransmission().
modbus.write( 1 );                                    // Ставим значение в очередь на запись. Это значение будет записано в следующий регистр по порядку.
modbus.write( 0 );                                    // Ставим значение в очередь на запись. Это значение будет записано в следующий регистр по порядку.
modbus.endTransmission();                             // Выполняем запись.
                                                      //
modbus.beginTransmission(9, HOLDING_REGISTERS, 5, 2); // Инициируем запись для модуля с адресом 9, в регистры "Holding Registers", начиная с адреса 5, всего 2 значения (2 регистра).
modbus.write( 11111 );                                // Ставим значение в очередь на запись. Это значение будет записано в регистр, адрес которого был указан в modbus.beginTransmission().
modbus.write( 22222 );                                // Ставим значение в очередь на запись. Это значение будет записано в следующий регистр по порядку.
modbus.endTransmission();                             // Выполняем запись.
  • Пример с проверкой:
modbus.beginTransmission(9, HOLDING_REGISTERS, 5, 2); // Инициируем запись для модуля с адресом 9, в регистры "Holding Registers", начиная с адреса 5, всего 2 значения (2 регистра).
modbus.write( 11111 );                                // Ставим значение в очередь на запись. Это значение будет записано в регистр, адрес которого был указан в modbus.beginTransmission().
modbus.write( 22222 );                                // Ставим значение в очередь на запись. Это значение будет записано в следующий регистр по порядку.
bool i = modbus.endTransmission();                    // Выполняем запись.
if(i){ Serial.println( "Ok"  ); }                     // Выводим сообщение о успешной записи.
else { Serial.println("Error"); }                     // Выводим сообщение о ошибке записи.

Функция requestFrom();

  • Назначение: Чтение из нескольких регистров указанного типа.
  • Синтаксис: requestFrom( [ АДРЕС_МОДУЛЯ ], ТИП, АДРЕС_РЕГИСТРА, КОЛИЧЕСТВО);
  • Параметры:
    • АДРЕС_МОДУЛЯ: uint8_t — значение от 1 до 247.
    • ТИП: COILS, или DISCRETE_INPUTS, или HOLDING_REGISTERS, или INPUT_REGISTERS.
    • АДРЕС_РЕГИСТРА: uint16_t — адрес первого читаемого регистра от 0 до 9999.
    • КОЛИЧЕСТВО: uint16_t — количество читаемых регистров.
  • Возвращаемое значение: uint16_t — количество прочитанных регистров, или 0 при неудаче.
  • Примечание:
    • Если адрес модуля не указан, команда будет отправлена всем устройствам.
    • Если задан некорректный диапазон адресов регистров, не будет прочитан ни один регистр.
    • Для получения прочитанных данных нужно обращаться к функции read().
    • Количество оставшихся доступных данных можно узнать функцией available().
  • Пример для функций requestFrom(), available() и read(), указан ниже.

Функция available();

  • Назначение: Получение количества значений, доступных для чтения функцией read().
  • Синтаксис: available();
  • Параметры: Нет.
  • Возвращаемое значение: uint16_t — количество значений доступных для чтения read().
  • Примечание: При обращении к любой функции библиотеки кроме available() и read(), буфер доступных значений будет очищен или перезаписан.
  • Пример для функций requestFrom(), available() и read(), указан ниже.

Функция read();

  • Назначение: Получение очередного прочитанного значения.
  • Синтаксис: read();
  • Параметры: Нет.
  • Возвращаемое значение: int32_t — очередное прочитанное значение, или -1 при неудаче.
  • Примечание: При обращении к любой функции библиотеки кроме available() и read(), буфер доступных значений будет очищен или перезаписан.
  • Пример без проверки чтения:
modbus.requestFrom(9, COILS, 0, 3);             // Читаем из модуля с адресом 9, регистры "Coils", начиная с адреса 0, всего 3 значения (3 регистра).
while( modbus.available() ){                    // Пока есть значения доступные для функции read()...
    Serial.println( modbus.read() );            // Выводим очередное прочитанное значение.
}                                               // 
                                                //
modbus.requestFrom(9, HOLDING_REGISTERS, 5, 2); // Читаем из модуля с адресом 9, регистры "Holding Registers", начиная с адреса 5, всего 2 значения (2 регистра).
while( modbus.available() ){                    // Пока есть значения доступные для функции read()...
    Serial.println( modbus.read() );            // Выводим очередное прочитанное значение.
}                                               // 
  • Пример с проверкой:
if( modbus.requestFrom(9, HOLDING_REGISTERS, 5, 2) ){ // Читаем из модуля с адресом 9, регистры "Holding Registers", начиная с адреса 5, всего 2 значения (2 регистра).
    while( modbus.available() ){                      // Пока есть значения доступные для функции read()...
        Serial.println( modbus.read() );              // Выводим очередное прочитанное значение.
    }                                                 //
}else{ Serial.println("Error"); }                     // Не удалось прочитать ни одного регистра.

Функция end();

  • Назначение: Функция для совместимости с библиотекой ArduinoModbus.
  • Синтаксис: end();
  • Параметры: Нет
  • Возвращаемое значение: Нет.
  • Примечание: Функция позволяет перенести код написанный для библиотеки ArduinoModbus в скетч с библиотекой iarduino_Modbus, без сообщений об ошибках, но сама ничего не делает.
  • Пример:
modbus.end(); // Пустая функция.
Serial.end(); // Отключение шины. Функцию можно применять как к аппаратным шинам UART, так и к программной.

Функция exceptionStatusRead();

  • Назначение: Чтение состояния 8 статусных выходов.
  • Синтаксис: exceptionStatusRead( [ АДРЕС_МОДУЛЯ ] );
  • Параметр: АДРЕС_МОДУЛЯ uint8_t — значение от 1 до 247.
  • Возвращаемое значение: int16_t — байт битов, или -1 при неудаче.
  • Примечание: Если адрес модуля не указан, команда будет отправлена всем устройствам.
  • Пример без проверки чтения:
uint8_t i = modbus.exceptionStatusRead(9); // Читаем статусные выходы устройства с адресом 9.
bool pin1 = bitRead(i, 0);                 // Получаем состояние 1 статусного выхода.
bool pin2 = bitRead(i, 1);                 // Получаем состояние 2 статусного выхода.
bool pin8 = bitRead(i, 7);                 // Получаем состояние 8 статусного выхода.
  • Пример с проверкой:
int16_t i = modbus.exceptionStatusRead(9); // Читаем статусные выходы устройства с адресом 9.
if( i<0 ){                                 // Если чтение не выполнено ...
    Serial.println( "Error"  ); }          // Выводим сообщение о ошибке чтения.
}else{                                     // Если чтение выполнено ...
    bool pin1 = bitRead(i, 0);             // Получаем состояние 1 статусного выхода.
    bool pin2 = bitRead(i, 1);             // Получаем состояние 2 статусного выхода.
    bool pin8 = bitRead(i, 7);             // Получаем состояние 8 статусного выхода.
}                                          //

Функция getSate();

  • Назначение: Чтение состояния устройства.
  • Синтаксис: getSate( [ АДРЕС_МОДУЛЯ ] );
  • Параметр: АДРЕС_МОДУЛЯ uint8_t — значение от 1 до 247.
  • Возвращаемое значение: int8_t — состояние ( 0-свободно, 1-занято ), или -1 при неудаче.
  • Примечание: Если адрес модуля не указан, команда будет отправлена всем устройствам.
  • Пример без проверки чтения:
bool i = modbus.getSate(9); // Читаем состояние устройства с адресом 9.
if(i){ Serial.println( "Устройство занято"   ); }
else { Serial.println( "Устройство свободно" ); }
  • Пример с проверкой:
int8_t i = modbus.getSate(9); // Читаем состояние устройства с адресом 9.
if( i<0  ){ Serial.println( "Ошибка чтения"       ); }
if( i==0 ){ Serial.println( "Устройство свободно" ); }
if( i>0  ){ Serial.println( "Устройство занято"   ); }

Функция diagnostic();

  • Назначение: Выполнение команды диагностики.
  • Синтаксис: diagnostic( АДРЕС_МОДУЛЯ , НОМЕР_ФУНКЦИИ_ДИАГНОСТИКИ [ , ДАННЫЕ ] );
  • Параметры:
    • АДРЕС_МОДУЛЯ: uint8_t — значение от 1 до 247.
    • НОМЕР_ФУНКЦИИ_ДИАГНОСТИКИ: uint16_t — значение от 0 до 65535.
      • 0 — Проверка связи. Результатом диагностики будет значение аргумента ДАННЫЕ.
      • 1 — Перезагрузка коммуникаций и диагностика внутренних систем устройства.
      • 2 — Получить значение регистра диагностики. Каждый бит информирует о ошибке.
      • 3 — Сменить символ конца пакета ASCII на значение аргумента ДАННЫЕ.
      • 4 — Включить режим «Только прослушивание» (Listen Only Mode).
      • 10 — Сбросить все счетчики и регистр диагностики.
      • 11 — Получить значение счётчика всех запросов на шине.
      • 12 — Получить значение счётчика запросов с ошибками CRC.
      • 13 — Получить значение счётчика ответов об ошибках.
      • 14 — Получить значение счётчика запросов адресованных устройству.
      • 15 — Получить значение счётчика запросов которые остались без ответа.
      • 16 — Получить значение счётчика ответов об ошибках CODE_ERR_NAK.
      • 17 — Получить значение счётчика ответов о «занятости» CODE_ERR_BUSY.
      • 18 — Получить значение счётчика ошибок переполнения приема символов.
      • Остальные номера функций доступны в документации протокола Modbus.
    • ДАННЫЕ: uint16_t — необязательное значение от 0 до 65535.
  • Возвращаемое значение: int32_t — результат диагностики (0…65535), или -1 при неудаче.
  • Примечание:
    • Счётчик событий, это счётчик успешно выполненных запросов.
    • Счётчик не реагирует на успешное выполнение команды чтения его значения.
  • Пример:
uint16_t i = modbus.diagnostic(9, 0, 12345); // Проверка связи устройства с адресом 9.
if( i!=12345 ){ Serial.println("Error"); }   // Выявлена ошибка связи.
  • Пример:
int32_t i = modbus.diagnostic(9, 2); // Получить значение регистра диагностики устройства с адресом 9.
if(i<0){ Serial.println( "Ошибка чтения" );                               }else
if( i ){ Serial.println( "Модуль обнаружил сбои в работе своих систем" ); }else
       { Serial.println( "Устройство работает без ошибок" );              }

Функция getEventCounter();

  • Назначение: Чтение счетчика событий.
  • Синтаксис: getEventCounter( [ АДРЕС_МОДУЛЯ ] );
  • Параметр: АДРЕС_МОДУЛЯ uint8_t — значение от 1 до 247.
  • Возвращаемое значение: int32_t — значение счётчика событий (0…65535), или -1 при неудаче.
  • Примечание:
    • Если адрес модуля не указан, команда будет отправлена всем устройствам.
    • Счётчик событий, это счётчик успешно выполненных запросов.
    • Счётчик не реагирует на успешное выполнение команды чтения его значения.
  • Пример без проверки чтения:
uint16_t i = modbus.getEventCounter(9); // Читаем счётчик событий устройства с адресом 9.
Serial.println( (String) "Устройство успешно выполнило "+i+" запросов." );
  • Пример с проверкой:
int32_t i = modbus.getEventCounter(9); // Читаем счётчик событий устройства с адресом 9.
if(i<0){ Serial.println( "Ошибка чтения" ); }
else   { Serial.println( (String) "Устройство успешно выполнило "+i+" запросов." ); }

Функция getEventLog();

  • Назначение: Чтение журнала событий.
  • Синтаксис: getEventLog( [ АДРЕС_МОДУЛЯ ] );
  • Параметр: АДРЕС_МОДУЛЯ uint8_t — значение от 1 до 247.
  • Возвращаемое значение: uint8_t — количество данных, доступных для чтения функцией read().
  • Примечание:
    • Если адрес модуля не указан, команда будет отправлена всем устройствам.
    • Для получения прочитанных данных нужно обращаться к функции read().
    • Количество оставшихся доступных данных можно узнать функцией available().
    • Данные будут получены в следующем порядке:
      • uint16_t — слово состояния (0x0000 — устройство свободно, 0xFFFF — устройство занято).
      • uint16_t — значение счётчика событий (0…65535).
      • uint16_t — значение счётчика всех сообщений на шине (0…65535).
      • uint8_t — массив байтов журнала событий (не более 64 байт).
  • Пример без проверки чтения:
modbus.getEventLog(9); // Читаем журнал событий устройства с адресом 9.
Serial.print( "Слово состояния   = " ); Serial.println( modbus.read() );
Serial.print( "Счётчик событий   = " ); Serial.println( modbus.read() );
Serial.print( "Счётчик сообщений = " ); Serial.println( modbus.read() );
Serial.println( "Байты журнала событий:" );
while( modbus.available() ){ Serial.println( modbus.read() ); }
  • Пример с проверкой:
if( modbus.getEventLog(9) ){ // Читаем журнал событий устройства с адресом 9.
    if( modbus.available() ){ Serial.print( "Слово состояния   = " ); Serial.println( modbus.read() ); }
    if( modbus.available() ){ Serial.print( "Счётчик событий   = " ); Serial.println( modbus.read() ); }
    if( modbus.available() ){ Serial.print( "Счётчик сообщений = " ); Serial.println( modbus.read() ); }
    Serial.println( "Байты журнала событий:" );
    while( modbus.available() ){ Serial.println( modbus.read() ); }
}else{
    Serial.println("Error"); // Модуль не поддерживает чтение журнала событий.
}

Функция getInfo();

  • Назначение: Чтение информации об устройстве.
  • Синтаксис: getInfo( [ АДРЕС_МОДУЛЯ ] );
  • Параметр: АДРЕС_МОДУЛЯ uint8_t — значение от 1 до 247.
  • Возвращаемое значение: uint8_t — количество данных, доступных для чтения функцией read().
  • Примечание:
    • Если адрес модуля не указан, команда будет отправлена всем устройствам.
    • Для получения прочитанных данных нужно обращаться к функции read().
    • Количество оставшихся доступных данных можно узнать функцией available().
    • Данные будут получены в следующем порядке:
      • uint8_t — идентификатор линейки устройств.
      • uint8_t — индикатор пуска: (0x00 — off, 0xFF — on).
      • uint8_t — массив данных об устройстве (количество байт зависит от устройства).
  • Пример без проверки чтения:
modbus.getInfo(9); // Читаем информацию о устройстве с адресом 9.
Serial.print( "Идентификатор линейки = "  ); Serial.println( modbus.read() );
Serial.print( "Индикатор пуска = "        ); Serial.println( modbus.read() );
Serial.println( "Остальные байты данных:" );
while( modbus.available() ){ Serial.println( modbus.read() ); }
  • Пример с проверкой:
if( modbus.getInfo(9) ){ // Читаем информацию о устройстве с адресом 9.
    if( modbus.available() ){ Serial.print( "Идентификатор линейки = " ); Serial.println( modbus.read() ); }
    if( modbus.available() ){ Serial.print( "Индикатор пуска = "       ); Serial.println( modbus.read() ); }
    Serial.println( "Остальные байты данных:" );
    while( modbus.available() ){ Serial.println( modbus.read() ); }
}else{
    Serial.println("Error"); // Модуль не поддерживает чтение информации о устройстве.
}
  • Пример для модулей iarduino:
if( modbus.getInfo(9) ){ // Читаем информацию о устройстве с адресом 9.
    if( modbus.available()   ){ Serial.println((String) "Идентификатор линейки "+modbus.read() ); }
    if( modbus.available()   ){ Serial.println((String) "Индикатор пуска "+modbus.read() ); }
    if( modbus.available()   ){ Serial.println((String) "Адрес модуля на шине "+modbus.read() ); }
    if( modbus.available()   ){ Serial.println((String) "Идентификатор устройства "+modbus.read() ); }
    if( modbus.available()   ){ Serial.println((String) "Версия прошивки "+modbus.read() ); }
    if( modbus.available()>=2){ Serial.println((String) "Регистр диагностики "+((modbus.read()<<8)|modbus.read()); }
    if( modbus.available()   ){ Serial.println((String) "Младший байт регистра диагностики "+modbus.read() ); }
    if( modbus.available()   ){ Serial.println((String) "Количество регистров DO "+modbus.read() ); }
    if( modbus.available()   ){ Serial.println((String) "Количество регистров DI "+modbus.read() ); }
    if( modbus.available()   ){ Serial.println((String) "Количество регистров AO "+modbus.read() ); }
    if( modbus.available()   ){ Serial.println((String) "Количество регистров AI "+modbus.read() ); }
    if( modbus.available()>=4){ Serial.println((String) "Задержка до ответа в мс"+(uint32_t)((modbus.read()<<24)|(modbus.read()<<16)|(modbus.read()<<8)|modbus.read()) ); }
    Serial.println( "Остальные байты данных:" );
    while( modbus.available() ){ Serial.println( modbus.read() ); }
}else{
    Serial.println("Error"); // Модуль не поддерживает чтение информации о устройстве.
}

Функция lastError();

  • Назначение: Получение кода причины последней ошибки.
  • Синтаксис: lastError();
  • Параметры: Нет.
  • Возвращаемое значение: uint8_t — код причины последней ошибки.
    • ERROR_ILLEGAL_FUNCTION — Команда запроса не поддерживается модулем.
    • ERROR_ILLEGAL_ADDRESS — У модуля отсутствует регистр с указанным адресом.
    • ERROR_ILLEGAL_VALUE — Недопустимое значение в поле данных запроса.
    • ERROR_DEVICE_FAILURE — Любая невосстановимая ошибка кроме первых трёх.
    • ERROR_ACKNOWLEDGE — Модуль принял запрос, но на обработку требуется время.
    • ERROR_DEVICE_BUSY — Ведомый занят, запрос проигнорирован.
    • ERROR_MEMORY_PARITY — Ошибка чтения/записи файла.
    • ERROR_GATEWAY_UNAVAILABLE — Шина перегружена данными или не настроена.
    • ERROR_GATEWAY_NO_DEVICE — Ведомого устройства нет или от него нет ответа.
    • ERROR_SYNTAX — Ошибка синтаксиса.
    • ERROR_ADR_IARDUINO — Ошибка назначения или сортировки адресов устройств iarduino.
    • ERROR_ADR_RESPONSE — Несовпадение адреса регистра в ответе.
    • ERROR_VAL_RESPONSE — Несовпадение данных в ответе.
    • ERROR_CRC_RESPONSE — Несовпадение CRC в принятом ответе.
    • ERROR_LEN_REQUEST — Размер отправляемого запроса превышает размер буфера.
    • ERROR_LEN_RESPONSE — Размер полученного ответа превышает размер буфера.
  • Пример:
int8_t i = modbus.coilRead(9, 100); // Читаем из модуля с адресом 9 регистр "Coil" (DO) с адресом 100.
if( i<0 ){
    switch( modbus.lastError() ){
        case ERROR_ILLEGAL_FUNCTION:    Serial.println( F("Модуль не поддерживает команду или функцию запроса")           ); break;
        case ERROR_ILLEGAL_ADDRESS:     Serial.println( F("Запрос содержит недопустимый адрес регистра")                  ); break;
        case ERROR_ILLEGAL_VALUE:       Serial.println( F("Запрос содержит недопустимое значение в поле данных")          ); break;
        case ERROR_DEVICE_FAILURE:      Serial.println( F("Ошибка запроса не связана с командой, адресом или данными")    ); break;
        case ERROR_ACKNOWLEDGE:         Serial.println( F("Модуль сообщает что занят и обработает запрос позже")          ); break;
        case ERROR_DEVICE_BUSY:         Serial.println( F("Модуль сообщает что занят и не будет обрабатывать запрос")     ); break;
        case ERROR_MEMORY_PARITY:       Serial.println( F("Модуль сообщает что произошла ошибка чтения/записи файла")     ); break;
        case ERROR_GATEWAY_UNAVAILABLE: Serial.println( F("Шлюз неправильно настроен или перегружен запросами")           ); break;
        case ERROR_GATEWAY_NO_DEVICE:   Serial.println( F("Модуль которому адресован запрос не отвечает")                 ); break;
        case ERROR_SYNTAX:              Serial.println( F("Ошибка синтаксиса")                                            ); break;
        case ERROR_ADR_IARDUINO:        Serial.println( F("Ошибка назначения или сортировки адресов устройств iarduino")  ); break;
        case ERROR_ADR_RESPONSE:        Serial.println( F("Ответ от модуля содержит недопустимый адрес регистра")         ); break;
        case ERROR_VAL_RESPONSE:        Serial.println( F("Ответ от модуля содержит недопустимое значение в поле данных") ); break;
        case ERROR_CRC_RESPONSE:        Serial.println( F("Несовпадение CRC в ответе модуля")                             ); break;
        case ERROR_LEN_REQUEST:         Serial.println( F("Размер запроса к модулю превышает буфер обмена или ADU")       ); break;
        case ERROR_LEN_RESPONSE:        Serial.println( F("Размер ответа от модуля превышает буфер обмена")               ); break;
        default:                        Serial.println( F("Неизвестная ошибка")                                           ); break;
    }
}else{ Serial.println( F("Ошибки не обнаружены") ); }

Функции для работы с адресами:

Функции перечисленные в данном разделе предназначены для устройств iarduino.

Функция checkID();

  • Назначение: Функция определения принадлежности устройства.
  • Синтаксис: checkID( АДРЕС_МОДУЛЯ );
  • Параметр: АДРЕС_МОДУЛЯ uint8_t — значение от 1 до 247.
  • Возвращаемое значение: uint8_t — принадлежность устройства:
    • DEVICE_MB_ABSENT — Устройство с указанным адресом отсутствует на шине.
    • DEVICE_MB_DOUBLE — Адрес принадлежит двум и более устройствам.
    • DEVICE_MB_IARDUINO — Устройство принадлежит линейке iarduino.
    • DEVICE_MB_UNKNOWN — Устройство не принадлежит линейке iarduino.
  • Пример:
uint8_t i = modbus.checkID(9); // Определяем принадлежность устройства с адресом 9.
Serial.print(F("Адрес 9 на шине Modbus принадлежит "));
switch(i){
    case DEVICE_MB_ABSENT:   Serial.println(F("отсутствующему устройству."  )); break;
    case DEVICE_MB_DOUBLE:   Serial.println(F("двум и более устройствам."   )); break;
    case DEVICE_MB_IARDUINO: Serial.println(F("устройству линейки iarduino.")); break;
    case DEVICE_MB_UNKNOWN:  Serial.println(F("устройству не iarduino."     )); break;
}

Функция changeID();

  • Назначение: Функция изменения адреса устройства iarduino на шине.
  • Синтаксис: changeID( СТАРЫЙ_АДРЕС , НОВЫЙ_АДРЕС );
  • Параметры:
    • СТАРЫЙ_АДРЕС: uint8_t — значение от 1 до 247.
    • НОВЫЙ_АДРЕС: uint8_t — значение от 1 до 247.
  • Возвращаемое значение: bool — результат изменения адреса (true или false).
  • Примечание:
    • Новый адрес сохраняется в энергонезависимую память устройства.
    • Выполнение функции занимает более 120 мс.
  • Пример без проверки:
modbus.changeID(9, 10); // Меняем адрес устройства с 9 на 10.
  • Пример с проверкой:
bool i = modbus.changeID(9, 10);  // Меняем адрес устройства с 9 на 10.
if(i){ Serial.println( "Ok"  ); } // Выводим сообщение о успешной смене адреса.
else { Serial.println("Error"); } // Выводим сообщение о ошибке смены адреса.

Функция sortID();

  • Назначение: Функция сортировки адресов устройств iarduino по порядку.
  • Синтаксис: sortID( ПЕРВЫЙ_АДРЕС_СПИСКА );
  • Параметр: ПЕРВЫЙ_АДРЕС_СПИСКА uint8_t — значение от 1 до 247.
  • Возвращаемое значение: int16_t — количество изменённых адресов, или -1 при неудаче.
  • Примечание:
    • Функция присваивает всем устройствам iarduino новые адреса по порядку.
    • Функция работает с устройствами iarduino даже если у них одинаковые адреса.
    • Функция пропускает адреса принадлежащие устройствам не iarduino.
    • Новые адреса сохраняются в энергонезависимую память устройств iaduino.
    • Выполнение функции занимает несколько секунд.
  • Пример без проверки:
modbus.sortID(10); // Меняем адреса устройств в список начинающийся с адреса 10 (например, для трёх устройств, адреса станут: 10,11,12).
  • Пример с проверкой:
int i=modbus.sortID(10); // Меняем адреса устройств в список начинающийся с адреса 10 (например, для трёх устройств, адреса станут: 10,11,12).
if(i<0){Serial.println( "Не удалось отсортировать все адреса устройств" ); }
else   {Serial.println( (String) "Изменены адреса "+i+" устройств" );      }

Функция searchERR();

  • Назначение: Обнаружение устройств iarduino с одинаковыми адресами.
  • Синтаксис: searchERR();
  • Параметры: Нет.
  • Возвращаемое значение: uint8_t — количество адресов, доступных для чтения функцией read().
  • Примечание:
    • Для получения найденных адресов нужно обращаться к функции read().
    • Количество оставшихся доступных данных можно узнать функцией available().
    • Выполнение функции занимает несколько секунд.
  • Пример:
uint8_t i = modbus.searchERR(); // Ищем адреса принадлежащие двум и более устройствам.
Serial.println( (String) "На шине есть "+i+" адресов принадлежащих нескольким устройствам" );
// Выводим найденные адреса:
while( modbus.available() ){ Serial.println( modbus.read() ); }

Функция findID();

  • Назначение: Обнаружение всех адресов ведомых устройств на шине.
  • Синтаксис: findID( [ МОДЕЛЬ ] );
  • Параметр: МОДЕЛЬ uint8_t — идентификатор искомых устройств (от 1 до 255).
  • Возвращаемое значение: uint8_t — количество адресов, доступных для чтения функцией read().
  • Примечание:
    • Для получения найденных адресов нужно обращаться к функции read().
    • Количество оставшихся доступных данных можно узнать функцией available().
    • Если вызвать функцию без аргумента то будут обнаружены все адреса всех устройств.
    • Если вызвать функцию с идентификатором устройства, то будут найдены только те адреса, которые принадлежат устройствам iarduino указанной модели.
    • Выполнение функции занимает несколько секунд.
  • Пример обнаружения всех адресов:
uint8_t i = modbus.findID(); // Ищем все адреса всех устройств (даже не iarduino).
Serial.println( (String) "На шине есть "+i+" устройств с адресами:" );
// Выводим найденные адреса:
while( modbus.available() ){ Serial.println( modbus.read() ); }
  • Пример обнаружения адресов принадлежащих устройствам iarduino указанной модели:
uint8_t i = modbus.findID(1); // Ищем адреса устройств iarduino с идентификатором 1.
Serial.println( (String) "На шине есть "+i+" устройств с адресами:" );
// Выводим найденные адреса:
while( modbus.available() ){ Serial.println( modbus.read() ); }
  • Пример получения идентификатора (номера модели) устройства iarduino:
uint8_t i;
if( modbus.getInfo(9) ){                           // Читаем информацию о устройстве с адресом 9.
    modbus.read(); modbus.read(); modbus.read();   // Пропускаем ненужные данные.
    if( modbus.available() ){ i = modbus.read(); } // Получаем идентификатор устройства.
}

Ссылки:

  • Библиотека iarduino_Modbus.

Modbus — коммуникационный протокол, основанный на архитектуре «клиент-сервер».
Широко применяется в промышленности для организации связи между
электронными устройствами. Может использовать для передачи данных
последовательные линии связи RS-485, RS-422, RS-232, а также сети TCP/IP (Modbus TCP).

История

Modbus был разработан фирмой Modicon (в настоящее время принадлежит Schneider Electric) для использования в её контроллерах с программируемой логикой. Впервые спецификация протокола была опубликована в 1979 году.[1]
Это был открытый стандарт, описывающий формат сообщений и способы их
передачи в сети состоящей из различных электронных устройств.

Первоначально контроллеры MODICON использовали последовательный интерфейс RS-232.[1]
Позднее стал применяться интерфейс RS-485, так как он обеспечивает
более высокую надёжность, позволяет использовать более длинные линии
связи и подключать к одной линии несколько устройств.

Многие производители электронного оборудования поддержали стандарт,
на рынке появились сотни использующих его изделий. В настоящее время
развитием Modbus занимается некоммерческая организация Modbus-IDA, созданная производителями и пользователями электронных приборов[2].

Введение

Modbus относится к протоколам прикладного уровня сетевой модели OSI.[3] Контроллеры на шине Modbus взаимодействуют, используя клиент-серверную модель, основанную на транзакциях, состоящих из запроса и ответа.

Обычно в сети есть только один клиент, так называемое, «главное» (англ. master) устройство, и несколько серверов — «подчиненных» (slaves)
устройств. Главное устройство инициирует транзакции (передаёт запросы).
Подчиненные устройства передают запрашиваемые главным устройством
данные, или производят запрашиваемые действия. Главный может
адресоваться индивидуально к подчиненному или инициировать передачу
широковещательного сообщения для всех подчиненных устройств.
Подчиненное устройство формирует сообщение и возвращает его в ответ на
запрос, адресованный именно ему. При получении широковещательного
запроса ответное сообщение не формируется.

Спецификация Modbus описывает структуру запросов и ответов. Их
основа — элементарный пакет протокола, так называемый PDU (Protocol
Data Unit). Структура PDU не зависит от типа линии связи и включает в
себя код функции и поле данных. Код функции кодируется однобайтовым
полем и может принимать значения в диапазоне 1…127. Диапазон значений
128…255 зарезервирован для кодов ошибок. Поле данных может быть
переменной длины. Размер пакета PDU ограничен 253 байтами.

Modbus PDU

номер функции данные
1 байт N < 253 (байт)

Для передачи пакета по физическим линиям связи PDU помещается в
другой пакет, содержащий дополнительные поля. Этот пакет носит название
ADU (Application Data Unit). Формат ADU зависит от типа линии связи.

Существуют три основных реализации протокола Modbus, две для
передачи данных по последовательным линиям связи, как медным
EIA/TIA-232-E (RS-232), EIA-422, EIA/TIA-485-A (RS-485), так и оптическим и радио:

  • Modbus RTU и
  • Modbus ASCII,

и для передачи данных по сетям Ethernet поверх TCP/IP:

  • Modbus TCP.

Общая структура ADU следующая (в зависимости от реализации, некоторые из полей могут отсутствовать):

адрес ведомого устройства код функции данные блок обнаружения ошибок

где

  • адрес ведомого устройства — адрес подчинённого устройства, к
    которому адресован запрос. Ведомые устройства отвечают только на
    запросы, поступившие в их адрес. Ответ также начинается с адреса
    отвечающего ведомого устройства, который может изменяться от 1 до 247.
    Адрес 0 используется для широковещательной передачи, его распознаёт
    каждое устройство, адреса в диапазоне 248…255 — зарезервированы;
  • номер функции — это следующее однобайтное поле кадра. Оно
    говорит ведомому устройству, какие данные или выполнение какого
    действия требует от него ведущее устройство;
  • данные — поле содержит информацию, необходимую ведомому
    устройству для выполнения заданной мастером функции или содержит
    данные, передаваемые ведомым устройством в ответ на запрос ведущего.
    Длина и формат поля зависит от номера функции;
  • блок обнаружения ошибок — контрольная сумма для проверки отсутствия ошибок в кадре.

Максимальный размер ADU для последовательных сетей RS232/RS485 — 256 байт, для сетей TCP — 260 байт.

Для Modbus TCP ADU выглядит следующим образом:

ид транзакции ид протокола длина пакета адрес ведомого устройства код функции данные

где

  • ид транзакции — два байта, обычно нули
  • ид протокола — два байта, нули
  • длина пакета — два байта, старший затем младший, длина следующей за этим полем части пакета
  • адрес ведомого устройства — адрес подчинённого устройства, к
    которому адресован запрос. Обычно игнорируется, если соединение
    установлено с конкретным устройством. Может использоваться, если
    соединение установлено с бриджом, который выводит нас, например, в сеть
    RS485.

Поле контрольной суммы в Modbus TCP отсутствует.

Категории кодов функций

В действующей в настоящее время спецификации протокола определяются три категории кодов функций:

Стандартные команды 
Их описание должно быть опубликовано и утверждено Modbus-IDA. Эта
категория включает в себя как уже определенные, так и свободные в
настоящее время коды.
Пользовательские команды 
Два диапазона кодов (от 65 до 72 и от 100 до 110), для которых
пользователь может реализовать произвольную функцию. При этом не
гарантируется, что какое-то другое устройство не будет использовать тот
же самый код для выполнения другой функции.
Зарезервированные 
В эту категорию входят коды функций, не являющиеся стандартными, но
уже используемые в устройствах, производимых различными компаниями. Это
коды 9, 10, 13, 14, 41, 42, 90, 91, 125, 126 и 127.

Модель данных

Одно из типичных применений протокола — чтение и запись данных в
регистры контроллеров. Спецификация протокола определяет четыре таблицы
данных:

Таблица Тип элемента Тип доступа
Дискретные входы (Discrete Inputs) один бит только чтение
Регистры флагов (Coils) один бит чтение и запись
Регистры ввода (Input Registers) 16-битное слово только чтение
Регистры хранения (Holding Registers) 16-битное слово чтение и запись

Доступ к элементам в каждой таблице осуществляется с помощью
16-битного адреса, первой ячейке соответствует адрес 0. Таким образом,
каждая таблица может содержать до 65536 элементов. Спецификация не
определяет, что физически должны представлять собой элементы таблиц и
по каким внутренним адресам устройства они должны быть доступны.
Например, допустимо организовать перекрывающиеся таблицы, В этом случае
команды работающие с дискретными данными и с 16-битными регистрами
будут фактически обращаться к одним и тем же данным.

Следует отметить, что со способом адресации данных связана
определённая путаница. Modbus был первоначально разработан для
контроллеров Modicon. В этих контроллерах для каждой из таблиц
использовалась специальная нумерация. Например, первому регистру ввода
соответствовал номер ячейки 30001, а первому регистру хранения — 40001.
Таким образом, регистру хранения с адресом 107 в команде Modbus
соответствовал регистр № 40108 контроллера. Хотя такое соответствие
адресов больше не является частью стандарта, некоторые программные
пакеты могут автоматически «корректировать» вводимые пользователем
адреса, например, вычитая 40001 из адреса регистра хранения.

PDU запроса и ответа для стандартных функций

номер
функции
запрос/ответ
1 (0x01) A1 A0 Q1 Q0
N D (N байт)
2 (0x02) A1 A0 Q1 Q0
N D (N байт)
3 (0x03) A1 A0 Q1 Q0
N D (N байт)
4 (0x04) A1 A0 Q1 Q0
N D (N байт)
5 (0x05) A1 A0 D1 D0
A1 A0 D1 D0
6 (0x06) A1 A0 D1 D0
A1 A0 D1 D0
15 (0x0F) A1 A0 Q1 Q0 N D (N байт)
A1 A0 Q1 Q0
16 (0x10) A1 A0 Q1 Q0 N D (N байт)
A1 A0 Q1 Q0
  • A1 и A0 — адрес элемента,
  • Q1 и Q0 — количество элементов,
  • N — количество байт данных
  • D — данные

Чтение данных

Для чтения значений из перечисленных выше таблиц данных используются функции с кодами 1—4 (шестнадцатеричные значения 0x01—0x04):

  • 1 (0x01) — чтение значений из нескольких регистров флагов (Read Coil Status)
  • 2 (0x02) — чтение значений из нескольких дискретных регистров (Read Discrete Inputs)
  • 3 (0x03) — чтение значений из нескольких регистров хранения (Read Holding Registers)
  • 4 (0x04) — чтение значений из нескольких регистров ввода (Read Input Registers)

Запрос состоит из адреса первого элемента таблицы, значение которого
требуется прочитать, и количества считываемых элементов. Адрес и
количество данных задаются 16-битными числами, старший байт каждого из
них передается первым.

В ответе передаются запрошенные данные. Количество байт данных
зависит от количества запрошенных элементов. Перед данными передается
один байт, значение которого равно количеству байт данных.

Значения регистров хранения и регистров ввода передаются начиная с
указанного адреса, по два байта на регистр, старший байт каждого
регистра передаётся первым:

байт 1 байт 2 байт 3 байт 4 байт N-1 байт N
RA,1 RA,0 RA+1,1 RA+1,0 RA+Q-1,1 RA+Q-1,0

Значения флагов и дискретных входов передаются в упакованном виде:
по одному биту на флаг. Единица означает включённое состояние, ноль —
выключенное. Значения запрошенных флагов заполняют сначала первый байт,
начиная с младшего бита, затем следующие байты, также от младшего бита
к старшим. Младший бит первого байта данных содержит значение флага,
указанного в поле «адрес». Если запрошено количество флагов, не кратное
восьми, то значения лишних битов заполняются нулями:

байт 1 байт N
FA+7 FA+6 FA+5 FA+4 FA+3 FA+2 FA+1 FA 0 0 FA+Q-1 FA+Q-2

Запись одного значения

  • 5 (0x05) — запись значения одного флага (Force Single Coil)
  • 6 (0x06) — запись значения в один регистр хранения (Preset Single Register)

Команда состоит из адреса элемента (2 байта) и устанавливаемого значения (2 байта).

Для регистра хранения значение является просто 16-битным словом.

Для флагов значение 0xFF00 означает включённое состояние, 0x0000 — выключенное, другие значения недопустимы.

Если команда выполнена успешно, ведомое устройство возвращает копию запроса.

Запись нескольких значений

  • 15 (0x0F) — запись значений в несколько регистров флагов (Force Multiple Coils)
  • 16 (0x10) — запись значений в несколько регистров хранения (Preset Multiple Registers)

Команда состоит из адреса элемента, количества изменяемых элементов,
количества передаваемых байт устанавливаемых значений и самих
устанавливаемых значений. Данные упаковываются так же, как в командах
чтения данных.

Ответ состоит из начального адреса и количества изменённых элементов.

Ниже приведён пример команды ведущего устройства и ответа ведомого (для Modbus RTU).

Направление передачи 00 адрес подчиненного устройства 01 номер функции 02 Адрес ст. байт 03 Адрес мл. байт 04 Количество флагов ст. байт 05 Количество флагов мл. байт 06 Количество байт данных 07 Данные (значения для флагов биты 0-7) 08 Данные (значения для флагов биты 8-15) 09 CRC мл. байт 0A CRC ст. байт

Master→Slave

0x01

0x0F

0x00

0x13

0x00

0x0A

0x02

0xCD

0x01

0x72

0xCB

Направление передачи 00 адрес подчиненного устройства 01 номер функции 02 Адрес ст. байт 03 Адрес мл. байт 04 Количество флагов ст. байт 05 Количество флагов мл. байт 05 CRC мл. байт 06 CRC ст. байт

 Контроль ошибок в протоколе Modbus RTU

Во время обмена данными могут возникать ошибки двух типов:

  • ошибки, связанные с искажениями при передаче данных;
  • логические ошибки.

Ошибки первого типа обнаруживаются при помощи фреймов символов, контроля чётности и циклической контрольной суммы CRC-16-IBM (используется число-полином = 0xA001).

RTU фрейм

В RTU режиме сообщение должно начинаться и заканчиваться интервалом
тишины — временем передачи не менее 3.5 символов при данной скорости в
сети. Первым полем затем передаётся адрес устройства.

Вслед за последним передаваемым символом также следует интервал
тишины продолжительностью не менее 3.5 символов. Новое сообщение может
начинаться после этого интервала.

Фрейм сообщения передаётся непрерывно. Если интервал тишины
продолжительностью 1.5 возник во время передачи фрейма, принимающее
устройство должно игнорировать этот фрейм как неполный.

Таким образом, новое сообщение должно начинаться не раньше 3.5 интервала, т.к. в этом случае устанавливается ошибка.

Немного об интервалах (речь идёт о Serial Modbus RTU): при скорости
9600 и 11 битах в кадре (стартовый бит + 8 бит данных + бит контроля
чётности + стоп-бит): 3.5 * 11 / 9600 = 0,00401041(6), т.е. более 4 мс;
1.5 * 11 / 9600 = 0,00171875, т.е. более 1 мс. Для скоростей более
19200 бод допускается использовать интервалы 1,75 и 0,75 мс
соответственно.

Логические ошибки

Для сообщений об ошибках второго типа протокол Modbus RTU
предусматривает, что устройства могут отсылать ответы,
свидетельствующие об ошибочной ситуации. Признаком того, что ответ
содержит сообщение об ошибке, является установленный старший бит кода
команды. Пример кадра при выявлении ошибки ведомым устройством, в ответ
на запрос приведён в (Таблица 2-1).

1. Если Slave принимает корректный запрос и может его нормально обработать, то возвращает нормальный ответ.

2. Если Slave не принимает какого-либо значения, никакого ответа не отправляется. Master диагностирует ошибку по таймауту.

3. Если Slave принимает запрос, но обнаруживает ошибку (parity, LRC,
or CRC), никакого ответа не отправляется. Master диагностирует ошибку
по таймауту.

4. Если Slave принимает запрос, но не может его обработать
(обращение к несуществующему регистру и т.д.), отправляется ответ
содержащий в себе данные об ошибке.

Таблица 2-1. Кадр ответа (Slave→Master) при возникновении ошибки modbus RTU

Направление передачи адрес подчинённого устройства номер функции данные (или код ошибки) CRC

Запрос (Master→Slave)

0x01

0x77

0xDD

0xC7 0xA9

Ответ (Slave→Master)

0x01

0xF7

0xEE

0xE6 0x7C

] Стандартные коды ошибок

01 Принятый код функции не может быть обработан на подчиненном.
02 Адрес данных, указанный в запросе, не доступен данному подчиненному.
03 Величина, содержащаяся в поле данных запроса, является не допустимой величиной для подчиненного.
04 Невосстанавливаемая ошибка имела место, пока подчиненный пытался выполнить затребованное действие.
05 Подчиненный принял запрос и обрабатывает его, но это требует много времени.
Этот ответ предохраняет главного от генерации ошибки таймаута.
06 Подчиненный занят обработкой команды.
Главный должен повторить сообщение позже, когда подчиненный освободится.
07 Подчиненный не может выполнить программную функцию, принятую в запросе.
Этот код возвращается для неудачного программного запроса, использующего функции с номерами 13 или 14.
Главный должен запросить диагностическую информацию или информацию об ошибках с подчиненного.
08 Подчиненный пытается читать расширенную память, но обнаружил ошибку паритета.
Главный может повторить запрос, но обычно в таких случаях требуется ремонт.

Примечания

  1. 1 2 Modbus interface tutorial
  2. About Modbus-IDA
  3. Understanding the Modbus Protocol

Ссылки на используемые в статье источники

  • Modbus-IDA некоммерческая организация, которая поддерживает данный протокол
  • Оригинальные спецификации протокола на английском языке
  • Modbus Specifications and Implementation Guides (ModBus TCPIP)
  • Введение в Modbus протокол
  • Описание стандартного протокола ModBus на английском языке
  • Linux C Programming Examples
  • Как Modbus работает
  • Site for software developers which develop, test modbus protocol in drivers, devices, PLC etc.
  • FreeModbus — ASCII/RTU и TCP для микроконтроллеров

Утилиты

  • Утилита опроса и записи данных по протоколу Modbus RTU/ASCII — некорректно формирует запросы по TCP/IP (не по Modbus TCPIP Specifications and Implementation)
  • modpoll (Master) (win32, linux, solaris, qnx6 )
    — Бесплатная консольная утилита опроса и записи данных по протоколу
    Modbus RTU/ASCII/TCPIP; достаточно удобная, но код выхода программы
    (при правильных параметрах) всегда 0 (версия 2.4.0) даже если Slave
    вернул код ошибки на запрос (обещано исправить в след релизе).
  • PeakHMI MODBUS TCP/IP Slave simulator (Win GUI) — бесплатная и очень удобная утилита, симулятор ModBus TCP/IP Slave
  • Simply Modbus — программа испытания Modbus.
  • [1] — свободная библиотека на языке Ruby.
  • NModbus — реализация протокола Modbus на языке C#.

Прочие ссылки

  • Образовательный сайт об АСУ ТП. Спецификация Modbus RTU на русском языке

Понравилась статья? Поделить с друзьями:
  • Руководство по неорганическому синтезу брауэр купить
  • Госпитальная хирургия руководство для врачей интернов под редакцией бисенкова трофимова
  • Инструкция для уборщицы во время коронавируса
  • Тропикамид глазные капли инструкция по применению цена отзывы аналоги
  • Ифнс по нижегородской области руководство по