当前位置:   article > 正文

PS2手柄和STM32通信_stm32 ps2手柄

stm32 ps2手柄
一、通讯周期

        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. //负责发送的同时接收1 byte数据
  2. static uint8_t s_ps2_sendAget(uint8_t CMD){
  3. uint8_t data=0;
  4. uint16_t ref;
  5. assert_param(HAL_GPIO_ReadPin(PS2_CS_PORT,PS2_CLK_PIN) == GPIO_PIN_RESET);
  6. for(ref=0x01;ref<0b100000000;ref<<=1){
  7. //默认低电平。咱先写,写完等会儿让PS2写
  8. if(ref&CMD){
  9. HAL_GPIO_WritePin(PS2_CMD_PORT, PS2_CMD_PIN, GPIO_PIN_SET);
  10. } else{
  11. HAL_GPIO_WritePin(PS2_CMD_PORT, PS2_CMD_PIN, GPIO_PIN_RESET);
  12. }
  13. delay(3);
  14. //拉高电平,咱先读,读完等会儿让PS2也读
  15. HAL_GPIO_WritePin(PS2_CLK_PORT,PS2_CLK_PIN,GPIO_PIN_SET);
  16. if(HAL_GPIO_ReadPin(PS2_DAT_PORT,PS2_DAT_PIN)) data|=ref;
  17. delay(3);
  18. HAL_GPIO_WritePin(PS2_CLK_PORT,PS2_CLK_PIN,GPIO_PIN_RESET);
  19. }
  20. return data;
  21. }

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

2、存在的问题

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

  1. static void s_ps2_hard_init(){
  2. GPIO_InitTypeDef GPIO_Init;
  3. //开启GPIO时钟
  4. PS2_SPI_CS_CLK_ENABLE();
  5. PS2_SPI_CMD_CLK_ENABLE();
  6. PS2_SPI_DAT_CLK_ENABLE();
  7. PS2_SPI_CLK_CLK_ENABLE();
  8. //开启SPI时钟
  9. PS2_SPI_CLK_ENABLE();
  10. //配置GPIO
  11. //配置DAT端口。该端口是PS2到单片机的通路。
  12. GPIO_Init.Mode = GPIO_MODE_INPUT;
  13. GPIO_Init.Alternate = PS2_SPI_AF;
  14. GPIO_Init.Pin = PS2_SPI_DAT_PIN;
  15. GPIO_Init.Pull = GPIO_PULLDOWN;
  16. GPIO_Init.Speed = GPIO_SPEED_FAST;
  17. HAL_GPIO_Init(PS2_SPI_DAT_PORT, &GPIO_Init);
  18. //配置CMD端口。该端口是单片机到PS2的命令通路。
  19. GPIO_Init.Mode = GPIO_MODE_AF_PP;
  20. GPIO_Init.Pin = PS2_SPI_CMD_PIN;
  21. HAL_GPIO_Init(PS2_SPI_CMD_PORT,&GPIO_Init);
  22. //配置CS端口。该端口是单片机到PS2的片选。
  23. GPIO_Init.Mode = GPIO_MODE_OUTPUT_PP;
  24. GPIO_Init.Pin = PS2_SPI_CS_PIN;
  25. HAL_GPIO_WritePin(PS2_SPI_CS_PORT,PS2_SPI_CS_PIN,GPIO_PIN_SET);
  26. HAL_GPIO_Init(PS2_SPI_CS_PORT,&GPIO_Init);
  27. //配置CLK端口。该端口是单片机控制的。
  28. GPIO_Init.Mode = GPIO_MODE_AF_PP;
  29. GPIO_Init.Pin = PS2_SPI_CLK_PIN;
  30. HAL_GPIO_Init(PS2_SPI_CLK_PORT,&GPIO_Init);
  31. //配置SPI结构体,初始化SPI。
  32. hspi.Instance = PS2_SPI;
  33. hspi.Init.Mode = SPI_MODE_MASTER;
  34. hspi.Init.Direction = SPI_DIRECTION_2LINES;
  35. hspi.Init.DataSize = SPI_DATASIZE_8BIT;
  36. hspi.Init.CLKPhase = SPI_PHASE_1EDGE;
  37. hspi.Init.CLKPolarity = SPI_POLARITY_LOW;
  38. hspi.Init.NSS = SPI_NSS_SOFT;
  39. hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
  40. hspi.Init.FirstBit = SPI_FIRSTBIT_LSB;
  41. hspi.Init.TIMode = SPI_TIMODE_DISABLE;
  42. hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  43. HAL_SPI_Init(&hspi);
  44. //__HAL_SPI_ENABLE(&hspi);
  45. }

发送接收代码如下:

  1. typedef struct {
  2. uint8_t null;
  3. uint8_t ID;
  4. uint8_t start;
  5. uint8_t button_Group1;
  6. uint8_t button_Group2;
  7. uint8_t RX;
  8. uint8_t RY;
  9. uint8_t LX;
  10. uint8_t LY;
  11. }my_PS2_Statues;
  12. SPI_HandleTypeDef hspi;
  13. uint8_t cmdList[9] = {0x01,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
  14. my_PS2_Statues my_ps2_getState(){
  15. my_PS2_Statues statues;
  16. HAL_GPIO_WritePin(PS2_SPI_CS_PORT,PS2_SPI_CS_PIN,GPIO_PIN_RESET);
  17. HAL_SPI_TransmitReceive(&hspi,cmdList,(uint8_t*)&statues,9,HAL_MAX_DELAY);
  18. HAL_GPIO_WritePin(PS2_SPI_CS_PORT,PS2_SPI_CS_PIN,GPIO_PIN_SET);
  19. return statues;
  20. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/973404
推荐阅读
相关标签
  

闽ICP备14008679号