【正点原子STM32连载】 第四十一章 游戏手柄实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

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所示:
【正点原子STM32连载】 第四十一章 游戏手柄实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第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所示:
【正点原子STM32连载】 第四十一章 游戏手柄实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第2张图片

图41.1.2 FC手柄读取时序和接线图
从上图可看出,读取手柄按键值的信息十分简单:先Latch(锁存键值),然后就得到了第一个按键值(A),之后在Clock的作用下,依次读取其他按键的键值,总共8个按键键值。标准的NES手柄高低电平周期12us,占空比为50%,且按键按下后DATA上的电平为负,不同手柄可能有差异,但时序差不多,我们编程时参考这个时序来实现即可。
有了以上了解,我们就可以通过STM32的IO来驱动FC手柄了。
41.2 硬件设计

  1. 例程功能
    本实验采用STM32的3个普通IO连接FC手柄的Clock、Data和Latch信号,本章实验功能简介:在主函数不停的查询手柄输入,一旦检测到输入信号,则在LCD模块上面显示键值和对应的按键符号。同样我们也是用LED0来指示程序正在运行。
  2. 硬件资源
    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所示。
    【正点原子STM32连载】 第四十一章 游戏手柄实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第3张图片

图41.2.1 COM3功能选择示意图
当K1打到上面(JOYPAD)时,COM3作为FC手柄接口,当K1打到下面(RS232)时,COM3作为RS232串口。COM3接口与MCU的连接原理图如41.2.2所示:
【正点原子STM32连载】 第四十一章 游戏手柄实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第4张图片

图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 程序流程图
【正点原子STM32连载】 第四十一章 游戏手柄实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第5张图片

图41.3.1.1 游戏手柄实验程序流程图
41.3.2 程序解析

  1. 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所示的内容:
【正点原子STM32连载】 第四十一章 游戏手柄实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第6张图片

图41.4.1程序运行效果图
此时我们按下FC手柄的按键,则可以看到LCD上显示了对应按键的键值以及对应的符号。如图41.4.2所示:
【正点原子STM32连载】 第四十一章 游戏手柄实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第7张图片

图41.4.2解码游戏手柄数据成功

你可能感兴趣的:(stm32,单片机,游戏)