对PS2遥控手柄与stm32单片机通信的理解(结合平衡小车之家的说明和程序)

对PS2遥控手柄与stm32单片机通信的理解(结合平衡小车之家的说明和程序)为了更好地应用PS2遥控手柄,我想尽可能理解一下它与stm32单片机间通信控制的过程,首先看了平衡小车之家给的PS2遥控手柄使用说明,讲解的内容比较简洁,光凭这个说明不能很轻易地理解配套的程序逻辑,接下来结合平衡小车之家的程序内容对照说明解释一下我的理解。因是个人理解并非官方说明,如有误请帮助指出改正,非常感谢!一、自己看一遍说明在看程序之前要先看一下说明里的介绍,大致了解一下。说明及源码:…

大家好,又见面了,我是你们的朋友全栈君。

为了更好地应用PS2遥控手柄,我想尽可能理解一下它与stm32单片机间通信控制的过程,首先看了平衡小车之家给的PS2遥控手柄使用说明,讲解的内容比较简洁,光凭这个说明不能很轻易地理解配套的程序逻辑,接下来结合平衡小车之家的程序内容对照说明解释一下我的理解。因是个人理解并非官方说明,如有误请帮助指出改正,非常感谢!

一、自己看一遍说明

在看程序之前要先看一下说明里的介绍,大致了解一下。
说明及测试源码:
链接:https://pan.baidu.com/s/1hC4Gbjfh87vsswuJyUsH0g
提取码:fdzf

二、结合说明理解程序pstwo.c(.h)

Tips:请按照顺序仔细阅读,前面介绍过的一些基础在后面其他函数中同样应用到时就不再赘述。

1.定义的数组:

Comd[2]={0x01,0x42} :存储了两条指令码,分别是开始指令和请求数据指令。
Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} :数据存储数组,初始全为0,这是最重要的数组,功能在接下来的程序理解中慢慢介绍。
MASK[16]={PSB_SELECT,PSB_L3,PSB_R3,PSB_START,PSB_PAD_UP,PSB_PAD_RIGHT,PSB_PAD_DOWN,PSB_PAD_LEFT,PSB_L2,PSB_R2,PSB_L1,PSB_R1 ,PSB_GREEN,PSB_RED,PSB_BLUE,PSB_PINK} :按键名字的数组,在宏定义中对这些按键赋予了从1~16的按键值。

2.发送命令函数PS2_Cmd(u8 CMD)

看完介绍接下来看程序内容,首先注意,DI与DO是一对同时传输的8 bit串行数据,所谓串行数据特点即按位(1 bit)传输,其次,CLK时钟信号下降沿时完成信号的发送与接收,根据这些,我们首先理解一下PS2_Cmd(u8 CMD)这个函数的意义:

void PS2_Cmd(u8 CMD)
{ 
   
 volatile u16 ref=0x01;
 Data[1] = 0;
 for(ref=0x01;ref<0x0100;ref<<=1)
 { 
   
  if(ref&CMD)
  { 
   
   DO_H;                   //输出一位控制位
  }
  else DO_L;
  CLK_H;                        //时钟拉高
  DELAY_TIME;
  CLK_L;
  DELAY_TIME;
  CLK_H;                        //手动拉出一个下降沿使DO和DI得以同时传送
  if(DI)
   Data[1] = ref|Data[1];      //运用或运算按位存入Data[1]的8位
 }
 delay_us(16);
}

Tips:volatile是易变型变量,是防止编译器优化代码时假设这个变量的值,保证每次小心地重新读取值。
对于图中的for循环,可以得知ref的变化是一个八位二进制数中唯一一个1的位置变化,从最低位到最高位移动,从0000 0001到1000 0000。
&按位与的操作,根据定义可以理解到ref&CMD得到的结果是:当ref中1的位置对应CMD中得位置上也为1时,结果为1;当ref中1的位置对应CMD中得位置上为0时,结果为0。CMD的其他位则不影响此结果。
而这个结果为1时,DO_H即输出1,这个结果为0时,DO_L即输出0。因此for循环八次,DO的结果就是将CMD的每一位传送了过去。
每次循环中下面这一段时钟信号拉高又拉低的操作,是为了手动置出一次下降沿,在这个下降沿中,DO信号才能得以发送,同时DI的信号得以接收回来。
因此接下来又对接收到的DI进行判断:当DI为1时,运用按位或操作,根据Data[1]初始值为0000 0000,以及按位或的定义,不难理解ref | Data[1]得到新的Data[1]的过程是:ref里的唯一的1以值不变位置不变的形式给到结果的二进制数中,比如某一次循环Data[1] = 0000 0010,ref = 0000 1000,且DI=1,则ref | Data[1]=0000 1010。
而这个给1的操作,只有这一bit的DI=1时才会进行;若DI=0,则ref只进行1的移位,不给予,但其实也就相当于这一位ref是给予了0给Data[1]。所以其实判断DI并执行从句的这一步在整个for循环后的结果即是将8 bit的DI按位保存到Data[1]。
至此,可以说理解了这个发送命令的函数的逻辑组成,总结到它的功能就是:每执行一次该函数,就将参数CMD以八位二进制按位发送给手柄,同时从手柄接收信号以八位二进制按位返回给单片机并存储到Data[1]。

2.读取手柄数据函数PS2_ReadData(void)

对于从手柄返回给单片机的数据,最重要的应该是按键和摇杆当前的状态数据,有了这个数据才能用程序处理判断当前用户的动作,再根据按键功能执行相应的操作程序。前面说到,Data[1]已经用来存储每次执行PS2_Cmd函数时DI返回的信号数据,那么Data数组其余的7个位置存储的就应该是需要返回给单片机进行程序处理的有效数据了。
首先要注意,数据的通讯传输必须在CS拉低期间进行,所以即使有了发送命令函数,在执行这个函数前也要先拉低CS,即如图程序中开头部分的CS_L,而在通讯结束、数据传输完成后,还要将CS拉回高电平,以便下一次的通讯,也就是这个函数的结尾的CS_H。

//判断是否为红灯模式,0x41=模拟绿灯,0x73=模拟红灯
//返回值;0,红灯模式
// 其他,其他模式
void PS2_ReadData(void)
{ 
   
 volatile u8 byte=0;
 volatile u16 ref=0x01;
 CS_L;
 PS2_Cmd(Comd[0]);  //发送开始命令0X01
 PS2_Cmd(Comd[1]);  //发送请求数据命令0X42
 for(byte=2;byte<9;byte++)          //开始接受数据
 { 
   
  for(ref=0x01;ref<0x100;ref<<=1)
   { 
   
     CLK_H;
     DELAY_TIME;
     CLK_L;
     DELAY_TIME;
     CLK_H;
        if(DI)
        Data[byte] = ref|Data[byte];  
    }
        delay_us(16);
 }
 CS_H;
}

接下来注意到程序向手柄发送了两条命令,这两条命令都来自于之前定义的Comd[2]数组,因此接下来要知道,想要让手柄返回有效的按键状态数据给单片机,要先发送开始命令0x01和请求数据命令0x42,而且紧接着,手柄将会返回一个数据0x5A给单片机,意味着已经接收到请求,即将返回数据。再接下来,就是返回各按键以及摇杆的状态数据了。说明中数据意义对照表如下:

在这里插入图片描述
Idle代表这时此时该数据线上无含有效意义的数据传送。这张表乍一看并不太能明白,但至少前三行的三个十六进制数的含义我们已经了解了。
回到PS2_ReadData这个函数的代码中继续看,接下来的一部分和PS2_Cmd中非常类似,不难理解,这一段的意义即为:内层循环结束后即将DI返回的八位二进制数据按位存储到了Data数组中的某一个元素位置,而外层循环则是将数据依次存储从Data[2]到Data[8]的位置。
到这里我才意识到两个函数中各个用到delay的意义,结合时序图其实很好理解,关于CLK拉低又拉高期间DELAY_TIME是CLK时钟信号频率的需求,说明中提到,如果数据接收不稳定,可以适当增加频率;而for循环结束后的delay_us应该是因为要等待DI和DO数据的发送与接收完成。
这个函数功能总结为:发送开始命令和请求数据命令,然后接收到返回的预告,存入Data[2],紧接着接收到按键及摇杆当前的状态数据,并存储到Data[3]到Data[8]这七个元素位置。
至此,发送命令与接收数据函数都得以理解。

3.判断模式函数u8 PS2_RedLight(void)

//判断是否为红灯模式,0x41=模拟绿灯,0x73=模拟红灯
//返回值;0,红灯模式
// 其他,其他模式
u8 PS2_RedLight(void)
{ 
   
 CS_L;
 PS2_Cmd(Comd[0]);  //发送开始命令0X01
 PS2_Cmd(Comd[1]);  //发送请求数据命令0X42
 CS_H;
 if( Data[1] == 0X73)   return 0 ;
 else return 1;
}

这个函数很简单,就是如数据意义对照表中1行,DO发送0X42同时DI返回ID,这个ID也是一个十六进制数,这个函数就是判断这个ID是什么,若是0x73,则为红灯模式,该函数返回值为0;若是其他值,则函数返回值为1。至于模式的设置我们接下来会再介绍。
注意这里判断的是Data[1],这是因为这个ID是在DO发送0X42同时DI返回的值,按照PS2_Cmd的意义,应当是存储在Data[1]里的,而不是其他元素位置。

4.清除数据缓冲区PS2_ClearData()

//清除数据缓冲区
void PS2_ClearData(void)
{ 
   
 u8 a;
 for(a=0;a<9;a++)
  Data[a]=0x00;
}

相信这个无需解释,就是清除之前缓冲存储在其中的数据,将Data数组中的元素全部归零,以便下次使用。

5.分析返回数据以判断哪一个按键按下u8 PS2_DataKey()

u8 PS2_DataKey()
{ 
   
 u8 index;
 PS2_ClearData();
 PS2_ReadData();
  Handkey=(Data[4]<<8)|Data[3];     //这是16个按键 按下为0, 未按下为1
  for(index=0;index<16;index++)
   { 
        
     if((Handkey&(1<<(MASK[index]-1)))==0)
     return index+1;
   }
 return 0;          //没有任何按键按下
}

手柄上的按键共有16个,接收到当前按键状态的数据,是以两个八位二进制数也就是两个元素存储在Data数组里的,根据读数据的函数以及数据意义对照表可以知道,即是Data[3]和Data[4],共16 bit,每一位存储一个按键当前的状态值,按键按下为0,未按为1。
Handkey在程序一开始进行了定义,是一个u16的变量,因此是16位二进制数,Data[4]<<8这一步的结果即是高8位为原Data[4],低8位为0000 0000 ,结果再与Data[3]进行按位或,得到的结果应是高8位为原Data[4],低8位为原Data[3],将这个结果赋给Handkey,则这个16位二进制数里就包含了所有的键状态值。
接下来的for循环是检测哪一个按键被按下的最重要的部分:
MASK[index]取出数组中的键值,再减一,得到的结果作为一个移位的位数X,1<<(MASK[index]-1)即让0000 0000 0000 0001中唯一的1左移这个位数X,因为每个键的键值都是它在数组中的序号加一,所以0000 0000 0000 0001移位后得到的结果中唯一的1所在的位置刚好是取出的那个键在数组中的位置(序号),移位后的结果与Handkey进行按位与,逻辑结果为:1<<(MASK[index]-1)的结果中应只有一个位置上值是1,则只有Handkey中对应同样位置上值是0时,这二者按位与的结果才为0。Handkey的其他位上值是什么不影响这个结果。
只有当结果为0时,index+1并作为函数返回值,则这个index+1就是键值。

这一段如果难以理解,最简便的办法就是index取一个值,走一遍程序,就能理解了。
这个循环执行16次,即将Handkey的每一位都进行检测,检测出按键状态值为0就立即返回这个键的键值,并且结束整个函数(return的作用)。
循环结束后还没有return值的话就说明没有按键按下,则return 0。
注意,开头的PS2_ClearData();再PS2_ReadData();是必要的,这是保证现在数组里存的是当前立即更新的键值。
注意,这个函数只能检测一个按键被按下,若同时按多个按键,则只能检测到键值最小的那个,因此如果有兴趣还可以自己写一个组合按键的函数,能实现更多功能。

6.得到摇杆的状态数据u8 PS2_AnologData(u8 button)


//得到一个摇杆的模拟量 范围0~256
u8 PS2_AnologData(u8 button)
{ 
   
 return Data[button];
}

根据数据意义对照表,Data[5]到Data[8]存储的是摇杆的状态数据,分为左/右摇杆的X/Y轴向值,共四个值,当摇杆向X/Y轴推动时,不同的位置会有不同的数值,每个轴向值范围都是0~256,0x00为最左或最上,0xff为最右或最下。应用时根据入口参数button的值返回Data数组相应位置序号里存储的状态数,因此在头文件中也宏定义了四个值对应的数组位置序号值5/6/7/8。

到这里我们可以引入我上网查的资料中所述所谓红灯模式与绿灯模式:
红灯模式时:左右摇杆发送模拟值,0x00~0xFF 之间,且摇杆按下的键值值 L3、R3 有效;
绿灯模式时:左右摇杆模拟值为无效,推到极限时,则对应发送为
UP、RIGHT、DOWN、LEFT、△、○、╳、□,此时按键 L3、R3 无效。

因此如果想要进行一些流畅性的控制比如小车行驶等等,则使用红灯模式比较合适,我认为像变化较大的调参用摇杆也比较方便,而模式选择MODE键是否可用在下面的函数中也可以设置。

7.其他函数

剩下的函数主要都是靠在CS拉低期间发送各种指令码实现的,这里简单带过一下重点的三个函数:

(1)手柄震动函数PS2_Vibration(u8 motor1, u8 motor2)

为了游戏体验感比如赛车撞墙等等,手柄还加入了震动功能,主要由参数motor1和motor2来决定,motor1仅在为0x00时关右侧电机,其他值则开右侧电机并小幅震动,motor2的值则可从0x40~0xff,这时左侧电机震动,且motor2的值越大,震动越强。

(2)发送模式设置PS2_TurnOnAnalogMode(void)

//发送模式设置
void PS2_TurnOnAnalogMode(void)
{ 
   
 CS_L;
 PS2_Cmd(0x01);  
 PS2_Cmd(0x44);  
 PS2_Cmd(0X00);
 PS2_Cmd(0x01); //analog=0x01;digital=0x00 软件设置发送模式
 PS2_Cmd(0x03); //Ox03锁存设置,即不可通过按键“MODE”设置模式。
       //0xEE不锁存软件设置,可通过按键“MODE”设置模式。
 PS2_Cmd(0X00);
 PS2_Cmd(0X00);
 PS2_Cmd(0X00);
 PS2_Cmd(0X00);
 CS_H;
 delay_us(16);
}

这里的重点在于函数内的第5行和第6行:
第5行语句实现软件设置发送模式,指令值为0x01则可发送摇杆模拟量,即红灯模式;指令值为0x00则为绿灯模式,不发送模拟量。
第6行则是对于发送模式可不可以用MODE按键设置的指令,指令值为0X03则只可以通过第5行指令软件设置发送模式;指令值为0xEE则不锁存软件设置,可以通过按MODE键设置红灯/绿灯模式。

(3)手柄配置初始化函数PS2_SetInit(void)

包含完成各种配置函数及保存配置函数,其中原代码默认注释掉了震动模式的配置,可以自己开启。

8.总结:

在main.c中有测试代码,理解了以上函数后就很好理解了,同时也很方便自己改动设置按键功能了,虽然本篇理解有些冗长,不过在写这篇理解的过程中还是很有意思的,尤其对于按位与和按位或的逻辑功能,这辈子是忘不了了…希望本篇对于想用遥控手柄做一些控制的读者能有所帮助,再次希望如果理解有误能有大神指出,万分感谢!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/134592.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • SoapUI 使用教程链接

    SoapUI 使用教程链接http://luyongxin88.blog.163.com/blog/#m=0&t=3&c=soapui

  • java总结体会_Java课程总结心得体会

    java总结体会_Java课程总结心得体会不知不觉中以学习Java将近4个月了,在这几个月的学习中我从一开始的迷茫懵逼,到现在的懵逼迷茫中,写下了这篇这个学期课程的Java学习心得体会。首先,我认为作为一个该开始学习Java的小白,在开始学习之前无论你有多大的热情与信心,都会在之后的学习中被程序啪啪打脸,让你无限的迷茫与懵逼。于是呢在学习中我们要学会”不抛弃,不放弃“,只要能坚定的去做你会发现,你的努力或给你带来回报,虽然很多时候你努力…

  • python十大框架_python 十大web框架排名总结

    python十大框架_python 十大web框架排名总结0引言python在web开发方面有着广泛的应用。鉴于各种各样的框架,对于开发者来说如何选择将成为一个问题。为此,我特此对比较常见的几种框架从性能、使用感受以及应用情况进行一个粗略的分析。1DjangoDjango是一个开放源代码的Web应用框架,由Python写成。采用了MTV的框架模式,即模型M,模板T和视图V。它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的,即是C…

  • java山寨qq账号密码验证_Java实战-山寨QQ

    java山寨qq账号密码验证_Java实战-山寨QQ功能:1.登录界面QQClientLogin.java,好友界面QQFriendList.java,聊天界面QQChar.java2.当用户点击登录后,把账号密码发送给QQserver.java,通过SqlHelper.java访问数据库进行验证3.好友一对一聊天功能4.好友多对多聊天功能5.好友在线显示彩色头像,不在线显示灰色头像6.好友上线提示7.自己不能和自己聊天8.不能和不在线的好友聊天S…

  • windows配置java环境[通俗易懂]

    windows配置java环境的方法是:1、首先进入高级系统设置选项,点击【环境变量】;2、接着点击【系统变量】下的【新建】;3、然后新建【JAVA_HOME】、【classpath】变量;4、编辑【Path】变量;5、最后进行测试即可。

  • ai修复照片软件是哪个软件_AI照片修复免费版-AI照片修复软件下载v1.1.0安卓版-西西软件下载…「建议收藏」

    ai修复照片软件是哪个软件_AI照片修复免费版-AI照片修复软件下载v1.1.0安卓版-西西软件下载…「建议收藏」AI照片修复软件是一款专业的照片图片修复处理工具,可以帮助用户将旧照片、老照片以及低像素的照片进行修复,软件界面赶紧操作简单,适合没有经验的伙伴修复使用,除此之外,还有各种美化效果,轻松将照片上色、换底、换像素,来西西下载即可使用哦!AI照片修复软件简介:AI照片修复是一款免费的智能AI照片修复处理工具,包括照片破损修复、黑白照片上色、无损放大、模糊照片清晰增强等实用工具。基于先进的AI图像生成技…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号