作为一名嵌入式开发工程师,我最近在STM32平台上成功移植了FreeModbus协议栈,实现了Modbus RTU通信功能。这个过程中遇到了不少坑,也积累了一些经验,今天就来详细分享一下完整的移植过程。
Modbus作为工业领域广泛应用的通信协议,其RTU模式在嵌入式系统中尤为常见。FreeModbus是一个开源的Modbus协议栈,但将其移植到STM32 HAL库环境时,很多网上的教程要么语焉不详,要么收费才能查看完整内容。这让我很是不爽,毕竟技术分享应该是开放的。
本文将基于STM32F407IGT6开发板,使用HAL库和CubeMX工具,手把手教你完成FreeModbus的移植。我会详细解释每个步骤的原理和注意事项,确保即使是没有Modbus经验的朋友也能顺利完成移植。
首先需要准备以下硬件设备:
提示:虽然我使用的是F407芯片,但本文的移植方法同样适用于其他STM32系列芯片,只需根据具体型号调整时钟配置即可。
需要安装的软件工具包括:
其中FreeModbus源码可以从GitHub等开源平台获取。这里特别说明一下,FreeModbus官方版本已经停止维护,但社区有很多改进版本,建议选择稳定性较好的v1.6版本。
首先打开CubeMX,创建一个新工程,选择对应的STM32芯片型号。以下是关键配置步骤:
在RCC配置中,选择外部高速时钟(HSE)作为系统时钟源。对于F407芯片,通常使用8MHz外部晶振。配置完成后,系统时钟可以设置为168MHz。
在SYS配置中,选择Serial Wire作为调试接口。这样可以使用ST-Link通过SWD接口进行程序下载和调试。
定时器2用于Modbus RTU的3.5字符超时检测。配置如下:
重要提示:必须使能定时器中断,否则无法触发超时检测,会导致Modbus通信失败。
选择一个USART作为Modbus通信接口,这里以USART3为例:
完成上述配置后,在Project Manager中设置工程名称和路径,选择MDK-ARM作为Toolchain/IDE。在Code Generator中勾选"Generate peripheral initialization as a pair of .c/.h files"选项,这样生成的代码结构更清晰。
点击GENERATE CODE按钮生成工程代码,然后用Keil MDK打开工程。
将下载的FreeModbus源码包解压后,我们需要将以下关键文件添加到工程中:
建议在工程目录下新建一个Modbus文件夹,将这些文件分类存放。例如:
在Keil MDK中,按照以下步骤添加文件:
FreeModbus需要针对具体硬件平台修改port目录下的三个关键文件:portevent.c、portserial.c和porttimer.c。
这个文件主要负责事件队列管理,通常不需要修改,保持原样即可。
这是串口驱动文件,需要根据HAL库进行适配。主要修改以下几个函数:
c复制BOOL xMBPortSerialInit(UCHAR ucPort, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity) {
// 串口初始化已在CubeMX中完成,这里主要设置Modbus协议参数
return TRUE;
}
BOOL xMBPortSerialPutByte(CHAR ucByte) {
// 使用HAL_UART_Transmit发送单个字节
HAL_UART_Transmit(&huart3, (uint8_t*)&ucByte, 1, 100);
return TRUE;
}
BOOL xMBPortSerialGetByte(CHAR *pucByte) {
// 使用HAL_UART_Receive接收单个字节
if(HAL_UART_Receive(&huart3, (uint8_t*)pucByte, 1, 0) == HAL_OK) {
return TRUE;
}
return FALSE;
}
注意:HAL_UART_Receive的最后一个参数设为0表示非阻塞接收,这是为了符合FreeModbus的调用要求。
定时器驱动文件,需要修改以下函数:
c复制BOOL xMBPortTimersInit(USHORT usTim1Timerout50us) {
// 计算定时器重载值
__HAL_TIM_SET_AUTORELOAD(&htim2, usTim1Timerout50us - 1);
// 启动定时器
HAL_TIM_Base_Start_IT(&htim2);
return TRUE;
}
void vMBPortTimersEnable() {
// 使能定时器中断
__HAL_TIM_SET_COUNTER(&htim2, 0);
HAL_TIM_Base_Start_IT(&htim2);
}
void vMBPortTimersDisable() {
// 禁用定时器中断
HAL_TIM_Base_Stop_IT(&htim2);
}
在主程序中添加Modbus初始化代码:
c复制#include "mb.h"
#include "mbrtu.h"
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART3_UART_Init();
MX_TIM2_Init();
// Modbus RTU初始化
eMBInit(MB_RTU, 0x01, 0, 9600, MB_PAR_NONE);
eMBEnable();
while (1) {
eMBPoll();
}
}
FreeModbus需要用户实现寄存器读写回调函数。以保持寄存器为例:
c复制#include "mb.h"
#include "mbfuncholding.h"
// 保持寄存器存储区
USHORT usRegHoldingBuf[REG_HOLDING_NREGS];
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 - REG_HOLDING_START);
switch(eMode) {
case MB_REG_READ:
while(usNRegs > 0) {
*pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] >> 8);
*pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] & 0xFF);
iRegIndex++;
usNRegs--;
}
break;
case MB_REG_WRITE:
while(usNRegs > 0) {
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
break;
}
} else {
eStatus = MB_ENOREG;
}
return eStatus;
}
使用Modbus Poll工具测试从机功能:
现象:Modbus Poll显示请求超时,无法收到响应。
可能原因:
解决方案:
现象:收到的数据与预期不符,出现字节错位。
可能原因:
解决方案:
现象:总线上有多个从机时通信不稳定。
可能原因:
解决方案:
中断优先级配置:
内存优化:
功能裁剪:
在实际项目中,我发现在STM32F407上运行FreeModbus时,CPU占用率通常低于5%,这意味着即使是在处理Modbus通信的同时,系统仍有足够的资源处理其他任务。对于更高性能要求的应用,可以考虑使用DMA方式进行串口数据传输,进一步降低CPU负载。