热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

PS2手柄通讯协议解析附资料和源码

文章目录一.PS2介绍二.PS2通讯协议介绍(1)PS2端口介绍(2)PS2通讯过程三.基于STM32的PS2通信源码四.文


文章目录

  • 一.PS2介绍
  • 二.PS2通讯协议介绍
    • (1)PS2端口介绍
    • (2)PS2通讯过程
  • 三.基于STM32的PS2通信源码
  • 四.文档与源码下载链接


一.PS2介绍




今天就带大家来认识一下PS2的通讯协议,如果你需要用PS2无线手柄搭配单面机来DIY制作,那么千万别错过这篇文章。

首先介绍一下我们今天的主角-----PS2手柄。 PS2手柄是日本SONY公司的PlayStation2 游戏机的遥控手柄。索尼的 PSX系列游戏主机在全球都很畅销。不知什么时候便有人打起 PS2手柄的主意,破解了通讯协议,使得手柄可以接在其他器件上遥控使用,比如遥控我们熟悉的机器人。突出的特点是这款手柄性价比极高,按键丰富,方便扩展到其它应用中。


二.PS2通讯协议介绍

PS2采用的是SPI通信协议,SPI是串行外设接口的缩写,是一种高速的、全双工、同步的通信总线,并且在芯片的管脚上只占用四根线(DI、DO、CS、CLK),节约了芯片的管脚,同时为PCB的布局上节省空间。


(1)PS2端口介绍


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

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

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

3.NC:空端口。

4.GND:电源地。

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

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

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

8.NC:空端口。

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


(2)PS2通讯过程


1. CS线在通讯期间拉低,通信过程中CS信号线在一串数据(9个字节,每个字节为8位)发送完毕后才会拉高,而不是每个字节发送完拉高。

2. DO、DI在在CLK时钟的下降沿完成数据的发送和读取。


下降沿:数字电平从高电平(数字“1”)变为低电平(数字“0”)的那一瞬间叫作下降沿。


3. CLK的每个周期为12us。若在某个时刻,CLK处于下降沿,若此时DO为高电平则取“1”,低电平则取“0”。连续读8次则得到一个字节byte的数据,连续读9个字节就能得到一次传输周期所需要的数据。DI也是一样的,发送和传输同时进行。

具体的通讯过程如下:
在这里插入图片描述
以STM32为例:

1. 首先STM32拉低CS片选信号线,然后在每个CLK的下降沿读一个bit,每读八个bit(即一个byte)CLK拉高一小段时间,一共读九组bit。

2. 第一个byte是STM32发给接收器命令“0X01” 。

3. PS2手柄会在第二个byte回复它的ID(0x41=绿灯模式,0x73=红灯模式),同时第二个byte时STM32发给PS2一个0x42请求数据。


红灯模式时 : 左右摇杆发送模拟值,0x00~0xFF 之间,且摇杆按下的 键值 L3 、 R3 有效;
绿灯模式时 : 左右摇杆模拟值为无效,推到极限时,对应发送 UP、RIGHT、DOWN、 LEFT、△、○、╳、□,按键 L3 、 R3 无效;


4. 第三个byte PS2 会给主机发送 “0x5A” 告诉STM32数据来了。

5. 从第四个byte开始全是接收器给主机发送数据,每个byte定义如上图,当有按键按下,对应位为“0 ”,例如当键“SELECT”被按下时, Data[3]=11111110。

对于整个通讯过程,你理解成下面的一段对话:


对于整个通讯过程,你理解成下面的一段对话:
首先,拉低CS,表示开始数据通信
byte 0 :
STM32(DO) : 0x01 ------------------------- [现在开始通信]
PS2手柄(DI) : 空 ---------------------------- [空]
byte 1 :
STM32(DO) : 0x42 -------------------------- [请求发送数据]
PS2手柄(DI) : 红灯0x73
            绿灯0X41 ---------------------[现在的ID]
byte 2 :
STM32(DO) : 空 ------------------------------ [空]
PS2手柄(DI) : 0X5A ------------------------- [数据来了]
byte 3 :
STM32(DO) : 0X00~0XFF ------------------ [右侧小震动电机是否开启]
PS2手柄(DI) : 00000000~11111111 ------- [SELECT、 L3 、 R3、 START 、 UP、 RIGHT、 DOWN、 LEFT 是否被按下,若被按下对应位为0]
byte 4 :
STM32(DO) : 0X00~0XFF ------------------ [左侧大震动电机振动幅度]
PS2手柄(DI) : 00000000~11111111 ------- [L2 、 R2、L1 、R1、△、○、╳、□ 是否被按下,若被按下对应位为0]
byte 5 :
STM32(DO) : 空 -------------------------------- [空]
PS2手柄(DI) : 0X00~0XFF ------------------ [左侧X轴摇杆模拟量]
byte 6 :
STM32(DO) : 空 -------------------------------- [空]
PS2手柄(DI) : 0X00~0XFF ------------------ [左侧Y轴摇杆模拟量]
byte 7 :
STM32(DO) : 空 -------------------------------- [空]
PS2手柄(DI) : 0X00~0XFF ------------------ [右侧X轴摇杆模拟量]
byte 8 :
STM32(DO) : 空 -------------------------------- [空]
PS2手柄(DI) : 0X00~0XFF ------------------ [右侧Y轴摇杆模拟量]
注:在手柄通信前还需要一系列的初始化(是否启动振动电机、是否进行锁存等),详情可以参考下面代码。当然,不进行初始化也是可以的,手柄会默认之前的配置。


注意:
1.模拟量只对红灯模式下有效,绿灯模式下摇杆推至极限分别对应 UP、RIGHT、DOWN、 LEFT、△、○、╳、□ 。
2. L3、R3只对红灯模式下有效,在绿灯模式下无效。



三.基于STM32的PS2通信源码

//采用模拟SPI通信
/*DI->PB12;DO->PB13;CS->PB14;CLK->PB15
*/

void PS2_Init(void) {
// 输入 DI->PB12RCC->APB2ENR|&#61;1<<3; // 使能 PORTB 时钟 GPIOB->CRH&&#61;0XFFF0FFFF;//PB12 设置成输入 默认下拉GPIOB->CRH|&#61;0X00080000; // DO->PB13 CS->PB14 CLK->PB15 RCC->APB2ENR|&#61;1<<3; // 使能 PORTB 时钟 GPIOB->CRH&&#61;0X000FFFFF;GPIOB->CRH|&#61;0X33300000; //PB13、 PB14 、 PB15 推挽输出 } //端口初始化&#xff0c;PB12 为输入&#xff0c;PB13 、PB14 、PB15 为输出。// 向手柄发送命令
void PS2_Cmd(u8CMD)
{volatile u16 ref&#61;0x01; Data[1]&#61;0; for(ref&#61;0x01;ref<0x0100;ref<<&#61;1) {if(ref&CMD) {DO_H; // 输出一位控制位}else DO_L; CLK_H; // 时钟拉高 delay_us(10); CLK_L; delay_us(10); CLK_H; if(DI) {Data[1]&#61;ref|Data[1];}}delay_us(16);
}// 判断是否为红灯模式&#xff0c;0x41&#61;模拟绿灯&#xff0c;0x73&#61;模拟红灯
// 返回值&#xff1b;0&#xff0c;红灯模式
// 其他&#xff0c;其他模式
u8PS2_RedLight(void)
{CS_L; PS2_Cmd(Comd[0]); // 开始命令PS2_Cmd(Comd[1]); // 请求数据CS_H; if( Data[1]&#61;&#61; 0X73) return 0&#xff1b;else return 1&#xff1b;
}// 读取手柄数据
void PS2_ReadData(void)
{volatile u8 byte&#61;0; volatile u16 ref&#61;0x01; CS_L; PS2_Cmd(Comd[0]); // 开始命令PS2_Cmd(Comd[1]); // 请求数据for(byte&#61;2;byte<9;byte&#43;&#43;) // 开始接受数据 {for(ref&#61;0x01;ref<0x100;ref<<&#61;1) { CLK_H; delay_us(10); CLK_L; delay_us(10); CLK_H; if(DI) {Data[byte]&#61; ref|Data[byte];}}delay_us(16);}CS_H;
} /*
上面两个函数分别为主机向手柄发送数据、手柄向主机发送数据。手柄向主机发送的数据缓存在数组 Data[]中&#xff0c;
数组中共有9个元素&#xff0c;每个元素的意义请见表1。
还有一个函数是用来判断手柄的发送模式&#xff0c;也就是判断 ID(红灯还是绿灯模式) 即 Data[1]的值。
*/
// 对读出来的 PS2 的数据进行处理,只处理按键部分
//按下为0&#xff0c;未按下为1
u8PS2_DataKey()
{u8 index; PS2_ClearData(); PS2_ReadData(); Handkey&#61;(Data[4]<<8)|Data[3]; // 这是 16个按键 按下为 0 &#xff0c; 未按下为 1 for(index&#61;0;index<16;index&#43;&#43;) {if((Handkey&(1<<(MASK[index]-1)))&#61;&#61;0) returnindex&#43;1;}return 0; // 没有任何按键按下
}// 得到一个摇杆的模拟量 范围 0~256
u8PS2_AnologData(u8 button)
{return Data[button];
}// 清除数据缓冲区
void PS2_ClearData()
{u8 a; for(a&#61;0;a<9;a&#43;&#43;) {Data[a]&#61;0x00;}
}/*
8 位数 Data[3]与 Data[4]&#xff0c;分别对应着 16个按键的状态&#xff0c;按下为 0&#xff0c;未按下为 1。
通过 对这两个数的处理&#xff0c;得到按键状态并返回键值。
另一个函数的功能就是返回模拟值&#xff0c;只有在“红灯模式”下值才是有效的&#xff0c;拨动摇杆&#xff0c; 值才会变化&#xff0c;这些值分别存储在 Data[5]、Data[6]、
Data[7]、 Data[8]。
*/
//手柄配置初始化&#xff1a;
void PS2_ShortPoll(void)
{CS_L; delay_us(16); PS2_Cmd(0x01); PS2_Cmd(0x42); PS2_Cmd(0X00); PS2_Cmd(0x00); PS2_Cmd(0x00); CS_H; delay_us(16);
}//进入配置
void PS2_EnterConfing(void)
{CS_L;delay_us(16); PS2_Cmd(0x01); PS2_Cmd(0x43); PS2_Cmd(0X00); PS2_Cmd(0x01); PS2_Cmd(0x00); PS2_Cmd(0X00);PS2_Cmd(0X00); PS2_Cmd(0X00); PS2_Cmd(0X00); CS_H; delay_us(16);
}// 发送模式设置
void PS2_TurnOnAnalogMode(void)
{CS_L; PS2_Cmd(0x01); PS2_Cmd(0x44); PS2_Cmd(0X00); PS2_Cmd(0x01);//analog&#61;0x01;digital&#61;0x00 软件设置发送模式 PS2_Cmd(0xEE);//Ox03 锁存设置&#xff0c;即不可通过按键“MODE ”设置模式。 //0xEE 不锁存软件设置&#xff0c;可通过按键“MODE ”设置模式。 PS2_Cmd(0X00); PS2_Cmd(0X00); PS2_Cmd(0X00); PS2_Cmd(0X00); CS_H; delay_us(16);
}// 振动设置
void PS2_VibrationMode(void)
{CS_L; delay_us(16); PS2_Cmd(0x01); PS2_Cmd(0x4D); PS2_Cmd(0X00); PS2_Cmd(0x00); PS2_Cmd(0X01); CS_H; delay_us(16);
}// 完成并保存配置
void PS2_ExitConfing(void)
{CS_L;delay_us(16);PS2_Cmd(0x01);PS2_Cmd(0x43); PS2_Cmd(0X00);PS2_Cmd(0x00); PS2_Cmd(0x5A); PS2_Cmd(0x5A); PS2_Cmd(0x5A);PS2_Cmd(0x5A); PS2_Cmd(0x5A); CS_H; delay_us(16);
}// 手柄配置初始化
void PS2_SetInit(void)
{PS2_ShortPoll();PS2_ShortPoll();PS2_ShortPoll(); PS2_EnterConfing(); // 进入配置模式 PS2_TurnOnAnalogMode(); // “红绿灯”配置模式&#xff0c;并选择是否保存 PS2_VibrationMode(); // 开启震动模式 PS2_ExitConfing(); // 完成并保存配置
}
/*
可以看出配置函数就是发送命令&#xff0c;发送这些命令后&#xff0c;手柄就会明白自己要做什么了&#xff0c;发送命令时&#xff0c;不需要考虑手柄发来的信息。
手柄配置初始化&#xff0c;PS2_ShortPoll()被执行了3次&#xff0c;主要是为了建立和恢复连接。
具体的配置方式请看注释。
*/

void PS2_Vibration(u8motor1,u8motor2)
{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);
}
//只 有 在 初 始 化 函 数 void PS2_SetInit(void) 中 &#xff0c; 对 震 动 电 机 进 行 了初 始 化 &#xff08;PS2_VibrationMode();//开启震动模式&#xff09;&#xff0c;这个函数命令才会被执行。

四.文档与源码下载链接

1.PS2参考文档CSDN&#xff1a;PS2解码通讯手册.pdf

2.这里还有一份我写的源码&#xff1a;PS2源码HAL库&#43;CubeMX&#43;Stm32F103C8 注意&#xff1a;


  • 里面除了PS2的源码还加了延时实验的源码。由于HAL库本身没有微秒级的延时&#xff0c;所以需要自己写微秒级的延时函数&#xff0c;详情看源码。至于延时的原理参考另一篇博客&#xff1a;Stm32延时与计时方法&#xff08;HAL库&#xff09;。
  • 为防止与PS2通信过快而乱码导致延迟&#xff0c;需要在主函数的while(1)中延时50ms&#xff0c;即加一句delay_ms(50)。&#xff08;原工程中没有&#xff0c;需要自行加上&#xff09;

推荐阅读
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • FeatureRequestIsyourfeaturerequestrelatedtoaproblem?Please ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
author-avatar
手机用户2602926791
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有