一、Modbus从站标准状态定义
做Modbus从站开发,说白了就是管理好五个状态。
我刚开始接触这个协议时,总觉得状态机这东西太理论。后来在项目里吃过亏,才明白——状态没管好,通讯就会乱套。
今天咱们就把这五个状态掰开揉碎讲清楚。
1.1 空闲态(Idle State)
职责:等待主站发起请求。此时从站不处理任何报文,只监听总线。
转换条件:
- 检测到起始位(3.5字符静默时间后第一个字节)→ 进入接收态
- 如果总线持续空闲 → 保持空闲态
我个人的习惯:在空闲态里把接收缓冲区清空,避免上次残留数据干扰新报文。曾经有个项目,就是因为没清缓冲区,导致解析了半截旧数据,从站直接死机。
1.2 接收态(Receive State)
职责:逐字节接收主站发来的报文,同时做基础校验。
转换条件:
- 接收完成(收到完整帧,含CRC校验)→ 进入处理态
- 接收超时(超过1.5字符时间无新字节)→ 进入错误态
- 接收过程中发现帧长度异常 → 进入错误态
避坑指南:我曾经在接收态里只判断了CRC,没判断帧长度。结果有个设备发了超长报文,缓冲区溢出,把后面代码区都覆盖了。嗯,从那以后我规定:接收态必须同时检查长度和CRC。
1.3 处理态(Process State)
职责:解析报文、执行功能码、准备响应数据。
转换条件:
- 处理成功(正常响应)→ 进入发送态
- 处理失败(异常响应)→ 进入发送态(发送异常码)
- 处理过程中检测到致命错误(如内存访问越界)→ 进入错误态
你想想看,处理态其实是最容易出bug的地方。功能码解析、寄存器地址合法性检查、数据边界判断……哪一步漏了都可能出问题。
注意:处理态里不要做耗时操作!比如写EEPROM、等待传感器采样。我见过有人把延时函数写在这里,结果主站超时重发,从站直接卡死。处理态应该只做数据组装,真正的IO操作放到任务调度里去做。
1.4 发送态(Transmit State)
职责:将响应帧(正常或异常)发送到总线上。
转换条件:
- 发送完成 → 进入空闲态
- 发送过程中检测到总线冲突(RS485半双工)→ 进入错误态
发送态有个细节容易被忽略:发送完成后要等待3.5字符时间再切回空闲态。为什么?因为要保证帧间隔,防止主站把两帧当成一帧处理。
我建议:发送态里用硬件自动CRC生成,别自己算。以前我手算CRC,结果算错了一位,主站一直收不到正确响应,排查了整整两天。
1.5 错误态(Error State)
职责:处理异常情况,恢复系统到安全状态。
转换条件:
- 错误恢复完成(如超时复位、缓冲区清理)→ 进入空闲态
- 连续错误超过阈值(如3次)→ 保持错误态,等待手动复位
警告:错误态里不要无限循环!我曾经犯过这个错:接收超时后进入错误态,然后在错误态里死等。结果主站发了三次请求都没回应,直接标记从站离线。正确的做法是:错误态里设置一个超时计数器,超时后自动回到空闲态。
1.6 状态转换总览
下面这张图是我用SVG画的,把五个状态和转换条件串起来了。你看一眼就能明白整体逻辑。
1.7 状态转换表
为了方便你对照开发,我把转换条件整理成了表格:
| 当前状态 | 触发事件 | 下一状态 | 备注 |
|---|---|---|---|
| 空闲态 | 检测到起始位 | 接收态 | 同时启动接收超时定时器 |
| 接收态 | 接收完成(含CRC校验通过) | 处理态 | 停止接收超时定时器 |
| 接收态 | 接收超时/帧长度异常 | 错误态 | 记录错误码 |
| 处理态 | 处理成功/处理失败(异常码) | 发送态 | 组装响应帧 |
| 处理态 | 致命错误(如内存越界) | 错误态 | 立即停止处理 |
| 发送态 | 发送完成 | 空闲态 | 等待3.5字符间隔 |
| 发送态 | 总线冲突 | 错误态 | RS485半双工时发生 |
| 错误态 | 恢复完成(超时/手动复位) | 空闲态 | 清空所有缓冲区 |
小技巧:实际编码时,我习惯用枚举定义状态:
typedef enum {
STATE_IDLE, // 空闲态
STATE_RECEIVE, // 接收态
STATE_PROCESS, // 处理态
STATE_TRANSMIT, // 发送态
STATE_ERROR // 错误态
} ModbusState;
这样switch-case写起来清晰,也方便调试时打印状态值。
好了,五个状态的定义和转换条件就讲完了。记住一句话:状态机是Modbus从站的骨架,骨架歪了,功能再多也白搭。
公众号:蓝海资料掘金营,微信deep3321