先了解ModbusRTU的通信协议。
功能码:
代码 | 名称 | 作用 |
01 | 读取线圈状态 | 取得一组逻辑线圈的当前状态(ON/OFF) |
02 | 读取输入状态 | 取得一组开关输入的当前状态(ON/OFF) |
03 | 读取保持寄存器 | 在一个或多个保持寄存器中取得当前的二进制值 |
04 | 读取输入寄存器 | 在一个或多个输入寄存器中取得当前的二进制值 |
05 | 强置单线圈 | 强置一个逻辑线圈的通断状态 |
06 | 预置单寄存器 | 放置一个特定的二进制值到一个单寄存器中 |
07 | 读取异常状态 | 取得8个内部线圈的通断状态 |
15 | 强置多线圈 | 强置一串连续逻辑线圈的通断 |
16 | 预置多寄存器 | 放置一系列特定的二进制值到一系列多寄存器中 |
17 | 报告从机标识 | 可使主机判断编址从机的类型及该从机运行指示灯的状态 |
写单个寄存器:
/// <summary>
/// 发送单寄存器控制数据
/// </summary>
/// <param name="DeviceAddr">设备地址</param>
/// <param name="Register">寄存器地址</param>
/// <param name="Data">数据</param>
/// <returns></returns>
private byte[] CreateControlCMD(byte DeviceAddr, ushort Register, ushort Data)
{
byte[] CMD = new byte[8];
try
{
//组织请求数据
CMD[0] = DeviceAddr;
CMD[1] = 0x06;//06码:预置单寄存器,把具体二进制装入一个保持寄存器
CMD[2] = BitConverter.GetBytes(Register)[1];
CMD[3] = BitConverter.GetBytes(Register)[0];
CMD[4] = BitConverter.GetBytes(Data)[1];
CMD[5] = BitConverter.GetBytes(Data)[0];
byte[] crcByte = CRC.CRC16(CMD.Take(6).ToArray());
CMD[6] = crcByte[1];
CMD[7] = crcByte[0];
return CMD;
}
catch (Exception ex)
{
Logger.Error(string.Format("构造指令出错:{0}", ex.ToString()));
}
return CMD;
}
读取寄存器(0x03):
public static byte[] RTUReadRegisterRequestCmd(byte address, byte function, ushort startRegister, ushort numOfRegister)
{
byte[] cmds = new byte[8];
cmds[0] = address;
cmds[1] = function; //read cmd
cmds[2] = (byte)(startRegister >> 8); //high byte of register address
cmds[3] = (byte)startRegister; //low byte of register address
cmds[4] = (byte)(numOfRegister >> 8); //high byte of register number
cmds[5] = (byte)numOfRegister; //low byte of register number
byte[] crcByteT = CRC.CRC16(cmds.Take(6).ToArray());
cmds[6] = crcByteT[1];
cmds[7] = crcByteT[0];
return cmds;
}
CRC16方法:
public static byte[] CRC16(byte[] data)
{
int len = data.Length;
if (len > 0)
{
ushort crc = 0xFFFF;
for (int i = 0; i < len; i++)
{
crc = (ushort)(crc ^ (data[i]));
for (int j = 0; j < 8; j++)
{
crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
}
}
byte hi = (byte)((crc & 0xFF00) >> 8); //高位置
byte lo = (byte)(crc & 0x00FF); //低位置
return new byte[] { hi, lo };
}
return new byte[] { 0, 0 };
}
读取数据的总长:
总长 = 地址(1)+功能码(1)+数据长度(2)+数据(n)+crc校验(2)