目录
一、串口相关参数介绍
1、端口(COM口)
2、波特率(Baud rate)
3、起始位
4、停止位(StopBits)
5、数据位
6、校验位
7、缓存区
二、串口通信助手
三、虚拟串口工具
四、进阶扩展
1、位运算
2、负数、浮点数存储方式
3、数据校验算法
3.1、奇偶校验
3.2、LRC
3.3、累加和校验
3.4、CRC
4、Modbus通信协议
4.1、介绍
4.2、Modbus-RTU
串口通讯(Serial Communication),是指外设和计算机间,通过数据信号线、地线等,按位进行传输数据的一种双向通讯方式。
串口是一种接口标准,它规定了接口的电气标准,没有规定接口插件电缆以及使用的通信协议,通信协议很多,可根据实际情况选择和自定义。
一、串口相关参数介绍
1、端口(COM口)
CMO口(Communication Module Outlet)是一种用于连接计算机和外部设备的接口,也称为串行端口,简称CMO口,常见串口通讯有一般电脑应用的RS-232(使用 25 针或 9 针连接器)和工业电脑应用的半双工RS-485与全双工RS-422。
电脑端口查看:设置->系统->关于->设备管理器->端口
2、波特率(Baud rate)
波特率是一个电子信号上的术语,用于描述信道的数据传输速度。
单位是bit/s,常见的9600波特率表示每秒传输9600个比特位,1个字节8位,9600/8=1200,代表每秒钟串口可传输1200个字节(不考虑起始位、停止位、校验位的情况下),注意:
波特率如果太高会导致传输数据不稳定,一般使用9600; 发送端与接收端波特率不一致会导致数据不一致;3、起始位
起始位必须是持续一个比特时间的逻辑0电平,标志传输一个字符的开始,接收方可用起始位使自己的接收时钟与发送方的数据同步;
C#开发中一般不需要设置起始位。
4、停止位(StopBits)
停止位可以是是1位、1.5位或2位,可以由软件设定,它一定是逻辑1电平,标志着传输一个字符的结束;
C#开发中一般通过StopBits枚举类型设置停止位,枚举值包括None、One、Two、OnePointFive四种(通常若设备不指定,默认停止位为1位)。
5、数据位
数据位紧跟在起始位之后,是通信中的真正有效信息。数据位的位数可以由通信双方共同约定,一般可以是5位、7位或8位;
C#开发中若设备不指定,默认数据位为8位。
6、校验位
校验位仅占一位,用于进行奇校验或偶校验,检验位不是必须有的。如果是奇校验,需要保证传输的数据总共有奇数个逻辑高位;如果是偶校验,需要保证传输的数据总共有偶数个逻辑高位;
C#开发中通常使用Parity枚举类型来设置校验位,有五种,日常开发中使用最多的是None无校验、Odd奇校验、Even偶校验。
7、缓存区
串口包括两个缓存区,发送缓存区和接收缓存区;
发送缓存区:(软件)发送数据时将数据先存在发送缓存区,再通过(硬件)串口发送出去;
接收缓存区:接收数据时先将数据存在接收缓存区,(软件)再从中读取数据;
二、串口通信助手
串口通信助手是用于测试和调试串口通信的工具,可通过串口与外部设备通信,提供界面来监视和控制串口数据的接收和发送,从而验证串口通信是否正常工作。
实现一个串口通信助手示例:C#Wpf-实现串口通信助手
分享一个串口通信助手,下载地址:友善串口通信助手
提取码: h64y
三、虚拟串口工具
当电脑无COM口时,使用该工具虚拟出COM口。
下载地址:VSPD
提取码:a2cp
破解说明:运行vspd.exe 安装,安装后先不运行,将 vspdctl.dll 文件放入工程目录中,覆盖原文件,即完成破解。
使用说明:点击Add Pair按钮添加虚拟COM口连接对,即可。
四、进阶扩展
1、位运算
符号 | 意义 | 逻辑介绍 | 示例 |
---|---|---|---|
~ | 逻辑非 | 取反 | ~a |
& | 逻辑与 | 清0 | a&b |
| | 逻辑或 | 置1 | a|b |
<< | 按位左移,超出舍掉 | 乘以2的n次方 | a<<n |
>> | 按位右移,超出舍掉 | 除以2的n次方 | a>>n |
^ | 异或 | 不同置1,相同清0 | a^b |
三次异或,可使变量值互换,示例如下:
int a = 13;int b = 20;a ^= b;b ^= a;a ^= b;Console.WriteLine($"a:{a}b:{b}");
2、负数、浮点数存储方式
负数在计算机中是以补码的形式存储,补码计算方式:
最高位为符号位,负数符号位为1,正数为0;除符号位,所有位取反得到反码;反码+1得到补码;浮点数一般分为单精度与双精度两种,在计算机中的存储方式通常分为三部分:
符号位:正数为0,负数为1;指数位:用于存储科学计数法中的指数部分;小数位:用于表示小数部分,不足补0单精度float格式:
符号位 | 指数位 | 小数位 |
---|---|---|
1bit | 8bits | 23bits |
双精度double格式:
符号位 | 指数位 | 小数位 |
---|---|---|
1bit | 11bits | 52bits |
float示例:
8.25转化为二进制表示为:整数部分除2取余,小数部分乘2取整,得到1000.01
科学计数法表示为:1.00001*2的3次方,指数为3(指数范围为-126-127),为消除负数影响,得到的指数+127,得130,即指数1000 0010,小数:00001,存储格式如下:
符号位 | 指数位 | 小数位 |
---|---|---|
0 | 1000 0010 | 000 0100 0000 0000 0000 0000 |
3、数据校验算法
由于数据传输距离的因素影响,计算机和受控设备间的通信数据常常出现不可预知的错误,为了避免错误带来的影响,通常会在通信时对数据进行校验。
3.1、奇偶校验
每8位加一个奇偶校验位,
奇校验:若数据位中1的数量为偶数,则校验位置1凑齐奇数个1,反之置0;
偶校验:若数据为中1的数量为奇数,则校验位置1凑齐偶数个1,反之置0;
缺点:减慢传输速率,偶数个错误时检测不出(出错率五五开);
3.2、LRC
纵向冗余校验(Longitudinal Redundancy Check,LRC),又称逐字节奇偶校验,将数据依字节为单位纵向排列,所有字节相同位全部异或(奇数个1为1,反之为0),计算出一个字节的结果。
缺点:纵向偶数个错误时检测不出;
3.3、累加和校验
将数据的每个字节的值相加,其结果减去255的倍数(控制在0-255区间),然后放在数据最后,接收方接收数据后累加和后与校验值比对;
单个字符出错率1/256
3.4、CRC
介绍
CRC,循环冗余校验,使用的算法思想为除法,将余数作为校验值放在数据末尾;
模二除法(二进制的除法),CRC实际就是模二除法的升级版,通过借位(位移),将余数补在原数据末尾,以此来消掉余数。(接收端将修改后的数据与生成项模二相除没有余数即代表数据无误)
CRC除数叫生成多项式,又称生成项,不同的生成项有不同的CRC,如:CRC8、CRC16、CRC-USB、CRC32……。(二进制转多项式:数字中多少位1代表的是x的多少次方)
模二除法举例:
模二借位举例:
常用多项式表
CRC-8
CRC-8,8位校验码,适用于对数据简单校验的场景,常见的应用领域包括遥控器、智能家居等,示例如下:
public static byte CalcCrc8(byte[] data) { byte crc = 0x00; byte polynomial = 0x8C; // CRC-8 多项式 foreach (byte b in data) { crc ^= b; for (int i = 0; i < 8; i++) { if ((crc & 0x80) != 0) { crc = (byte)((crc << 1) ^ polynomial); } else { crc <<= 1; } } } return crc; }
CRC-16
CRC-16,16位校验码,适用于中等程度校验场景,常见的应用领域包括Modbus通信协议、SD卡存储等,示例如下:
public static ushort CalcCrc16(byte[] data) { ushort crc = 0xFFFF; ushort polynomial = 0xA001; // CRC-16 多项式 foreach (byte b in data) { crc ^= (ushort)(b << 8); for (int i = 0; i < 8; i++) { if ((crc & 0x01) == 0x01) { crc >>= 1; crc ^= polynomial; } else { crc >>= 1; } } } return crc; }
CRC-32
CRC-32,32位校验码,适用于对数据需高强度校验的场景,常见的应用领域包括网络通信、文件传输、数据库存储等,示例如下:
public static uint CalcCrc32(byte[] data) { uint crc = 0xFFFFFFFF; uint polynomial = 0xEDB88320; // CRC-32 多项式 foreach (byte b in data) { crc ^= b; for (int i = 0; i < 8; i++) { if ((crc & 0x00000001) != 0) { crc = (crc >> 1) ^ polynomial; } else { crc >>= 1; } } } return ~crc; }
CRC-USB
CRC-USB,CRC特殊算法之一,适用于USB接口,例如USB 1.1、USB 2.0等协议中的数据校验,示例如下:
public static uint CalcCrcUsb(byte[] data) { uint crc = 0xFFFFFFFF; uint polynomial = 0x04C11DB7; // USB 多项式 foreach (byte b in data) { crc ^= (uint)(b << 24); for (int i = 0; i < 8; i++) { if ((crc & 0x80000000) != 0) { crc = (crc << 1) ^ polynomial; } else { crc <<= 1; } } } return ~crc; }
4、Modbus通信协议
4.1、介绍
Modbus,串行通信协议,属于OSI七层模型中的应用层,最初设计用于可编程逻辑控制器(PLC),Modbus是一种开放式协议,物理层支持使用RS232/RS485/RS422协议的串行设备,同时还支持调制解调器;
Modbus通过设备之间的串行线进行数据传输,最简单的设置是使用一根串行电缆连接两个设备(主设备和从设备)上的串行端口;
Modbus遵循主从模式通信,实现主站与一个(单播)或多个(广播)从站交换报文数据,通信由主机发起,一问一答式,从设备无法主动向主机发送数据。
常用类别有Modbus-RTU、Modbus-ASCII、Modbus-TCP,这里主要介绍Modbus-RTU;
4.2、Modbus-RTU
报文格式:
地址码 | 功能码 | 数据 | CRC-16 |
---|---|---|---|
1字节 | 1字节 | n字节 | 2字节 |
地址码:0为主机广播地址,1-247为从机地址,248-255保留;
功能码:常用功能码如下表:
代码 | 名称 | 作用 |
01 | 读取线圈状态 | 取得一组逻辑线圈的当前状态(ON/OFF) |
02 | 读取输入状态 | 取得一组开关输入的当前状态(ON/OFF) |
03 | 读取保持寄存器 | 在一个或多个保持寄存器中取得当前的二进制值 |
04 | 读取输入寄存器 | 在一个或多个输入寄存器中取得当前的二进制值 |
05 | 强置(写)单线圈 | 强置一个逻辑线圈的通断状态 |
06 | 预置(写)单寄存器 | 放置一个特定的二进制值到一个单寄存器中 |
07 | 读取异常状态 | 取得8个内部线圈的通断状态 |
15 | 强置(写)多线圈 | 强置一串连续逻辑线圈的通断 |
16 | 预置(写)多寄存器 | 放置一系列特定的二进制值到一系列多寄存器中 |
17 | 报告从机标识 | 可使主机判断编址从机的类型及该从机运行指示灯的状态 |
数据:通常包括起始地址、数据长度或数据,都为大端模式(低位存于高地址);
CRC校验:通常使用16位校验码,采用小端模式(低位存于低地址);
Modbus-Rtu报文封装示例(仅供参考):
/// <summary> /// MODEBUS_RTU通信数据 /// </summary> /// <param name="devadress">地址码</param> /// <param name="funccode">功能码</param> /// <param name="paraadress">读/写参数代号地址(起始地址)</param> /// <param name="value">读取数据长度或写数据值</param> /// <returns></returns> public static List<byte> GetModbusData(byte devadress, byte funccode, int paraadress, int value) { List<byte> data = new List<byte>() { devadress, funccode, }; var high = (paraadress & 0xFF00) >> 8;//高8位 var low = paraadress & 0xFF;//低8位 data.Add(Convert.ToByte(high)); data.Add(Convert.ToByte(low)); high = (value & 0xFF00) >> 8; low = value & 0xFF; data.Add(Convert.ToByte(high)); data.Add(Convert.ToByte(low)); var res = Crc16.CRC16(data.ToArray());//16位校验 high = (res & 0xFF00) >> 8; low = res & 0xFF; data.Add(Convert.ToByte(low)); data.Add(Convert.ToByte(high)); return data; }