实现串口通信数据帧打包与解析,串口通信可靠传输,屡试不爽的数据封包与状态机数据解析程序

您所在的位置:网站首页 cmd功能c51 实现串口通信数据帧打包与解析,串口通信可靠传输,屡试不爽的数据封包与状态机数据解析程序

实现串口通信数据帧打包与解析,串口通信可靠传输,屡试不爽的数据封包与状态机数据解析程序

2023-09-25 09:47| 来源: 网络整理| 查看: 265

提示:本文所述内容为实际项目中多次实践的成果,稳定可靠,且方便移植,适合多种通信场景。

文章目录 前言一、实现思路一、发送端1.1 实现过程1.2 实现代码1.2.1 定义数据发送函数1.2.2 CRC16校验代码1.2.3 数据帧打包代码 二、接收端2.1 实现过程2.1.1 有几种状态?2.1.2 状态之间如何切换? 2.2 实现代码2.2.1 定义数据处理函数2.2.2 CRC16校验代码2.2.3 状态机解析数据 总结

前言

串口通信是一种异步通信方式,收发双方约定好通信速率,通过两根数据线即可简单的时序全双工数据收发。最常用的串口通信协议由1位起始位 8位数据位 1位停止位组成,总共10位,为了提高通信可靠性,也可在停止位前增加 1位奇偶校验位,但同时也增加可开销,每字节数据需要多传1位二进制数。

串口通信虽然简单方便,但实际使用时会发现需要传输的不止1个字节,往往需要传输n个字节组成的数据包,而因为串口通信中字节之间相互独立,在接收数据时面临 数据包对齐 和防止出错的两大问题。为了解决这两个问题,本文在发送端通过将数据按指定格式打包,在接收端使用状态机解析数据,实现串口通信可靠传输。

一、实现思路

前言指出串口通信面临 数据包对齐 和防止出错的两大问题。

数据包对齐在也叫数据帧同步,解决方法就是引入帧同步字节,也就是增加帧头、帧尾等,对于固定长度数据帧通信可以只使用帧头帧尾,对于可变长度数据帧通信还需引入描述帧长的字节。利用帧头、帧尾、帧长即可解决。防止数据出错也叫差错控制,在通信原理中,有四种差错控制方法:检错重发、前向纠错、反馈校验、检错删除。四种差错控制方法各有其优缺点,本文采用检错删除的差错控制算法,故只需要考虑如何检错这一个问题,只需要在数据帧中增加校验字节。 一、发送端 1.1 实现过程

本文针对较为复杂的一种通信场景进行总结,需要发送变长的数据帧。其他场景可在此基础上进行简化。为了发送变长数据帧,使用帧头+帧长+命令字节+数据字节+校验字节+帧尾的格式对数据包进行打包,其实帧头和帧长已经足够解决帧对齐问题,帧尾可以去掉,为了适应更复杂的情况,这里保留帧尾。

帧头:本人喜欢使用 0xA5,0x5A两个字节作为帧头,因为它们对应的二进制位0与1的个数相同,分布均匀不易出错。帧长:根据数据帧实际长度确定帧长字节,这里只使用1个字节,故帧长字节最大为255,为提高利用率,规定帧长字节描述的是数据字节的长度,故应重新命名为数据长度字节。命令字节:利用命令字节指定数据字节的功能,例如命令字节为1表示传输温度,为2表示传输湿度等,1字节命令+n字节数据是工业中比较常用的一种格式。数据字节:数据字节长度可变,帧长字节为0,表示没有数据,帧长字节为255,表示有255字节数据。校验字节:比较简单的一种校验方式为和校验,即把校验字节前的所以字节求和,最后保留低8位作为校验字节。在MODBUS协议中常用CRC16循环冗余校验方式,将校验字节前的所以字节加入计算,得到两字节CRC16校验码,本文采用此方式。帧尾:与帧头相似,这里使用0xFF作为帧尾。

于是我们得到以下数据帧格式

帧头数据长度命令数据CRC16校验帧尾A5 5AXXXX…XX …XX XXFF2字节1字节1字节n字节 …2字节1字节 1.2 实现代码 1.2.1 定义数据发送函数

这部分可根据需要进行修改,只需调用字节发送函数即可。

void Send(const uint8_t *data,uint8_t len) { uint8_t i; for (i = 0; i uint16_t CRC16 = 0xFFFF; uint8_t state,i,j; for(i = 0; i state = CRC16 & 0x01; CRC16 >>= 1; if(state) { CRC16 ^= 0xA001; } } } return CRC16; } 1.2.3 数据帧打包代码

最后一句Send(buf,cnt);调用数据帧发送函数将打包好的数据帧发送出去

void Send_Cmd_Data(uint8_t cmd,const uint8_t *datas,uint8_t len) { uint8_t buf[300],i,cnt=0; uint16_t crc16; buf[cnt++] = 0xA5; buf[cnt++] = 0x5A; buf[cnt++] = len; buf[cnt++] = cmd; for(i=0;i //根据需要处理数据 } 2.2.2 CRC16校验代码

与发送端完全相同

uint16_t CRC16_Check(const uint8_t *data,uint8_t len) { uint16_t CRC16 = 0xFFFF; uint8_t state,i,j; for(i = 0; i state = CRC16 & 0x01; CRC16 >>= 1; if(state) { CRC16 ^= 0xA001; } } } return CRC16; } 2.2.3 状态机解析数据

输入参数:接收到的数据字节

//接收数据 void Receive(uint8_t bytedata) { static uint8_t step=0,//状态变量初始化为0 在函数中必须为静态变量 static uint8_t cnt=0,Buf[300],len,cmd,*data_ptr; static uint16_t crc16; //进行数据解析 状态机 switch(step) { case 0://接收帧头1状态 if(bytedata== 0xA5) { step++; cnt = 0; Buf[cnt++] = bytedata; }break; case 1://接收帧头2状态 if(bytedata== 0x5A) { step++; Buf[cnt++] = bytedata; } else if(bytedata== 0xA5) { step = 1; } else { step = 0; } break; case 2://接收数据长度字节状态 step++; Buf[cnt++] = bytedata; len = bytedata; break; case 3://接收命令字节状态 step++; Buf[cnt++] = bytedata; cmd = bytedata; data_ptr = &Buf[cnt];//记录数据指针首地址 if(len == 0)step++;//数据字节长度为0则跳过数据接收状态 break; case 4://接收len字节数据状态 Buf[cnt++] = bytedata; if(data_ptr + len == &Buf[cnt])//利用指针地址偏移判断是否接收完len位数据 { step++; } break; case 5://接收crc16校验高8位字节 step++; crc16 = bytedata; break; case 6://接收crc16校验低8位字节 crc16 step = 1; } else { step = 0; } break; case 7://接收帧尾 if(bytedata== 0xFF)//帧尾接收正确 { Data_Analysis(cmd,data_ptr,len);//数据解析 step = 0; } else if(bytedata == 0xA5) { step = 1; } else { step = 0; } break; default:step=0;break;//多余状态,正常情况下不可能出现 } } 总结

本文程序可方便的嵌入到源代码中,发送端只需调用 void Send_Cmd_Data(uint8_t cmd,const uint8_t *datas,uint8_t len); 函数发送命令和数据,接收端定义 void Data_Analysis(uint8_t cmd,const uint8_t *datas,uint8_t len); 函数解析数据。 状态机的思想参考《数字电子技术基础》,差错控制方法参考《通信原理》而通信层级关系可参考ISO模型,串口通信传输的是比特流类似于物理层,而数据帧通信传输的是数据帧类似于数据链路层,文末两个接口可以看成应用层。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3