外观和电气连接
外观
手柄外观如下
接收器外观
这是接收器和底座
电气连接
需要4根连接线
单片机输出是CLK DO CS
单片机输入是DI
电源电压是3.3-5v
注意模块和单片机共地
模块不支持高速,最大时钟周期约为4us左右
因此使用软件模拟时序的方式来与模块通信
只需要将模块的4根线与单片机软件中对应的GPIO连接起来就行了
#define PS2_CS_GPIOx GPIOA
#define PS2_CS_Pin GPIO_PIN_4#define PS2_CLK_GPIOx GPIOA
#define PS2_CLK_Pin GPIO_PIN_5#define PS2_DO_GPIOx GPIOA
#define PS2_DO_Pin GPIO_PIN_7#define PS2_DI_GPIOx GPIOA
#define PS2_DI_Pin GPIO_PIN_6
软件设计
HAL初始化
需要 3个推挽输出模式的GPIO 分别是 CLK DO CS
1个上拉输入模式 DI
协议分析
cs拉低开始单片机与模块的通信,拉高则通信结束
DO,DI在在CLK时钟的下降沿完成数据的发送和读取
本协议是类似SPI的协议
通信一共9个字节
这里的DO与DI指的是单片机的GPIO名称
Byte | 功能 |
---|---|
0 | DO->0x01 DI <-(无意义) |
1 | DO->0x42 DI<-(红灯0x73 无灯0X41) |
2 | DO->0xFF DI<-0X5A |
3-8 | DO->0xFF DI<-(按键数据) |
按键数据如下
按键分析
手柄发送来的有两种模式的数据
无灯模式(MOD LED不亮)发送的摇杆是数字量,摇杆左右两侧的四向按键功能和摇杆重合
即如下图
红灯模式(MODE LED亮红灯)
模块传输的是摇杆的模拟量
外侧大按键独立
代码
为了方便建立了一个结构体来存储按键的数据,代码有注释,请根据设备自行测试是哪个按键即可
typedef struct
{uint8_t A_D; //模拟(红灯)为1 数字(无灯)为0int8_t Rocker_RX, Rocker_RY, Rocker_LX, Rocker_LY; //摇杆值(模拟状态为实际值0-0xFF)(数字态为等效的值0,0x80,0xFF)//按键值0为未触发,1为触发态uint8_t Key_L1, Key_L2, Key_R1, Key_R2; //后侧大按键uint8_t Key_L_Right, Key_L_Left, Key_L_Up, Key_L_Down; //左侧按键uint8_t Key_R_Right, Key_R_Left, Key_R_Up, Key_R_Down; //右侧按键uint8_t Key_Select; //选择键uint8_t Key_Start; //开始键uint8_t Key_Rocker_Left, Key_Rocker_Right; //摇杆按键} PS2_TypeDef;
extern PS2_TypeDef PS2_Data;
#include "PS2.h"
uint8_t PS2_RawData[9] = {0};
PS2_TypeDef PS2_Data = {0};
void PS2_CS(uint8_t Val)
{if (Val)HAL_GPIO_WritePin(PS2_CS_GPIOx, PS2_CS_Pin, GPIO_PIN_SET);elseHAL_GPIO_WritePin(PS2_CS_GPIOx, PS2_CS_Pin, GPIO_PIN_RESET);
}
void PS2_CLK(uint8_t Val)
{if (Val)HAL_GPIO_WritePin(PS2_CLK_GPIOx, PS2_CLK_Pin, GPIO_PIN_SET);elseHAL_GPIO_WritePin(PS2_CLK_GPIOx, PS2_CLK_Pin, GPIO_PIN_RESET);
}
void PS2_DO(uint8_t Val)
{if (Val)HAL_GPIO_WritePin(PS2_DO_GPIOx, PS2_DO_Pin, GPIO_PIN_SET);elseHAL_GPIO_WritePin(PS2_DO_GPIOx, PS2_DO_Pin, GPIO_PIN_RESET);
}
uint8_t PS2_Read_DI()
{return HAL_GPIO_ReadPin(PS2_DI_GPIOx, PS2_DI_Pin);
}
void PS2_Delay()
{for (int i = 0; i < 0xBf; i++)__nop();
}
uint8_t PS2_ReadWrite_Byte(uint8_t TxData)
{uint8_t TX = TxData;uint8_t RX = 0;for (int i = 0; i < 8; i++){if (TX & 0x01)PS2_DO(1);elsePS2_DO(0);TX >>= 1;PS2_CLK(1);PS2_Delay();PS2_CLK(0);RX >>= 1;RX |= (PS2_Read_DI() << 7);PS2_Delay();PS2_CLK(1);PS2_Delay();}return RX;
}void PS2_Decode()
{if (PS2_RawData[2] == 0x5A){PS2_Data.Key_Select = (~PS2_RawData[3] >> 0) & 0x01; //选择键PS2_Data.Key_Start = (~PS2_RawData[3] >> 3) & 0x01; //开始键//左侧按键PS2_Data.Key_L_Up = (~PS2_RawData[3] >> 4) & 0x01;PS2_Data.Key_L_Right = (~PS2_RawData[3] >> 5) & 0x01;PS2_Data.Key_L_Down = (~PS2_RawData[3] >> 6) & 0x01;PS2_Data.Key_L_Left = (~PS2_RawData[3] >> 7) & 0x01;//后侧按键PS2_Data.Key_L2 = (~PS2_RawData[4] >> 0) & 0x01;PS2_Data.Key_R2 = (~PS2_RawData[4] >> 1) & 0x01;PS2_Data.Key_L1 = (~PS2_RawData[4] >> 2) & 0x01;PS2_Data.Key_R1 = (~PS2_RawData[4] >> 3) & 0x01;//右侧按键PS2_Data.Key_R_Up = (~PS2_RawData[4] >> 4) & 0x01;PS2_Data.Key_R_Right = (~PS2_RawData[4] >> 5) & 0x01;PS2_Data.Key_R_Down = (~PS2_RawData[4] >> 6) & 0x01;PS2_Data.Key_R_Left = (~PS2_RawData[4] >> 7) & 0x01;if (PS2_RawData[1] == 0x41){ //无灯模式(摇杆值八向)PS2_Data.Rocker_LX = 127 * (PS2_Data.Key_L_Right - PS2_Data.Key_L_Left);PS2_Data.Rocker_LY = 127 * (PS2_Data.Key_L_Up - PS2_Data.Key_L_Down);PS2_Data.Rocker_RX = 127 * (PS2_Data.Key_R_Right - PS2_Data.Key_R_Left);PS2_Data.Rocker_RY = 127 * (PS2_Data.Key_R_Up - PS2_Data.Key_R_Down);}else if (PS2_RawData[1] == 0x73){ //红灯模式(摇杆值模拟)//摇杆按键PS2_Data.Key_Rocker_Left = (~PS2_RawData[3] >> 1) & 0x01;PS2_Data.Key_Rocker_Right = (~PS2_RawData[3] >> 2) & 0x01;//摇杆值PS2_Data.Rocker_LX = PS2_RawData[7] - 0x80;PS2_Data.Rocker_LY = -1 - (PS2_RawData[8] - 0x80);PS2_Data.Rocker_RX = PS2_RawData[5] - 0x80;PS2_Data.Rocker_RY = -1 - (PS2_RawData[6] - 0x80);}}
}
void PS2_Read_Data(void)
{PS2_CS(0);PS2_RawData[0] = PS2_ReadWrite_Byte(0x01); // 0PS2_RawData[1] = PS2_ReadWrite_Byte(0x42); // 1for (int i = 2; i < 9; i++)PS2_RawData[i] = PS2_ReadWrite_Byte(0xff);PS2_CS(1);PS2_Decode();
}
手柄的效果还可以,对得起25元的价格
可以二次开发做成遥控车遥控器或者电脑手柄
成品
Github