解密PS2手柄与STM32F1单片机的交互之道

1.亮灯模式:即手柄MODE指示灯亮时,手柄ID输出为0x73(网上所谓的红灯模式),左右摇杆才会输出模拟量。L3、R3按键有效

图1 摇杆模拟量大小变化方向图(两个摇杆都是这图)

2.无灯模式:即MODE指示灯不亮灯时,手柄输出为0x41(网上所谓的绿灯模式),左右摇杆不会输出模拟量,其极限值“化身”为左右两边四个方向的按键。L3、R3按键无效

【注意】:不论在亮灯模式还是无灯模式里,除了L3、R3的其他所有按键均有效

图2 手柄按键序号图(因手柄反着放所以左右颠倒)

一、硬件连接

① PS2接收器

② PS2接收器转接板

麻烦一定要用转接板,别想着直接用杜邦线把接收器与单片机连在一起!!!(本人之前一天PS2与单片机没有通讯成功,就是因为没有接转接板)

大致思路:

本人用STM32F103RCT6单片机的PC6、PC7、PC8、PC9、GND、3v3IO口与转接板的DAT\DI、CMD\DO、CS、CLK、GND、VCC端口相连(顺序前后一致),

使用PC7即CMD\DO将数据从单片机发送给PS2手柄、

使用PC6即DAT\DI将数据从PS2手柄接收到单片机、

使用PC8即CS作为片选信号、使用PC9即CLK作为时钟信号,协助PC7和PC8同步传输数据、

使用GND与3V3给PS2接收器供电。

二、软件协议

PS2协议:协议,PS2是按照一个什么样的协议通信呢,是这样的,由下面时序图我们可以知道以下几点:

1.DI代表接收的数据,DO代表发送的数据,之前分别与PC6和PC7相连。

2.数据在时钟CLK下降沿的时候才可以正常传输。

3.数据的接收需要在CS端口为低电平时传输,且在通信时,只有一串数据传输完,CS才由低拉到高,而不是传输完一个字节后就由低拉到高。

对应的stm32程序:(用软件模拟上述的时序图,延时在6us左右)

//单片机向手柄发送一个8位数,同时接收数放Data[1]中
uint8_t PS2_Cmd(uint8_t CMD)
{
	volatile uint16_t ref=0x01;
	volatile uint8_t flag=0;
	//每循环一次发送一位,由低位到高位
	for(ref=0x01;ref<0x0100;ref<<=1)
	{			
		if(ref&CMD)
		{
			DO_H;                   
		}
		else DO_L;		
		//高-低-高一个周期,一个下降沿
		CLK_H;                      
		delay_us(6);
		CLK_L;
		delay_us(6 );
		CLK_H;
		if(DI)
		{
			flag=flag|ref;
		}
	}
return flag;	
 
}

那现在,我们知道PS2与单片机需要按照这样的方式传输数据,那PS2规定了发送啥我才告诉你你需要的数据呢,例如,刚刚你按了啥键。是这样的,有这么一个顺序:

首先,单片机通过PC6和PC7模拟上面那个时序,向PS2发送0x01,再发送一个0x42,PS2会传输回单片机一个ID(0x73代表亮灯模式,0x41代表无灯模式),接着你再随便发一个数,PS2会回一个0x5A,说我要开始传输你需要的按键信息了,接着你按照之前的方式发6个数,同时也会接着收到PS2发回来的6个数,这六个数就是你需要的按键信息,下面这个表讲的就是这个事:

对应的stm32程序:

//读取手柄数据
void PS2_ReadData(uint8_t *get_buf)
{ 
   
	volatile uint8_t byte=0;
	volatile uint16_t ref=0x01;
	
	CS_L;
	get_buf[0]=PS2_Cmd(Comd[0]);  //开始命令0x01
	get_buf[1]=PS2_Cmd(Comd[1]);  //请求数据0x42
	get_buf[2]=PS2_Cmd(0x00); //0x51是随便一个数,因为这时候DO已经没有用了
	get_buf[3]=PS2_Cmd(0x00);
	get_buf[4]=PS2_Cmd(0x00);
	get_buf[5]=PS2_Cmd(0x51);
	get_buf[6]=PS2_Cmd(0x51);
	get_buf[7]=PS2_Cmd(0x51);
	get_buf[8]=PS2_Cmd(0x51);
	CS_H;	
}

上述代码出现的其他代码:

bsp_ps2.c文件中
uint8_t Comd[2]={0x01,0x42};
uint8_t Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

bsp_ps2.h文件中
#define DI      GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_6)          
#define DO_H    GPIO_SetBits(GPIOC, GPIO_Pin_7)
#define DO_L    GPIO_ResetBits(GPIOC, GPIO_Pin_7)
#define CS_H    GPIO_SetBits(GPIOC, GPIO_Pin_8)
#define CS_L    GPIO_ResetBits(GPIOC, GPIO_Pin_8)
#define CLK_H   GPIO_SetBits(GPIOC, GPIO_Pin_9)
#define CLK_L   GPIO_ResetBits(GPIOC, GPIO_Pin_9)

在bsp_delay.h文件中
void delay_us(uint16_t time)
{    
    uint16_t i=0;  
    while(time--)
      {
        i=10;  //自己定义
        while(i--) ;    
      }
}

所以要实现PS2与STM32通信:

在main.c你要做的事:

1.初始化你与PS2相连的4个端口

void PS2_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin= GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9 ;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
	GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	
	DO_H;
	CS_H;
	CLK_H;
}

2.在while(1)中不断间隔50ms或者其他时间,调用读取手柄数据的函数PS2_ReadData(uint8_t *get_buf)即可读到每个按键的信息,“0”代表按下,1代表没按下,而读取的数一般存在一个全局无符号8位的数组Data[9]中,其与按键的对应关系如下图:


在学习PS2与STM32单片机通信的过程中找到的很好的资源:

PS2手柄移植到STM32上面的小笔记_stm32 spi铜须实现ps2-CSDN博客

工程文件源码及一些资源如下:

链接:https://pan.baidu.com/s/1Vz9w6U7t2d86SO9NLcHrXg?pwd=stm3 
提取码:stm3

物联沃分享整理
物联沃-IOTWORD物联网 » 解密PS2手柄与STM32F1单片机的交互之道

发表评论