# modbus **Repository Path**: X1aSheng/modbus ## Basic Information - **Project Name**: modbus - **Description**: modbus - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-15 - **Last Updated**: 2026-10-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # modbus **版本 1.0.1** (`MB_VERSION_MAJOR/MINOR/PATCH` = 1/0/1, `MB_VERSION_NUM` = 10001) 面向资源受限 MCU 的 C99 Modbus 协议组件: **从站 + 主站双角色, RTU / ASCII / TCP 三传输**, 零动态分配、零可写静态量、完全可重入多实例。 整合者只需要 `#include "modbus.h"`。 --- ## 文档入口 索引是 [docs/README.md](docs/README.md)。按"我现在要干什么"选: | 我想... | 读 | |---|---| | 搞懂**为什么这样设计** (硬约束 / 可替换边界 / 模式地图 / ADR / 并发模型) | [docs/01-设计理念与模式地图.md](docs/01-设计理念与模式地图.md) | | 把它**接进工程** (编译清单 / 22 个回调 / ISR / 时基 / RS485 / 换 CRC / 常见坑) | [docs/02-快速接入与工程集成.md](docs/02-快速接入与工程集成.md) | | 按**功能码**查语义 (19 个 FC / 主站 API / 异常码触发条件) | [docs/03-功能码参考.md](docs/03-功能码参考.md) | | **裁剪体积 / 评估 RAM** (实例账 / 栈 / Flash / 66 个旋钮 / 复测命令) | [docs/04-资源与裁剪指南.md](docs/04-资源与裁剪指南.md) | | 查**某个问题改没改 / 版本兼容** (改进项 / 缺陷 / 欠账 / 版本历史) | [docs/05-改进综合与版本说明.md](docs/05-改进综合与版本说明.md) | > **数字只有一个家**: 体积 / RAM / 栈在 [docs/04-资源与裁剪指南.md](docs/04-资源与裁剪指南.md); > 帧布局在 `modbus_proto.h`; 配置旋钮在 `modbus_cfg.h`。本文不复制它们。 --- ## 一页事实 | 项 | 值 | |---|---| | 版本 | **1.0.1** —— 1.0.0 兼容线上的缺陷修复版 (见 [docs/05-改进综合与版本说明.md](docs/05-改进综合与版本说明.md) §5) | | 源码 | **20 个文件** = 9 个 `.c` + 11 个头 (10 个公共头 + 1 个私有头); 整合者只 `#include "modbus.h"` | | 角色 | 从站 **19 个功能码** + 主站事务引擎 | | 传输 | RTU / ASCII / TCP, 由 `MB_*_ENABLED` **编译期**选定, 实例存储是 `union` | | 依赖 | 仅 C99 标准库 (`stdint.h` `stddef.h` `string.h`); 无 RTOS / 芯片头 / 板级头 | | 分配 | 零动态分配; 实例由调用方按值持有 | | 可重入 | 组件内零可写静态量; 同实例上中断侧入口与线程侧入口可安全交错 | | 可替换边界 | 三条: 移植层回调表 / 校验接缝 (2 个弱符号) / 编译期裁剪 (68 个配置宏) | | 验证 | `python test/run_tests.py` → 末行须为 `门禁结果: PASS` | --- ## 特性 | 特性 | 说明 | |---|---| | 双角色 | 从站 19 个功能码 + 主站事务引擎; 同一 `modbus_t` 可只开其一 | | 三传输 | RTU (长度优先 + T3.5 兜底成帧) / ASCII (CR-LF + LRC) / TCP (MBAP) | | 完全可重入 | 组件内零可写静态量; 同实例上中断侧与线程侧入口可安全交错 | | 零动态分配 | 实例由调用方按值持有 (`static modbus_t` / 栈上 / 嵌入装配体); 多实例零共享 | | 空闲零中断 | 周期节拍由组件起停: 收全一帧即停, 无字节永不启用, 不烧空闲 CPU | | 双槽交付队列 | ISR 写 / 线程读的 SPSC 所有权模型; 读后释放, 消灭分派期覆写竞态 | | 编译即裁剪 | 传输层 / 功能码 / 主站 / 寄存器表全部 `#if` 包整段, 未编入的层不背代码不背缓冲 | | 弱符号 CRC | `modbus_crc16_accum` / `modbus_lrc_accum` 可被宿主强定义覆盖 (如硬件 CRC), 不改组件 | | 内置寄存器表 | `mbr_` 子系统: 四表描述 + 读写钩子 + 编解码 + 字序, 与协议栈零符号依赖, 可整块剥离 | | 可机检契约 | 门禁含编译矩阵 / 零可写静态量 / 移植层成员都有调用点 / 注释无悬空文件 / 领域头自足 / 声明单一归属 / 并发矩阵覆盖 / 功能码手册覆盖 | | 领域头自足 | 10 个公共头各自可单独 `#include` 编译; 每个宏与原型只有一个家 | --- ## 快速开始 (从站, RTU) ```c #include "modbus.h" static modbus_t g_mb; static const modbus_port_t g_port = { .fn_ser_init = my_uart_init, .fn_ser_close = my_uart_close, .fn_ser_enable = my_uart_dir, /* 半双工收发方向 */ .fn_ser_put_byte = my_uart_put, /* 必须非阻塞, 可在 ISR 调用 */ .fn_tmr_init = my_tmr_init, /* 周期节拍, 单位 50us */ .fn_tmr_close = my_tmr_close, .fn_tmr_enable = my_tmr_enable, /* 必须幂等 */ .fn_tmr_disable = my_tmr_disable, /* 必须幂等 */ .ctx = &my_hw, }; void app_init(void) { modbus_cfg_t cfg = { .mode = MB_MODE_RTU, .slave_addr = 1u, .baud = 9600u, .data_bits = 8u, .parity = MB_PAR_NONE, .stop_bits = 1u }; modbus_init(&g_mb, &g_port, &cfg); modbus_enable(&g_mb); } void app_loop(void) { modbus_poll(&g_mb); } /* 主循环: 每次最多一帧 */ void uart_isr(void) { modbus_byte_in(&g_mb, uart_read_byte()); } /* 接收中断 */ void tick_isr(void) { modbus_on_tick(&g_mb); } /* 周期节拍中断 */ ``` 寄存器后端二选一 (自写回调 / 内置 `mbr_` 表)、主站用法、ISR 与任务流程、RS485 方向控制、 时基契约、换 CRC 实现, 以及**常见的 10 个坑**, 见 [docs/02-快速接入与工程集成.md](docs/02-快速接入与工程集成.md)。 --- ## 想直接看它跑起来? [`example/`](example/README.md) 里有三个**主机上可编译可运行**的示例 (不需要硬件, 逐字节打印请求与应答): | 示例 | 教什么 | |---|---| | `example/modbus_example_slave_rtu.c` | 从站闭环: 读写 / 异常路径 / 地址门 / 空闲零中断 | | `example/modbus_example_master_rtu.c` | 主站事务: 组帧 / 解析 / 异常应答 / 超时重试 / 广播写 | | `example/modbus_example_regmap_slave.c` | 用声明式寄存器表 `mbr_` 喂协议栈 (权限 / 越界 / 钩子) | ```bash python test/run_tests.py --quick # 门禁会连同这三个示例一起编译并运行 ``` --- ## 目录 | 目录 / 文件 | 内容 | |---|---| | 组件根 `modbus_*.c` / `modbus_*.h` | **组件本体, 20 个文件**; 拷走组件时其余目录可不带 | | [`docs/`](docs/README.md) | 文档 5 篇 + 索引 (编号 = 阅读顺序) | | [`example/`](example/README.md) | 三个可运行示例 + 极简假移植层 | | [`test/`](test/README.md) | 回归用例 + 门禁 (`run_tests.py`) | --- ## 命名规范 | 对象 | 形式 | 例 | |---|---|---| | 公共函数 | `modbus_<动词>` 或 `modbus_<子域>_<动词>` | `modbus_poll` / `modbus_master_read` | | 从站 FC handler | `modbus_slave_fc_<规范名>` | `modbus_slave_fc_read_holding` | | 传输实现 (static) | `modbus__` | `modbus_rtu_receive` | | 寄存器表 | `mbr_<动词>` | `mbr_encode_words_ex` | | 类型 | `modbus_<名>_t` / `mbr_<名>_t` | `modbus_port_t` / `mbr_item_t` | | 枚举常量 | `MB_<组>_<值>`, 末元素为 `_MAX` 边界守卫 | `MB_MODE_RTU` | | 配置宏 | `MB_<域>_ENABLED` / `_MAX` / `_SIZE` | `MB_RTU_ENABLED` | | 协议常量 | 裸 `#define MB_<组>_<名>`, 值带 `u` 后缀 | `MB_FUNC_READ_COILS 0x01u` | | 输出参数 | `p_out_<名>` | `p_out_resp_len` | | 时基单位 | 一律 `_50us` 后缀 | `tick_50us` | 命名空间边界: `modbus_` / `MB_` 是协议栈本体, `mbr_` / `MBR_` 是寄存器表子系统 (它可整块剥离, 因此独立成族)。已登记的命名漂移与有意不做的取舍见 [docs/05-改进综合与版本说明.md](docs/05-改进综合与版本说明.md) §4。 --- ## 构建与测试 (门禁) ```bash python test/run_tests.py # 全量: 30 个裁剪配置 x -Os/-O2 + 10 组行为用例 + 静态扫描 + 风格/格式 + 示例 + 文档链接 python test/run_tests.py --quick # 只跑默认配置 (开发时用) python test/run_tests.py --cc D:/Programs/w64devkit/bin/gcc.exe ``` 末行须为 `门禁结果: PASS`。各类检查的逐条清单见 [docs/05-改进综合与版本说明.md](docs/05-改进综合与版本说明.md) §6。 --- ## License 组件结构参照 FreeMODBUS (BSD-3-Clause), 实现为独立重写。