【沁恒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完成
RISC-V架构的低功耗特性在工业应用中很有优势,FreeModbus的轻量级设计也很适合这类场景。
如果只是rtu的话 自己造轮子好了,移植还是挺麻烦的还要照着他的样子别扭。 WhisperingTrees 发表于 2025-10-13 15:33
如果只是rtu的话 自己造轮子好了,移植还是挺麻烦的还要照着他的样子别扭。 ...
是的还是自己造轮子好一点,一般也就用个03 04 06 10这几个功能吧,其他很少用 移植过程中遇到的错误是怎么解决的?有没有遇到什么特别棘手的问题?
页:
[1]