1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#
第四十一章 游戏手柄实验
FC游戏机(又称:红白机/小霸王游戏机)发行了很多经典的游戏,给不少人的童年留下了无限乐趣。本章,我们将向大家介绍如何通过STM32来驱动FC游戏机手柄,将FC游戏机的手柄作为战舰STM32开发板的输入设备(综合实验可以直接通过这个手柄来玩FC游戏)。
在本章中,我们将使用STM32驱动FC手柄,将手柄的按键键值等信息通过TFT LCD模块显示出来。本章分为如下几个部分:
41.1 游戏手柄简介
41.2 硬件设计
41.3 软件设计
41.4 下载验证
41.1 游戏手柄简介
FC游戏机曾经是一统天下(现在也还是很多人玩),红极一时,那时任天堂单是FC机的主机的发售收入就超过全美国的电视台的收入的总和。本章,我们将使用STM32来驱动FC手柄,实现手柄控制信号的读取,我们先来了解一下FC手柄。
FC手柄,大致可分为两种:一种手柄插口是11针的,一种是9针的。但11针的现在市面上很少了(因为11针手柄是早期FC组装兼容机最主要的周边),现在几乎都是9针FC组装手柄的天下,所以我们本章使用的是9针FC手柄,该手柄还有一个特点,就是可以直接和DB9的串口公头对插!这样与开发板的连接就简单了。FC手柄的外观如图41.1.1所示:
图41.1.1 FC手柄外观图
这种手柄一般有10个按键(实际是8个键值):上、下、左、右、Start、Select、A、B、A连发、B连发。这里的A和A连发是一个键值,而B和B连发也是一个键值,只是连发按键当你一直按下的时候,会不停的发送(方便快速按键,比如发炮弹之类的功能)。
FC手柄的控制电路,由1个8位并入串出的移位寄存器(CD4021),外加一个时基集成电路(NE555,用于连发)构成。不过现在的手柄,为了节约成本,直接就在PCB上做绑定了,所以你拆开手柄,一般是看不到里面有四四方方的IC,而只有一个黑色的小点,所有电路都集成到这个里面了,但是他们的控制和读取方法还是一样的。
9针手柄实际上只有5根线起作用,不同的文档里命名会有一些差别,分别如下:
VCC = 5V供电
GND = 地线
LATCH = 锁存信号,由主机发送
CLOCK = 时钟信号,有些文档会叫PULSE,由主机发送
DATA = 串行数据线 低电平有效。
我们可以把它看为键盘,标准的FC手柄的控制读取时序和接线图如图41.1.2所示:
图41.1.2 FC手柄读取时序和接线图
从上图可看出,读取手柄按键值的信息十分简单:先Latch(锁存键值),然后就得到了第一个按键值(A),之后在Clock的作用下,依次读取其他按键的键值,总共8个按键键值。标准的NES手柄高低电平周期12us,占空比为50%,且按键按下后DATA上的电平为负,不同手柄可能有差异,但时序差不多,我们编程时参考这个时序来实现即可。
有了以上了解,我们就可以通过STM32的IO来驱动FC手柄了。
41.2 硬件设计
- 例程功能
本实验采用STM32的3个普通IO连接FC手柄的Clock、Data和Latch信号,本章实验功能简介:在主函数不停的查询手柄输入,一旦检测到输入信号,则在LCD模块上面显示键值和对应的按键符号。同样我们也是用LED0来指示程序正在运行。 - 硬件资源
1)LED灯
LED0 - PB5
2)串口1 (PA9/PA10连接在板载USB转串口芯片CH340上面)
3)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
4)FC游戏手柄
Clock(PD3)、Data(PB10)和Latch(PB11)
战舰STM32开发板板载了一个FC手柄接口(COM3),其实就是一个DB9接公头插座,FC手柄接口和COM3公用一个接口,通过开发板上的K1开关来选择,如图41.2.1所示。
图41.2.1 COM3功能选择示意图
当K1打到上面(JOYPAD)时,COM3作为FC手柄接口,当K1打到下面(RS232)时,COM3作为RS232串口。COM3接口与MCU的连接原理图如41.2.2所示:
图41.2.2 FC手柄接头与STM32的连接电路图
图41.2.2中,COM3就是用来连接FC手柄,该接头采用标准的DR9座,当K1开关打到上面的时候,COM_TX连接U3_TX、COM_RX连接U3_RX,然后通过P8,连接在PB11和PB10上面。所以要将FC手柄通过COM3连接在STM32上面,必须K1开关打到上面,并且P8需要用跳线帽连接PB10(TX)和COM3_RX、PB11(RX)和COM3_TX。
图中的D3稳压二极管,是为了防止COM3做RS232使用时,高压烧坏MCU的IO口。
本例程使用FC手柄(JOYPAD)功能时,COM_TX是LAT(Latch)信号,COM_RX是DAT(Data)信号,JOY_CLK是CLK(Clock)信号,分别连接在STM32的PB11、PB10和PD3上面,这里JOY_CLK和OV_SCL信号线共用PD3,所以FC手柄和摄像头模块得分时复用PD3才可以。
在设置好开发板的连接后(P8跳线帽:PB10(TX)和COM3_RX连接、PB11(RX)和COM3_TX连接,K1开关打JOYPAD位置),将FC手柄插入COM3插口即可。
41.3 程序设计
41.3.1 程序流程图
图41.3.1.1 游戏手柄实验程序流程图
41.3.2 程序解析
- joypad驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。JOYPAD驱动源码包括两个文件:joypad.c和joypad.h。joypad.h和前面定时器输入捕获功能的.h头文件代码相似,这里就不介绍了,详见本例程源码。
下面我们直接介绍joypad.c的程序,下面是与红外遥控初始化相关的函数,其定义如下:
/**
* @brief 初始化手柄接口
* @param 无
* @retval 无
*/
void joypad_init(void)
{
JOYPAD_CLK_GPIO_CLK_ENABLE(); /* CLK所在IO时钟初始化 */
JOYPAD_LAT_GPIO_CLK_ENABLE(); /* LATCH所在IO时钟初始化 */
JOYPAD_DATA_GPIO_CLK_ENABLE(); /* DATA 所在IO时钟初始化 */
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin = JOYPAD_CLK_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(JOYPAD_CLK_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = JOYPAD_LAT_GPIO_PIN;
HAL_GPIO_Init(JOYPAD_LAT_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = JOYPAD_DATA_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_INPUT;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(JOYPAD_DATA_GPIO_PORT, &gpio_init_struct);
}
/**
* @brief 手柄延迟函数
* @param t : 要延时的时间
* @retval 无
*/
static void joypad_delay(uint16_t t)
{
while (t--);
}
/**
* @brief 读取手柄按键值
* @note FC手柄数据输出格式:
* 每给一个脉冲,输出一位数据,输出顺序:
* A -> B -> SELECT -> START -> UP -> DOWN -> LEFT -> RIGHT.
* 总共8位, 对于有C按钮的手柄, 按下C其实就等于 A + B 同时按下.
* 按下是1,松开是0.
* @param 无
* @retval 按键结果, 格式如下:
* [7]:右
* [6]:左
* [5]:下
* [4]:上
* [3]:Start
* [2]:Select
* [1]:B
* [0]:A
*/
uint8_t joypad_read(void)
{
volatile uint8_t temp = 0;
uint8_t t;
JOYPAD_LAT(1); /* 锁存当前状态 */
joypad_delay(80);
JOYPAD_LAT(0);
for (t = 0; t < 8; t++) /* 移位输出数据 */
{
temp >>= 1;
if (JOYPAD_DATA == 0)
{
temp |= 0x80; /* LOAD之后,就得到第一个数据 */
}
JOYPAD_CLK(1); /* 每给一次脉冲,收到一个数据 */
joypad_delay(80);
JOYPAD_CLK(0);
joypad_delay(80);
}
return temp;
}
有了以上函数,那我们在应用程序中初始化控制手柄的IO后,就可以利用joypad_read()返回的键值来设计其它的应用程序了,如同我们使用开发板上的按键一样简单。
2. main.c代码
在main.c里面编写如下代码。
/* 手柄按键符号定义 */
const char *JOYPAD_SYMBOL_TBL[8] = {"Right", "Left", "Down", "Up",
"Start", "Select", "A", "B"};
int main(void)
{
uint8_t key;
uint8_t t = 0, i = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
joypad_init(); /* 游戏手柄初始化 */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "JOYPAD TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "KEYVAL:", RED);
lcd_show_string(30, 130, 200, 16, 16, "SYMBOL:", RED);
while (1)
{
key = joypad_read();
if (key) /* 手柄有按键按下 */
{
lcd_show_num(116, 130, key, 3, 16, BLUE); /* 显示键值 */
for (i = 0; i < 8; i++)
{
if (key & (0X80 >> i))
{
/* 清除之前的显示 */
lcd_fill(30 + 56, 130, 30 + 56 + 48, 150 + 16, WHITE);
lcd_show_string(30 + 56, 130, 200, 16, 16,
(char *)JOYPAD_SYMBOL_TBL[i], BLUE);/*显示符号*/
}
}
}
delay_ms(10);
t++;
if (t == 20)
{
t = 0;
LED0_TOGGLE(); /* LED0闪烁 */
}
}
}
此部分代码也比较简单,初始化JOYPAD之后,就一直扫描FC手柄(joypad_read函数),然后只要接收到手柄的有效型号,就在LCD模块上显示出来。
41.4 下载验证
在代码编译成功之后,我们通过下载代码到正点原子战舰STM32开发板上,可以看到LCD显示如图41.4.1所示的内容:
图41.4.1程序运行效果图
此时我们按下FC手柄的按键,则可以看到LCD上显示了对应按键的键值以及对应的符号。如图41.4.2所示:
图41.4.2解码游戏手柄数据成功