解密PS2手柄与STM32F1单片机的交互之道
1.亮灯模式:即手柄MODE指示灯亮时,手柄ID输出为0x73(网上所谓的红灯模式),左右摇杆才会输出模拟量。L3、R3按键有效
图1 摇杆模拟量大小变化方向图(两个摇杆都是这图)
2.无灯模式:即MODE指示灯不亮灯时,手柄输出为0x41(网上所谓的绿灯模式),左右摇杆不会输出模拟量,其极限值“化身”为左右两边四个方向的按键。L3、R3按键无效
【注意】:不论在亮灯模式还是无灯模式里,除了L3、R3的其他所有按键均有效
图2 手柄按键序号图(因手柄反着放所以左右颠倒)
一、硬件连接
① PS2接收器
② PS2接收器转接板
麻烦一定要用转接板,别想着直接用杜邦线把接收器与单片机连在一起!!!(本人之前一天PS2与单片机没有通讯成功,就是因为没有接转接板)
大致思路:
本人用STM32F103RCT6单片机的PC6、PC7、PC8、PC9、GND、3v3IO口与转接板的DAT\DI、CMD\DO、CS、CLK、GND、VCC端口相连(顺序前后一致),
使用PC7即CMD\DO将数据从单片机发送给PS2手柄、
使用PC6即DAT\DI将数据从PS2手柄接收到单片机、
使用PC8即CS作为片选信号、使用PC9即CLK作为时钟信号,协助PC7和PC8同步传输数据、
使用GND与3V3给PS2接收器供电。
二、软件协议
PS2协议:协议,PS2是按照一个什么样的协议通信呢,是这样的,由下面时序图我们可以知道以下几点:
1.DI代表接收的数据,DO代表发送的数据,之前分别与PC6和PC7相连。
2.数据在时钟CLK下降沿的时候才可以正常传输。
3.数据的接收需要在CS端口为低电平时传输,且在通信时,只有一串数据传输完,CS才由低拉到高,而不是传输完一个字节后就由低拉到高。
对应的stm32程序:(用软件模拟上述的时序图,延时在6us左右)
//单片机向手柄发送一个8位数,同时接收数放Data[1]中
uint8_t PS2_Cmd(uint8_t CMD)
{
volatile uint16_t ref=0x01;
volatile uint8_t flag=0;
//每循环一次发送一位,由低位到高位
for(ref=0x01;ref<0x0100;ref<<=1)
{
if(ref&CMD)
{
DO_H;
}
else DO_L;
//高-低-高一个周期,一个下降沿
CLK_H;
delay_us(6);
CLK_L;
delay_us(6 );
CLK_H;
if(DI)
{
flag=flag|ref;
}
}
return flag;
}
那现在,我们知道PS2与单片机需要按照这样的方式传输数据,那PS2规定了发送啥我才告诉你你需要的数据呢,例如,刚刚你按了啥键。是这样的,有这么一个顺序:
首先,单片机通过PC6和PC7模拟上面那个时序,向PS2发送0x01,再发送一个0x42,PS2会传输回单片机一个ID(0x73代表亮灯模式,0x41代表无灯模式),接着你再随便发一个数,PS2会回一个0x5A,说我要开始传输你需要的按键信息了,接着你按照之前的方式发6个数,同时也会接着收到PS2发回来的6个数,这六个数就是你需要的按键信息,下面这个表讲的就是这个事:
对应的stm32程序:
//读取手柄数据
void PS2_ReadData(uint8_t *get_buf)
{
volatile uint8_t byte=0;
volatile uint16_t ref=0x01;
CS_L;
get_buf[0]=PS2_Cmd(Comd[0]); //开始命令0x01
get_buf[1]=PS2_Cmd(Comd[1]); //请求数据0x42
get_buf[2]=PS2_Cmd(0x00); //0x51是随便一个数,因为这时候DO已经没有用了
get_buf[3]=PS2_Cmd(0x00);
get_buf[4]=PS2_Cmd(0x00);
get_buf[5]=PS2_Cmd(0x51);
get_buf[6]=PS2_Cmd(0x51);
get_buf[7]=PS2_Cmd(0x51);
get_buf[8]=PS2_Cmd(0x51);
CS_H;
}
上述代码出现的其他代码:
bsp_ps2.c文件中
uint8_t Comd[2]={0x01,0x42};
uint8_t Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
bsp_ps2.h文件中
#define DI GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_6)
#define DO_H GPIO_SetBits(GPIOC, GPIO_Pin_7)
#define DO_L GPIO_ResetBits(GPIOC, GPIO_Pin_7)
#define CS_H GPIO_SetBits(GPIOC, GPIO_Pin_8)
#define CS_L GPIO_ResetBits(GPIOC, GPIO_Pin_8)
#define CLK_H GPIO_SetBits(GPIOC, GPIO_Pin_9)
#define CLK_L GPIO_ResetBits(GPIOC, GPIO_Pin_9)
在bsp_delay.h文件中
void delay_us(uint16_t time)
{
uint16_t i=0;
while(time--)
{
i=10; //自己定义
while(i--) ;
}
}
所以要实现PS2与STM32通信:
在main.c你要做的事:
1.初始化你与PS2相连的4个端口
void PS2_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
DO_H;
CS_H;
CLK_H;
}
2.在while(1)中不断间隔50ms或者其他时间,调用读取手柄数据的函数PS2_ReadData(uint8_t *get_buf)即可读到每个按键的信息,“0”代表按下,1代表没按下,而读取的数一般存在一个全局无符号8位的数组Data[9]中,其与按键的对应关系如下图:
在学习PS2与STM32单片机通信的过程中找到的很好的资源:
PS2手柄移植到STM32上面的小笔记_stm32 spi铜须实现ps2-CSDN博客
工程文件源码及一些资源如下:
链接:https://pan.baidu.com/s/1Vz9w6U7t2d86SO9NLcHrXg?pwd=stm3
提取码:stm3