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

2023-10-27

学习 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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

学习PS2无线手柄解码通讯手册 的相关文章

随机推荐

  • LeetCode-Python-377. 组合总和 Ⅳ

    给定一个由正整数组成且不存在重复数字的数组 找出和为给定目标正整数的组合的个数 示例 nums 1 2 3 target 4 所有可能的组合为 1 1 1 1 1 1 2 1 2 1 1 3 2 1 1 2 2 3 1 请注意 顺序不同的序
  • WPF拖动事件

    1 定义一个Dragthumb模板
  • QT 文件操作 QFile

    目录 QFile类介绍 写入数据到txt文件 实例代码 从txt文件中读取所有数据 实例代码 从txt文件中一行一行读取数据 实例代码 部分函数参数及作用 QFile类介绍 QIODevice 类是 Qt 中所有 I O 设备的基础接口类
  • 欧拉角万向节死锁

    https malei0311 gitbooks io threejs doc cn content getstart euler angle gimbal lock html 欧拉角万向节死锁 什么是欧拉角 Eular Angles 欧拉
  • ctf练习题

    攻防世界 WEB Challenge area Web php include WEB Challenge area upload1 WEB Challenge area ics 06 WEB Challenge area baby web
  • 保险项目测试,保险分类

    项目紧任务重时间少的996下 我还是需要抽个空来整理一下子 理一理自己的逻辑 保险项目 保险项目中关键词 保险主要分为社保和商保 社保 商保 商保细分 测试保险项目 保单生命周期 项目测试流程 什么是短期健康险 实际项目中具体负责了哪些测试
  • 探索【Stable-Diffusion WEBUI】的插件:ControlNet 1.1

    文章目录 零 前言 一 ControlNet 二 ControlNet v1 1 2 1 模型 2 2 新版界面 2 3 预处理器 三 偷懒 零 前言 本篇主要提到ControlNet新版本的使用 和旧版本的变化 并偷懒参考了别人很不错的文
  • 基于web的图片资源库管理系统的设计与实现

    技术 Java JSP等 摘要 本系统是一种基于B S架构的图片资源管理系统 它采用目前最流行的Java语言编写 用到了当今先进的技术如 JSP技术 Hibernate Spring Struts框架等来实现该系统 系统分为五大模块 图片夹
  • sqli-lab-less8

    sqli lab less8 一 靶标地址 Less 8 GET Blind Boolian Based Single Quotes 单引号布尔盲注 http 127 0 0 1 sqli less 8 二 漏洞探测 由于探测的fuzz参数
  • 【SLAM】A-LOAM 算法部署与测试(Win10 + VMWare + Ubuntu18.04)

    基础环境 ubuntu及ROS安装 略 安装完ROS以后 默认已经安装好了PCL和Eigen库 安装Ceres 下载Ceres源文件 Vmware没有网络 到下面的网址手动下载安装包 https github com ceres solve
  • 【解决】使用uniapp做App,首页需要显示实时时间及日期,一秒跳动一下

    需求 App首页放置一个实时时间 效果图如下 每秒跳一下 解决 在onShow 里面使用定时器获取当前时间 在onHide 里面进行定时器清除 示例代码如下
  • Source Insight (SI) 变量、函数、宏定义变成黑色,无法快速查看调用的几种解决方法

    Source Insight 变量 函数 宏定义变成黑色 无法快速查看调用的几种解决方法 方法一 同步SI与本地的代码 方法二 重构SI工程 其他解决方法 在source insight中 一般即使鼠标点在函数或者变量处 context w
  • Puppeteer介绍

    前面已经介绍了Cypress框架 为什么还要介绍puppeteer呢 因为puppeteer支持的一些功能cypress不支持 例如多个tab页窗口切换的场景 同一个测试场景中访问不同域页面等 另外 puppeteer有google大厂支持
  • 【数据结构入门】堆(Heap)

    文章目录 一 堆的结构及实现 重要 1 1 二叉树的顺序结构 1 2 堆的概念及结构 1 3 堆的实现 1 3 1 堆的向下调整算法 1 3 2 向下调整算法的时间复杂度 1 3 3 堆的创建 向下调整 1 3 4 堆排序 1 3 5 建堆
  • 解决cmake的时候json;json-c not found

    转自 http blog csdn net u013393502 article details 49226531 zmap的安装可以到官方网站https www zmap io 去看看 点击download 提供了两种安装方式 软件包方式
  • SpringBoot1.5.8 +Activiti6 不自动创建表

    SpringBoot1 5 8 Activiti6 自动建表配置 activiti 工作流程引擎 自动检查 部署流程定义文件 spring activiti check process definitions true 自动更新数据库结构
  • proTable--Request获取数据--actionRef 触发更新

    最近在做proTable 简单记录一下 便于后面用的时候看 1 Requeat 获取数据 proTable没有设置从页数或者总数据数的入口 这时候就要用到request request 能自动获取表格数据 还有一个很重要的功能就是支持自动分
  • 基于Java Agent内存马

    前言 前面说完最常见的基于Servlet API型内存马 这里再提一下Java Agent内存马 像冰蝎 哥斯拉工具的内存马注入都是基于 agent 的 以后用到再分析 大的思路 第一种是通过permain 函数实现 定义一个 MANIFE
  • 元组的操作

    x 100 print x type x y 100 print y type y z 10 20 30 print z type z a b c z print c c b c c b print c c tuple1 男 10 20 3
  • 学习PS2无线手柄解码通讯手册

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