(六)YModbus读写数据:线圈、离散输入、保持寄存器、输入寄存器

(六)YModbus读写数据:线圈、离散输入、保持寄存器、输入寄存器 GitHub 项目地址https://github.com/lidecong133/YModbusClient 创建好以后下一步就是选功能码、填地址、读数据。这一步看起来简单现场却经常出问题。很多设备手册不会直接告诉你“请用 03 功能码从地址 0 开始读”。它可能写40001也可能写Holding Register 1还有些只给一张寄存器表默认你懂它的习惯。所以读写前先把 Modbus 四类数据区分清楚。数据区常见用途读功能码标准写功能码YModbus 返回Coils可读写开关量0105/0Fbool[]Discrete Inputs只读开关量02无bool[]Holding Registers可读写寄存器0306/10ushort[]Input Registers只读寄存器04无ushort[]一句话记线圈和保持寄存器通常能写离散输入和输入寄存器通常只读。当然设备厂家不一定完全按名字设计业务。有些保持寄存器虽然理论上能写实际也可能只允许读。能不能写最终还是看设备手册和设备响应。线圈读写开关量线圈用来表示布尔状态常见的是输出点、启动位、复位位、报警清除位。读线圈bool[]coilsawaitclient.ReadCoilsAsync(0,8);写单个线圈awaitclient.WriteSingleCoilAsync(0,true);写多个线圈awaitclient.WriteMultipleCoilsAsync(startAddress:0,values:new[]{true,false,true,true});写线圈要小心。很多设备会把线圈映射成动作命令比如启动、停止、复位。你在调试软件里点一下设备那边可能真的动作。我的习惯是第一次接设备只读不写。等确认站号、地址、功能码都对再做写入测试。离散输入只读开关量离散输入也是布尔量但一般是只读。读离散输入bool[]inputsawaitclient.ReadDiscreteInputsAsync(0,8);它常见于这些状态急停输入限位开关光电信号设备就绪报警状态标准 Modbus 没有“写离散输入”的功能码。你如果在从站模拟器里改离散输入那是模拟器内部改值不是主站通过标准功能码写进去。这个区别要分清楚否则调试时会误以为主站能写所有状态。保持寄存器最常用也最容易踩坑保持寄存器用功能码03读取用06或10写入。读保持寄存器ushort[]registersawaitclient.ReadHoldingRegistersAsync(0,10);写单个保持寄存器awaitclient.WriteSingleRegisterAsync(100,123);写多个保持寄存器awaitclient.WriteMultipleRegistersAsync(startAddress:100,values:newushort[]{123,456,789});保持寄存器返回的是ushort[]每个元素就是一个 16 位寄存器。如果设备手册写40001 当前温度程序里不一定填40001。多数情况下40001是给人看的编号真正协议地址要填0。这就是 Modbus 最常见的地址差 1。第一次读保持寄存器时我建议这样试ushort[]valuesawaitclient.ReadHoldingRegistersAsync(0,1);先读一个。通了再扩大数量。不要上来就读一大段报错以后反而不好判断是起始地址错、数量太大还是跨了非法区域。输入寄存器读测量值很常见输入寄存器用功能码04。ushort[]registersawaitclient.ReadInputRegistersAsync(0,10);很多仪表会把温度、压力、流量、重量放在输入寄存器里。但也有设备把这些值放在保持寄存器。所以看到“测量值”不要直接猜功能码。手册写30001通常对应04写40001通常对应03但还是要看厂家说明。如果你用03读不到可以试04。反过来也一样。只要设备返回异常码或超时就回到功能码和地址表上查。地址一律按协议地址传YModbus 的 API 里地址都是协议地址也就是从0开始。比如设备手册写40001 当前速度 40002 当前压力程序里通常写ushort[]valuesawaitclient.ReadHoldingRegistersAsync(0,2);如果手册直接写地址 0 当前速度 地址 1 当前压力那程序里也从0开始。麻烦就在于不同厂家手册写法不统一。你要判断它给的是显示地址还是协议地址。读出来不对时不要马上怀疑 YModbus。先把地址基准确认清楚。一次读很多用分块方法Modbus 协议对一次读取数量有限制。保持寄存器一次最多读 125 个线圈一次也有数量限制。你要读几百个、上千个点位就应该拆成多次请求。YModbus 提供了分块辅助方法ushort[]registersawaitclient.ReadHoldingRegistersInBlocksAsync(startAddress:0,quantity:1000);写一大段保持寄存器也可以分块awaitclient.WriteHoldingRegistersInBlocksAsync(0,registers);这些方法适合参数备份、地址表导出、批量采集。不过现场第一次联调还是那句话先小范围读通再扩大。MultiUnitClient只是多了站号参数如果你用的是ModbusMultiUnitClient方法名基本一样只是第一个参数多了 UnitId / SlaveId。ushort[]unit1awaitclient.ReadHoldingRegistersAsync(1,0,10);ushort[]unit2awaitclient.ReadHoldingRegistersAsync(2,0,10);写入也是awaitclient.WriteSingleRegisterAsync(unitId:1,address:100,value:123);这种写法很适合 TCP 网关和 RS485 多站号轮询。读写前先核对这几件事我自己接设备时会先看这几项手册写的是哪类数据区功能码是01、02、03还是04地址是协议地址还是40001这种显示地址一次读的数量有没有超过设备支持范围这个地址到底能不能写写入会不会触发设备动作能读不代表能写。写成功也不代表设备业务一定执行了。有些设备写完参数还要保存命令有些设备必须在停机或远程模式下才接受。到这里YModbus 读写四类数据区对应的方法其实很直接ReadCoilsAsyncReadDiscreteInputsAsyncReadHoldingRegistersAsyncReadInputRegistersAsyncWriteSingleCoilAsyncWriteSingleRegisterAsyncWriteMultipleCoilsAsyncWriteMultipleRegistersAsync真正要花心思的不是记方法名而是把功能码、地址、数量、站号和设备手册对上。这些对上了读写代码反而很简单。