freemodbus使用

文章目录

    • ✅ **CubeMX配置**
      • 1. UART配置(RS485通信)
      • 2. Timer配置(RTU字符间隔检测)
      • 3. GPIO配置(RS485方向控制)
    • ✅ **STM32F103 + RS485 + FreeModbus RTU 配置概览**
      • **1️⃣ CubeMX硬件配置**
      • **2️⃣ FreeModbus源码文件**
      • **3️⃣ 配置文件修改**
      • **4️⃣ 移植层代码编写**
      • **5️⃣ 主程序配置**
      • **6️⃣ 中断处理配置**
      • **7️⃣ 工程配置**
      • **📋 配置检查清单**
    • ✅ **STM32F103 + HAL库 + CubeMX的FreeModbus RTU实现方案**
      • 必须保留的源码文件
    • ✅ **配置文件**
      • mbconfig.h
      • port.h
    • ✅ **移植层实现**
      • portserial.c
      • porttimer.c
      • portevent.c
    • ✅ **中断处理函数**
      • stm32f1xx_it.c中添加
    • ✅ **主程序实现**
      • main.c
    • ✅ **工程配置要点**
      • 1. 包含路径添加
      • 2. 源文件添加到工程
      • 3. 编译宏定义(可选)
    • 🔄 **RS485方向控制原理**
      • **RS485是半双工通信**
    • 📡 **PA1 (RS485_DE) 的作用**
      • **DE/RE引脚说明**
      • **方向控制逻辑**
    • 🔄 **完整通信流程**
      • **1. STM32发送数据给从站**
      • **2. STM32接收从站返回的数据**
    • 📋 **实际工作时序**
      • **Modbus RTU主从通信过程**
      • **举例:读取保持寄存器**
    • ⚡ **关键要点**
      • **✅ 能双向通信**
      • **🎯 方向控制的意义**
      • **📝 代码中的自动切换**

缩写/单词全称含义
ModBusModicon Bus最早由 Modicon(现施耐德) 开发的工业通信协议
RTURemote Terminal Unit远程终端单元,指一种紧凑、二进制的传输格式(区别于 ASCII 模式)

下载freemodbus,解压。

在这里插入图片描述


名称作用说明
modbus核心源码目录,包含 FreeModbus 协议栈的所有 .c/.h 文件(如 mb.c, mbport.h, mbrtu.c 等)。
demo示例工程目录,包含 FreeModbus 在不同平台(如 AVR、Win32、STR71x)上的完整示例项目。你可以参考这些例子移植到 STM32。
doc文档目录,包含协议栈的说明文档(如 modbus.txtdemo.txt),但内容较旧。
tools辅助工具目录,包含一些生成 CRC 表的小工具(如 crcgen.py),通常用不到。
名称作用说明
__MACOSXmacOS 压缩时自动生成的隐藏文件夹可以删除,对代码无影响。
Changelog.txtFreeModbus 的版本更新日志。
gpl.txtGNU GPL 开源协议(FreeModbus 采用 GPL v3 授权)。
lgpl.txtGNU LGPL 协议(部分文件可能用 LGPL 授权)。
bsd.txtBSD 协议(某些平台移植代码可能用 BSD 授权)。

📁 目录说明

目录名作用说明
asciiModbus ASCII 模式的源码(基于文本的通信方式,用得少)。
rtuModbus RTU 模式的源码(二进制、高效,最常用)。
tcpModbus TCP 模式的源码(基于以太网 TCP/IP,用于网口通信)。
functions各种 Modbus 功能码的实现(如 0x03 读保持寄存器、0x06 写单个寄存器等)。
include公共头文件(如 mb.h, mbport.h 等)。
mb.c主协议栈入口文件(初始化、轮询、状态机等)。
使用场景需要哪些目录
串口 RTU 从站rtu + functions + include + mb.c
串口 ASCII 从站ascii + functions + include + mb.c
以太网 TCP 从站tcp + functions + include + mb.c

✅ 关于 tcp 目录

  • 作用:实现 Modbus TCP 协议,用于 以太网通信(如通过 W5500、ENC28J60 等模块)。
  • 可以不用
    如果你只用 串口(RS-485/RS-232)通信完全可以不用 tcp 目录,甚至可以从工程中移除,节省空间。

CubeMX配置

1. UART配置(RS485通信)

Connectivity -> USART1:
├── Mode: Asynchronous  
├── Baud Rate: 9600 (或其他)
├── Word Length: 8 Bits
├── Parity: None  
├── Stop Bits: 1
├── Data Direction: Receive and Transmit
└── NVIC Settings: ✅ USART1 global interrupt

2. Timer配置(RTU字符间隔检测)

Timers -> TIM2:
├── Clock Source: Internal Clock
├── Prescaler: 71 (得到1MHz时钟)
├── Counter Period: 1750 (3.5字符时间@9600bps)
└── NVIC Settings: ✅ TIM2 global interrupt

定时器需要配置定时器的中断

字符间隔检测:
作用:

  • 检测Modbus RTU数据帧之间的静默期(3.5字符时间)
  • 当接收到数据后,如果在3.5字符时间内没有新数据到达,则认为一帧数据接收完成
  • 用于帧同步和数据完整性判断

工作原理:

数据帧: [地址][功能码][数据][CRC] ----静默期(≥3.5字符)---- [下一帧...]↑定时器检测这段时间 

时钟频率计算:

 // STM32F103时钟配置 系统时钟: 72MHz APB1时钟: 36MHz (通常是SYSCLK/2)
TIM2时钟: 72MHz (当APB1预分频≠1时,定时器时钟×2)// 定时器频率计算 定时器频率 = TIM2时钟 / (Prescaler + 1) 定时器频率 = 72MHz / (71 + 1) =
1MHz 每个计数 = 1μs

字符时间计算:

// 以9600bps为例 波特率 = 9600 bps 每位时间 = 1/9600 ≈ 104.17μs 每字符位数 = 10位
(1起始位 + 8数据位 + 1停止位) 每字符时间 = 10 × 104.17μs = 1041.7μs
3.5字符时间 = 3.5 × 1041.7μs ≈ 3646μs// 对应的计数值 Counter Period = 3646 (定时器频率1MHz时)
// 反推波特率 1750μs / 3.5 = 500μs (每字符时间) 500μs / 10位 = 50μs (每位时间) 波特率 =
1/50μs = 20000 bps// 或者可能是针对19200bps 19200bps每位时间 = 1/19200 ≈ 52.08μs 每字符时间 = 10 ×
52.08μs = 520.8μs
3.5字符时间 = 3.5 × 520.8μs ≈ 1823μs ```

Modbus RTU常用两种格式:

  • 8-N-1: 8数据位 + 无校验 + 1停止位 = 10位/字符
  • 8-E-1/8-O-1: 8数据位 + 奇偶校验 + 1停止位 = 11位/字符
// 9600bps, 8-N-1格式 (10位/字符)
每位时间 = 1/9600 = 104.167 μs
每字符时间 = 104.167 × 10 = 1041.67 μs  
3.5字符时间 = 1041.67 × 3.5 = 3645.83 μs
定时器计数值 = 3646// 9600bps, 8-E-1格式 (11位/字符)  
每位时间 = 1/9600 = 104.167 μs
每字符时间 = 104.167 × 11 = 1145.83 μs
3.5字符时间 = 1145.83 × 3.5 = 4010.42 μs  
定时器计数值 = 4010
// 根据Modbus标准,波特率 > 19200 时使用固定值
// 不管是8-N-1还是8-E-1格式,都使用固定的1.75ms定时器计数值 = 1750 μs (固定值)
# 9600
Timers -> TIM2:
├── Clock Source: Internal Clock  
├── Prescaler: 71
├── Counter Period: 3646 (8-N-1)4010 (8-E-1)
├── Counter Mode: Up
└── NVIC Settings: ✅ TIM2 global interrupt
#115200
Timers -> TIM2:
├── Clock Source: Internal Clock
├── Prescaler: 71  
├── Counter Period: 1750 (固定值)
├── Counter Mode: Up
└── NVIC Settings: ✅ TIM2 global interrupt
波特率格式每字符时间3.5字符时间定时器Period值
96008-N-11041.67μs3645.83μs3646
96008-E-11145.83μs4010.42μs4010
115200任意-1750μs1750

3. GPIO配置(RS485方向控制)

GPIO -> PA1:
├── GPIO mode: GPIO_Output  
├── GPIO Pull-up/Pull-down: No pull-up and no pull-down
└── User Label: RS485_DE

串口也最好配置下中断


STM32F103 + RS485 + FreeModbus RTU 配置概览

1️⃣ CubeMX硬件配置

📌 UART配置(RS485通信):USART1 -> Asynchronous, 9600, 8N1, 开启中断📌 Timer配置(RTU字符间隔):  TIM2 -> 内部时钟, 分频71, 周期1750, 开启中断📌 GPIO配置(RS485方向控制):PA1 -> 输出模式, 标签RS485_DE

2️⃣ FreeModbus源码文件

必须包含的源码文件:
├── mb.c, mbutils.c                    (协议栈主体)
├── mbrtu.c, mbcrc.c                   (RTU模式+CRC)  
├── mbfunccoils.c, mbfuncholding.c     (功能码实现)
├── mbfuncinput.c, mbfuncdisc.c        
├── 所有include/*.h文件                 (头文件)
└── port/*.c文件                       (移植层-自己写)

3️⃣ 配置文件修改

📝 mbconfig.h:
#define MB_RTU_ENABLED    1
#define MB_ASCII_ENABLED  0  
#define MB_TCP_ENABLED    0
#define MB_FUNC_READ_HOLDING_ENABLED 1  // 按需开启功能码📝 port.h:  
// 定义数据类型、外部句柄声明、RS485引脚宏

4️⃣ 移植层代码编写

📝 portserial.c (4个函数):xMBPortSerialInit()      - UART初始化vMBPortSerialEnable()    - 使能收发+RS485方向控制  xMBPortSerialPutByte()   - 发送1字节xMBPortSerialGetByte()   - 接收1字节📝 porttimer.c (3个函数):xMBPortTimersInit()      - 定时器初始化vMBPortTimersEnable()    - 启动定时器vMBPortTimersDisable()   - 停止定时器📝 portevent.c (3个函数):  xMBPortEventInit/Post/Get() - 事件处理(简单实现)

5️⃣ 主程序配置

📝 main.c:
1. 包含头文件: #include "mb.h"
2. 定义寄存器数组: usRegHoldingBuf[], usRegInputBuf[]  
3. 初始化: eMBInit(MB_RTU, 1, 1, 9600, MB_PAR_NONE)
4. 启用: eMBEnable()
5. 轮询: while(1) { eMBPoll(); }
6. 实现4个回调函数:- eMBRegHoldingCB()    (保持寄存器)- eMBRegInputCB()      (输入寄存器)  - eMBRegCoilsCB()      (线圈)- eMBRegDiscreteCB()   (离散输入)

6️⃣ 中断处理配置

📝 stm32f1xx_it.c:
USART1_IRQHandler():RXNE中断 -> pxMBFrameCBByteReceived()TXE中断  -> pxMBFrameCBTransmitterEmpty()TIM2_IRQHandler():  UPDATE中断 -> pxMBPortCBTimerExpired()

7️⃣ 工程配置

📌 包含路径添加:../freemodbus/modbus/include../freemodbus/modbus/rtu../freemodbus/port📌 源文件添加:将所有.c文件加入工程编译

📋 配置检查清单

  • CubeMX生成代码(UART1+TIM2+GPIO中断已开启)
  • FreeModbus源码文件已添加到工程
  • mbconfig.h已配置(RTU=1, ASCII=0, TCP=0)
  • port.h已定义类型和句柄声明
  • portserial.c已实现4个串口函数
  • porttimer.c已实现3个定时器函数
  • portevent.c已实现3个事件函数
  • main.c已添加Modbus初始化和轮询
  • main.c已实现4个寄存器回调函数
  • stm32f1xx_it.c已添加UART和TIM中断处理
  • 工程包含路径和源文件已配置

完成以上配置后,STM32F103就可以作为Modbus RTU从机通过RS485与主机通信!


STM32F103 + HAL库 + CubeMX的FreeModbus RTU实现方案

必须保留的源码文件

freemodbus/
├── modbus/
│   ├── mb.c                    ✅ 协议栈主入口
│   ├── mbutils.c               ✅ 工具函数(你漏了这个)
│   ├── rtu/
│   │   ├── mbrtu.c            ✅ RTU模式核心
│   │   ├── mbrtu.h
│   │   └── mbcrc.c            ✅ CRC计算(你漏了这个)
│   ├── functions/
│   │   ├── mbfunccoils.c      ✅ 线圈功能码
│   │   ├── mbfuncdisc.c       ✅ 离散输入功能码  
│   │   ├── mbfuncholding.c    ✅ 保持寄存器功能码
│   │   ├── mbfuncinput.c      ✅ 输入寄存器功能码
│   │   └── mbfuncother.c      ✅ 其他功能码(可选)
│   └── include/
│       ├── mb.h               ✅ 主头文件
│       ├── mbconfig.h         ✅ 配置文件
│       ├── mbport.h           ✅ 移植层接口
│       ├── mbproto.h          ✅ 协议定义
│       ├── mbframe.h          ✅ 帧处理
│       ├── mbfunc.h           ✅ 功能码定义
│       └── mbutils.h          ✅ 工具函数
└── port/├── port.h                 ✅ 移植层总头文件├── portserial.c           ✅ 串口移植├── porttimer.c            ✅ 定时器移植└── portevent.c            ✅ 事件移植

配置文件

mbconfig.h

#ifndef _MB_CONFIG_H
#define _MB_CONFIG_H/* ----------------------- RTU specific defines ---------------------------*/
#define MB_RTU_ENABLED                      1
#define MB_ASCII_ENABLED                    0  
#define MB_TCP_ENABLED                      0/* ----------------------- Function codes defines --------------------------*/
#define MB_FUNC_OTHER_REP_SLAVEID_BUF       34
#define MB_FUNC_OTHER_REP_SLAVEID_ENABLED   1#define MB_FUNC_READ_INPUT_ENABLED          1
#define MB_FUNC_READ_HOLDING_ENABLED        1
#define MB_FUNC_WRITE_HOLDING_ENABLED       1
#define MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED 1
#define MB_FUNC_READ_COILS_ENABLED          1
#define MB_FUNC_WRITE_COIL_ENABLED          1
#define MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED 1
#define MB_FUNC_READ_DISCRETE_INPUTS_ENABLED 1
#define MB_FUNC_READWRITE_HOLDING_ENABLED   1#endif

port.h

#ifndef _PORT_H
#define _PORT_H#include "stm32f1xx_hal.h"
#include <stdint.h>
#include <stdbool.h>/* ----------------------- Type definitions ---------------------------------*/
typedef uint8_t    BOOL;
typedef uint8_t    UCHAR;
typedef int8_t     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/* ----------------------- Critical section ---------------------------------*/
#define ENTER_CRITICAL_SECTION()    __disable_irq()
#define EXIT_CRITICAL_SECTION()     __enable_irq()/* ----------------------- Hardware definitions -----------------------------*/
extern UART_HandleTypeDef huart1;
extern TIM_HandleTypeDef htim2;#define RS485_DE_Pin GPIO_PIN_1
#define RS485_DE_GPIO_Port GPIOA/* ----------------------- Function prototypes ------------------------------*/
// 这些函数需要在中断中调用
extern BOOL pxMBFrameCBByteReceived(void);
extern BOOL pxMBFrameCBTransmitterEmpty(void);
extern BOOL pxMBPortCBTimerExpired(void);#endif

移植层实现

portserial.c

#include "port.h"
#include "mb.h"
#include "mbport.h"/* ----------------------- Start implementation -----------------------------*/
void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{if (xRxEnable) {// 启用接收中断__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);// RS485设为接收模式HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET);} else {__HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);}if (xTxEnable) {// 启用发送中断  __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);// RS485设为发送模式HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET);} else {__HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);// 发送完成后切换到接收模式HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET);}
}BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)
{// CubeMX已经初始化了UART,这里可以重新配置参数huart1.Init.BaudRate = ulBaudRate;switch (eParity) {case MB_PAR_NONE:huart1.Init.Parity = UART_PARITY_NONE;huart1.Init.WordLength = UART_WORDLENGTH_8B;break;case MB_PAR_ODD:huart1.Init.Parity = UART_PARITY_ODD;huart1.Init.WordLength = UART_WORDLENGTH_9B;break;case MB_PAR_EVEN:huart1.Init.Parity = UART_PARITY_EVEN;huart1.Init.WordLength = UART_WORDLENGTH_9B;break;default:return FALSE;}if (HAL_UART_Init(&huart1) != HAL_OK) {return FALSE;}// 初始化RS485为接收模式HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET);return TRUE;
}BOOL xMBPortSerialPutByte(CHAR ucByte)
{huart1.Instance->DR = ucByte;return TRUE;
}BOOL xMBPortSerialGetByte(CHAR * pucByte)
{*pucByte = huart1.Instance->DR;return TRUE;
}

porttimer.c

#include "port.h"
#include "mb.h"
#include "mbport.h"/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortTimersInit(USHORT usTim1Timerout50us)
{// 设置定时器周期:usTim1Timerout50us * 50us// 1MHz时钟下,1us = 1个计数uint32_t ulTimerReload = usTim1Timerout50us * 50;__HAL_TIM_SET_AUTORELOAD(&htim2, ulTimerReload - 1);__HAL_TIM_SET_COUNTER(&htim2, 0);return TRUE;
}void vMBPortTimersEnable(void)
{// 重置计数器并启动定时器__HAL_TIM_SET_COUNTER(&htim2, 0);__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);__HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);HAL_TIM_Base_Start(&htim2);
}void vMBPortTimersDisable(void)
{HAL_TIM_Base_Stop(&htim2);__HAL_TIM_DISABLE_IT(&htim2, TIM_IT_UPDATE);
}// 延时函数(如果需要)
void vMBPortTimersDelay(USHORT usTimeOutMS)
{HAL_Delay(usTimeOutMS);
}

portevent.c

#include "port.h"
#include "mb.h"
#include "mbport.h"/* ----------------------- Variables ----------------------------------------*/
static eMBEventType eQueuedEvent;
static BOOL xEventInQueue;/* ----------------------- Start implementation -----------------------------*/
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;
}

中断处理函数

stm32f1xx_it.c中添加

/* USER CODE BEGIN Includes */
#include "port.h"
/* USER CODE END Includes *//* USER CODE BEGIN EV */
void USART1_IRQHandler(void)
{/* USER CODE BEGIN USART1_IRQn 0 */// 接收中断if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) && __HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE)) {pxMBFrameCBByteReceived();}// 发送中断if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE) && __HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_TXE)) {pxMBFrameCBTransmitterEmpty();}/* USER CODE END USART1_IRQn 0 */HAL_UART_IRQHandler(&huart1);/* USER CODE BEGIN USART1_IRQn 1 *//* USER CODE END USART1_IRQn 1 */
}void TIM2_IRQHandler(void)
{/* USER CODE BEGIN TIM2_IRQn 0 */if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) && __HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_UPDATE)) {__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);pxMBPortCBTimerExpired();}/* USER CODE END TIM2_IRQn 0 */HAL_TIM_IRQHandler(&htim2);/* USER CODE BEGIN TIM2_IRQn 1 *//* USER CODE END TIM2_IRQn 1 */
}
/* USER CODE END EV */

主程序实现

main.c

/* USER CODE BEGIN Includes */
#include "mb.h"
#include "mbutils.h"
/* USER CODE END Includes *//* USER CODE BEGIN PV */
// 寄存器定义
#define REG_HOLDING_START    1
#define REG_HOLDING_NREGS    10
#define REG_INPUT_START      1  
#define REG_INPUT_NREGS      10
#define REG_COILS_START      1
#define REG_COILS_SIZE       16// 寄存器数组
USHORT usRegHoldingBuf[REG_HOLDING_NREGS];
USHORT usRegInputBuf[REG_INPUT_NREGS]; 
UCHAR ucRegCoilsBuf[REG_COILS_SIZE / 8];
/* USER CODE END PV */int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();MX_TIM2_Init();/* USER CODE BEGIN 2 */// 初始化Modbus RTU从机:地址1,UART1端口,9600波特率,无校验if (eMBInit(MB_RTU, 1, 1, 9600, MB_PAR_NONE) != MB_ENOERR) {Error_Handler();}// 启用Modbus协议栈if (eMBEnable() != MB_ENOERR) {Error_Handler();}// 初始化寄存器数据for (int i = 0; i < REG_HOLDING_NREGS; i++) {usRegHoldingBuf[i] = i + 100;}/* USER CODE END 2 */while (1){/* USER CODE BEGIN 3 */// 轮询Modbus协议栈eMBPoll();// 更新输入寄存器(模拟传感器数据)usRegInputBuf[0]++;usRegInputBuf[1] = HAL_GetTick() & 0xFFFF;HAL_Delay(10);/* USER CODE END 3 */}
}/* USER CODE BEGIN 4 */
// ==================== Modbus回调函数实现 ====================// 保持寄存器回调(功能码03/06/16)
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;
}// 输入寄存器回调(功能码04)
eMBErrorCode eMBRegInputCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs)
{eMBErrorCode eStatus = MB_ENOERR;int iRegIndex;if ((usAddress >= REG_INPUT_START) && (usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS)) {iRegIndex = (int)(usAddress - REG_INPUT_START);while (usNRegs > 0) {*pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] >> 8);*pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] & 0xFF);iRegIndex++;usNRegs--;}} else {eStatus = MB_ENOREG;}return eStatus;
}// 线圈回调(功能码01/05/15)
eMBErrorCode eMBRegCoilsCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode)
{eMBErrorCode eStatus = MB_ENOERR;int iNCoils = (int)usNCoils;int usBitOffset;// 检查地址范围if ((usAddress >= REG_COILS_START) && (usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE)) {usBitOffset = (int)(usAddress - REG_COILS_START);switch (eMode) {case MB_REG_READ:while (iNCoils > 0) {*pucRegBuffer++ = xMBUtilGetBits(ucRegCoilsBuf, usBitOffset, (UCHAR)(iNCoils > 8 ? 8 : iNCoils));iNCoils -= 8;usBitOffset += 8;}break;case MB_REG_WRITE:while (iNCoils > 0) {xMBUtilSetBits(ucRegCoilsBuf, usBitOffset, (UCHAR)(iNCoils > 8 ? 8 : iNCoils), *pucRegBuffer++);iNCoils -= 8;usBitOffset += 8;}break;}} else {eStatus = MB_ENOREG;}return eStatus;
}// 离散输入回调(功能码02)
eMBErrorCode eMBRegDiscreteCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete)
{// 简单实现:返回一些固定值return MB_ENOREG;
}
/* USER CODE END 4 */

工程配置要点

1. 包含路径添加

Project Settings -> C/C++ -> Include Paths:
├── ../Core/freemodbus/modbus/include
├── ../Core/freemodbus/modbus/rtu  
├── ../Core/freemodbus/port
└── ../Core/freemodbus/modbus

2. 源文件添加到工程

将所有 .c 文件添加到Keil/CubeIDE工程中

3. 编译宏定义(可选)

// 在工程设置中添加(如果需要)
#define MB_RTU_ENABLED 1

🔄 RS485方向控制原理

RS485是半双工通信

  • 同一时刻只能单向传输:要么发送,要么接收,不能同时进行
  • 需要方向控制信号来切换收发模式

📡 PA1 (RS485_DE) 的作用

DE/RE引脚说明

RS485收发器(如MAX485)通常有两个控制引脚:
├── DE (Driver Enable):   高电平=使能发送驱动器
└── RE (Receiver Enable): 低电平=使能接收器大多数情况下:DE和RE连接在一起,或者RE = !DE
所以用一个GPIO就能控制收发方向

方向控制逻辑

// 发送模式:STM32 -> RS485总线 -> 从站
HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET);   // DE=1
// 此时:DE=1(发送使能), RE=0(接收禁用)// 接收模式:从站 -> RS485总线 -> STM32  
HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET); // DE=0
// 此时:DE=0(发送禁用), RE=1(接收使能)

🔄 完整通信流程

1. STM32发送数据给从站

// 在portserial.c的vMBPortSerialEnable()函数中:
if (xTxEnable) {HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET);  // 切换到发送模式__HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);
}

2. STM32接收从站返回的数据

if (xRxEnable) {HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET); // 切换到接收模式__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
}

📋 实际工作时序

Modbus RTU主从通信过程

时间轴:  发送请求    →    等待响应    →    接收响应
STM32:   Master发送   →    切换到接收   →    读取Slave回复
RS485_DE: 1(发送模式)  →    0(接收模式)  →    0(接收模式)

举例:读取保持寄存器

1. 主机准备发送:DE=1,进入发送模式
2. 主机发送:01 03 00 00 00 01 84 0A (读从机1的寄存器)
3. 发送完成:DE=0,切换到接收模式  
4. 从机响应:01 03 02 01 F4 B8 FA (返回数据500)
5. 主机接收:通过UART接收中断读取从机数据

关键要点

✅ 能双向通信

  • 能发送:DE=1时,STM32可以通过RS485发送数据给从站
  • 能接收:DE=0时,STM32可以通过RS485接收从站返回的数据

🎯 方向控制的意义

  • 防止总线冲突:确保同一时刻只有一个设备在发送
  • 实现半双工:在发送和接收之间正确切换
  • 保护硬件:避免多个发送器同时驱动总线造成损坏

📝 代码中的自动切换

// FreeModbus会自动调用vMBPortSerialEnable()来控制方向
// 你不需要手动控制,协议栈会:
// 1. 发送时自动设置DE=1
// 2. 发送完成后自动设置DE=0等待接收
// 3. 接收完成后保持DE=0等待下次发送

参考博客1
参考博客2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/90090.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/90090.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Ansible】Ansible 管理 Elasticsearch 集群启停

一、集群节点信息 通过 Ansible inventory 定义的集群节点分组如下&#xff1a;[es]&#xff08;Elasticsearch 节点&#xff09; 192.168.100.150192.168.100.151192.168.100.152[logstash]&#xff08;Logstash 节点&#xff09; 192.168.100.151[kibana]&#xff08;Kibana …

Effective Python 第15条 不要过分依赖给字典添加条目时所用的顺序

引言&#xff1a;字典顺序的重要性 在Python编程中&#xff0c;字典&#xff08;dict&#xff09;是一种常用的数据结构&#xff0c;用于存储键值对。然而&#xff0c;字典的迭代顺序问题常常困扰着开发者。从Python 3.7开始&#xff0c;字典保证了键的插入顺序&#xff0c;这一…

事务隔离级别和传播方式

事务隔离级别 事务隔离级别是数据库系统中控制事务间相互影响程度的重要机制。不同的隔离级别在数据一致性保证和系统性能之间提供不同的权衡选择。下面我将详细解析四种标准隔离级别、它们能解决的问题以及可能存在的并发问题。 一、四种标准隔离级别 1. 读未提交 (Read Uncom…

不同地区的主要搜索引擎工具

研究seo&#xff0c;想汇总一下不同国家的搜索引擎工具&#xff0c;顺带了解一下这些公司提供的服务。 韩国&#xff1a;NAVER——>LINE 日本: 我还不知道&#xff0c;如果你知道可以评论告诉我 俄罗斯&#xff1a;yandex yandex有点像本土化的google 搜索引擎 邮箱 网盘 在…

实操:AWS CloudFront的动态图像转换

概述 适用于 Amazon CloudFront 的动态图像转换&#xff08;前身为无服务器图像处理器&#xff09;&#xff0c;通过 Amazon CloudFront 的全球内容分发网络&#xff08;CDN&#xff09;实现实时图像处理。此 AWS 解决方案可帮助您优化视觉内容交付&#xff0c;同时显著降低运营…

Spring Boot 实战详解:从静态资源到 Thymeleaf 模板引擎

Spring Boot 凭借其 "约定大于配置" 的理念&#xff0c;极大简化了 Java 应用开发流程。本文将从 Spring Boot 核心特性出发&#xff0c;详细解析静态资源映射规则、Thymeleaf 模板引擎的使用&#xff0c;并结合完整实战案例&#xff0c;帮助开发者快速上手 Spring B…

docker的镜像与推送

docker build# 1. 基本构建命令&#xff08;使用当前目录的 Dockerfile&#xff09; docker build .# 2. 指定 Dockerfile 路径和构建上下文 docker build -f /path/to/Dockerfile /path/to/build/context# 3. 为镜像设置名称和标签 docker build -t my-image:latest .# 4. 设置…

计算机网络学习----域名解析

在互联网世界中&#xff0c;我们习惯通过域名&#xff08;如www.example.com&#xff09;访问网站&#xff0c;而非直接记忆复杂的 IP 地址&#xff08;如 192.168.1.1&#xff09;。域名与 IP 地址之间的转换过程&#xff0c;就是域名解析。它是互联网通信的基础环节&#xff…

构建高性能推荐系统:MixerService架构解析与核心实现

——深入剖析推荐服务的分层设计、工作流引擎与高可用策略 一、整体架构与分层设计 该推荐服务采用经典分层架构模式​7&#xff0c;各层职责清晰&#xff1a; ​HTTP接口层​ 支持 GET/POST 请求解析&#xff0c;自动映射参数到 RcmdReq 协议对象统一错误处理&#xff1a;参…

【安全漏洞】隐藏服务器指纹:Nginx隐藏版本号配置修改与重启全攻略

🚀 隐藏服务器指纹:Nginx配置修改与重启全攻略 你是否知道,默认情况下Nginx会在HTTP响应头中暴露版本号?这个看似无害的Server: nginx/1.x.x字段,实则可能成为黑客的"藏宝图"。今天我们就来揭秘如何通过简单配置提升服务器安全性,并手把手教你完成Windows环境…

构建RAG智能体(2):运行状态链

在现代AI应用开发中&#xff0c;如何让聊天机器人具备记忆能力和上下文理解是一个核心挑战。传统的无状态对话系统往往无法处理复杂的多轮对话场景&#xff0c;特别是当用户需要提供多种信息来完成特定任务时。 本文就来讨论一下如何利用runnable来编排更有趣的语言模型系统&a…

RPA认证考试全攻略:如何高效通过uipath、实在智能等厂商考试

rpa认证考试有什么作用&#xff1f;数字洪流席卷全球&#xff0c;企业效率之争已进入秒级战场。当重复性工作吞噬着创造力&#xff0c;RPA&#xff08;机器人流程自动化&#xff09;技术正以前所未有的速度重塑职场生态。财务对账、报表生成、跨系统数据搬运……这些曾经耗费人…

浅析MySQL事务隔离级别

MySQL 的事务隔离级别定义了多个并发事务在访问和修改相同数据时&#xff0c;彼此之间的可见性和影响程度。它解决了并发事务可能引发的三类核心问题&#xff1a; 脏读&#xff1a; 一个事务读取了另一个未提交事务修改的数据。不可重复读&#xff1a; 一个事务内多次读取同一行…

【Linux系统】基础IO(上)

1. 深入理解"文件"概念1.1 文件的狭义理解狭义上的“文件”主要指存储在磁盘上的数据集合。具体包括&#xff1a;文件在磁盘里&#xff1a;文件是磁盘上以特定结构&#xff08;如FAT、ext4文件系统&#xff09;保存的数据集合&#xff0c;由字节或字符序列构成。磁盘…

构建智能可视化分析系统:RTSP|RTMP播放器与AI行为识别的融合实践

技术背景 随着人工智能向边缘侧、实时化方向加速演进&#xff0c;视频已从传统的“记录媒介”跃升为支撑智能感知与自动决策的关键数据入口。在安防监控、工业安全、交通治理等复杂应用场景中&#xff0c;行为识别系统的准确性和响应效率&#xff0c;越来越依赖于视频源的时效…

AI入门学习-Python 最主流的机器学习库Scikit-learn

一、Scikit-learn 核心定位是什么&#xff1a;Python 最主流的机器学习库&#xff0c;涵盖从数据预处理到模型评估的全流程。 为什么测试工程师必学&#xff1a;✅ 80% 的测试机器学习问题可用它解决✅ 无需深厚数学基础&#xff0c;API 设计极简✅ 与 Pandas/Numpy 无缝集成&a…

apache-doris安装兼datax-web配置

Doris安装 官方快速开始链接 下载2.1.10&#xff0c;解压。我这边个人服务器CPU是J1900&#xff0c;是没有 avx2的&#xff0c;所以选no 配置JAVA_HOME&#xff0c;这里没有配置的要配置下&#xff0c;注意要Oracle的jdk&#xff0c;openjdk没有jps等工具集&#xff0c;后面跑…

问题实例:4G网络下语音呼叫失败

问题描述 测试机 拨号呼出后&#xff0c;一直在4G&#xff0c;超时后自动挂断。 对比机可以呼出成功&#xff0c;呼出时回落3G。 日志分析 测试机和对比机一样发起了CSFB 呼叫。 只是测试机后面没有回落3G。 03:44:40.373264 [0xB0ED] LTE NAS EMM Plain OTA Outgoing Message …

MATLAB 2024b深度学习新特性全面解析与DeepSeek大模型集成开发技术

随着人工智能技术向多学科交叉融合与工程实践领域纵深发展&#xff0c;MATLAB 2024b深度学习工具箱通过架构创新与功能强化&#xff0c;为科研创新和行业应用提供了全栈式解决方案。基于该版本工具链的三大革新方向展开&#xff1a;一是构建覆盖经典模型与前沿架构的体系化&…

Springboot美食分享平台

一、 绪论 1.1 研究意义 当今社会作为一个飞速的发展社会&#xff0c;网络已经完全渗入人们的生活&#xff0c; 网络信息已成为传播的第一大媒介&#xff0c; 可以毫不夸张说网络资源获取已逐步改变了人们以前的生活方式&#xff0c;网络已成为人们日常&#xff0c;休闲主要工…