单片机学习小组
直播中

fdvcxhtg

8年用户 1016经验值
擅长:嵌入式技术
私信 关注

FreeMODBUS的移植过程是怎样的

FreeMODBUS是什么?
FreeMODBUS的移植过程是怎样的?



回帖(2)

朱虹

2022-1-24 13:55:29
FreeModbus的移植,基于STM32F4+HAL库平台的MODBUS RTU从机

FreeModbus


FreeMODBUS 是针对通用的Modbus协议栈在嵌入式系统中应用的一个实现。Modbus协议是一个在工业制造领域中得到广泛应用的一个网络协议。
一个Modbus通信协议栈包括两层:定义了数据结构和功能Modbus应用协议和网络层。
在FreeMODBUS的当前版本中,提供了Modbus Application Protocol v1.1a 的实现并且支持在Modbus over serial line specification 1.0中定义的RTU/ASCII传输模式。从0.7版本开始,FreeModbus也支持在TCP defined in Modbus Messaging on TCP/IP Implementation Guide v1.0a中定义的TCP传输。Freemodbus遵循BSD ,这意味着本协议栈的实现代码可以应用于商业用途。
目前版本的FreeModbus支持如下的功能码:



  • 读输入寄存器 (0x04)
  • 读保持寄存器 (0x03)
  • 写单个寄存器 (0x06)
  • 写多个寄存器 (0x10)
  • 读/写多个寄存器 (0x17)
  • 读取线圈状态 (0x01)
  • 写单个线圈 (0x05)
  • 写多个线圈 (0x0F)
  • 读输入状态 (0x02)
  • 报告从机标识 (0x11)
移植过程



  • 下载freemodbus 源码
    在上面的链接下,可以下载1.6版本的源代码,此版本包括Modbus RTU / ASCII,Modbus TCP,输入/保持寄存器访问,离散寄存器等。此外,它还被移植到以下平台:FreeRTOS / Cortex M3 SAM3S,FreeRTOS / ARM STR71X,FreeRTOS / ARM AT91SAM7X,lwIP / PPP / STR71X,飞思卡尔MCF5235,lwIP / MCF5235,Atmel AVR ATMega168,TI-MSP430,Win32和Linux操作系统。(居然没有STM32平台?)


    移植需要的文件再demomodbus文件夹中
    demo文件夹中的内容☟

    我们需要BARE文件夹里的文件

    之后是modbus文件夹,里面有modbus功能的源代码

  • 新建stm32F4的工程
    因为我使用的是正点原子阿波罗的开发板,我用cubeMX软件进行初始化配置,芯片选择stm32f429IGT6
    时钟,GPIO,调试端口这些按需要进行配置,这里freemodbus需要的外设有1个TIMER和1个UART。(可以看出来,freeModbus需要的外设资源就是这么少)串口是用来承载modbus协议的物理层端口,定时器是用来产生T3.5的中断的,freemodbus是根据这个超时中断来判断一帧数据接收完成的。

    TIMER6的配置,这里预分频器和自动装载寄存器的值可以先随便填一个,因为后续我们在freemodbus的移植文件里还要重新对TIMER进行初始化。


    uart1的配置。这里也是可以先随便填(停止位要配置好),后续在移植文件里还要重新对UART1进行初始化。

    之后记得使能两个外设的中断,这里优先级的设置要提一下,查阅了很多文档,都没有说明这两个外设(串口和定时器)优先级的配置,我在开始配置的时候认为定时器的优先权要高一些,也许freemodbus要使用这个定时器定时查询什么状态。但是在之后移植完成后,我仔细阅读了源代码,认为应该把串口的优先级调高,优先保证将数据从串口中取出,定时器中断的作用就是post一个信号量,使得eMBPoll() 可以 get这个信号量,这个post的动作适当推迟一些应该也没什么问题,所以定时器的中断优先级就可以比中断优先级低。(图中是我一开始配置的优先级,之后我在程序里将这两个外设的优先级对调了一下?)
    时钟配置按需配置就行,记得TIMER6用的PCLK频率是多少,这个需要我们来计算分频系数的。
    配置完成就可以generate code了,我用的最新的F4的HAL库(V1.24.1)
  • 添加*.c *.h文件

    上面是keil的工程目录结构
    sys.cusart.c是我从正点原子代码工程里拷贝来的,里面有些比较好用的宏定义和函数API
    注意sys.h中需要修改1个宏

#define SYSTEM_SUPPORT_OS                0                //系统文件夹是否支持OS,1:支持  0:不支持
然后就是把usart.c中和其他文件中冲突的代码都注释掉,因为这个文件里我们只用如下功能

//重定义fput()
int fputc(int ch, FILE *f)
{        
        while((USART1->SR&0X40)==0);//循环发送
        USART1->DR = (u8) ch;      
        return ch;
}


紧接着添加头文件



  • 正式移植过程
    freemodbus中需要移植的都是BARE文件夹里port的文件





  • port.h

#define ENTER_CRITICAL_SECTION( )   __disable_irq()
#define EXIT_CRITICAL_SECTION( )    __enable_irq()
补充完整这两个宏定义



  • portserial.c
    串口相关的API


/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "sys.h"
extern UART_HandleTypeDef huart1;
/* ----------------------- static functions ---------------------------------*/
void prvvUARTTxReadyISR( void );
void prvvUARTRxISR( void );


添加sys.h头文件,去掉*prvvUARTTxReadyISR( void )*和 prvvUARTRxISR( void )static属性,这是方便在ISR中调用这两个函数。
添加huart1声明,目的是在这个文件中使用这个句柄。

void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
        if(xRxEnable == TRUE)
                 __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
        else
                 __HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);
       
        if(xTxEnable == TRUE)
                 __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);
        else
                 __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);
}


完成串口使能函数体,接收时使用RXNE中断,发送时使用TXE中断
这两个中断是在RM0090中USART状态寄存器 (USART_SR)定义的



BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
        HAL_UART_DeInit(&huart1);//DEINIT cubeMX中的初始化配置
        (void)ucPORT;
        huart1.Instance = USART1;
    huart1.Init.BaudRate = ulBaudRate;


  
    huart1.Init.StopBits = UART_STOPBITS_1;
        switch (eParity)//使用校验位,就需要将uart的数据位配置为9位
  {
          case MB_PAR_ODD:
                        huart1.Init.WordLength = UART_WORDLENGTH_9B;
                        huart1.Init.Parity = UART_PARITY_ODD;
                  break;
          case MB_PAR_EVEN:
                        huart1.Init.WordLength = UART_WORDLENGTH_9B;
                        huart1.Init.Parity = UART_PARITY_EVEN;
                  break;
          default:
                        huart1.Init.WordLength = UART_WORDLENGTH_8B;
                        huart1.Init.Parity = UART_PARITY_NONE;
                  break;
  }
  
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    return FALSE;
  }


                return TRUE;
}
重新对串口1进行初始化,重新配置波特率和校验位,至于数据位,freemodbus中固定为8位,停止位按需配置,端口号暂时我没有用到。

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    /* Put a byte in the UARTs transmit buffer. This function is called
     * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
     * called. */
       
                if(HAL_UART_Transmit(&huart1,(uint8_t*)&ucByte,1,1) == HAL_OK)
                        return TRUE;
                else
                        return FALSE;
}


BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    /* Return the byte in the UARTs receive buffer. This function is called
     * by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
     */


        if(HAL_UART_Receive(&huart1,(uint8_t*)pucByte,1,1) == HAL_OK)
            return TRUE;
        else
                return FALSE;
}


发送和接收1个字节,调用的HAL库函数。

/* Create an interrupt handler for the transmit buffer empty interrupt
* (or an equivalent) for your target processor. This function should then
* call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
* a new character can be sent. The protocol stack will then call
* xMBPortSerialPutByte( ) to send the character.
*/
void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}


/* Create an interrupt handler for the receive interrupt for your target
* processor. This function should then call pxMBFrameCBByteReceived( ). The
* protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
* character.
*/
void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}
这两个函数在串口1ISR中会调用,在这里只需要删掉函数前的static即可



  • porttimer.c


/* ----------------------- Platform includes --------------------------------*/
#include "port.h"


/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"


/* ----------------------- static functions ---------------------------------*/
void prvvTIMERExpiredISR( void );
extern TIM_HandleTypeDef htim6;


/* ----------------------- Start implementation -----------------------------*/


BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
        HAL_TIM_Base_DeInit(&htim6);
       
        TIM_MasterConfigTypeDef sMasterConfig = {0};


  /* USER CODE BEGIN TIM6_Init 1 */


  /* USER CODE END TIM6_Init 1 */
  htim6.Instance = TIM6;
  htim6.Init.Prescaler = 4499;//50us分频,这里使用的timer PCLK频率是90Mhz
  htim6.Init.CounterMode = TIM_COUNTERMODE_UP;




  htim6.Init.Period = usTim1Timerout50us-1;//modbus 规定的TIMEOUT时间


               
  htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
  {
     return FALSE;
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
  {
     return FALSE;
  }
         return TRUE;
       
}


定时器的初始化。这里是被eMBInit进行调用,调用时usTim1Timerout50us参数是用来计算50us的倍数的,这个值在eMBRtuInit()中根据波特率进行计算。

inline void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
        __HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);
        __HAL_TIM_SetCounter(&htim6,0);//这里一定要清零计数器
        HAL_TIM_Base_Start_IT(&htim6);


}


inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
        HAL_TIM_Base_Stop_IT(&htim6);
       
        __HAL_TIM_SetCounter(&htim6,0);
        __HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);
       
}
定时器的启停

/* Create an ISR which is called whenever the timer has expired. This function
* must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
* the timer has expired.
*/
void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}


在TIMER6的ISR中调用,去掉前面static即可



  • portevent.c
    这是实现事件标志的代码,完全不用动,至少不动不会影响代码运行。

  看到这里freemodbus中的工作已经完成,现在需要修改原工程下的一些文件




  • stm32f4xx_it.c


void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
        uint8_t tmp;
        if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_PE))//奇偶校验位判断
        {
                HAL_UART_Receive(&huart1,&tmp,1,1);
        }
       
        else if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)&&__HAL_UART_GET_IT_SOURCE(&huart1,UART_IT_RXNE))
        {
                prvvUARTRxISR();
               
        }
        if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TXE)&&__HAL_UART_GET_IT_SOURCE(&huart1,UART_IT_TXE))
        {
                prvvUARTTxReadyISR();
               
        }
       
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */


  /* USER CODE END USART1_IRQn 1 */
}


/**
  * @brief This function handles TIM6 global interrupt, DAC1 and DAC2 underrun error interrupts.
  */
void TIM6_DAC_IRQHandler(void)
{
  /* USER CODE BEGIN TIM6_DAC_IRQn 0 */


  /* USER CODE END TIM6_DAC_IRQn 0 */
  HAL_TIM_IRQHandler(&htim6);
  /* USER CODE BEGIN TIM6_DAC_IRQn 1 */
        prvvTIMERExpiredISR();
  /* USER CODE END TIM6_DAC_IRQn 1 */
}
修改两个外设的ISR,在UART的ISR中加入了奇偶校验的判断
主函数main.c应用程序


#include "mb.h"
#include "sys.h"
//添加头文件


/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START     0x0001U //寻址地址是从1开始的
#define REG_INPUT_NREGS 4


#define REG_HOLDING_START               ( 1 )
#define REG_HOLDING_NREGS               ( 32 )
/* ----------------------- Static variables ---------------------------------*/
static uint16_t   usRegInputStart = REG_INPUT_START;
static uint16_t   usRegInputBuf[REG_INPUT_NREGS]={0x01,0x02,0x03,0x04};//为了验证使用的初始化值


static USHORT   usRegHoldingStart = REG_HOLDING_START;
static USHORT   usRegHoldingBuf[REG_HOLDING_NREGS];
举报

何洁萍

2022-1-24 13:55:35
添加输入寄存器和保持寄存器的起始地址,定义寄存器大小,定义数组

int main(void)
{
    /* MCU Configuration--------------------------------------------------------*/


  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();


   /* Configure the system clock */
  SystemClock_Config();


  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_TIM6_Init();


        __HAL_TIM_CLEAR_FLAG(&htim6,TIM_FLAG_UPDATE);//是HAL库的一个BUG,初始化完成后会置位UIF
        eMBInit(MB_RTU, 0x01, 1, 9600, MB_PAR_ODD);//Modbus初始化
        eMBEnable();//使能协议栈




  /* Infinite loop */


  while (1)
  {


                eMBPoll();


                PBout(0) = !PBout(0);//这是我点亮LED的语句
                PBout(1) = !PBout(1);


                HAL_Delay(100);//轮询时间为100ms
  }


}
void __aeabi_assert(const char * x1, const char * x2, int x3)
{
       
}
freemodbus中使用了断言函数,这里添加函数体解决断言函数找不到的问题

eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;


    if( ( usAddress >= REG_INPUT_START )
        && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegInputStart );
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }


    return eStatus;
}


eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;


    if( ( usAddress >= REG_HOLDING_START ) && ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegHoldingStart );
        switch ( eMode )
        {
        case MB_REG_READ:
            while( usNRegs > 0 )
            {
                *pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 );
                *pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] & 0xFF );
                iRegIndex++;
                usNRegs--;
            }
            break;


        case MB_REG_WRITE:
            while( usNRegs > 0 )
            {
                usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
                usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
                iRegIndex++;
                usNRegs--;
            }
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}
在这里实现了输入寄存器和保持寄存器的相关功能函数。
试验0x04功能


试验0x06功能


试验0x03功能


试验0x10功能


0x17功能也可以实现,这里就偷懒不放图了
到这里就全部移植完成了
举报

更多回帖

发帖
×
20
完善资料,
赚取积分