STM32 FreeModbus 移植(最详细)

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 - 串口移植

实现串口初始化、发送、接收及中断处理。

关键函数:

  1. xMBPortSerialInit: 初始化串口,启动接收中断。
  2. vMBPortSerialEnable: 使能/禁用发送和接收。
  3. xMBPortSerialPutByte: 发送单个字节。
  4. xMBPortSerialGetByte: 接收单个字节。
  5. 中断回调函数 : 在 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 字符超时定时器。

关键函数:

  1. xMBPortTimersInit: 初始化定时器(此处由 CubeMX 完成,返回 TRUE)。
  2. vMBPortTimersEnable: 启动定时器。
  3. vMBPortTimersDisable: 停止定时器。
  4. 中断回调函数 : 在 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 调试技巧

  1. 检查中断回调: 确保 HAL_UART_RxCpltCallback 和 HAL_TIM_PeriodElapsedCallback 被正确调用。
  2. 缓冲区溢出: 监控 bufWritePos 和 bufReadPos,确保缓冲区大小足够。
  3. 超时定时器: 使用逻辑分析仪或示波器检查 TIM13 的输出,确保超时时间正确。
  4. RS485 控制: 检查 PG8 引脚电平,确保收发切换正常。

6. 常见问题

6.1 无法接收数据

  • 检查 UART 中断是否使能。
  • 检查 RS485 收发控制引脚是否正确配置。
  • 检查缓冲区大小是否足够。

6.2 超时不准确

  • 检查 TIM13 的时钟配置和预分频器。
  • 调整 Period 值以匹配波特率。

6.3 Modbus 无响应

  • 检查从站地址是否正确。
  • 检查寄存器地址范围是否匹配。
  • 检查回调函数是否正确实现。

7. 总结

本文档详细说明了 FreeModbus 在 STM32F4 平台上的移植过程,包括硬件配置、软件架构、代码实现和调试技巧。通过遵循本文档,开发者可以快速将 FreeModbus 移植到新的 STM32 设备上。

相关推荐
HQB20264 小时前
EG1198|屹晶微电子 120V 内置 MOS 异步降压 DC‑DC(高性价比)
嵌入式硬件·硬件工程
果壳science6 小时前
张祥前实验如何获取正电荷
笔记·科技·嵌入式硬件·硬件工程·制造
南山电子nscn6 小时前
中科芯CETC汽车电池管理系统BMS进口芯片国产型号替代指南
单片机·汽车·bms
沉淀的.晴天6 小时前
can总线
stm32
桀人6 小时前
认识AT24C02
stm32·单片机·嵌入式硬件
国科安芯8 小时前
星间激光通信终端CANFD控制网络的时延确定性与辐射可靠性
网络·嵌入式硬件·信息与通信·canfd·低轨卫星·商业航天·抗辐射
一直C8 小时前
嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动
linux·驱动开发·嵌入式硬件·学习·imx6ull
传感器与混合集成电路8 小时前
150MHz主频、8路DAC与6路PWM:JLH221360伺服控制SIP解析
嵌入式硬件
comyccccc8 小时前
STM32PID控制板设计全解析学习笔记
单片机·嵌入式硬件
碧海银沙音频科技研究院9 小时前
实现炬芯ATS308X的usb cdc 串口IAP 固件升级功能实现
人工智能·嵌入式硬件·深度学习·算法