1.概述
既然谈到ModbusRTU、Modbus ASCII、ModbusTCP数据返回,那我们多少会对Modbus数据发送会有了解,所以本文不再对Modbus的基础知识进行阐述,如果想了解Modbus常用的发送指令,可以参看我的文章"C# ModbusRTU命令功能码"、“C# Modbus ASCII命令功能码"和"C# ModbusTCP命令功能码”。 Modbus常用的通信指令有8条,其中功能码0x01、0x02、0x03、0x04是查询指令,0x05、0x06、0x0F、0x10是写入指令,对应的返回数据也就有8种。写入指令0x05和0x06的返回数据跟发送指令完全相同(返回异常码除外),0x0F和0x10返回数据跟发送指令的前6个字节完全相同,LRC、CRC校验码不同(ModbusTCP无此校验),写入指令的返回数据相当于确认功能,它们解析为数值是没有意义的。所以我们着重谈的是功能码0x01至0x04的返回数据的值解析。
2.解析代码
Modbus功能码0x01和0x02是查询线圈和离散量状态的,所以返回值类型为bool类型,功能码0x03和0x04是查询保持寄存器和输入寄存器的值,返回类型是数值型的。由于不同的通信硬件设备的解码字节顺序不同,也会带来返回数据的值不同,例如Modicon PLC的整形与浮点型均按3412顺序,而PLC返回的原始数据为1234,解析出来的值自然不会正确。 首先声明四个个枚举,其中有几条枚举项现在用不上,但是我写上它是为了能列出来更多的Modbus指令和生成Modbus发送指令时使用的,无用的大家可自己过滤掉。
///
/// Modbus的类型
///
public enum ModbusType
{
///
/// Modbus serial RTU
///
SERIAL_RTU = 0,
///
/// Modbus serial ASCII
///
SERIAL_ASCII = 1,
///
/// Modbus TCP/IP
///
TCP_IP = 2,
}
///
/// Modbus指令代码
///
public enum ModbusCodes
{
READ_COILS = 0x01, //读取线圈
READ_DISCRETE_INPUTS = 0x02, //读取离散量输入
READ_HOLDING_REGISTERS = 0x03, //读取保持寄存器
READ_INPUT_REGISTERS = 0x04, //读取输入寄存器
WRITE_SINGLE_COIL = 0x05, //写单个线圈
WRITE_SINGLE_REGISTER = 0x06, //写单个保持寄存器
READ_EXCEPTION_STATUS = 0x07, //读取异常状态
DIAGNOSTIC = 0x08, //回送诊断校验
GET_COM_EVENT_COUNTER = 0x0B, //读取事件计数
GET_COM_EVENT_LOG = 0x0C, //读取通信事件记录
WRITE_MULTIPLE_COILS = 0x0F, //写多个线圈
WRITE_MULTIPLE_REGISTERS = 0x10, //写多个保持寄存器
REPORT_SLAVE_ID = 0x11, //报告从机标识(ID)
READ_FILE_RECORD = 0x14, //读文件记录
WRITE_FILE_RECORD = 0x15, //写文件记录
MASK_WRITE_REGISTER = 0x16, //屏蔽写寄存器
READ_WRITE_MULTIPLE_REGISTERS = 0x17, //读写多个寄存器
READ_FIFO_QUEUE = 0x18, //读取队列
READ_DEVICE_IDENTIFICATION = 0x2B //读取设备标识
}
///
/// 错误代码
///
public enum Errors
{
NO_ERROR = 0,//无错误
EXCEPTION_UNKNOWN = 1,//未知异常
EXCEEDING_MODBUSCODE_RANGE = 2,//超出ModbusCode范围
UNPROCESSED_MODBUSCODE = 3,//没有处理的ModbusCode
WRONG_RESPONSE_ADDRESS = 4,//响应地址错误
WRONG_RESPONSE_REGISTERS = 5,//响应寄存器错误
WRONG_RESPONSE_VALUE = 6,//响应值错误
WRONG_CRC = 7,//CRC16校验错误
TOO_MANY_REGISTERS_REQUESTED = 8,//请求的寄存器数量太多
ZERO_REGISTERS_REQUESTED = 9,//零寄存器请求
EXCEPTION_ILLEGAL_FUNCTION = 20,//非法的功能码
EXCEPTION_ILLEGAL_DATA_ADDRESS = 21,//非法的数据地址
EXCEPTION_ILLEGAL_DATA_VALUE = 22,//非法的数据值
EXCEPTION_SLAVE_DEVICE_FAILURE = 23,//从站(服务器)故障
}
///
/// Modbus返回字节大小/类型/顺序
///
public enum ByteOrder
{
NONE = 0,
TWO_WORD_12 = 1,
TWO_WORD_21 = 2,
FOUR_INT_1234 = 3,
FOUR_INT_1243 = 4,
FOUR_INT_2134 = 5,
FOUR_INT_2143 = 6,
FOUR_INT_3412 = 7,
FOUR_INT_3421 = 8,
FOUR_INT_4312 = 9,
FOUR_INT_4321 = 10,
FOUR_FLOAT_1234 = 11,
FOUR_FLOAT_1243 = 12,
FOUR_FLOAT_2134 = 13,
FOUR_FLOAT_2143 = 14,
FOUR_FLOAT_3412 = 15,
FOUR_FLOAT_3421 = 16,
FOUR_FLOAT_4312 = 17,
FOUR_FLOAT_4321 = 18
}
下面就是解析从站返回数据的方法了,返回类型是object,是因为我们上面说过,返回类型可能是bool型和数值型,所以最后根据需要自己去强制转换。考虑到功能码0x01和0x02可能查询是多个线圈或离散量,所以解析方法返回的数据就会是一个bool数组序列,功能码0x03和0x04查询的可能是多个保持寄存器或输入寄存器,其解析回来的数值根据设备字节顺序要求,会是byte、ushort、int和float数组。
#region Modbus
public class Modbus
{
#region 内部方法
///
/// 16进制的字符串转换成等效的byte[]数组
///
private byte[] HexStringToBytes(string source)
{
source = source.Replace(" ", "");
if (source.Length % 2 == 1) return null;//字符串为奇数就无法转换,返回null
byte[] buffer = new byte[source.Length / 2];
for (int i = 0; i |