ModBus Slave RTU/ASCII без смс и регистрации

от автора

image

В открытом доступе присутствует множество библиотек для реализации 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_H 

modbus.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; //Ура! Пакет принят!!!         }       }     //////////////////////////////////////////////////////////////////////////////     //                         Ура! Пакет принят!!!     /////////////////////////////////////////////////////////////////////////////     UkPaket=0;//сбросить указатель пакета          //проверка адреса     if((PaketRX[0]!=ModBusID)&&(PaketRX[0]!=ModBusID_FF))       {//Не наш адрес       CRCmodbus=0xFFFF; //установить начальное значение CRC       return;//повторный запрос не требуется       }                //переходим в состояние передача ответа     Sost=!0; #if ModBusMaxPauseResp!=0       return;//повторный запрос не требуется #endif      }      /////////////////////////////////////////////////////////////////////////////    if(Sost!=0  #if ModBusMaxPauseResp!=0           && (ModBusSysTimer-TimModbus)>=ModBusMaxPauseResp #endif           )     {//Состояние передача ответа     Sost=0;     /////////////////////////////////////////////////////////////////////////////         //                       обработка команд                                  //     /////////////////////////////////////////////////////////////////////////////     //Код функции 01 - чтение статуса Coils (дискретных выходных битов).      /*Сообщение-запрос содержит адрес начального бита и количество битов для чтения.      Биты нумеруются начиная с 0.      В сообщении-ответе каждое значение переменной передается одним битом,     то есть в одном байте пакуется статус 8 битов переменных.      Если количество их не кратно восьми, остальные биты в байте заполняются нулями.      Счетчик вмещает количество байт в поле данных.     01 Чтение статуса выходов            ОПИСАНИЕ            Читает статуса ON/OFF дискретных выходов в подчиненном.            ЗАПРОС            Запрос содержит адрес начального выхода и количество выходов для чтения.            Выхода адресуются начиная с нуля: выхода 1-16 адресуются как 0-15.           Ниже приведен пример запроса на чтение выходов 20-56 с подчиненного устройства 17.            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						01	1           Начальный адрес Hi					00	2           Начальный адрес Lo					13	3           Количество Hi						00	4           Количество Lo						25	5           Контрольная сумма (CRC или LRC)			--            ОТВЕТ            Статус выходов в ответном сообщении передается как один выход на бит.           Если возвращаемое количество выходов не кратно восьми, то оставшиеся биты в последнем байте сообщения будут установлены в 0.            Счетчик байт содержит количество байт передаваемых в поле данных.            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						01	1           Счетчик байт						05	2           Данные(Выхода 27-20)					CD	3           Данные(Выхода 35-28)					6B	4           Данные(Выхода 43-36)					B2	5           Данные(Выхода 51-44)					0E	6           Данные(Выхода 56-52)					1B	7           Контрольная сумма (CRC или LRC)			--     */ #if ModBusUseFunc1!=0            if(PaketRX[1]==0x01)       {       //вычисление адреса запрашиваемых бит       unsigned short AdresBit=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //вычисление количества запрашиваемых бит       unsigned short KolvoBit=((((unsigned short)PaketRX[4])<<8)|(PaketRX[5]));       //если неправильный адрес и количество       if((AdresBit+KolvoBit)>(ModBusMaxOutBit) || KolvoBit>ModBusMaxOutBitTX || KolvoBit==0)         {//неправильный адрес и количество         CRCmodbus=0xFFFF; //установить начальное значение CRC         return;//повторный запрос не требуется         }       Prg2ModBusOutBit();//Заполнение регистров Модбас (GlobalDate->ModBus)       //формирование пакета ответа       //адрес       ModBusPUT(PaketRX[0]);       CRCmodbus=CRCfunc(0xFFFF,PaketRX[0]);       //код команды           ModBusPUT(1);       CRCmodbus=CRCfunc(CRCmodbus,1);       //количества полных байт       ModBusPUT((KolvoBit+7)>>3);       CRCmodbus=CRCfunc(CRCmodbus,((KolvoBit+7)>>3));       //копирование битов в пакет ответа       unsigned char TxByte=0;//текущий байт       unsigned char Bit=AdresBit&7;//указатель бит в ModBusOutBit[]       AdresBit=AdresBit>>3;//указатель байт ModBusOutBit[]       //копирование из регистра ModBusOutBit[] в пакет       int i=0;       while(!0)         {         if((ModBusOutBit[AdresBit].byte)&(1<<Bit))           {           TxByte=TxByte|(1<<(i&7));           }         //инкрементруем указатели          Bit++;         if(Bit==8){Bit=0;AdresBit++;}         i++;         if((i&7)==0)           {           ModBusPUT(TxByte);           CRCmodbus=CRCfunc(CRCmodbus,TxByte);           TxByte=0;           if(i==KolvoBit) break; else continue;           }         if(i==KolvoBit)            {           ModBusPUT(TxByte);           CRCmodbus=CRCfunc(CRCmodbus,TxByte);           break;           }         }       ModBusPUT(CRCmodbus);       ModBusPUT(CRCmodbus>>8);       //конец       CRCmodbus=0xFFFF; //установить начальное значение CRC       return;//повторный запрос не требуется        } #endif         /////////////////////////////////////////////////////////////////////////////     //Код функции 2 - чтение статуса дискретных входов     /*02 Read Input Status            ОПИСАНИЕ            Чтение ON/OFF состояния дискретных входов (ссылка 1Х) в пдчиненном.            ЗАПРОС            Запрос содержит номер начального входа и количество входов для чтения. Входа адресуются начиная с 0.           Ниже приведен пример запроса на чтение входов 10197-10218 с подчиненного устройства 17.                    Запрос            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						02	1           Начальный адрес ст.					00	2           Начальный адрес мл.					C4	3           Кол-во входов ст.					00	4           Кол-во входов мл.					16	5           Контрольная сумма					--            ОТВЕТ            Статус входов в ответном сообщении передается как один выход на бит.           Если возвращаемое количество входов не кратно восьми, то оставшиеся биты в последнем байте сообщения будут установлены в 0.            Счетчик байт содержит количество байт передаваемых в поле данных.            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						01	1           Счетчик байт						03	2           Данные(Входы 10204-10197)				AC	3           Данные(Входы 10212-10205)				DB	4           Данные(Входы 10218-10213)				35	5           Контрольная сумма (CRC или LRC)			--       */ #if ModBusUseFunc2!=0          if(PaketRX[1]==0x02)       {       //вычисление адреса запрашиваемых бит       unsigned short AdresBit=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //вычисление количества запрашиваемых бит       unsigned short KolvoBit=((((unsigned short)PaketRX[4])<<8)|(PaketRX[5]));       //если неправильный адрес и количество       if((AdresBit+KolvoBit)>(ModBusMaxInBit) || KolvoBit>ModBusMaxInBitTX || KolvoBit==0)         {//неправильный адрес и количество         CRCmodbus=0xFFFF; //установить начальное значение CRC         return;//повторный запрос не требуется         }       Prg2ModBusInBit();//Заполнение регистров Модбас (GlobalDate->ModBus)       //формирование пакета ответа       //адрес       ModBusPUT(PaketRX[0]);       CRCmodbus=CRCfunc(0xFFFF,PaketRX[0]);       //код команды           ModBusPUT(2);       CRCmodbus=CRCfunc(CRCmodbus,2);       //количества полных байт       ModBusPUT((KolvoBit+7)>>3);       CRCmodbus=CRCfunc(CRCmodbus,((KolvoBit+7)>>3));       //копирование битов в пакет ответа       unsigned char TxByte=0;//текущий байт       unsigned char Bit=AdresBit&7;//указатель бит        AdresBit=AdresBit>>3;//указатель байт        //копирование из регистра ModBusInBit[] в пакет       int i=0;       while(!0)         {         if((ModBusInBit[AdresBit].byte)&(1<<Bit))           {//устанавливаем бит в пакете           TxByte=TxByte|(1<<(i&7));           }         //инкрементруем указатели          Bit++;         if(Bit==8){Bit=0;AdresBit++;}         i++;         if((i&7)==0)           {           ModBusPUT(TxByte);           CRCmodbus=CRCfunc(CRCmodbus,TxByte);           TxByte=0;           if(i==KolvoBit) break; else continue;           }         if(i==KolvoBit)           {           ModBusPUT(TxByte);           CRCmodbus=CRCfunc(CRCmodbus,TxByte);           break;           }         }       ModBusPUT(CRCmodbus);       ModBusPUT(CRCmodbus>>8);       //конец       CRCmodbus=0xFFFF; //установить начальное значение CRC       return;//повторный запрос не требуется       } #endif         /////////////////////////////////////////////////////////////////////////////     //Код функции 03 - чтение значения выходных/внутренних регистров.      /*Сообщение-запрос содержит адрес начального исходного/внутреннего регистра (двухбайтовое слово),      и количество регистров для чтения. Регистры нумеруются начиная с 0.     03 Read Holding Registers            ОПИСАНИЕ            Чтение двоичного содержания регистров (ссылка 4Х) в подчиненном.            ЗАПРОС            Сообщение запроса специфицирует начальный регистр и количество регистров для чтения.            Регистры адресуются начина с 0: регистры 1-16 адресуются как 0-15.           Ниже приведен пример чтения регистров 40108-40110 с подчиненного устройства 17.            Запрос            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						03	1           Начальный адрес ст.					00	2           Начальный адрес мл.					6B	3           Кол-во регистров ст.					00	4           Кол-во регистров мл.					03	5           Контрольная сумма					--            ОТВЕТ            Данные регистров в ответе передаются как два бйта на регистр.            Для каждого регистра, первый байт содержит старшие биты второй байт содержит младшие биты.           За одно обращение может считываться 125 регистров для контроллеров 984-Х8Х (984-685 и т.д.),            и 32 регистра для других контроллеров. Ответ дается когда все данные укомплектованы.           Это пример ответа на запрос представленный выше:            Ответ            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						03	1           Счетчик байт						06	2           Данные (регистр 40108) ст.				02	3           Данные (регистр 40108) мл.				2B	4           Данные (регистр 40109) ст.				00	5           Данные (регистр 40109) мл.				00	6           Данные (регистр 40110) ст.				00	7           Данные (регистр 40110) мл.				64	8           Контрольная сумма					--     */ #if ModBusUseFunc3!=0           if(PaketRX[1]==0x03)       {       //вычисление адреса запрашиваемых слов       unsigned short AdresWord=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //вычисление адреса количества запрашиваемых слов       unsigned short KolvoWord=((((unsigned short)PaketRX[4])<<8)|(PaketRX[5]));        //если неправильный адрес и количество       if(((AdresWord+KolvoWord)>ModBusMaxOutReg) || (KolvoWord>ModBusMaxOutRegTX))         {//тады конец         CRCmodbus=0xFFFF;//установить начальное значение CRC         return;//Ошибка, повторный запрос не требуется         }       Prg2ModBusOutReg();//Заполнение регистров Модбас (GlobalDate->ModBus)       //формирование пакета ответа       //адрес       ModBusPUT(PaketRX[0]);       CRCmodbus=CRCfunc(0xFFFF,PaketRX[0]);       //код команды           ModBusPUT(3);       CRCmodbus=CRCfunc(CRCmodbus,3);       //количества полных байт       ModBusPUT(KolvoWord<<1);       CRCmodbus=CRCfunc(CRCmodbus,(KolvoWord<<1));       //Копирование из регистра ModBusOutReg[] в пакет ответа       for(int i=0;i<KolvoWord;i++)         {         ModBusPUT(ModBusOutReg[AdresWord+i]>>8);         CRCmodbus=CRCfunc(CRCmodbus,(ModBusOutReg[AdresWord+i]>>8));         ModBusPUT(ModBusOutReg[AdresWord+i]>>0);         CRCmodbus=CRCfunc(CRCmodbus,(ModBusOutReg[AdresWord+i]>>0));         }       ModBusPUT(CRCmodbus);       ModBusPUT(CRCmodbus>>8);       //конец       CRCmodbus=0xFFFF; //установить начальное значение CRC       return;//повторный запрос не требуется       } #endif          /////////////////////////////////////////////////////////////////////////////     //Код функции 04 - чтение значения входных регистров     /*04 Read Input Registers            СОДЕРЖАНИЕ            Чтение двоичного содержания входных регистров (ссылка 3Х) в подчиненном.            ЗАПРОС            Запрос содержит номер начального регистра и количество регистров для чтения.           Ниже приведен пример запроса для чтения регистра 30009 с подчиненного устройства 17.            Запрос            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						03	1           Начальный адрес ст.					00	2           Начальный адрес мл.					6B	3           Кол-во регистров ст.					00	4           Кол-во регистров мл.					03	5           Контрольная сумма					--               ОТВЕТ            Данные регистров в ответе передаются как два бйта на регистр.            Для каждого регистра, первый байт содержит старшие биты второй байт содержит младшие биты.           За одно обращение может считываться 125 регистров для контроллеров 984-Х8Х (984-685 и т.д.),            и 32 регистра для других контроллеров. Ответ дается когда все данные укомплектованы.           Это пример ответа на запрос представленный выше:            Ответ            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						03	1           Счетчик байт						02	2           Данные (регистр 30009) ст.				00	3           Данные (регистр 30009) мл.				2A	4           Контрольная сумма					--       */ #if ModBusUseFunc4!=0          if(PaketRX[1]==0x04)       {       //вычисление адреса запрашиваемых слов       unsigned short AdresWord=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //вычисление адреса количества запрашиваемых слов       unsigned short KolvoWord=((((unsigned short)PaketRX[4])<<8)|(PaketRX[5]));        //если неправильный адрес и количество       if(((AdresWord+KolvoWord)>ModBusMaxInReg) || (KolvoWord>ModBusMaxInRegTX))         {//тады конец         CRCmodbus=0xFFFF;//установить начальное значение CRC         return;//Ошибка, повторный запрос не требуется         }       Prg2ModBusInReg();//Заполнение регистров Модбас (GlobalDate->ModBus)       //формирование пакета ответа       //адрес       ModBusPUT(PaketRX[0]);       CRCmodbus=CRCfunc(0xFFFF,(PaketRX[0]));       //код команды           ModBusPUT(4);       CRCmodbus=CRCfunc(CRCmodbus,4);       //количества полных байт       ModBusPUT(KolvoWord<<1);       CRCmodbus=CRCfunc(CRCmodbus,(KolvoWord<<1));       //Копирование из регистра ModBusInReg[] в пакет ответа       for(int i=0;i<KolvoWord;i++)         {         ModBusPUT(ModBusInReg[AdresWord+i]>>8);         CRCmodbus=CRCfunc(CRCmodbus,(ModBusInReg[AdresWord+i]>>8));         ModBusPUT(ModBusInReg[AdresWord+i]>>0);         CRCmodbus=CRCfunc(CRCmodbus,(ModBusInReg[AdresWord+i]>>0));         }       ModBusPUT(CRCmodbus);       ModBusPUT(CRCmodbus>>8);       //конец       CRCmodbus=0xFFFF; //установить начальное значение CRC       return;//повторный запрос не требуется       } #endif           /////////////////////////////////////////////////////////////////////////////     //Код функции 05 - запись выходного/внутреннего бита     /*05 Force Single Coil            ОПИСАНИЕ            Установка единичного выхода (ссылка 0Х) в ON или OFF.            При широковещательной передаче функция устанавливает все выходы с данным адресом во всех подчиненных контроллерах.            ЗАМЕЧАНИЕ Функция может пересекаться с установкой защиты                           памяти и установкой недоступности выходов.            ЗАПРОС            Запрос содержит номер выхода для установки. Выходы адресуются начиная с 0. Выход 1 адресуется как 0.           Состояние, в которое необходимо установить выход (ON/OFF) описывается в поле данных.            Величина FF00 Hex - ON. Величина 0000 - OFF. Любое другое число неверно и не влияет на выход.           В приведенном ниже примере устанавливается выход 173 в состояние ON в подчиненном устройстве 17.            Запрос            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						05	1           Адрес выхода мл.					00	2           Адрес выхода ст.					AC	3           Данные ст.						FF	4           Данные мл.						00	5           Контрольная сумма					--               ОТВЕТ            Нормальный ответ повторяет запрос.            Ответ            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						05	1           Адрес выхода мл.					00	2           Адрес выхода ст.					AC	3           Данные ст.						FF	4           Данные мл.						00	5           Контрольная сумма					--       */ #if ModBusUseFunc5!=0          if(PaketRX[1]==0x05)       {       //вычисление адреса записываемого выхода       unsigned short AdresBit=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //проверка на допустимый адрес         if(AdresBit>=ModBusMaxOutBit)         {//если неправильный адрес         CRCmodbus=0xFFFF; //установить начальное значение CRC         return;//Ошибка, повторный запрос не требуется         }       //установка сброс бита       switch (((((unsigned short)PaketRX[4])<<8)|(PaketRX[5])))         {         case 0xFF00:         //установка бита         ModBusOutBit[(AdresBit>>3)].byte|=(1<<(AdresBit&7));         break;         case 0x0000:         //сброс бита         ModBusOutBit[(AdresBit>>3)].byte&=(~(1<<(AdresBit&7)));         break;         default:           {//конец           CRCmodbus=0xFFFF; //установить начальное значение CRC           return;//Ошибка, повторный запрос не требуется           }          }       //Ответ       for(int i=0;i<8;i++) ModBusPUT(PaketRX[i]);//запускаем передачу пакета ответа       ModBus2PrgOutBit();//Считывание регистров Модбас (ModBus->GlobalDate)       //конец       CRCmodbus=0xFFFF; //установить начальное значение CRC       return;//повторный запрос не требуется        } #endif          /////////////////////////////////////////////////////////////////////////////     //Код функции 06 - запись выходного/внутреннего регистра.      /*Функция аналогична 05, но оперирует с регистрами (словами).      В запросе указывается номер выходного/внутреннего регистра и его значение.      06 Preset Single Register            ОПИСАНИЕ. Записывает величину в единичный регистр (ссылка 4Х).           При щироковезательной передаче на всех подчиненных устройствах устанавливается один и тот же регистр.            ЗАМЕЧАНИЕ            Функция может пересекаться с установленной защитой памяти.            ЗАПРОС            Запрос содержит ссылку на регистр, который необходимо установить. Регистры адресуются с 0.           Величина, в которую необходимо установить регистр передается в поле данных.            Контроллеры M84 и 484 используют 10-ти битную величину, старшие шесть бит заполняются 0.            Все другие контроллерыиспользуют 16 бит.           В приведенном ниже примере в регистр 40002 записывается величина 0003 Hex в подчиненном устройстве 17.            Запрос            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						06	1           Адрес регистра мл.					00	2           Адрес регистра ст.					01	3           Данные ст.						00	4           Данные мл.						03	5           Контрольная сумма					--               ОТВЕТ            Нормальный ответ повторяет запрос.            Ответ            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						06	1           Адрес регистра мл.					00	2           Адрес регистра ст.					01	3           Данные ст.						00	4           Данные мл.						03	5           Контрольная сумма					--       */ #if ModBusUseFunc6!=0         if(PaketRX[1]==0x06)       {       //вычисление адреса записываемого выхода       unsigned short AdresWord=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //проверка на допустимый адрес         if(AdresWord>=(ModBusMaxOutReg))         {//если неправильный адрес         CRCmodbus=0xFFFF; //установить начальное значение CRC         return;//Ошибка, повторный запрос не требуется         }       //запись слова       ModBusOutReg[AdresWord]=(((((unsigned short)PaketRX[4])<<8)|(PaketRX[5])));       //Ответ       for(int i=0;i<8;i++) ModBusPUT(PaketRX[i]);//запускаем передачу пакета ответа       ModBus2PrgOutReg();//Считывание регистров Модбас (ModBus->GlobalDate)       //конец       CRCmodbus=0xFFFF; //установить начальное значение CRC       return;//повторный запрос не требуется       } #endif          /////////////////////////////////////////////////////////////////////////////     //Код функции 0x0F - запись нескольких выходных/внутренних битов.      /*В запросе указывается начальный адрес бита, количество бит для записи, счетчик байтов и непосредственно значения.      15 (0F Hex) Force Multiple Coils            ОПИСАНИЕ            Устанавливает каждый выход (ссылка 0Х) последовательности выходов в одно из состояний ON или OFF.            При широковещательной передаче функция устанавливает подобные выходы на всех подчиненных.            ЗАМЕЧАНИЕ Функция может пересекаться с установкой защиты памяти и установкой недоступности выходов.            ЗАПРОС            Запрос специфицирует выходы для установки. Выходы адресуются начиная с 0.           Ниже показан пример запроса на установку последовательности выходов начиная с 20 (адресуется как 19)            в подчиненном устройстве 17.           Поле данных запроса содержит 2 байта: CD 01 Hex (1100 1101 0000 0001 двоичное).            Соответствие битов и выходов представлено ниже:            Бит:    1  1  0  0  1  1  0  1		0  0  0  0  0  0   0  1            Выход: 27 26 25 24 23 22 21 20		-  -  -  -  -  -  29 28            Запрос            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						0F	1           Адрес выхода ст.					00	2           Адрес выхода мл.					13	3           Кол-во выходов ст.					00	4           Кол-во выходов мл.					0A	5           Счетчик байт						02	6           Данные для установки (Выходы 27-20)			CD	7           Данные для установки (Выходы 29-28) 			01	8           Контрольная сумма					--	9               ОТВЕТ            Нормальный ответ возвращает адрес подчиненного, код функции, начальный адрес, и количество установленных выходов.           Это пример ответа на представленный выше запрос.            Ответ            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						0F	1           Адрес выхода ст.					00	2           Адрес выхода мл.					13	3           Кол-во выходов ст.					00	4           Кол-во выходов мл.					0A	5           Контрольная сумма					--     */ #if ModBusUseFunc15!=0         if(PaketRX[1]==0x0F)       {       //вычисление адреса записываемых бит       unsigned short AdresBit=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //вычисление количества записываемых бит       unsigned short KolvoBit=(((((unsigned short)PaketRX[4])<<8)|(PaketRX[5])));       //если неправильный адрес и количество       if(((AdresBit+KolvoBit)>ModBusMaxOutBit) || (KolvoBit>ModBusMaxOutBitRX))         {//тады конец         CRCmodbus=0xFFFF; //установить начальное значение CRC         return;//Ошибка, повторный запрос не требуется         }       //установка битов       unsigned char Bit=(AdresBit&7);//указатель бит в ModBusOutBit[]       AdresBit=AdresBit>>3;//указатель байт ModBusOutBit[]       //цикл по битам       for(int i=0;i<KolvoBit;i++)         {         if(PaketRX[7+(i>>3)]&(1<<(i&7)))//если текущий бит PaketRX равен 1           {//устанавливаем бит в ModBusOutBit[]           ModBusOutBit[AdresBit].byte=(ModBusOutBit[AdresBit].byte)|((unsigned char)(1<<Bit));           }         else           {//сбрасываем бит ModBusOutBit[]           ModBusOutBit[AdresBit].byte=(ModBusOutBit[AdresBit].byte)&((unsigned char)(~(1<<Bit)));           }         //инкрементруем указатели          Bit++;if(Bit==8){Bit=0;AdresBit++;}         }                  //вычисляем CRC пакета передачи и передаем       CRCmodbus=0xFFFF;       for(int i=0;i<6;i++)         {         ModBusPUT(PaketRX[i]);         CRCmodbus=CRCfunc(CRCmodbus,(PaketRX[i]));         }       ModBusPUT(CRCmodbus);       ModBusPUT(CRCmodbus>>8);                  ModBus2PrgOutBit();//Считывание регистров Модбас (ModBus->GlobalDate)              //конец       CRCmodbus=0xFFFF; //установить начальное значение CRC       return;//повторный запрос не требуется       } #endif          //Код функции 0x10 запись нескольких выходных/внутренних регистров.     /*16 (10 Hex) Preset Multiple Regs            ОПИСАНИЕ            Запись данных в последовательность регистров (ссылка 4Х).            При широковещательной передаче, функция устанавливает подобные регистры во всех подчиненных устройствах.            ЗАМЕЧАНИЕ            Функция может пересекаться с установленной защитой памяти.            ЗАПРОС            Запрос специфицирует регистры для записи. Регистры адресуются начиная с 0.           Данные для записи в регистры содержатся в поле данных запроса.            Контроллеры M84 и 484 используют 10-битовую величину, со старшими шестью битами установленными в 0.            Все остальные контроллеры используют 16 бит.           Ниже приведен пример запроса на установку двух регистров начиная с 40002 в 00 0A и 01 02 Hex,            в подчиненном устройстве 17:            Запрос            Имя поля						Пример                                                                   (Hex)            Адрес подчиненного					11	0           Функция						10	1           Начальный адрес					00	2           Начальный адрес					01	3           Кол-во регистров ст.					00	4           Кол-во регистров мл.					02	5           Счетчик байт						04	6           Данные ст.						00	7           Данные мл.						0A	8           Данные ст.						01	9           Данные мл.						02	10           Контрольная сумма					--               ОТВЕТ            Нормальный ответ содержит адрес подчиненного, код функции, начальный адрес, и количество регистров.      */ #if ModBusUseFunc16!=0          if(PaketRX[1]==0x10)       {       //вычисление адреса записываемых слов       unsigned short b=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //вычисление количества записываемых слов       unsigned short c=(((((unsigned short)PaketRX[4])<<8)|(PaketRX[5])));       //если неправильный адрес и количество       if(((b+c)>ModBusMaxOutReg) || c>ModBusMaxOutRegRX || c==0)         {//тады конец         CRCmodbus=0xFFFF;//установить начальное значение CRC         return;//Ошибка, повторный запрос не требуется         }       //Копирование из пакета в регистр ModBusOutReg[]       for(int i=0;i<c;i++)         {         ModBusOutReg[b+i]=(((unsigned short)PaketRX[7+(i<<1)])<<8)|(PaketRX[8+(i<<1)]);         }       //вычисляем CRC пакета передачи и передаем       CRCmodbus=0xFFFF;       for(int i=0;i<6;i++)         {         ModBusPUT(PaketRX[i]);         CRCmodbus=CRCfunc(CRCmodbus,(PaketRX[i]));         }       ModBusPUT(CRCmodbus);       ModBusPUT(CRCmodbus>>8);       ModBus2PrgOutReg();//Считывание регистров Модбас (ModBus->GlobalDate)       //конец       CRCmodbus=0xFFFF; //установить начальное значение CRC       return;//повторный запрос не требуется        } #endif              /////////////////////////////////////////////////////////////////////////////     //полный конец     CRCmodbus=0xFFFF; //установить начальное значение CRC     return;////Ошибка, нераспознана команда, повторный запрос не требуется     }   return;//повторный запрос не требуется    }  //Функция конвертация шеснадцатиричных символов в число static inline unsigned char Hex2Dig(unsigned char h)   {   if((h>='0')&&(h<='9')) return (h -'0');   if((h>='A')&&(h<='F')) return (h -'A'+10);   return 0;   } static unsigned char LRCmodbus;//тукущий LRC static unsigned char Simvol0;//предидущий принятвй символ #define ASCII_CR (0x0D)//возврат каретки  #define ASCII_LF (0x0A)//перевод строки static const unsigned char BCD[]="0123456789ABCDEF";//строка для конвертации числа в символ  //Функция обработки Сообщений модбас ASCII void ModBusASCII(void)   {   if(Sost==0)     {//Состояние прием     while(!0)       {//Цикл приема символов       unsigned short Tmp=ModBusGET(); //читаем символ из входного потока       if(Tmp==0) return; //если нет данных повторный запрос не требуется        //Символ принят       Tmp=Tmp&0xFF;//отбрасываем признак приема байта       //проверка на начало пакета       if(Tmp==':')         {//начало пакета         LRCmodbus=0;//обнуляем LRC         UkPaket=0;//указатель в массиве, текущий принятый символ         continue;//запускаем повторный запрос символа         }               //проверка на алфавит сообщения       if(!(            ((Tmp>='0')&&(Tmp<='9'))||            ((Tmp>='A')&&(Tmp<='F'))||            (Tmp==ASCII_CR)||            (Tmp==ASCII_LF)            ))          {         return;//Ошибка, повторный запрос не требуется         }                //сохраняем принятый символ       if((UkPaket&1)==0)         {//указатель принятых данных четный 0,2,4,6...         Simvol0=Tmp; //сохраняем первый символ пакета         UkPaket++; //икреметируем указатель пакета         continue;//запускаем повторный запрос          }       else          {//указатель принятых данных нечетный 1,3,5,7...         if(Tmp!=ASCII_LF)           {//не достигнут конец           PaketRX[UkPaket>>1]=(Hex2Dig(Simvol0)<<4)|(Hex2Dig(Tmp));//сохраняем байт пакета            LRCmodbus=LRCmodbus-PaketRX[UkPaket>>1];//считаем LRC           UkPaket++;//икреметируем указатель пакета           if(UkPaket>(ModBusMaxPaketRX<<1))//проверка на переполнение             {//Буфер приема переполнился             UkPaket=0;//сбросить указатель пакета             return;//ошибка, повторный запрос не требуется             }           }         else break;         }       }                //Проверка LCR     if(LRCmodbus!=0) return;//Ошибка, повторный запрос не требуется          //Провекка адреса     if((PaketRX[0]!=ModBusID)&&(PaketRX[0]!=ModBusID_FF))       {//Не наш адрес       return;//повторный запрос не требуется       }            //преходим в состояние передача     Sost=!0;     TimModbus=ModBusSysTimer;//запомнить таймер #if ModBusMaxPauseResp!=0       return;//повторный запрос не требуется #endif       }        /////////////////////////////////////////////////////////////////////////////    if(Sost!=0  #if ModBusMaxPauseResp!=0           && (ModBusSysTimer-TimModbus)>=ModBusMaxPauseResp #endif           )     {//Состояние передача ответа     Sost=0;     /////////////////////////////////////////////////////////////////////////////         //                       обработка команд                                  //     ///////////////////////////////////////////////////////////////////////////// #if ModBusUseFunc1!=0          //01 Чтение статуса выходов      if(PaketRX[1]==0x01)       {       //вычисление адреса запрашиваемых бит       unsigned short AdresBit=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //вычисление количества запрашиваемых бит       unsigned short KolvoBit=((((unsigned short)PaketRX[4])<<8)|(PaketRX[5]));       //если неправильный адрес и количество       if((AdresBit+KolvoBit)>(ModBusMaxOutBit) || KolvoBit>ModBusMaxOutBitTX || KolvoBit==0)         {//конец         return;//Ошибка, повторный запрос не требуется         }       Prg2ModBusOutBit();//Заполнение регистров Модбас (GlobalDate->ModBus)       //формирование пакета ответа       ModBusPUT(':');       //адрес       ModBusPUT(BCD[PaketRX[0]>>4]);//Передаем старший        ModBusPUT(BCD[PaketRX[0]&0x0F]);//передаем младший       LRCmodbus=0-PaketRX[0];//считаем LRC       //код команды           ModBusPUT(BCD[1>>4]);//Передаем старший        ModBusPUT(BCD[1&0x0F]);//передаем младший       LRCmodbus=LRCmodbus-1;//считаем LRC       //количества полных байт       ModBusPUT(BCD[((KolvoBit+7)>>3)>>4]);//Передаем старший        ModBusPUT(BCD[((KolvoBit+7)>>3)&0x0F]);//передаем младший       LRCmodbus=LRCmodbus-((KolvoBit+7)>>3);//считаем LRC       //копирование битов в пакет ответа       unsigned char TxByte=0;//текущий байт       unsigned char Bit=AdresBit&7;//указатель бит в ModBusOutBit[]       AdresBit=AdresBit>>3;//указатель байт ModBusOutBit[]       //копирование из регистра ModBusOutBit[] в пакет       int i=0;       while(!0)         {         if((ModBusOutBit[AdresBit].byte)&(1<<Bit))//если текущий бит ModBusOutBit[] равен 1           {//устанавливаем бит в пакете           TxByte=TxByte|(1<<(i&7));           }         //инкрементруем указатели          Bit++;         if(Bit==8){Bit=0;AdresBit++;}         i++;         if((i&7)==0)           {           ModBusPUT(BCD[TxByte>>4]);//Передаем старший            ModBusPUT(BCD[TxByte&0x0F]);//передаем младший           LRCmodbus=LRCmodbus-TxByte;//считаем LRC           TxByte=0;           if(i==KolvoBit) break; else continue;           }         if(i==KolvoBit)            {           ModBusPUT(BCD[TxByte>>4]);//Передаем старший            ModBusPUT(BCD[TxByte&0x0F]);//передаем младший           LRCmodbus=LRCmodbus-TxByte;//считаем LRC           break;           }         }       ModBusPUT(BCD[LRCmodbus>>4]);       ModBusPUT(BCD[LRCmodbus&0x0F]);       ModBusPUT(ASCII_CR);       ModBusPUT(ASCII_LF);       //конец       return;//повторный запрос не требуется       } #endif #if ModBusUseFunc2!=0          //02 Read Input Status      if(PaketRX[1]==0x02)       {       //вычисление адреса запрашиваемых бит       unsigned short AdresBit=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //вычисление количества запрашиваемых бит       unsigned short KolvoBit=((((unsigned short)PaketRX[4])<<8)|(PaketRX[5]));       //если неправильный адрес и количество       if((AdresBit+KolvoBit)>(ModBusMaxInBit) || KolvoBit>ModBusMaxInBitTX || KolvoBit==0)         {//конец         return;//Ошибка, повторный запрос не требуется         }       Prg2ModBusInBit();//Заполнение регистров Модбас (GlobalDate->ModBus)       //формирование пакета ответа       ModBusPUT(':');       //адрес       ModBusPUT(BCD[PaketRX[0]>>4]);//Передаем старший        ModBusPUT(BCD[PaketRX[0]&0x0F]);//передаем младший       LRCmodbus=0-PaketRX[0];//считаем LRC       //код команды           ModBusPUT(BCD[2>>4]);//Передаем старший        ModBusPUT(BCD[2&0x0F]);//передаем младший       LRCmodbus=LRCmodbus-2;//считаем LRC       //количества полных байт       ModBusPUT(BCD[((KolvoBit+7)>>3)>>4]);//Передаем старший        ModBusPUT(BCD[((KolvoBit+7)>>3)&0x0F]);//передаем младший       LRCmodbus=LRCmodbus-((KolvoBit+7)>>3);//считаем LRC       //копирование битов в пакет ответа       unsigned char TxByte=0;//текущий байт       unsigned char Bit=AdresBit&7;//указатель бит в ModBusOutBit[]       AdresBit=AdresBit>>3;//указатель байт ModBusOutBit[]       //копирование из регистра ModBusOutBit[] в пакет       int i=0;       while(!0)         {         if((ModBusInBit[AdresBit].byte)&(1<<Bit))//если текущий бит ModBusOutBit[] равен 1           {//устанавливаем бит в пакете           TxByte=TxByte|(1<<(i&7));           }         //инкрементруем указатели          Bit++;         if(Bit==8){Bit=0;AdresBit++;}         i++;         if((i&7)==0)           {           ModBusPUT(BCD[TxByte>>4]);//Передаем старший            ModBusPUT(BCD[TxByte&0x0F]);//передаем младший           LRCmodbus=LRCmodbus-TxByte;//считаем LRC           TxByte=0;           if(i==KolvoBit) break; else continue;           }         if(i==KolvoBit)            {           ModBusPUT(BCD[TxByte>>4]);//Передаем старший            ModBusPUT(BCD[TxByte&0x0F]);//передаем младший           LRCmodbus=LRCmodbus-TxByte;//считаем LRC           break;           }         }       ModBusPUT(BCD[LRCmodbus>>4]);       ModBusPUT(BCD[LRCmodbus&0x0F]);       ModBusPUT(ASCII_CR);       ModBusPUT(ASCII_LF);       //конец       return;//повторный запрос не требуется       } #endif #if ModBusUseFunc3!=0          //03 Read Holding Registers      if(PaketRX[1]==0x03)       {       //вычисление адреса запрашиваемых слов       unsigned short AdresWord=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //вычисление адреса количества запрашиваемых слов       unsigned short KolvoWord=((((unsigned short)PaketRX[4])<<8)|(PaketRX[5]));        //если неправильный адрес и количество       if(((AdresWord+KolvoWord)>ModBusMaxOutReg) || KolvoWord>ModBusMaxOutRegTX)         {//тады конец         return;//Ошибка, повторный запрос не требуется         }       Prg2ModBusOutReg();//Заполнение регистров Модбас (GlobalDate->ModBus)       //формирование пакета ответа       ModBusPUT(':');       //адрес       ModBusPUT(BCD[PaketRX[0]>>4]);//Передаем старший        ModBusPUT(BCD[PaketRX[0]&0x0F]);//передаем младший       LRCmodbus=0-PaketRX[0];//считаем LRC       //код команды       ModBusPUT(BCD[3>>4]);//Передаем старший        ModBusPUT(BCD[3&0x0F]);//передаем младший       LRCmodbus=LRCmodbus-3;//считаем LRC       //количества полных байт       ModBusPUT(BCD[(KolvoWord<<1)>>4]);//Передаем старший        ModBusPUT(BCD[(KolvoWord<<1)&0x0F]);//передаем младший       LRCmodbus=LRCmodbus-(KolvoWord<<1);//считаем LRC       //Копирование из регистра ModBusOutReg[] в пакет ответа       for(int i=0;i<KolvoWord;i++)         {         ModBusPUT(BCD[((ModBusOutReg[AdresWord+i])>>8)>>4]);//Передаем старший          ModBusPUT(BCD[((ModBusOutReg[AdresWord+i])>>8)&0x0F]);//передаем младший         LRCmodbus=LRCmodbus-((ModBusOutReg[AdresWord+i])>>8);//считаем LRC         ModBusPUT(BCD[(((ModBusOutReg[AdresWord+i])>>0)>>4)&0x0F]);//Передаем старший          ModBusPUT(BCD[(((ModBusOutReg[AdresWord+i])>>0)>>0)&0x0F]);//передаем младший         LRCmodbus=LRCmodbus-((ModBusOutReg[AdresWord+i])>>0);//считаем LRC         }       ModBusPUT(BCD[LRCmodbus>>4]);       ModBusPUT(BCD[LRCmodbus&0x0F]);       ModBusPUT(ASCII_CR);       ModBusPUT(ASCII_LF);       //конец       return;//повторный запрос не требуется       } #endif #if ModBusUseFunc4!=0          //04 Read Input Registers      if(PaketRX[1]==0x04)       {       //вычисление адреса запрашиваемых слов       unsigned short AdresWord=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //вычисление адреса количества запрашиваемых слов       unsigned short KolvoWord=((((unsigned short)PaketRX[4])<<8)|(PaketRX[5]));        //если неправильный адрес и количество       if(((AdresWord+KolvoWord)>ModBusMaxOutReg) || KolvoWord>ModBusMaxOutRegTX)         {//тады конец         return;//Ошибка, повторный запрос не требуется         }       Prg2ModBusInReg();//Заполнение регистров Модбас (GlobalDate->ModBus)       //формирование пакета ответа       ModBusPUT(':');       //адрес       ModBusPUT(BCD[PaketRX[0]>>4]);//Передаем старший        ModBusPUT(BCD[PaketRX[0]&0x0F]);//передаем младший       LRCmodbus=0-PaketRX[0];//считаем LRC       //код команды       ModBusPUT(BCD[4>>4]);//Передаем старший        ModBusPUT(BCD[4&0x0F]);//передаем младший       LRCmodbus=LRCmodbus-4;//считаем LRC       //количества полных байт       ModBusPUT(BCD[(KolvoWord<<1)>>4]);//Передаем старший        ModBusPUT(BCD[(KolvoWord<<1)&0x0F]);//передаем младший       LRCmodbus=LRCmodbus-(KolvoWord<<1);//считаем LRC       //Копирование из регистра ModBusOutReg[] в пакет ответа       for(int i=0;i<KolvoWord;i++)         {         ModBusPUT(BCD[((ModBusInReg[AdresWord+i])>>8)>>4]);//Передаем старший          ModBusPUT(BCD[((ModBusInReg[AdresWord+i])>>8)&0x0F]);//передаем младший         LRCmodbus=LRCmodbus-((ModBusInReg[AdresWord+i])>>8);//считаем LRC         ModBusPUT(BCD[(((ModBusInReg[AdresWord+i])>>0)>>4)&0x0F]);//Передаем старший          ModBusPUT(BCD[(((ModBusInReg[AdresWord+i])>>0)>>0)&0x0F]);//передаем младший         LRCmodbus=LRCmodbus-((ModBusInReg[AdresWord+i])>>0);//считаем LRC         }       ModBusPUT(BCD[LRCmodbus>>4]);       ModBusPUT(BCD[LRCmodbus&0x0F]);       ModBusPUT(ASCII_CR);       ModBusPUT(ASCII_LF);       //конец       return;//повторный запрос не требуется       } #endif #if ModBusUseFunc5!=0          //05 Force Single Coil      if(PaketRX[1]==0x05)       {       //вычисление адреса записываемого выхода       unsigned short AdresBit=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //проверка на допустимый адрес         if(AdresBit>=ModBusMaxOutBit)//если неправильный адрес         {//тады конец         return;//Ошибка, повторный запрос не требуется         }       //установка сброс бита       switch (((((unsigned short)PaketRX[4])<<8)|(PaketRX[5])))         {         case 0xFF00:         //установка бита         ModBusOutBit[(AdresBit>>3)].byte|=(1<<(AdresBit&7));         break;         case 0x0000:         //сброс бита         ModBusOutBit[(AdresBit>>3)].byte&=(~(1<<(AdresBit&7)));         break;         default:           { //конец           return;//Ошибка, повторный запрос не требуется           }          }                      //Ответ       ModBusPUT(':');       for(int i=0;i<7;i++)         {         ModBusPUT(BCD[PaketRX[i]>>4]);//Передаем старший          ModBusPUT(BCD[PaketRX[i]&0x0F]);//передаем младший         }       ModBusPUT(ASCII_CR);       ModBusPUT(ASCII_LF);                 ModBus2PrgOutBit();//Считывание регистров Модбас (ModBus->GlobalDate)              //конец       return;//повторный запрос не требуется        } #endif #if ModBusUseFunc6!=0          //06 Preset Single Register      if(PaketRX[1]==0x06)       {       //вычисление адреса записываемого выхода       unsigned short AdresWord=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));              //проверка на допустимый адрес         if(AdresWord>=(ModBusMaxOutReg))//если неправильный адрес         {//тады конец         return;//Ошибка, повторный запрос не требуется         }       //запись слова       ModBusOutReg[AdresWord]=(((((unsigned short)PaketRX[4])<<8)|(PaketRX[5])));              //Ответ       ModBusPUT(':');       for(int i=0;i<7;i++)         {         ModBusPUT(BCD[PaketRX[i]>>4]);//Передаем старший          ModBusPUT(BCD[PaketRX[i]&0x0F]);//передаем младший         }       ModBusPUT(ASCII_CR);       ModBusPUT(ASCII_LF);              ModBus2PrgOutReg();//Считывание регистров Модбас (ModBus->GlobalDate)                //конец       return;//повторный запрос не требуется       } #endif #if ModBusUseFunc15!=0           //15 (0F Hex) Force Multiple Coils      if(PaketRX[1]==0x0F)       {       //вычисление адреса записываемых бит       unsigned short AdresBit=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //вычисление количества записываемых бит       unsigned short KolvoBit=(((((unsigned short)PaketRX[4])<<8)|(PaketRX[5])));       //если неправильный адрес и количество       if(((AdresBit+KolvoBit)>ModBusMaxOutBit) || (KolvoBit>ModBusMaxOutBitRX))         {//тады конец         return;//Ошибка, повторный запрос не требуется         }       //установка битов       unsigned char Bit=(AdresBit&7);//указатель бит в ModBusOutBit[]       AdresBit=AdresBit>>3;//указатель байт ModBusOutBit[]       //цикл по битам       for(int i=0;i<KolvoBit;i++)         {         if(PaketRX[7+(i>>3)]&(1<<(i&7)))//если текущий бит PaketRX равен 1           {//устанавливаем бит в ModBusOutBit[]           ModBusOutBit[AdresBit].byte=(ModBusOutBit[AdresBit].byte)|((unsigned char)(1<<Bit));           }         else           {//сбрасываем бит ModBusOutBit[]           ModBusOutBit[AdresBit].byte=(ModBusOutBit[AdresBit].byte)&((unsigned char)(~(1<<Bit)));           }         //инкрементруем указатели          Bit++;if(Bit==8){Bit=0;AdresBit++;}         }                         //вычисляем LRC пакета передачи и передаем       LRCmodbus=0;       ModBusPUT(':');       for(int i=0;i<6;i++)         {         ModBusPUT(BCD[PaketRX[i]>>4]);//Передаем старший          ModBusPUT(BCD[PaketRX[i]&0x0F]);//передаем младший         LRCmodbus=LRCmodbus-PaketRX[i];//считаем LRC         }       ModBusPUT(BCD[LRCmodbus>>4]);       ModBusPUT(BCD[LRCmodbus&0x0F]);       ModBusPUT(ASCII_CR);       ModBusPUT(ASCII_LF);              ModBus2PrgOutBit();//Считывание регистров Модбас (ModBus->GlobalDate)              //конец       return;//повторный запрос не требуется       } #endif #if ModBusUseFunc16!=0             //16 (10 Hex) Preset Multiple Regs      if(PaketRX[1]==0x10)       {       //вычисление адреса записываемых слов       unsigned short b=(((((unsigned short)PaketRX[2])<<8)|(PaketRX[3])));       //вычисление количества записываемых слов       unsigned short c=(((((unsigned short)PaketRX[4])<<8)|(PaketRX[5])));              //если неправильный адрес и количество       if(((b+c)>ModBusMaxOutReg) || c>ModBusMaxOutRegRX)         {         //тады конец         return;//Ошибка, повторный запрос не требуется         }       //Копирование из пакета в регистр ModBusOutReg[]       for(int i=0;i<c;i++)         {         ModBusOutReg[b+i]=(((unsigned short)PaketRX[7+(i<<1)])<<8)|(PaketRX[8+(i<<1)]);         }              //вычисляем LRC пакета передачи и передаем       LRCmodbus=0;       ModBusPUT(':');       for(int i=0;i<6;i++)         {         ModBusPUT(BCD[PaketRX[i]>>4]);//Передаем старший          ModBusPUT(BCD[PaketRX[i]&0x0F]);//передаем младший         LRCmodbus=LRCmodbus-PaketRX[i];//считаем LRC         }       ModBusPUT(BCD[LRCmodbus>>4]);       ModBusPUT(BCD[LRCmodbus&0x0F]);       ModBusPUT(ASCII_CR);       ModBusPUT(ASCII_LF);              ModBus2PrgOutReg();//Считывание регистров Модбас (ModBus->GlobalDate)              //конец       return;//повторный запрос не требуется        } #endif         }    //конец   return;//Ошибка, нераспознана команда, повторный запрос не требуется   } 

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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *