学习 PS2 无线手柄的使用方法,将市场上 PS2 手柄通过解码应用到我们自己产品之中,比如控制智能车,机械臂等等任何涉及无线通信控制的一些diy场景。本次主要让大家了解 PS2 无线手柄的工作原理,以及掌握 PS2 无线手柄的使用并最终通过串口打印各按键的键值。
常见用途
diy 产品,舵机,寄存器 一些无线控制的设置和产品
手柄原理
ps2 由手柄与接收器两部分组成,手柄主要负责发送按键信息。都接通电源并打开手柄 开关时,手柄与接收器自动配对连接,在未配对成功的状态下,接收器绿灯闪烁,手柄上的 灯也会闪烁,配对成功后,接收器上绿灯常亮,手柄上灯也常亮,这时可以按“MODE” 键,选择手柄发送模式。
接收、、和主机(单片机)相连,实现主机与手柄之间的通讯。
实物参考如下图
接收器引脚输出,
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|
DI/DAT | DO/CMD | NC | GND | VDD | CS/SEL | CLK | NC | ACK |
数据 | 命令 | 空/不接 | 负 | 通讯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