FreeModbus 移植说明文档
1. 概述
本文档详细说明如何将 FreeModbus 库移植到基于 STM32F4 系列微控制器的嵌入式系统中。本移植基于 STM32CubeMX 生成的 HAL 库代码,并使用 FreeRTOS 作为操作系统。
移植环境:
- 硬件平台:STM32F407xx
- 开发环境:STM32CubeMX + Keil MDK / IAR / GCC
- 操作系统:FreeRTOS
- 通信接口:USART6 (RS485)
- 定时器:TIM13
2. 硬件资源配置
2.1 UART 配置 (USART6)
使用 STM32CubeMX 配置 USART6 用于 Modbus RTU 通信:
CubeMX 配置参数:
- Mode: Asynchronous
- Baud Rate: 115200
- Word Length: 8 bits
- Stop Bits: 1
- Parity: None (Modbus RTU 通常使用偶校验,但此处配置为 None,可在代码中调整)
- Flow Control: None
- DMA: 未使用 (使用中断方式)
- 中断: 使能
引脚配置:
- TX: PG14 (USART6_TX)
- RX: PG9 (USART6_RX)
- RTS/DE: PG8 (用于 RS485 收发控制)

代码生成:
CubeMX 会生成 MX_USART6_UART_Init() 函数,初始化 huart6 句柄。
2.2 定时器配置 (TIM13)
使用 STM32CubeMX 配置 TIM13 作为 Modbus 3.5 字符超时定时器:
CubeMX 配置参数:
- Prescaler: 42 (得到 1MHz 计数频率,假设系统时钟 168MHz APB1 42MHz)
- Counter Mode: Up
- Period: 5000 (对应 5ms 超时,适用于 115200 波特率)
- Clock Division: DIV1
- Auto-reload Preload: Disable
超时计算:
对于 115200 波特率,1 位时间 = 1/115200 ≈ 8.68μs。
3.5 字符时间 ≈ 3.5 * 10 * 8.68μs ≈ 304μs。
此处设置 5ms 超时,留有足够余量(实际只要1.8ms足够)。

代码生成:
CubeMX 会生成 MX_TIM13_Init() 函数,初始化 htim13 句柄。
2.3 GPIO 配置
RS485 收发控制引脚:
- PG8: 输出,推挽,上拉,用于控制 RS485 收发器的 DE/RE 引脚。
- 高电平:发送模式
- 低电平:接收模式
CubeMX 配置:
在 GPIO 配置中,将 PG8 设置为 GPIO_Output,并配置为推挽输出、上拉。
3. 软件架构
3.1 文件结构
Core/
├── Src/
│ ├── main.c # 主程序,包含 Modbus 任务和回调函数
│ └── ...
├── freemodbus/
│ └── modbus/ # FreeModbus 核心源码
│ ├── mb.c
│ ├── rtu/mbrtu.c
│ └── ...
Middlewares/
└── freemodbus/
└── port/ # 移植层代码
├── port.h
├── portserial.c
├── porttimer.c
└── portevent.c
3.2 移植层实现
3.2.1 port.h - 移植头文件
定义数据类型和宏,适配 STM32 环境。
c
#ifndef _PORT_H
#define _PORT_H
#include <assert.h>
#include <inttypes.h>
#define INLINE inline
#define PR_BEGIN_EXTERN_C extern "C" {
#define PR_END_EXTERN_C }
#define ENTER_CRITICAL_SECTION( ) // 临界区保护,可根据需要实现
#define EXIT_CRITICAL_SECTION( )
typedef uint8_t BOOL;
typedef unsigned char UCHAR;
typedef char CHAR;
typedef uint16_t USHORT;
typedef int16_t SHORT;
typedef uint32_t ULONG;
typedef int32_t LONG;
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#endif
3.2.2 portserial.c - 串口移植
实现串口初始化、发送、接收及中断处理。
关键函数:
xMBPortSerialInit: 初始化串口,启动接收中断。vMBPortSerialEnable: 使能/禁用发送和接收。xMBPortSerialPutByte: 发送单个字节。xMBPortSerialGetByte: 接收单个字节。- 中断回调函数 : 在
main.c中实现HAL_UART_RxCpltCallback和HAL_UART_TxCpltCallback。
代码示例 (portserial.c):
c
#include "cmsis_os2.h"
#include "port.h"
#include "mb.h"
#include "mbport.h"
#include "stm32f4xx_hal_gpio.h"
#include "stm32f4xx_hal_uart.h"
#include <stdint.h>
extern UART_HandleTypeDef huart6;
#define MBPORT_BUFFER_SIZE 128
CHAR ucBuffer[MBPORT_BUFFER_SIZE];
uint32_t bufReadPos = 0;
uint32_t bufWritePos = 0;
static volatile UCHAR * pucSndBufferCur;
static volatile USHORT usSndBufferCount;
void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
if (xTxEnable) {
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_8, GPIO_PIN_SET); // 发送使能
osDelay(2); // 等待 485 收发器稳定
if (pxMBFrameCBTransmitterEmpty != NULL) {
pxMBFrameCBTransmitterEmpty();
}
} else {
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_8, GPIO_PIN_RESET); // 接收使能
osDelay(1); // 等待 485 收发器稳定
HAL_UART_AbortTransmit_IT(&huart6);
}
if (xRxEnable) {
if (HAL_UART_GetState(&huart6) != HAL_UART_STATE_BUSY_RX) {
HAL_UART_Receive_IT(&huart6, (uint8_t *)&ucBuffer[bufWritePos], 1);
}
} else {
HAL_UART_AbortReceive(&huart6);
}
}
BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
// UART 初始化由 CubeMX 生成的 MX_USART6_UART_Init() 完成
// 此处只需启动接收中断
HAL_UART_Receive_IT(&huart6, (uint8_t *)&ucBuffer[bufWritePos], 1);
return TRUE;
}
BOOL xMBPortSerialPutByte( CHAR ucByte )
{
pucSndBufferCur = (UCHAR *)&ucByte;
usSndBufferCount = 1;
HAL_UART_Transmit_IT(&huart6, (uint8_t *)pucSndBufferCur, 1);
return TRUE;
}
BOOL xMBPortSerialGetByte( CHAR * pucByte )
{
*pucByte = ucBuffer[bufReadPos];
bufReadPos++;
if (bufReadPos >= MBPORT_BUFFER_SIZE) {
bufReadPos = 0;
}
return TRUE;
}
中断回调函数 (main.c):
c
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART6) {
// 增加缓冲区写指针
bufWritePos++;
if (bufWritePos >= MBPORT_BUFFER_SIZE) {
bufWritePos = 0;
}
// 调用 Modbus 回调
if (pxMBFrameCBByteReceived != NULL) {
pxMBFrameCBByteReceived();
}
// 重启接收下一个字节
HAL_UART_Receive_IT(&huart6, (uint8_t *)&ucBuffer[bufWritePos], 1);
}
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART6) {
// 通知 Modbus 栈发送器已空
if (pxMBFrameCBTransmitterEmpty != NULL) {
pxMBFrameCBTransmitterEmpty();
}
}
}
3.2.3 porttimer.c - 定时器移植
实现 Modbus 3.5 字符超时定时器。
关键函数:
xMBPortTimersInit: 初始化定时器(此处由 CubeMX 完成,返回 TRUE)。vMBPortTimersEnable: 启动定时器。vMBPortTimersDisable: 停止定时器。- 中断回调函数 : 在
main.c中实现HAL_TIM_PeriodElapsedCallback。
代码示例 (porttimer.c):
c
#include "port.h"
#include "mb.h"
#include "mbport.h"
#include "stm32f4xx_hal_tim.h"
extern TIM_HandleTypeDef htim13;
BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
{
// 定时器初始化由 CubeMX 生成的 MX_TIM13_Init() 完成
return TRUE;
}
inline void vMBPortTimersEnable( )
{
HAL_TIM_Base_Start_IT(&htim13);
}
inline void vMBPortTimersDisable( )
{
HAL_TIM_Base_Stop_IT(&htim13);
}
static void prvvTIMERExpiredISR( void )
{
( void )pxMBPortCBTimerExpired( );
}
中断回调函数 (main.c):
c
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
HAL_IncTick();
}
else if (htim->Instance == TIM13) {
if (pxMBPortCBTimerExpired != NULL) {
pxMBPortCBTimerExpired( );
}
}
}
3.2.4 portevent.c - 事件移植
实现 Modbus 事件队列(用于 RTU 模式)。
代码示例 (portevent.c):
c
#include "mb.h"
#include "mbport.h"
static eMBEventType eQueuedEvent;
static BOOL xEventInQueue;
BOOL xMBPortEventInit( void )
{
xEventInQueue = FALSE;
return TRUE;
}
BOOL xMBPortEventPost( eMBEventType eEvent )
{
xEventInQueue = TRUE;
eQueuedEvent = eEvent;
return TRUE;
}
BOOL xMBPortEventGet( eMBEventType * eEvent )
{
BOOL xEventHappened = FALSE;
if( xEventInQueue )
{
*eEvent = eQueuedEvent;
xEventInQueue = FALSE;
xEventHappened = TRUE;
}
return xEventHappened;
}
4. 主程序集成
4.1 Modbus 任务创建
在 main.c 中创建 Modbus 任务,并初始化 Modbus 栈。
c
void Modbus_task(void *argument)
{
// 初始化 Modbus 协议栈 (RTU 模式)
// 从站地址: 0x01, 端口: 1 (USART6), 波特率: 115200, 校验: 偶校验
eMBErrorCode eStatus = eMBInit(MB_RTU, 0x01, 6, 115200, MB_PAR_EVEN);
if (eStatus != MB_ENOERR) {
printf("Modbus initialization failed with error: %d\n", eStatus);
Error_Handler();
}
// 使能 Modbus 协议栈
eStatus = eMBEnable();
if (eStatus != MB_ENOERR) {
printf("Modbus enable failed with error: %d\n", eStatus);
Error_Handler();
}
// 初始化 Modbus 寄存器缓冲区
memset(usRegInputBuf, 0, sizeof(usRegInputBuf));
memset(usRegHoldingBuf, 0, sizeof(usRegHoldingBuf));
memset(ucCoilBuf, 0, sizeof(ucCoilBuf));
memset(ucDiscreteInputBuf, 0, sizeof(ucDiscreteInputBuf));
printf("Modbus RTU slave initialized with address 0x01\n");
/* 无限循环 */
for(;;)
{
// 调用 Modbus 协议栈的主轮询函数
eMBPoll();
// 延迟 1ms 以允许其他任务运行
osDelay(1);
}
}
注意: eMBInit 的第三个参数是端口号,此处应为 6(对应 USART6),而不是 1。
4.2 寄存器回调函数
实现 Modbus 寄存器读写回调函数。
输入寄存器 (30001-30004):
c
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
// 实现读取输入寄存器的逻辑
// ...
}
保持寄存器 (40001-40016):
c
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
// 实现读写保持寄存器的逻辑
// ...
}
线圈 (00001-00016):
c
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
// 实现读写线圈的逻辑
// ...
}
离散输入 (10001-10013):
c
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
// 实现读取离散输入的逻辑
// ...
}
5. 编译与调试
5.1 编译配置
确保在 Makefile 或项目配置中包含以下路径:
C_INCLUDES += -ICore/freemodbus/modbus/include
-IMiddlewares/freemodbus/port/
-ICore/freemodbus/modbus/rtu
-ICore/freemodbus/modbus/ascii
C_COURCES += Core/freemodbus/modbus/mb.c \
Core/freemodbus/modbus/rtu/mbrtu.c \
Core/freemodbus/modbus/rtu/mbcrc.c \
Core/freemodbus/modbus/ascii/mbascii.c \
Core/freemodbus/modbus/functions/mbfunccoils.c \
Core/freemodbus/modbus/functions/mbfuncdiag.c \
Core/freemodbus/modbus/functions/mbfuncholding.c \
Core/freemodbus/modbus/functions/mbfuncinput.c \
Core/freemodbus/modbus/functions/mbfuncother.c \
Core/freemodbus/modbus/functions/mbfuncdisc.c \
Core/freemodbus/modbus/functions/mbutils.c \
Middlewares/freemodbus/port/portevent.c \
Middlewares/freemodbus/port/porttimer.c \
Middlewares/freemodbus/port/portserial.c \
5.2 调试技巧
- 检查中断回调: 确保
HAL_UART_RxCpltCallback和HAL_TIM_PeriodElapsedCallback被正确调用。 - 缓冲区溢出: 监控
bufWritePos和bufReadPos,确保缓冲区大小足够。 - 超时定时器: 使用逻辑分析仪或示波器检查 TIM13 的输出,确保超时时间正确。
- RS485 控制: 检查 PG8 引脚电平,确保收发切换正常。
6. 常见问题
6.1 无法接收数据
- 检查 UART 中断是否使能。
- 检查 RS485 收发控制引脚是否正确配置。
- 检查缓冲区大小是否足够。
6.2 超时不准确
- 检查 TIM13 的时钟配置和预分频器。
- 调整
Period值以匹配波特率。
6.3 Modbus 无响应
- 检查从站地址是否正确。
- 检查寄存器地址范围是否匹配。
- 检查回调函数是否正确实现。
7. 总结
本文档详细说明了 FreeModbus 在 STM32F4 平台上的移植过程,包括硬件配置、软件架构、代码实现和调试技巧。通过遵循本文档,开发者可以快速将 FreeModbus 移植到新的 STM32 设备上。