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
英文地址,請注明出處:http://en.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;休閑主要工…