单片机作为从机如何移植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
#嵌入式秋招#
