STM32_HAL:点亮第一个LED
STM32_HAL:按键输入检测
得益于STM32系列MCU的外设功能丰富、软件驱动库规范与参考资料多等特性,该系列MCU被广泛运用于各种电子/电气设备中,本文所使用的软件驱动库以官方的HAL(硬件抽象层)库为基础,以结构体指针的方式将各种外设功能进行进一步的封装,让程序具有面对对象编程的部分特性。
本文所使用的驱动库均为自行编写,受限于自身的水平,代码中难免会有不规范的地方,各位多指正~
在完成了上一章“点亮第一个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);
}
}
其实这个KEY_COMMON觉得写得挺烂的,有很多地方不规范也有很多地方不合逻辑,以后有时间再维护吧
main中用到的uart对象会下次再说
Gitee:https://gitee.com/hyjjjjjjjj/STM32_HAL_MODULES
Github:https://github.com/HYJJJJJJJJ/STM32_HAL_MODULES(不怎么更新