单片机作为从机如何移植FreeModbus
将 FreeModbus 移植到从机设备上需要按照以下步骤进行,下面以 STM32 为例详细说明:
一、准备工作
- 获取 FreeModbus 源码:从官方 GitHub 下载最新版本(https://github.com/ARMmbed/freemodbus)。
- 开发环境:安装 STM32CubeMX、Keil MDK 或 GCC 工具链。
- 硬件资源:UART 接口(用于 RTU 模式)或以太网控制器(用于 TCP 模式)。定时器(用于超时检测)。
二、文件结构分析
FreeModbus 源码主要包含以下核心文件:
plaintext
├── demo/ # 示例代码 ├── include/ # 头文件 │ ├── mb.h # Modbus主头文件 │ ├── mbconfig.h # 配置文件(需用户自定义) │ ├── mbframe.h # 帧结构定义 │ └── ... ├── src/ # 源文件 │ ├── mbcrc.c # CRC校验 │ ├── mbfunccoils.c # 线圈操作功能码处理 │ ├── mbfuncdiag.c # 诊断功能码处理 │ ├── mbfuncinput.c # 输入寄存器功能码处理 │ ├── mbfuncholding.c # 保持寄存器功能码处理 │ ├── mbinit.c # 初始化函数 │ ├── mbrtu.c # RTU模式实现 │ ├── mbtcp.c # TCP模式实现 │ └── ... └── port/ # 移植层(需用户实现) ├── port.h # 平台相关定义 ├── portevent.c # 事件处理 ├── porttimer.c # 定时器实现 └── portserial.c # 串口通信实现
三、创建自定义配置文件
在工程中创建mbconfig.h
文件,根据需求配置 Modbus 参数:
c
运行
/* mbconfig.h */ #ifndef __MB_CONFIG_H #define __MB_CONFIG_H /* ----------------------- 配置选项 ----------------------- */ #define MB_RTU_ENABLED 1 // 启用RTU模式 #define MB_TCP_ENABLED 0 // 禁用TCP模式(根据需求修改) #define MB_ASCII_ENABLED 0 // 禁用ASCII模式 /* ----------------------- 从机地址 ----------------------- */ #define MB_SLAVE_ADDRESS_MIN 1 #define MB_SLAVE_ADDRESS_MAX 247 /* ----------------------- 寄存器配置 ----------------------- */ #define MB_FUNC_HOLDING_ENABLED 1 // 启用保持寄存器 #define MB_FUNC_INPUT_ENABLED 1 // 启用输入寄存器 #define MB_FUNC_COIL_ENABLED 1 // 启用线圈 #define MB_FUNC_DISCRETE_INPUT_ENABLED 1 // 启用离散输入 /* ----------------------- 缓冲区大小 ----------------------- */ #define MB_SER_PDU_SIZE_MAX 256 // 最大PDU长度 #define MB_TCP_PDU_SIZE_MAX 253 // TCP模式最大PDU长度 #endif
四、实现移植层接口
在port/
目录下实现与硬件相关的接口:
1. portserial.c
- 串口通信实现
c
运行
/* portserial.c */ #include "port.h" #include "stm32f1xx_hal.h" extern UART_HandleTypeDef huart1; // 根据实际使用的UART修改 BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ) { // 配置UART参数 // 示例:使用HAL库初始化UART return TRUE; } void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) { // 启用/禁用接收和发送中断 } BOOL xMBPortSerialPutByte( CHAR ucByte ) { // 发送单个字节 HAL_UART_Transmit(&huart1, (uint8_t*)&ucByte, 1, 100); return TRUE; } BOOL xMBPortSerialGetByte( CHAR * pucByte ) { // 接收单个字节 HAL_UART_Receive(&huart1, (uint8_t*)pucByte, 1, 100); return TRUE; } /* 串口接收中断回调函数 */ void vMBPortSerialIRQHandler( void ) { // 处理串口中断 // 调用eMBRTUReceiveFSM()或eMBTCPProcess() }
2. porttimer.c
- 定时器实现
c
运行
/* porttimer.c */ #include "port.h" #include "stm32f1xx_hal.h" extern TIM_HandleTypeDef htim2; // 根据实际使用的定时器修改 BOOL xMBPortTimersInit( USHORT usTim1Timerout50us ) { // 配置定时器,设置超时时间 // 示例:使用HAL库初始化TIM2 return TRUE; } void vMBPortTimersEnable( ) { // 启用定时器 HAL_TIM_Base_Start_IT(&htim2); } void vMBPortTimersDisable( ) { // 禁用定时器 HAL_TIM_Base_Stop_IT(&htim2); } /* 定时器中断回调函数 */ void vMBPortTimersIRQHandler( void ) { // 处理定时器中断 // 调用eMBRTUTimerT35Expired() }
3. portevent.c
- 事件处理
c
运行
/* portevent.c */ #include "port.h" static eMBEventType eQueuedEvent; static BOOL xEventInQueue; BOOL xMBPortEventInit( void ) { xEventInQueue = FALSE; return TRUE; } BOOL xMBPortEventPost( eMBEventType eEvent ) { // 事件入队 eQueuedEvent = eEvent; xEventInQueue = TRUE; return TRUE; } BOOL xMBPortEventGet( eMBEventType * eEvent ) { // 事件出队 BOOL xEventHappened = xEventInQueue; if( xEventHappened ) { *eEvent = eQueuedEvent; xEventInQueue = FALSE; } return xEventHappened; }
五、实现寄存器操作回调函数
根据应用需求,实现寄存器读写回调函数:
c
运行
/* user_mb_app.c */ #include "mb.h" #include "mbport.h" /* 定义寄存器存储空间 */ static USHORT usRegHoldingBuf[100]; // 保持寄存器 static USHORT usRegInputBuf[100]; // 输入寄存器 static UCHAR ucRegCoilsBuf[100]; // 线圈 static UCHAR ucRegDiscreteBuf[100]; // 离散输入 /* 保持寄存器读写回调 */ eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode ) { // 实现保持寄存器的读写逻辑 // 检查地址范围,读写usRegHoldingBuf数组 return MB_ENOERR; } /* 输入寄存器读回调 */ eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) { // 实现输入寄存器的读逻辑 // 通常从硬件读取数据存入pucRegBuffer return MB_ENOERR; } /* 线圈读写回调 */ eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode ) { // 实现线圈的读写逻辑 // 读写ucRegCoilsBuf数组 return MB_ENOERR; } /* 离散输入读回调 */ eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete ) { // 实现离散输入的读逻辑 return MB_ENOERR; }
六、主程序初始化与调用
在主程序中初始化 Modbus 从机并循环处理:
c
运行
/* main.c */ #include "main.h" #include "stm32f1xx_hal.h" #include "mb.h" #include "mbport.h" /* 函数声明 */ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); static void MX_TIM2_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 初始化UART MX_TIM2_Init(); // 初始化定时器 /* 初始化Modbus RTU从机,地址1,波特率9600,无校验 */ eMBInit( MB_RTU, 0x01, 0, 9600, MB_PAR_NONE ); /* 注册回调函数 */ eMBSetRegHoldingCB( eMBRegHoldingCB ); eMBSetRegInputCB( eMBRegInputCB ); eMBSetRegCoilsCB( eMBRegCoilsCB ); eMBSetRegDiscreteCB( eMBRegDiscreteCB ); /* 启用Modbus */ eMBEnable(); while (1) { /* 轮询处理Modbus请求 */ (void)eMBPoll(); /* 其他应用逻辑 */ // ... } }
七、常见问题与调试技巧
- 通信失败:检查硬件连接(RX/TX 是否接反,是否共地)。确认波特率、数据位、校验位是否一致。
- 寄存器访问异常:检查寄存器地址范围是否越界。调试时可在回调函数中添加日志输出。
- 性能优化:合理设置定时器超时值(T35 时间)。使用中断方式处理串口收发以提高响应速度。
八、移植到其他平台的注意事项
- 不同 MCU 的差异:替换相应的 HAL 库函数(如 STM32、ESP32 的串口和定时器 API 不同)。
- 内存管理:注意堆栈大小,避免溢出。
- 中断优先级:确保 Modbus 相关中断优先级足够高,避免被其他中断干扰。
通过以上步骤,即可完成 FreeModbus 在从机设备上的移植。实际应用中,还需根据具体需求调整配置参数和寄存器操作逻辑。
更多内容全在下方专栏
全网最受欢迎的嵌入式笔试专栏
笔试专栏包含全部最新的笔试必考考点,非常适合在找工作面经薄弱的同学
3000+订阅还会涨价,提前订阅提前享受,持续更新中。
专栏链接:https://www.nowcoder.com/creation/manager/columnDetail/mPZ4kk
#嵌入式秋招#