前言
Modbus通信协议!
一、Modbus概念
从站设备编码(从站地址、单元ID),一主多从。
存储区:0-线圈状态、1-输入线圈(只读)、3-输入寄存器(只读)、4-保持型寄存器
协议公开的,设备可以支持 1-65535
功能码:
线圈状态(01-读取,05-单写,15-多写)
输入线圈(02-读取)
输入寄存器(04-读取)
保持型寄存器(03-读取,06-单写,16-多写)
协议分类:ModbusRTU、ModbusASCII、ModbusTCP
二、使用步骤
1.使用Modbus准备
1、下载ModbusSalve和ModbusPoll软件
ModbusPoll是主站
ModbusSalve是从站
Modbus是一主多从的形式,所以ModbusPoll只能开一个,ModbusSalve可以开多个。
2、安装Modbus4库
2.使用步骤
ModbusRTU和ModbusAscii是基于串口进行通信的所以在使用这两个协议时需要先创建串口。
ModbusRTU
SerialPort port = new SerialPort("COM3",9600,Parity.None,8,StopBits.One);
port.Open();
// 创建ModbusRTU通信协议对象
IModbusSerialMaster master = ModbusSerialMaster.CreateRtu(port);
master.Transport.ReadTimeout = 2000;// 设置超时时间
master.Transport.Retries = 3;// 设置重试次数
// 读取从站地址1的保存型寄存器0~9
ushort[] registers = master.ReadHoldingRegisters(
slaveAddress: 1,// 从站地址(1-247)
startAddress: 0,// 起始寄存器地址
numberOfPoints: 10// 读取寄存器数量
);
// 异步读取从站地址1的保存型寄存器0~9
//Task<ushort[]> taskRegisters = master.ReadHoldingRegistersAsync(1, 0, 10);
// 第一种处理异步方法的返回值,所在方法不需要变更异步方法
//ushort[] registersAsync = taskRegisters.GetAwaiter().GetResult();
// 第二种处理异步方法的返回值,所在方法需要变更为异步方法
//ushort[] registersAsync = await master.ReadHoldingRegistersAsync(1, 0, 10);
Debug.WriteLine($"保持寄存器值:{string.Join(",", registers)}");
// 读取从站地址1的输入型寄存器0~9
ushort[] inputRegisters = master.ReadInputRegisters(
slaveAddress: 1,
startAddress: 0,
numberOfPoints: 6
);
// 异步读取从站地址1的输入型寄存器0~9
//ushort[] inputRegistersAsync = await master.ReadInputRegistersAsync(1, 0, 6);
Debug.WriteLine($"输入寄存器值:{string.Join(",", inputRegisters)}");
// 读取从站地址1的线圈状态0~7
bool[] coils = master.ReadCoils(
slaveAddress: 1,
startAddress: 0,
numberOfPoints: 8
);
// 异步读取从站地址1的线圈状态0~7
//bool[] coilsAsync = await master.ReadCoilsAsync(1, 0, 8);
Debug.WriteLine($"读取线圈状态:{string.Join(",", coils.Select(c=>c.ToString()))}");
// 读取从站地址1的输入线圈0~7
bool[] inputCoils = master.ReadInputs(
slaveAddress: 1,
startAddress: 0,
numberOfPoints: 8
);
// 异步读取从站地址1的输入线圈0~7
//bool[] inputCoilsAsycn = await master.ReadInputsAsync(1, 0, 8);
Debug.WriteLine($"读取输入线圈:{string.Join(",", inputCoils.Select(c => c.ToString()))}");
// 写入单个寄存器,向从站1的寄存器6号地址写入数据
//master.WriteSingleRegister(1,6,11111);
ushort[] writeValue = { 1, 2, 3 };
// 写入多个寄存器,向从站1的寄存器0号地址、1号地址、2号地址中依次写入三个数据写入数据
//master.WriteMultipleRegisters(1, 0, writeValue);
// 大小端字节序问题
//例如 300的16进制为12C,可以使用两个字节来表示 0x01 0x2C
// 大端字节序:高位在前低位在后
// 0x01 0x2C 存储在字节数组中 datas[0] = 0x01 datas[1] = 0x2C
//小端字节序:低位在前高位在后
// 0x2C 0x01 存储在字节数组中 datas[0] = 0x2C datas[1] = 0x01
// 一个寄存器地址存储2个byte字节
float v7 = 4.5f; // 浮点型数据占4个字节,但是Modbus协议只能传输2个字节的ushort数据
// 首先将浮点数据转换成字节数组,连续两次发送数据
// 获取浮点数据对应的字节信息,C#中的字节转换后的字节数据为小端字节序,然而Modbus协议要求的是大端字节序,所以后面还需处.单最终还得以设备方要求为准.
byte[] v7_bytes = BitConverter.GetBytes(v7);
// 将上面大端数据转换成小端顺序,也就是反转一下数组
Array.Reverse( v7_bytes );// 转换成大端顺序
// 将处理好的浮点型的字节数组转换成ushort数据类型
// 在转换后有4个字节两两一组,一组字节中前面那个字节为高位字节,后面的字节为低位字节,如果需要将字节转换成ushort类型
// ushort类型为16为其中前8位为高位,后8位为低位,如果需要将两个字节转换成16位,则高位字节需要左移8为也就是乘以256,将左移后的
// 数据与低位相加也就得到一个转换后的ushort数据.
ushort s1 = (ushort)(v7_bytes[0] * 256 + v7_bytes[1]);
// 使用C#自带的BitConverter来将字节数据转换成ushort类型
s1 = BitConverter.ToUInt16(new Byte[] { v7_bytes[1], v7_bytes[0] });// 注意:C#自带的BitConverter方法转换是按小端字节序,所以传入的字节数组得是小端字节序否则数据转换错误
//将浮点型的后两位转换成ushort类型
ushort s2 = (ushort)(v7_bytes[2] *256+v7_bytes[3]);
s2 = BitConverter.ToUInt16(new Byte[] { v7_bytes[3], v7_bytes[2] });
ushort[] floatToUshort = new ushort[2];
floatToUshort[0] = s1;
floatToUshort[1] = s2;
master.WriteMultipleRegisters(1,8,floatToUshort);
// 读取从站地址为1的保持型寄存器8~9(读取浮点型数据)
ushort[] fs = master.ReadHoldingRegisters(1, 8, 2);
// 将读取到的ushort类型的数据转换成byte类型
byte[] us_byte_1 = BitConverter.GetBytes(fs[0]);// BitConverter方法得到的是小端字序
byte[] us_byte_2 = BitConverter.GetBytes(fs[1]);
// 得把小端字序改成大端字序
byte[] f_bytes = new byte[4]
{
us_byte_1[1],
us_byte_1[0],
us_byte_2[1],
us_byte_2[0]
};
Array.Reverse(f_bytes);
float f2 = BitConverter.ToSingle(f_bytes, 0);// BitConverter需要小端所以需要使用Array.Reverse将大端转换成小端
// 向从站1的0号线圈写入True状态
master.WriteSingleCoil(1, 0, true);
// 向从站1的5号线圈连续写入5个状态
master.WriteMultipleCoils(1, 5, new bool[] { true, true, true, true, true });
//异步执行写入操作
//master.WriteSingleCoilAsync(1, 0, true);
//master.WriteMultipleCoilsAsync(1, 5, new bool[] { true, true, true, true, true });
//master.WriteSingleRegisterAsync(1, 6, 11111);
//master.WriteMultipleRegistersAsync(1, 0, writeValue);
ModbusASCII
修改上面代码
将 IModbusSerialMaster master = ModbusSerialMaster.CreateRtu(port);中的CreateRtu替换成CreateAscii
三、Modbus RTU 与 Modbus ASCII对比
特性 | Modbus RTU | Modbus ASCII |
---|---|---|
数据表示方式 | 二进制格式,每个字节包含 8 位数据 | ASCII 码格式,每个字节用两个 ASCII 字符表示 |
传输效率 | 高效,数据紧凑,适合大量数据传输 | 较低,数据量是 RTU 模式的两倍左右 |
校验方式 | CRC(循环冗余校验),检测能力强 | LRC(纵向冗余校验),检测能力相对较弱 |
通信线路要求 | 要求较高,对数据准确性敏感 | 要求相对较低,对噪声和干扰有一定容忍度 |
适用场景 | 工业自动化设备间的高速可靠通信 | 与旧设备集成、调试阶段或通信速率要求不高的系统 |
抗干扰能力 | 较弱,需采取抗干扰措施 | 较强,有一定容错能力 |
实现复杂度 | 较复杂,需处理二进制编码和 CRC 校验 | 较简单,易于实现 |
工业场景一般使用ModbusRTU