STM32上使用HAL库驱动9针FC手柄实现游戏自由!

一、FC游戏手柄介绍

提到FC游戏机手柄,想必80 90后的那批人必然会燃起激情了,那是我们难以忘记的童年!

在时序上,FC 手柄的控制电路由 1 个 8 位并入串出的移位寄存器(CD4021)和一个时基集成电路(NE555,用于连发)构成。

可采用 STM32 的 3 个普通 IO 连接 FC 手柄的 Clock、Data 和 Latch 信号,通过设置 IO 口状态,按照特定的时序读取手柄按键值。如先将 Latch 置 1 锁存当前状态,再置 0,然后在 Clock 的作用下,依次读取 A、B、SELECT、START、UP、DOWN、LEFT、RIGHT 这 8 个按键的键值,按下为 0,松开为 1。

二、FC游戏手柄驱动实现

驱动代码是2个手柄的,方便双人游戏!!

1.FC游戏手柄相关宏定义

复制代码
/*
手柄DB9母头定义 
			母头 ---> 公头
DATA  ---> DB9_2 ---> DB9_4  GPIOA_2
LATCH ---> DB9_3 ---> DB9_3  GPIOA_3
CLOCK ---> DB9_4 ---> DB9_2  GPIOA_4
+5V   ---> DB9_6 ---> DB9_9
GND   ---> DB9_8 ---> DB9_7
*/
#define JOYPAD1 1
#define JOYPAD2 2

#define JoyPad_Delay_us 0

/* JOYPAD1引脚 定义 */
#define JOYPAD1_CLK_GPIO_PORT            GPIOB
#define JOYPAD1_CLK_GPIO_PIN             GPIO_PIN_13
#define JOYPAD1_CLK_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* JOYPAD1_CLK口时钟使能 */

#define JOYPAD1_LAT_GPIO_PORT            GPIOB
#define JOYPAD1_LAT_GPIO_PIN             GPIO_PIN_14
#define JOYPAD1_LAT_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)    /* JOYPAD1_LAT口时钟使能 */

#define JOYPAD1_DATA_GPIO_PORT           GPIOD
#define JOYPAD1_DATA_GPIO_PIN            GPIO_PIN_13
#define JOYPAD1_DATA_GPIO_CLK_ENABLE()   do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0)    /* JOYPAD1_DATA口时钟使能 */


/* JOYPAD2引脚 定义 */
#define JOYPAD2_CLK_GPIO_PORT            GPIOB
#define JOYPAD2_CLK_GPIO_PIN             GPIO_PIN_2
#define JOYPAD2_CLK_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* JOYPAD2_CLK口时钟使能 */

#define JOYPAD2_LAT_GPIO_PORT            GPIOC
#define JOYPAD2_LAT_GPIO_PIN             GPIO_PIN_4
#define JOYPAD2_LAT_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)    /* JOYPAD2_LAT口时钟使能 */

#define JOYPAD2_DATA_GPIO_PORT           GPIOB
#define JOYPAD2_DATA_GPIO_PIN            GPIO_PIN_1
#define JOYPAD2_DATA_GPIO_CLK_ENABLE()   do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)    /* JOYPAD2_DATA口时钟使能 */


/* JOYPAD1手柄连接引脚 */
#define JOYPAD1_CLK(x)   do{ x ? \
                              HAL_GPIO_WritePin(JOYPAD1_CLK_GPIO_PORT, JOYPAD1_CLK_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(JOYPAD1_CLK_GPIO_PORT, JOYPAD1_CLK_GPIO_PIN, GPIO_PIN_RESET); \
                        }while(0)   /* JOYPAD_CLK */

#define JOYPAD1_LAT(x)   do{ x ? \
                              HAL_GPIO_WritePin(JOYPAD1_LAT_GPIO_PORT, JOYPAD1_LAT_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(JOYPAD1_LAT_GPIO_PORT, JOYPAD1_LAT_GPIO_PIN, GPIO_PIN_RESET); \
                        }while(0)   /* JOYPAD_LATCH */

#define JOYPAD1_DATA     HAL_GPIO_ReadPin(JOYPAD1_DATA_GPIO_PORT, JOYPAD1_DATA_GPIO_PIN)   /* JOYPAD_DATA */

/* JOYPAD2手柄连接引脚 */
#define JOYPAD2_CLK(x)   do{ x ? \
                              HAL_GPIO_WritePin(JOYPAD2_CLK_GPIO_PORT, JOYPAD2_CLK_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(JOYPAD2_CLK_GPIO_PORT, JOYPAD2_CLK_GPIO_PIN, GPIO_PIN_RESET); \
                        }while(0)   /* JOYPAD_CLK */

#define JOYPAD2_LAT(x)   do{ x ? \
                              HAL_GPIO_WritePin(JOYPAD2_LAT_GPIO_PORT, JOYPAD2_LAT_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(JOYPAD2_LAT_GPIO_PORT, JOYPAD2_LAT_GPIO_PIN, GPIO_PIN_RESET); \
                        }while(0)   /* JOYPAD_LATCH */

#define JOYPAD2_DATA     HAL_GPIO_ReadPin(JOYPAD2_DATA_GPIO_PORT, JOYPAD2_DATA_GPIO_PIN)   /* JOYPAD_DATA */

2.FC游戏手柄GPIO初始化

复制代码
/**
 * @brief       初始化手柄接口
 * @param       无
 * @retval      无
 */
void joypadInit(uint8_t joypadNum)
{
	GPIO_InitTypeDef gpio_init_struct = {0};
	if(JOYPAD1 == joypadNum)
	{
		JOYPAD1_CLK_GPIO_CLK_ENABLE();  /* CLK   所在IO时钟初始化 */
		JOYPAD1_LAT_GPIO_CLK_ENABLE();  /* LATCH 所在IO时钟初始化 */
		JOYPAD1_DATA_GPIO_CLK_ENABLE(); /* DATA  所在IO时钟初始化 */

		
		gpio_init_struct.Pin = JOYPAD1_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(JOYPAD1_CLK_GPIO_PORT, &gpio_init_struct); /* JOYPAD1_CLK  引脚模式设置 */

		gpio_init_struct.Pin = JOYPAD1_LAT_GPIO_PIN;
		HAL_GPIO_Init(JOYPAD1_LAT_GPIO_PORT, &gpio_init_struct); /* JOYPAD1_LAT  引脚模式设置 */

		gpio_init_struct.Pin = JOYPAD1_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(JOYPAD1_DATA_GPIO_PORT, &gpio_init_struct); /* JOYPAD1_DATA 引脚模式设置 */
		printf("joypad1 init ok !\r\n");
	}
	else if(JOYPAD2 == joypadNum)
	{
		JOYPAD2_CLK_GPIO_CLK_ENABLE();  /* CLK   所在IO时钟初始化 */
		JOYPAD2_LAT_GPIO_CLK_ENABLE();  /* LATCH 所在IO时钟初始化 */
		JOYPAD2_DATA_GPIO_CLK_ENABLE(); /* DATA  所在IO时钟初始化 */

		gpio_init_struct.Pin = JOYPAD2_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(JOYPAD2_CLK_GPIO_PORT, &gpio_init_struct); /* JOYPAD1_CLK  引脚模式设置 */

		gpio_init_struct.Pin = JOYPAD2_LAT_GPIO_PIN;
		HAL_GPIO_Init(JOYPAD2_LAT_GPIO_PORT, &gpio_init_struct); /* JOYPAD1_LAT  引脚模式设置 */

		gpio_init_struct.Pin = JOYPAD2_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(JOYPAD2_DATA_GPIO_PORT, &gpio_init_struct); /* JOYPAD1_DATA 引脚模式设置 */
		printf("joypad2 init ok !\r\n");
	}
    
}

3.FC游戏手柄键值获取

复制代码
/**
 * @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 joypadRead(uint8_t joypadNum) 
{
    volatile uint8_t temp = 0;
    uint8_t t;
	if(JOYPAD1 == joypadNum)
	{
		JOYPAD1_LAT(1);          /* 锁存当前状态 */
		delay_us(JoyPad_Delay_us);
		JOYPAD1_LAT(0);
		for (t = 0; t < 8; t++) /* 移位输出数据 */
		{
			temp >>= 1;
			if (JOYPAD1_DATA == 0)
			{
				temp |= 0x80;   /* LOAD之后,就得到第一个数据 */
			}
			JOYPAD1_CLK(1);      /* 每给一次脉冲,收到一个数据 */
			delay_us(JoyPad_Delay_us);
			JOYPAD1_CLK(0);
			delay_us(JoyPad_Delay_us);
		}
	}
	else if(JOYPAD2 == joypadNum)
	{
		JOYPAD2_LAT(1);          /* 锁存当前状态 */
		delay_us(JoyPad_Delay_us);
		JOYPAD2_LAT(0);
		for (t = 0; t < 8; t++) /* 移位输出数据 */
		{
			temp >>= 1;
			if (JOYPAD2_DATA == 0)
			{
				temp |= 0x80;   /* LOAD之后,就得到第一个数据 */
			}
			JOYPAD2_CLK(1);      /* 每给一次脉冲,收到一个数据 */
			delay_us(JoyPad_Delay_us);
			JOYPAD2_CLK(0);
			delay_us(JoyPad_Delay_us);
		}
	}
//	printf("joypadNum%d=0x%X\r\n",joypadNum,temp);
    return temp;
}

三、游戏体验

相关推荐
1+2单片机电子设计7 小时前
基于 STM32 的太阳能 MPPT 充电控制器设计
stm32·单片机·嵌入式硬件
xiaohai@Linux8 小时前
STM32之移植原生的infoNES nes游戏模拟器源码实现游戏自由!!!(原生纯C版,非汇编版)
stm32·游戏·模拟器·infones·nes游戏机
小李做物联网9 小时前
【单片机毕业设计】143.1基于单片机stm32塔吊控制反馈物联网嵌入式项目程序开发系统
stm32·单片机·嵌入式硬件·物联网
无人装备硬件开发爱好者20 小时前
深度解析:STM32 MDK 工程 HEX 文件转 BIN 文件 —— 原理、方法、优缺点与实战指南(中)
stm32·嵌入式软件·hex2bin
嵌入式的飞鱼1 天前
SD NAND 焊接避坑指南:LGA-8 封装手工焊接技巧与常见错误
人工智能·stm32·单片机·嵌入式硬件·tf卡
网易独家音乐人Mike Zhou1 天前
【嵌入式模块芯片开发】LP87524电源PMIC芯片配置流程,给雷达供电的延时上电时序及API函数
c语言·stm32·单片机·51单片机·嵌入式·电源·毫米波雷达
无人装备硬件开发爱好者1 天前
深度解析:STM32 MDK 工程 HEX 文件转 BIN 文件 —— 原理、方法、优缺点与实战指南(下)
stm32·嵌入式软件·mdk·hex2bin
无人装备硬件开发爱好者1 天前
深度解析:STM32 MDK 工程 HEX 文件转 BIN 文件 —— 原理、方法、优缺点与实战指南(上)
stm32·嵌入式硬件·hex2bin
youcans_1 天前
【动手学电机驱动】 STM32-FOC(11)ST MCSDK6.0 电机控制软件框架
stm32·单片机·嵌入式硬件·foc·电机驱动