STM32_HAL:按键输入检测

0x00.章索引

STM32_HAL:点亮第一个LED

STM32_HAL:按键输入检测

0x01.目录

节目录

  • 0x00.章索引
  • 0x01.目录
  • 0x02.背景/声明
  • 0x03.开始前...
  • 0X04.捕获按键状态
  • 0x05.小结
  • 附录

0x02.背景/声明

得益于STM32系列MCU的外设功能丰富、软件驱动库规范与参考资料多等特性,该系列MCU被广泛运用于各种电子/电气设备中,本文所使用的软件驱动库以官方的HAL(硬件抽象层)库为基础,以结构体指针的方式将各种外设功能进行进一步的封装,让程序具有面对对象编程的部分特性。
本文所使用的驱动库均为自行编写,受限于自身的水平,代码中难免会有不规范的地方,各位多指正~

0x03.开始前…

  • STM32Cube IDE
  • 正点原子战舰V3板(STM32F103ZET6)
  • ST-Link V2

0X04.捕获按键状态

在完成了上一章“点亮第一个LED”后,我们已经编写好了GPIO类以及相关的实现函数,如此一来,开发者不仅可以控制GPIO的输出,同时也能读取GPIO的状态:

    u8 sta=key->ReadPin(key);

而当GPIO用于按键输入管脚时,通常还需要结合实际的硬件结构与具体应用需求,在软件中实现按键消抖与长按检测等功能,由此我们可以定义出KEY_COMMON(按键通用类)及其成员变量与函数,KEY_COMMON类中包含一个GPIO_COMMON对象(用于定义作为按键的GPIO的模式与配置)、上/下拉状态、相同配置的管脚个数与长按时间等成员,具体定义如下所示:

enum{
	L_PRES_ENABLE=0,L_PRES_DISABLE
}Press_Mode;

typedef struct KEY_COMMON {
	GPIO_COMMON *pin;
	u8 mode;
	uint32_t pull;
	u8 pin_count;
	u32 press_time;
	void (*append)(struct KEY_COMMON *this, GPIO_TypeDef *port, uint32_t pin);
	u8 (*Scan)(struct KEY_COMMON *this);
} KEY_COMMON;

定义了结构体之后,还需要实现该“类”的“构造函数”(也就是对该结构体进行初始化):

KEY_COMMON* new_Key(struct KEY_COMMON *this, GPIO_TypeDef *port, uint32_t pin,
		uint32_t pull, uint8_t mode) {
	this = (struct KEY_COMMON*) calloc(1, sizeof(struct KEY_COMMON));
	this->pull = pull;
	this->mode = mode;
	this->pin_count = 0;
	this->press_time = 0;
	this->append = KEY_Add_New_Key;
	this->Scan=KEY_Scan;
	this->append(this,port, pin);
	return this;
}

当KEY_COMMON对象的mode被赋值为L_PRES_ENABLE后,按键检测被配置为长按检测模式,在该模式下:当按键被按下时,程序被阻塞并开始计时,当按键松开后,按键检测函数返回按键按下的结果,并记录长按的时间(最长可记录100s);
当mode为L_PRES_DISABLE时,按键检测函数被配置为连续检测模式,在该模式下,当按键被按下时,程序立即返回被按下的按键键值;
两种模式下按键检测函数的具体实现函数如下所示:(忘记加软件消抖了!!!)

u8 KEY_Scan(struct KEY_COMMON *this) {
	if (this->mode == L_PRES_DISABLE) {				//支持连按,不支持长按
		for (int i = 0; i < this->pin_count; i++) {
			if (this->pin[i].GPIO_InitStruct.Pull == GPIO_PULLUP) {
				if (!this->pin[i].ReadPin(&this->pin[i])) {
					return (i + 1);
				}
			} else if (this->pin[i].GPIO_InitStruct.Pull == GPIO_PULLDOWN) {
				if (this->pin[i].ReadPin(&this->pin[i])) {
					return (i + 1);
				}
			}
		}
	} else {												//不支持连按(支持长按,检测按住按键的时间)
		for (int i = 0; i < this->pin_count; i++) {
			if (this->pin[i].GPIO_InitStruct.Pull == GPIO_PULLUP) {
				if (!this->pin[i].ReadPin(&this->pin[i])) {
					while (!this->pin[i].ReadPin(&this->pin[i])) {
						HAL_Delay(10);
						this->press_time++;
						if (this->press_time > 10000) {
							return (i + 1);
						}
					}
					return (i + 1);
				}
			} else if (this->pin[i].GPIO_InitStruct.Pull == GPIO_PULLDOWN) {
				if (this->pin[i].ReadPin(&this->pin[i])) {
					while (this->pin[i].ReadPin(&this->pin[i])) {
						HAL_Delay(10);
						this->press_time++;
						if (this->press_time > 10000) {
							return (i + 1);
						}
					}
					return (i + 1);
				}
			}
		}
	}
	return 0;
}

当系统有多个按键,且按键的mode与pull均相同时,可用obj->append函数对按键对象内的GPIO成员对象进行扩容,实现将多个按键存放于一个对象中,并遍历检测所有按键的状态:

void KEY_Add_New_Key(struct KEY_COMMON *this, GPIO_TypeDef *port, uint32_t pin) {
	this->pin_count++;
	GPIO_COMMON *p = (GPIO_COMMON*) malloc(this->pin_count * sizeof(GPIO_COMMON));
	for (int i = 0; i < this->pin_count; i++) {
		p[i] = this->pin[i];
	}
	free(this->pin);
	this->pin = p;
	GPIO_COMMON *key_pin = new_Gpio(key_pin, port, pin, GPIO_MODE_INPUT,	this->pull,GPIO_SPEED_FREQ_HIGH);
	this->pin[this->pin_count - 1] = *key_pin;
}

最后,只需要在主函数中定义一个KEY_COMMON类型的key对象,并进行相关的初始化,结合按键检测函数,即可实现对按键不同方式的检测:

int main(void) {
	u32 count = 0;
	HAL_Init();
	SystemClock_Config();
	KEY_COMMON *key1 = new_Key(key1, GPIOA, GPIO_PIN_0, GPIO_PULLDOWN,
			L_PRES_ENABLE);																									//初始化第一个按键对象 将PA0添加至该对象
	KEY_COMMON *key2 = new_Key(key2, GPIOA, GPIO_PIN_1, GPIO_PULLUP,
			L_PRES_DISABLE);																									//初始化第二个按键对象 将PA1添加至该对象
	key2->append(key2, GPIOA, GPIO_PIN_2);																	//将PA2作为第二个按键加入第二个按键对象中

	UART_COMMON *uart1_common = new_Uart(uart1_common, USART1, 115200);			//初始化串口一 注册相关成员函数
	uart1_common->UPrintf(uart1_common, "KEY PRESS EXAMPLE!\r\n");							//串口输出
	while (1) {
		if (key1->mode == L_PRES_DISABLE) {																		//判断当前是否处于连按模式
			if (key1->Scan(key1) == 1) {																					//扫描第一个按键对象中的所有按键  (PA0)
				count++;																											//连按模式,非阻塞
				uart1_common->UPrintf(uart1_common, "KEY1 Press!\t count: %d\r\n",count);
			}
		} else {																														//非连按模式(也就是带松开按键的检测),阻塞,并记录按下的时间至对象的press_time成员中
			if (key1->Scan(key1) == 1) {																					//扫描第一个按键对象中的所有按键  (PA0)
				uart1_common->UPrintf(uart1_common, "KEY1 Press!\t Long Press Time:%d ms \r\n",key1->press_time * 10);
				key1->press_time = 0;
			}
		}
		if (key2->Scan(key2) == 1) {																						//扫描第二个按键对象中的所有按键  (PA1|PA2),若检测到第一个按键被按下
			uart1_common->UPrintf(uart1_common, "Change to L_PRES_ENABLE\r\n");			//将第一个按键对象设置为非连按模式
			key1->mode = L_PRES_ENABLE;
		} else if (key2->Scan(key2) == 2) {																			//扫描第二个按键对象中的所有按键  (PA1|PA2),若检测到第二个按键被按下
			uart1_common->UPrintf(uart1_common, "Change to L_PRES_DISABLE\r\n"); 		//将第一个按键对象设置为连按模式
			key1->mode = L_PRES_DISABLE;
			count=0;
		}
		HAL_Delay(200);
	}
}

0x05.小结

其实这个KEY_COMMON觉得写得挺烂的,有很多地方不规范也有很多地方不合逻辑,以后有时间再维护吧
main中用到的uart对象会下次再说

附录

Gitee:https://gitee.com/hyjjjjjjjj/STM32_HAL_MODULES
Github:https://github.com/HYJJJJJJJJ/STM32_HAL_MODULES(不怎么更新

你可能感兴趣的:(STM32_HAL编程)