PS2手柄通讯协议解析—附资料和源码「建议收藏」

PS2手柄通讯协议解析—附资料和源码「建议收藏」文章目录一.PS2介绍二.PS2通讯协议介绍一.PS2介绍今天就带大家来认识一下PS2的通讯协议,如果你需要用PS2无线手柄搭配单面机来DIY制作,那么千万别错过这篇文章。首先介绍一下我们今天的主角–PS2手柄。PS2手柄是日本SONY公司的PlayStation2游戏机的遥控手柄。索尼的PSX系列游戏主机在全球都很畅销。不知什么时候便有人打起PS2手柄的主意,破解了通讯协议,使…

大家好,又见面了,我是你们的朋友全栈君。
PS2手柄通讯协议解析---附资料和源码「建议收藏」

一.PS2介绍

PS2手柄通讯协议解析---附资料和源码「建议收藏」

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

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

二.PS2通讯协议介绍

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

(1)PS2端口介绍

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通讯过程

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轴摇杆模拟量]
注:在手柄通信前还需要一系列的初始化(是否启动振动电机、是否进行锁存等),详情可以参考下面代码。当然,不进行初始化也是可以的,手柄会默认之前的配置。

PS2手柄通讯协议解析---附资料和源码「建议收藏」

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

三.基于STM32的PS2通信源码

//采用模拟SPI通信
/*DI->PB12; DO->PB13; CS->PB14; CLK->PB15 */
void PS2_Init(void) { 
    
// 输入 DI->PB12
 RCC->APB2ENR|=1<<3; // 使能 PORTB 时钟 
 GPIOB->CRH&=0XFFF0FFFF;//PB12 设置成输入 默认下拉
 GPIOB->CRH|=0X00080000; 
 // DO->PB13 CS->PB14 CLK->PB15 
 RCC->APB2ENR|=1<<3; // 使能 PORTB 时钟 
 GPIOB->CRH&=0X000FFFFF;
 GPIOB->CRH|=0X33300000; //PB13、 PB14 、 PB15 推挽输出 
  } 
  //端口初始化,PB12 为输入,PB13 、PB14 、PB15 为输出。
  
// 向手柄发送命令
void PS2_Cmd(u8CMD) 
{ 
   
  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(10); 
    CLK_L; 
    delay_us(10); 
    CLK_H; 
    if(DI) 
    { 
   Data[1]=ref|Data[1];}
  }
 delay_us(16);
}

// 判断是否为红灯模式,0x41=模拟绿灯,0x73=模拟红灯 
// 返回值;0,红灯模式 
// 其他,其他模式
u8PS2_RedLight(void) 
{ 
   
  CS_L; 
  PS2_Cmd(Comd[0]); // 开始命令
  PS2_Cmd(Comd[1]); // 请求数据
  CS_H; 
  if( Data[1]== 0X73) return 0else 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; 
      delay_us(10); 
      CLK_L; 
      delay_us(10); 
      CLK_H; 
      if(DI) 
      { 
   Data[byte]= ref|Data[byte];}
    }
    delay_us(16);
  }
  CS_H;
}   

/* 上面两个函数分别为主机向手柄发送数据、手柄向主机发送数据。手柄向主机发送的数据缓存在数组 Data[]中, 数组中共有9个元素,每个元素的意义请见表1。 还有一个函数是用来判断手柄的发送模式,也就是判断 ID(红灯还是绿灯模式) 即 Data[1]的值。 */


// 对读出来的 PS2 的数据进行处理,只处理按键部分
//按下为0,未按下为1
u8PS2_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) 
    returnindex+1;
  }
  return 0; // 没有任何按键按下
}

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


// 清除数据缓冲区
void PS2_ClearData() 
{ 
   
  u8 a; 
  for(a=0;a<9;a++) 
    { 
   Data[a]=0x00;}
}

/* 8 位数 Data[3]与 Data[4],分别对应着 16个按键的状态,按下为 0,未按下为 1。 通过 对这两个数的处理,得到按键状态并返回键值。 另一个函数的功能就是返回模拟值,只有在“红灯模式”下值才是有效的,拨动摇杆, 值才会变化,这些值分别存储在 Data[5]、Data[6]、 Data[7]、 Data[8]。 */


//手柄配置初始化: 
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=0x01;digital=0x00 软件设置发送模式 
  PS2_Cmd(0xEE);//Ox03 锁存设置,即不可通过按键“MODE ”设置模式。 //0xEE 不锁存软件设置,可通过按键“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(); // “红绿灯”配置模式,并选择是否保存 
  PS2_VibrationMode(); // 开启震动模式 
  PS2_ExitConfing(); // 完成并保存配置
}
/* 可以看出配置函数就是发送命令,发送这些命令后,手柄就会明白自己要做什么了,发送命令时,不需要考虑手柄发来的信息。 手柄配置初始化,PS2_ShortPoll()被执行了3次,主要是为了建立和恢复连接。 具体的配置方式请看注释。 */
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) 中 , 对 震 动 电 机 进 行 了初 始 化 (PS2_VibrationMode();//开启震动模式),这个函数命令才会被执行。 

四.文档与源码下载链接

1.PS2参考文档CSDN:PS2解码通讯手册.pdf

2.这里还有一份我写的源码:PS2源码HAL库+CubeMX+Stm32F103C8 注意:

  • 里面除了PS2的源码还加了延时实验的源码。由于HAL库本身没有微秒级的延时,所以需要自己写微秒级的延时函数,详情看源码。至于延时的原理参考另一篇博客:Stm32延时与计时方法(HAL库)
  • 为防止与PS2通信过快而乱码导致延迟,需要在主函数的while(1)中延时50ms,即加一句delay_ms(50)。(原工程中没有,需要自行加上)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/130068.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • [Python图像处理] 十七.图像锐化与边缘检测之Roberts算子、Prewitt算子、Sobel算子和Laplacian算子

    [Python图像处理] 十七.图像锐化与边缘检测之Roberts算子、Prewitt算子、Sobel算子和Laplacian算子图像锐化和边缘提取技术可以消除图像中的噪声,提取图像信息中用来表征图像的一些变量,为图像识别提供基础。通常使用灰度差分法对图像的边缘、轮廓进行处理,将其凸显。本文分别采用Laplacian算子、Robert算子、Prewitt算子和Sobel算子进行图像锐化边缘处理实验。本文主要讲解灰度线性变换,基础性知识希望对您有所帮助。1.Roberts算子2.Prewitt算子3.Sobel算子4.Laplacian算子5.总结代码

    2025年7月1日
    2
  • calendar java_java中Calendar类的使用讲解

    calendar java_java中Calendar类的使用讲解Calendar类是我们在工作中经常用到时间相关的一个工具类;比如月初、月末、年初、年末、指定月份所在季度的季末等操作,对它有更深入的了解,在工作中会起到事半功倍的效果,下面就来了解一下吧!!!**一.Calendar类概述Calendar是日历类,该类将所有可能用到的时间信息封装为静态成员变量,方便获取。常用方法如下如下://根据日历的规则,为给定的日历字段添加或减去指定的时间量。abstrac…

    2022年9月14日
    2
  • MySQL concat函数的使用

    MySQL concat函数的使用MySQLconcat函数是MySQL数据库中众多的函数之一,下文将对MySQLconcat函数的语法和使用进行说明,供您参考和学习。MySQLconcat函数使用方法:CONCAT(str1,str2,…) 返回结果为连接参数产生的字符串。如有任何一个参数为NULL,则返回值为NULL。注意:如果所有参数均为非二进制字符串,则结果为非二进制字符串。 

    2022年6月6日
    85
  • ac测评题库_用标号法求网络最大流

    ac测评题库_用标号法求网络最大流给定一个包含 n 个点 m 条边的有向图,并给定每条边的容量,边的容量非负。图中可能存在重边和自环。求从点 S 到点 T 的最大流。输入格式第一行包含四个整数 n,m,S,T。接下来 m 行,每行三个整数 u,v,c,表示从点 u 到点 v 存在一条有向边,容量为 c。点的编号从 1 到 n。输出格式输出点 S 到点 T 的最大流。如果从点 S 无法到达点 T 则输出 0。数据范围2≤n≤10000,1≤m≤100000,0≤c≤10000,S≠T输入样例:7 14 1 71

    2022年8月9日
    6
  • c++ 指针赋值「建议收藏」

    c++ 指针赋值「建议收藏」
    一、一级指针
     
    char*a=”a”;
    char *b=”b”;
     
    a=b;//这个是b把存的内容b给a,此时a的内容是b,所以指针与指针赋值实际也是传值,和普通变量一样。若a的内容变为c,则b的内容还是b
     
    二、二级指针
     
    char**c=&a;
     
    char**d=&b;
     
    c=d;//这样是把b的地址给了c,此时d和c都指向b,若c的内容发生改变

    2022年7月27日
    2
  • PLSQL来Oracle创建表空间和创建用户

    PLSQL来Oracle创建表空间和创建用户//创建临时表空间createtemporarytablespacetest_temptempfile’E:/oracle/product/10.2.0/oradata/testserver/test_temp01.dbf’size32mautoextendonnext32mmaxsize2048mextentmanagementlocal;

    2022年7月27日
    8

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号