智能小车六《串口协议》
发布日期:2021-10-03 12:40:21
浏览次数:4
分类:技术文章
本文共 3242 字,大约阅读时间需要 10 分钟。
在智能小车四《串口通信》中讲解了串口的通信原理,它就是一个直接把信息转为电信号的工具,透明传输。接着这篇文章我们来解决一下没有协议而发生信息错乱的情况。比如在我们的小车里,收到字符u表示要前进。我们用实际手机给小车发一条蓝牙串口命令。 从上面你拼出什么了么?CONNECT ...... 这些是蓝牙协议的内容,他可能会与我们的命令重合,使我们的小车发生错乱。于是我想自己定义一个协议,我参考TCP协议的结构来定义的。关于TCP协议也是大学里网络课程有的,我简单描述一下。 TCP包结构: TCP协议是基于端口的,所以它有源端口、目的端口,而串口协议不存在这个。其它的字段的含义可以网上查到,我这里面不再赘述了。最后我把协议定义成如下结构: 如何实现这个协议呢?需要分别在小车(arduino c语言)和控制端(android java)各实现一套数据包的解析和生成程序。 首先是小车端: 一、发送数据包: ZZProtocol zzp; /** cmd是要发送的命令 */ void sendCmd(String cmd){ int len=cmd.length(); char data[len+3]; char cmdArray[len]; for(int i=0;i<len;i++){ cmdArray[i]=cmd.charAt(i); } zzp.sendMsg(cmdArray,len,data); for(int i=0;i<len+3;i++){ Serial.print(data[i]); } Serial.println(); } /* msg:要发送的内容 len:数据长度 data:最后发送数据包 */ void ZZProtocol::sendMsg(char msg[], int& len, char data[]) { if (len <= 0) { return; } data[0] = STARTFLAG; data[1] = (char) len; data[3] = msg[0]; char tmpCode = data[3]; for (int i = 1; i < len; i++) { data[i + 3] = msg[i]; tmpCode = tmpCode^data[i + 3]; } data[2] = tmpCode; } 二、接收数据包: /** 得到命令 */ int getCmd(){ int receiveData[64]; int rstData[64]; int rstNum=0; receiveMsg(receiveData,rstData,rstNum); if(rstNum>0){ if(rstNum==1){ int cmd=(int)rstData[0]; return cmd; } } return 0; } /** 接收命令 */ void receiveMsg(int receiveData[],int rstData[],int &rstNum){ int readNum=Serial.available(); if(readNum>0){ int startIndex=0; readHead(receiveData,startIndex); int rstFlag=zzp.checkFullPackage(receiveData,startIndex,rstData,rstNum); if(rstNum>0){ //正常命令 /* Serial.println("cmd:"); printMsg(rstData,rstNum); */ }else{ //错误信息,调试时用,Serial.print会再次传给蓝牙,造成arduino死机 Serial.print("error:"); Serial.println(rstFlag); if(rstFlag==-1){ char str[100]=""; sprintf(str, "[(head error) byte0 btye1 char0 char1 length]:%d,%d,%c,%c,%d",receiveData[0],receiveData[1],receiveData[0],receiveData[1],readNum); Serial.println(str); }else if(rstFlag==-2){ for(int i=1;i<startIndex;i++){ Serial.print((byte)receiveData[i]); Serial.print(" "); } Serial.println(); } } }else{ //读到的数,调试用 /* if(readNum>0){ Serial.print("readNum:"); Serial.println(readNum); } */ } } 里面的包头识别函数(readHead)与检查包函数(checkFullPackage)我要用伪代码了。因为代码太多了,再粘下去,文章不能看了。 包头识别函数(readHead): 一个字符一个字符的读取,只到读到首部标识字符。第二位是长度,所以用一个循环while读取,直到读取到长度或超时退出。如果读到长度,则可以计算出还需多少空间来存储包。当然第二位也有可能是首部标识,这种情况,就舍弃第一个字符,重新从头计算。之后就可以开始读取数据了,但数据里还有可能是首部字符,这时又舍弃前面的字符,重新执行本函数。 检查包函数(checkFullPackage)比较简单: /** * * @param receiveData 接收字符串 * @param dataLen 接收字符串长度 * @param rstData 返回字符串 * @param rstLen 返回字符串长度 * @return -1:head或len验证没通过。-2 checkCode验证没通过 。1正常返回 */ int ZZProtocol::checkFullPackage(int receiveData[],int dataLen,int rstData[],int &rstLen) { int head= receiveData[0]; int len = receiveData[1]; int checkCode = receiveData[2]; int dataCheck=-1; if(head==STARTFLAG&&dataLen==len+3){ for (int i=3; i < dataLen ; i++) { if (dataCheck == -1) { dataCheck = receiveData[i]; } else { dataCheck = dataCheck^receiveData[i]; } } }else{ return -1; } if(checkCode==dataCheck){ int index = 0; while (index < len ) { rstData[index]=receiveData[index+3]; index++; } rstLen=len; return 1; }else{ return -2; } } 最后得到这个命令,可以看得出,getCmd的返回值是个int,并不是一个字符串。嗯,这是因为我的小车功能还比较简单,一个int就能表示所有的命令了,这样也方便调试。另外还有一个问题就是int占用空间少,比较适合arduino这样的硬件受限的设备,字符串可能效率非常低。不过只要我一直玩下去,这就能变成一个字符串。 好累,android部分的协议我还是放到讲android的时候再讲吧。转载地址:https://blog.csdn.net/koolfret/article/details/77106702 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
感谢大佬
[***.8.128.20]2024年03月30日 02时24分35秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
Android KeyCode列表
2019-04-27
platform_device与platform_driver
2019-04-27
platform设备驱动全透析
2019-04-27
Linux内核模块简介
2021-06-30
Linux Platform Device and Driver
2021-06-30
Linux内核开发之将驱动程序添加到内核
2021-06-30
PSAM 卡的应用 操作方法
2021-06-30
imx51-linux的cpuinfo之分析
2021-06-30
CE6.0 下获得 SD 卡序列号的方法
2021-06-30
高通android开发摘要
2019-04-27
Qual F&Q
2019-04-27
Android 5.0 SEAndroid下如何获得对一个内核节点的访问权限
2019-04-27
SMEM介绍
2019-04-27
Device tree customization
2019-04-27
MSM平台RPM
2019-04-27
简谈高通Trustzone的实现
2019-04-27
Linux驱动基础:msm平台,modem等framework加载
2019-04-27