modbus rtu和modbus ascii协议的数据部分是类似的,RTU模式下是数据部分+CRC校验码,ASCII模式下是“:”开始符+数据部分+LRC校验码+"\r \n"结束符,中间所需要做的转换就是ASCII模式下需要将数据部分转为ASCII码方式显示。
public byte[] ModbusAsciiToRtu(byte[] AsciiBytes)
{
if (AsciiBytes[0] != ':')
{
throw new InvalidOperationException("Invalid Modbus ASCII message format.");
}
// 去除起始符和结束符
byte[] messageWithoutStartEnd = new byte[AsciiBytes.Length - 3];
Array.Copy(AsciiBytes, 1, messageWithoutStartEnd, 0, AsciiBytes.Length - 3);
if (messageWithoutStartEnd.Length % 2 != 0)
{
throw new InvalidOperationException("Invalid Modbus ASCII message length.");
}
// 检查是否为有效的十六进制字符
bool IsValidHexChar(char c)
{
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}
// 将 ASCII 字符转换为字节值
List<byte> messageByteList = new List<byte>();
for (int i = 0; i < messageWithoutStartEnd.Length; i += 2)
{
char highNibble = (char)messageWithoutStartEnd[i];
char lowNibble = (char)messageWithoutStartEnd[i + 1];
if (!IsValidHexChar(highNibble) || !IsValidHexChar(lowNibble))
{
continue;
}
string byteString = Encoding.ASCII.GetString(messageWithoutStartEnd, i, 2);
messageByteList.Add(byte.Parse(byteString, NumberStyles.HexNumber));
}
byte[] messageBytes = messageByteList.ToArray();
// 提取 LRC 校验码
byte receivedLRC = messageBytes[messageBytes.Length - 1];
// 去除 LRC 校验码
byte[] messageWithoutLRC = new byte[messageBytes.Length - 1];
Array.Copy(messageBytes, 0, messageWithoutLRC, 0, messageBytes.Length - 1);
// 计算 LRC 校验码
byte calculatedLRC = CalculateLRC(messageWithoutLRC);
if (receivedLRC != calculatedLRC)
{
throw new InvalidOperationException("LRC mismatch in received Modbus ASCII message.");
}
ushort rtuMessageCRC = CalculateCRC16(messageWithoutLRC, 0, messageWithoutLRC.Length);
byte[] rtuMessage = new byte[messageWithoutLRC.Length + 2];
Array.Copy(messageWithoutLRC, 0, rtuMessage, 0, messageWithoutLRC.Length);
rtuMessage[messageWithoutLRC.Length] = (byte)(rtuMessageCRC & 0xff);
rtuMessage[messageWithoutLRC.Length + 1] = (byte)(rtuMessageCRC >> 8);
return rtuMessage;
}
这个为ASCII数据包转为RTU的数据包,下面还有RTU数据包转为ASCII数据包的方法,
public byte[] ModbusRtuToAscii(byte[] RtuBytes)
{
//提取CRC校验码并进行校验
byte[] crc = new[] { RtuBytes[RtuBytes.Length - 2], RtuBytes[RtuBytes.Length - 1] };
ushort crcValue = BitConverter.ToUInt16(crc, 0);
//去除CRC校验码
byte[] RtuBytesWithoutCRC = new byte[RtuBytes.Length-2];
Array.Copy(RtuBytes, 0, RtuBytesWithoutCRC, 0, RtuBytes.Length-2);
ushort rtuMessageCRC = CalculateCRC16(RtuBytes, 0, RtuBytes.Length-2);
if (crcValue != rtuMessageCRC)
{
return null;
}
//数据转换为ASCII类型
string asciiMessage = ConvertRTUFrameToASCII(RtuBytesWithoutCRC);
asciiMessage = ":" + asciiMessage;
//生成LRC校验码并拼接转换
byte lrc = CalculateLRC(RtuBytesWithoutCRC);
string asciiLRC = BitConverter.ToString(new byte[] { lrc });
asciiMessage = asciiMessage+ asciiLRC + "\r\n";
byte[] asciiBytes = Encoding.ASCII.GetBytes(asciiMessage);
return asciiBytes;
}
其中有几个辅助方法,用来进行CRC-16校验码的生成和LRC校验码的生成。crc生成后因为Modbus协议中CRC校验码是低位在前,所以要注意大小端的转换
public static ushort CalculateCRC16(byte[] data, int offset, int length)
{
ushort crc = 0xFFFF;
for (int i = offset; i < offset + length; i++)
{
crc ^= (ushort)(data[i]);
for (int j = 0; j < 8; j++)
{
bool isLSBSet = (crc & 0x0001) != 0;
crc >>= 1;
if (isLSBSet)
{
crc ^= 0xA001;
}
}
}
return crc;
}
public string ConvertRTUFrameToASCII(byte[] rtuRequestFrame)
{
StringBuilder asciiFrameBuilder = new StringBuilder();
foreach (byte dataByte in rtuRequestFrame)
{
byte highNibble = (byte)((dataByte & 0xF0) >> 4);
byte lowNibble = (byte)(dataByte & 0x0F);
asciiFrameBuilder.Append((char)(highNibble + (highNibble < 10 ? '0' : 'A' - 10)));
asciiFrameBuilder.Append((char)(lowNibble + (lowNibble < 10 ? '0' : 'A' - 10)));
}
return asciiFrameBuilder.ToString();
}
public static byte CalculateLRC(byte[] data)
{
byte lrc = 0;
for (int i = 0; i < data.Length; i++)
{
lrc += data[i];
}
lrc = (byte)((lrc ^ 0xFF) + 1);
return lrc;
}
我属于C#和Modbus协议的初学者,如有不对或者需要改进的地方,欢迎大家讨论指导。