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 USART2_RX_BUF_BCK[USART2_BUF_LEN]={0};//缓存接收数据,用于主函数处理,传递给 psx_buf[8]

//接收状态
//bit0,        接收到0x73,首校验
//bit7,        接收到0x5A,尾校验
//bit1~6,接收到的有效字节数目

u8 U2RecSta = 0;//接收数据计数,接收到第几个数据了

int USART2_IRQHandler(void)
{
        u8 Clear = Clear; //这种定义方法,用来消除编译器的*没有用到*提醒
        u8 UartBuf = 0;//临时存储,先校验,再存入数据
       
        static u8 U2RecCnt = 0;//接收数据计数
       
/*中断处理*/
        if(USART_GetITStatus(USART2,USART_IT_RXNE) == SET) //中断+使能标志,用于中断函数内
        {
                USART_ClearFlag(USART2, USART_FLAG_RXNE); //接收数据寄存器非空标志位
                USART_ClearITPendingBit(USART2, USART_IT_RXNE);//清中断接收标志
               
                UartBuf = USART_ReceiveData(USART2);//读串口2接收缓存,临时存储
                                
                USART2_RX_BUF[U2RecCnt] = UartBuf;//所有数据均接收
               
                if(U2RecCnt==0)//帧头0x73,只校验首字符
                {
                        U2RecCnt = (0x73!=UartBuf)?0:U2RecCnt+1;//若为真(不等),则U2RecCnt =0;若相等,则计数+1
                }
                else if(U2RecCnt > 0)//值value,不是首字符,当数据处理
                {
                        U2RecCnt++;
                        if(U2RecCnt == 8)//计数到第8个数,清零
                        {
                                U2RecCnt=0;
                                //uart1_send_nbyte(USART2_RX_BUF,8);//
                                memcpy(USART2_RX_BUF_BCK,USART2_RX_BUF,8);//二级缓存,第二个数组用于给主函数处理
                                U2RecSta=1;//标记状态,一帧数据接收完,主循环处理
                        }        
                }
        }               
        return 0;               
}

没什么用的溢出中断,空闲中断,以及各种清除异常中断

//清除各种异常中断
        if(USART_GetFlagStatus(USART2, USART_FLAG_PE) == SET)//奇偶错误标志位
        {
                Clear =        USART_ReceiveData(USART2);  //接收到数据不处理,抛弃
                USART_ClearFlag(USART2, USART_FLAG_PE);
        }

if(USART_GetFlagStatus(USART2, USART_FLAG_ORE) == SET)//溢出错误标志位
        {
                Clear =        USART_ReceiveData(USART2);  //接收到数据不处理,抛弃
                USART_ClearFlag(USART2, USART_FLAG_ORE);
        }

if(USART_GetFlagStatus(USART2, USART_FLAG_NE) == SET)//噪声错误标志
        {
                Clear =        USART_ReceiveData(USART2);  //接收到数据不处理,抛弃
                USART_ClearFlag(USART2, USART_FLAG_NE);
        }

if(USART_GetFlagStatus(USART2, USART_FLAG_FE) == SET)//帧错误标志位
        {
                Clear =        USART_ReceiveData(USART2);

}

if(USART_GetITStatus(USART2,USART_IT_IDLE) == SET)//空闲中线标志位,一帧数据
        {
                Clear = USART2->SR; //读SR寄存器
                Clear = USART2->DR; //读DR寄存器(先读SR再读DR,就是为了清除IDLE中断)
        }

2.2 PS2手柄处理函数

校验一帧接收完毕(数组长度8个字节),赋值给psx_buf[8].

u8 psx_buf[8]={0};        //存储手柄按键信息

//接收手柄按键数据
void handle_button(void)
{
        if(U2RecSta ==1)//帧接收完毕标志
        {
                U2RecSta = 0;//接收状态计数,清零
                //uart1_send_nbyte(USART2_RX_BUF_BCK,8);//8个字节数据
               
                if(USART2_RX_BUF_BCK[0] == 0x73 && USART2_RX_BUF_BCK[7] == 0x5A)//首尾字符校验 
                {  
                        memcpy(psx_buf,USART2_RX_BUF_BCK,8*sizeof(u8));//校验完成,赋值给数组psx_buf[8]

//Test,8位数据接收OK,LED点亮一次
                        LED_ON;
                        delay_ms(90);
                        LED_OFF;
                }       
        }
       
        static unsigned char psx_button_bak[2] = {0};                       
       
        if((psx_button_bak[0] == psx_buf[1]) && (psx_button_bak[1] == psx_buf[2]))
        {               
        }
        else if((psx_buf[0] == 0)&& (psx_buf[1] == 0))//20ms自动查询一次,handle_ps2()函数,更新psx_buf[]数组为0
        {
                //uart1_send_nbyte(psx_buf,8);//串口1发送 按键数据(全0)
                //uart1_send_str(psx_buf);
               
                parse_psx_buf(psx_buf+1, psx_buf[0]);//按键的首地址psx_buf+1,按键模式类型psx_buf[0](数组顺序与PSX手柄不同)
                psx_button_bak[0] = psx_buf[1];
                psx_button_bak[1] = psx_buf[2];
        }
        else if((psx_buf[0] == 0x73)&& (psx_buf[7] == 0x5A))//串口2中断,无线串口模块,更新psx_buf[]数组为按键与摇杆数据
        {
                uart1_send_nbyte(psx_buf,8);//串口1发送,发送缓存数组,PSX按键值
               
                //Test,按键响应OK
                if(psx_buf[1] == 0x7F && psx_buf[2] == 0xFF)//第一组按键,左键按下,蜂鸣器响一声                               
                {
                BEEP_ON;
                delay_ms(90);
                BEEP_OFF;
                }
                else if(psx_buf[1] == 0xFF && psx_buf[2] == 0x7F)//第二组按键,方形键按下,蜂鸣器响两声                               
                {                       
                //Test
                BEEP_ON;
                delay_ms(90);
                BEEP_OFF;
                delay_ms(90);       
                BEEP_ON;
                delay_ms(90);
                BEEP_OFF;
                }
               
                parse_psx_buf(psx_buf+1, psx_buf[0]);
                psx_button_bak[0] = psx_buf[1];
                psx_button_bak[1] = psx_buf[2];
        }
               
        if(psx_buf[0] == PS2_LED_GRN)
        {
//                joy_left_pwm = 0;
//                joy_right_pwm = 0;
        }
        else if(psx_buf[1]

2.3 串口1发送数据

void tb_usart1_send_nbyte(u8 *Data, u16 size)
{
        u16 i = 0;       
       
        //发送首个数据之前,先读取一下USART_SR,那么就不会出现首个数据丢失
        for(i=0; i<size; i++)
        {
        //发送一串数据的逻辑必须按照先检测TC,再发送字符的顺序进行
        //监测发送状态位用TC而尽量不要使用TXE,TC是监测的是否数据发送完,而TXE只是监测缓存区是否移位到移位寄存器而已
                while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
                USART_SendData(USART1, Data[i]);
        }
        //提升代码健壮性,避免其他部分代码出现类似不检测发送的问题
               while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET) ;       
        return;
}

2.4 运行结果

备注:(1)PS2处理函数中有些会调用串口1发送字符 uart1_send_str();

(2)优化:主函数与终端

2.5 主函数与中断冲突:代码优化

就放上来的这段代码,有个大问题。当主函数在处理数据时,中断可能还在发生。所以你处理的数据,可能一半是上一次发的,一半是这一次发的。

这种问题有两个方案:

1、做个全局互锁开关,通讯完毕了打开开关,只要开关开着,就不能再接收数据。主函数判断开关,如果开着就处理数据,处理完了就关掉开关。

2、做两个缓存数组,一个用于实时接收,接收完毕了用memcpy函数拷贝到另一个数组。主函数永远只能处理另一个数组的数据。如果缓存太大怕memcpy函数执行太久影响中断,就做指针切换也行。接收完了指针就切换过去,主函数永远通过这个指针去取数组。

补充说明一下,第二个方案只适合于主函数运行足够快,能够实时处理完每一帧数据的情况,即,在处理数据时,那边又开始接受数据,这没问题,但是在接收完成数据开始memcpy时,就得考虑主函数是否已经处理完了。如果这个无法保证,就必定会有冲突,只能使用第一个方案,放弃某些帧。如果主函数轮询太慢,可以考虑设置软中断,接收完一帧后触发软中断去处理数据,这样最节约时间。

来自 <http://www.openedv.com/thread-103476-1-1.html>

MJKDZ PS2手柄控制OskarBot小车(三):STM32接收无线串口模块的数据并处理相关推荐

  1. MJKDZ PS2手柄控制OskarBot小车(一):Arduino串口发送数据

    MJKDZ PS2手柄控制OskarBot小车(一):Arduino串口发送数据 [目录] - 1.无线通信模块设置 - 1.1 设置参数 - 1.2 调试步骤 - 2.按键与通信格式 - 2.1 P ...

  2. 【STM32】 无线转接板模块

    目录 一:介绍 二:WX无线转接板模块内部原理图 三: WX无线转接板模块实物图 一:介绍           在电路设计中,为了追求硬件的兼容性,一般会选择电路接口的标准化,然而WiFi模块.蓝牙模 ...

  3. STM32 LoRa无线数传模块 PC通过串口传输数据到单片机

    STM32 PC通过串口助手无线传输数据到单片机   之前学习了STM32单片机,使用正点原子的精英板.两个TTL 转LoRa 半双工无线数传模块,通过PC机串口助手,向32单片机传输数据,接收数据使 ...

  4. C语言程序周期接收虚拟串口发送的数据

    背景 我之前的一篇博客讲解了怎么使用虚拟串口和串口调试助手:虚拟串口模拟器和串口调试助手使用教程,这次我们在此基础上继续来使用虚拟串口周期发送和接收功能. 我们知道,在Windows的操作系统上,将串 ...

  5. ros手柄控制机器人小车(三)

    今天学到了,使用了ros::spin()就是让程序一直在subscriber里面死循环. 更新需求,要把按键设置成控制速度按钮,摇杆作为前进后退的控制. 首先需要测试手柄上所有的按键对应的button ...

  6. STM32操作TFBS4711红外模块、数据发送抓波分析

    序言 : STM32内部USART支持红外IrDA物理层协议,可以配置成IrDA模式直接驱动TFBS4711,实现红外收发. 一:STM32CubeMx对串口配置Irda模式 如下所示: 发送端  R ...

  7. stm32驱动Lora串口模块

    本文使用的硬件 stm32MCU+亿佰特-Lora串口模块,型号E32-433T20D 定义各配置字节 /*配置报文头部选项*/ #define HEAD_Save 0xC0//配置后掉电保存 #de ...

  8. STM32项目-STM32智能小车-电子设计大赛-STM32cubemx-STM32f103c8t6STM32串口通信-

    记录项目的详细制作过程,所以笔记很长,图很多.很多图不好CSDN搬运, 我把笔记放网盘或者自己根据资料下载 笔记网盘下载: 链接:https://pan.baidu.com/s/1Mk2EVIha7F ...

  9. stm32接收OpenMv发送的数据,并用oled屏显示

    自己备赛的过程中遇到了一些问题,网上找了很久才总结出来,openmv通过串口发送数据,stm32进行接收并显示在液晶屏上,亲测可用. 使用设备stm32f103zet6,液晶屏为四线 感谢拾牙慧者提供 ...

最新文章

  1. HTML的标签描述5
  2. 如何查看别人公众号自定义菜单的功能_如何注册自己的个人公众号?手把手教您开启自媒体赚钱之路?...
  3. mysql where 拼接_分一个mysql拼接where语句的Directive,并请教一个问题
  4. DotLucene源码浅读笔记(1) : Lucene.Net.Analysis
  5. Linux命令工作中常用的总结
  6. 快速入门 Jupyter notebook
  7. php mysql 迁移_将phpstudy中的mysql迁移至Linux教程
  8. 常见网络命令整理(ping、trcert、netstat)
  9. Eclipse无法查看Servlet源代码的解决方案
  10. 如何打印被加密的PDF文件
  11. 软件各项会议评审意见模版
  12. IDEA插件系列(41):Code Reading Note插件——代码阅读笔记
  13. mysql 生成假数据_一个好玩的假数据生成器mimesis
  14. 美化我们的windows xp
  15. #navigation
  16. 高速数据采集卡“王牌”对“王牌”
  17. 双非计算机专业考研失败总结
  18. win10此计算机未连接到网络,win10提示无法连接到此网络是怎么回事 怎么办
  19. 【单片机毕业设计】【mcuclub-jj-050】基于单片机的门禁的设计
  20. vue如何引入外部js文件,待解决,急!!!

热门文章

  1. goland防止sql注入的方法
  2. 关学生使用计算机心得,关于计算机课的心得体会范文
  3. java获取IP地址
  4. java获取客户端的IP地址工具类
  5. MIT scratch安装教程
  6. C语言:字符串数组与字符串指针数组
  7. 学习了编程之后,是不是就可以进行APP开发了?
  8. Spring排除功能
  9. linux cp 排除文件,cp、tar命令排除文件和子目录 - 米扑博客
  10. setContextProperty qmlRegisterType qRegisterMetaType等区别