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-разрядных микроконтроллеров.