当前位置: 首页 > news >正文

(三)YModbus上手:先把寄存器读出来

GitHub 项目地址:https://github.com/lidecong133/YModbus

前面两篇先把为什么做 YModbus、Modbus 协议的基本概念讲了一遍。

这一篇开始写代码。

不用一上来就想着把所有功能码都用一遍,也不用急着研究各种高级封装。

工控调试里,第一步通常很简单:

我能不能连上设备,并且读到一组正确的寄存器?

只要这一步通了,后面写参数、读 float、批量轮询、多设备采集,都是在这个基础上往上加。

先记住几个参数

用 YModbus 读设备之前,先把下面几个东西搞清楚:

参数RTU 场景TCP 场景
连接方式串口,比如COM3IP 和端口,比如192.168.1.10:502
设备编号slaveIDunitId
功能码比如03读保持寄存器一样
起始地址协议地址,通常从0开始一样
数量要读几个线圈或寄存器一样

这里特别注意地址。

如果设备手册写的是40001,很多时候代码里要填的是0,不是40001

因为40001通常表示“保持寄存器区第 1 个点”,而不是报文里真的发送地址40001

从 RTU 开始

现场最常见的还是 Modbus RTU。

比如电脑通过 USB 转 RS485 接一个仪表,仪表站号是1,波特率9600,我们想读保持寄存器地址0开始的 4 个寄存器。

用 YModbus 大概就是这样:

usingSystem.IO.Ports;usingYModbus.Clients;usingYModbus.Serial;usingSerialPortport=new("COM3"){BaudRate=9600,DataBits=8,Parity=Parity.None,StopBits=StopBits.One,ReadTimeout=2000,WriteTimeout=2000};port.Open();byteslaveID=1;ushortstartAddress=0;ushortquantity=4;awaitusingModbusClientclient=ModbusSerialClientFactory.CreateRtu(slaveID,port,leaveOpen:true);ushort[]registers=awaitclient.ReadHoldingRegistersAsync(startAddress,quantity);foreach(ushortvalueinregisters){Console.WriteLine(value);}

这段代码做的事情其实很直白:

  1. 打开串口
  2. 创建 RTU client
  3. 用功能码03读取保持寄存器
  4. 拿到ushort[]结果

YModbus 里ReadHoldingRegistersAsync对应的就是功能码03

如果现场读不到,先不要急着改代码,先查这几件事:

  • 串口号是不是对的
  • 波特率是不是对的
  • 校验位是不是对的
  • slaveID是不是对的
  • 地址是不是应该从0开始
  • 设备是不是支持功能码03

很多现场问题,最后都是这些参数里有一个没对上。

写保持寄存器

读通以后,再考虑写。

比如从地址100开始写 3 个保持寄存器:

ushortstartAddress=100;ushort[]values=newushort[]{1,2,3};awaitclient.WriteMultipleRegistersAsync(startAddress,values);

这个方法对应功能码16,也就是0x10

如果只写一个寄存器,可以用:

awaitclient.WriteSingleRegisterAsync(100,123);

这个对应功能码06

写操作要比读操作谨慎。

读错了,大多数时候只是读不到或者数据不对。写错了,可能会把设备参数改掉,甚至影响现场动作。

所以刚开始调试时,我一般建议:

  • 先读
  • 再写不影响设备运行的测试地址
  • 确认设备手册里的写入范围
  • 确认写入值有没有单位和倍率

比如手册写“温度设定值,单位 0.1 ℃”,那你想写25.0 ℃,可能实际要写250

TCP 怎么写

Modbus TCP 的代码更短一些,因为不用自己配置串口参数。

比如连接127.0.0.1:1502,UnitId 是1

usingYModbus.Clients;awaitusingModbusClientclient=awaitModbusClientFactory.CreateTcpAsync(host:"127.0.0.1",port:1502,unitId:1);ushort[]registers=awaitclient.ReadHoldingRegistersAsync(0,4);foreach(ushortvalueinregisters){Console.WriteLine(value);}

这里再强调一下:

CreateTcpAsync创建的是 Modbus TCP client,它会主动连接对方的 TCP server。

这个 client/server 是 TCP 通讯关系,不要简单理解成主站/从站。

你在代码里填的unitId,更多是 Modbus 报文里的 Unit Identifier。直连普通 TCP 设备时,很多设备填1就可以;如果是 TCP 转 RTU 网关,它后面可能挂了多个 RTU 设备,这时unitId往往就对应后面的 RTU 站号。

一个 client 固定一个 UnitId

上面的ModbusClient有一个特点:创建时就指定了slaveIDunitId

也就是说,它适合这种场景:

我现在主要跟一个设备通讯。

比如:

awaitusingModbusClientclient=awaitModbusClientFactory.CreateTcpAsync("192.168.1.10",502,unitId:1);

后面这个client发出去的请求,默认都发给unitId = 1

如果你要在同一条连接里轮询多个 UnitId,可以用ModbusMasterClient

awaitusingModbusMasterClientmaster=awaitModbusClientFactory.CreateTcpMasterAsync("192.168.1.10",502);ushort[]unit1=awaitmaster.ReadHoldingRegistersAsync(1,0,10);ushort[]unit2=awaitmaster.ReadHoldingRegistersAsync(2,0,10);

这样每次调用时都可以传不同的 UnitId。

这在网关场景很有用。

读 float 和 int32

Modbus 寄存器本身是 16 位的。

但现场很多数据不是 16 位,比如:

  • int32
  • uint32
  • float
  • double

这种数据一般会占多个寄存器。

YModbus 里可以先读原始寄存器:

ushort[]registers=awaitclient.ReadHoldingRegistersAsync(0,2);

也可以用 typed helper 直接转成 .NET 类型:

usingYModbus.Clients;usingYModbus.Protocol;floattemperature=awaitclient.ReadHoldingRegisterSingleAsync(startAddress:0,wordOrder:ModbusWordOrder.HighWordFirst,byteOrder:ModbusByteOrder.BigEndian);

这里最容易出问题的是字节序和字顺序。

如果读出来的 float 特别离谱,比如一个温度读成了一个很大的数,先别怀疑设备坏了,优先查:

  • 高字在前还是低字在前
  • 每个寄存器内部是不是大端
  • 手册里有没有写 ABCD、CDAB、BADC、DCBA 这种顺序

YModbus 里用ModbusWordOrderModbusByteOrder来控制这件事。

RetryOptions 什么时候用

工业现场通讯不一定每次都很稳。

偶尔一次超时、设备忙、网关后面的设备没响应,都可能遇到。

YModbus 里可以在创建 client 时传ModbusRetryOptions

usingYModbus.Transports;ModbusRetryOptionsretryOptions=new(){RetryCount=2,RetryDelayMilliseconds=100};awaitusingModbusClientclient=awaitModbusClientFactory.CreateTcpAsync("192.168.1.10",502,unitId:1,retryOptions:retryOptions);

这里的RetryCount = 2,意思是第一次请求失败后,最多再重试 2 次。

重试不是万能的。

如果地址错了、功能码错了、站号错了,重试多少次都没用。

它更适合处理偶发性的通讯抖动。

先跑 samples

如果你刚拿到项目,不想一上来就写代码,可以先跑 sample。

TCP 可以先启动一个本地从站模拟:

dotnet run--project.\samples\YModbus.Sample.TcpSlave

再开一个终端读它:

dotnet run--project.\samples\YModbus.Sample.TcpClient--127.0.0.1 1502 1 0 4

RTU 需要真实串口设备,比如:

dotnet run--project.\samples\YModbus.Sample.RtuClient--COM3 9600 1 0 4

这几个参数分别是:

参数含义
COM3串口号
9600波特率
1slaveID
0起始地址
4读取数量

先把 sample 跑通,再复制里面的关键代码到自己的项目里,会比一开始就硬写省很多时间。

我建议的调试顺序

用 YModbus 调设备时,我建议按这个顺序来:

  1. 先确认通讯方式:RTU 还是 TCP
  2. RTU 先确认串口参数,TCP 先确认 IP 和端口
  3. 先读保持寄存器,不要一上来就写
  4. 手册写40001时,代码里优先试地址0
  5. 先读ushort[]原始值,再处理floatint32
  6. 原始值对了,再做单位、倍率、字节序转换
  7. 最后再考虑批量轮询、重试、异常处理

这样排查起来会比较稳。

很多时候不是库的问题,也不是设备的问题,而是地址、功能码、站号、字节序这些基础信息没有对齐。

写在最后

YModbus 的目标不是把 Modbus 包装得看不见。

我更希望它做两件事:

  • 常见读写操作写起来简单
  • 真出问题时,还能看得懂底层到底在干什么

所以你会看到它既有ReadHoldingRegistersAsync这种直接可用的方法,也保留了slaveIDunitId、功能码、寄存器地址这些 Modbus 里本来就很重要的概念。

对工控调试来说,这样反而更踏实。

因为现场最怕的不是代码多写几行,而是出了问题以后完全不知道从哪里查。

http://www.cnnetsun.cn/news/2894007.html

相关文章:

  • 制造型企业数据整合:图纸、BOM、订单的AI集成方案
  • 2026 大学生笔记本选购指南 | 预算 4000-5000 元档优选机型实测
  • 带图形界面的C# WebSocket服务端,支持实时连接监控与Unity3D通信调试
  • 2026实测!免费视频去水印工具推荐:好用的视频去水印软件有哪些?
  • 如何告别多软件混乱:OpenRGB统一控制所有RGB设备的终极指南
  • Springboot毕设项目:基于springboot和vue的校园二手书交易系统 (源码+文档,讲解、调试运行,定制等)
  • 5分钟掌握QKeyMapper:Windows最强开源改键工具,让游戏手柄秒变键鼠
  • 从在线聊天室到股票行情:手把手教你根据业务场景选对轮询策略(性能对比+避坑指南)
  • MSC8157ADS开发板实战:多核DSP调试与高速接口验证指南
  • 如何免费解锁B站4K视频下载:开源工具完全指南
  • NXP TJA1104:集成MACsec的汽车以太网PHY如何重塑车载网络安全
  • 告别界面困扰:Windows界面定制神器ExplorerPatcher完全指南
  • 技术多点开花 应用全面落地 武汉云克隆多因子检测技术领跑国内精准检测赛道
  • 艺学启航:深耕技能教育,以Python赋能学员职业新发展
  • 终极3DS格式转换指南:5分钟掌握.3ds到CIA的完整转换方案
  • 2026 完整版 GSC 使用手册:站点验证、收录监控、流量分析、AI 报表、技术排错全流程落地
  • NXP KM系列MCU:高精度测量系统的专用芯片选型与设计实战
  • CheatEngine-DMA插件:终极硬件级内存访问解决方案
  • 为什么你的音乐文件被锁定?深度解析音频解密解决方案
  • STS8200 PVI10 原理图
  • 嵌入式安全启动与密钥管理:基于NXP MCUXpresso工具的实战指南
  • 通用零部件来料材质证书智能把关,IACheck搭配AI报告审核通审Agent版比对订单与报告参数
  • 5分钟掌握B站缓存视频转换:m4s转MP4无损转换方案
  • 收藏必备!小白程序员轻松入门大模型:8阶段学习地图带你从零到精通AI Agent
  • 5个关键步骤:用Label Studio构建高效数据标注工作流
  • 如何用3个真实故事告诉你:douyin-downloader如何改变内容创作者的工作流
  • 深入解析MPC5668G/E汽车MCU:Power架构、双核设计及车载网络实战
  • OpenSSL 4.0.1发布:修复多个高危CVE漏洞,保障系统安全!
  • 无线基础设施DSP核心架构解析:从MSC8126看多核与硬件加速设计
  • 别再只记Payload了!深入理解Python对象继承链,让你的SSTI绕过思路更清晰