星享社 发表于 2025-9-24 12:00

【沁恒CH32V307开发板测评】FreeModbus移植

本次测评之所以移植FreeModbus,是因为在项目过程使用RS485是比较频繁,而使用开源的FreeModbus,可使设备快速具备Modbus从站或主站功能,降低开发难度,提升项目效率,同时充分利用RISC-V架构的低功耗与高性能特性。
一、FreeModbus简单介绍
1、是什么
FreeMODBUS是一个完全开源(GPLv2许可)的Modbus协议栈,也就是一种modbus库
2、特点
1)轻量级​​
专为​​资源受限的嵌入式系统​​设计,RAM占用通常小于2KB,适合微控制器环境
2)协议支持全面​​
支持​​Modbus RTU​​(二进制编码,高效紧凑)和​​Modbus ASCII​​(文本编码,易于调试)两种串行模式,覆盖了工业应用中最常见的通信方式
3)可移植性与模块化​​
库采用模块化设计,​​核心协议栈与硬件层分离​​。开发者只需实现有限的硬件接口函数(如串口收发、定时器控制),即可轻松移植到不同平台
4)功能码覆盖广泛​​
支持Modbus标准功能码,如读线圈状态(01)、读离散输入(02)、读保持寄存器(03)、读输入寄存器(04)、写单个线圈(05)、写单个寄存器(06)、写多个线圈(15)和写多个寄存器(16)等
5)稳定性
FreeMODBUS拥有​​活跃的社区支持和丰富的实践案例​​,稳定性高,降低了项目风险
二、移植过程
1、在官网的github仓库下载FreeModbus源码
GitHub - cwalter-at/freemodbus: BSD licensed MODBUS RTU/ASCII and TCP slave
下载之后文件

2、新建工程,具体工程不赘述
3、复制如下文件到工程目录
1)复制port文件夹到工程目录

2)复制modbus文件到工程目录

4、添加modbus相关的头文件目录

5、编译之后报两处错误,暂时不管

6、修改portserial.c文件
该文件主要是串口使能、串口初始化、使能串口的发送与接收
根据FreeModbus的接口添加相关的功能接口,如下
/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
   * transmitter empty interrupts.
   */
   
    if(xRxEnable == TRUE)//串口接收中断使能
    {
      USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//UART中断使能
    }
    else
    {
      USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);//禁止接收和接收中断
    }

    if(xTxEnable == TRUE)//串口发送中断使能
    {
      USART_ITConfig(USART2, USART_IT_TC, ENABLE);//使能发送中断
    }
    else
    {
      USART_ITConfig(USART2, USART_IT_TC, DISABLE);//禁止发送中断
    }
}

BOOL xMBPortSerialInit( UCHAR ucPort, ULONG ulBaudRate,UCHAR ucDataBits, eMBParity eParity,UCHAR ucStopBits )
{
    USART2_Init();//初始化串口
    return FALSE;
}

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. */
    while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
    USART_SendData(USART2, ucByte);
    return TRUE;
}

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.
   */
    *pucByte = USART_ReceiveData(USART2);
    return TRUE;
}

/* 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.
*/
static 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.
*/
static void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived();
}

/*********************************************************************
* @fn      USART2_IRQHandler
*
* @brief   This function handles USART2 global interrupt request.
*
* @returnnone
*/
void USART2_IRQHandler(void)
{
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
      prvvUARTRxISR(); //串口接收中断调用函数
      USART_ClearITPendingBit(USART2,USART_FLAG_RXNE);
    }

    if(USART_GetITStatus(USART2, USART_IT_ORE) != RESET)
    {
      prvvUARTRxISR(); //串口接收中断调用函数
      USART_ClearITPendingBit(USART2,USART_IT_ORE);
    }

    if(USART_GetITStatus(USART2, USART_IT_TC) != RESET)
    {
      prvvUARTTxReadyISR(); //串口接收中断调用函数
      USART_ClearITPendingBit(USART2, USART_IT_TC);
    }
}7、修改porttimer.c文件
modbus工作时需要一个定时器来周期工作,定时器时基是50us,周期做为参数输入,去掉前面的inline,具体代码如下:
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
#include "debug.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
void TIM1_UP_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
    TIM1_INT_Init( usTim1Timerout50us);
    return TRUE;
}

void vMBPortTimersEnable()
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */

    TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
    TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
    TIM_SetCounter(TIM1,0x0000);
    TIM_Cmd(TIM1, ENABLE);
}

void vMBPortTimersDisable()
{
    /* Disable any pending timers. */
    TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
    TIM_ITConfig(TIM1, TIM_IT_Update, DISABLE);
    TIM_SetCounter(TIM1,0x0000);
    TIM_Cmd(TIM1, DISABLE);
}

/* 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.
*/
static void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired();
}

void TIM1_UP_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM1, TIM_IT_Update)==SET)
    {
      prvvTIMERExpiredISR();
      TIM_ClearITPendingBit( TIM1, TIM_IT_Update );
    }
}8、修改mbconfig.h文件
如图修改,主要修改支持RTU形式

9、在主函数初始定时器和串口,代码如下
/*********************************************************************
* @fn      main
*
* @brief   Main program.
*
* @returnnone
*/
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n",SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
    printf("This is printf example\r\n");

    modbus_task();
}

/*********************************************************************
* @fn      USART2_Init
*
* @brief   Initializes the USART2 peripheral.
*
* @returnnone
*/
void USART2_Init(void) {
    GPIO_InitTypeDefGPIO_InitStructure = {0};
    USART_InitTypeDef USART_InitStructure = {0};
    NVIC_InitTypeDefNVIC_InitStructure = {0};

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* USART2 TX-->A.2   RX-->A.3 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_Init(USART2, &USART_InitStructure);

    // USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//UART中断使能
    // USART_ITConfig(USART2, USART_IT_TC, ENABLE);//使能发送中断
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    USART_Cmd(USART2, ENABLE);
}

/*********************************************************************
* @fn      TIM1_INT_Init
*
* @brief   Initializes TIM1 output compare.
*
* @param   arr - the period value.
*          psc - the prescaler value.
*
* @returnnone
*/
void TIM1_INT_Init(u16 psc)
{

    NVIC_InitTypeDef NVIC_InitStructure={0};
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure={0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE );

    TIM_TimeBaseInitStructure.TIM_Period = psc * 50;
    TIM_TimeBaseInitStructure.TIM_Prescaler = 95;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit( TIM1, &TIM_TimeBaseInitStructure);

    TIM_ClearITPendingBit( TIM1, TIM_IT_Update );

    NVIC_InitStructure.NVIC_IRQChannel =TIM1_UP_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
    NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE;
    NVIC_Init(&NVIC_InitStructure);

}10、在新建mbtask.c文件添加modbus相关寄存器和函数
代码如下:


#include "stdio.h"
#include "mbtask.h"
#include "debug.h"

/* ----------------------- Static variables ---------------------------------*/
static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegInputBuf = {0x1234, 0x5678, 0x9ABC, 0xDEF0};
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegHoldingBuf = {0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};

//coils buffer
static UCHARucRegCoilsBuf = {0x01, 0x02};
//discrete Inputs buffer
static UCHARucRegDiscreteBuf = {0x80, 0x90};


void modbus_task(void)
{
eMBErrorCode    eStatus;

/* ucPort: select port_uart.
* this parameter can be one of the following values:
* 0: USART2: tx--PA2,rx--PA3,de--PA1;
* 1: USART3: tx--PB10, rx--PB11, de--PB14;
* other: invalid.
*/
eStatus = eMBInit(MB_RTU, MB_SLAVE_ADDRESS, 0, MB_BAUDRATE, MB_PAR_NONE);
if(MB_ENOERR == eStatus)
{
    printf("modbus init ok\r\n");
    eStatus = eMBEnable();
    if(MB_ENOERR == eStatus)
    {
      printf("modbus enable ok\r\n");
    }
    else
    {
      printf("modbus enable fail, error code: %u\r\n", eStatus);
    }
}
else
{
    printf("modbus init fail, error code: %u\r\n", eStatus);
}

if(MB_ENOERR != eStatus)
{
    printf("exit modbus task.\r\n");
    return;
}
   
printf("start modbus pooling..\r\n");
for(;;){
    eMBPoll();
}
}

/****************************************************************************
* brief: read input register;
*      responsive function code:
*      04 (Read Input Register).
* param: pucRegBuffer: data buffer, used for respond master;
*         usAddress: register address;
*             usNRegs: register number.
* retval: eStatus: error code.
****************************************************************************/
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 >> 8 );
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf & 0xFF );
            iRegIndex++;
            usNRegs--;
      }
    }
    else
    {
      eStatus = MB_ENOREG;
    }

    return eStatus;
}

/****************************************************************************
* brief: read/write holding register;
*      responsive function code:
*      06 (Write Holding Register);
*      16 (Write Multiple Holding Register);
*      03 (Read Holding Register);
*      23 (Read/Write Multiple Holding Register).
* param: pucRegBuffer: data buffer, used for respond master;
*         usAddress: register address;
*             usNRegs: register number;
*               eMode: access register mode.
* retval: eStatus: error code.
****************************************************************************/
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 >> 8 );
                *pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf & 0xFF );
                iRegIndex++;
                usNRegs--;
            }
            break;

      case MB_REG_WRITE:
            while( usNRegs > 0 )
            {
                usRegHoldingBuf = *pucRegBuffer++ << 8;
                usRegHoldingBuf |= *pucRegBuffer++;
                iRegIndex++;
                usNRegs--;
            }
      }
    }
    else
    {
      eStatus = MB_ENOREG;
    }
    return eStatus;
}

extern void xMBUtilSetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits, UCHAR ucValue );
extern UCHAR xMBUtilGetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits );

/****************************************************************************
* brief: read/write coils;
*      responsive function code:
*      01 (Read Coils);
*      05 (Write Coil);
*      15 (Write Multiple Coils).
* param: pucRegBuffer: data buffer, used for respond master;
*         usAddress: coils address;
*            usNCoils: coils number;
*               eMode: access register mode.
* retval: eStatus: error code.
****************************************************************************/
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
               eMBRegisterMode eMode )
{
    eMBErrorCode eStatus = MB_ENOERR;
    int16_t iNCoils = ( int16_t )usNCoils;
    int16_t usBitOffset;
   
    if( ( (int16_t)usAddress >= REG_COILS_START ) &&
       ( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) )
    {
      usBitOffset = ( int16_t )( usAddress - REG_COILS_START );
      switch ( eMode )
      {
      case MB_REG_READ:
      while( iNCoils > 0 )
      {
          *pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,
                                           ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ) );
          iNCoils -= 8;
          usBitOffset += 8;
      }
      break;
      
      case MB_REG_WRITE:
      while( iNCoils > 0 )
      {
          xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,
                         ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ),
                         *pucRegBuffer++ );
          iNCoils -= 8;
      }
      break;
      }
    }
    else
    {
      eStatus = MB_ENOREG;
    }
    return eStatus;
}

/****************************************************************************
* brief: read discrete inputs;
*      responsive function code:
*      02 (read discrete inputs).
* param: pucRegBuffer: data buffer, used for respond master;
*         usAddress: discrete inputs address;
*         usNDiscrete: discrete inputs number.
* retval: eStatus: error code.
****************************************************************************/
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    eMBErrorCode eStatus = MB_ENOERR;
    int16_t iNDiscrete = ( int16_t )usNDiscrete;
    uint16_t usBitOffset;
   
    if( ( (int16_t)usAddress >= REG_DISCRETE_START ) &&
       ( usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_SIZE ) )
    {
      usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START );
      
      while( iNDiscrete > 0 )
      {
      *pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset,
                                       ( uint8_t)( iNDiscrete > 8 ? 8 : iNDiscrete ) );
      iNDiscrete -= 8;
      usBitOffset += 8;
      }
    }
    else
    {
      eStatus = MB_ENOREG;
    }
    return eStatus;
}mbtask.h文件


#ifndef __MBTASK_H
#define __MBTASK_H

#ifdef __cplusplus
extern "C" {
#endif

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

/* ----------------------- Defines ------------------------------------------*/
#define MB_SLAVE_ADDRESS                ( 0x01 )
#define MB_BAUDRATE                     ( 115200 )

//input register start address
#define REG_INPUT_START               ( 1 )
//input register number
#define REG_INPUT_NREGS               ( 16 )

//holding register start address
#define REG_HOLDING_START               ( 1 )
//holding register number
#define REG_HOLDING_NREGS               ( 16 )

//coils start address
#define REG_COILS_START               ( 1 )
//coils number
#define REG_COILS_SIZE                  ( 16 )

//discrete inputs start address
#define REG_DISCRETE_START            ( 1 )
//discrete inputs number
#define REG_DISCRETE_SIZE               ( 16 )

void modbus_task(void);

#ifdef __cplusplus
}
#endif

#endif11、移植成功后,编译无误,下载验证
日志打印
MODBUS初始化成功

读保持寄存器测试

写保持寄存器测试

移植modbus-rtu完成


















旧时光放映机 发表于 2025-10-13 14:49

RISC-V架构的低功耗特性在工业应用中很有优势,FreeModbus的轻量级设计也很适合这类场景。

WhisperingTrees 发表于 2025-10-13 15:33

如果只是rtu的话 自己造轮子好了,移植还是挺麻烦的还要照着他的样子别扭。

kissdb 发表于 2025-10-14 11:54

WhisperingTrees 发表于 2025-10-13 15:33
如果只是rtu的话 自己造轮子好了,移植还是挺麻烦的还要照着他的样子别扭。 ...

是的还是自己造轮子好一点,一般也就用个03 04 06 10这几个功能吧,其他很少用

灵犀幻影 发表于 2025-10-17 17:05

移植过程中遇到的错误是怎么解决的?有没有遇到什么特别棘手的问题?
页: [1]
查看完整版本: 【沁恒CH32V307开发板测评】FreeModbus移植