高级会员

- 积分
- 822
- 金钱
- 822
- 注册时间
- 2020-7-21
- 在线时间
- 85 小时
|
本帖最后由 16424740 于 2021-10-5 18:19 编辑
- 前言:近期在用32大法做项目,拿到好几个新模块,要调TFmini-S的激光雷达以及JY901陀螺仪模块。TFmini官方给的数据手册挺全面的。但很遗憾,官方不提供任何例程,没有正点原子那么贴心。只能自己看着数据协议写。花了两天时间终于搞定了。后面调JY901,官方给了例程,看了它运用结构体处理数据感觉挺有收获的,但这个例程编译出来有问题,并且没做数据的校验,迁移过来的时候做了一些调整。这里开个帖子打算写一下几天调试过来发现的问题和解决方法。希望通过这篇文章能让读者对数据协议的处理有所帮助。//先打个草稿,最近在调项目时间稍微有点紧,但我会慢慢更完
复制代码 器件背景:
TFmini-S是一款高精度、窄角度的激光雷达模块。可以通过串口/IIC协议与单片机进行通信。上电后会不断进行数据测量,并通过串口/IIC返回距离信息、信号强度、器件温度信息。为了加快数据的传输,一般会采用串口协议进行测量。其数据格式如下:
TFmini数据格式
其中BYTE0-1为帧头,即两个0x59,然后紧跟距离、信号强度、温度的低八位、高八位,最后是前面九个数据加起来取低八位作为校验码。
JY901则是一款基于MPU9250经集成处理的陀螺仪模块。一般而言,MPU6050由于温飘、不进行滤波、焊接等原因输出的航向角可能会有1-2度误差,并且在不进行运动时数据会不停变化需要较长时间才能收敛。这个模块亲测在完全不动的情况下数据可以保持0.05度以内的误差并在10s以内收敛(但是很贵TAT)。其数据格式如下(它可以输出年月日、角速度、角度、加速度等信息,此处以角度为例):
JY901角度输出数据格式
即帧头为0x55,0x53,然后输出欧拉角的低八位高八位再以前面数据的和取低八位作为校验位。
现在将尝试用串口3处理JY901,串口4处理TFmini-S的数据。
一般而言,完成初始化等配置后,利用串口中断处理数据并解析可以考虑这样的处理方法:①定义一个BUFFER缓冲数组接收一定个数的数据 ②判断帧头所在的数组下标 ③检查校验位 ④提取所需要的数据进行处理。
那么我们会遇到什么问题呢:首先,应当确定,我们利用串口应该如何采集数据,取多大的数据处理。现在以TFmini为例,取9个u8数据处理显然是不合适的。由于器件是不断发送数据的,当我们开启测量开始处理接收的数据的时候未必是从帧头开始的。因此取9个显然是不合适的。因此,若要保证能取到完整的一帧数据,至少应该取18个u8存入缓存。- void UART4_IRQHandler(void)
- {
- u8 Res;
- tRes = USART_GetFlagStatus(UART4, USART_FLAG_ORE);
- if (USART_GetFlagStatus(UART4, USART_FLAG_PE) != RESET) //①
- {
- USART_ReceiveData(UART4);
- USART_ClearFlag(UART4, USART_FLAG_PE);
- }
- if (USART_GetFlagStatus(UART4, USART_FLAG_ORE) != RESET) //②
- {
- USART_ReceiveData(UART4);
- USART_ClearFlag(UART4, USART_FLAG_ORE);
- }
- if(USART_GetITStatus(UART4, USART_IT_RXNE) != RESET)
- {
- Res =USART_ReceiveData(UART4);
- if(Dis_read) //③
- {
- if(ii < 18) //④
- {
- A_RcvBuf[ii] = Res;
- ii++;
- }
- else
- {
- // Dis_read = 0; //initialize all params
- ii = 0;
- }
- }
- USART_ClearITPendingBit(UART4, USART_IT_RXNE);
- }
-
- }
复制代码
上述代码是串口中断处理的部分。简单解释一下几个关键点,应该很快就能看懂。①、②处是为了解决串口波特率较高(115200)时会出现溢出错误,导致主函数无法正常执行的假死问题。③是一个使能开关,当需要开启距离探测功能时打开,④是缓存过程。
解决了数据读取问题后就要对数据进行处理。- void Output_disA(void)
- {
- u8 temp = 0; //Necessary! do not delete this variable
-
- for(jj = 0; jj < 17; jj++)
- {
- if(A_RcvBuf[jj] == 0x59 && A_RcvBuf[jj+1] == 0x59) //①
- {
- break;
- }
- }
- temp = (A_RcvBuf[jj]+A_RcvBuf[jj+1]+A_RcvBuf[jj+2]+A_RcvBuf[jj+3]+A_RcvBuf[jj+4]+A_RcvBuf[jj+5]+A_RcvBuf[jj+6]+A_RcvBuf[jj+7]); //②
-
- if(temp == A_RcvBuf[jj+8]) //true, deal with the data
- {
- disA = ((A_RcvBuf[jj+3]<<8)+A_RcvBuf[jj+2]);
- }
- else //false, retry
- {
- Dis_read = 1;
- }
-
- }
复制代码
同样的,简单解释一些关键点。①处是为了寻找帧头,②则是对校验位进行处理提高数据的可靠性。如此便完成了器件数据的读取。当然,这个方法具有一定的缺陷,由于数据是每两帧读取一次处理一次,这会导致实际处理过程出现数据降频,比如一个器件发送数据频率50Hz,在单片机处理的时候就变成了25Hz,当然,这方面如果没什么要求问题也不大。
之后在参考了JY901的例程后,我们发现了一种新的方法解决降频问题:- void USART3_IRQHandler(void)
- {
- u8 Res = 0;
- u8 temp = 0;
-
- if (USART_GetFlagStatus(USART3, USART_FLAG_PE) != RESET)
- {
- USART_ReceiveData(USART3);
- USART_ClearFlag(USART3, USART_FLAG_PE);
- }
- if (USART_GetFlagStatus(USART3, USART_FLAG_ORE) != RESET)
- {
- USART_ReceiveData(USART3);
- USART_ClearFlag(USART3, USART_FLAG_ORE);
- }
- if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
- {
- Res =USART_ReceiveData(USART3);
- if(MPU_read)
- {
- ucRxBuffer[ucRxCnt++]=Res; //将收到的数据存入缓冲区中 ①
- if (ucRxBuffer[0]!=0x55) //数据头不对,则重新开始寻找0x55数据头
- {
- ucRxCnt=0;
- return;
- }
- if (ucRxCnt<11) {return;}//数据不满11个,则返回
- else
- {
- temp = ucRxBuffer[0]+ucRxBuffer[1]+ucRxBuffer[2]+ucRxBuffer[3]+ucRxBuffer[4]+ucRxBuffer[5]+ucRxBuffer[6]+ucRxBuffer[7]+ucRxBuffer[8]+ucRxBuffer[9];
- if(temp == ucRxBuffer[10])
- {
- switch(ucRxBuffer[1])//判断数据是哪种数据,然后将其拷贝到对应的结构体中,有些数据包需要通过上位机打开对应的输出后,才能接收到这个数据包的数据
- {
- // case 0x50: memcpy(&stcTime,&ucRxBuffer[2],8);break;//memcpy为编译器自带的内存拷贝函数,需引用"string.h",将接收缓冲区的字符拷贝到数据结构体里面,从而实现数据的解析。
- // case 0x51: memcpy(&stcAcc,&ucRxBuffer[2],8);break;
- // case 0x52: memcpy(&stcGyro,&ucRxBuffer[2],8);break;
- case 0x53: memcpy(&stcAngle,&ucRxBuffer[2],8);break;
- // case 0x54: memcpy(&stcMag,&ucRxBuffer[2],8);break;
- // case 0x55: memcpy(&stcDStatus,&ucRxBuffer[2],8);break;
- // case 0x56: memcpy(&stcPress,&ucRxBuffer[2],8);break;
- // case 0x57: memcpy(&stcLonLat,&ucRxBuffer[2],8);break;
- // case 0x58: memcpy(&stcGPSV,&ucRxBuffer[2],8);break;
- // case 0x59: memcpy(&stcQ,&ucRxBuffer[2],8);break;
- }
- ucRxCnt=0;//清空缓存区
- }
- }
-
- }
- // USART_SendData(USART1,Res); //Output to usart1 to debug
- }
- }
复制代码
这个程序来源于JY901提供的例程,当然,这边做了一些改动,加入了校验位的判定(源程序没有校验位判定)。可以看到,大体思路基本一致,都是要开辟一个缓存区后对数据处理。但该程序较上一个程序在①处做了改动,即实时监测帧头,检测到后便存入缓存区处理,这样不会出现降频问题。
最后简要总结一下一些要点。首先,应该通过器件手册了解通信协议,帧头是什么?校验位有没有?要处理的数据在哪一位?然后,检测帧头,检测到后存入一个数组。接着从数组提取所需的元素进行数据处理得到想要的被测量。最后指针置零等待下一次数据更新。同时,应该注意到,如果波特率较高,应该注意对PE、ORE进行处理,避免假死问题。
|
|