C#上位机之Modbus通信协议!

发布于:2025-07-04 ⋅ 阅读:(14) ⋅ 点赞:(0)


前言

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


网站公告

今日签到

点亮在社区的每一天
去签到