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

1、本身看一遍说明

在看程序以前要先看一下说明里的介绍,大体了解一下。

说明及测试源码:

连接:https://pan.baidu.com/s/1hC4Gbjfh87vsswuJyUsH0g

提取码:fdzfjava

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

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

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的按键值。web

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]。svg

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]里的,而不是其余元素位置。spa

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<

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<

只有当结果为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 之间,且摇杆按下的键值值 L三、R3 有效;

绿灯模式时:左右摇杆模拟值为无效,推到极限时,则对应发送为

UP、RIGHT、DOWN、LEFT、△、○、╳、□,此时按键 L三、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中有测试代码,理解了以上函数后就很好理解了,同时也很方便本身改动设置按键功能了,虽然本篇理解有些冗长,不过在写这篇理解的过程当中仍是颇有意思的,尤为对于按位与和按位或的逻辑功能,这辈子是忘不了了…但愿本篇对于想用遥控手柄作一些控制的读者能有所帮助,再次但愿若是理解有误能有大神指出,万分感谢!

单片机右摇杆c语言函数英文,对PS2遥控手柄与stm32单片机通讯的理解(结合平衡小车之家的说明和程序)...相关推荐

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

    为了更好地应用PS2遥控手柄,我想尽可能理解一下它与stm32单片机间通信控制的过程,首先看了平衡小车之家给的PS2遥控手柄使用说明,讲解的内容比较简洁,光凭这个说明不能很轻易地理解配套的程序逻辑,接 ...

  2. PS2无线遥控手柄与STM32单片机通信

    PS2无线遥控手柄 接下来要做一个小车玩玩,在网上淘了一个PS2无线遥控手柄,用于控制小车的前进.后退等功能. 实物图如下: 其中有两个PS2游戏摇杆,对应的驱动原理参见下文: PS2游戏摇杆原理及控 ...

  3. Keil用C语言定义函数,STC单片机Keil中C语言函数定位的方法

    STC单片机Keil中C语言函数定位的方法:STC单片机Keil中C语言函数定位的方法 下面以演示程序进行说明 演示程序中有ReadIAP.ProgramIAP和EraseIAP三个函数 最终目的是将 ...

  4. 单片机r6/r7c语言怎么用,关于单片机C51中c语言函数(-nop-())?

    实现延时通常有两种方法:一种是硬件延时,要用到定时器/计数器,这种方法可以提高CPU的工作效率,也能做到精确延时:另一种是软件延时,这种方法主要采用循环体进行. 1  使用定时器/计数器实现精确延时 ...

  5. 平衡小车c语言程序,【全部开源】两轮平衡小车(原理图、PCB、程序源码、BOM等)...

    同网上一般网友制作的平衡小车不一样,这个平衡小车最大的特点就是它的整体很小,PCB面积只有2.5cm*5.0cm,这个可能还没有网友制作的平衡小车的一个电机驱动板大,但是却已经实现了相同的功能.我在器 ...

  6. python调用c语言函数_从Python调用C函数

    python调用c语言函数 We can call a C function from Python program using the ctypes module. 我们可以使用ctypes模块从P ...

  7. 【嵌入式设计】【炒鸡详细】STM32单片机控制机器人程序设计框架解读(不定时更新)

    因为疫情原因,我小机器人的底层单片机代码没人搞了,没人弄了就得我自己上.硕士时候有点儿基础,现在一边儿做一边儿学,争取用一天时间把机器人的底层STM32代码给搞出来. 雨哥最NB的地方就是学东西和做东 ...

  8. 关于stm32单片机低功耗的实现和唤醒

    关于stm32单片机低功耗的实现和唤醒 - 沙河淘金 - 博客园 关于stm32单片机低功耗的实现和唤醒 最近做的项目中要求低功耗,在单片机完成了手头上的工作之后,就进入低功耗模式,项目的需求是单片机 ...

  9. 使用vscode + gcc进行 STM32 单片机开发(三)DMA读写SD卡,移植FATFS文件系统

    背景 在本系列的前两篇文章( 使用vscode + gcc进行 STM32 单片机开发(一)编译及调试 使用vscode + gcc进行 STM32 单片机开发(二)gcc环境 移植rtthread) ...

最新文章

  1. R语言使用ggplot2包使用geom_violin函数绘制分组小提琴图(配置显示散点、抖动点jitter)实战
  2. [BJOI2015]树的同构
  3. Leetcode51 n皇后 DFS+回溯(模板题)
  4. Andorid应用去google广告
  5. java按钮改变窗口大小_布局似乎有问题,JButton在调整窗口大小时显示出意外的行为。...
  6. Category 特性在 iOS 组件化中的应用与管控
  7. Python中递归字符串反转
  8. SpringBoot2基础,进阶,数据库,中间件等系列文章目录分类
  9. ICCV2021 视频领域的纯Transformer方案!谷歌提出ViViT,在多个视频分类基准上SOTA!代码已开源!...
  10. 五个温度带的分界线_亚热带,暖温带,到底是些什么带?
  11. lisp princ详解_LISP - 输入和输出(Input Output)
  12. mac android studio keymap,Android Studio keymap for Mac
  13. python实验题目:中文数字对照表输入一个数字,转换成中文数字。比如:1234567890 -> 壹贰叁肆伍陆柒捌玖零。
  14. 循环队列求元素个数为什么为(rear-front+maxSize)%maxSize?
  15. iOS开发-苹果开发者账号注册、申请续费整个流程
  16. 资深程序员被逼无奈出来摆摊了,快来捧个人场!
  17. java比较器原理理解
  18. input上传图片之获取图片名字
  19. python编辑距离正则匹配_(C/C++学习)33.编辑距离和正则表达式匹配分析
  20. html5 桌面时钟,超级实用的html5制作15种数字时钟样式代码

热门文章

  1. 博本计算机配置似乎是正确的,一键系统重装win7,博本g16笔记本一键安装win7操作方法...
  2. 欢欣雀跃服务器维护到几点,4月25日停服维护公告
  3. 亚马逊、eBay、速卖通等跨境电商自养号测评,你知道多少?
  4. echarts 柱状图柱子改成圆柱体_Origin做多因子柱状图
  5. 国际EPC招标文件进度要求剖析系列2:某轨道项目招标摘要分析
  6. OceanBase 在线与离线安装方式详解
  7. Linux 可视化管理-webmin 和 bt 运维工具
  8. 手写Vue个人组件库——fl-Tree 树形选择器
  9. MySql-moji表情引发的存储异常-微信昵称
  10. 一文讲透数据治理体系