MJKDZ PS2手柄控制OskarBot小车(三):无线串口模块接收数据并处理
【目录】
1、硬件与软件设计思路
- 1.1 硬件资源
- 1.2 STM32串口接收数据的方法
2、源代码详解
- 2.1 串口中断接收数据
- 2.2 PS2手柄处理函数
- 2.3 串口1发送数据
- 2.4 运行结果
- 2.5 主函数与中断冲突:代码优化
1、硬件与软件设计思路
1.1 硬件资源
主控:STM32F103RCT6,主频 72MHz;
串口1:通过CH340G芯片和USB口连接电脑,用于ISP方式采用FlyMCU下载程序,以及打印串口数据在电脑上显示。
串口2:连接MJKDZ的无线串口模块,接收MJKDZ PS2无线手柄传来的数据,传送给STM32芯片。
1.2 STM32串口接收数据的方法
串口通信基础知识:
【第20章 USART—串口通讯 - 野火_firege - 博客园】
参考: https://www.cnblogs.com/firege/p/9323114.html
STM32F1串口接收数据有多种方法,随着数据越来越复杂,数据越长,数据量越大,速度越快,接收函数越复杂:
(1)少量数据,直接接收
【STM32串口中断的4种接收数据的实现方式 - 小胖墩 - CSDN博客】
参考: https://blog.csdn.net/weibo1230123/article/details/80596220
【STM32串口发送数据和接收数据方式总结 - qq_35281599的博客 - CSDN博客】
参考: https://blog.csdn.net/qq_35281599/article/details/80299770#
(2)有一定格式的数据,按帧接收(选择此方式)
主要参考这篇文章【STM32串口中断接收一个完整的数据帧 - cuishumao的专栏 - CSDN博客】
参考: https://blog.csdn.net/cuishumao/article/details/43701789
【串口中怎样接收一个完整数据包的解析】
参考: https://blog.csdn.net/lpp0900320123/article/details/28239765
其中也有人提到了使用IDLE串口空闲中断来判断一帧读取完毕,对于不定长度的数组,可能有用。
对于我这次传输的8个字节的数组,没有效果,还不如自行判断接收完毕。
【STM32学习笔记之-串口中断接收不定数据buff - 一颗偏执的心 - CSDN博客】
参考: https://blog.csdn.net/sinat_23338865/article/details/76599239
讲得详细,但是少提了一句,初始化的时候,要开启IDLE空闲中断。
【教你使用stm32接收串口的一帧数据!】
参考: https://blog.csdn.net/qq_35341807/article/details/79157437
(3)消息队列 & 环形缓冲
【STM32——串口通信升级版(队列方式) - 血染风采2018 - CSDN博客】
已剪辑自: https://blog.csdn.net/wqx521/article/details/69191025
(4)DMA接收
【STM32之串口DMA接收不定长数据 - 知乎】
已剪辑自: https://zhuanlan.zhihu.com/p/50767564
2、源代码详解
(1)串口波特率设置:
串口1,设置成9600波特率,才能正常发送数据在电脑上显示;串口1在下载编译文件时,可以选择成115200波特率,加快下载速度,没有影响。
串口2,设置成9600波特率。
(2)MJKDZ无线串口模块,博通BK2461,2.4GHz。
串口设置要求:
数据位 8,波特率 9600,校验位 N,停止位 1,空中速率 1Mbps。
时序要求:
1)上电大约 20ms 后才可以正常通信。
2)从休眠到唤醒后 2-15ms 内可以接收和发射到数据。
3)从休眠到唤醒后 2ms 后可以发射数据。如果进行休眠工作轮询,唤醒后延时 2ms 再给串口数据,数据给完后要延时一定时间(因为无线还没发完,根据数据长度延时,1 字节 1ms,保证数据的正确性)再进入睡眠,否则数据发不出去。
4)写程序设置参数时,可以通过检查返回指令数据来确保设置成功以及等待时间。
2.1 串口中断接收数据
串口接收的数据格式为,数组,8个字节的8位数据,首字符0x73,2个按键值,4个摇杆值,尾字符0x5A。
中断处理减少if判断,仅校验首字符,存到第8个数就将数组位置清0,重新接收。
两级缓存,第一个数组用来接收,第二个数组给主函数处理。
u8 USART2_RX_BUF[USART2_BUF_LEN]={0};//缓存串口2接收数据,存入数组 |
//接收状态 u8 U2RecSta = 0;//接收数据计数,接收到第几个数据了 int USART2_IRQHandler(void)
|
没什么用的溢出中断,空闲中断,以及各种清除异常中断
//清除各种异常中断 if(USART_GetFlagStatus(USART2, USART_FLAG_ORE) == SET)//溢出错误标志位 if(USART_GetFlagStatus(USART2, USART_FLAG_NE) == SET)//噪声错误标志 if(USART_GetFlagStatus(USART2, USART_FLAG_FE) == SET)//帧错误标志位 }
if(USART_GetITStatus(USART2,USART_IT_IDLE) == SET)//空闲中线标志位,一帧数据 |
2.2 PS2手柄处理函数
校验一帧接收完毕(数组长度8个字节),赋值给psx_buf[8].
u8 psx_buf[8]={0}; //存储手柄按键信息 //接收手柄按键数据 //Test,8位数据接收OK,LED点亮一次 |
2.3 串口1发送数据
void tb_usart1_send_nbyte(u8 *Data, u16 size) |
2.4 运行结果
备注:(1)PS2处理函数中有些会调用串口1发送字符 uart1_send_str();
(2)优化:主函数与终端
2.5 主函数与中断冲突:代码优化
就放上来的这段代码,有个大问题。当主函数在处理数据时,中断可能还在发生。所以你处理的数据,可能一半是上一次发的,一半是这一次发的。 这种问题有两个方案: 1、做个全局互锁开关,通讯完毕了打开开关,只要开关开着,就不能再接收数据。主函数判断开关,如果开着就处理数据,处理完了就关掉开关。 2、做两个缓存数组,一个用于实时接收,接收完毕了用memcpy函数拷贝到另一个数组。主函数永远只能处理另一个数组的数据。如果缓存太大怕memcpy函数执行太久影响中断,就做指针切换也行。接收完了指针就切换过去,主函数永远通过这个指针去取数组。 补充说明一下,第二个方案只适合于主函数运行足够快,能够实时处理完每一帧数据的情况,即,在处理数据时,那边又开始接受数据,这没问题,但是在接收完成数据开始memcpy时,就得考虑主函数是否已经处理完了。如果这个无法保证,就必定会有冲突,只能使用第一个方案,放弃某些帧。如果主函数轮询太慢,可以考虑设置软中断,接收完一帧后触发软中断去处理数据,这样最节约时间。 |