game4automation教程(三十六)ModbusTCP通讯的问题解析

勇哥注:

modbus通讯协议本身是可靠的,不然也不会发明起至今快50年了,还在自动化行业中广泛使用。

然而当应用各种第三方modbus组件编程的时候,由于组件的实现者、组件的应用者没有遵循标准来实现和应用,

就会造成各种通讯异常。

这就有点像一台pc机,硬件厂商能保证自己的硬件没有问题,然而架不住使用者乱装软件,最终还是可能会死机蓝屏。

使用modbus组件的使用者想怎么用就怎么用,没有章法,这是此问题的原因。


有关ModbusTCP的介绍及使用,见勇哥下面的教程:

Modbus Poll和Modbus slave软件应用(二)

http://47.98.154.65/?id=2528


Modbus Poll和Modbus slave软件应用(一)

http://47.98.154.65/?id=2494


ModbusTCP是主从模式的通讯架构,即Master/Slave。

game4automation实现了Slave从站。

Slave从站会公布自己的ip及端口号,若干个Master会连接它。


下图显示的是game4automation的Modubs组件参数面板。

可以看到没有任何设置。

原因是它的缺省值就是:ip为本机,可以是127.0.0.1,或者是本机的地址。

端口号为502。

端口号不可以改变。

image.png


master端通常由C#端使用某种ModbusTCP通讯组件来进行编程。

请使用github上开源的并且使用量较大的组件,例如EasyModbus。

开源很重要,不然通讯失败你都不知道是什么原因。

另外,不要牛B哄哄总想着自己去写,这里面水很深,不是一件容易的事。


勇哥重点说一下Master端访问要注意的内容:


(1)modbus的四个存储区(1.输入继电器、2.可读可写继电器、3.输入寄存器、4.可读可写寄存器),

请在程序里使用6个通信通道进行通讯(存储区1一个,2两个,3一个,4两个)。

原因是你一定会在多线程进通讯操作,用一个通道进行多个区的并发操作,死定了。


(2)对某通道信道操作的时候,你最好设计成队列方式,然后这个队列统一交给一个线程逐条处理这些通信请求

这是因为任何一次通道操作,都是有交互握手的过程,如果这个过程没完成,你再来一个,就会报下面这样的错误。

image.png

它实际上就是通讯阻塞了。

具体来说,就是两种写入的通道(继电器写入,寄存器写入)应该以这种队列来完成。

然后队列的追加操作内容的函数你应该把它Lock起来,这样就让调用的线程间进行了排队,有了先后次序。


(3)对于读的通道,设计一个线程专门定时读。

读的内容是4个通道:输入继电器、输入寄存器、可读可写继电器、可读可写寄存器。

读到的内容进行缓存。

任何读通道数据的api都设计成从缓存内容中获取数据。


(4)4个区的6个通道是可以并发进行操作的,这个没有问题。

因此读通道与写通道是可以并发的,不用互锁。


(5)以上设计中,由于读的线程的周期不固定,并且读线程和应用缓存数据的逻辑不在同一个周期内,因此要特别留意死等模式的逻辑判定。

这种模式是在一个死循环方式读一组缓存数据是否满足条件,如果满足就跳出,否则继续循环。

因为读线程和应用缓存数据的逻辑不在同一个周期内的原因,有可能对于“动作很快”的情况下,死循环里只有一次机会读到缓存数据才会退出,然而不幸的是读和应用者的周期不同,错过了瞬间的正确值后,就再也读不到退出条件的值了。

然后结果就是死循环成立,永远退不出去

然后由于这个死循环位于一个函数内,调用者每次调用都会卡在这个死循环中,函数其实是在反复重入而不能正常退出的,所以时间一长就会有内存与堆栈方面的问题。

另外还会造成一个现场,就是主逻辑卡死,不能继续。


因为这种原因,你得确保你的循环跳出条件是有机会多个执行周期内总有机会命中。

像那种起步时两个状态,停止时两个状态,它们是交替的,这一种逻辑判断往往是不可靠的,因为一但速度快了后,这种状态就是瞬间的过程,错过了就没有了。


总结一下:

多个通道之间可以并发,单个通道间的任何读写,只能一个完成后,再进行下一条指令。如果想做到这一点,可以考虑用队列方式来进行。


2025.6.7勇哥注:

引入ai的一个问答,很好的补充一下上面的内容。


modbus tcp中,4区(保持继电器)它应该是可读可写的是吧? 我的C#程序用的是easyModbus开源组件。然后我这边是Master端。 我一个线程写入它的地址:0-9, 然后另一个线程读取它的地址10-20 这个时候会有问题吗?还是说我的写与读必需交替而不能同时?


在 Modbus TCP 协议中处理保持寄存器(4区)的读写操作,结合使用 EasyModbus 开源组件及 C# 实现主站(Master)时,关键结论如下:

  1. 保持寄存器特性
    4区(保持寄存器)‌是可读可写的存储区‌,符合 Modbus 标准规范45

  2. 并发读写操作可行性
    当写入地址范围(0-9)与读取地址范围(10-20)完全无重叠时,理论上可安全并发操作‌,原因如下:

    • 地址独立‌:操作的目标寄存器物理地址互不冲突,无数据竞争风险78

    • 协议支持‌:Modbus TCP 允许主站发起独立读写请求,服务器端(如 PLC)能正确处理非重叠区域的并发访问23

  3. 线程安全要求
    尽管协议和地址无冲突,‌仍需显式同步线程‌,原因在于:

    • 库实现限制‌:EasyModbus 的 ModbusClient ‌非线程安全类‌,多线程共享同一实例可能导致内部状态(如 Socket 连接、请求队列)紊乱78

    • 资源竞争‌:未同步的并发调用可能引发 IOException 或数据包错乱(如写操作中断读响应解析)7

  4. 推荐的同步方案
    建议采用以下任一方式确保稳定性:

    csharpCopy Codeprivate readonly object _modbusLock = new object(); // 声明锁对象// 写线程示例void WriteThread(){    lock (_modbusLock)
        {
            modbusClient.WriteMultipleRegisters(0, writeValues); // 写入0-9
        }
    }// 读线程示例void ReadThread(){    lock (_modbusLock)
        {        int[] values = modbusClient.ReadHoldingRegisters(10, 10); // 读取10-20
        }
    }

    此举‌确保同一时间仅一个线程访问 ModbusClient‌,规避库内部竞争78


总结建议

  • ✅ ‌可读写性‌:4区保持寄存器支持读写操作45

  • ✅ ‌并发可行性‌:读写非重叠地址(0-9写 vs 10-20读)‌协议层面无冲突23

  • ⚠️ ‌强制同步‌:必须通过锁机制(如 lock)保护 ModbusClient 实例,解决库的线程安全问题78

  • ➕ ‌优化建议‌:若频繁操作,可合并读写请求(如批量写后读),减少锁竞争提升效率8

注:交替读写(如先写后读同一连接)非必需,重点在于‌地址隔离 + 实例同步‌。


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:
本帖最后由 勇哥,很想停止 于 2024-12-19 14:17:29 编辑

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2025年6月    »
1
2345678
9101112131415
16171819202122
23242526272829
30
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864