Modbus ошибка устройства 129

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

Описание протокола 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 RTU, который широко применяется в АСУ ТП. Англоязычная версия статьи доступна на сайте ipc2u.com. Описание протокола Modbus TCP можно найти в статье.

Оглавление:

  • Описание протокола Modbus RTU
  • Какие бывают команды Modbus RTU?
  • Как послать команду Modbus RTU на чтение дискретного вывода? Команда 0x01
  • Как послать команду Modbus RTU на чтение дискретного ввода? Команда 0x02
  • Как послать команду Modbus RTU на чтение аналогового вывода? Команда 0x03
  • Как послать команду Modbus RTU на чтение аналогового ввода? Команда 0x04
  • Как послать команду Modbus RTU на запись дискретного вывода? Команда 0x05
  • Как послать команду Modbus RTU на запись аналогового вывода? Команда 0x06
  • Как послать команду Modbus RTU на запись нескольких дискретных выводов? Команда 0x0F
  • Как послать команду Modbus RTU на запись нескольких аналоговых выводов? Команда 0x10
  • Какие бывают ошибки запроса Modbus?
  • Программы для работы с протоколом Modbus RTU

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

Modbus — коммуникационный протокол, основан на архитектуре ведущий-ведомый (master-slave). Использует для передачи данных интерфейсы RS-485, RS-422, RS-232, а также Ethernet сети TCP/IP (протокол Modbus TCP).

Сообщение Modbus RTU состоит из адреса устройства SlaveID, кода функции, специальных данных в зависимости от кода функции и CRC контрольной суммы.

SlaveID Код функции Специальные данные CRC

Если отбросить SlaveID адрес и CRC контрольную сумму, то получится PDU, Protocol Data Unit.

SlaveID – это адрес устройства, может принимать значение от 0 до 247, адреса с 248 до 255 зарезервированы.

Данные в модуле хранятся в 4 таблицах.

Две таблицы доступны только для чтения и две для чтения-записи.

В каждой таблице помещается 9999 значений.

Номер регистра Адрес регистра HEX Тип Название Тип
1-9999 0000 до 270E Чтение-запись Discrete Output Coils DO
10001-19999 0000 до 270E Чтение Discrete Input Contacts DI
30001-39999 0000 до 270E Чтение Analog Input Registers AI
40001-49999 0000 до 270E Чтение-запись Analog Output Holding Registers AO

В сообщении Modbus используется адрес регистра.

Например, первый регистр AO Holding Register, имеет номер 40001, но его адрес равен 0000.

Разница между этими двумя величинами есть смещение offset.

Каждая таблица имеет свое смещение, соответственно: 1, 10001, 30001 и 40001.

Ниже приведен пример запроса Modbus RTU для получения значения AO аналогового выхода (holding registers) из регистров от #40108 до 40110 с адресом устройства 17.

11 03 006B 0003 7687

11 Адрес устройства SlaveID (17 = 11 hex)
03 Функциональный код Function Code (читаем Analog Output Holding Registers)
006B Адрес первого регистра (40108-40001 = 107 =6B hex)
0003 Количество требуемых регистров (чтение 3-х регистров с 40108 по 40110)
7687 Контрольная сумма CRC

В ответе от Modbus RTU Slave устройства мы получим:

11 03 06 AE41 5652 4340 49AD

Где:

11 Адрес устройства (17 = 11 hex) SlaveID
03 Функциональный код Function Code
06 Количество байт далее (6 байтов идут следом) Byte Count
AE Значение старшего разряда регистра (AE hex) Register value Hi (AO0)
41 Значение младшего разряда регистра (41 hex) Register value Lo (AO0)
56 Значение старшего разряда регистра (56 hex) Register value Hi (AO1)
52 Значение младшего разряда регистра (52 hex) Register value Lo (AO1)
43 Значение старшего разряда регистра (43 hex) Register value Hi (AO2)
40 Значение младшего разряда регистра (40 hex) Register value Lo (AO2)
49 Контрольная сумма CRC value Lo
AD Контрольная сумма CRC value Hi

Регистр аналогового выхода AO0 имеет значение AE 41 HEX или 44609 в десятичной системе.

Регистр аналогового выхода AO1 имеет значение 56 52 HEX или 22098 в десятичной системе.

Регистр аналогового выхода AO2 имеет значение 43 40 HEX или 17216 в десятичной системе.

Значение AE 41 HEX — это 16 бит 1010 1110 0100 0001, может принимать различное значение, в зависимости от типа представления.

Значение регистра 40108 при комбинации с регистром 40109 дает 32 бит значение.

Пример представления.

Тип представления Диапазон значений Пример в HEX Будет в десятичной форме
16-bit unsigned integer 0 до 65535 AE41 44,609
16-bit signed integer -32768 до 32767 AE41 -20,927
two character ASCII string 2 знака AE41 ® A
discrete on/off value 0 и 1 0001 0001
32-bit unsigned integer 0 до 4,294,967,295 AE41 5652 2,923,517,522
32-bit signed integer -2,147,483,648 до 2,147,483,647 AE41 5652 -1,371,449,774
32-bit single precision IEEE floating point number 1,2·10−38 до 3,4×10+38 AE41 5652 -4.395978 E-11
four character ASCII string 4 знака AE41 5652 ® A V R

Наверх к оглавлению

Какие бывают команды Modbus RTU?

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

Код функции Что делает функция Тип значения Тип доступа
01 (0x01) Чтение DO Read Coil Status Дискретное Чтение
02 (0x02) Чтение DI Read Input Status Дискретное Чтение
03 (0x03) Чтение AO Read Holding Registers 16 битное Чтение
04 (0x04) Чтение AI Read Input Registers 16 битное Чтение
05 (0x05) Запись одного DO Force Single Coil Дискретное Запись
06 (0x06) Запись одного AO Preset Single Register 16 битное Запись
15 (0x0F) Запись нескольких DO Force Multiple Coils Дискретное Запись
16 (0x10) Запись нескольких AO Preset Multiple Registers 16 битное Запись

Наверх к оглавлению

Как послать команду Modbus RTU на чтение дискретного вывода? Команда 0x01

Эта команда используется для чтения значений дискретных выходов DO.

В запросе PDU задается начальный адрес первого регистра DO и последующее количество необходимых значений DO. В PDU значения DO адресуются, начиная с нуля.

Значения DO в ответе находятся в одном байте и соответствуют значению битов.

Значения битов определяются как 1 = ON и 0 = OFF.

Младший бит первого байта данных содержит значение DO адрес которого указывался в запросе. Остальные значения DO следуют по нарастающей к старшему значению байта. Т.е. справа на лево.

Если запрашивалось меньше восьми значений DO, то оставшиеся биты в ответе будут заполнены нулями (в направлении от младшего к старшему байту). Поле Byte Count Количество байт далее указывает количество полных байтов данных в ответе.

Пример запроса DO с 20 по 56 для SlaveID адреса устройства 17. Адрес первого регистра будет 0013 hex = 19, т.к. счет ведется с 0 адреса (0014 hex = 20, -1 смещение нуля = получаем 0013 hex = 19).

Байт Запрос Байт Ответ
(Hex) Название поля (Hex) Название поля
11 Адрес устройства 11 Адрес устройства
01 Функциональный код 01 Функциональный код
00 Адрес первого регистра Hi байт 05 Количество байт далее
13 Адрес первого регистра Lo байт CD Значение регистра DO 27-20 (1100 1101)
00 Количество регистров Hi байт 6B Значение регистра DO 35-28 (0110 1011)
25 Количество регистров Lo байт B2 Значение регистра DO 43-36 (1011 0010)
0E Контрольная сумма CRC 0E Значение регистра DO 51-44 (0000 1110)
84 Контрольная сумма CRC 1B Значение регистра DO 56-52 (0001 1011)
45 Контрольная сумма CRC
E6 Контрольная сумма CRC

Состояния выходов DO 27-20 показаны как значения байта CD hex, или в двоичной системе 1100 1101.

В регистре DO 56-52 5 битов справа были запрошены, а остальные биты заполнены нулями до полного байта (0001 1011).

Каналы DO 56 DO 55 DO 54 DO 53 DO 52
Биты 0 0 0 1 1 0 1 1
Hex 1B

Модули с дискретным выводом: M-7065, ioLogik R1214, ADAM-4056S

Наверх к оглавлению

Как послать команду Modbus RTU на чтение дискретного ввода? Команда 0x02

Эта команда используется для чтения значений дискретных входов DI.

Пример запроса DI с регистров от #10197 до 10218 для SlaveID адреса устройства 17. Адрес первого регистра будет 00C4 hex = 196, т.к. счет ведется с 0 адреса.

Байт Запрос Байт Ответ
(Hex) Название поля (Hex) Название поля
11 Адрес устройства 11 Адрес устройства
02 Функциональный код 02 Функциональный код
00 Адрес первого регистра Hi байт 03 Количество байт далее
C4 Адрес первого регистра Lo байт AC Значение регистра DI 10204-10197 (1010 1100)
00 Количество регистров Hi байт DB Значение регистра DI 10212-10205 (1101 1011)
16 Количество регистров Lo байт 35 Значение регистра DI 10218-10213 (0011 0101)
BA Контрольная сумма CRC 20 Контрольная сумма CRC
A9 Контрольная сумма CRC 18 Контрольная сумма CRC

Модули с дискретным вводом: M-7053, ioLogik R1210, ADAM-4051

Наверх к оглавлению

Как послать команду Modbus RTU на чтение аналогового вывода? Команда 0x03

Эта команда используется для чтения значений аналоговых выходов AO.

Пример запроса AO с регистров от #40108 до 40110 для SlaveID адреса устройства 17. Адрес первого регистра будет 006B hex = 107, т.к. счет ведется с 0 адреса.

Байт Запрос Байт Ответ
(Hex) Название поля (Hex) Название поля
11 Адрес устройства 11 Адрес устройства
03 Функциональный код 03 Функциональный код
00 Адрес первого регистра Hi байт 06 Количество байт далее
6B Адрес первого регистра Lo байт AE Значение регистра Hi #40108
00 Количество регистров Hi байт 41 Значение регистра Lo #40108
03 Количество регистров Lo байт 56 Значение регистра Hi #40109
76 Контрольная сумма CRC 52 Значение регистра Lo #40109
87 Контрольная сумма CRC 43 Значение регистра Hi #40110
40 Значение регистра Lo #40110
49 Контрольная сумма CRC
AD Контрольная сумма CRC

Модули с аналоговым выводом: M-7024, ioLogik R1241, ADAM-4024

Наверх к оглавлению

Как послать команду Modbus RTU на чтение аналогового ввода? Команда 0x04

Эта команда используется для чтения значений аналоговых входов AI.

Пример запроса AI с регистра #30009 для SlaveID адреса устройства 17. Адрес первого регистра будет 0008 hex = 8, т.к. счет ведется с 0 адреса.

Байт Запрос Байт Ответ
(Hex) Название поля (Hex) Название поля
11 Адрес устройства 11 Адрес устройства
04 Функциональный код 04 Функциональный код
00 Адрес первого регистра Hi байт 02 Количество байт далее
08 Адрес первого регистра Lo байт 00 Значение регистра Hi #30009
00 Количество регистров Hi байт 0A Значение регистра Lo #30009
01 Количество регистров Lo байт F8 Контрольная сумма CRC
B2 Контрольная сумма CRC F4 Контрольная сумма CRC
98 Контрольная сумма CRC

Модули с аналоговым вводом: M-7017, ioLogik R1240, ADAM-4017+

Наверх к оглавлению

Как послать команду Modbus RTU на запись дискретного вывода? Команда 0x05

Эта команда используется для записи одного значения дискретного выхода DO.

Значение FF 00 hex устанавливает выход в значение включен ON.

Значение 00 00 hex устанавливает выход в значение выключен OFF.

Все остальные значения недопустимы и не будут влиять значение на выходе.

Нормальный ответ на такой запрос — это эхо (повтор запроса в ответе), возвращается после того, как состояние DO было изменено.

Пример записи в DO с регистром #173 для SlaveID адреса устройства 17. Адрес регистра будет 00AC hex = 172, т.к. счет ведется с 0 адреса.

Байт Запрос Байт Ответ
(Hex) Название поля (Hex) Название поля
11 Адрес устройства 11 Адрес устройства
05 Функциональный код 05 Функциональный код
00 Адрес первого регистра Hi байт 00 Адрес первого регистра Hi байт
AC Адрес первого регистра Lo байт AC Адрес первого регистра Lo байт
FF Значение Hi байт FF Значение Hi байт
00 Значение Lo байт 00 Значение Lo байт
4E Контрольная сумма CRC 4E Контрольная сумма CRC
8B Контрольная сумма CRC 8B Контрольная сумма CRC

Состояние выхода DO173 поменялось с выключен OFF на включен ON.

Модули с дискретным выводом: M-7053, ioLogik R1210, ADAM-4051

Наверх к оглавлению

Как послать команду Modbus RTU на запись аналогового вывода? Команда 0x06

Эта команда используется для записи одного значения аналогового выхода AO.

Пример записи в AO с регистром #40002 для SlaveID адреса устройства 17. Адрес первого регистра будет 0001 hex = 1, т.к. счет ведется с 0 адреса.

Байт Запрос Байт Ответ
(Hex) Название поля (Hex) Название поля
11 Адрес устройства 11 Адрес устройства
06 Функциональный код 06 Функциональный код
00 Адрес первого регистра Hi байт 00 Адрес первого регистра Hi байт
01 Адрес первого регистра Lo байт 01 Адрес первого регистра Lo байт
00 Значение Hi байт 00 Значение Hi байт
03 Значение Lo байт 03 Значение Lo байт
9A Контрольная сумма CRC 9A Контрольная сумма CRC
9B Контрольная сумма CRC 9B Контрольная сумма CRC

Модули с аналоговым выводом: M-7024, ioLogik R1241, ADAM-4024

Наверх к оглавлению

Как послать команду Modbus RTU на запись нескольких дискретных выводов? Команда 0x0F

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

Пример записи в несколько DO с регистрами от #20 до #29 для SlaveID адреса устройства 17. Адрес регистра будет 0013 hex = 19, т.к. счет ведется с 0 адреса.

Байт Запрос Байт Ответ
(Hex) Название поля (Hex) Название поля
11 Адрес устройства 11 Адрес устройства
0F Функциональный код 0F Функциональный код
00 Адрес первого регистра Hi байт 00 Адрес первого регистра Hi байт
13 Адрес первого регистра Lo байт 13 Адрес первого регистра Lo байт
00 Количество регистров Hi байт 00 Кол-во записанных рег. Hi байт
0A Количество регистров Lo байт 0A Кол-во записанных рег. Lo байт
02 Количество байт далее 26 Контрольная сумма CRC
CD Значение байт DO 27-20 (1100 1101) 99 Контрольная сумма CRC
01 Значение байт DO 29-28 (0000 0001)
BF Контрольная сумма CRC
0B Контрольная сумма CRC

В ответе возвращается количество записанных регистров.

Модули с дискретным выводом: M-7053, ioLogik R1210, ADAM-4051

Наверх к оглавлению

Как послать команду Modbus RTU на запись нескольких аналоговых выводов? Команда 0x10

Эта команда используется для записи нескольких значений аналогового выхода AO.

Пример записи в несколько AO с регистрами #40002 и #40003 для SlaveID адреса устройства 17. Адрес первого регистра будет 0001 hex = 1, т.к. счет ведется с 0 адреса.

Байт Запрос Байт Ответ
(Hex) Название поля (Hex) Название поля
11 Адрес устройства 11 Адрес устройства
10 Функциональный код 10 Функциональный код
00 Адрес первого регистра Hi байт 00 Адрес первого регистра Hi байт
01 Адрес первого регистра Lo байт 01 Адрес первого регистра Lo байт
00 Количество регистров Hi байт 00 Кол-во записанных рег. Hi байт
02 Количество регистров Lo байт 02 Кол-во записанных рег. Lo байт
04 Количество байт далее 12 Контрольная сумма CRC
00 Значение Hi 40002 98 Контрольная сумма CRC
0A Значение Lo 40002
01 Значение Hi 40003
02 Значение Lo 40003
C6 Контрольная сумма CRC
F0 Контрольная сумма CRC

Модули с аналоговым выводом: M-7024, ioLogik R1241, ADAM-4024

Наверх к оглавлению

Какие бывают ошибки запроса Modbus?

Если устройство получило запрос, но запрос не может быть обработан, то устройство ответит кодом ошибки.

Ответ будет содержать измененный Функциональный код, старший бит будет равен 1.

Пример:

Было Стало
Функциональный код в запросе Функциональный код ошибки в ответе
01 (01 hex) 0000 0001 129 (81 hex) 1000 0001
02 (02 hex) 0000 0010 130 (82 hex) 1000 0010
03 (03 hex) 0000 0011 131 (83 hex) 1000 0011
04 (04 hex) 0000 0100 132 (84 hex) 1000 0100
05 (05 hex) 0000 0101 133 (85 hex) 1000 0101
06 (06 hex) 0000 0110 134 (86 hex) 1000 0110
15 (0F hex) 0000 1111 143 (8F hex) 1000 1111
16 (10 hex) 0001 0000 144 (90 hex) 1001 0000

Пример запроса и ответ с ошибкой:

Байт Запрос Байт Ответ
(Hex) Название поля (Hex) Название поля
0A Адрес устройства 0A Адрес устройства
01 Функциональный код 81 Функциональный код с измененным битом
04 Адрес первого регистра Hi байт 02 Код ошибки
A1 Адрес первого регистра Lo байт B0 Контрольная сумма CRC
00 Количество регистров Hi байт 53 Контрольная сумма CRC
01 Количество регистров Lo байт
AC Контрольная сумма CRC
63 Контрольная сумма CRC

Расшифровка кодов ошибок

01 Принятый код функции не может быть обработан.
02 Адрес данных, указанный в запросе, недоступен.
03 Значение, содержащееся в поле данных запроса, является недопустимой величиной.
04 Невосстанавливаемая ошибка имела место, пока ведомое устройство пыталось выполнить затребованное действие.
05 Ведомое устройство приняло запрос и обрабатывает его, но это требует много времени. Этот ответ предохраняет ведущее устройство от генерации ошибки тайм-аута.
06 Ведомое устройство занято обработкой команды. Ведущее устройство должно повторить сообщение позже, когда ведомое освободится.
07 Ведомое устройство не может выполнить программную функцию, заданную в запросе. Этот код возвращается для неуспешного программного запроса, использующего функции с номерами 13 или 14. Ведущее устройство должно запросить диагностическую информацию или информацию об ошибках от ведомого.
08 Ведомое устройство при чтении расширенной памяти обнаружило ошибку паритета. Ведущее устройство может повторить запрос, но обычно в таких случаях требуется ремонт.
10
(0A hex)
Шлюз неправильно настроен или перегружен запросами.
11
(0B hex)
Slave устройства нет в сети или от него нет ответа.

Наверх к оглавлению

Программы для работы с протоколом Modbus RTU

Ниже перечислены программы, которые облегчают работу с Modbus.

DCON Utility Pro с поддержкой Modbus RTU, ASCII, DCON. Скачать

Modbus Master Tool с поддержкой Modbus RTU, ASCII, TCP. Скачать

Modbus TCP client с поддержкой Modbus TCP. Скачать

Наверх к оглавлению

Оставить заявку

5. Modbus

Протокол Modbus и сеть Modbus являются самыми распространенными в мире. Несмотря на свой возраст (стандартом де-факто Modbus стал еще в 1979 году), Modbus не только не устарел, но, наоборот, существенно возросло количество новых разработок и объем организационной поддержки этого протокола. Миллионы Modbus-устройств по всему миру продолжают успешно работать, а последняя версия описания протокола появилась в декабре 2006 г.

Одним из преимуществ Modbus является отсутствие необходимости в специальных интерфейсных контроллерах (Profibus и CAN требуют для своей реализации заказные микросхемы), простота программной реализации и элегантность принципов функционирования. Все это снижает затраты на освоение стандарта как системными интеграторами, так и разработчиками контроллерного оборудования. Высокая степень открытости протокола обеспечивается также полностью бесплатными текстами стандартов, которые можно скачать с сайта www.modbus.org.

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

Основным недостатком Modbus является сетевой обмен по типу «ведущий/ведомый», что не позволяет ведомым устройствам передавать данные по мере их появления и поэтому требует интенсивного опроса ведомых устройств ведущим.

Разновидностями Modbus являются протоколы Modbus Plus – многомастерный протокол с кольцевой передачей маркера и Modbus TCP, рассчитанный на использование в сетях Ethernet и интернет.

Протокол Modbus имеет два режима передачи: RTU (Remote Terminal Unit – «удаленное терминальное устройство») и ASCII. Стандарт предусматривает, что режим RTU в протоколе Modbus должен присутствовать обязательно, а режим ASCII является опционным. Пользователь может выбирать любой из них, но все модули, включенные в сеть Modbus, должны иметь один и тот же режим передачи.

Мы рассмотрим только протокол Modbus RTU, поскольку Modbus ASCII в России практически не используется. Отметим, что Modbus ASCII нельзя путать с частно-фирменным протоколом DCON, который используется в модулях фирм Advantech и ICP DAS и не соответствует стандарту Modbus.

Стандарт Modbus предусматривает применение физического интерфейса RS-485, RS-422 или RS-232. Наиболее распространенным для организации промышленной сети является 2-проводной интерфейс RS-485. Для соединений точка-точка может быть использован интерфейс RS-232 или RS-422.

В стандарте Modbus имеются обязательные требования, рекомендуемые и опционные (необязательные). Существует три степени соответствия стандарту: «полностью соответствует» – когда протокол соответствует всем обязательным и всем рекомендуемым требованиям, «условно соответствует» – когда протокол соответствует только обязательным требованиям и не соответствует рекомендуемым, и «не соответствует».

Модель OSI протокола Modbus содержит три уровня: физический, канальный и прикладной.

Физический уровень. В новых разработках на основе Modbus стандарт рекомендует использовать интерфейс RS-485 с двухпроводной линией передачи, но допускается применение четырехпроводной линии и интерфейса RS-232.

Modbus-шина должна состоять из одного магистрального кабеля, от которого могут быть сделаны отводы. Магистральный кабель Modbus должен содержать 3 проводника в общем экране, два из которых представляют собой витую пару, а третий соединяет общие («земляные») выводы всех интерфейсов RS-485 в сети. Общий провод и экран должны быть заземлены в одной точке, желательно около ведущего устройства.

Устройства могут подключаться к кабелю тремя способами: непосредственно к магистральному кабелю; через пассивный разветвитель (тройник); через активный разветвитель (содержащий развязывающий повторитель интерфейса).

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

На каждом конце магистрального кабеля должны быть установлены резисторы для согласования линии передачи, как это требуется для интерфейса RS-485. В отличие от физического интерфейса RS-485, в котором терминальные резисторы на низких скоростях обмена можно не использовать, стандарт на протокол Modbus формально требует применения терминальных резисторов для всех скоростей обмена. Их номинал может быть равным 150 Ом и мощность 0.5 Вт. Стандарт требует, чтобы в руководствах по эксплуатации устройств Modbus было сказано, имеются ли указанные резисторы внутри устройства, или их необходимо устанавливать при монтаже сети. Если требуются внешние резисторы, то они должны иметь номинал в интервале от 450 до 650 Ом и быть установлены только в одном месте в пределах каждого сегмента сети (сегментами считаются части сети между повторителями интерфейса).

Modbus-устройство обязательно должно поддерживать скорости обмена 9600 бит/с и 19200 бит/с, из них 19200 бит/с устанавливается «по умолчанию». Допускаются также скорости 1200, 2400, 4800,…,38400 бит/с, 65 кбит/с, 115 кбит/с,… .

Скорость передачи должна выдерживаться в передатчике с погрешностью не хуже 1%, а приемник должен принимать данные при отклонении скорости передачи до 2%.

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

Максимальная длина магистрального кабеля при скорости передачи 9600 бит/с и сечении жил более 0.13 кв. мм (AWG26) составляет 1 км. Отводы от магистрального кабеля не должны быть длиннее 20 м. При использовании многопортового пассивного разветвителя с N отводами длина каждого отвода не должна превышать значения 40 м/N.

Modbus не устанавливает конкретных типов разъемов, но если используются разъемы RJ45, mini-DIN или D-Shell, они должны быть экранированными, а цоколевки должны соответствовать стандарту.

Для минимизации ошибок при монтаже рекомендуется использовать провода следующих цветов: желтый – для положительного вывода RS-485 (на котором устанавливается логическая «1», когда через интерфейс выводится логическая «1»); коричневый – для второго вывода интерфейса RS-485; серый – для общего провода.

Типовым сечением кабеля является AWG 24 (0.2 кв. мм, диаметр провода 0.51 мм). При использовании кабеля категории 5 его длина не должна превышать 600 м. Волновое сопротивление кабеля желательно выбирать более 100 Ом, особенно для скорости обмена более 19200 бит/с.

Канальный уровень. Протокол Modbus предполагает, что только одно ведущее устройство (контроллер) и до 247 ведомых (модулей ввода-вывода) могут быть объединены в промышленную сеть. Обмен данными всегда инициируется ведущим. Ведомые устройства никогда не начинают передачу данных, пока не получат запрос от ведущего. Ведомые устройства также не могут обмениваться данными друг с другом. Поэтому в любой момент времени в сети Modbus может происходить только один акт обмена.

Адреса с 1 по 247 являются адресами Modbus устройств в сети, а с 248 по 255 зарезервированы. Ведущее устройство не должно иметь адреса и в сети не должно быть двух устройств с одинаковыми адресами.

Ведущее устройство может посылать запросы всем устройствам одновременно («широковещательный режим») или только одному. Для широковещательного режима зарезервирован адрес «0» (при использовании в команде этого адреса она принимается всеми устройствами сети).

Описание кадра (фрейма) протокола Modbus. В протоколе Modbus RTU сообщение начинает восприниматься как новое после паузы (тишины) на шине длительностью не менее 3.5 символов (14 бит), т.е. величина паузы в секундах зависит от скорости передачи.

Формат кадра протокола Modbus RTU.

Формат кадра протокола Modbus RTU; PDU – «Protocol Data Unit» – «элемент данных протокола»; ADU – «Application Data Unit» – «элемент данных приложения».

Формат кадра показан на рисунке выше. Поле адреса всегда содержит только адрес ведомого устройства, даже в ответах на команду, посланную ведущим. Благодаря этому ведущее устройство знает, от какого модуля пришел ответ.

Поле «Код функции» говорит модулю о том, какое действие нужно выполнить.

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

Поле «Контрольная сумма» содержит контрольную сумму CRC длиной 2 байта.

Структура данных в режиме RTU. В режиме RTU данные передаются младшими разрядами вперед. По умолчанию в RTU режиме бит паритета устанавливают равным 1, если количество двоичных единиц в байте нечетное, и равным 0, если оно четное. Такой паритет называют четным (even parity) и метод контроля называют контролем четности.

При четном количестве двоичных единиц в байте бит паритета может быть равен 1. В этом случае говорят, что паритет является нечетным (odd parity).

Контроль четности может отсутствовать вообще. В этом случае вместо бита паритета должен использоваться второй стоповый бит. Для обеспечения максимальной совместимости с другими продуктами рекомендуется использовать возможность замены бита паритета на второй стоповый бит.

Ведомые устройства могут воспринимать любой из вариантов: четный, нечетный паритет или его отсутствие.

Структура Modbus RTU сообщения. Сообщения Modbus RTU передаются в виде кадров, для каждого из которых известно начало и конец. Признаком начала кадра является пауза (тишина) продолжительностью не менее 3.5 шестнадцатеричных символов (14 бит). Кадр должен передаваться непрерывно. Если при передаче кадра обнаруживается пауза продолжительностью более 1.5 шестнадцатеричных символа (6 бит), то считается, что кадр содержит ошибку и должен быть отклонен принимающим модулем. Эти величины пауз должны строго соблюдаться при скоростях ниже 19200 бит/с, однако при более высоких скоростях рекомендуется использовать фиксированные значения паузы, 1.75 мс и 750 мкс соответственно.

Контроль ошибок. В режиме RTU имеется два уровня контроля ошибок в сообщении: контроль паритета для каждого байта (опционно); контроль кадра в целом с помощью CRC метода.

CRC метод используется независимо от проверки паритета. Значение CRC устанавливается в ведущем устройстве перед передачей. При приеме сообщения вычисляется CRC для всего сообщения и сравнивается с его значением, указанным в поле CRC кадра. Если оба значения совпадают, считается, что сообщение не содержит ошибки.

Стартовые, стоповые биты и бит паритета в вычислении CRC не участвуют.

Прикладной уровень. Прикладной уровень Modbus RTU обеспечивает коммуникацию между устройствами типа «ведущий/ведомый». Прикладной уровень является независимым от физического и канального, в частности, он может использовать протоколы Ethernet TCP/IP (Modbus TCP/IP), Modbus Plus (многомастерная сеть с передачей маркера), интерфейсы RS-232, RS-422, RS-485, оптоволоконные, радиоканалы и другие физические среды для передачи сигналов.

Прикладной уровень Modbus основан на запросах с помощью кодов функций. Код функции указывает ведомому устройству, какую операцию оно должно выполнить.

При использовании протокола прикладного уровня с различными протоколами транспортного и канального уровня сохраняется неизменным основной блок Modbus-сообщения, включающий код функции и данные (этот блок называется PDU – «Protocol Data Unit» – «элемент данных протокола»). К блоку PDU могут добавляться дополнительные поля при использовании его в различных промышленных сетях и тогда он называется «ADU» – «Application Data Unit» – «элемент данных приложения».

Коды функций. Стандартом Modbus предусмотрены три категории кодов функций: установленные стандартом, задаваемые пользователем и зарезервированные.

Коды функций являются числами в диапазоне от 1 до 127. Коды в диапазоне от 65 до 72 и от 100 до 110 относятся к задаваемым пользователем функциям, в диапазоне от 128 до 255 зарезервированы для пересылки кодов ошибок в ответном сообщении. Код «0» не используется.

Коды ошибок используются ведомым устройством, чтобы определить, какое действие предпринять для их обработки. Значения кодов и их смысл описаны в стандарте на Modbus RTU.

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

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

Содержание поля данных. В сообщении ведущего устройства ведомому поле данных содержит дополнительную информацию, необходимую для выполнения указанной функции. Например, если код функции указывает, что необходимо считать данные из группы регистров устройства ввода (код функции 03 hex), то поле данных содержит адрес начального регистра и количество регистров. Если ведущее устройство посылает команду записи данных в группу регистров (код функции 10 hex), то поле данных должно содержать адрес начального регистра, количество регистров, количество байтов данных и данные для записи в регистр.

Конкретное содержание поля данных устанавливается стандартом для каждой функции отдельно.

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

Сообщение Modbus RTU состоит из адреса устройства SlaveID, кода функции, специальных данных в зависимости от кода функции и CRC контрольной суммы.

SlaveID Код функции Специальные данные CRC

Если отбросить SlaveID адрес и CRC контрольную сумму, то получится PDU, Protocol Data Unit. SlaveID – это адрес устройства, может принимать значение от 0 до 247, адреса с 248 до 255 зарезервированы. Данные в модуле хранятся в 4 таблицах. Две таблицы доступны только для чтения и две для чтения-записи. В каждой таблице помещается 9999 значений.

Номер регистра Адрес регистра HEX Тип Название Тип
1-9999 0000 до 270E Чтение-запись Discrete Output Coils DO
10001-19999 0000 до 270E Чтение Discrete Input Contacts DI
30001-39999 0000 до 270E Чтение Analog Input Registers AI
40001-49999 0000 до 270E Чтение-запись Analog Output Holding Registers AO

В сообщении Modbus используется адрес регистра. Например, первый регистр AO Holding Register, имеет номер 40001, но его адрес равен 0000. Разница между этими двумя величинами есть смещение offset. Каждая таблица имеет свое смещение, соответственно: 1, 10001, 30001 и 40001. Ниже приведен пример запроса Modbus RTU для получения значения AI аналогового выхода (holding registers) из регистров от #40108 до 40110 с адресом устройства 17.

11 03 006B 0003 7687

Регистр аналогового выхода AO0 имеет значение AE 41 HEX или 44609 в десятичной системе.

Регистр аналогового выхода AO1 имеет значение 56 52 HEX или 22098 в десятичной системе.

Регистр аналогового выхода AO2 имеет значение 43 40 HEX или 17216 в десятичной системе.

Значение AE 41 HEX — это 16 бит 1010 1110 0100 0001, может принимать различное значение, в зависимости от типа представления.

Значение регистра 40108 при комбинации с регистром 40109 дает 32 бит значение.

Пример представления.

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

Эта команда используется для чтения значений дискретных выходов DO.

В запросе PDU задается начальный адрес первого регистра DO и последующее количество необходимых значений DO. В PDU значения DO адресуются, начиная с нуля.

Значения DO в ответе находятся в одном байте и соответствуют значению битов.

Значения битов определяются как 1 = ON и 0 = OFF.

Младший бит первого байта данных содержит значение DO адрес которого указывался в запросе. Остальные значения DO следуют по нарастающей к старшему значению байта. Т.е. справа на лево.

Если запрашивалось меньше восьми значений DO, то оставшиеся биты в ответе будут заполнены нулями (в направлении от младшего к старшему байту). Поле Byte Count Количество байт далее указывает количество полных байтов данных в ответе.

Пример запроса DO с 20 по 56 для SlaveID адреса устройства 17. Адрес первого регистра будет 0013 hex = 19, т.к. счет ведется с 0 адреса (0014 hex = 20, -1 смещение нуля = получаем 0013 hex = 19).

Состояния выходов DO 27-20 показаны как значения байта CD hex, или в двоичной системе 1100 1101.

В регистре DO 56-52 5 битов справа были запрошены, а остальные биты заполнены нулями до полного байта (0001 1011).

Эта команда используется для чтения значений дискретных входов DI.

Пример запроса DI с регистров от #10197 до 10218 для SlaveID адреса устройства 17. Адрес первого регистра будет 00C4 hex = 196, т.к. счет ведется с 0 адреса.

Эта команда используется для чтения значений аналоговых выходов AO.

Пример запроса AO с регистров от #40108 до 40110 для SlaveID адреса устройства 17. Адрес первого регистра будет 006B hex = 107, т.к. счет ведется с 0 адреса.

Эта команда используется для чтения значений аналоговых входов AI.

Пример запроса AI с регистра #30009 для SlaveID адреса устройства 17. Адрес первого регистра будет 0008 hex = 8, т.к. счет ведется с 0 адреса.

Эта команда используется для записи одного значения дискретного выхода DO.

Значение FF 00 hex устанавливает выход в значение включен ON.

Значение 00 00 hex устанавливает выход в значение выключен OFF.

Все остальные значения недопустимы и не будут влиять значение на выходе.

Нормальный ответ на такой запрос — это эхо (повтор запроса в ответе), возвращается после того, как состояние DO было изменено.

Пример записи в DO с регистром #173 для SlaveID адреса устройства 17. Адрес регистра будет 00AC hex = 172, т.к. счет ведется с 0 адреса.

Состояние выхода DO173 поменялось с выключен OFF на включен ON.

Эта команда используется для записи одного значения аналогового выхода AO.

Пример записи в AO с регистром #40002 для SlaveID адреса устройства 17. Адрес первого регистра будет 0001 hex = 1, т.к. счет ведется с 0 адреса.

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

Пример записи в несколько DO с регистрами от #20 до #29 для SlaveID адреса устройства 17. Адрес регистра будет 0013 hex = 19, т.к. счет ведется с 0 адреса.

В ответе возвращается количество записанных регистров.

Эта команда используется для записи нескольких значений аналогового выхода AO.

Пример записи в несколько AO с регистрами #40002 и #40003 для SlaveID адреса устройства 17. Адрес первого регистра будет 0001 hex = 1, т.к. счет ведется с 0 адреса.

Если устройство получило запрос, но запрос не может быть обработан, то устройство ответит кодом ошибки.

Ответ будет содержать измененный Функциональный код, старший бит будет равен 1.

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

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

  • логические ошибки.

Ошибки первого
типа обнаруживаются при помощи фреймов
символов, контроля чётности и циклической
контрольной суммы 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 принимает
запрос, но не может его обработать
(обращение к несуществующему регистру
и т.д.), отправляется ответ содержащий
в себе данные об ошибке.

Направление
передачи

адрес
подчинённого устройства

номер
функции

данные
(или код ошибки)

CRC

Запрос
(Master→Slave)

0x01

0x77

0xDD

0xC7
0xA9

Ответ
(Slave→Master)

0x01

0xF7

0xEE

0xE6
0x7C

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

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

01
Принятый код функции не может быть
обработан на подчиненном.

02
Адрес данных указанный в запросе не
доступен данному подчиненному.

03
Величина содержащаяся в поле данных
запроса является не допустимой

величиной
для подчиненного.

04
Невосстанавливаемая ошибка имела место
пока подчиненный пытался выполнить

затребованное
действие.

05
Подчиненный принял запрос и обрабатывает
его, но это требует много времени.

Этот
ответ предохраняет главного от генерации
ошибки таймаута.

06
Подчиненный занят обработкой команды.

Главный
должен повторить сообщение позже, когда
подчиненный освободится.

07
Подчиненный не может выполнить программную
функцию, принятую в запросе.

Этот
код возвращается для неудачного
программного запроса, использующего

функции
с номерами 13 или 14.

Главный
должен запросить диагностическую
информацию или информацию об

ошибках
с подчиненного.

08
Подчиненный пытается читать расширенную
память, но обнаружил ошибку паритета.

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Это утверждённая версия страницы. Она же — наиболее свежая версия.

Основные понятия

Modbus — это протокол прикладного (седьмого) уровня модели OSI, он служит для обмена данными, чаще всего между устройствами автоматизации и реализован в виде «протокола ответов на запросы (request-reply protocol)».

В устройствах Wirenboard данные Modbus передаются по последовательным линиям связи RS-485. В последовательных линиях связи протокол RS-485 полудуплексный и работает по принципу «клиент-сервер». Каждое устройство в сети (кроме ведущего см. далее) имеет адрес от 1 до 247, адрес 0 используется для широковещательной передачи данных всем устройствам, а адреса 248–255 считаются зарезервированными согласно спецификации Modbus, их использование не рекомендуется.

Существует две спецификации протокола: Modbus RTU и Modbus ASCII. В Modbus RTU передается 11-битный символ, состоящий из 1 стартового бита, 8 бит данных (начиная с младшего бита), бит четности (необязателен) и 2 стоповых бита, если бит четности не передается, или 1 стоповый бит, если бит четности передается. Такой символ позволяет передать 1 байт данных. В устройствах Wiren Board бит контроля четности не передается и используется 2 стоповых бита. В Modbus ASCII каждый байт передается двумя символами, представляющими ASCII-коды младшей и старшей четырехбитной группы байта (пример). Modbus RTU позволяет передавать больше информации при той же скорости последовательной линии и в устройствах Wiren Board используется именно он. Все дальнейшее описание относится к Modbus RTU.

Ведущее устройство («мастер», или «клиент») периодически опрашивает «ведомое», или «сервер». Ведущее устройство не имеет адреса, передача сообщений от устройства-сервера ведущему без запроса ведущего в протоколе не предусмотрена.

Датаграмма Modbus в общем виде

Пакет данных Modbus выглядит, как это показано на рисунке. PDU (Protocol Data Unit) — общая часть пакета MODBUS, включающая код функции и данные пакета. ADU (Application Data Unit) — полный пакет MODBUS. Включает в себя специфичную для физического уровня часть пакета и PDU. Для последовательных линий в заголовке ADU передается адрес устройства, а в конце — контрольная сумма CRC16. Максимальный размер ADU в последовательных коммуникационных линиях составляет 253 байта (из максимальных, разрешенных спецификацией 256 байт вычитается 1 байт адреса и два байта контрольной суммы). Для справки — в Modbus TCP максимальная длина пакета составляет 260 байт.

Функция кодируется одним байтом и определяет, какое действие должно выполнить устройство-сервер. Значение кодов функций лежат в диапазоне от 1 до 255, причем коды от 128 до 255 зарезервированы для сообщений об ошибках со стороны устройства-сервера. Код 0 не используется. Размер блока данных может варьироваться от нуля до максимально допустимого. Если обработка запроса прошла без ошибок, то устройство-сервер возвращает пакет ADU, содержащий запрошенные данные.

Modbus-транзакция, прошедшая без ошибок


При возникновении ошибки устройством возвращается код ошибки. В случае обычной транзакции код функции в ответе возвращается без изменений; в случае ошибки старший бит кода функции устанавливается в единицу (то есть код функции + 0x80)

Modbus-транзакция с ошибками


Стоит определить таймаут ожидания ответа от ведомого устройства — бессмысленно долго ждать ответ, который, возможно, из-за какой-то ошибки никогда и не придет.

Структуры данных Modbus

В Modbus принято кодировать адреса и данные в формате big-endian, то есть в формате, когда байты следуют, начиная со старшего: например, при передаче шестнадцатеричного числа 0x1234 сначала устройством будет принят байт 0x12, а затем — 0x34. Для передачи данных другого типа, например, чисел с плавающей запятой (float), текстовых строк, даты и времени суток и т.п. производитель может выбрать свой собственный способ кодирования — для расшифровки получаемых данных важно ознакомится со спецификацией производителя устройства.

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

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

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

Регистры флагов (Coils) хранят однобитные значения — то есть могут находится в состоянии 0 или 1. Такие регистры могут обозначать текущее состояние выхода (включено реле). Название «coil» буквально и означает обмотку-актюатор электромеханического реле. Регистры флагов допускают как чтение, так и запись.

Дискретные входы (Discrete Inputs) также являются однобитными регистрами, описывающими состояние входа устройства (например подано напряжение — 1). Эти регистры поддерживают только чтение.

Регистры хранения (Holding Registers) и регистры ввода (Input Registers) представлены двухбайтовым словом и могут хранить значения от 0 до 65535 (0x0000 — 0xFFFF).
Регистры ввода допускают только чтение (например, текущее значение температуры). Регистры хранения поддерживают как чтение, так и запись (для хранения настроек). В настоящее время во многих устройствах, в частности в устройствах Wiren Board, эти регистры не разделяются. Команды на чтение регистра хранения N и регистра ввода N обратятся к одному и тому же значению в адресном пространстве устройства.

Адреса и номера регистров

В стандарте Modbus для каждого из четырех типов регистров используются разные таблицы с номерами 0,1,3,4. Таким образом, регистр определенного типа с определенным номером (иначе его называют физическим адресом) имеет свой адрес в соответствующей таблице.

Таблица Номер таблицы Начальный логический адрес Номер регистра (физический адрес) Диапазон логических адресов
Регистры флагов (Coils) 0 000001 0 000001 — 065535
Дискретные входы (Discrete Inputs) 1 100001 0 100001 — 165535
Регистры хранения (Holding Registers) 3 300001 0 300001 — 365535
Регистры ввода (Input Registers) 4 400001 0 400001 — 465535

Это вносит некоторую путаницу в понимание, по какому же адресу обратиться к регистру с нужным номером. Более того, понятия «адрес» и «регистр» могут применяться производителем произвольно. Чаще всего указываются номера регистров, как, например для устройств Wiren Board. В некоторых устройствах применяются более короткие логические адреса (.0001 — .9999), и для адреса используется 5, а не 6 цифр.

Иногда в описаниях устройства указываются только логические адреса. Например, coil-регистр 0 имеет адрес 000001, регистр ввода 4 — 400005 и т.д.

В готовых шаблонах устройств контроллера Wiren Board 5 есть шаблон для однофазного счетчика электроэнергии SDM220 (/usr/share/wb-mqtt-serial/templates/config-sdm220.json). В документации от производителя «Eastron SDM
220 Modbus Smart Meter Modbus Protocol Implementation V1.0» перечислены регистры и соответствующие им измеряемые параметры, например:

Address (Register) Description Units Modbus Protocol Start Address Hex (Hi Byte Lo Byte)
30001 Line to neutral volts. Volts 00 00
30007 Current. Amps. 00 06
30013 Active power Whatts 00 0C
30019 Apparent power VoltAmps 00 12
… …

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

Фрагмент шаблона счетчика SDM220

Коды функций чтения и записи регистров

В следующей таблице приведены наиболее распространенные коды функций Modbus:

Код функции HEX Название Действие
1 0x01 Read Coils Чтение значений нескольких регистров флагов
2 0x02 Read Discrete Inputs Чтение значений нескольких дискретных входов
3 0x03 Read Holding Registers Чтение значений нескольких регистров хранения
4 0x04 Read Input Registers Чтение значений нескольких регистров ввода
5 0x05 Write Single Coil Запись одного регистра флагов
6 0x06 Write Single Register Запись одного регистра (ввода или хранения)
15 0x0F Write Multiple Coils Запись нескольких регистров флагов
16 0x10 Write Multiple Register Запись нескольких регистров (ввода или хранения)

Команды условно можно разделить по типам: чтение значений — запись значений; операция с одним значением — операция с несколькими значениями.

Формат данных запросов и ответов Modbus

Рассмотрим подробнее, как происходит обмен данными между устройством-клиентом, отправляющим запрос, и устройством-сервером, отвечающим ему.
На следующем рисунке показан обмен данными контроллера с устройством с адресом 0x01. Мы хотим прочесть 8 coil-регистров, начиная с первого.

В качестве данных мы получили шестнадцатеричное число 0x2D, то есть состояние восьми coil-регистров в двоичном виде такое: 0b10110100.


В следующей таблице приведены структуры данных запросов и ответов для основных функций Modbus.

Код функции Запрос Ответ
1 (Read Coils) и 2 (Read Discrete Inputs)
  • Адрес первого регистра флагов или входного регистра (16 бит)
  • Количество данных (8 значений на байт) (16 бит)
    • Число передаваемых байт (8 бит)
    • Значения регистров флагов или входных регистров (8 значений на байт)
    3 (Read Holding Registers) и 4 (Read Input Registers)
    • Адрес первого регистра (16 бит)
    • Количество регистров, которые нужно прочесть
      • Число передаваемых байт (8 бит)
      • Значения регистров (16 бит на 1 регистр)
      5 (Write Single Coil)
      • Адрес регистра (16 бит)
      • Значение, которое нужно записать (0 — выключить, 0xFF00 — включить)
        Ответ аналогичен запросу
        6 (WriteSingle Register)
        • Адрес регистра(16 бит)
        • Новое значение регистра (16 бит)
        Ответ аналогичен запросу
        15 (WriteMultipleCoils)
        • Адрес первого регистра флагов для записи (16 бит)
        • Количество регистров флагов для записи (16 бит)
        • Количество передаваемых байт данных для регистров флагов (8 бит)
        • Данные (8 регистров флагов на байт)
        • Адрес первого coil-регистра (16 бит)
        • Количество записанных coil-регистров(16 бит)
        16 (Write Multiple register )
        • Адрес первого регистра хранения для записи (16 бит)
        • Количество регистров хранения для записи (16 бит)
        • Количество передаваемых байт данных для регистров (8 бит)
        • Данные (16 байт на регистр)
        • Адрес первого регистра хранения (16 бит)
        • Количество записанных регистров хранения(16 бит)

        Коды исключений (ошибки) Modbus

        В случае, если запрос не может по той или иной причине быть обработан устройством-сервером, то в ответ он отправляет сообщение об ошибке. Соообщение об ошибке содержит адрес Modbus-устройства, код функции, при выполнении которой произошла ошибка, увеличенный на 0x80, код ошибки и контрольную сумму:

        Транзакция завершилась с ошибкой

        В этом случае мы попытались обратиться к несуществующему адресу регистра 0xFFFF и попытались прочесть 8 регистров флагов. В результате мы получили код ошибки 0x03 — «В поле данных передано неверное значение».

        Наиболее распространенные коды ошибок Modbus приведены в следующей таблице:

        Код ошибки Название ошибки Что означает
        1 Illegal Function В запросе был передан недопустимый код функции
        2 Illegal Data Address Указанный в запросе адрес не существует
        3 Illegal Data Value В поле данных передано неверное значение
        4 Slave Device Failure Произошла невосстановимая ошибка на устройстве при выполнении запрошенной операции
        5 Acknowledge Запрос принят, выполняется, но выполнение потребует много времени; необходимо увеличить таймаут.
        6 Slave Device Busy Устройство занято обработкой предыдущего запроса.
        7 Negative Acknowledge Устройство не может выполнить запрос, необходимо получить от устройства дополнительную диагностическую информацию. Возможно, требуется тех. обслуживание.
        8 Memory Parity Error Ошибка четности при обращении к внутренней памяти устройства.

        Вычисление контрольной суммы Modbus

        Для протокола Modbus RTU 16-битная контрольная сумма (CRC) вычисляется по алгоритму, описанному в спецификации Modbus, в документе «Modbus Serial Line Protocol and Implementation Guide», раздел «CRC-generation». Передающее устройство формирует два байта контрольной суммы на основе данных сообщения, а принимающее устройство заново вычисляет контрольную сумму и сравнивает с полученной. Совпадение принятой и вычисленной контрольной суммы Modbud RTU считается индикатором успешного обмена данными.

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

        Статья посвящена промышленному протоколу ModBus — наиболее простому, а потому широко распространённому цифровому протоколу передачи данных.

        Стандарт ModBus был изобретён ещё в 1979 году компанией Modicon (ныне Schneider Electric) и с того времени не утратил своей актуальности, а даже наоборот получил широкое распространение и большую популярность среди разработчиков АСУ ТП.

        Преимущества и недостатки протокола ModBus

        Преимущества:

        • прост в реализации
        • отсутствует необходимость установки специальных микросхем для реализации протокола при разработке контроллеров и устройств
        • простота диагностики и отладки
        • поддерживается большинством устройств, применяемых при построении АСУ ТП
        • высокая надёжность и достоверность при передаче данных

        Недостатки:

        • ModBus сеть построена по принципу «ведущий-ведомый», где ведущее устройство может быть только одно. Поэтому обмен данными происходит только по инициативе ведущего устройства (оно по очереди опрашивает все ведомые). Если ведомому устройству нужно срочно передать данные, оно не может этого сделать, пока его не опросит «ведущий».

        Общие сведения о ModBus сети

        ModBus сеть объединяет одно ведущее (мастер) и несколько ведомых (слейвов). Обмен данными в сети происходит по инициативе мастера. Он может отправить запрос одному из подчинённых устройств или широковещательное сообщение сразу всем ведомым устройствам сети.

        структура modbus сети

        После отправки запроса мастер ожидает ответ в течение заданного времени («время таймаута»). Если в течение этого времени ответ не получен, мастер считает, что связь с ведомым отсутствует. На широковещательное сообщение ответ не предусмотрен.

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

        Существует три разновидности протокола:

        • ModBus ASCII — разновидность протокола, в которой сообщения кодируются с помощью ASCII-символов. Сообщения разделяются символами «:» и CR/LF. Не очень удобен, в России используется крайне редко.
        • ModBus RTU — разновидность протокола, в которой сообщения кодируются «как есть» (числами). Между собой сообщения разделяются временной паузой в 3,5 символа при заданной скорости передачи.
        • ModBus TCP — разновидность протокола для работы поверх TCP/IP стека, требуется при соединении устройств по Ethernet.

        Физический уровень протокола ModBus

        Для передачи ModBus сообщений используется последовательные асинхронные интерфейсы (RS232, RS485, RS422) в случае использования протоколов ASCII и RTU и Ethernet интерфейс для протокола ModBus TCP.

        Использование стандартных интерфейсов делает ModBus удобным для пользователей и разработчиков.

        Типы данных ModBus

        Любой узел сети ModBus — это интеллектуальное устройство (контроллер, регулятор, датчик и др.). Согласно спецификации узел сети может иметь следующие структуры данных:

        • Discrete Inputs  — состояния дискретных входов устройства, их можно только прочитать. Однобитовый тип данных.
        • Coils — состояния дискретных выходов устройства, их можно прочитать и изменить (записать новое состояние). Однобитовый тип.
        • Input Registers — 16-битные регистры, доступные только для чтения.
        • Holding Registers — 16-битные регистры свободного назначения, доступны для чтения и записи.

        Указанные типы данных необязательны для всех устройств, поддерживающих ModBus. Например, Discrete Inputs и Coils характерны больше для ПЛК.

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

        Структура обмена данными по ModBus

        Как уже было сказано, обмен данными по ModBus состоит из запросов и ответов. Ведущее устройство посылает запрос одному из подчинённых устройств, указывая в запросе его адрес, или всем устройствам сразу, указывая адрес 0.

        Структура ModBus-пакета
        Рис. Структура ModBus-пакета

        Типовой запрос или ответ состоит из следующих блоков:

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

        Состав данных блоков отличается для RTU и TCP реализаций ModBus. Далее мы подробно рассотрим каждый из них.

        ModBus ASCII мы не будем подробно рассматривать, поскольку он используется крайне редко. Состав пакета в ModBus ASCII такой же как и ModBus RTU, и отличается только типом кодирования и способом разделения пакетов.

        Функции ModBus

        Номер функции определяет тип запрашиваемых данных и что с ними нужно сделать (прочитать/записать).

        Функций ModBus достаточно много и они разделены на три категории:

        • стандартные — функции, описанные в стандарте протокола. Среди них много устаревших и неиспользуемых.
        • пользовательские — диапазон номеров функций (с 65 по 72 и с 100 по 110), которые может использовать любой производитель устройств для реализации своих специфичных функций. При этом вполне возможно, что у устройств различных производителей под одинаковыми номерами будут разные по смыслу функции.
        • зарезервированные — функции, не описанные в базовом стандарте, но реализованные в устройствах различных производителей. При этом гарантируется, что данные производители зарезервировали эти номера для себя и другие производители не могут ими воспользоваться.

        Однако, это всё лирика… На практике в большинстве случаев используются всего несколько функций, мы подробно поговорим о них в отдельной статье, а в этой будем рассматривать всё на примере функции Read Holding Registers (чтение регистров общего назначения).

        Функция Read Holding Registers (0x03) 

        Функция под номером 3 —  одна из самых употребимых функций, предназначена для чтения регистров общего назначения устройства.

        В запросе указывается количество регистров, которые нужно прочитать и адрес первого из них.

        Ответ содержит количество байт (количество регистров умноженное на 2) и значения запрошенных регистров.

        Структура запроса ModBus
        Рис. Запрос от мастера
        Структура ответа ModBus
        Рис. Ответ слейва

        Количество байт в ответе помогает ведущему устройству по мере получения данных понять, когда все данные уже получены. То есть если мастер получил третий байт с числом 200 — это означает, что ему осталось получить еще 100 байт + 2 байта контроля целостности. Это позволит ему посчитать количество пришедших байт и закончить приём, не дожидаясь, когда закончится время таймаута, отведённое слейву на ответ.

        Коды ошибок ModBus

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

        Обратимся к предыдущему примеру. Там подчинённое устройство ответило без ошибки и второй байт в ответе был 0х03. Если бы ответ содержал код ошибки, то к номеру функции подчинённое устройство добавило бы 0х80 и получилось бы 0х83. Вот так:

        Коды ошибок ModBus
        Рис. Ответ слейва с признаком ошибки

        В этом примере код ошибки 02 — это один из стандартных кодов. Вот какие они бывают:

        01 — функция не поддерживается. Это значит, что, возможно, функция не стандартная или просто не реализована конкретно в этом устройстве.

        02 — запрошенная область памяти не доступна. Каждое устройство содержит определённое количество данных определённого типа. Например, в устройстве доступно 100 регистров общего  назначения. Если при этом запросить чтение 101 регистров — возникнет ошибка 02.

        03 — функция не поддержит запрошенное количество данных. Например, функция №3 «Read Holding Registers» позволяет читать от 1 до 2000 регистров общего назначения. Поэтому, даже если в подчинённом устройстве доступно для чтения 10 000 регистров, при запросе  более 2000 с помощью функции №3 — возникнет эта ошибка.

        04 — функция выполнена с ошибкой. Такой код ошибки будет возвращён, если есть какое-то иное препятствие для нормального выполнения команды, не охваченное тремя предыдущими кодами ошибки. Проще говоря, это такая заглушка «на всякий случай», если что-то пошло не так, но в протоколе специального кода для такой ошибки не предусмотрено.

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

        ModBus RTU

        Как уже говорилось, в протоколе ModBus RTU данные передаются в виде сообщений, разделённых между собой временными паузами длиной в 3,5 символа при заданной скорости передачи.

        В сообщении обязательно указывается адрес получателя (или 0, если сообщение широковещательное) и номер функции.Номер функции определяет какие данные содержит сообщение и как их интерпретировать.

        За номером функции идут данные. Регистры данных в ModBus 32-битные, а передаются ввиде двух 16-битных сло. Сначала идёт старший байт, затем младший.

        Пример. Допустим, мы хотим прочитать из удалённого модуля сбора данных 2 регистра, начиная с первого. Адрес удалённого модуля в сети ModBus «4».  Для этого воспользуемся функцией №3 Read Holding Registers.

        Функции ModbusРис. Запрос на чтение 2-х регистров, начиная с 1-го
        Ответ слейва ModBusРис. Ответ от слейва на запрос

        В ответе подчинённое устройство повторяет свой адрес и номер функции, далее следует количество полезных байт в ответе. Каждый регистр состоит из двух байт (сначала идёт старший, затем младший). Значение запрошенных регистров оказались равны 11 и 22 в десятичной системе исчисления (0B и 16 в шестнадцатеричной соответственно).

        О  том, как использовать другие ModBus функции мы выпустим отдельную статью.

        Контроль целостности пакета в ModBus RTU (CRC-16)

        В предыдущем примере за байтами данных идут два байта проверки целостности пакета. Они являются результатом вычисления кода CRC-16 для всего сообщения.

        Мастер, передавая запрос, вычисляет CRC-код и добавляет его в конец сообщения. Слейв, получив сообщение, проверяет сообщение на целостность согласно алгоритму CRC-16. Затем подчинённое устройство составляет ответ, точно так же вычисляет для него CRC и добавляет в конец пакета.

        Подробно рассматривать алгоритм CRC-16 мы не будем, т.к. мы стараемся быть ближе к практике… А на практике программисту практически никогда не приходится писать блок вычисления CRC — в любой среде программирования можно найти соответствующую функцию или функциональный блок.

        Заключение

        В данной статье мы рассмотрели общую структуру протокола ModBus и его классическую разновидность ModBus RTU. Вообще говоря, ModBus RTU — это и есть «истинный Modbus» (если отбросить ModBus ASCII, который уже устарел).

        В следующей статье мы поговорим о разновидности протокола ModBus TCP, который является «притягиванием за уши» классического ModBus с целью использования его в Ethernet-сетях, что, конечно же, накладывает определённые ограничения. Но об этом в следующей статье. Следите за обновлениями на LAZY SMART.


        Понравилась статья? Поделить с друзьями:
      • Modbus tcp error codes
      • Modbus tcp connection failed bind error 10049
      • Modbus slave error 10049
      • Modbus rtu error codes
      • Modbus poll write error