本文主要记录串口通信,主要记录 modbus 的默认通信协议 modbus_RTU,当然modbus还包含 modbus_TCP(网口)和 modbus_ASCII(串口)。
一、基础知识
串口和网口
串口:串口是一种物理接口,通常用于连接计算机和外部设备,如打印机、鼠标等。它使用一根线缆进行数据传输,常见的接口有RS-232、RS-485等。串口通信是异步的,可以在一根线上同时发送和接收数据。串口通信适用于需要简单、低成本通信的场景,如打印机、外部存储设备等。它也适用于需要长距离通信的应用,如工业控制系统中的远程监控。网口:网口主要用于计算机网络连接,如以太网接口(Ethernet Port),通常通过RJ45接口连接,使用双绞线或光纤进行数据传输。网口通信支持网络协议,如TCP/IP,用于在局域网或广域网中进行数据传输。
基本名词
传输TX:TXD(Transmit Data)发送数据,用于数据的双向传输。 接收RX:RXD(Received Data)接收数据 地线GND:GND(Ground)代表地线或0线,公共端的意思,这个地并不是真正意义上的地,是出于应用而假设的地,对于电源来说,它就是一个电源的负极。用于建立电平参考,保证信号的稳定传输。位:0或1,就是二进制里面的每一个数 字节:8个二进制组成一个字节 字符:一个汉字2个字节,一个英文字母1个字节数据位:表示每次发送的数据长度,取值5~8 报文格式:起始位+报文(数据位)+停止位,其中0表示起始位,1和2表示结束位 大端序:大位在前,小位在后 波特率:1秒内能传输多少个高低电平,双方必须设置成一致 低电平:0~0.4V 高电平:2.4~5V
RS232
RS232标准:转换高低电平的作用,减少感染,增加传输距离。把高电平位拉高到12V传输,把高电平转换成5V解析。传输距离最大15米,波特率2M,全双工通信(只能1对1)
RS485
RS485标准:通过转换芯片,把外部干扰的高低电平同时升高,大大增加了抗干扰的能力,增加传输距离。传输距离最大1200米,波特率50M,半双工通信(1主多从)
modbus
存储区
存储的数据类型分为 :布尔量 和 16位寄存器布尔量:位操作。16位寄存器: 字节操作,长度16位。2个字节,2的16次方。10进制最大值65536,因此值范围0~65535。
区号 |
名称 |
读写 |
值类型 |
定义范围 |
实际范围 |
0区 |
输出线圈 Coil Status |
可读写布尔量 |
布尔 |
00001~09999 |
00001~65535 |
1区 |
输入状态Input Status |
只读布尔量 |
布尔 |
10001~19999 |
100001~165535 |
3区 |
输入寄存器Input Register |
只读寄存器 |
2字节无符号 |
30001~39999 |
300001~365535 |
4区 |
保持(输出)寄存器 Holding Register |
可读寄存器 |
2字节无符号 |
40001~49999 |
400001~465535 |
功能码
功能码 | 寄存器类型 | 读写状态 | PLC地址 | modbus地址 | 操作数据类型 | 操作数量 |
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 | 字节操作 | 多个 |
这里可以总结,输入都是只读,输出可读可写。
CRC
CRC全称循环冗余校验(Cyclic Redundancy Check, CRC),是通信领域数据传输技术中常用的检错方法,用于保证数据传输的可靠性。
CRC16算法过程
1 初始化一个16位的寄存器地址 用作初始值 2 遍历数据字节,从最高位到最低位, 3 将数据字节与寄存器异或 4 对寄存器进行8次迭代,每一次迭代将寄存器右移一位 5 如果最低位位1,将寄存器与生成多项式0x8005异或,否则只进行右移操作 6 重复上述步骤直到遍历完所有的字节 7 最终寄存器的值就是crc16校验码 8 crc计算之后高低位进行互换
CRC16算法
public static class CRC16{/// <summary>/// CRC校验,参数data为byte数组/// </summary>/// <param name="data">校验数据,字节数组</param>/// <returns>字节0是高8位,字节1是低8位</returns>public static byte[] CRCCalc(byte[] data){//crc计算赋初始值int crc = 0xffff;for (int i = 0; i < data.Length; i++){crc = crc ^ data[i];for (int j = 0; j < 8; j++){int temp;temp = crc & 1;crc = crc >> 1;crc = crc & 0x7fff;if (temp == 1){crc = crc ^ 0xa001;}crc = crc & 0xffff;}}//CRC寄存器的高低位进行互换byte[] crc16 = new byte[2];//CRC寄存器的高8位变成低8位,crc16[1] = (byte)((crc >> 8) & 0xff);//CRC寄存器的低8位变成高8位crc16[0] = (byte)(crc & 0xff);return crc16;}/// <summary>/// CRC校验,参数为空格或逗号间隔的字符串/// </summary>/// <param name="data">校验数据,逗号或空格间隔的16进制字符串(带有0x或0X也可以),逗号与空格不能混用</param>/// <returns>字节0是高8位,字节1是低8位</returns>public static byte[] CRCCalc(string data){//分隔符是空格还是逗号进行分类,并去除输入字符串中的多余空格IEnumerable<string> datac = data.Contains(",") ? data.Replace(" ", "").Replace("0x", "").Replace("0X", "").Trim().Split(',') : data.Replace("0x", "").Replace("0X", "").Split(' ').ToList().Where(u => u != "");List<byte> bytedata = new List<byte>();foreach (string str in datac){bytedata.Add(byte.Parse(str, System.Globalization.NumberStyles.AllowHexSpecifier));}byte[] crcbuf = bytedata.ToArray();//crc计算赋初始值return CRCCalc(crcbuf);}/// <summary>/// CRC校验,截取data中的一段进行CRC16校验/// </summary>/// <param name="data">校验数据,字节数组</param>/// <param name="offset">从头开始偏移几个byte</param>/// <param name="length">偏移后取几个字节byte</param>/// <returns>字节0是高8位,字节1是低8位</returns>public static byte[] CRCCalc(byte[] data, int offset, int length){byte[] Tdata = data.Skip(offset).Take(length).ToArray();return CRCCalc(Tdata);} }
modbus_RTU协议
01H 读取线圈状态(多个)
格式:
发送报文:从站地址+功能码+开始线圈地址+线圈数量+CRC返回报文:从站地址+功能码+字节计数+数据+CRC
02H 读输入状态
格式:
发送报文:从站地址+功能码+起始地址+输入长度+CRC返回报文:从站地址+功能码+数据长度+数据+CRC
03H读取保持寄存器
格式:
发送报文:从站地址+功能码+开始寄存器地址+寄存器数量+CRC返回报文:从站地址+功能码+字节计数+数据+CRC
04H读取输入寄存器
格式:
发送报文:从站地址+功能码+开始寄存器地址+寄存器数量+CRC返回报文:从站地址+功能码+字节计数+数据+CRC
05H写入单线圈
格式:
发送报文:从站地址+功能码+线圈地址+线圈值+CRC返回报文:从站地址+功能码+线圈地址+线圈值+CRC
06H写入单寄存器
格式:
发送报文:从站地址+功能码+寄存器地址+寄存器值+CRC返回报文:从站地址+功能码+寄存器地址+寄存器值+CRC
0FH写入多线圈
格式:
发送报文:从站地址+功能码+起始线圈地址+数量+字节技术+数据+CRC返回报文:从站地址+功能码+起始线圈地址+数量+CRC
10H写入多寄存器
格式:
发送报文:从站地址+功能码+起始寄存器地址+数量+字节计数+数据+CRC返回报文:从站地址+功能码+起始寄存器地址+数量+CRC