一、硬件准备:战舰开发板、PS2手柄接收器、PS2手柄、连接线

二、硬件连接:
PS2手柄接收器有六个引脚,和单片机连接IO口连接,如下图:

接收器信号 单片机IO
GND GND
VCC 3.3V
DI/DAT PB12
DO/CMD PB13
CS PB14
CLK PB15


三、PS2通信简介
通讯时序如下,感觉和SPI很像,也是四线
DI与DO是一对同时传输的8 bit串行数据,传输的时候需要CS为低电平,CLK由高变低。
DO是单片机发送给接收器的信号。
DI是接收器发送给单片机的信号。

第一点:CS在数据输出或者输入的时候,都是低电平的,那么我们在数据传输的时候先把CS拉高再拉低,然后数据进行传输,传输完成之后再把CS拉高。
第二点:DI(Data Input)与DO(Data Output)是同时完成的,说明这是全双工通信。串口是全双工通信。IIC是半双工通信。
第三点:在时钟上降沿的时候,DI和DO的数据有交叉,也就是说数据进行交换(数据只有0和1),这个时候我们是不能够读和写数据的,因为数据还不稳定,我们读到的数据不准确。在时钟为下降沿的时候,数据已经稳定了,我们在这个时候开始读和写数据。
第四点:由于是从0到7,可以知道有8位数据,并且是从低位到高位进行读写。我们可以把数据放到数组中。一个时钟进行一个数据位(也可以叫做比特位0或1)传输。


时钟频率250KHZ(4us),数据不稳定可适当增加频率。
当单片机发送0x01时,接收器会回复它的ID“0X41表示绿灯模式”、“0x73表示红灯模式”;在手柄发送ID的同时,单片机将发送0X42,手柄会发送0X5A,高速单片机数据来了。
上表中的idle表示数据线空闲,该数据线无数据传送。
所以Data[0]、Data[1]、Data[2]不能用来存放PS2摇杆的按键
Data[3]、Data[4]用来存放按键的值
Data[5]、Data[6]、Data[7]、Data[8]用来存放摇杆的模拟量

当有按键下,对应位为0,其他位为1
譬如,当SELECT按下,Data[3]=1111 1110B
当L3按下,Data[3]=1111 1101B
当R3按下,Data[3]=1111 1011B
当START按下,Data[3]=1111 0111B
当UP按下,Data[3]=1110 1111B
当RIGHT按下,Data[3]=1101 1111B
当DOWN按下,Data[3]=1011 1111B
当LEFT按下,Data[3]=0111 1111B

手柄有两个模式,红灯模式(手柄亮红灯+绿灯)和绿灯模式(手柄只亮绿灯),可以通过按下MODE按键进行切换。


红灯模式:
1.按键L3/R3按下有效
2.推动左右摇杆,根据行程不一样,可输出0x00-0xff的模拟量
绿灯模式:
1.按键L3/R3按下有效
2.左右摇杆不输出模拟量,推动到上下左右的极限值,左摇杆实现的效果和UP/DOWN/RIGHT/LEFT一样,右摇杆实现的效果和和△/X/□/○一样。

设置震动模式后:
WW用来控制右侧的小电机,0x00表示关,其他值为开。
YY用来控制左侧的大电机,0x40-0xff表示电机开,值越大,震感越强烈;其他值表示电机关。

四、代码分析
1.配置IO口,将PB12设置为下拉输入;PB13/14/15设置为推挽输出

void PS2_Init(void)
{   GPIO_InitTypeDef GPIO_InitStructure;//输入  DI->PB12RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);       //使能PORTB时钟GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_12;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;         //设置成上拉、下拉、浮空输入皆可GPIO_Init(GPIOB, &GPIO_InitStructure);//输出  DO->PB13    CS->PB14  CLK->PB15GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;                //设置成推挽输出 GPIO_Init(GPIOB, &GPIO_InitStructure);
}

2.定义3个数组

u8 Comd[2]={0x01,0x42}; //开始命令。请求数据
u8 Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; //数据存储数组
//每个按键对应一个数值
u16 MASK[]={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
};

3.给PB12/PB13/PB14/PB15这4个IO的状态均进行宏定义

#define DI   PBin(12)           //PB12  输入#define DO_H PBout(13)=1        //命令位高
#define DO_L PBout(13)=0        //命令位低#define CS_H PBout(14)=1       //CS拉高
#define CS_L PBout(14)=0       //CS拉低#define CLK_H PBout(15)=1      //时钟拉高
#define CLK_L PBout(15)=0      //时钟拉低

4.单片机向手柄发送命令

//向手柄发送命令
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_us(50);CLK_L;delay_us(50);CLK_H;if(DI)Data[1] = ref|Data[1];}
}
//
假如单片机给接收器发送0x01=0000 0001B,
接收器接受到0x01后给单片机发送了0x41=0100 0001B,
ref=0x01=0000 0001B;
Data[1] = 0;
CMD=0000 0001B
DI=0100 0001B
/
从低位到高位执行8次循环
第一次循环:ref=0000 0001B
if(ref&CMD)为真,输出PB13=1
if(DI)为真,输出Data[1] = ref|Data[1]=0000 0001|0000 0000=0000 0001第二次循环:ref=0000 0010B
if(ref&CMD)为假,输出PB13=0
if(DI)为假,不执行操作第三次循环:ref=0000 0100B
if(ref&CMD)为假,输出PB13=0
if(DI)为假,不执行操作第四次循环:ref=0000 1000B
if(ref&CMD)为假,输出PB13=0
if(DI)为假,不执行操作第五次循环:ref=0001 0000B
if(ref&CMD)为假,输出PB13=0
if(DI)为假,不执行操作第六次循环:ref=0010 0000B
if(ref&CMD)为假,输出PB13=0
if(DI)为假,不执行操作第七次循环:ref=0100 0000B
if(ref&CMD)为假,输出PB13=0
if(DI)为真,输出Data[1] = ref|Data[1]=0100 0000|0000 0001=0100 0001第八次循环:ref=1000 0000B
if(ref&CMD)为假,输出PB13=0
if(DI)为假,不执行操作
/
所以最后的结果就是
单片机将0x01按位发送了出去
接收机发送的数据0x41保存到了Data[1]里面

a…volatile修饰符可以保证ref每次开始都是0x01即0000 0001B
b.ref=0x01;ref<0x0100;ref<<=1理解这句首先需要将十六进制改为二进制,即ref=0000 0001B;ref<0000 0001 0000 000B;ref<<=1即将ref=0x01每次左移一位,循环八次。
c.ref&CMD即可以通过与运算循环八次,将CMD 这个八位二进制的数按位发送出去。
d.CLK电平进行高-低-高可以产生一个周期,同时产生一个下降沿。在这个过程中,DO将信号从单片机(发送)给接收器(接收),DI将信号从接收器(发送)给单片机(接收)
e.单片机接收到的数据被保存在了Data[1]里面

5.判断手柄是红灯模式还是绿灯模式,通过单片机给手柄发送0x01 0x42后,手柄返回的值来判断,如果返回的是0X41表示"绿灯模式"、0x73表示"红灯模式"

//判断是否为红灯模式
//返回值;0,红灯模式
//返回值;1,绿灯模式
u8 PS2_RedLight(void)
{CS_L;PS2_Cmd(Comd[0]);  //开始命令0x01PS2_Cmd(Comd[1]);  //请求数据0x42CS_H;if( Data[1] == 0X73)   return 0 ;else return 1;
}

5.单片机接收手柄数据

//读取手柄数据
void PS2_ReadData(void)
{volatile u8 byte=0;volatile u16 ref=0x01;CS_L;PS2_Cmd(Comd[0]);  //开始命令0x01PS2_Cmd(Comd[1]);  //请求数据0x42for(byte=2;byte<9;byte++)          //开始接受数据{for(ref=0x01;ref<0x100;ref<<=1){CLK_H;CLK_L;delay_us(50);CLK_H;if(DI)Data[byte] = ref|Data[byte];}delay_us(50);}CS_H;
}

a.数据传输必须在CS拉低期间进行,数据传输完成后,还要将CS拉回高电平,以便下一次的通讯。
b.单片机发送了0x01 0x42给手柄,此时手柄会返回0x5A给单片机,意味着接收到了请求,即将返回数据。所以Data[2]保存的就是手柄返回的0x5A。后面的Data[3]-Data[8]返回的都是按键和摇杆的状态信息。

6.检测按键状态

//对读出来的PS2的数据进行处理      只处理了按键部分         默认数据是红灯模式  只有一个按键按下时
//按下为0, 未按下为1
u8 PS2_DataKey()
{u8 index;PS2_ClearData();PS2_ReadData();Handkey=(Data[4]<<8)|Data[3];     //这是16个按键  按下为0, 未按下为1for(index=0;index<16;index++){     if((Handkey&(1<<(MASK[index]-1)))==0)return index+1;}return 0;          //没有任何按键按下
}

a.上图有给手柄按键标号,一共16个按键,包括两个摇杆,不包括MODE键。16个按键刚好是两个八位二进制数。所以用Data[3]和Data[4]表示所有的按键的状态。
b.u16 Handkey,通过这个变量定义可以看出来Handkey是一个16位的二进制数,Handkey=(Data[4]<<8)|Data[3]表示的是Handkey这个变量的高八位是Data[4],低八位是Data[3]

假如我SELECT按下了,那么Data[3]=1111 1110B
如果L2按下了,那么Data[4]=1111 1110B
所以Handkey表示的就是1111 1110 1111 1110B
c.for(index=0;index<16;index++)因为有16个按键,所以进行16次循环,以此判断是被按下的是哪个键。
if((Handkey&(1<<(MASK[index]-1)))==0)这个函数由内到外分析0<=index<=15,所以1<=MASK[index]<=16,所以0<=MASK[index]-1)<=15,然后将1换算成16位的二进制数为:0000 0000 0000 0001B,所以1<<(MASK[index]-1)就是每次左移一位,循环15次。然后和Handkey进行与运算,如果为0,则说明按键被按下。最后index+1是因为index是从0开始算的,而按键是从1开始计算的,所以最后返回的值需要+1。但这个函数只能判断单个按键按下。如果有多个按键按下,只能检测按键数较小的那个值,譬如方向上(5)和START(4)同时被按下,则返回值就是4。

#define PSB_SELECT      1
#define PSB_L3          2
#define PSB_R3          3
#define PSB_START       4
#define PSB_PAD_UP      5
#define PSB_PAD_RIGHT   6
#define PSB_PAD_DOWN    7
#define PSB_PAD_LEFT    8
#define PSB_L2          9
#define PSB_R2          10
#define PSB_L1          11
#define PSB_R1          12
#define PSB_GREEN       13
#define PSB_RED         14
#define PSB_BLUE        15
#define PSB_PINK        16u16 MASK[]={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
};

7.检测摇杆的状态

#define PSS_RX 5
#define PSS_RY 6
#define PSS_LX 7
#define PSS_LY 8//得到一个摇杆的模拟量     范围0~256
u8 PS2_AnologData(u8 button)
{return Data[button];
}

a.四个摇杆反馈的是模拟量,范围在0x00~0xFF,转换成十进制即为0-255。摇杆的数值存放在Data[5]、Data[6]、Data[7]、Data[8]中
b.注意只有红灯模式下摇杆才反馈模拟量,绿灯模式下摇杆不反馈模拟量。

8.其他函数
8.1清除数据缓冲

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

8.2手柄震动函数

/******************************************************
motor1:右侧小震动电机 0x00关,其他开
motor2:左侧大震动电机 0x40~0xFF 电机开,值越大 震动越大
******************************************************/
void PS2_Vibration(u8 motor1, u8 motor2)
{CS_L;delay_us(16);PS2_Cmd(0x01);  //开始命令PS2_Cmd(0x42);  //请求数据PS2_Cmd(0X00);PS2_Cmd(motor1);PS2_Cmd(motor2);PS2_Cmd(0X00);PS2_Cmd(0X00);PS2_Cmd(0X00);PS2_Cmd(0X00);CS_H;delay_us(16);
}

8.3发送模式设置
a.第八行PS2_Cmd(0x01)则为红灯模式;PS2_Cmd(0x00)则为绿灯模式;
b.第九行PS2_Cmd(0x03)则只可以通过第八行的指令进行红绿灯模式切换;PS2_Cmd(0xEE)则可以通过按MODE进行红绿灯模式切换。

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

百度云链接:附件有STM32F103CBT6开发板和工程文件,还有手柄的规格书,及使用手册
链接:https://pan.baidu.com/s/1Pprxf4hvpu94GJAxjdi1cg
提取码:bpkz

PS2手柄移植到STM32上面的小笔记相关推荐

  1. ucos移植到stm32上的中断小小改进

    uCosII移植到stm32上的文章和demo已经很多了,细节上建议大家可以看官方的移植文档( 当然是E文的).网上流传的各种移植版本基本都是基于官方的移植版本做了小改进.这些改进基本都限制在更适合自 ...

  2. Eclipse里面web项目上面的小地球消失的原因

    一般java的web目录在eclipse里面的显示主要的目录.settings,src和WebContent , .classpath ,  .project 如果项目上面小地球消失,先查看目录格式是 ...

  3. stm32 移植java_把Lua移植到stm32上,效果不错! (amoBBS 阿莫电子论坛)

    因为我们产品的需要满足不同行业需求,所以一直在寻找一个脚本语言,以便灵活配置. 前段时间还自己花时间去实现一个C语言解释器,看了一堆编译原理的东西,以及虚拟机等,头都搞大了,把基本功能实现了,但总是不 ...

  4. mavlink协议详解_MAVLink通讯协议在STM32上移植,并自定义协议

    mavlink全称是(Micro Air Vehicle Message Marshalling Library),从名字可以看出,mavlink是主要面向飞控的一种开源通信协议.因此它默认定义了很多 ...

  5. 12864移植到STM32平台

    利用串口方式将原本51单片机上的串口程序移植到STM32上,调试了两天终于可以显示了 1. 排查IO的初始化是否有问题,在STM32平台上需要对IO口时钟使能,IO口模式配置,一些特殊的管脚还需要做特 ...

  6. uc/OS-II操作系统移植:uc/OS-II移植到STM32流程(文章末尾附实例)

    一.实验目的 1.掌握在STM32上移植嵌入式实时操作系统uC/OS-II的方法 2.掌握在STM32上移植uC/OS-II的基本步骤 3.掌握uC/OS-II的文件OS_CPU.H编写 4.掌握uC ...

  7. 接口有个电池标志_USB接口上的小标签有啥用?从“+”号到闪电的奥秘

    你有注意过自己PC,尤其是笔记本USB接口上面的小符号了吗?从"+"号到闪电,小狮子前几天移动硬盘接驳始终没反应的时候,各种切换完毕发现这似乎是一个平时很少注意的细节啊-- 从&q ...

  8. 去除桌面快捷方式上的小箭头

    电脑桌面上默认快捷方式左下角是有个小箭头的.很多用户可能不习惯快捷方式小箭头.那怎么去掉呢? 方式一 1.新建一个TXT文档(文档的名称自己顺便命名即可),然后把下面的这些英文全部复制到TXT文档内保 ...

  9. 触摸控制芯片MPR121驱动移植(STM32)

    本文记录将arduino下的mpr121触摸板驱动程序移植到stam32f1 1.触摸板简介 之前买了块mpr121做主控的触摸控制板(如下图),卖家给的驱动是arduino的,最近做项目需要移植到s ...

  10. 路由器上的usb接口有什么用_USB接口上的小标签有啥用?从“+”号到闪电的奥秘...

    你有注意过自己PC,尤其是笔记本USB接口上面的小符号了吗?从"+"号到闪电,小狮子前几天移动硬盘接驳始终没反应的时候,各种切换完毕发现这似乎是一个平时很少注意的细节啊-- 从&q ...

最新文章

  1. Lucene 对文档打分的规则整理记录
  2. 在java中读取某个文件中的数据内容
  3. Exp4 恶意代码分析 20164309
  4. 关于CSS3圆角矩形的一些学习探讨
  5. CSDN编程挑战——《进制转换》
  6. 泛泰A860(高通公司8064 cpu 1080p) 拂4.4中国民营recovery TWRP2.7.1.2文本(通过刷第三版)...
  7. 计算机网络—时延相关真题练习(三)
  8. jvm crash分析工具
  9. Linux 服务器为什么被黑
  10. 操作系统——基本概念·
  11. Linux快速入门之文件操作(01)
  12. 使用八爪鱼工具爬取京东当前所有手机信息存入mysql
  13. 数学表达式Round2
  14. beetl模板使用场景_Beetl模板引擎入门教程
  15. Python 实现文字聊天室-功能拓展
  16. java 开发工具eli_二进制开发ELI5 –第1部分
  17. 闪存卡提示格式化怎么办?里面的数据怎么恢复
  18. Unreal Engine 4(虚幻UE4)GameplayAbilities 插件入门教程(三)技能标签(Ability Tags)
  19. MySQL查学生年龄30到40之间_MYSQL查询练习 1
  20. 雅马哈机器人左手右手系统_雅马哈四轴机器人调试笔记

热门文章

  1. 字节跳动面试题后台_JAVA字节跳动面试题分享,一面
  2. 定时器控制一个ADC实现双通道采样(TIM+ADC+DMA)
  3. delphi 剪切板变量_Delphi操作剪贴板
  4. ARM嵌入式系统的问题分析与总结
  5. CAD,SolidWorks相比ProE,UG等软件有什么区别?怎么下载?
  6. Data Center TCP (DCTCP)学习笔记
  7. 计算机网络为什么要分层?
  8. 单片机大学生实习感悟体验
  9. 服务器如何备份系统和配置,windows server 2008和2012如何设置完整备份+增量备份
  10. 30款各大论坛的XP