3.4 PS2 无线手柄控制模块
PS2 手柄兼容索尼的 PlayStation2 游戏机的遥控手柄。索尼的 psx 系列游戏主机在全球很是畅销。不知什么时候便有人打起 PS2 手柄的主意,破解了通讯协议,使得手柄可以接在其他器件上遥控使用,比如遥控我们熟悉的机器人。突出的特点是这款手柄性价比极高,按键丰富,方便扩展到其它应用中。
3.4.1 模块来源
采购链接: PS2 手柄机器人遥控器 STM32 2.4G 无线遥控器送转接板
资料下载链接: https://pan.baidu.com/s/15WcgkOqKLfXpQpZ4qv3rJA 资料提取码:3qdg
3.1.2 规格参数
手柄工作电压:两节 7 号电池供电
接收器工作电压:3~5V
遥控距离:15 米左右
控制方式:串行通信
管脚数量: 6 Pin
3.1.3 移植过程
我们的目标是在梁山派 GD32F470 上能够实现读取 PS2 手柄上的哪一个按键被按下和读取遥感的 AD 值的功能。首先要获取资料,查看数据手册应如何实现,再移植至我们的工程。
3.1.3.1 查看资料
PS2 手柄由手柄与接收器两部分组成,手柄主要负责发送按键信息;接收器与单片机 (也可叫作主机,可直接用在 PS2 游戏机上)相连,用于接收手柄发来的信息,并传递给单片机,单片机也可通过接收器,向手柄发送命令,配置手柄的发送模式。 特别声明:因批次不同或代工厂家不同,手柄和接收器的外观会有所区别(接收器上 都有指示灯,但一种接收器上有电源灯,另一种没有电源灯),但是接收器的引脚定义是一样的,解码方式是一样的,使用相同。
引脚说明
| 引脚 | 说明 |
|---|---|
| DI/DAT | 信号流向,从手柄到主机,此信号是一个 8bit 的串行数据,同步传送于时钟的下降沿。信号的读取在时钟由髙到低的变化过程中完成; |
| DO/CMD | 信号流向,从主机到手柄,此信号和 DI 相对,信号是一个 8bit 的串行数据, 同步传送于时钟的下降沿; |
| GND | 电源地; |
| VDD | 接收器工作电源,电源范围 3~5V; |
| CS/SEL | 用于提供手柄触发信号。在通讯期间,处于低电平; |
| CLK | 时钟信号,由主机发出,用于保持数据同步; |
控制说明
时钟频率 250KHz(4us),如果接收数据不稳定,可以适当的增加频率。在通讯过中,一串数据通讯完成后 CS 才会由低转高,不是 1 个字节通讯完成后就由低转高,在通讯期间,一直处于低电平。 在时钟下降沿时,完成数据(lbit)的发送与接收,发送和接收是同时完成的。
当单片机想读手柄数据或向手柄发送命令时,将会拉低 CS 线电平,并发出一个命令“0x01”;手柄会回复它的 ID “0x41=绿灯模式,0x73=红灯模式”;在手柄发送 ID 的同时,单片机将传送 0x42,请求数据;随后手柄发送出 0x5A,告诉单片机“数据来了”。
一个通讯周期有 9 个字节(8 位),这些数据是依次按位传送。
idle:数据线空闲,该数据线无数据传送。
举例:当有按键按下,对应位为“0”,其他位为“1”,例如当键“SELECT”被按下时,Data[3]=11111110。
红灯模式时:左右摇杆发送模拟值,0x00〜OxFF 之间,且摇杆按下的键值 L3、R3 有效;
绿灯模式时:左右摇杆模拟值为无效,推到极限时,对应发送 UP、RIGHT、DOWN、LEFT、△、〇、X、□,但是按键 L3、R3 无效。
3.1.3.2 引脚选择
| 指纹识别模块 | 立创·梁山派 |
|---|---|
| CLK | PF6 |
| CS | PF9 |
| CMD | PF8 |
| DAT | PF7 |
| VDD | 3V3 |
| GND | GND |
3.1.3.3 移植至工程
移植步骤中的导入.c 和.h 文件与上一节相同,只是将.c 和.h 文件更改为 bsp_ps2.c 与 bsp_ps2.h。见 2.2.3.3 移植至工程。这里不再过多讲述。移植完成后面修改相关代码。
在文件 bsp_ps2.c 中,编写如下代码。
#include "bsp_ps2.h"
#include "bsp_usart.h"
#include "stdio.h"
uint8_t Comd[2]={0x01,0x42}; //开始命令和请求数据命令
uint8_t Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; //数据存储数组
//按键值
uint16_t MASK[]={
PSB_SELECT, //SELECT键
PSB_L3, //左遥杆被按下
PSB_R3 , //右遥杆被按下
PSB_START, //START键
PSB_PAD_UP, //上键
PSB_PAD_RIGHT, //右键
PSB_PAD_DOWN, //下键
PSB_PAD_LEFT, //左键
PSB_L2, //左边顶部2键
PSB_R2, //右边顶部2键
PSB_L1, //左边顶部1键
PSB_R1 , //右边顶部1键
PSB_GREEN, //绿色的三角形图案键
PSB_RED, //红色的圆形图案键
PSB_BLUE, //蓝色的X形图案键
PSB_PINK //粉色的矩形图案键
};
/**********************************************************
* 函 数 名 称:PS2_GPIO_Init
* 函 数 功 能:初始化PS2手柄接收端引脚
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void PS2_GPIO_Init(void)
{
/* 使能时钟 */
rcu_periph_clock_enable(RCU_CLK);
rcu_periph_clock_enable(RCU_DAT);
rcu_periph_clock_enable(RCU_CMD);
rcu_periph_clock_enable(RCU_CS);
/* 配置CLK */
gpio_mode_set(PORT_CLK,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_CLK);
gpio_output_options_set(PORT_CLK,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_CLK);
CLK_OUT(1);
/* 配置DI */
gpio_mode_set(PORT_DAT,GPIO_MODE_INPUT,GPIO_PUPD_PULLDOWN,GPIO_DAT);
/* 配置DO */
gpio_mode_set(PORT_CMD,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_CMD);
gpio_output_options_set(PORT_CMD,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_CMD);
/* 配置CS */
gpio_mode_set(PORT_CS,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_CS);
gpio_output_options_set(PORT_CS,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_CS);
CS_OUT(1);
}
/******************************************************************
* 函 数 名 称:PS2_Cmd
* 函 数 说 明:向手柄发送命令
* 函 数 形 参:CMD要发送的命令
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PS2_Cmd(uint8_t CMD)
{
volatile uint16_t ref=0x01;
Data[1] = 0;
for(ref=0x01;ref<0x0100;ref<<=1)
{
CMD_OUT( (ref&CMD) );
CLK_OUT(1); //时钟拉高
delay_us(50);
CLK_OUT(0);
delay_us(50);
CLK_OUT(1);
if( DAT_GET() )
{
Data[1] = ref|Data[1];
}
}
}
/******************************************************************
* 函 数 名 称:PS2_ReadData
* 函 数 说 明:读取手柄数据
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PS2_ReadData(void)
{
volatile uint8_t byte=0;
volatile uint16_t ref=0x01;
uint16_t i = 0;
CS_OUT(0);
PS2_Cmd(Comd[0]); //开始命令
PS2_Cmd(Comd[1]); //请求数据
for(byte=2;byte<9;byte++) //开始接受数据
{
for(ref=0x01;ref<0x100;ref<<=1)
{
CLK_OUT(1);
delay_us(10);
CLK_OUT(0);
delay_us(10);
CLK_OUT(1);
if( DAT_GET() )
{
Data[byte] = ref|Data[byte];
}
}
delay_us(16);
}
CS_OUT(1);
}
/******************************************************************
* 函 数 名 称:PS2_RedLight
* 函 数 说 明:判断是否为红灯模式
* 函 数 形 参:无
* 函 数 返 回:0:红灯模式 其他:其他模式
* 作 者:LC
* 备 注:0X73=红灯亮的模式 0x41红灯灭的模式
******************************************************************/
uint8_t PS2_RedLight(void)
{
CS_OUT(0);
PS2_Cmd(Comd[0]); //开始命令
PS2_Cmd(Comd[1]); //请求数据
CS_OUT(1);
if( Data[1] == 0X73) return 0 ;
else return 1;
}
/******************************************************************
* 函 数 名 称:PS2_ClearData
* 函 数 说 明:清除数据缓冲区
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PS2_ClearData(void)
{
uint8_t a;
for(a=0;a<9;a++)
Data[a]=0x00;
}
//对读出来的PS2的数据进行处理
//只处理了按键部分,默认数据是红灯模式,只有一个按键按下时
//按下为0, 未按下为1
/******************************************************************
* 函 数 名 称:PS2_DataKey
* 函 数 说 明:对读出来的PS2的数据进行处理,处理了按键部分
* 函 数 形 参:无
* 函 数 返 回:0=无按键按下 其他=对应键值,返回的值见数组MASK里的按键
* 作 者:LC
* 备 注:当有按键按下,对应位为“0”,其他位为“1”,
* 例如当键“SELECT”被按下时,Data[3]=11111110B。
* 低为在前
******************************************************************/
uint8_t PS2_DataKey(void)
{
uint8_t index=0;
uint16_t Handkey=0;
PS2_ClearData();//清除接收缓存
PS2_ReadData();//读取PS2发送过来的数据
//这是16个按键 按下为0, 未按下为1
Handkey=(Data[4]<<8)|Data[3];
for(index=0;index<16;index++)
{
if( (Handkey & ( 1 << (MASK[index]-1) ) ) ==0 )
{
return index+1;//返回键值
}
}
return 0; //没有任何按键按下
}
/******************************************************************
* 函 数 名 称:PS2_AnologData
* 函 数 说 明:得到一个摇杆的模拟量
* 函 数 形 参:button=摇杆的哪个方向的模拟量,取值范围有:
* PSS_LX 左摇杆左右方向的模拟量
* PSS_LY 左摇杆上下方向的模拟量
* PSS_RX 右摇杆左右方向的模拟量
* PSS_RY 右摇杆上下方向的模拟量
* 函 数 返 回:摇杆的模拟量
* 作 者:LC
* 备 注:范围0~255
******************************************************************/
uint8_t PS2_AnologData(uint8_t button)
{
return Data[button];
}
/******************************************************************
* 函 数 名 称:PS2_shortPoll
* 函 数 说 明:手柄配置初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PS2_shortPoll(void)
{
CS_OUT(0);
delay_us(16);
PS2_Cmd(0x01);
PS2_Cmd(0x42);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
PS2_Cmd(0x00);
CS_OUT(1);
delay_us(16);
}
/******************************************************************
* 函 数 名 称:PS2_EnterConfing
* 函 数 说 明:PS2进入配置模式
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PS2_EnterConfing(void)
{
CS_OUT(0);
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_OUT(1);
delay_us(16);
}
/******************************************************************
* 函 数 名 称:PS2_TurnOnAnalogMode
* 函 数 说 明:发送模式设置
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PS2_TurnOnAnalogMode(void)
{
CS_OUT(0);
PS2_Cmd(0x01);
PS2_Cmd(0x44);
PS2_Cmd(0X00);
PS2_Cmd(0x01); //analog=Ox01;digital=Ox00软件设置发送模式
PS2_Cmd(0xEE);//Ox03锁存设置,即不可通过按键“MODE”设置模式。
//OxEE不锁存软件设置,可通过按键“MODE”设置模式。
PS2_Cmd(0X00);
PS2_Cmd(0X00);
PS2_Cmd(0X00);
PS2_Cmd(0x00);
CS_OUT(1);
delay_us(16);
}
/******************************************************************
* 函 数 名 称:PS2_VibrationMode
* 函 数 说 明:振动设置
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PS2_VibrationMode(void)
{
CS_OUT(0);
delay_us(16);
PS2_Cmd(0x01);
PS2_Cmd(0x4D);
PS2_Cmd(0X00);
PS2_Cmd(0x00);
PS2_Cmd(0X01);
CS_OUT(1);
delay_us(16);
}
/******************************************************************
* 函 数 名 称:PS2_ExitConfing
* 函 数 说 明:完成并保存配置
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PS2_ExitConfing(void)
{
CS_OUT(0);
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_OUT(1);
delay_us(16);
}
/******************************************************************
* 函 数 名 称:PS2_SetInit
* 函 数 说 明:手柄配置初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PS2_SetInit(void)
{
PS2_GPIO_Init();//引脚初始化
PS2_shortPoll();
PS2_shortPoll();
PS2_shortPoll();
PS2_EnterConfing();//进入配置模式
PS2_TurnOnAnalogMode();//“红绿灯”配置模式,并选择是否保存
PS2_VibrationMode();
PS2_ExitConfing(); //完成并保存配置
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
在文件 bsp_ps2.h 中,编写如下代码。
#ifndef _BSP_PS2_H
#define _BSP_PS2_H
#include "gd32f4xx.h"
#include "systick.h"
//端口移植
#define RCU_CLK RCU_GPIOF
#define PORT_CLK GPIOF
#define GPIO_CLK GPIO_PIN_6
#define RCU_DAT RCU_GPIOF
#define PORT_DAT GPIOF
#define GPIO_DAT GPIO_PIN_7
#define RCU_CMD RCU_GPIOF
#define PORT_CMD GPIOF
#define GPIO_CMD GPIO_PIN_8
#define RCU_CS RCU_GPIOF
#define PORT_CS GPIOF
#define GPIO_CS GPIO_PIN_9
#define CMD_OUT(x) gpio_bit_write(PORT_CMD,GPIO_CMD, (x?SET:RESET))
#define CLK_OUT(x) gpio_bit_write(PORT_CLK,GPIO_CLK, (x?SET:RESET))
#define DAT_GET() gpio_input_bit_get(PORT_DAT,GPIO_DAT)
#define CS_OUT(x) gpio_bit_write(PORT_CS,GPIO_CS, (x?SET:RESET))
/***********************************************************************************/
#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 //X键
#define PSB_PINK 16 //矩形键
#define PSB_TRIANGLE 13
#define PSB_CIRCLE 14
#define PSB_CROSS 15
#define PSB_SQUARE 26
#define PSS_RX 5 //右摇杆X轴数据
#define PSS_RY 6
#define PSS_LX 7
#define PSS_LY 8
void PS2_GPIO_Init(void);
uint8_t PS2_RedLight(void);
uint8_t PS2_DataKey(void);
uint8_t PS2_AnologData(uint8_t button);
void PS2_SetInit(void);
#endif2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
3.1.4 移植验证
在自己工程中的 main 主函数中,编写如下。
/********************************************************************************
* 文 件 名: main.c
* 版 本 号: 初版
* 修改作者: LC
* 修改日期: 2022年04月21日
* 功能介绍:
******************************************************************************
* 注意事项:
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "sys.h"
#include "bsp_usart.h"
#include "bsp_ps2.h"
/************************************************
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
作 者 : LC
*************************************************/
int main(void)
{
uint16_t key = 0;
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化
usart_gpio_config(9600U); // 串口0初始化
//PS2手柄初始化
PS2_SetInit();
printf("start\r\n");
delay_1ms(1000);
while(1)
{
key = PS2_DataKey();//读取是否有按键按下
if( key != 0 )
{
//输出每一个按键被按下后的值
printf("%d down \r\n",key);
}
if( PS2_RedLight() == 0 )//如果当前是红灯模式
{
//输出左右两个遥感的XY值
printf(" %5d %5d %5d %5d\r\n",PS2_AnologData(PSS_LX),PS2_AnologData(PSS_LY),
PS2_AnologData(PSS_RX),PS2_AnologData(PSS_RY) );
}
delay_1ms(50);//防止发送太快,接收器反应不过来
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
移植现象:随意按下按键,都有反应
移植成功示例,见文件 3.4.4-1 。
文件 3.4.4-1 移植成功示例
通过网盘分享的文件:立创·梁山派GD32F470ZGT6开发板【模块移植手册代码】
链接: https://pan.baidu.com/s/1pp44yjD1Dhh7U9iZ2a11IA 提取码: LCKF