完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
一.PS2介绍
今天就带大家来认识一下PS2的通讯协议,如果你需要用PS2无线手柄搭配单面机来DIY制作,那么千万别错过这篇文章。 首先介绍一下我们今天的主角-----PS2手柄。 PS2手柄是日本SONY公司的PlayStation2 游戏机的遥控手柄。索尼的 PSX系列游戏主机在全球都很畅销。不知什么时候便有人打起 PS2手柄的主意,破解了通讯协议,使得手柄可以接在其他器件上遥控使用,比如遥控我们熟悉的机器人。突出的特点是这款手柄性价比极高,按键丰富,方便扩展到其它应用中。 二.PS2通讯协议介绍 PS2采用的是SPI通信协议,SPI是串行外设接口的缩写,是一种高速的、全双工、同步的通信总线,并且在芯片的管脚上只占用四根线(DI、DO、CS、CLK),节约了芯片的管脚,同时为PCB的布局上节省空间。 (1)PS2端口介绍 PS2接收器上一共有九根引脚,按上图从左往右,依次为: 1.DI/DAT:信号流向,从手柄到主机,此信号是一个8bit 的串行数据,同步传送于时钟的下降沿。信号的读取在时钟由高到低的变化过程中完成。 2.DO/CMD:信号流向,从主机到手柄,此信号和 DI相对,信号是一个 8bit 的串行数据, 同步传送于时钟的下降沿。 3.NC:空端口。 4.GND:电源地。 5.VCC:接收器工作电源,电源范围 3~5V。 6.CS/SEL:用于提供手柄触发信号。在通讯期间,处于低电平。 7.CLK:时钟信号,由主机发出,用于保持数据同步。 8.NC:空端口。 9.ACK:从手柄到主机的应答信号。此信号在每个8bits数据发送的最后一个周期变低并且CS一直保持低电平,如果CS信号不变低,约60微秒PS主机会试另一个外设。在编程时未使用ACK端口。(可以忽略) (2)PS2通讯过程 1. CS线在通讯期间拉低,通信过程中CS信号线在一串数据(9个字节,每个字节为8位)发送完毕后才会拉高,而不是每个字节发送完拉高。 2. DO、DI在在CLK时钟的下降沿完成数据的发送和读取。 下降沿:数字电平从高电平(数字“1”)变为低电平(数字“0”)的那一瞬间叫作下降沿。3. CLK的每个周期为12us。若在某个时刻,CLK处于下降沿,若此时DO为高电平则取“1”,低电平则取“0”。连续读8次则得到一个字节byte的数据,连续读9个字节就能得到一次传输周期所需要的数据。DI也是一样的,发送和传输同时进行。 具体的通讯过程如下: 以STM32为例: 1. 首先STM32拉低CS片选信号线,然后在每个CLK的下降沿读一个bit,每读八个bit(即一个byte)CLK拉高一小段时间,一共读九组bit。 2. 第一个byte是STM32发给接收器命令“0X01” 。 3. PS2手柄会在第二个byte回复它的ID(0x41=绿灯模式,0x73=红灯模式),同时第二个byte时STM32发给PS2一个0x42请求数据。 红灯模式时 : 左右摇杆发送模拟值,0x00~0xFF 之间,且摇杆按下的 键值 L3 、 R3 有效;4. 第三个byte PS2 会给主机发送 “0x5A” 告诉STM32数据来了。 5. 从第四个byte开始全是接收器给主机发送数据,每个byte定义如上图,当有按键按下,对应位为“0 ”,例如当键“SELECT”被按下时, Data[3]=11111110。 对于整个通讯过程,你理解成下面的一段对话: [tr]对于整个通讯过程,你理解成下面的一段对话:[/tr]
注意: 三.基于STM32的PS2通信源码 //采用模拟SPI通信 /*DI->PB12; DO->PB13; CS->PB14; CLK->PB15 */ void PS2_Init(void) { // 输入 DI->PB12 RCC->APB2ENR|=1<<3; // 使能 PORTB 时钟 GPIOB->CRH&=0XFFF0FFFF;//PB12 设置成输入 默认下拉 GPIOB->CRH|=0X00080000; // DO->PB13 CS->PB14 CLK->PB15 RCC->APB2ENR|=1<<3; // 使能 PORTB 时钟 GPIOB->CRH&=0X000FFFFF; GPIOB->CRH|=0X33300000; //PB13、 PB14 、 PB15 推挽输出 } //端口初始化,PB12 为输入,PB13 、PB14 、PB15 为输出。 // 向手柄发送命令 void PS2_Cmd(u8CMD) { volatile u16 ref=0x01; Data[1]=0; for(ref=0x01;ref<0x0100;ref<<=1) { if(ref&CMD) { DO_H; // 输出一位控制位 } else DO_L; CLK_H; // 时钟拉高 delay_us(10); CLK_L; delay_us(10); CLK_H; if(DI) {Data[1]=ref|Data[1];} } delay_us(16); } // 判断是否为红灯模式,0x41=模拟绿灯,0x73=模拟红灯 // 返回值;0,红灯模式 // 其他,其他模式 u8PS2_RedLight(void) { CS_L; PS2_Cmd(Comd[0]); // 开始命令 PS2_Cmd(Comd[1]); // 请求数据 CS_H; if( Data[1]== 0X73) return 0; else return 1; } // 读取手柄数据 void PS2_ReadData(void) { volatile u8 byte=0; volatile u16 ref=0x01; CS_L; PS2_Cmd(Comd[0]); // 开始命令 PS2_Cmd(Comd[1]); // 请求数据 for(byte=2;byte<9;byte++) // 开始接受数据 { for(ref=0x01;ref<0x100;ref<<=1) { CLK_H; delay_us(10); CLK_L; delay_us(10); CLK_H; if(DI) {Data[byte]= ref|Data[byte];} } delay_us(16); } CS_H; } /* 上面两个函数分别为主机向手柄发送数据、手柄向主机发送数据。手柄向主机发送的数据缓存在数组 Data[]中, 数组中共有9个元素,每个元素的意义请见表1。 还有一个函数是用来判断手柄的发送模式,也就是判断 ID(红灯还是绿灯模式) 即 Data[1]的值。 */ // 对读出来的 PS2 的数据进行处理,只处理按键部分 //按下为0,未按下为1 u8PS2_DataKey() { u8 index; PS2_ClearData(); PS2_ReadData(); Handkey=(Data[4]<<8)|Data[3]; // 这是 16个按键 按下为 0 , 未按下为 1 for(index=0;index<16;index++) { if((Handkey&(1<<(MASK[index]-1)))==0) returnindex+1; } return 0; // 没有任何按键按下 } // 得到一个摇杆的模拟量 范围 0~256 u8PS2_AnologData(u8 button) { return Data[button]; } // 清除数据缓冲区 void PS2_ClearData() { u8 a; for(a=0;a<9;a++) {Data[a]=0x00;} } /* 8 位数 Data[3]与 Data[4],分别对应着 16个按键的状态,按下为 0,未按下为 1。 通过 对这两个数的处理,得到按键状态并返回键值。 另一个函数的功能就是返回模拟值,只有在“红灯模式”下值才是有效的,拨动摇杆, 值才会变化,这些值分别存储在 Data[5]、Data[6]、 Data[7]、 Data[8]。 */ //手柄配置初始化: void PS2_ShortPoll(void) { CS_L; delay_us(16); PS2_Cmd(0x01); PS2_Cmd(0x42); PS2_Cmd(0X00); PS2_Cmd(0x00); PS2_Cmd(0x00); CS_H; delay_us(16); } //进入配置 void PS2_EnterConfing(void) { CS_L; delay_us(16); PS2_Cmd(0x01); PS2_Cmd(0x43); PS2_Cmd(0X00); PS2_Cmd(0x01); PS2_Cmd(0x00); PS2_Cmd(0X00); PS2_Cmd(0X00); PS2_Cmd(0X00); PS2_Cmd(0X00); CS_H; delay_us(16); } // 发送模式设置 void PS2_TurnOnAnalogMode(void) { CS_L; PS2_Cmd(0x01); PS2_Cmd(0x44); PS2_Cmd(0X00); PS2_Cmd(0x01);//analog=0x01;digital=0x00 软件设置发送模式 PS2_Cmd(0xEE);//Ox03 锁存设置,即不可通过按键“MODE ”设置模式。 //0xEE 不锁存软件设置,可通过按键“MODE ”设置模式。 PS2_Cmd(0X00); PS2_Cmd(0X00); PS2_Cmd(0X00); PS2_Cmd(0X00); CS_H; delay_us(16); } // 振动设置 void PS2_VibrationMode(void) { CS_L; delay_us(16); PS2_Cmd(0x01); PS2_Cmd(0x4D); PS2_Cmd(0X00); PS2_Cmd(0x00); PS2_Cmd(0X01); CS_H; delay_us(16); } // 完成并保存配置 void PS2_ExitConfing(void) { CS_L; delay_us(16); PS2_Cmd(0x01); PS2_Cmd(0x43); PS2_Cmd(0X00); PS2_Cmd(0x00); PS2_Cmd(0x5A); PS2_Cmd(0x5A); PS2_Cmd(0x5A); PS2_Cmd(0x5A); PS2_Cmd(0x5A); CS_H; delay_us(16); } // 手柄配置初始化 void PS2_SetInit(void) { PS2_ShortPoll(); PS2_ShortPoll(); PS2_ShortPoll(); PS2_EnterConfing(); // 进入配置模式 PS2_TurnOnAnalogMode(); // “红绿灯”配置模式,并选择是否保存 PS2_VibrationMode(); // 开启震动模式 PS2_ExitConfing(); // 完成并保存配置 } /* 可以看出配置函数就是发送命令,发送这些命令后,手柄就会明白自己要做什么了,发送命令时,不需要考虑手柄发来的信息。 手柄配置初始化,PS2_ShortPoll()被执行了3次,主要是为了建立和恢复连接。 具体的配置方式请看注释。 */ void PS2_Vibration(u8motor1,u8motor2) { CS_L; delay_us(16); PS2_Cmd(0x01); // 开始命令 PS2_Cmd(0x42);// 请求数据 PS2_Cmd(0X00); PS2_Cmd(motor1); PS2_Cmd(motor2); PS2_Cmd(0X00); PS2_Cmd(0X00); PS2_Cmd(0X00); PS2_Cmd(0X00); CS_H; delay_us(16); } //只 有 在 初 始 化 函 数 void PS2_SetInit(void) 中 , 对 震 动 电 机 进 行 了初 始 化 (PS2_VibrationMode();//开启震动模式),这个函数命令才会被执行。 四.文档与源码下载链接 1.PS2参考文档CSDN:PS2解码通讯手册.pdf 注:文档里面有PS2的通讯原理的解析,还有上面代码块的代码都在里面,如果在CSDN里看代码比较累,建议下载PDF文档直接看PS2代码例程。 2.这里还有一份我写的源码:PS2源码HAL库+CubeMX+Stm32F103C8 注: 里面除了PS2的源码还加了延时实验的源码,HAL库本身没有微秒级的延时,所以需要自己写微秒级的延时函数,详情看源码。至于延时的原理参考另一篇博客:Stm32延时与计时方法(HAL库)。 为防止与PS2通信过快而乱码导致延迟,需要在主函数的while(1)中延时50ms,即加一句delay_ms(50)。(原工程中没有,需要自行加上) |
|
|
|
只有小组成员才能发言,加入小组>>
3329 浏览 9 评论
3007 浏览 16 评论
3503 浏览 1 评论
9085 浏览 16 评论
4099 浏览 18 评论
1211浏览 3评论
620浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
607浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2349浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1913浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-1-4 13:03 , Processed in 0.935351 second(s), Total 45, Slave 36 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (威廉希尔官方网站 图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号