跳转到内容
新建笔记

Modbus主站事务:私有API、轮询与命令寿命

主站调用的核心是管理一笔完整事务:准备请求、获得总线、发送、等待匹配响应、处理异常或超时,再把结果交给业务。函数返回、收到任意帧、寄存器写回成功和电机实际完成动作,是不同的完成条件。

本文合并两份 mb_master 工程记录。原库源码与版本尚未提供,以下函数名、缓冲区及 ack_* 标志均是历史私有接口记录,不能当成 FreeModbus 或所有 Modbus 库的统一 API。

项目原记录需要核对的契约
串口/方向UART1、RS485使能脚引脚、收发器、DE极性、最后停止位后的切换时刻
波特率mb_port.h 中38400实际数据位、校验、停止位以及双方配置
模块mb_master.c/.h、mb_port.c/.h、mb_reg.c/.h文件名暗示职责,仍须阅读具体源码
初始化HAL_Init、时钟、GPIO、DMA、USART1DMA仅在该工程确实采用时启用;还须完成库自身初始化
推进主循环持续调用 mb_master_poll()它是否负责解帧、超时、发送和回调
完成标志ack_03h、ack_04h、ack_10h 等是有效响应、接收事件还是提交成功;何时清除

另一份控制接口笔记使用 USART2/115200,是不同项目配置,不应拼接成同一工程的确定设置。

2. 八个功能接口与标准含义

跳转到“2. 八个功能接口与标准含义”
历史调用名称标准功能码操作
mb_master_01h(id, start, n)0x01读线圈
mb_master_02h(id, start, n)0x02读离散输入
mb_master_03h(id, start, n)0x03读保持寄存器
mb_master_04h(id, start, n)0x04读输入寄存器
mb_master_05h(id, addr, value)0x05写单线圈
mb_master_06h(id, addr, value)0x06写单个保持寄存器
mb_master_0fh(id, start, n, data)0x0F,十进制15写多个线圈
mb_master_10h(id, start, n, data)0x10,十进制16写多个保持寄存器

保持寄存器并不天然等于模拟输出,输入寄存器也不必来自物理模拟输入;内容由应用定义。API是否接受布尔值还是0xFF00/0x0000、多线圈缓冲区如何按位打包、数量单位、返回值和最大长度,都应从实际头文件与实现确认。应用协议

3. 三个原示例的正确阅读方式

跳转到“3. 三个原示例的正确阅读方式”

以下为历史调用片段,缺少实际库,不能独立编译运行。

/* 从站1:从保持寄存器地址0读10个字。 */
mb_master_03h(1, 0x0000, 10);
/* 从站1:从0x0010起写入五个16位值。 */
static uint16_t write_data[5] = {100, 200, 300, 400, 500};
mb_master_10h(1, 0x0010, 5, write_data);
/* 从站1:从输入寄存器0x0100起读两个字。 */
mb_master_04h(1, 0x0100, 2);

原稿分别在轮询后观察 ack_03h、ack_10h 和 ack_04h,处理一次后清零。保留这个历史用法,但必须先确认标志只会对当前事务的合法响应置位;清除旧标志不能代替匹配请求。

原读取缓冲区记录如下:

功能历史缓冲区名不能直接假设的事项
读线圈mb_coil_buf[]每元素是一位还是一字节;是否位打包
读离散输入mb_discrete_buf[]位序、有效数量与更新范围
读保持寄存器mb_hold_buf[]绝对地址下标还是本次请求从0开始
读输入寄存器mb_input_buf[]站号隔离、稀疏映射、字节序及陈旧状态

原温湿度示例把 0x0100 当成温度、0x0101 当成湿度,并将温度除以10。这是示例设备假设,不是 Modbus 定义。真实温度可能有负值,需确认补码、有符号位宽和比例系数;使用紧凑映射后不能继续直接用 buffer[0x0100]。

4. 一秒轮询必须有忙碌状态

跳转到“4. 一秒轮询必须有忙碌状态”

下面是与具体库无关的组织逻辑。需要把“提交、完成、异常、超时”映射到真实库接口后才能实现。

初始化硬件、主站与业务状态
每次主循环:
推进主站状态机,处理收到的字节与计时事件
若当前事务完成:验证/复制结果,释放事务所有权
若异常或超时:记录原因,执行结束/恢复流程
若总线可用、无未完成事务且距离上次提交达到1秒:
准备从站1、功能03、起始0、数量10的请求
若库接受请求:记录提交时间和预期响应,进入等待状态
执行其他非阻塞业务

用 uint32_t 毫秒计数时,可用无符号差值处理一次计数回绕,但任务必须足够频繁地运行,不能跨过一个完整计数周期仍指望差值保留真实时间。更重要的是:计时到期不等于上一事务完成,不能每秒无条件覆盖正在使用的请求结构。

响应处理应核对站号、功能码/异常功能码、长度及读响应的字节计数;写响应核对地址和数量/值。帧校验失败、设备异常和无响应超时分别记录。多个调用者应经过同一个串行调度器,RTU总线上同一时刻只保持一笔未完成事务。

RTU帧没有应用可任意填写的事务序号。内部递增编号可以保护本地回调,却不能使迟到的相同站号/功能码响应自动带上该编号;超时后的收发清理、静默间隔和下一请求时机仍需要驱动与协议状态机配合。

5. 写请求寿命与重复触发

跳转到“5. 写请求寿命与重复触发”

旧短稿每轮先 mb_master_poll(),再调用 mb_master_10h(2, 0xA864, 2, ...),然后阻塞5秒。需要分别处理三个问题:

  1. 5秒阻塞可能停止接收状态机与超时处理,应改为非阻塞计时。
  2. 无条件重复写入可能反复触发动作。原注释称地址为“启动”,但没有设备手册,不能把0xA864作为通用启动命令发布。
  3. 异步库若只借用数组指针,缓冲区必须保持有效且内容不变,直到发送/事务真正不再访问它。局部数组、C复合字面量的作用域及C++支持差异都需核对。

超时也不必然意味着底层DMA已经停止访问发送缓冲区。释放或重用前,应确认驱动取消/完成契约。static只解决存储寿命,并不阻止下一请求提前改写同一数组。

对读取请求和某些“写入同一目标值”的请求,可以按设备语义设置有限重试;相对运动、计数递增、一次性启动等命令不能默认无害重试。应让应用保存命令状态,并区分“写响应成功”“设备接受命令”“动作完成”。

6. 落地时保存哪些证据

跳转到“6. 落地时保存哪些证据”

保存实际库版本/源码、串口设置、设备寄存器手册、请求与响应十六进制、超时门限和一次异常记录。先验证只读访问,再验证参数写入及读回,最后在受控条件下联调动作。未读到返回值定义前,不把私有函数的返回当成响应成功。

本次重整没有连接设备或编译未知库;独立的CRC与映射算法在各自笔记中验证。相关:CRC计算、地址映射、FreeModbus从站移植。

原始记录:主站使用示例、写多个寄存器调用记录。