PS2手柄和STM32通信

本文详细解析了PS2手柄的通信协议,包括其与SPI的异同,全双工通信过程,以及STM32如何通过HAL库实现发送和接收数据。还讨论了使用SPI硬件外设的问题及可能的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、通讯周期

        PS2手柄的通信协议与SPI类似,物理层由四条线路构成,分别为CS(片选)、DAT(MISO)、CMD(MOSI)、CLK(时钟线)。

        CS片选信号线默认为高电平,在一个通讯周期间一直处于低电平状态,通讯结束后需要手动拉高电平。PS2手柄在CS低电平状态会被“锁定”,无法获取按键值,因此与SPI不同,需要手动拉低拉高,而不能全程选中置低。CLK默认低电平,PS2是低电平写入、高电平读取,即时钟线由高变低时数据线发生变化,由低变高时双方设备对信号进行读取。

        PS2的一个通讯周期由9帧构成,收发同时进行。其中,各个帧的含义如下:

(图1:表来自YFROBOT电子工作室PS2解码通讯图)

        PS2的通信是全双工的,收发同时进行,且每一帧低位先行。一个典型的PS2通讯周期时序图如下所示:

(图2:PS2通讯时序图,红灯模式,按LEFT键)

        首先,通讯开始时,STM32拉低CS片选。

        发送第0帧0x01,同时,PS2回复一个无意义的随机值。

        第1帧,STM32发送0X42,同时PS2回复它的ID。若ID=0x41则处于绿灯模式,此时摇杆没有模拟量,只有推到最大时会有一个数字量。0x73红灯模式时,摇杆输出模拟量。

        第2帧,STM32发送一个随意值,PS2回复0x5A,通讯开始。

        第3帧,STM32发送WW,代表右侧电机的震动程度,值越大,震动越强烈;PS2回复button_group1的值,如图1所示。此时若按下LEFT,第7位会被置零。由于是低位先行的,因此时序图中第3帧最后一位为0。

        第4帧,STM32发送YY,代表左侧电机震动程度。PS2回复button_group2的值。

        第5帧,STM32发送随机值,PS2回复RX值,即右侧摇杆横向值,未动时时0x80,即处于中间,0x00代表摇杆在最左侧,0xFF为最右侧。

        第6帧,STM32发送随机值,PS2回复RY值,即右侧摇杆纵向值。

        第7帧,STM32发送随机值,PS2回复LX值,即左侧摇杆横向值。

        第8帧,STM32发送随机值,PS2回复LY值,即左侧摇杆纵向值,通讯结束。

二、单帧分析

 (图3 通讯周期第2帧时序图)

        图3给出了和PS2通讯的第2帧时序图。选择第2帧是因为在这一帧中,既有PS2写,也有STM32写,这样方便对帧进行说明。接下来逐位对其分析。

(图4 通讯周期第2帧第0位)

        注意第2帧的第0位实际上是从245799us处开始的。开始时,CLK处于低电平状态,STM32先拉低CMD电平,写出0,然后延时一段时间等PS2写,然后拉高CLK电平,时钟上升沿时STM32读PS2所写数据1,延时一段时间等PS2读,再拉低CLK电平,完成第0位通讯。

(图5 通讯周期第2帧第1位) 

        第2帧第1位从245808us处开始。开始时,CLK处于低电平状态,STM32先拉高CMD电平,写出1,然后延时一段时间等PS2写,然后拉高CLK电平,时钟上升沿时STM32读PS2所写数据1,延时一段时间等PS2读,再拉低CLK电平,完成第1位通讯。

(图6 通讯周期第2帧第2位) 

        第2位比较典型。这一帧开始时,CLK处于低电平状态,STM32先拉低CMD电平,写出0,然后延时一段时间等PS2写,可以看到PS2也在CLK低电平期间写出了数据,说明PS2确实是低电平写、高电平读的。接着STM32拉高CLK电平,时钟上升沿时STM32读PS2所写数据0,延时一段时间等PS2读,再拉低CLK电平,完成第2位通讯。

        剩余5位皆按照上述规律通信,可参照图3进行分析。可以看出,整个第2帧期间,PS2均在CLK电平下降沿写出数据,即“低电平写,高电平读”。这与一般的默认的SPI在“高电平写,低电平读”是相反的。若参照图1,可以看出整个通讯周期皆遵循此协议,除了第0帧。但考虑到第0帧PS2的输出是无意义的,因此上述协议成立。

三、代码参考
1、软件实现

        作者参考网上的代码,基于HAL库给出接收发送1byte的参考代码:

//负责发送的同时接收1 byte数据
static uint8_t s_ps2_sendAget(uint8_t CMD){
  uint8_t data=0;
  uint16_t ref;
  assert_param(HAL_GPIO_ReadPin(PS2_CS_PORT,PS2_CLK_PIN) == GPIO_PIN_RESET);

  for(ref=0x01;ref<0b100000000;ref<<=1){
    //默认低电平。咱先写,写完等会儿让PS2写
    if(ref&CMD){
      HAL_GPIO_WritePin(PS2_CMD_PORT, PS2_CMD_PIN, GPIO_PIN_SET);
    } else{
      HAL_GPIO_WritePin(PS2_CMD_PORT, PS2_CMD_PIN, GPIO_PIN_RESET);
    }
    delay(3);
    //拉高电平,咱先读,读完等会儿让PS2也读
    HAL_GPIO_WritePin(PS2_CLK_PORT,PS2_CLK_PIN,GPIO_PIN_SET);
    if(HAL_GPIO_ReadPin(PS2_DAT_PORT,PS2_DAT_PIN)) data|=ref;
    delay(3);

    HAL_GPIO_WritePin(PS2_CLK_PORT,PS2_CLK_PIN,GPIO_PIN_RESET);
  }
  return data;
}

该代码中,PORT、PIN均需要用户自己定义;delay函数也需要用户自己实现。与网上大多数代码不同,该代码根据作者自己所抓的通讯时序调整了拉高拉低时钟的时机。

2、存在的问题

        作者亦尝试使用STM32F429IGT6(主频180MHZ,APB2总线频率90MHZ)的SPI1硬件外设实现通讯,但发送相同的命令,PS2回复的结果是错误的,结果会左移一位。作者怀疑可能是通信速率过高的原因,但未尝试解决。给出配置代码如下,供取笑、参考。

static void s_ps2_hard_init(){
  GPIO_InitTypeDef GPIO_Init;
  //开启GPIO时钟
  PS2_SPI_CS_CLK_ENABLE();
  PS2_SPI_CMD_CLK_ENABLE();
  PS2_SPI_DAT_CLK_ENABLE();
  PS2_SPI_CLK_CLK_ENABLE();
  //开启SPI时钟
  PS2_SPI_CLK_ENABLE();
  //配置GPIO

  //配置DAT端口。该端口是PS2到单片机的通路。
  GPIO_Init.Mode = GPIO_MODE_INPUT;
  GPIO_Init.Alternate = PS2_SPI_AF;
  GPIO_Init.Pin = PS2_SPI_DAT_PIN;
  GPIO_Init.Pull = GPIO_PULLDOWN;
  GPIO_Init.Speed = GPIO_SPEED_FAST;
  HAL_GPIO_Init(PS2_SPI_DAT_PORT, &GPIO_Init);


  //配置CMD端口。该端口是单片机到PS2的命令通路。
  GPIO_Init.Mode = GPIO_MODE_AF_PP;
  GPIO_Init.Pin = PS2_SPI_CMD_PIN;
  HAL_GPIO_Init(PS2_SPI_CMD_PORT,&GPIO_Init);

  //配置CS端口。该端口是单片机到PS2的片选。
  GPIO_Init.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_Init.Pin = PS2_SPI_CS_PIN;
  HAL_GPIO_WritePin(PS2_SPI_CS_PORT,PS2_SPI_CS_PIN,GPIO_PIN_SET);
  HAL_GPIO_Init(PS2_SPI_CS_PORT,&GPIO_Init);

  //配置CLK端口。该端口是单片机控制的。
  GPIO_Init.Mode = GPIO_MODE_AF_PP;
  GPIO_Init.Pin = PS2_SPI_CLK_PIN;
  HAL_GPIO_Init(PS2_SPI_CLK_PORT,&GPIO_Init);

  //配置SPI结构体,初始化SPI。
  hspi.Instance = PS2_SPI;
  hspi.Init.Mode = SPI_MODE_MASTER;
  hspi.Init.Direction = SPI_DIRECTION_2LINES;
  hspi.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi.Init.NSS = SPI_NSS_SOFT;
  hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
  hspi.Init.FirstBit = SPI_FIRSTBIT_LSB;
  hspi.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  HAL_SPI_Init(&hspi);

  //__HAL_SPI_ENABLE(&hspi);

}

发送接收代码如下:

typedef struct {
    uint8_t null;
    uint8_t ID;
    uint8_t start;
    uint8_t button_Group1;
    uint8_t button_Group2;
    uint8_t RX;
    uint8_t RY;
    uint8_t LX;
    uint8_t LY;
}my_PS2_Statues;

SPI_HandleTypeDef hspi;
uint8_t cmdList[9] = {0x01,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

my_PS2_Statues my_ps2_getState(){
  my_PS2_Statues statues;
  HAL_GPIO_WritePin(PS2_SPI_CS_PORT,PS2_SPI_CS_PIN,GPIO_PIN_RESET);
  HAL_SPI_TransmitReceive(&hspi,cmdList,(uint8_t*)&statues,9,HAL_MAX_DELAY);
  HAL_GPIO_WritePin(PS2_SPI_CS_PORT,PS2_SPI_CS_PIN,GPIO_PIN_SET);
  return statues;
}

 

### STM32PS2手柄交互的标准库使用 对于STM32微控制器而言,在处理外部设备如PS2游戏手柄时,通常会利用HAL (Hardware Abstraction Layer) 库来简化硬件编程工作。虽然官方提供的固件库并不直接包含针对PS2接口的支持,但是可以通过组合使用GPIO、SPI通信协议以及编写相应的驱动逻辑来实现对接。 #### GPIO初始化配置 为了使能PS2手柄的数据交换过程,首先需要正确设定用于连接手柄信号线的引脚模式。这涉及到CLK(时钟), CMD(命令), ATT(注意/选通),DATA(数据)四条线路。这些线路一般通过通用输入输出端口(GPIOs)来进行控制读取[^2]。 ```c // 初始化GPIO引脚作为PS2接口 void PS2_GPIO_Init(void){ __HAL_RCC_GPIOA_CLK_ENABLE(); // 假设使用PA口 GPIO_InitTypeDef GPIO_InitStruct = {0}; /* CLK */ GPIO_InitStruct.Pin = GPIO_PIN_8; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } ``` #### SPI模拟或专用外设 考虑到PS2采用半双工同步串行通讯机制,可以考虑借助于STM32内部集成的SPI模块或是纯软件方式去仿真这种特定类型的总线行为。如果选择后者,则需手动管理位周期并遵循严格的时序要求[^4]。 #### 中断服务例程ISR 当接收到有效指令后,应触发中断以便及时响应来自手柄的状态更新事件。此时可定义专门的服务程序(ISR)来捕获变化,并据此执行后续动作,比如查询按键状态等。 ```c extern "C" void EXTI9_5_IRQHandler(void){ HAL_GPIO_EXTI_IRQHandler(PS2_CMD_Pin); // 处理CMD线上沿跳变产生的中断请求 } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ if(GPIO_Pin == PS2_CMD_Pin){ // 实现具体的业务逻辑... } } ``` #### 数据解析算法 最后一步就是按照既定格式解码由手柄传回的信息包,从中提取出各按钮及摇杆的位置参数。这部分往往依赖于具体型号的手柄所规定的编码规则[^1]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值