第四阶段C#通讯开发-3:串口通讯之Modbus协议
1_Modbus协议概述
1.1_Modbus简介
(1)Modbus是一个请求Request/应答(响应)Response协议,包括AScll、RTU、TCP三种主流传输模式,是1979年开发出的一种工业通信协议,并没有规定物理层。也支持网络通讯,其实Modbus协议是七层网络协议【应用层】协议。
(2)此协议定义了控制器能够认识和使用的【消息结构,即数据帧格式】,而不管它们是通过何种硬件方式进行通信的。所以硬件接口可以是RS-232、RS-422、RS-485和以太网等设备。
(3)TCP和RTU比ASCII常见,其中TCP的使用频率更高一些
(4)Modbus是一种【单主站】的【主/从通信模式】。即Modbus网络上只能有一个主站存在。 主站在 Modbus网络上没有地址,从站的地址范围为 0 - 247。 其中 0 为广播地址,从站的实际地址范围为 1 - 247。
(5)Modbus可以支持串口通讯,主站一个,从站一个。 Modbus可以支持网络通讯,比如:TCP。主站一个,从站多个。
模拟器可以模拟主站(ModbusPoll)和从站(ModbusSlave)。
工作时,开发的工控软件充当主站,各种硬件设备充当的是从站。
1.2_Modbus协议的特点
(1)免费。
(2)Modbus支持多种接口。如RS-232\485等(串口),还可以在各种介质上传输,如双绞线、光纤、无线等。
(3)Modbus的帧格式简单,通俗易懂好开发。
(4)可靠性好。Modbus协议需要对数据进行校验,串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验,但TCP模式没有额外规定校验,因为TCP协议是一个面向连接的可靠协议。
1.3_Modbus常用的传输方式
(1)RTU/ASCII/TCP三种传输模式。其中RTU/ASCII是串口通讯,TCP是网络通讯。
(2)RTU模式下,有主站与从站之分,一个主站可以连接多个从站,且主站有且只能有一个,从站至少一个。
(3)TCP模式下,主要分为服务器与客户端。服务器(主站)一个。客户端(从站)至少一个。
(4)RTU:Remote Terminal Unit远程终端设备
(5)ASCII:American Standard Code for Information Interchange美国信息交换标准代码(美国国标码)
(6)TCP:Transmission Control Protocol传输控制协议,是一种面向连接的、可靠的、基于字节流的【传输层】通信协议
1.4_Modbus常用功能码
功能码 | 描述 | PLC地址 | 寄存器地址 | 位/字操作 | 操作数量 |
---|---|---|---|---|---|
01H | 读线圈寄存器 | 00001-09999 | 0000H-FFFFH | 位 | 单个或多个 |
02H | 读离散输入寄存器 | 10001-19999 | 0000H-FFFFH | 位 | 单个或多个 |
03H | 读保持寄存器 | 40001-49999 | 0000H-FFFFH | 字 | 单个或多个 |
04H | 读输入寄存器 | 30001-39999 | 0000H-FFFFH | 字 | 单个或多个 |
05H | 写单个线圈寄存器 | 00001-09999 | 0000H-FFFFH | 位 | 单个 |
06H | 写单个保持寄存器 | 40001-49999 | 0000H-FFFFH | 字 | 单个 |
0FH | 写多个线圈寄存器 | 00001-09999 | 0000H-FFFFH | 位 | 多个 |
10H | 写多个保持寄存器 | 40001-49999 | 0000H-FFFFH | 字 | 多个 |
1.5_常用寄存器
在备中存储数据的称为寄存器,
常用寄存器有:保持寄存器(),输入寄存器,线圈寄存器,离散寄存器
寄存器种类 | 数据类型 | 访问类型 | 功能码 |
---|---|---|---|
线圈(Coils) | 位 | 读写 | 01H 05H 0FH |
离散输入(Discrete Inputs) | 位 | 读写(主站只读) | 02H |
保持寄存器(Holding Registers) | 字 | 读写 | 04H |
输入寄存器(Input Registers) | 字 | 读写(主站只读) | 03H 06H 10H |
在Modbus模拟器中,从机可以更改模拟寄存器的种类
1.6_Modbus常用的数据格式(消息格式)
发送:
假设从站地址为01H,读取线圈寄存器的起始地址为0017H,读取38(十进制)个寄存器,指令结构如下表:
从站地址 | 功能码 | 起始地址高位 | 起始地址低位 | 寄存器数量高位 | 寄存器数量低位 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|
01 | 01 | 00 | 17 | 00 | 26 | 0D | D4 |
响应:各线圈的状态与数据内容的每个bit对应,1代表ON,0代表OFF.若查询线圈的数量不是8的倍数,则在最后一个字节的高位补0.
读线圈结果举例:
从站地址 | 功能码 | 返回字节数 | 数据1 | 数据2 | 数据3 | 数据4 | 数据5 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|---|---|
01 | 01 | 05 | CD | 6B | B2 | 0E | 1B | 44 | EA |
2_模拟器演示Modbus串口通讯步骤
(1)用串口虚拟驱动工具,模拟两个端口COM1和COM2
(2)分别启动主站和从站模拟器ModbusPoll、ModbusSlave
(3)别配置好主站和从站(重要过程) 两个菜单:Setup, Connection
(4)主站和从站建立连接
(5)准备数据,会自动发送
(6)配置主站和从站要求:
-
主站连接的从站地址要正确
-
主站配置的功能码要和从站一致
-
主站配置的起始地址要和从站一致
-
主站串口配置和从站要一致
3_NModbus
3.2_NModbus使用步骤
(1)下载NModbus库:通过NuGet程序包下载,最好下载稳定版本
(2)创建主站实例:
首先创建SerialPort实例,通过ModbusFactory().CreateRtuMaster创建实例,注意,NModbus3.0.81有一个缺陷,创建主站实例的时候会报错,需要添加ModbusFactoryExtensions和SerialPortAdapter两个类,源码可以从NModbus源码中赋值
NModbus源码中获取:
(3)让主站与从站通讯
(4)single 单个 Coil 线圈寄存器 Multiple 多个 Holding 保持寄存器 input 输入 Modbus中很多方法名称有规律
//1.创建主站
//生成Modbus工厂 创建了工厂实例 Modbus工厂可以生成主站实例 也可以生成从站实例
SerialPort port = new SerialPort("COM1");
port.Open();
var factory = new ModbusFactory();
// CreateRtuMaster() 创建主站 传入SerialPort串口对象
// 但是SerialPort 没有实现IStreamResource接口,所以需要需要添加ModbusFactoryExtensions和SerialPortAdapter两个类
//主站实例
IModbusMaster master = factory.CreateRtuMaster(port);
//2.读写数据要用到很多函数 找规律
//Read 读取
//Write 写入
//single 单个 Coil 线圈寄存器 Multiple 多个 Holding 保持寄存器 input 输入
//带s一次可以读写多个
// Async 异步 不带Async的 是省略了sync 是同步方法
//参数1: 从机的地址
//参数2: 起始地址
//参数3: 读取的寄存器的个数,
ushort[] data= master.ReadHoldingRegisters(1,0,2);
label1.Text = data[0].ToString();
(5)注意:
线圈只有两个状态 on(1) off (0)所以返回的是 bool数组
写入的时候参数为:参数1:从机地址;参数2:寄存器地址;参数3:写入值
SerialPort modbusSerialPort = new SerialPort("COM1");
IModbusSerialMaster modbusmaster = new ModbusFactory().CreateRtuMaster(modbusSerialPort);
if (!modbusSerialPort.IsOpen) modbusSerialPort.Open();
byte slaveAddress = 0x01; //从站的地址 0x01
ushort startAddress = 0x00; ; //读取的起始位置
ushort value = 8888; //写入的值
modbusmaster.WriteSingleRegister(slaveAddress, startAddress, value);
modbusSerialPort.Close();
4_NModbus4
(1)NModbus4的使用与NModbus差别只有在创建主机的时候有差别,其他API等于Modbus一致
SerialPort modbusSerialPort = new SerialPort("COM1");;//主站实例
IModbusSerialMaster modbusmaster = ModbusSerialMaster.CreateRtu(modbusSerialPort);;//串口实例
if (!modbusSerialPort.IsOpen) modbusSerialPort.Open();
ushort [] data = modbusmaster.ReadHoldingRegisters(1, 0, 4);
modbusSerialPort.Close();
foreach (ushort value in data)
{Console.WriteLine(value);
}
5_数据冗余校验:CRC校验
(1)CRC校验概念: CRC即循环冗余校验码(Cyclic Redundancy Check):数据通信领域中最常用的一种差错校验码,其信息字段和校验字段长度可以任意指定,但要求通信双方定义的CRC标准一致。CRC校验主要应用在通信过程中,用来检测或校验数据传输或者保存后可能出现的错误。
(2)CRC原理: 在K位(6位)信息码(目标发送数据)后再拼接R位(2位)校验码,使整个编码长度为N位(8位),因此这种编码也叫(N,K)码。通俗的说,就是在需要发送的信息后面附加一个数(即校验码),生成一个新的发送数据发送给接收端。这个数据要求能够使生成的新数据被一个特定的数整除。
(3)建议阅读
CRC校验算法: https://www.jianshu.com/p/2551ea7dbb14 https://www.jianshu.com/p/c0d93c2e89ce https://cloud.tencent.com/developer/article/1852647 https://blog.csdn.net/neuzhangno/article/details/130529058
C#实现CRC16校验:实现的原理都一样。 https://blog.csdn.net/dxm809/article/details/126082630 https://blog.51cto.com/u_15307523/3133048 https://www.5axxw.com/questions/simple/trfml1
所有的校验方式C#实现: https://www.cnblogs.com/li-sx/p/17859420.html
6_补充
C#编码转换有大端和小端之分: 大端:数据的高位存在在低地址中,数据的低位存在在高地址中。 小端:数据的高位存在在高地址中,数据的低位存在在低地址中。 C#中,默认转换使用的小端模式。
请求/应答 Request/Response
ASCII美国信息交换编码 远程终端单元( Remote Terminal Unit,RTU)