PS2手柄和STM32通信

一、通讯周期

        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;
}

 

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
根据您的问题,我目前还没有找到与PS2手柄遥控STM32接线相关的引用信息。但是,我可以为您提供一个一般的PS2手柄STM32的接线方法。 PS2手柄STM32的接线通常需要使用4根引脚,它们分别是: 1. 数据引脚(Data):用于传输手柄数据的引脚。 2. 命令引脚(Command):用于发送命令给手柄的引脚。 3. 时钟引脚(Clock):用于同步数据传输的引脚。 4. 电源引脚(VCC):用于为手柄供电的引脚。 具体的接线方法如下: 1. 将PS2手柄的数据引脚连接到STM32的一个GPIO引脚上。 2. 将PS2手柄的命令引脚连接到STM32的另一个GPIO引脚上。 3. 将PS2手柄的时钟引脚连接到STM32的另一个GPIO引脚上。 4. 将PS2手柄的电源引脚连接到STM32的3.3V或5V电源引脚上。 请注意,接线方法可能会因STM32开发板的型号和PS2手柄的型号而有所不同。在实际接线之前,建议您参考您使用的STM32开发板和PS2手柄的数据手册或引脚布局图来确定正确的接线方法。 另外,为了能够成功读取和解析PS2手柄的数据,您可能还需要编写适当的程序代码来与手柄进行通信和数据处理。这包括设置GPIO引脚的输入/输出模式,以及使用相应的通信协议来与手柄进行数据交换。具体的代码实现会因您使用的开发环境和编程语言而有所不同。 希望这些信息能对您有所帮助!如果您还有其他问题,请随时提问。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值