学习PS2无线手柄解码通讯手册

6 篇文章 3 订阅
1 篇文章 1 订阅

学习 PS2 无线手柄的使用方法,将市场上 PS2 手柄通过解码应用到我们自己产品之中,比如控制智能车,机械臂等等任何涉及无线通信控制的一些diy场景。本次主要让大家了解 PS2 无线手柄的工作原理,以及掌握 PS2 无线手柄的使用并最终通过串口打印各按键的键值。

常见用途

diy 产品,舵机,寄存器 一些无线控制的设置和产品

手柄原理

ps2 由手柄与接收器两部分组成,手柄主要负责发送按键信息。都接通电源并打开手柄 开关时,手柄与接收器自动配对连接,在未配对成功的状态下,接收器绿灯闪烁,手柄上的 灯也会闪烁,配对成功后,接收器上绿灯常亮,手柄上灯也常亮,这时可以按“MODE” 键,选择手柄发送模式。
接收、、和主机(单片机)相连,实现主机与手柄之间的通讯。

实物参考如下图
在这里插入图片描述

接收器引脚输出,

123456789
DI/DATDO/CMDNCGNDVDDCS/SELCLKNCACK
数据命令空/不接通讯3.3V----时钟一般不接不接

PS2接收器上一共有九根引脚,按上图从左往右,依次为:

1.DI/DAT:信号流向,从手柄到主机,此信号是一个8bit 的串行数据,同步传送于时钟的下降沿。信号的读取在时钟由高到低的变化过程中完成。

2.DO/CMD:信号流向,从主机到手柄,此信号和 DI相对,信号是一个 8bit 的串行数据, 同步传送于时钟的下降沿。

3.NC:空端口。

4.GND:电源地。

5.VCC:接收器工作电源,电源范围 3~5V,一般3.3v。

6.CS/SEL:用于提供手柄触发信号。在通讯期间,处于低电平。

7.CLK:时钟信号,由主机发出,用于保持数据同步。

8.NC:空端口。

9.ACK:从手柄到主机的应答信号。此信号在每个8bits数据发送的最后一个周期变低并且CS一直保持低电平,如果CS信号不变低,约60微秒PS主机会试另一个外设。在编程时未使用ACK端口。(可以忽略)

在这里插入图片描述

	在时钟下降沿时,完成数据的发送与接收。

当主机想读手柄数据时,将会拉低 CS 线电平,并发出一个命令“0x01”; 手柄会回复 它的 ID“0x41=模拟绿灯,0x73=模拟红灯”;在手柄发送 ID 的同时,主机将传送 0x42,请求数据;随后手柄发送出 0x5A,告诉主机“数据来了”。idle:数据线空闲,改数据线无数据传送。
一共一个通讯周期有 9 个数据,这些数据是依次按为传送。
表 1:数据意义对照表!
在这里插入图片描述

当有按键按下,对应位为“0”,其他位为“1”,例如当键“SELECT”被按下时,Data[3]=11111110B,
红灯模式时:左右摇杆发送模拟值,0x00~0xFF(256) 之间的模拟量,且摇杆按下的键值值
L3、R3 有效;
绿灯模式时:左右摇杆模拟值为无效,推到极限时,对应发送 UP、RIGHT、DOWN、 LEFT、△、○、╳、□,按键 L3、R3 无效。

硬件连接部分

接收器与 stm32 连接方式
Dl—>PC13
DO—>PB14
CS—>OC15
CLK—>PB8

下面就是测试程序

完整程序详见工程文件。 这里主要介绍 ps2.c 文件中的函数。

void PS2_Init(void)
{
    //ÊäÈë  DI->PC13   		
	GPIO_InitTypeDef GPIO_InitStructure; 
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO,       
                           ENABLE);
  GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_13;//PC13
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //ÏÂÀ­Ä£Ê½
	PWR_BackupAccessCmd(ENABLE);
	RCC_LSEConfig(RCC_LSE_OFF);
	BKP_TamperPinCmd(DISABLE);
	PWR_BackupAccessCmd(DISABLE);
 	GPIO_Init(GPIOC, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure); 	
		
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_9;//PB9
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOB, &GPIO_InitStructure); 	
	
}

端口初始化

//向手柄发送命令

 void PS2_Cmd(u8 CMD)
{
	volatile u16 ref=0x01; 
	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;
	}
}

//读取手柄数据

 void PS2_ReadData()
{
	volatile u8 byte=0; 
	volatile u16 ref=0x01;
	//PS2 解码通讯
	CS_L;
	PS2_Cmd(Comd[0]); //开始命令
	 PS2_Cmd(Comd[1]); //请求数据 
	 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;
}

上面两个函数分别为主机向手柄发送数据、手柄向主机发送数据。手柄向主机发送的数 据缓存在数组 Data[ ]中,数组中共有 9 个元素,每个元素的意义请见表 1。
//对读出来的 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;	//没有任何按键按下
}	

8 位数 Data[3]与 Data[4],分别对应着 16 个按键的状态,按下为 0,未按下为 1。通过
对这两个数的处理,得到按键状态并返回键值。
编写主函数:
PS2 解码通讯

int main(void)
{
	u8 key;
	Stm32_Clock_Init(9); //系统时钟设置
	delay_init(72);	//延时初始化
	uart_init(72,9600);	//串口 1 初始化
	PS2_Init();	
	while(1)	
	{	
		key=PS2_DataKey();
		if(key!=0)	//有按键按下
		{	
			printf("	\r\n   %d  is  pressed  \r\n",key);
		}
		printf(" %5d %5d %5d %5d\r\n",PS2_AnologData(PSS_LX),PS2_AnologData(PSS_LY), 
	    PS2_AnologData(PSS_RX),PS2_AnologData(PSS_RY) );
		delay_ms(50);
	}
}

上面两个函数分别为主机向手柄发送数据、手柄向主机发送数据。手柄向主机发送的数 据缓存在数组 Data[ ]中,数组中共有 9 个元素,每个元素的意义请见表 1。

//对读出来的 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;	//没有任何按键按下
}	

当有按键按下时,输出按键值

4 下载与测试

编译程序并下载。按 ANALOG 可以改变模式,先选择红灯模式,遥控器上指示灯为红 色。串口输出的模拟值为 127 或 128,当晃动摇杆时,相应的模拟值就会改变,这时摇杆按 键可以按下,可以输出键值,见图 2。
在这里插入图片描述

在这里插入图片描述

图 2 按下“△”,输出对应的键值“13”。
在这里插入图片描述
图 3 按“ANALOG”,改为绿灯模式,手柄上指示灯变为“绿色”,串口输出的模拟值为

“255”,轻轻晃动摇杆,模拟值不变。

在这里插入图片描述

图 4 我们将右摇杆向上推到极限,这时串口输出“13 is pressed”,键值对应“△”,但模

拟的值不改变。
在这里插入图片描述

图 5 “红灯模式”和“绿灯模式”的主要区别就在与摇杆模拟值的输出。
在这里插入图片描述

测试案例2

ps2 函数

/*********************************************************
File:PS2驱动程序
Description: PS2驱动程序
**********************************************************/	 
u16 Handkey;
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
	};	//按键值与按键明



void PS2_Init(void)
{
    //输入  DI->PB9   		
	GPIO_InitTypeDef GPIO_InitStructure; 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable , ENABLE);
  GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable , ENABLE);
  GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_14;//PA14
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //下拉模式
	//PWR_BackupAccessCmd(ENABLE);
	//RCC_LSEConfig(RCC_LSE_OFF);
	//BKP_TamperPinCmd(DISABLE);
	//PWR_BackupAccessCmd(DISABLE);
 	GPIO_Init(GPIOA,&GPIO_InitStructure);
    //  DO->PA12    CS->PA8  CLK->PB9
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出模式
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure); 	
		
}


//向手柄发送命令
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);
		Delay_us(50);
		CLK_L;
		//delay_us(50);
		Delay_us(50);
		CLK_H;
		if(DI)
			Data[1] = ref|Data[1];
	}
}
//判断是否为红灯模式
//返回值;0,红灯模式
//		  其他,其他模式
u8 PS2_RedLight(void)
{
	CS_L;
	PS2_Cmd(Comd[0]);  //开始命令
	PS2_Cmd(Comd[1]);  //请求数据
	CS_H;
	if( Data[1] == 0X73)   return 0 ;
	else return 1;

}
//读取手柄数据
void PS2_ReadData(void)
{
	volatile u8 byte=0;
	volatile u16 ref=0x01;

	CS_L;

	PS2_Cmd(Comd[0]);  //开始命令
	PS2_Cmd(Comd[1]);  //请求数据

	for(byte=2;byte<9;byte++)          //开始接受数据
	{
		for(ref=0x01;ref<0x100;ref<<=1)
		{
			CLK_H;
			CLK_L;
			//delay_us(50);
			Delay_us(50);
			CLK_H;
		      if(DI)
		      Data[byte] = ref|Data[byte];
		}
       //delay_us(50);
		   Delay_us(50);
	}
	CS_H;	
}

//对读出来的PS2的数据进行处理      只处理了按键部分         默认数据是红灯模式  只有一个按键按下时
//按下为0, 未按下为1

u8 PS2_DataKey()
{
//	static uint8 temp=1;
	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)
		{
			//temp=(Handkey&(1<<(MASK[index]-1)));
			return index+1;
		}
		
	}

	return 0;          //没有任何按键按下
}

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

//清除数据缓冲区
void PS2_ClearData()
{
	u8 a;
	for(a=0;a<9;a++)
		Data[a]=0x00;
}
/**************************************************************************
函数功能:获取PS2无线手柄按键值,定时器每隔段时间进行读取PS2_DataKey()
入口参数:无
返回  值:无 
**************************************************************************/
void scan_ps2(void)
{

	if (flag_scan_ps2)   //定时时间到
	{
		flag_scan_ps2 = 0;
		key = PS2_DataKey();
		switch(key)
				{
					case PSB_PAD_UP:CPWM[1]+=10;if(CPWM[1]>=2300)   CPWM[1]=2300;dj1+=10;if(dj1>=2200)dj1=2200;sprintf(buf,"#1P%dT1\r\n",dj1);UART_PutStr(USART3,buf);break; 
					case PSB_PAD_DOWN:CPWM[1]-=10;if(CPWM[1]<=700)  CPWM[1]=700;dj1-=10;if(dj1<=700)dj1=700;sprintf(buf,"#1P%dT1\r\n",dj1);UART_PutStr(USART3,buf);break;
					case PSB_PAD_LEFT:CPWM[2]+=10;if(CPWM[2]>=2300) CPWM[2]=2300;dj2+=10;if(dj2>=2200)dj2=2200;sprintf(buf,"#2P%dT1\r\n",dj2);UART_PutStr(USART3,buf);break; 
					case PSB_PAD_RIGHT:CPWM[2]-=10;if(CPWM[2]<=700) CPWM[2]=700;dj2-=10;if(dj2<=700)dj2=700;sprintf(buf,"#2P%dT1\r\n",dj2);UART_PutStr(USART3,buf);break;
		
					case PSB_TRIANGLE:CPWM[3]+=10;if(CPWM[3]>=2300) CPWM[3]=2300;dj3+=10;if(dj3>=2200)dj3=2200;sprintf(buf,"#3P%dT1\r\n",dj3);UART_PutStr(USART3,buf);break; 
					case PSB_CROSS:CPWM[3]-=10;if(CPWM[3]<=700)  CPWM[3]=700;dj3-=10;if(dj3<=700)dj3=700;sprintf(buf,"#3P%dT1\r\n",dj3);UART_PutStr(USART3,buf);break;
					case PSB_PINK:CPWM[4]+=10;if(CPWM[4]>=2300)  CPWM[4]=2300;dj4+=10;if(dj4>=2200)dj4=2200;sprintf(buf,"#4P%dT1\r\n",dj4);UART_PutStr(USART3,buf);break; 
					case PSB_CIRCLE:CPWM[4]-=10;if(CPWM[4]<=700) CPWM[4]=700;dj4-=10;if(dj4<=700)dj4=700;sprintf(buf,"#4P%dT1\r\n",dj4);UART_PutStr(USART3,buf);break;

					case PSB_L1:CPWM[5]+=10;if(CPWM[5]>=2300) CPWM[5]=2300;dj5+=10;if(dj5>=2200)dj5=2200;sprintf(buf,"#5P%dT1\r\n",dj5);UART_PutStr(USART3,buf);break; 
					case PSB_L2:CPWM[5]-=10;if(CPWM[5]<=700)  CPWM[5]=700;dj5-=10;if(dj5<=700)dj5=700;sprintf(buf,"#5P%dT1\r\n",dj5);UART_PutStr(USART3,buf);break;
					case PSB_R1:CPWM[6]+=10;if(CPWM[6]>=2300) CPWM[6]=2300;dj6+=10;if(dj6>=2200)dj6=2200;sprintf(buf,"#6P%dT1\r\n",dj6);UART_PutStr(USART3,buf);break; 
					case PSB_R2:CPWM[6]-=10;if(CPWM[6]<=700)  CPWM[6]=700;dj6-=10;if(dj6<=700)dj6=700;sprintf(buf,"#6P%dT1\r\n",dj6);UART_PutStr(USART3,buf);break;
					default:break;
				}

		}
	}

main 函数

uint16 CPWM[9]= {1500,1500,1500,1500,1500,1500,1500,1500,1500};
unsigned int dj1=1500;
unsigned int dj2=1500;
unsigned int dj3=1500;
unsigned int dj4=1500;
unsigned int dj5=1500;
unsigned int dj6=1600;
 char buf[30];
uint16 UartRec[9]; 			  //上位机字符串解析都放在这个数组里
unsigned char flag_uart1_rev=0;
unsigned char flag_uart2_rev=0;
char uart2_buf[255];
char uart1_buf[255];
unsigned char i=0;
u32 key, key_bak;
uint8 flag_vpwm=0;
unsigned char flag_scan_ps2;
void scan_ps2(void);
int main(void)
{   
	SysTick_Init();		//系统滴答定时器初始化 	
  	Servor_GPIO_Config();		
	LED_Init();	      //LED 初始化函数
	Beep_Init();      //蜂鸣器初始化函数
	Beep_Test();      //蜂鸣器测试
	Led_Test(); 
	Timer_Init();
	Timer_ON();
	PS2_Init();
	Uart_Init(1);	
	Uart_Init(3);	
  	USART3_Config(115200);
	USART_Config(USART1,115200);
	while (1)
	{	
		scan_ps2();
	}
} 

由于设备坏了,就不测试了,自己测试吧!!!1

快速学习Stm32舵机控制板控制多个舵机运动以及调速

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闰土小蒋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>