STM32_按键

该文使用函数指针来实现按键的扫描,以确保代码具有更好的移植性

函数指针

1、直接定义

例如我们按键扫描时会调用HAL_GPIO_ReadPin()函数,具体的函数声明如下:

GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)

那么定义该函数的函数指针,只需将函数名换成(*p)即可,具体定义如下:

GPIO_PinState (*p)(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin) = HAL_GPIO_ReadPin;

这样我们就定义好了一个函数指针,这个指针p指向函数HAL_GPIO_ReadPin()

2、别名定义

上述定义的函数指针太长,可以使用typedef来进行别名,具体别名如下:

typedef GPIO_PinState (*KEY_Type)(GPIO_TypeDef*,uint16_t);

这样别名之后,KEY_Type就替代了typedef GPIO_PinState (*)(GPIO_TypeDef*,uint16_t)这一部分。之后定义指针类型只需使用KEY_Type即可,具体定义如下:

KEY_Type KEY_Fun = HAL_GPIO_ReadPin;

这样我们就定义好了一个函数指针,这个指针KEY_Fun指向函数HAL_GPIO_ReadPin()

3、调用

使用函数指针,并不需要像其他指针一样用*去取值。只需直接使用指针即可。比如要实现HAL_GPIO_ReadPin的功能,具体使用方法如下:

/* 正常的函数 */
HAL_GPIO_ReadPin(KEY_Port,KEY_Pin);

/* 使用指针的方法 */
KEY_Fun(KEY_Port,KEY_Pin);

基本按键的功能的实现

这里使用轮询法去实现按键单机的功能。

key.h文件预处理内容

根据原理图,定义相关的引脚

#define KEY1_Port		GPIOA
#define KEY1_Pin		GPIO_PIN_0
#define KEY2_Port		GPIOA
#define KEY2_Pin		GPIO_PIN_1

对函数进行别名处理

typedef GPIO_PinState (*KEY_Type)(GPIO_TypeDef*,uint16_t);

宏定义按键按下状态

#define KEY_DOWN 		GPIO_PIN_RESET				

宏定义按键调用的函数

#define KEY					HAL_GPIO_ReadPin

未使用函数指针的扫描函数

具体代码如下:

uint8_t KEY_Base(void){
	
	uint8_t key_num = 0;
	static uint8_t key_up = 1;
	
	if(KEY1 == KEY_DOWN){            
		if(key_up == 1){               //防止连按
			HAL_Delay(10);             //消抖
			if(KEY1  == KEY_DOWN){
				key_up = 0;
				
				/* 功能代码 */
				key_num = 1;
			}
		}
	}else{
		key_up = 1;
	}
	
	return key_num;
}

在该代码中,KEY1是一个宏定义,具体定义如下:

#define KEY1	HAL_GPIO_ReadPin(KEY1_Port,KEY1_Pin)

该代码中,最大的问题就是 if(KEY1 == KEY_DOWN) 这一句,当我们需要多种按键时,这一句并不能兼容其他按键。比如再有个KEY2,那么这一句就要换成 if(KEY2 == KEY_DOWN)。这样就使得每多一个按键就要多编写一个扫描代码,非常的不方便。

使用函数指针的扫描函数

由于轮询法就是调用HAL_GPIO_ReadPin()来读取GPIO的状态,因此不论是KEY1还是KEY2还是更多的按键,它们都需要调用HAL_GPIO_ReadPin(),即:这个函数是兼容的。

各个按键的区别在于引脚不同,因此选择将函数指针传入、并传入相应的引脚,即可实现函数能够兼容的需求。

具体的代码如下:

uint8_t KEY_Base(KEY_Type KEY_Fun,GPIO_TypeDef* KEY_Port,uint16_t KEY_Pin){
	
	uint8_t key_num = 0;
	static uint8_t key_up = 1;
	
	if(KEY_Fun(KEY_Port,KEY_Pin) == KEY_DOWN){        //使用函数指针来替代KEY1
		if(key_up == 1){
			HAL_Delay(10);
			if(KEY_Fun(KEY_Port,KEY_Pin) == KEY_DOWN){
				key_up = 0;
				
				/* 功能代码 */
				key_num = 1;
			}
		}
	}else{
		key_up = 1;
	}
	
	return key_num;
}

调用的使用方法如下:

while (1)
{
   
    if(KEY_Base(KEY,KEY1_Port,KEY1_Pin) == 1){    //传入函数指针和对应的KEY引脚参数即可
		LED1_ON;
	}
	if(KEY_Base(KEY,KEY2_Port,KEY2_Pin) == 1){
		LED1_OFF;
	}
		
}

连续扫描Bug修正

在上述代码调试过程中发现,当调用两次KEY_Base时发生了连续扫描的情况。下面首先分析为什么会出现这样的情况,之后提出相应的改进思路和改进代码。

原因分析

在while中调用了两次KEY_Base。

第一步:假设KEY1按下,进入函数之后,

if(KEY_Fun(KEY_Port,KEY_Pin) == KEY_DOWN)成立,最终key_up = 0;

第二步:假设此时KEY2没有按下,进入函数之后,

if(KEY_Fun(KEY_Port,KEY_Pin) == KEY_DOWN)不成立,最终key_up = 1;

第三步:此时while执行结束,KEY1再次调用函数,但扫描速度快,这时KEY1并未抬起。

if(KEY_Fun(KEY_Port,KEY_Pin) == KEY_DOWN)成立,且key_up==1成立

最终判断为KEY1又一次按下,导致连续扫描。

解决思路分析

由上面的叙述可知,导致连续扫描的问题是KEY2进行了判断,将key_up设置为了1。造成这样的原因是:KEY1和KEY2同时使用一个key_up来判断上一次按键是否抬起。但为了代码的兼容性,并不能设置两个key_up来分别表示KEY1,KEY2的抬起状态,这样就失去了改进的意义。

因此,改进的思路为:如何将key_up分开来,既能表示key1,又能表示key2。

在一次按键扫描操作中,只有一个按键会按下(我们默认不会存在KEY1,KEY2同时按下的情况)。那么,当按键1如果按下后,按键2就没必要扫描了。

因此设置一个锁即可。当检测到有按键按下后,进行上锁。上锁之后仅仅判断上锁时的按键,来看看是否解锁。解锁之后才进行正常的扫描。

改进代码

在整体的扫描按键代码上加了一个锁key_lock

1、当key_lock为0时,视作没有锁上,这时进行正常的扫描

2、如果扫描到按键,key_num将会有数值。

3、通过检测key_num,来判断是否有按键按下,并保存这个按键的引脚状态。

4、当下一次进入时,如果按键是锁上的,就判断是否去解锁

解锁需要两层判断:

  • 按键的引脚要和上锁时的引脚一致
  • 按键要处于抬起状态,以便设置key_up=1;

通过这样的设置,就实现了一个key_up对应多个按键的操作,具体的代码实现如下:

uint8_t KEY_Base(KEY_Type KEY_Fun,GPIO_TypeDef* KEY_Port,uint16_t KEY_Pin){
	
	uint8_t key_num = 0;
	static uint8_t key_up = 1;
	static uint8_t key_lock = 0;
	static GPIO_TypeDef* port_tmp;
	static uint16_t pin_tmp; 
	
	/* 判断是否有锁,有锁就不进行扫描 */
	if(key_lock == 0){
		
		/* 扫描按键,判断按键是否按下 */
		if(KEY_Fun(KEY_Port,KEY_Pin) == KEY_DOWN){
			if(key_up == 1){
				HAL_Delay(10);
				if(KEY_Fun(KEY_Port,KEY_Pin) == KEY_DOWN){
					key_up = 0;
					
					/* 功能代码 */
					key_num = 1;
				}
			}
		}
	}
	/* 解锁判断 */
	else{
		/* 检测到同一个按键,解锁 */
		if(port_tmp == KEY_Port && pin_tmp == KEY_Pin){
			if(KEY_Fun(KEY_Port,KEY_Pin) != KEY_DOWN){
				key_up = 1;
				port_tmp = NULL;
				pin_tmp = 0xffff;
				key_lock = 0;
			}
		}
	}
	
	/* 有按键按下,记录按键信息 */
	if(key_num != 0){

		key_lock = 1;
		port_tmp = KEY_Port;
		pin_tmp = KEY_Pin;
	}
	
	return key_num;
}

调用验证代码如下:

while (1)
{
		
	if(KEY_Base(KEY,KEY1_Port,KEY1_Pin)== 1){
		HAL_GPIO_TogglePin(LED1_Port,LED1_Pin);
	}
	if(KEY_Base(KEY,KEY2_Port,KEY2_Pin)== 1){
		HAL_GPIO_TogglePin(LED1_Port,LED1_Pin);
	}

}

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值