在破土而出的那刻,小草就做好了拼尽全力成长的准备,加油,永远热情向前! |
本文主要介绍了Modbus读取输出线圈的状态02H功能码的询问报文和响应报文的格式以及含义,也通过编码实现读取线圈状态示例,希望对你理解Modbus如何读取线圈状态有点帮助。欢迎点赞 + 关注,第一时间了解更多Modbus信息。 |
1、02H功能码干什么用?
1.1、一个问题:02H功能码是干什么用?
02H功能码用于读取从设备的离散输入(开关量),即DI(Discrete Input)的ON/OFF状态。根据消息帧格式,我们可以知道需要读取的离散输入寄存器的起始地址和数目,Modbus支持读取1-2000个连续的离散量输入状态。如果从设备接受主设备的请求则回复功能码02,并返回离散量且输入各变量的当前状态。如果返回的离散输入数量的个数不是8个整数倍,将用0填充最后的数据字节的剩余位。
2、02H功能码格式:
主站询问报文格式:
从站地址 | 功能码 | 起始地址(高位) | 起始地址(低位) | 线圈数量(高位) | 线圈数量(低位) | CRC |
---|---|---|---|---|---|---|
0x11 | 0x02 | 0x00 | 0xC4 | 0x00 | 0x1D | XXXX |
主站询问报文格式:【从站地址】+【功能码】+【起始地址(高位)】+【起始地址(低位)】+【线圈数量(高位)】+【线圈数量(低位)】+CRC ;
从站应答报文格式:
从站地址 | 功能码 | 字节计数 | 线圈状态20-27 | 线圈状态28-35 | 线圈状态36-43 | 线圈状态44-46 | CRC |
---|---|---|---|---|---|---|---|
0x11 | 0x02 | 0x04 | 0xCD | 0x6B | 0xB2 | 0x05 | XXXX |
从站应答报文格式:【从站地址】+【功能码】+【字节计数】+【线圈状态(根据实际确定数量)】+【CRC校验】
3、代码示例:
【注】Modbus串行链路是一个专题,有些通用方法,前面写过的不再在进行编写,请参考【03】之前的文章。
目的:我们是想实现读取线圈的输入状态,即是DI量的值。DI的开关量状态只有两种ON/OFF,对应我们现实中的开关就是true和false,对应bit位的值就是1和0,那就是根据报文具体要求读取字节中位的值。
3.1、在ModbusRTU实体类中创建读取线圈状态的方法
思路:读取线圈的状态:拼接报文===>>>发送报文===>>>接收报文===>>>判断,解析报文===>>>截取报文===>>>返回字节数组;
//读取线圈状态的方法:ReadCoilInputStatus
public byte[] ReadCoilInputStatus(int iDeviceAdd, int ininiAddress, int iReadLength)
{
//【01】发送的报文拼接,此处用到我们实体类中ByteArray中的方法
ByteArray SendCommand = new ByteArray();
SendCommand.Add(new byte[] { (byte)iDeviceAdd, 0x02, (byte)(iniAddress / 256), (byte)(iniAddress % 256) });
SendCommand.Add(new byte[] { (byte)(iReadLength / 256), (byte)(iReadLength % 256) });
SendCommand.Add(Crc16(SendCommand.array, 6));
//【02】发送报文 接收报文
int byteLength = 0;
if (iReadLength % 8 == 0)
{
byteLength = iReadLength / 8;
}
else
{
byteLength = iReadLength / 8 + 1;
}
//思考一下:为什么是5+byteLength===>>>【由返回报文的格式决定的】;
byte[] resByteArray = new byte[5 + byteLength];
if (SendData(SendCommand.array, ref resByteArray))
{
//【03】解析报文://验证:功能码+字节计数
if (resByteArray[1] == 0x02 && resByteArray[2] == byteLength)
{
//调用截取字节数据的方法
return BoolGetByteArray(resByteArray, 3, byteLength);
}
else
{
return null;
}
}
else
{
return null;
}
}
//截取字节数组的方法
private byte[] BoolGetByteArray(byte[] dest,int offset,int count)
{
byte[] res = new byte[count];
//我们的目标数组的长度一定要大于:偏移量+读取长度;
if (dest != null && dest.Length >= offset + count)
{
for (int i = 0; i < count; i++)
{
res[i] = dest[offset + i];
}
return res;
}
else
{
return null;
}
}
3.2、UI后台代码
// 在UI后台代码中的btn_Read_Click方法中我们修改相关读取线圈状态的相关代码:case VarType.Bit:
//创建读取线圈状态的字节数组===>>>改写了上次读取线圈输出状态的字节数组变量
byte[] stateresult = null;
switch (varType)
{
case VarType.Bit:
switch (storeArea)
{
case StoreArea.输入状态1x:
stateresult = objModbus.ReadCoilInputStatus(DevAdd, Address, Length);
break;
case StoreArea.输出线圈0x:
case StoreArea.输出寄存器4x:
case StoreArea.输入寄存器3x:
AddLog(1,"读取失败,存储区类型不正确");
return;
}
// binarystring ,最终二进制的结果====>>>>我们就是针对这个进行解析的
string binarystring = string.Empty;
if (stateResult != null)
{
foreach (var item in stateResult)
{
//将位数据进行颠倒,将字节转换成二进制,Padleft:是补足8位,不足的话用0代替;
char[] charvalue = Convert.ToString(item, 2).PadLeft(8, '0').ToCharArray();
Array.Reverse(charvalue);
binarystring += new string(charvalue);
}
AddLog(0, "读取成功,结果为:"+binarystring.Substring(0,Length));
}
else
{
AddLog(1,"读取失败,请检查地址、类型或者连接状态");
}
break;
4、调试结果:
作者:小羊,软件开发/技术写作者,欢迎关注,我们一起探讨有趣的编程历程。 |