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

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 运行结果

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

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

 

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

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

这种问题有两个方案:

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

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

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

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

  • 3
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32是一款广泛应用于嵌入式系统的微控制器,而PS2手柄是一种常见的游戏控制器。该问题主要涉及如何利用STM32控制PS2手柄,进而控制小车转动。 首先,我们需要了解PS2手柄的工作原理及通信协议。通常,PS2手柄通过串行通信与外部设备(比如电脑或者控制器)进行连接。手柄会产生相应的控制信号,通过串行通信传送给外部设备。而STM32可以通过串行通信接受这些信号,并分析处理。 接下来,我们需要在STM32上配置相关的外围设备,以实现与PS2手柄通信。可以利用STM32的串行接口(如USART)连接手柄,并设置相应的通信参数。然后,可以通过STM32的中断机制,实时监测串行接口上的数据变化,以便接收PS2手柄发送的控制信号。 在STM32接收PS2手柄控制信号后,我们需要对其进行解码和处理,以得到相应的动作指令。手柄控制信号通常是通过特定的数据帧格式传输的。我们可以编写相应的程序来解析和处理这些数据帧,例如识别手柄的按键状态、摇杆位置等,并将其转换为小车控制指令。 最后,我们可以利用STM32的输出接口(如PWM)来控制小车的电机驱动模块。根据小车的具体设计,可以将转动控制指令转换为对应的电机驱动信号,通过PWM信号输出给电机驱动模块,从而实现小车的转动。 总结起来,通过适当的硬件连接、通信配置和程序设计,我们可以让STM32接收并解析PS2手柄控制信号,进而输出相应的控制指令给小车的电机驱动模块,从而实现小车的转动控制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值