PS2手柄移植到STM32上面的小笔记[通俗易懂]

PS2手柄移植到STM32上面的小笔记[通俗易懂]一、硬件准备:战舰开发板、PS2手柄接收器、PS2手柄、连接线二、硬件连接:PS2手柄接收器有六个引脚,和单片机连接IO口连接,如下图:接收器信号单片机IOGNDGNDVCC3.3VDI/DATPB12DO/CMDPB13CSPB14CLKPB15三、PS2通信简介通讯时序如下,感觉和SPI很像,也是四线DI与DO是一对同时传输的8bit串行数据,传输的时候需要CS为低电平,CLK由高变低。DO是单片机发送给接收器的信号。

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

一、硬件准备:战舰开发板、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->PB12
	RCC_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->PB15
	GPIO_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]);  //开始命令0x01
	PS2_Cmd(Comd[1]);  //请求数据0x42
	CS_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]);  //开始命令0x01
	PS2_Cmd(Comd[1]);  //请求数据0x42

	for(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, 未按下为1
	for(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        16

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
};	

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

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

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

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

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

(0)


相关推荐

  • Mac(OSX)下媲美XShell的神器Termius「建议收藏」

    Mac(OSX)下媲美XShell的神器Termius「建议收藏」文章目录简介特点软件环境配置配置项配置密钥配置说明配置主机配置项简介XShell的大名不用多说,称它为Windows平台最好用的远程终端不为过吧。唯一不足的地方就是它只有Windows版本。所以今天跟大家介绍一款全平台的远程终端——Termius。Termius不仅涵盖了Windows、Linux、OSX,还变态得支持Android和iOS(以后在地铁、公交上都可以随时拿出手机来排查线上问题啦…………

  • win10指纹识别用不了_windowshello指纹识别驱动

    win10指纹识别用不了_windowshello指纹识别驱动目前市面上除游戏本以外大多数新出的Windows10笔记本电脑都支持WindowsHello(面容、指纹、虹膜等),但是对于台式机来说,很少会有消费者专门去购置一台支持WindowsHello的主机,同时外置的USB指纹识别器价格也不便宜,所以很多人即便想和对Windows10说声Hello也不行,久而久之,即便用户想和Windows10交流也没办法,最终只能形…

  • 多线程之ForkJoin框架[通俗易懂]

    多线程之ForkJoin框架[通俗易懂]Fork/Join框架是Java7提供了的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。工作窃取算法工作窃取算法:通过此算法降低线程等待和竞争。工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。那么为什么需要使用工作窃取算法呢?假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任

  • 跨网段远程网络唤醒计算机,远程唤醒及跨网段远程唤醒

    跨网段远程网络唤醒计算机,远程唤醒及跨网段远程唤醒远程唤醒:1、除了在BIOS中开启远程唤醒功能外,有些主板要求开启以下功能:ACBackFunction(设置掉电重启后系统的状态)主板的来电唤醒功能比如一插电源线主机就启动看你想怎么设置了使用远程唤醒将“ACBACKFunction”设置为“Full-on”即可关机,开机,先前的状态(原来开时后来电就马上开机,原来关机后来电时还保持关机状态)2、另外,有些网卡还要在其属性中进行另外…

  • 不推荐使用executors创建线程池_创建线程池的几种方式

    不推荐使用executors创建线程池_创建线程池的几种方式 java中线程池的创建除了使用ThreadPoolExecutor之外,还可以使用Executors的静态方法来获取不同的线程池。Executors类 Executors利用工厂模式向我们提供了4种线程池静态实现方式。创建无大小限制的线程池publicstaticExecutorServicenewCachedThreadPool(){return…

  • QStringList用法总结

    QStringList用法总结QStringList继承自QList,提供了一个QString的List;同QList一样,QStringList也是隐式数据共享的,并且支持按索引访问及快速插入、删除元素的操作。所有QList支持的操作都可用于QStringList,同时QStringList在此基础上提供了一些便于操作QString的功能。1.QStringList的构造函数及析构函数构造函数:a.QSt

发表回复

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

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