赞
踩
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的输出是无意义的,因此上述协议成立。
作者参考网上的代码,基于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函数也需要用户自己实现。与网上大多数代码不同,该代码根据作者自己所抓的通讯时序调整了拉高拉低时钟的时机。
作者亦尝试使用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;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。