В открытом доступе присутствует множество библиотек для реализации ModBus Slave устройства, но они зачастую, содержат избыточный функционал, сложны в освоении и содержат грубые ошибки. В данной статье рассматривается библиотека, по скромному мнению автора, лишенная этих недостатков.
Программное обеспечение библиотеки поставляется в виде открытого исходного кода на языке Си.
modbus.h
////////////////////////////////////////////////////////////////////
// Асинхронная обработка сообщение ModBus v2 //
// Автор - Iван //
///////////////////////////////////////////////////////////////////
#ifndef __MODBUS_H
#define __MODBUS_H
#include "main.h"
///////////////////////////////////////////////////////////////////////////////
//Настройки МодБас
//Данные, регистры Модбас
#define ModBusUseGlobal (0) //Использовать глобальные входы/выходы, входные/выходные регистры
//Функции протокола Модбас
#define ModBusUseFunc1 (0) //Использовать функцию 1 - чтение статуса Coils (дискретных выходных битов)
#define ModBusUseFunc2 (0) //Использовать функцию 2 - чтение статуса дискретных входов
#define ModBusUseFunc3 (1) //Использовать функцию 3 - чтение значения выходных регистров
#define ModBusUseFunc4 (0) //Использовать функцию 4 - чтение значения входных регистров
#define ModBusUseFunc5 (0) //Использовать функцию 5 - запись выходного бита
#define ModBusUseFunc6 (1) //Использовать функцию 6 - запись выходного регистра
#define ModBusUseFunc15 (0) //Использовать функцию 15 - запись нескольких выходных битов
#define ModBusUseFunc16 (1) //Использовать функцию 16 - запись нескольких выходных регистров
//Адрес устройства
#define ModBusID (1) //Адрес на шине МодБас
#define ModBusID_FF (255) //Адрес на шине МодБас, на который отвечает всегда
//Таймауты
#define ModBusMaxPause (5)//Пауза между символами, для определения начала пакета [mS],
#define ModBusMaxPauseResp (2) //Пауза между запросом Мастера и ответом Слайва [mS]
//Длинна пакетов
#define ModBusMaxPaketRX (96)//Максимальный размер принимаемого пакета <127
//Дискретные входы
#define ModBusMaxInBit (0) //Количество дискретных входов
#define ModBusMaxInBitTX (8) //Максимальное количество дискретных входов при передаче пакета
#define ModBusMaxInByte ((ModBusMaxInBit+7)/8) //Количество дискретных входов в байтах
//Дискретные выходы
#define ModBusMaxOutBit (0) //Количество дискретных выходов
#define ModBusMaxOutByte ((ModBusMaxOutBit+7)/8) //Количество дискретных выходов в байтах
#define ModBusMaxOutBitTX (8) //Максимальное количество дискретных выходов в передаваемом пакете
#define ModBusMaxOutBitRX (8) //Максимальное количество дискретных выходов доступное для груповой установки
//Регистры доступные для чтения
#define ModBusMaxInReg (0) //Количество входных регистров (регистры только для чтения)
#define ModBusMaxInRegTX (24) //Максимальное количество входных регистров в передаваемом пакете
//Регистры доступные для чтения-записи
#define ModBusMaxOutReg (48) //Количество выходных регистров
#define ModBusMaxOutRegTX (32)//Максимальное количество выходных регистров в передаваемом пакете
#define ModBusMaxOutRegRX (32)//Максимальное количество выходных регистров доступное для груповой установки
////////////////////////////////////////////////////////////////////////////////
//Опорные функции, связь с системой
//Системный таймер, инкрементируется каждую миллисекунду
#define ModBusSysTimer TimingDelay
//Запись байта в поток последовательного порта - void ModBusPUT(unsigned char A)
#define ModBusPUT(A) PutFifo0(A)
//Чтение байта из потока последовательного порта, - unsigned short ModBusGET(void)
//Если нет данных возвращает 0х0000, иначе возвращает 0х01ХХ
#define ModBusGET() Inkey16Fifo0()
////////////////////////////////////////////////////////////////////////////////
//Инициализация
void ModBusIni(void);
//Функция обработки Сообщений модбас RTU
//Работает совместно с системным таймером
//Использует макросы ModbusPUT(A) ModbusGET()
void ModBusRTU(void);
//Функция обработки Сообщений модбас ASCII
//Работает совместно с системным таймером
//Использует макросы ModbusPUT(A) ModbusGET()
void ModBusASCII(void);
//Заполнение регистров Модбас
//перенос данных из программных переменных в регистры МодБас
void Prg2ModBusOutBit(void);
void Prg2ModBusInBit(void);
void Prg2ModBusOutReg(void);
void Prg2ModBusInReg(void);
//Считывание регистров Модбас
//перенос данных из регистров МодБас в программные переменные
void ModBus2PrgOutBit(void);
void ModBus2PrgOutReg(void);
#pragma pack(push,1)
//Тип данных для работы с дискретными входами/выходами
typedef union
{
unsigned char byte;
struct
{
unsigned char bit0:1;
unsigned char bit1:1;
unsigned char bit2:1;
unsigned char bit3:1;
unsigned char bit4:1;
unsigned char bit5:1;
unsigned char bit6:1;
unsigned char bit7:1;
};
}
ModBusBit_t;
#pragma pack(pop)
#ifdef __MODBUS2PRG_C
#if ModBusMaxInBit!=0
ModBusBit_t ModBusInBit[ModBusMaxInByte]; //массив дискретных входов
#endif
#if ModBusMaxOutBit!=0
ModBusBit_t ModBusOutBit[ModBusMaxOutByte]; //массив дискретных выходов
#endif
#if ModBusMaxInReg!=0
unsigned short ModBusInReg[ModBusMaxInReg]; //массив входных регистров
#endif
#if ModBusMaxOutReg!=0
unsigned short ModBusOutReg[ModBusMaxOutReg]; //массив выходных регистров
#endif
#else
#if ModBusUseGlobal!=0 || defined(__MODBUS_C)
#if ModBusMaxInBit!=0
extern ModBusBit_t ModBusInBit[ModBusMaxInByte]; //массив дискретных входов
#endif
#if ModBusMaxOutBit!=0
extern ModBusBit_t ModBusOutBit[ModBusMaxOutByte]; //массив дискретных выходов
#endif
#if ModBusMaxInReg!=0
extern unsigned short ModBusInReg[ModBusMaxInReg]; //массив входных регистров
#endif
#if ModBusMaxOutReg!=0
extern unsigned short ModBusOutReg[ModBusMaxOutReg]; //массив выходных регистров
#endif
#endif//#if ModBusUseGlobal!=0
#endif//#ifdef __MODBUS2PRG_C
#endif//#ifndef __MODBUS_Hmodbus.c
#define __MODBUS_C
#include "modbus.h"
static unsigned char PaketRX[ModBusMaxPaketRX];//массив для сохранения пакета
static unsigned char UkPaket;//указатель в массиве, текущий принятый символ
static unsigned long TimModbus; //время приема пакета по системному таймеру
static unsigned short CRCmodbus;//текущий CRC
static unsigned char Sost;//состояние 0/1 прием/передача
//Инициализация
void ModBusIni(void)
{
TimModbus=ModBusSysTimer;//запомнить таймер
UkPaket=0;//сбросить указатель пакета
CRCmodbus=0xFFFF; //установить начальное значение CRC
//Инициализация регистров МодБас
#if ModBusMaxOutBit!=0
Prg2ModBusOutBit();
#endif
#if ModBusMaxInBit!=0
Prg2ModBusInBit();
#endif
#if ModBusMaxOutReg!=0
Prg2ModBusOutReg();
#endif
#if ModBusMaxInReg!=0
Prg2ModBusInReg();
#endif
return;
}
//Функция вычисления CRC
static inline unsigned short CRCfunc(unsigned short inCRC, unsigned char in)
{
inCRC=inCRC^in;
for(int j=0;j<8;j++){if(inCRC&1){inCRC=(inCRC>>1)^0xA001U;}else {inCRC=inCRC>>1;}}
return inCRC;
}
//Функция обработки Сообщений модбас
void ModBusRTU(void)
{
if(Sost==0)
{//Состояние прием
while(!0)
{//Цикл приема символов
unsigned short Tmp=ModBusGET(); //читаем символ из входного потока
if(Tmp==0) return; //если нет данных - возврат
//символ принят
Tmp=Tmp&0xFF;//отбрасываем признак приема байта
//Проверка временного интервала между символами
if((ModBusSysTimer-TimModbus)>ModBusMaxPause)
{//превышен таймаут, начинаем прием нового пакета
PaketRX[0]=Tmp;//сохранить принятый символ в буфер приема
UkPaket=1;//установить указатель пакета
TimModbus=ModBusSysTimer;//сбросить таймер
//вычисление CRC
CRCmodbus=CRCfunc(0xFFFF,Tmp);
continue;//повторный запрос символа
}
else
{//таймаут не превышен, принимаем уже начатый пакет
TimModbus=ModBusSysTimer;//сбросить таймер
PaketRX[UkPaket]=Tmp;//сохранить принятый символ
UkPaket++;//инкремент указателя пакета
if(UkPaket==ModBusMaxPaketRX)//проверяем на длину пакета
{//буфер пакета переполнился
UkPaket=0;//сбросить указатель пакета
CRCmodbus=0xFFFF; //установить начальное значение CRC
return;//ошибка, повторный запрос символа требуется
}
//вычисление CRC
CRCmodbus=CRCfunc(CRCmodbus,Tmp);
}
//Если принято мало данных
if(UkPaket<8) continue; //повторный запрос символа
//проверка на принятие пакета
if(CRCmodbus==0)
{//проверка на длинные пакеты
if(PaketRX[1]==15 || PaketRX[1]==16)
{//если длинные команды (15,16) , проверяем "Счетчик байт"
if((PaketRX[6]+9)!=UkPaket) continue;
}
break; //Ура! Пакет принят!!!
}
}
//////////////////////////////////////////////////////////////////////////////
// Ура! Пакет принят!!!
/////////////////////////////////////////////////////////////////////////////
(полный код в первоисточнике)
ModBus2Prg.c
#define __MODBUS2PRG_C
#include "modbus.h"
//Заполнение регистров Модбас
//перенос данных из программных переменных в регистры МодБас
void Prg2ModBusOutBit(void)
{//заполнение регистров дискретных выходов
return;
}
void Prg2ModBusInBit(void)
{//заполнение регистров дискретных входов
//ModBusInBit[0].bit0=1;
return;
}
void Prg2ModBusOutReg(void)
{//заполнение регистров 4Х регистры для чтения/записи
return;
}
void Prg2ModBusInReg(void)
{//заполнение регистов 3Х регистры для чтения
return;
}
//Считывание регистров Модбас
//Перенос данных из регистров МодБас в программные переменные
void ModBus2PrgOutBit(void)
{//чтение регистров дискретных выходов
return;
}
void ModBus2PrgOutReg(void)
{//чтение регистров 4Х регистры для чтения/записи
return;
}В файле modbus.h содержатся требуемые объявления, опции компиляции и настроечные константы. Кратко опишем основные опции и настроечные параметры.
ModBusUseFunc1 — ModBusUseFunc15 – опция компиляции, определяющая использование функций протокола ModBus. Практические реализации устройств ModBus работают с ограниченным набором функций протокола, наиболее часто, функции 3,6 и 16. Нет необходимости включать в проект лишний код.
ModBusID, ModBusID_FF – Адреса на шине ModBus. Данный реализация протокола поддерживает два адреса. Это может быть удобно для ввода в эксплуатацию устройств, адрес ModBusID является настраиваемым адресом устройства, а адрес ModBusID_FF адресом для индивидуальной настройки устройства.
ModBusMaxPause – Пауза между символами, для определения начала пакета, задается в квантах ModBusSysTimer. Как правило квант ModBusSysTimer равен 1мС. Для большинства приложений соблюдение таймаутов описанных в стандарте протокола просто невозможно.
Например, ModBus Master работающий на Win-машине никогда не сможет обеспечить требуемые протоколом таймауты. Поэтому задавать квант времени менее 1мС можно считать нецелесообразным. Практические наблюдения показывают, что величина ModBusMaxPause должна быть порядка 5-10мС.
ModBusMaxPauseResp — Пауза между запросом Master и ответом Slave. Многие ModBus Master устройства имеют задержку переключения с передачи на прием, эту задержку можно скомпенсировать этой константой.
ModBusMaxInBit, ModBusMaxOutBit, ModBusMaxInReg, ModBusMaxOutReg — Количество дискретных входов, выходов, регистров для чтения, регистров для чтения/записи. В программе резервируется память под регистры ModBus. Если определенный тип регистров не используется значение необходимо указать равное нулю.
ModBusMaxInBitTX, ModBusMaxOutBitTX, ModBusMaxInRegTX, ModBusMaxOutRegTX — Максимальное количество дискретных входов, выходов, регистров для чтения, регистров для чтения/записи выходных регистров в передаваемом пакете. Эта настройка должна совпадать с соответствующей настройкой ModBus Master.
Для портирования библиотеки на любую платформу необходимо указать через макросы три функций.
ModBusSysTimer — Системный таймер, переменная, инкрементирующаяся каждую миллисекунду в отдельном потоке выполнения. В качестве этой переменной может выступать uwTick, из библиотеки HAL STM32, или стандартная функция языка Си clock().
void ModBusPUT(unsigned char A) — Запись байта в последовательный поток.
unsigned short ModBusGET(void) — Чтение байта из последовательного потока. Если в последовательном потоке нет данных, то функция возвращает 0, если данные есть, то возвращаемое значение — старший байт 0х01, младший байт — прочитанные данные.
Для использования библиотеки необходимо заполнить тело функций Prg2ModBusOutBit(), Prg2ModBusInBit(), Prg2ModBusOutReg(), Prg2ModBusInReg(), отвечающие за копирование переменных пользователя в регистры ModBus. Так же, необходимо заполнить тело функций
ModBus2PrgOutBit(), ModBus2PrgOutReg(), отвечающие за копирование регистров ModBus в переменные пользователя. В теле этих функций можно выполнить некоторые действия связанные с изменением регистров, например, осуществить проверку на допустимые значения.
Например:
void Prg2ModBusOutReg(void)
{//заполнение регистров, 4Х регистры для чтения/записи
ModBusOutReg[0]=A;
ModBusOutReg[1]=B;
ModBusOutReg[2]=C;
return;
}
void ModBus2PrgOutReg(void)
{ //чтение регистров 4Х, регистры для чтения/записи
if(ModBusOutReg[0] < MaxA) A= ModBusOutReg[0];
B=ModBusOutReg[1];
C=ModBusOutReg[2];
return;
}
Допускается не заполнять тело указанных функций, а работать с регистрами напрямую, при этом надо использовать опцию ModBusUseGlobal.
Для инициализации ModBus устройства необходимо вызвать функцию ModBusIni(). Функции ModBusRTU() или ModBusASCII() обеспечивающей работу устройства по протоколам RTU и ASCII соответственно. Их необходимо вызывать в главном цикле программы:
ModBusIni();
while(!0)
{
if(ModBusTip==RTU) ModBusRTU(); else ModBusASCII();
}
Не стоит забывать, что перед инициализацией и вызовом функции, обеспечивающей работу ModBus устройства, необходимо позаботится об инициализации последовательного потока (UART). Программные решения, связанные с организацией поточного ввода/вывода, зависят от аппаратной платформы, и их рассмотрение выходит за рамки данной статьи.
Данная библиотека была протестирована с OPC сервером Kepware, панелями SIMATIC и Wientek, также другими ModBus Masterами, во множестве устройств на микроконтроллерах семейства PIC и STM32, и показала свою 142% работоспособность. Простота портирования данной библиотеки позволит легко адаптировать ее под другие типы 8-16-32-разрядных микроконтроллеров.
Источник: https://habr.com/ru/post/526350/