# tiny485-mbrtu **Repository Path**: ZeroOneLab/tiny485-mbrtu ## Basic Information - **Project Name**: tiny485-mbrtu - **Description**: 轻量、高通用性的 RS-485 Modbus-RTU 嵌入式驱动框架,分层解耦设计,适配 STM32/ESP32 等 MCU,支持多设备通信,裸机 / RTOS 兼容,移植简单,适用于工业控制、物联网数据采集场景。 - **Primary Language**: C - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2026-02-28 - **Last Updated**: 2026-07-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: RS-485, stm32, modbus-rtu ## README # tiny485-mbrtu [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 一款**轻量、高通用性、分层解耦**的RS-485 Modbus-RTU驱动框架,专为嵌入式MCU设计,解决工业控制/物联网数据采集中Modbus设备碎片化、代码冗余、跨平台移植困难、多路485设备并行通信难等问题,适配STM32等主流MCU平台,裸机/RTOS环境均兼容,移植仅需修改硬件抽象层,核心逻辑无需改动。 ## 关键词 Modbus-RTU, RS-485, 嵌入式驱动, 工业通信, 分层解耦, 多设备并行, 硬件无关性 ## 命名说明 本项目命名为`tiny485-mbrtu`,其中: - `tiny` = 轻量,代表框架静态内存分配、无堆使用,资源占用低,适配小型嵌入式系统; - `485` = RS-485,代表框架基于RS-485总线实现物理层通信; - `mbrtu` = Modbus-RTU,代表框架实现的工业现场总线协议; 因此`tiny485-mbrtu`本质是**标准化、可扩展的嵌入式Modbus-RTU驱动框架**,区别于传统项目中为单个设备编写的零散485通信代码。 ## 🌟 核心特性 ✅ **分层解耦设计**:核心通信逻辑与硬件操作完全分离,移植仅需修改硬件抽象层,核心代码跨平台复用 ✅ **硬件无关性**:适配任意带UART外设的MCU,支持STM32/ESP32/AT32等主流嵌入式平台 ✅ **多设备并行**:通过LUN(逻辑单元号)标识多路RS-485总线,每路总线可接247个Modbus从机,通信互不干扰 ✅ **完善的协议校验**:实现标准Modbus-RTU 16位CRC校验,自动过滤错误帧、异常帧 ✅ **鲁棒性保护**:内置接收缓冲区溢出防护、帧超时判断、异常响应识别,工业场景稳定可靠 ✅ **轻量无依赖**:静态内存分配,无动态内存申请,无第三方库依赖,适合资源受限的MCU ✅ **裸机/RTOS兼容**:预留RTOS临界区保护接口,裸机可留空,RTOS下实现资源互斥访问 ✅ **简洁易用的API**:封装Modbus-RTU核心操作(读/写寄存器),无需关注帧结构、CRC计算等底层细节 ✅ **易调试**:支持日志打印,通信失败时精准返回错误码(超时/CRC错误/长度错误/异常响应) ## 📂 文件结构 驱动仅包含4个核心文件,结构清晰,可直接集成到任意嵌入式工程中,无需额外修改核心代码: ```C tiny485-mbrtu/ ├── mbrtu.c # 核心驱动层:Modbus-RTU帧构建、CRC校验、数据收发、解析等通用逻辑 ├── mbrtu.h # 核心驱动头文件:对外暴露所有API接口、错误码枚举 ├── mbrtu_port.c # 硬件抽象层:串口操作、延时、485引脚控制等硬件相关实现(需用户适配) └── mbrtu_port.h # 硬件抽象层头文件:宏定义、LUN配置、硬件层函数声明 ``` ## 🏗️ 系统架构 采用**三层架构**实现硬件与逻辑的解耦,大幅降低移植成本和维护难度,核心逻辑一次开发多平台复用: ```C 应用层(工业业务逻辑:传感器数据采集、执行器控制、PLC通信) ↕️ 核心驱动层(mbrtu.c/h):通用Modbus-RTU协议逻辑(帧构建、CRC校验、数据收发、解析、超时判断) ↕️ 硬件抽象层(mbrtu_port.c/h):平台相关硬件操作(串口收发、485引脚控制、延时、Tick、临界区) ``` ## 🚀 快速开始 移植和使用框架的核心仅需修改**硬件抽象层(mbrtu_port.c/h)**,核心驱动层无需任何改动,以下为通用适配步骤(以STM32为例)。 ### 步骤1:硬件抽象层适配(唯一需要修改的部分) #### 1.1 宏定义配置(`mbrtu_port.h`) 根据实际项目需求修改核心宏定义,定义RS-485总线数量、缓冲区大小等: ```C // 逻辑单元号定义:一个LUN对应一路独立的RS-485总线 #define MBRTU_LUN_USER1 0 /* 自定义LUN1,对接第一路485总线 */ #define MBRTU_LUN_USER2 1 /* 自定义LUN2,对接第二路485总线 */ // 核心配置参数 #define MBRTU_NUM 1 /* 实际使用的485总线数量(LUN数量) */ #define MBRTU_RECV_BUF_SIZE 64 /* 接收缓冲区大小,按需调整 */ #define MBRTU_LOG(...) //printf(__VA_ARGS__) /* 调试日志宏,默认printf,可自定义关闭 */ ``` #### 1.2 硬件层函数实现(`mbrtu_port.c`) 实现与平台相关的硬件操作函数,对接MCU的HAL/LL库,**LUN为多路485总线的唯一标识**,不同LUN对应不同串口/485引脚: ```C // 1. 获取系统毫秒级Tick(用于超时判断) uint32_t mbrtu_port_get_tick(void) { return HAL_GetTick(); // 对接平台Tick函数,如ESP32的xTaskGetTickCount() } // 2. 毫秒级延时函数 void mbrtu_port_delay_ms(uint32_t ms) { HAL_Delay(ms); // 对接平台延时函数 } // 3. 485总线初始化(串口+DE/RE引脚) void mbrtu_port_init(uint8_t lun) { switch (lun) { case MBRTU_LUN_USER1: HAL_UART_Receive_IT(&huart2, mbrtu_rx_data[lun], 1); // 开启串口中断接收 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET); // DE/RE拉低,初始为接收模式 break; // 新增LUN只需添加case,实现对应串口/引脚初始化 default: break; } } // 4. 485数据发送(含DE/RE收发切换) void mbrtu_port_send(uint8_t lun, uint8_t *buf, uint16_t len) { switch (lun) { case MBRTU_LUN_USER1: HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET); // 切换为发送模式 HAL_Delay(1); // 引脚切换延时,按需调整 HAL_UART_Transmit(&huart2, buf, len, 1000); // 串口发送数据 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET); // 切回接收模式 break; default: break; } } // 5. 临界区保护(裸机留空,RTOS下实现互斥锁/关中断) void mbrtu_port_enter_critical(uint8_t lun) {} void mbrtu_port_exit_critical(uint8_t lun) {} ``` ### 步骤2:工程集成 1. 将`tiny485-mbrtu`文件夹下的4个核心文件添加到嵌入式工程,新建分组`tiny485-mbrtu`管理; 2. 在编译器(Keil/STM32CubeIDE/VSCode)中添加该文件夹的头文件路径; 3. 若使用日志宏`MBRTU_LOG`,需重定向`printf`到调试串口,并开启编译器微库(如Keil的Use MicroLIB)。 ### 步骤3:驱动初始化与串口回调对接 1. **驱动初始化**:在工程主函数中,完成MCU平台初始化(时钟、GPIO、串口)后,调用驱动初始化函数; 2. **串口回调对接**:将MCU的串口接收中断回调指向驱动的接收处理函数,为驱动提供数据输入。 ```C #include "mbrtu.h" int main(void) { // 1. 平台初始化:时钟、GPIO、串口、延时等 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART2_UART_Init(); // 对接485的串口初始化 // 2. 初始化tiny485-mbrtu驱动 mbrtu_init(); while(1) { // 3. 业务逻辑:Modbus-RTU读/写寄存器操作 } } // 串口接收中断回调函数(以STM32 HAL库为例) void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART2) { // 驱动接收核心入口:传递LUN、接收数据、长度 mbrtu_recv_handler(MBRTU_LUN_USER1, mbrtu_rx_data[MBRTU_LUN_USER1], 1); // 重新开启串口中断接收,持续获取数据 HAL_UART_Receive_IT(&huart2, mbrtu_rx_data[MBRTU_LUN_USER1], 1); } } ``` ## 📚 核心API 驱动对外封装了**基础初始化**、**数据收发**、**数据解析**三类核心API,屏蔽了Modbus-RTU底层细节,直接调用即可实现寄存器读/写,满足99%的工业485通信需求。 ### 基础初始化函数 ```C void mbrtu_init(void); // 驱动初始化:清空缓冲区、初始化硬件层 void mbrtu_clear_recv_buffer(uint8_t lun); // 清空指定LUN的接收缓冲区 ``` ### 数据收发与协议操作函数 |函数声明|功能描述| |---|---| |`uint8_t mbrtu_build_read_holding(uint8_t lun, uint8_t slave, uint16_t regAddr, uint16_t quantity, uint16_t timeout)`|构建读保持寄存器请求帧(功能码0x03),自动发送并等待响应| |`void mbrtu_build_write_single(uint8_t lun, uint8_t slave, uint16_t regAddr, uint16_t value)`|构建写单个寄存器请求帧(功能码0x06),自动发送| |`uint8_t mbrtu_recv_handler(uint8_t lun, const uint8_t *data, uint16_t len)`|驱动接收核心入口,串口接收后必须调用,传递数据| |`uint8_t mbrtu_get_recv_data(uint8_t lun, uint8_t **data, uint16_t *dataLen)`|获取指定LUN的接收原始数据,返回数据指针和长度| ### 数据解析函数 ```C uint8_t mbrtu_parse_data(uint8_t lun, uint8_t *raw, uint16_t rawLen, uint8_t *pdu, uint8_t *pduLen); ``` **功能**:解析Modbus-RTU原始接收数据,完成CRC校验、异常帧识别,提取去除CRC的PDU有效数据。 ### 错误码枚举 驱动通过错误码精准标识通信状态,所有API的返回值均遵循以下枚举: ```C typedef enum { MBRTU_OK = 0, /* 操作成功 */ MBRTU_ERR_CRC, /* CRC校验错误 */ MBRTU_ERR_EXCEPTION, /* 从机异常响应(功能码最高位为1) */ MBRTU_ERR_LEN, /* 数据帧长度错误 */ MBRTU_ERR_TIMEOUT, /* 通信超时,未收到从机响应 */ MBRTU_ERR_RECV, /* 接收错误,无有效数据 */ MBRTU_ERR_PARAM, /* 入参错误(如从站地址/寄存器数量非法) */ } mbrtu_err_t; ``` ## 💻 使用示例 以下为最常用的**读保持寄存器**和**写单个寄存器**示例,基于STM32裸机环境,直接复用即可。 ### 示例1:读保持寄存器(功能码0x03) 读取指定从机的保持寄存器数据,自动发送请求帧、等待响应、解析数据: ```C #include "mbrtu.h" #include #define LUN_485_1 MBRTU_LUN_USER1 // 定义当前使用的485总线LUN #define SLAVE_ADDR 0x01 // 从站设备地址 #define REG_ADDR 0x0000 // 寄存器起始地址 #define REG_NUM 0x0002 // 读取寄存器数量 #define TIMEOUT 1000 // 通信超时时间(ms) int main(void) { // 平台+驱动初始化... mbrtu_init(); uint8_t state; uint8_t *recv_data; uint16_t recv_len; uint8_t pdu[64]; uint8_t pdu_len; while(1) { // 1. 构建并发送读保持寄存器请求 state = mbrtu_build_read_holding(LUN_485_1, SLAVE_ADDR, REG_ADDR, REG_NUM, TIMEOUT); if (state == MBRTU_ERR_TIMEOUT) { MBRTU_LOG("[MBRTU][ERR] Read timeout\r\n"); HAL_Delay(1000); continue; } // 2. 获取接收的原始数据 state = mbrtu_get_recv_data(LUN_485_1, &recv_data, &recv_len); if (state == MBRTU_ERR_RECV) { MBRTU_LOG("[MBRTU][ERR] No recv data\r\n"); HAL_Delay(1000); continue; } MBRTU_LOG("[MBRTU][OK] Recv raw data: "); for (uint16_t i = 0; i < recv_len; i++) MBRTU_LOG("%02X ", recv_data[i]); MBRTU_LOG("\r\n"); // 3. 解析数据(CRC校验+提取PDU) state = mbrtu_parse_data(LUN_485_1, recv_data, recv_len, pdu, &pdu_len); if (state != MBRTU_OK) { MBRTU_LOG("[MBRTU][ERR] Parse error: %d\r\n", state); HAL_Delay(1000); continue; } MBRTU_LOG("[MBRTU][OK] Parse PDU data: "); for (uint16_t i = 0; i < pdu_len; i++) MBRTU_LOG("%02X ", pdu[i]); MBRTU_LOG("\r\n"); HAL_Delay(1000); } } ``` ### 示例2:写单个寄存器(功能码0x06) 向指定从机的单个寄存器写入数值,自动构建并发送请求帧: ```C #include "mbrtu.h" #define LUN_485_1 MBRTU_LUN_USER1 // 485总线LUN #define SLAVE_ADDR 0x01 // 从站地址 #define REG_ADDR 0x0001 // 要写入的寄存器地址 #define REG_VALUE 0x1234 // 要写入的寄存器数值 int main(void) { // 平台+驱动初始化... mbrtu_init(); while(1) { // 构建并发送写单个寄存器请求帧 mbrtu_build_write_single(LUN_485_1, SLAVE_ADDR, REG_ADDR, REG_VALUE); MBRTU_LOG("[MBRTU][OK] Write register 0x%04X with value 0x%04X\r\n", REG_ADDR, REG_VALUE); HAL_Delay(2000); } } ``` ## 🚨 故障排除 框架使用中常见问题及解决方法,按**优先级**排查,快速定位485通信问题: |问题现象|可能原因|解决方法| |---|---|---| |读寄存器返回`MBRTU_ERR_TIMEOUT`(超时)|1. 485接线错误(A/B接反)
2. 串口波特率/校验位不匹配
3. DE/RE引脚未正确切换
4. 从站地址错误|1. 核对485模块A/B接线,与从机一致
2. 确保MCU与从机的通信参数(9600/8/N/1)一致
3. 检查`mbrtu_port_send`中DE/RE引脚控制逻辑
4. 确认从机实际Modbus地址,非0x00/0xFF| |解析数据返回`MBRTU_ERR_CRC`(CRC错误)|1. 485总线干扰严重
2. 串口接收数据丢包
3. 波特率过高导致采样错误|1. 485总线增加终端电阻(120Ω)
2. 优化串口接收(如改用DMA空闲接收)
3. 降低波特率(如9600),增加收发切换延时| |返回`MBRTU_ERR_EXCEPTION`(从机异常)|1. 寄存器地址越界
2. 读取/写入的寄存器数量非法
3. 从机不支持对应功能码|1. 核对从机寄存器映射表,确保地址有效
2. 读寄存器数量≤123,写寄存器仅支持单个
3. 确认从机支持0x03/0x06功能码| |返回`MBRTU_ERR_LEN`(长度错误)|1. 接收缓冲区过小
2. 从机返回异常帧长度|1. 增大`MBRTU_RECV_BUF_SIZE`宏定义
2. 用串口助手抓取原始帧,核对从机返回格式| |485总线通信乱码|1. 串口初始化错误
2. DE/RE切换延时过短
3. 电源纹波干扰|1. 检查MCU串口初始化代码,确保参数正确
2. 增大`mbrtu_port_send`中的`HAL_Delay`时间(如2ms)
3. 485模块使用隔离电源,增加滤波电容| |多路485总线数据串扰|1. LUN与串口/引脚映射错误
2. RTOS下无临界区保护|1. 检查`mbrtu_port_init/send`中LUN的case分支,确保一一对应
2. 在`mbrtu_port_enter/exit_critical`中实现互斥锁| ## 📄 许可证 本项目基于**MIT开源协议**发布,可自由用于商业/非商业项目,保留作者版权声明即可。 ## 🔗 相关链接 - 详细移植教程:[tiny485-mbrtu 高通用性的485MB-RTU驱动框架](https://www.yuque.com/u54102073/rs06a5/xdgnn1fdl7cgyg36?singleDoc#) - 嵌入式技术交流群:181921938 如果这个项目对你的工业控制/物联网开发有帮助,请给它一个 ⭐ !