FreeMODBUS это бесплатная реализация популярного протокола MODBUS, специально разработанная для встраиваемых систем. MODBUS это популярный сетевой протокол, широко используемый в производственной индустрии.
Стек коммуникаций MODBUS требует наличия двух слоев протокола:
Modbus Application Protocol (протокол приложения Modbus) - определяет модель данных и функции. Network - определяет среду обмена данными.
В текущей версии FreeMODBUS (на момент написания статьи это версия FreeMODBUS 1.6) предоставляется реализация Modbus Application Protocol v1.1a, и поддерживаются режимы передачи RTU/ASCII, определенные в спецификации Modbus over serial 1.0. Поскольку версия 0.7 FreeMODBUS также поддерживает MODBUS TCP, определенный в руководстве [2]. Стек FreeMODBUS лицензируется под защитой лицензии BSD (что не относится к демо-приложениям, которые могут лицезироваться отдельно), разрешающей использование в коммерческих целях. В настоящий момент поддерживаются следующие функции MODBUS:
Код
Функция
0x01
Read Coils
0x02
Read Discrete Inputs
0x03
Read Holding Registers
0x04
Read Input Register
0x05
Write Single Coil
0x06
Write Single Register
0x0F
Write Multiple Coils
0x10
Write Multiple Registers
0x11
Report Slave ID
0x17
Read/Write Multiple Registers
Библиотека FreeMODBUS основана на самых свежих стандартах, и должна быть с ними полностью совместима. Прием и передача фреймов Modbus RTU/ASCII реализованы на машинах состояний, управляемых callback-функциями из слоя абстракции от аппаратуры (hardware abstraction layer, HAL). Это делает простым портирование стека на другие платформы. Если фрейм завершен, то он передается в слой приложения (Modbus Application Layer), где анализируется его содержимое. В слое приложения доступны хуки, позволяющие добавлять новые функции Modbus.
Протокол MODBUS определяет структуру обмена сообщений между главным и подчиненным устройствами (модель master-slave). Устройство master выдает slave-устройству инструкции, которые позволяют считывать или записывать данные slave-устройства. Slave-устройства отвечает на инструкции, которые ему дает master.
Master может быть контроллером системы, а slave представлять периферийные устройства. Master может либо обмениваться данными с определенным slave, или организовать широковещательное сообщение для всех slave-устройств.
Modbus master передает данные, содержащие адрес опрашиваемого slave (1 байт), код функции, определяющий запрос (1 байт), данные для обмена (n байт), проверочный код CRC16 (2 байта).
В рабочем режиме Modbus slave никакого функционального блока для коммуникации по протоколу Modbus не требуется. Отправка и прием телеграмм Modbus выполняются автоматически.
[Режим последовательной передачи]
Режим передачи определяет содержимое бит в байтах сообщения, которые передаются по сети, и как информация сообщения упаковывается в поток сообщений, и как декодируется. Существует два стандартных режима последовательной передачи:
ASCII mode RTU mode
Кодирование фреймов для обоих этих режимов показано на рисунке ниже.
Режим ASCII. В этом режиме каждый байт сообщений кодируется двумя символами ASCII (American Standard Code for Information Interchange). Режим ASCII позволяет применять интервалы времени до секунд, что упрощает декодирование как отдельных символов, так и границ сообщений.
Сообщение ASCII начинается с символа двоеточия, и заканчивается на последовательность возврата каретки и перевода строки (CRLF, коды ASCII 0DH и 0AH). В режиме ASCII байт данных или символ несет в себе только 7 полезных бит данных.
В режиме ASCII все устройства сети ждут поступления символа двоеточия, которое обозначает начало сообщения. И это сообщение декодируется только тем устройством, которому оно было адресовано.
Режим RTU. В режиме RTU (Remote Terminal Unit) каждый 8-битный байт сообщения содержит два 4-битных HEX-символа (т. е. байт передается в двоичном виде), и сообщение передается как непрерывный поток байт.
Промежуток времени между сообщениями позволяет отделить сообщения друг от друга. В режиме RTU сообщения начинаются в интервале тишины, который должен быть по длительности как минимум 3.5 символа на заданной скорости передачи данных сети. Во время передачи сообщения интервалы между отдельными байтами не должны превышать длительности 1.5 символа. Если интервал превысил 1.5 символа, то следующий байт будет адресован как новое сообщение.
Утилиты проверки Modbus используются для диагностики обмена данными и проверки данных по определенному адресу. Эти утилиты используются для тестирования slave-устройств Modbus.
[Radzio! Modbus Master Simulator]
Это бесплатная Windows-утилита, может использоваться как альтернатива для Modscan, Modbus Poll, Simplymodbus. Утилита может обмениваться данными с несколькими slave-устройствами. Интерфейс программы очень удобен и надежен. Поддерживаемый протокол – Modbus RTU / Modbus TCP 1.6.
Ссылка для загрузки - Radzio! Modbus Master Simulator site:en.radzio.dxp.pl.
[Modbus Tester компании Schneider electric]
Modbus Tester бесплатен. Это простая Windows-утилита, позволяющая читать из регистра и записывать в регистр slave-устройства Modbus. Поддерживаемые протоколы - Modbus TCP/IP, Modbus RTU, Modbus ASCII, Jbus.
Чтобы соединиться со slave-устройством через TCP/IP, нужно выбрать порт подключения и ввести IP-адрес устройства. Для последовательного подключения к slave-устройству ASCII/MODBUS RTU нужно выбрать COM-порт и ввести параметры передачи данных - скорость (baud rate), использовать или нет контроль четности (parity). Затем нужно ввести адрес устройства, тип данных, адрес нужного регистра. После этого можно читать или записывать значения регистра.
Ссылка для загрузки - What is Modbus Tester and how do I use it? site:se.com.
[QModMaster]
Бесплатная утилита для систем Windows и Linux, поддерживает протоколы Modbus RTU и Modbus TCP. Позволяет легко обмениваться данными со slave-устройствами и мониторить трафик Modbus.
Ссылка для загрузки - QModMaster Download site:sourceforge.net.
[Modpoll Modbus® Polling Tool]
Это бесплатная утилита командной строки - симулятор мастера Modbus, работающая на Windows, Linux, Solaris, QNX 6, Raspberry Pi, BeagleBoard и других основанных на ядре ARM устройствах Linux.
Справка по командной строке (вывод в ответ на команду modpoll.exe -h):
Usage: modpoll [OPTIONS] SERIALPORT|HOST [WRITEVALUES...]
Аргументы:
SERIALPORT Последовательный порт, когда используется протокол Modbus ASCII или Modbus RTU.
COM1, COM2 ... на Windows
/dev/ttyS0, /dev/ttyS1 ... на Linux
HOST Имя хоста или IP-адрес, когда используется протокол MDBUS/TCP.
WRITEVALUES Список значений для записи. Если ничего не указано (по умолчанию) то modpoll
прочитает данные.
Основные опции:
-m ascii Протокол Modbus ASCII.
-m rtu Протокол RTU (по умолчанию, если SERIALPORT содержит \ или COM).
-m tcp Протокол MODBUS/TCP (иначе по умолчанию).
-m udp MODBUS UDP.
-m enc Modbus RTU, инкапсулированный через TCP.
-a # Slave address (1-255 для serial, 0-255 для TCP, 1 по умолчанию).
-r # Начальный адрес значений (1-65536, по умолчанию 100).
-c # Количество значений для чтения (1-125, 1 по умолчанию).
-t 0 Тип данных дискретный выход (coil).
-t 1 Тип данных дискретный вход.
-t 3 Тип данных 16-битный входной регистр.
-t 3:hex Тип данных 16-битный входной регистр с HEX-отображением.
-t 3:int Тип данных 32-битный integer в таблице входных регистров.
-t 3:mod Тип данных 32-битный module 10000 в таблице входных регистров.
-t 3:float Тип данных 32-битный float в таблице входных регистров.
-t 4 Тип данных 16-битный регистр выхода (holding), по умолчанию.
-t 4:hex Тип данных 16-битный регистр выхода (holding), с HEX-отображением.
-t 4:int Тип данных 32-битный integer в таблице выходных регистров (holding).
-t 4:mod Тип данных 32-битный module 10000 в таблице выходных регистров (holding).
-t 4:float Тип данных 32-битный float в таблице выходных регистров (holding).
-i Slave работает с 32-битными integer с порядком байт big-endian.
-f Slave работает с 32-битными float с порядком байт big-endian.
-e Использовать 32-битный режим регистра Daniel/Enron (подразумевает -i и -f).
-0 Начальный адрес отсчитывается от 0 (адресация PDU) вместо 1.
-1 Опросить только один раз, иначе опрашивать с интервалом poll rate.
-l # Интервал опроса (poll rate) в мс, (по умолчанию 1000).
-o # Таймаут в секундах (0.01 .. 10.0, по умолчанию 1.0 секунда).
Опции для MODBUS/TCP, UDP и RTU через TCP:
-p # номер порта IP (по умолчанию 502).
Опции для Modbus ASCII и Modbus RTU:
-b # Скорость, baudrate (например 9600, 19200, ...). По умолчанию 19200.
-d # Количество бит в фрейме (7 или 8 для протокола ASCII, 8 для RTU).
-s # Количество stop-бит (1 или 2, по умолчанию 1).
-p none Без контроля четности.
-p even Контроль по четности (по умолчанию).
-p odd Контроль по нечетности.
-4 # Режим RS-485, RTS включен при передаче и еще # мс после этого.
Ссылка для загрузки - Modpoll Modbus Master Simulator site:modbusdriver.com.
Все описанные утилиты можно скачать по ссылке [6].
[Реализация устройства Modbus TCP]
Здесь рассматривается реализация устройства Modbus TCP на основе FreeRTOS и библиотек LwIP и FreeMODBUS. Подразумевается, что вы уже создали рабочий проект, где можете создавать и запускать задачи FreeRTOS, и стек LwIP сконфигурирован и работает. Осталось добавить функционал устройства ModbusTCP.
Процесс по шагам:
1. Скачайте библиотеку FreeMODBUS [4]. На момент написания статьи это была версия FreeMODBUS 1.6. Распакуйте архив библиотеки в отдельный каталог, находящийся в папке проекта.
2. Создайте задачу FreeRTOS, которая до входа в бесконечный цикл for(;;) запускает функцию eMBTCPInit, и внутри бесконечного цикла делает вызовы eMBPoll (конкретный пример см. во врезке далее).
3. Перекомпилируйте проект. Компилятор ругнется на отсутствие определений для новых функций Modbus, которые Вы только что добавили (eMBTCPInit, eMBPoll). Разрешите в проекте все необходимые зависимости путем добавления нужных модулей FreeMODBUS и настройки в проекте путей поиска необходимых заголовочных файлов.
4. На каком-то этапе добавления модулей в проект компилятор запросит реализацию функций для портирования FreeMODBUS на рабочую среду, где библиотека работает (функции порта FreeMODBUS). Это функции генерации и обработки событий, установки и закрытия соединения TCP, приема и передачи пакетов, отладки и т. п., реализация функций привязана к особенностям целевой системы - к модели микроконтроллера, библиотеке TCP и особенностям RTOS. Функции порта FreeMODBUS перечислены в таблице ниже:
Функция
Описание
Модуль portevent.c (необходимые функции).
xMBPortEventInit
Инициализация подсистемы генерации и обработки событий.
vMBPortEventClose
Деинициализация подсистемы генерации и обработки событий.
xMBPortEventPost
Генерация события библиотеки FreeModbus. Появление события разблокирует поток обработки события FreeModbus, и будут выполнены соответствующие действия.
xMBPortEventGet
Выборка информации о событии.
Модуль portother.c. Это отладочные функции, и они нужны только в том случае, если разрешена отладка кода библиотеки FreeMODBUS.
prvvMBTCPLogFrame
Генерирует краткое описание содержимого фрейма Modbus TCP, и выводит его с помощью функции vMBPortLog.
vMBPortLog
Функция физического вывода отладочной информации. В зависимости от реализации может выводить текст отладочных сообщений либо на экран LCD, либо в порт UART (пример реализации вывода в порт USART6 микроконтроллера STM32F4xx см. во врезке ниже).
Модуль porttcp.c (необходимые функции).
xMBTCPPortInit
Подготавливает систему обмена пакетами TCP.
prvvMBPortReleaseClient
Останавливает сессию соединения с сервером Modbus TCP.
vMBTCPPortClose
Закрытие всех сетевых сокетов TCP.
vMBTCPPortDisable
Закрывает все существующие сессии Modbus TCP.
prvxMBTCPPortAccept
Принимает сетевое соединение.
prvvMBTCPPortError
Обработка фатальной ошибки.
prvxMBTCPPortReceive
Прием пакета.
xMBTCPPortGetRequest
Получение запроса Modbus TCP.
xMBTCPPortSendResponse
Отправка ответа на запрос Modbus TCP.
Примечание: в качестве сервера Modbus выступает подчиненное опрашиваемое устройство, а в качестве клиента - главное устройство сети, обычно персональный компьютер. Однако в терминологии протокола TCP/IP оба этих устройства в процессе обмена выступают в двух ролях - и как сервер TCP (прослушивающий по умолчанию порт 502), и как клиент TCP.
Проще всего взять функции порта FreeMODBUS готовые, из рабочих примеров. Примеры находятся в каталоге demo, а модули portevent.c, portother.c и porttcp.c в подкаталоге port примера. При необходимости эти модули можно модифицировать, чтобы они учитывали специфику Вашего проекта. Модули для микроконтроллера STM32F4xx см. во врезке ниже.
5. Функциональные возможности устройства Modbus (какие запросы обрабатываются, и каким образом) определяются таблицей xFuncHandlers. Это простая таблица, где каждая строка состоит их двух полей - команда (запрос) Modbus и указатель на функцию его обработки (строка это экземпляр структуры xMBFunctionHandler). Пример таблицы xFuncHandlers:
В этой таблице реализован следующий функционал устройства Modbus: чтение обмоток (запрос 0x01), чтение цифровых входов (запрос 0x02), чтение регистра хранения (запрос 0x03), чтение входного регистра (запрос 0x04), запись обмоток (запрос 0x05), запись нескольких обмоток (запрос 0x0F), запись нескольких регистров хранения (запрос 0x10), запрос идентификатора устройства Modbus (запрос 0x11), обновление нескольких регистров хранения (запрос 0x17).
Для своего устройства Modbus Вам необходимо подготовить аналогичную таблицу и реализовать код обработчиков запросов.
6. Если что-то пошло не так, и необходимо отлаживать поведение стека FreeMODBUS, то в заголовочном файле port.h определите в 1 макрос MB_TCP_DEBUG, и подготовьте необходимую реализацию функции vMBPortLog (пример см. во врезке ниже).
Ниже показаны только куски кода, демонстрирующие основной функционал. Полный готовый проект IAR 8.30 с исходным кодом для STM32F407 можно скачать по ссылке [5].
Макросом MODBUS_EVENT_PORT можно выбрать один из вариантов организации обработки событий - на основе очереди или на основе mailbox FreeRTOS. На мой взгляд, они работают почти одинаково, на основе mailbox отклик на события немного быстрее.
[portother.c]
#define MB_FRAME_LOG_BUFSIZE 512
#ifdef MB_TCP_DEBUG
voidprvvMBTCPLogFrame( UCHAR * pucMsg, UCHAR * pucFrame, USHORT usFrameLen )
{
int i;
int res =0;
int iBufPos =0;
size_t iBufLeft = MB_FRAME_LOG_BUFSIZE;
static CHAR arcBuffer[MB_FRAME_LOG_BUFSIZE];
assert( pucFrame !=NULL );
for( i =0; i < usFrameLen; i++ )
{
/* Выводит на печать некоторую дополнительную информацию о фрейме. */switch ( i )
{
case0:
/* TID = Transaction Identifier (идентификатор трансляции). */
res = snprintf( &arcBuffer[iBufPos], iBufLeft, "| TID = " );
break;
case2:
/* PID = Protocol Identifier (идентификатор протокола). */
res = snprintf( &arcBuffer[iBufPos], iBufLeft, " | PID = " );
break;
case4:
/* Длина */
res = snprintf( &arcBuffer[iBufPos], iBufLeft, " | LEN = " );
break;
case6:
/* UID = Unit Identifier (идентификатор юнита). */
res = snprintf( &arcBuffer[iBufPos], iBufLeft, " | UID = " );
break;
case7:
/* MB Function Code (код функции MODBUS). */
res = snprintf( &arcBuffer[iBufPos], iBufLeft, "|| FUNC = " );
break;
case8:
/* MB PDU rest (остаток: данные PDU). */
res = snprintf( &arcBuffer[iBufPos], iBufLeft, " | DATA = " );
break;
default:
res =0;
break;
}
if( res ==-1 )
{
break;
}
else
{
iBufPos += res;
iBufLeft -= res;
}
/* Вывод на печать данных. */
res = snprintf( &arcBuffer[iBufPos], iBufLeft, "%02X", pucFrame[i] );
if( res ==-1 )
{
break;
}
else
{
iBufPos += res;
iBufLeft -= res;
}
}
if( res !=-1 )
{
/* Добавление конца строки фрейма. */
res = snprintf( &arcBuffer[iBufPos], iBufLeft, " |\r\n" );
if( res !=-1 )
{
vMBPortLog( MB_LOG_DEBUG, (const CHAR*)pucMsg, "%s", arcBuffer );
}
}
}
// Вернет ошибку, если формат является указателем NULL:if (!fmt) { return-1; }
// Вернет ошибку, если строка превышает размер буфера, с учетом// необходимых дополнительных 2 символов: CR и нулевой терминатор ASCIIZ:if (UPRINTF_BUF_SIZE-2< strlen(fmt)) { return-1; }