PS2手柄移植到STM32上面的小笔记
一、硬件准备:战舰开发板、PS2手柄接收器、PS2手柄、连接线
二、硬件连接:
PS2手柄接收器有六个引脚,和单片机连接IO口连接,如下图:
接收器信号 | 单片机IO |
---|---|
GND | GND |
VCC | 3.3V |
DI/DAT | PB12 |
DO/CMD | PB13 |
CS | PB14 |
CLK | PB15 |
三、PS2通信简介
通讯时序如下,感觉和SPI很像,也是四线
DI与DO是一对同时传输的8 bit串行数据,传输的时候需要CS为低电平,CLK由高变低。
DO是单片机发送给接收器的信号。
DI是接收器发送给单片机的信号。
第一点:CS在数据输出或者输入的时候,都是低电平的,那么我们在数据传输的时候先把CS拉高再拉低,然后数据进行传输,传输完成之后再把CS拉高。
第二点:DI(Data Input)与DO(Data Output)是同时完成的,说明这是全双工通信。串口是全双工通信。IIC是半双工通信。
第三点:在时钟上降沿的时候,DI和DO的数据有交叉,也就是说数据进行交换(数据只有0和1),这个时候我们是不能够读和写数据的,因为数据还不稳定,我们读到的数据不准确。在时钟为下降沿的时候,数据已经稳定了,我们在这个时候开始读和写数据。
第四点:由于是从0到7,可以知道有8位数据,并且是从低位到高位进行读写。我们可以把数据放到数组中。一个时钟进行一个数据位(也可以叫做比特位0或1)传输。
时钟频率250KHZ(4us),数据不稳定可适当增加频率。
当单片机发送0x01时,接收器会回复它的ID“0X41表示绿灯模式”、“0x73表示红灯模式”;在手柄发送ID的同时,单片机将发送0X42,手柄会发送0X5A,高速单片机数据来了。
上表中的idle表示数据线空闲,该数据线无数据传送。
所以Data[0]、Data[1]、Data[2]不能用来存放PS2摇杆的按键
Data[3]、Data[4]用来存放按键的值
Data[5]、Data[6]、Data[7]、Data[8]用来存放摇杆的模拟量
当有按键下,对应位为0,其他位为1
譬如,当SELECT按下,Data[3]=1111 1110B
当L3按下,Data[3]=1111 1101B
当R3按下,Data[3]=1111 1011B
当START按下,Data[3]=1111 0111B
当UP按下,Data[3]=1110 1111B
当RIGHT按下,Data[3]=1101 1111B
当DOWN按下,Data[3]=1011 1111B
当LEFT按下,Data[3]=0111 1111B
手柄有两个模式,红灯模式(手柄亮红灯+绿灯)和绿灯模式(手柄只亮绿灯),可以通过按下MODE按键进行切换。
红灯模式:
1.按键L3/R3按下有效
2.推动左右摇杆,根据行程不一样,可输出0x00-0xff的模拟量
绿灯模式:
1.按键L3/R3按下有效
2.左右摇杆不输出模拟量,推动到上下左右的极限值,左摇杆实现的效果和UP/DOWN/RIGHT/LEFT一样,右摇杆实现的效果和和△/X/□/○一样。
设置震动模式后:
WW用来控制右侧的小电机,0x00表示关,其他值为开。
YY用来控制左侧的大电机,0x40-0xff表示电机开,值越大,震感越强烈;其他值表示电机关。
四、代码分析
1.配置IO口,将PB12设置为下拉输入;PB13/14/15设置为推挽输出
void PS2_Init(void)
{ GPIO_InitTypeDef GPIO_InitStructure;//输入 DI->PB12RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能PORTB时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置成上拉、下拉、浮空输入皆可GPIO_Init(GPIOB, &GPIO_InitStructure);//输出 DO->PB13 CS->PB14 CLK->PB15GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置成推挽输出 GPIO_Init(GPIOB, &GPIO_InitStructure);
}
2.定义3个数组
u8 Comd[2]={0x01,0x42}; //开始命令。请求数据
u8 Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; //数据存储数组
//每个按键对应一个数值
u16 MASK[]={PSB_SELECT,PSB_L3,PSB_R3 ,PSB_START,PSB_PAD_UP,PSB_PAD_RIGHT,PSB_PAD_DOWN,PSB_PAD_LEFT,PSB_L2,PSB_R2,PSB_L1,PSB_R1 ,PSB_GREEN,PSB_RED,PSB_BLUE,PSB_PINK
};
3.给PB12/PB13/PB14/PB15这4个IO的状态均进行宏定义
#define DI PBin(12) //PB12 输入#define DO_H PBout(13)=1 //命令位高
#define DO_L PBout(13)=0 //命令位低#define CS_H PBout(14)=1 //CS拉高
#define CS_L PBout(14)=0 //CS拉低#define CLK_H PBout(15)=1 //时钟拉高
#define CLK_L PBout(15)=0 //时钟拉低
4.单片机向手柄发送命令
//向手柄发送命令
void PS2_Cmd(u8 CMD)
{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(50);CLK_L;delay_us(50);CLK_H;if(DI)Data[1] = ref|Data[1];}
}
//
假如单片机给接收器发送0x01=0000 0001B,
接收器接受到0x01后给单片机发送了0x41=0100 0001B,
ref=0x01=0000 0001B;
Data[1] = 0;
CMD=0000 0001B
DI=0100 0001B
/
从低位到高位执行8次循环
第一次循环:ref=0000 0001B
if(ref&CMD)为真,输出PB13=1
if(DI)为真,输出Data[1] = ref|Data[1]=0000 0001|0000 0000=0000 0001第二次循环:ref=0000 0010B
if(ref&CMD)为假,输出PB13=0
if(DI)为假,不执行操作第三次循环:ref=0000 0100B
if(ref&CMD)为假,输出PB13=0
if(DI)为假,不执行操作第四次循环:ref=0000 1000B
if(ref&CMD)为假,输出PB13=0
if(DI)为假,不执行操作第五次循环:ref=0001 0000B
if(ref&CMD)为假,输出PB13=0
if(DI)为假,不执行操作第六次循环:ref=0010 0000B
if(ref&CMD)为假,输出PB13=0
if(DI)为假,不执行操作第七次循环:ref=0100 0000B
if(ref&CMD)为假,输出PB13=0
if(DI)为真,输出Data[1] = ref|Data[1]=0100 0000|0000 0001=0100 0001第八次循环:ref=1000 0000B
if(ref&CMD)为假,输出PB13=0
if(DI)为假,不执行操作
/
所以最后的结果就是
单片机将0x01按位发送了出去
接收机发送的数据0x41保存到了Data[1]里面
a…volatile修饰符可以保证ref每次开始都是0x01即0000 0001B
b.ref=0x01;ref<0x0100;ref<<=1
理解这句首先需要将十六进制改为二进制,即ref=0000 0001B;ref<0000 0001 0000 000B;ref<<=1
即将ref=0x01
每次左移一位,循环八次。
c.ref&CMD
即可以通过与运算循环八次,将CMD 这个八位二进制的数按位发送出去。
d.CLK电平进行高-低-高可以产生一个周期,同时产生一个下降沿。在这个过程中,DO将信号从单片机(发送)给接收器(接收),DI将信号从接收器(发送)给单片机(接收)
e.单片机接收到的数据被保存在了Data[1]里面
5.判断手柄是红灯模式还是绿灯模式,通过单片机给手柄发送0x01 0x42后,手柄返回的值来判断,如果返回的是0X41表示"绿灯模式"、0x73表示"红灯模式"
//判断是否为红灯模式
//返回值;0,红灯模式
//返回值;1,绿灯模式
u8 PS2_RedLight(void)
{CS_L;PS2_Cmd(Comd[0]); //开始命令0x01PS2_Cmd(Comd[1]); //请求数据0x42CS_H;if( Data[1] == 0X73) return 0 ;else return 1;
}
5.单片机接收手柄数据
//读取手柄数据
void PS2_ReadData(void)
{volatile u8 byte=0;volatile u16 ref=0x01;CS_L;PS2_Cmd(Comd[0]); //开始命令0x01PS2_Cmd(Comd[1]); //请求数据0x42for(byte=2;byte<9;byte++) //开始接受数据{for(ref=0x01;ref<0x100;ref<<=1){CLK_H;CLK_L;delay_us(50);CLK_H;if(DI)Data[byte] = ref|Data[byte];}delay_us(50);}CS_H;
}
a.数据传输必须在CS拉低期间进行,数据传输完成后,还要将CS拉回高电平,以便下一次的通讯。
b.单片机发送了0x01 0x42给手柄,此时手柄会返回0x5A给单片机,意味着接收到了请求,即将返回数据。所以Data[2]保存的就是手柄返回的0x5A。后面的Data[3]-Data[8]返回的都是按键和摇杆的状态信息。
6.检测按键状态
//对读出来的PS2的数据进行处理 只处理了按键部分 默认数据是红灯模式 只有一个按键按下时
//按下为0, 未按下为1
u8 PS2_DataKey()
{u8 index;PS2_ClearData();PS2_ReadData();Handkey=(Data[4]<<8)|Data[3]; //这是16个按键 按下为0, 未按下为1for(index=0;index<16;index++){ if((Handkey&(1<<(MASK[index]-1)))==0)return index+1;}return 0; //没有任何按键按下
}
a.上图有给手柄按键标号,一共16个按键,包括两个摇杆,不包括MODE键。16个按键刚好是两个八位二进制数。所以用Data[3]和Data[4]表示所有的按键的状态。
b.u16 Handkey
,通过这个变量定义可以看出来Handkey是一个16位的二进制数,Handkey=(Data[4]<<8)|Data[3]
表示的是Handkey这个变量的高八位是Data[4],低八位是Data[3]
假如我SELECT按下了,那么Data[3]=1111 1110B
如果L2按下了,那么Data[4]=1111 1110B
所以Handkey表示的就是1111 1110 1111 1110B
c.for(index=0;index<16;index++)
因为有16个按键,所以进行16次循环,以此判断是被按下的是哪个键。
if((Handkey&(1<<(MASK[index]-1)))==0)
这个函数由内到外分析0<=index<=15
,所以1<=MASK[index]<=16
,所以0<=MASK[index]-1)<=15
,然后将1换算成16位的二进制数为:0000 0000 0000 0001B
,所以1<<(MASK[index]-1)
就是每次左移一位,循环15次。然后和Handkey进行与运算,如果为0,则说明按键被按下。最后index+1
是因为index是从0开始算的,而按键是从1开始计算的,所以最后返回的值需要+1。但这个函数只能判断单个按键按下。如果有多个按键按下,只能检测按键数较小的那个值,譬如方向上(5)和START(4)同时被按下,则返回值就是4。
#define PSB_SELECT 1
#define PSB_L3 2
#define PSB_R3 3
#define PSB_START 4
#define PSB_PAD_UP 5
#define PSB_PAD_RIGHT 6
#define PSB_PAD_DOWN 7
#define PSB_PAD_LEFT 8
#define PSB_L2 9
#define PSB_R2 10
#define PSB_L1 11
#define PSB_R1 12
#define PSB_GREEN 13
#define PSB_RED 14
#define PSB_BLUE 15
#define PSB_PINK 16u16 MASK[]={PSB_SELECT,PSB_L3,PSB_R3 ,PSB_START,PSB_PAD_UP,PSB_PAD_RIGHT,PSB_PAD_DOWN,PSB_PAD_LEFT,PSB_L2,PSB_R2,PSB_L1,PSB_R1 ,PSB_GREEN,PSB_RED,PSB_BLUE,PSB_PINK
};
7.检测摇杆的状态
#define PSS_RX 5
#define PSS_RY 6
#define PSS_LX 7
#define PSS_LY 8//得到一个摇杆的模拟量 范围0~256
u8 PS2_AnologData(u8 button)
{return Data[button];
}
a.四个摇杆反馈的是模拟量,范围在0x00~0xFF,转换成十进制即为0-255。摇杆的数值存放在Data[5]、Data[6]、Data[7]、Data[8]中
b.注意只有红灯模式下摇杆才反馈模拟量,绿灯模式下摇杆不反馈模拟量。
8.其他函数
8.1清除数据缓冲
//清除数据缓冲区
void PS2_ClearData()
{u8 a;for(a=0;a<9;a++)Data[a]=0x00;
}
8.2手柄震动函数
/******************************************************
motor1:右侧小震动电机 0x00关,其他开
motor2:左侧大震动电机 0x40~0xFF 电机开,值越大 震动越大
******************************************************/
void PS2_Vibration(u8 motor1, u8 motor2)
{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);
}
8.3发送模式设置
a.第八行PS2_Cmd(0x01)
则为红灯模式;PS2_Cmd(0x00)
则为绿灯模式;
b.第九行PS2_Cmd(0x03)
则只可以通过第八行的指令进行红绿灯模式切换;PS2_Cmd(0xEE)
则可以通过按MODE进行红绿灯模式切换。
//发送模式设置
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);
}
百度云链接:附件有STM32F103CBT6开发板和工程文件,还有手柄的规格书,及使用手册
链接:https://pan.baidu.com/s/1Pprxf4hvpu94GJAxjdi1cg
提取码:bpkz
PS2手柄移植到STM32上面的小笔记相关推荐
- ucos移植到stm32上的中断小小改进
uCosII移植到stm32上的文章和demo已经很多了,细节上建议大家可以看官方的移植文档( 当然是E文的).网上流传的各种移植版本基本都是基于官方的移植版本做了小改进.这些改进基本都限制在更适合自 ...
- Eclipse里面web项目上面的小地球消失的原因
一般java的web目录在eclipse里面的显示主要的目录.settings,src和WebContent , .classpath , .project 如果项目上面小地球消失,先查看目录格式是 ...
- stm32 移植java_把Lua移植到stm32上,效果不错! (amoBBS 阿莫电子论坛)
因为我们产品的需要满足不同行业需求,所以一直在寻找一个脚本语言,以便灵活配置. 前段时间还自己花时间去实现一个C语言解释器,看了一堆编译原理的东西,以及虚拟机等,头都搞大了,把基本功能实现了,但总是不 ...
- mavlink协议详解_MAVLink通讯协议在STM32上移植,并自定义协议
mavlink全称是(Micro Air Vehicle Message Marshalling Library),从名字可以看出,mavlink是主要面向飞控的一种开源通信协议.因此它默认定义了很多 ...
- 12864移植到STM32平台
利用串口方式将原本51单片机上的串口程序移植到STM32上,调试了两天终于可以显示了 1. 排查IO的初始化是否有问题,在STM32平台上需要对IO口时钟使能,IO口模式配置,一些特殊的管脚还需要做特 ...
- uc/OS-II操作系统移植:uc/OS-II移植到STM32流程(文章末尾附实例)
一.实验目的 1.掌握在STM32上移植嵌入式实时操作系统uC/OS-II的方法 2.掌握在STM32上移植uC/OS-II的基本步骤 3.掌握uC/OS-II的文件OS_CPU.H编写 4.掌握uC ...
- 接口有个电池标志_USB接口上的小标签有啥用?从“+”号到闪电的奥秘
你有注意过自己PC,尤其是笔记本USB接口上面的小符号了吗?从"+"号到闪电,小狮子前几天移动硬盘接驳始终没反应的时候,各种切换完毕发现这似乎是一个平时很少注意的细节啊-- 从&q ...
- 去除桌面快捷方式上的小箭头
电脑桌面上默认快捷方式左下角是有个小箭头的.很多用户可能不习惯快捷方式小箭头.那怎么去掉呢? 方式一 1.新建一个TXT文档(文档的名称自己顺便命名即可),然后把下面的这些英文全部复制到TXT文档内保 ...
- 触摸控制芯片MPR121驱动移植(STM32)
本文记录将arduino下的mpr121触摸板驱动程序移植到stam32f1 1.触摸板简介 之前买了块mpr121做主控的触摸控制板(如下图),卖家给的驱动是arduino的,最近做项目需要移植到s ...
- 路由器上的usb接口有什么用_USB接口上的小标签有啥用?从“+”号到闪电的奥秘...
你有注意过自己PC,尤其是笔记本USB接口上面的小符号了吗?从"+"号到闪电,小狮子前几天移动硬盘接驳始终没反应的时候,各种切换完毕发现这似乎是一个平时很少注意的细节啊-- 从&q ...
最新文章
- Lucene 对文档打分的规则整理记录
- 在java中读取某个文件中的数据内容
- Exp4 恶意代码分析 20164309
- 关于CSS3圆角矩形的一些学习探讨
- CSDN编程挑战——《进制转换》
- 泛泰A860(高通公司8064 cpu 1080p) 拂4.4中国民营recovery TWRP2.7.1.2文本(通过刷第三版)...
- 计算机网络—时延相关真题练习(三)
- jvm crash分析工具
- Linux 服务器为什么被黑
- 操作系统——基本概念·
- Linux快速入门之文件操作(01)
- 使用八爪鱼工具爬取京东当前所有手机信息存入mysql
- 数学表达式Round2
- beetl模板使用场景_Beetl模板引擎入门教程
- Python 实现文字聊天室-功能拓展
- java 开发工具eli_二进制开发ELI5 –第1部分
- 闪存卡提示格式化怎么办?里面的数据怎么恢复
- Unreal Engine 4(虚幻UE4)GameplayAbilities 插件入门教程(三)技能标签(Ability Tags)
- MySQL查学生年龄30到40之间_MYSQL查询练习 1
- 雅马哈机器人左手右手系统_雅马哈四轴机器人调试笔记
热门文章
- 字节跳动面试题后台_JAVA字节跳动面试题分享,一面
- 定时器控制一个ADC实现双通道采样(TIM+ADC+DMA)
- delphi 剪切板变量_Delphi操作剪贴板
- ARM嵌入式系统的问题分析与总结
- CAD,SolidWorks相比ProE,UG等软件有什么区别?怎么下载?
- Data Center TCP (DCTCP)学习笔记
- 计算机网络为什么要分层?
- 单片机大学生实习感悟体验
- 服务器如何备份系统和配置,windows server 2008和2012如何设置完整备份+增量备份
- 30款各大论坛的XP