简介:这份PDF面向工业自动化与通信技术领域的技术开发人员,围绕欧姆龙小型可编程控制器的通信技术展开问答式讲解,适合需要查阅产品选型、程序解密与老旧设备兼容方案的工程师作为专业参考。内容涵盖P型机、CPM型机、CQ1V/H型机及CJ1V型机的分类与结构特点,并针对程序加密解密、C40P等旧型号通过适配器与计算机通信、手持编程器型号匹配以及SSS、CPT、CX-PROGRAMMER等编程软件的适用范围逐一解答,还涉及间隔定时中断等应用细节。资源包共1个PDF文件,大小约103KB,轻量便于随时查阅。目前已有48人学习,内容以问答形式组织,条理清晰,可帮助读者快速定位具体技术问题,掌握欧姆龙小型PLC通信与编程的实用排错思路。
1. 小型可编程序控制器通信技术的问答(六):串口轮询为什么总在第三帧丢包
如果你手头有一台小型可编程序控制器,用 RS485 总线挂了三五台从站,程序跑起来前两帧数据都对,第三帧开始随机丢包或者校验失败——这个场景我见过太多次了。小型可编程序控制器通信技术里,串口轮询是最基础也最容易翻车的一环,而“问答(六)”这个编号本身就暗示了前面已经聊过物理层接线、波特率配置、站号分配这些前置话题,轮到第六篇,该啃的是轮询时序和帧间隔这个硬骨头了。
这篇文章面向的是已经能把线接对、能点亮通信指示灯,但数据就是不稳定的一线调试人员。我会把小型可编程序控制器通信技术中轮询机制的时序逻辑拆开,给出可复现的参数配置和排查路径,让你从“能通”走到“稳定通”。不聊虚的,直接看帧与帧之间那几毫秒到底发生了什么。
2. 轮询时序的底层逻辑:从请求帧到响应帧的完整链路
2.1 主站轮询的状态机到底在等什么
小型可编程序控制器做 Modbus RTU 主站时,轮询逻辑本质上是一个状态机:发送请求帧 → 等待响应 → 校验响应 → 处理数据 → 等待帧间隔 → 发送下一站请求。问题往往出在“等待响应”和“等待帧间隔”这两个状态的切换上。
很多入门级 PLC 的通信指令是“发送并等待”的阻塞模式,比如某类小型 PLC 的 RS 指令,发完一帧后程序就卡在那里等从站回复。如果从站因为处理内部逻辑慢了半拍,主站的等待超时设得太短,就会直接判超时,然后跳到下一站。但此时从站的响应帧可能已经在路上了,等主站发下一站请求时,总线上就出现了两帧数据碰撞——这就是第三帧开始丢包的典型原因。
正确的做法是把轮询拆成非阻塞状态机:发送请求后立刻返回,用定时器中断或者高速计数器来管理超时,收到完整响应帧后再触发下一轮。这样主站不会因为某一站的延迟而打乱整个轮询周期。
2.2 帧间隔时间为什么不能拍脑袋设
Modbus RTU 规范里,帧间隔(t3.5)的定义是 3.5 个字符时间。在 9600 bps 下,一个字符是 11 位(1 起始位 + 8 数据位 + 1 校验位 + 1 停止位),所以一个字符时间约 1.146 ms,t3.5 就是 4 ms 左右。但这是理论值,实际工程中我一般会留 2 到 3 倍余量。
为什么?因为小型可编程序控制器的串口中断响应有延迟,从站设备的单片机处理 Modbus 帧也有延迟。如果你严格按 4 ms 设,主站可能在从站还没完全释放总线时就发了下一帧,导致从站把新帧的起始位误判为上一帧的延续。表现就是:前两帧正常,第三帧开始校验错误率飙升。
我常用的参数是:9600 bps 下帧间隔设 10 到 15 ms,19200 bps 下设 5 到 8 ms,38400 bps 下设 3 到 5 ms。这个值不是固定的,需要用示波器或者串口分析仪抓一次实际波形来微调。
2.3 用状态机重写轮询逻辑的最小代码框架
下面这段伪代码展示了一个非阻塞轮询状态机的核心结构,用 C 语言风格描述,方便移植到各类小型 PLC 的 ST 或梯形图环境中。
// 轮询状态机核心变量 typedef enum { STATE_IDLE, // 空闲,等待启动 STATE_SEND_REQ, // 发送请求帧 STATE_WAIT_RESP, // 等待响应 STATE_PROCESS, // 处理响应数据 STATE_GAP // 帧间隔等待 } PollState; PollState pollState = STATE_IDLE; uint8_t currentSlave = 1; // 当前轮询站号 uint16_t timeoutCounter = 0; // 超时计数器 uint16_t gapCounter = 0; // 帧间隔计数器 uint16_t timeoutLimit = 50; // 超时阈值,单位:ms uint16_t gapLimit = 12; // 帧间隔阈值,单位:ms void pollStateMachine(void) { switch (pollState) { case STATE_IDLE: currentSlave = 1; pollState = STATE_SEND_REQ; break; case STATE_SEND_REQ: sendModbusRequest(currentSlave, 0x03, 0x0000, 0x000A); timeoutCounter = 0; pollState = STATE_WAIT_RESP; break; case STATE_WAIT_RESP: if (isResponseComplete()) { // 收到完整帧,进入处理 pollState = STATE_PROCESS; } else if (++timeoutCounter >= timeoutLimit) { // 超时,跳过该站 logTimeout(currentSlave); currentSlave++; if (currentSlave > MAX_SLAVE) currentSlave = 1; gapCounter = 0; pollState = STATE_GAP; } break; case STATE_PROCESS: if (verifyCRC()) { parseResponseData(); } else { logCRCError(currentSlave); } currentSlave++; if (currentSlave > MAX_SLAVE) currentSlave = 1; gapCounter = 0; pollState = STATE_GAP; break; case STATE_GAP: if (++gapCounter >= gapLimit) { pollState = STATE_SEND_REQ; } break; } }这段代码的关键点在于:STATE_WAIT_RESP里用timeoutCounter做超时判断,而不是死等;STATE_GAP里用gapCounter强制插入帧间隔,保证总线空闲时间足够。timeoutLimit和gapLimit这两个参数需要根据实际波特率和从站数量调整,我一般会先用保守值跑通,再用示波器逐步收紧。
提示:如果你的 PLC 不支持这种细粒度的状态机编程,可以用定时器中断来驱动状态切换,把
pollStateMachine放在 1 ms 周期的中断服务程序里调用。
3. 参数配置实战:波特率、超时与重试次数的组合调优
3.1 波特率不是越高越好
很多新手觉得波特率越高通信越快,直接把小型可编程序控制器的串口设到 115200 bps。结果线一长、从站一多,误码率直接爆炸。RS485 总线的传输距离和波特率是反比关系:9600 bps 下可以跑 1200 米,115200 bps 下超过 100 米就开始不稳定。
我的建议是:从站数量少于 5 台、总线长度小于 50 米,可以用 38400 bps;超过 5 台或者线长超过 100 米,老老实实降到 9600 bps。通信周期慢一点没关系,稳定比快重要。下面这张表是我在不同场景下的常用配置组合。
| 场景 | 从站数量 | 总线长度 | 推荐波特率 | 帧间隔 | 超时阈值 | 重试次数 |
|---|---|---|---|---|---|---|
| 小型产线 | 3 台 | < 30 m | 38400 bps | 4 ms | 30 ms | 2 |
| 中型车间 | 8 台 | < 100 m | 19200 bps | 8 ms | 50 ms | 3 |
| 长距离采集 | 12 台 | < 500 m | 9600 bps | 15 ms | 80 ms | 3 |
| 高干扰环境 | 5 台 | < 50 m | 9600 bps | 20 ms | 100 ms | 5 |
3.2 超时阈值怎么算才不拍脑袋
超时阈值的计算公式是:超时阈值 = 帧传输时间 + 从站处理时间 + 余量。帧传输时间取决于波特率和帧长度,从站处理时间需要查手册或者实测。
以 9600 bps、请求 8 字节、响应 25 字节为例:请求帧传输时间约 9.2 ms,响应帧传输时间约 28.6 ms,从站处理时间假设 10 ms,余量取 20 ms,那么超时阈值至少设 70 ms。如果你设了 30 ms,从站稍微忙一点就超时了。
实测方法很简单:在主站发送请求后,用串口分析仪记录从站响应的时间戳,连续抓 100 次,取最大值再乘以 1.5 作为超时阈值。这个值比任何理论计算都靠谱。
3.3 重试次数与轮询周期的平衡
重试次数设多少?我的经验是 2 到 3 次。设 1 次,偶发干扰会导致数据丢失;设 5 次以上,一旦某站故障,整个轮询周期会被拖垮。
假设你有 8 台从站,每站超时 50 ms,重试 3 次,那么单站故障就会占用 150 ms。如果轮询周期原本是 200 ms,直接变成 350 ms,上位机可能就判通信中断了。所以重试次数要和轮询周期一起算:最坏轮询周期 = 从站数量 × (正常响应时间 + 超时阈值 × 重试次数)。这个值必须小于上位机的通信超时设置。
# 轮询周期估算脚本 slave_count = 8 normal_response_ms = 30 timeout_ms = 50 retry_times = 3 worst_cycle = slave_count * (normal_response_ms + timeout_ms * retry_times) print(f"最坏轮询周期: {worst_cycle} ms") # 输出: 最坏轮询周期: 1440 ms # 如果上位机超时设 1000 ms,就需要降低重试次数或超时阈值这段脚本用来快速验证参数组合是否合理。如果算出来的最坏周期超过上位机容忍范围,优先降重试次数,其次降超时阈值,最后才考虑降波特率。
4. 避坑与排查:第三帧丢包的五个血泪教训
4.1 现象:前两帧正常,第三帧校验错误
原因:主站发送下一帧请求时,从站的响应帧还没完全发送完毕,总线上出现帧重叠。这通常是因为帧间隔设得太短,或者主站没有等待从站释放总线。
解决:把帧间隔从 4 ms 增加到 10 ms 以上,用示波器抓一次总线波形,确认两帧之间有明显的空闲电平。如果从站响应本身就很慢,还需要增加超时阈值。
4.2 现象:轮询到某一站就卡死,后续站全部超时
原因:该从站可能处于故障状态,一直拉低总线或者发送乱码,主站陷入无限等待。如果超时阈值设得过大,或者重试次数过多,就会卡死。
解决:检查该从站的电源和接线,确认没有硬件故障。同时在主站程序里加一个“最大重试次数”限制,超过后直接跳过该站并记录故障,不要无限重试。
4.3 现象:通信指示灯闪烁正常,但数据偶尔跳变
原因:RS485 总线的 A/B 线没有加终端电阻,或者终端电阻阻值不对。信号反射导致数据位采样错误,表现为偶发的数据跳变。
解决:在总线两端各加一个 120 欧姆的终端电阻。如果总线长度小于 10 米,可以不加;超过 50 米必须加。用万用表测 A/B 之间的电阻,正常应该在 60 欧姆左右(两个 120 欧姆并联)。
4.4 现象:白天正常,晚上丢包率升高
原因:夜间电网负载变化导致电源波动,或者附近有变频器、大功率设备在夜间启动,引入电磁干扰。
解决:给 PLC 和从站加装隔离电源或者滤波器。RS485 线使用屏蔽双绞线,屏蔽层单端接地。如果干扰特别严重,可以考虑改用光纤转换器。
4.5 现象:更换从站设备后,原来正常的程序开始丢包
原因:不同厂家的从站设备,响应时间差异很大。原来的程序针对旧设备的响应时间调好了参数,换新设备后响应变慢,原来的超时阈值不够用了。
解决:每次更换从站设备后,重新实测响应时间,调整超时阈值和帧间隔。不要假设所有 Modbus 设备的响应速度都一样。
5. 进阶技巧:用示波器抓一次轮询波形,比读十遍手册都管用
如果你手头有示波器或者带协议解码功能的逻辑分析仪,我强烈建议你抓一次完整的轮询波形。这是排查通信问题最直接的手段,没有之一。
具体操作:把探头接到 RS485 的 A/B 线上,设置触发条件为“下降沿触发”,时间基准设 10 ms/格。然后让 PLC 跑一轮完整的轮询,你会看到一串脉冲。放大看每一帧的起始位和停止位,重点观察三个地方:帧与帧之间的空闲时间是否足够、响应帧的起始位是否干净、有没有毛刺或者振铃。
我自己的习惯是:每次调试新的通信方案,先抓波形确认时序,再写程序。程序写完后,再抓一次波形验证。两次波形对比,能发现很多肉眼看不到的问题。比如有一次我发现从站的响应帧末尾有一个额外的 2 ms 低电平,导致主站误判为下一帧的起始位。后来在程序里把帧间隔从 8 ms 加到 15 ms 才解决。
对于没有示波器的场景,可以用串口分析仪代替。把分析仪串在总线里,设置过滤条件为“只显示错误帧”,跑一个小时,看看错误帧的分布规律。如果错误集中在某个时间段或者某个站号,就能快速定位问题。
最后说一个我踩过的坑:曾经为了省事,把帧间隔设成 0,靠从站的响应时间来自然分隔。结果在从站数量增加到 6 台后,第三帧开始必丢。后来老老实实加了 12 ms 帧间隔,问题消失。这个参数不能省,省了就是给自己埋雷。
希望帮到你。
本文还有配套的精品资源,点击获取