说到工控,通讯则是基础。今天我们就来一起学习一下关于ModbusRtu通讯的相关内容。适合初学者积累相关经验。
首先说明,Modbus通讯协议支持串口通讯、和网口通讯。本文主要是基于串口通讯的Modbus例程。
第一步,创建通讯库,命名未CHHModbusRtu(各位可自行定义)。在通讯库中创建串口对象,并进行相关设置,创建启动串口、关闭串口的方法。
private SerialPort MyCom;
public int RcvTimeOut { get; set; } = 2000;
public void Connect(string iPortName, int iBaudRate, Parity iParity, int iDataBits, StopBits iStopBits)
{
if (MyCom == null || !MyCom.IsOpen)
{
MyCom = new SerialPort(iPortName, iBaudRate, iParity, iDataBits, iStopBits);
MyCom.Open();
}
}
public void DisConnect()
{
if (MyCom.IsOpen)
{
MyCom.Close();
}
}
第二步:创建输入输出线圈的读取方法、输入输出寄存器的读取方法
///
/// 读取输出线圈
///
/// 主站/从站地址
/// 开始地址
/// 读取长度
///
public byte[] ReadOutputStatus(byte iDevAdd, ushort iAddress, ushort iLength)
{
//拼接报文
List
SendCommand.Add(iDevAdd);
SendCommand.Add(0x01);
SendCommand.Add((byte)(iAddress / 256));
SendCommand.Add((byte)(iAddress % 256));
SendCommand.Add((byte)(iLength / 256));
SendCommand.Add((byte)(iLength % 256));
byte[] crc = Crc16(SendCommand.ToArray(), SendCommand.Count);
SendCommand.AddRange(crc);
byte[] response = null;
int byteLength = iLength % 8 == 0 ? iLength / 8 : iLength / 8 + 1;
if (SendAndReceive(SendCommand.ToArray(), ref response))
{
//验证
if (response.Length == 5 + byteLength)
{
if (response[0] == iDevAdd && response[1] == 0x01 && response[2] == byteLength && CheckCRC(response))
{
return GetByteArray(response, 3, response.Length - 5);
}
else { return null; }
}
else { return null; }
}
else { return null; }
}
///
/// 读取输入线圈
///
/// 从站地址
/// 开始地址
/// 读取长度
///
public byte[] ReadInputStatus(byte iDevAdd, ushort iAddress, ushort iLength)
{
//拼接报文
List
SendCommand.Add(iDevAdd);
SendCommand.Add(0x02);
SendCommand.Add((byte)(iAddress / 256));
SendCommand.Add((byte)(iAddress % 256));
SendCommand.Add((byte)(iLength / 256));
SendCommand.Add((byte)(iLength % 256));
byte[] crc = Crc16(SendCommand.ToArray(), SendCommand.Count);
SendCommand.AddRange(crc);
byte[] response = null;
int byteLength = iLength % 8 == 0 ? iLength / 8 : iLength / 8 + 1;
if (SendAndReceive(SendCommand.ToArray(), ref response))
{
//验证
if (response.Length == 5 + byteLength)
{
if (response[0] == iDevAdd && response[1] == 0x02 && response[2] == byteLength && CheckCRC(response))
{
return GetByteArray(response, 3, response.Length - 5);
}
else { return null; }
}
else { return null; }
}
else { return null; }
}
///
/// 读取输出寄存器
///
/// 从站地址
/// 开始地址
/// 读取长度
///
public byte[] ReadOutPutReg(byte iDevAdd, ushort iAddress, ushort iLength)
{
//拼接报文
List
SendCommand.Add(iDevAdd);
SendCommand.Add(0x03);
SendCommand.Add((byte)(iAddress / 256));
SendCommand.Add((byte)(iAddress % 256));
SendCommand.Add((byte)(iLength / 256));
SendCommand.Add((byte)(iLength % 256));
byte[] crc = Crc16(SendCommand.ToArray(), SendCommand.Count);
SendCommand.AddRange(crc);
byte[] response = null;
int byteLength = iLength * 2;
if (SendAndReceive(SendCommand.ToArray(), ref response))
{
//验证
if (response.Length == 5 + byteLength)
{
if (response[0] == iDevAdd && response[1] == 0x03 && response[2] == byteLength && CheckCRC(response))
{
return GetByteArray(response, 3, response.Length - 5);
}
else { return null; }
}
else { return null; }
}
else { return null; }
}
///
/// 读取输入寄存器
///
/// 从站地址
/// 开始地址
/// 读取长度
///
public byte[] ReadInputReg(byte iDevAdd, ushort iAddress, ushort iLength)
{
//拼接报文
List
SendCommand.Add(iDevAdd);
SendCommand.Add(0x04);
SendCommand.Add((byte)(iAddress / 256));
SendCommand.Add((byte)(iAddress % 256));
SendCommand.Add((byte)(iLength / 256));
SendCommand.Add((byte)(iLength % 256));
byte[] crc = Crc16(SendCommand.ToArray(), SendCommand.Count);
SendCommand.AddRange(crc);
byte[] response = null;
int byteLength = iLength * 2;
if (SendAndReceive(SendCommand.ToArray(), ref response))
{
//验证
if (response.Length == 5 + byteLength)
{
if (response[0] == iDevAdd && response[1] == 0x04 && response[2] == byteLength && CheckCRC(response))
{
return GetByteArray(response, 3, response.Length - 5);
}
else { return null; }
}
else { return null; }
}
else { return null; }
}
第三步:创建发送指令方法
///
/// 发送指令
///
///
///
public byte[] WriteData(string str)
{
byte[] getbyte = ConvertHexStringToBytes(str);
//拼接报文
List
SendCommand.AddRange(getbyte);
byte[] crc = Crc16(SendCommand.ToArray(), SendCommand.Count);
SendCommand.AddRange(crc);
byte[] response = null;
if (SendAndReceive(SendCommand.ToArray(), ref response))
{
if (CheckCRC(response))
{
return GetByteArray(response, 3, response.Length - 5);
}
else { return null; }
}
else { return null; }
}
第四步:补充RCR校验、数据处理等方法(源码太多,此处复制一小部分,文末有源码链接)
///
/// 生成CRC
///
/// 字节组
/// 字节组长度
///
private byte[] Crc16(byte[] pucFrame, int usLen)
{
int i = 0;
byte[] res = new byte[2] { 0xFF, 0xFF };
UInt16 iIndex = 0x0000;
while (usLen-- > 0)
{
iIndex = (UInt16)(res[0] ^ pucFrame[i++]);
res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);
res[1] = aucCRCLo[iIndex];
}
return res;
}
///
/// 校验CRC
///
///
///
private bool CheckCRC(byte[] response)
{
byte[] crc = Crc16(response, response.Length - 2);
if (crc[0] == response[response.Length - 2] && crc[1] == response[response.Length - 1]) return true;
else return false;
}
最后就是进行调用,硬件测试和调用的方法,在源码中均有体现,源码参考地址如下:C#含有ModbusRtu通讯库,通讯示例硬件设备测试例程-C#文档类资源-CSDN文库