Довольно часто в промышленной автоматизации возникает необходимость обмена информацией между контроллером (ПЛК) и различным оборудованием — датчиками, исполнительными устройствами, преобразователями частоты и т.д. Один из самых распространенных протоколов обмена это MODBUS. Его поддержка встроена практически в любой ПЛК или серийно производимый датчик/устройство. Но если в схеме автоматизации присутствует специфическое оборудование для которого необходима электроника на заказ, то перед разработчиком этого устройства встает вопрос — как добавить поддержку протокола MODBUS? Один из вариантов это использование библиотеки FreeMODBUS.

Краткое описание

Библиотека представляет собой свободную реализацию протокола MODBUS, предназначенную для встраиваемых систем. Текущая реализация поддерживает протокол уровня приложения версии 1.1a и транспортные уровни RTU/ASCII/TCP. Библиотека выпущена под лицензией BSD, которая допускает использование ее кода в коммерческих проектах. Поддерживаются следующие функции:

  • Read Input Register (0x04);
  • Read Holding Registers (0x03);
  • Write Single Register (0x06);
  • Write Multiple Registers (0x10);
  • Read/Write Multiple Registers (0x17);
  • Read Coils (0x01);
  • Write Single Coil (0x05);
  • Write Multiple Coils (0x0F);
  • Read Discrete Inputs (0x02);
  • Report Slave ID (0x11).

Этого набора в большинстве случаев достаточно для внедрения поддержки протокола MODBUS в своем проекте. Реализация протокола основана на существующих стандартах и полностью соответствует им. Прием и передача пакетов данных выполнена с помощью конечных автоматов, которые вызываются из функций уровня абстракции оборудования. Библиотека написана на языке С. Это облегчает портирование библиотеки на другие платформы.

Структура исходников

После скачивания архива исходников с сайта и их распаковки структура папок имеет следующий вид: fm_dir1В папке «demo» находятся примеры использования и платформенно-зависимый код. В папке «doc» можно найти документацию в формате Doxygen. В папке «modbus» находится собственно код, обеспечивающий реализацию протокола MODBUS:

fm_dir_2В папках «ascii», «rtu», «tcp» находятся реализации соответствующих транспортных уровней. Исходники неиспользуемых в текущем проекте уровней можно удалить. В папке «functions» находятся реализации поддерживаемых функций. В папке «includes» находятся заголовочные файлы:fm_dir_3Здесь интерес представляет файл «mbconfig.h». С его помощью производится настройка библиотеки под требуемые условия использования:

//Тут следует разрешить поддержку требуемого транспорта
#define MB_ASCII_ENABLED ( 0 )
#define MB_RTU_ENABLED  ( 1 )
#define MB_TCP_ENABLED  ( 0 )

//Здесь следует указать максимальное число кодов
//поддерживаемых функций MODBUS
#define MB_FUNC_HANDLERS_MAX ( 16 )

//Здесь включаем поддержку нужных функций

//Разрешена ли функция READ_INPUT_REGS
#define MB_FUNC_READ_INPUT_ENABLED ( 1 )

//Разрешена ли функция READ_HOLDING_REGS
#define MB_FUNC_READ_HOLDING_ENABLED ( 1 )

//Разрешена ли функция WRITE_SINGLE_HOLDING_REG
#define MB_FUNC_WRITE_HOLDING_ENABLED ( 1 )

//Разрешена ли функция WRITE_MULTIPLE_HOLDING_REGS
#define MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED ( 1 )

//Разрешена ли функция READ_COILS
#define MB_FUNC_READ_COILS_ENABLED ( 1 )

//Разрешена ли функция WRITE_SINGLE_COIL
#define MB_FUNC_WRITE_COIL_ENABLED ( 1 )

//Разрешена ли функция WRITE_MULTIPLE_COILS
#define MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED ( 1 )

//Разрешена ли функция READ_DISCRETE_INPUTS
#define MB_FUNC_READ_DISCRETE_INPUTS_ENABLED ( 1 )

//Разрешена ли функция READWRITE_HOLDINGS_REGS
#define MB_FUNC_READWRITE_HOLDING_ENABLED ( 1 )

Портирование библиотеки

Код, обеспечивающий уровень абстракции над используемым микроконтроллером, располагается в папке «port» и содержит платформенно-зависимый код:

fm_dir_4Для портирования нужно реализовать специфичную для аппаратуры инициализацию последовательного порта, таймера и очереди событий (это требуется, если в вашем проекте используется ОС, в данной статье не рассматривается).

В первую очередь необходимо исправить файл «port.h». В нем нужно переопределить два макроса «ENTER_CRITICAL_SECTION» и «EXIT_CRITICAL_SECTION» для запрещения/разрешения прерываний соответственно.

Далее, если необходимо, адаптируем файл «mbcrc.c» с целью размещения достаточно объемных таблиц кодов CRC в программной памяти для экономии ОЗУ (это нужно для микроконтроллеров с раздельными адресными пространствами кода и данных, например, для AVR).

Файл «porttimer.c» отвечает за реализацию таймера, который используется транспортным уровнем для отсчета временных интервалов:

BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
{
/*производим настройку таймера на генерацию события
через интервал времени равный 50мкc*значение параметра функции*/
return TRUE;
}

INLINE void vMBPortTimersEnable( )
{
//здесь включаем таймер
}

INLINE void vMBPortTimersDisable( )
{
//здесь выключаем таймер
}

/*Тут пишем обработчик события таймера в котором
нужно вызывать функцию pxMBPortCBTimerExpired() для уведомления верхнего 
уровня о прошедшем временном интервале*/
static void prvvTIMERExpiredISR( void )
{
  pxMBPortCBTimerExpired( );
}

Файл «portserial.c» отвечает за работу с последовательным портом:

void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
//здесь соответственно разрешаем/запрещаем прием/передачу
}

BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
/*настраиваем оборудование порта ucPORT на требуемую скорость и режим передачи
настраиваем буферизацию*/
return TRUE;
}

BOOL xMBPortSerialPutByte( CHAR ucByte )
{
//Здесь реализуем вставку байта в буфер передачи
return TRUE;
}

BOOL xMBPortSerialGetByte( CHAR * pucByte )
{
//здесь реализуем выборку байта из буфера приема
return TRUE;
}

/*здесь реализуем обработчик прерывания или аналог в котором вызовом 
функции pxMBFrameCBTransmitterEmpty( ) оповещаем стек протокола о факте
завершения передачи символа*/
static void prvvUARTTxReadyISR( void )
{
  pxMBFrameCBTransmitterEmpty( );
}

/*здесь реализуем обработчик прерывания или аналог в котором вызовом
функции pxMBFrameCBByteReceived( ) оповещаем стек протокола о факте
приема нового символа*/
static void prvvUARTRxISR( void )
{
   pxMBFrameCBByteReceived( );
}

После написания прослойки кода под ваш микроконтроллер нужно обязательно убедиться в ее корректной работе, иначе можно провести продолжительное время, отлавливая необъяснимые ошибки. Лучше всего сначала провести отдельное от стека MODBUS тестирование этого участка кода, а затем тестировать совместно со стеком. После этого портирование можно считать законченным.

Подключение к своему проекту и минимальный код

Для использования библиотеки в своем проекте необходимо добавить в него исходники из папки «modbus» и написать платформенно-зависимый код для своего микроконтроллера (файлы в папке «port»). Также возможен вариант, что в библиотеке уже есть код для вашего микроконтроллера.

Далее нужно подключить к проекту заголовочные файлы библиотеки:

#include “mb.h”
#include “mbport.h”

Минимальный код выглядит так:

#include 
//подключаемые файлы библиотеки
#include "mb.h"
#include "mbport.h"

//задаем стартовый адрес и количество регистров MODBUS
#define MB_INPUT_REG_START 20000
#define MB_INPUT_REGS_NUM  4

//выделяем память для регистров в виде массива
uint16_t mbInputReg[MB_INPUT_REGS_NUM];

void doWork(void)
{
  /*для примера будем увеличивать значение регистра*/
  mbInputReg[0]++;
}

int main(void)
{
  /*Инициализируем стек, эта функция должна вызываться первой. 
  Здесь переводим стек в режим RTU с адресом 10, скоростью обмена 38400,
  с контролем четности. Внимание! В данном примере нет обработки ошибок.*/
  eMBInit( MB_RTU, 0x0A, 38400, MB_PAR_EVEN );
 
  //...
 
  //Разрешаем работу протокола когда мы готовы к этому
  eMBEnable( );
 
  for(;;)
  {
     //делаем что либо полезное
     doWork(); 
     /*периодически вызываем функцию библиотеки для обработки пакетов MODBUS
     период вызова должен быть короче времени допустимой задержки реакции
     устройства на событие на шине*/
     eMBPoll();
  }     
}

//обработчики соответствующих команд MODBUS
eMBErrorCode 
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    uint16_t        index;
    /*проверяем адрес на допустимость*/
    if( ( usAddress >= MB_INPUT_REG_START )
        && ( usAddress + usNRegs <= MB_INPUT_REG_START + MB_INPUT_REGS_NUM ) )
    {         
        index = usAddress-MB_INPUT_REG_START;
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ =
                ( uint8_t )( mbInputReg[index] >> 8 );
            *pucRegBuffer++ =
                ( uint8_t )( mbInputReg[index] & 0xFF );
            index++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
                 eMBRegisterMode eMode )
{
    return MB_ENOREG;
}


eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
               eMBRegisterMode eMode )
{
    return MB_ENOREG;
}

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    return MB_ENOREG;
}

Остальные тонкости использования смотрите в официальной документации библиотеки. Также вы можете заказать разработку электроники с использованием MODBUS в нашей компании.