文章目錄
- ? **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主從通信過程**
- **舉例:讀取保持寄存器**
- ? **關鍵要點**
- **? 能雙向通信**
- **🎯 方向控制的意義**
- **📝 代碼中的自動切換**
縮寫/單詞 | 全稱 | 含義 |
---|---|---|
ModBus | Modicon Bus | 最早由 Modicon(現施耐德) 開發的工業通信協議 |
RTU | Remote Terminal Unit | 遠程終端單元,指一種緊湊、二進制的傳輸格式(區別于 ASCII 模式) |
下載freemodbus,解壓。
名稱 | 作用說明 |
---|---|
modbus | 核心源碼目錄,包含 FreeModbus 協議棧的所有 .c/.h 文件(如 mb.c , mbport.h , mbrtu.c 等)。 |
demo | 示例工程目錄,包含 FreeModbus 在不同平臺(如 AVR、Win32、STR71x)上的完整示例項目。你可以參考這些例子移植到 STM32。 |
doc | 文檔目錄,包含協議棧的說明文檔(如 modbus.txt 、demo.txt ),但內容較舊。 |
tools | 輔助工具目錄,包含一些生成 CRC 表的小工具(如 crcgen.py ),通常用不到。 |
名稱 | 作用說明 |
---|---|
__MACOSX | macOS 壓縮時自動生成的隱藏文件夾,可以刪除,對代碼無影響。 |
Changelog.txt | FreeModbus 的版本更新日志。 |
gpl.txt | GNU GPL 開源協議(FreeModbus 采用 GPL v3 授權)。 |
lgpl.txt | GNU LGPL 協議(部分文件可能用 LGPL 授權)。 |
bsd.txt | BSD 協議(某些平臺移植代碼可能用 BSD 授權)。 |
📁 目錄說明
目錄名 | 作用說明 |
---|---|
ascii | Modbus ASCII 模式的源碼(基于文本的通信方式,用得少)。 |
rtu | Modbus RTU 模式的源碼(二進制、高效,最常用)。 |
tcp | Modbus 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值 |
---|---|---|---|---|
9600 | 8-N-1 | 1041.67μs | 3645.83μs | 3646 |
9600 | 8-E-1 | 1145.83μs | 4010.42μs | 4010 |
115200 | 任意 | - | 1750μs | 1750 |
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