超级版主
 
- 积分
- 5228
- 金钱
- 5228
- 注册时间
- 2019-5-8
- 在线时间
- 1319 小时
|
|
第十二章 KEY实验
1)实验平台:正点原子DNESP32P4开发板
2)章节摘自【正点原子】ESP32-P4开发指南— V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=873309579825
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
在嵌入式系统开发中,按键(KEY)的应用非常广泛,尤其是在用户交互和控制方面。本章教程将为大家介绍ESP32-P4的GPIO输入应用,重点讲解按键的使用方法与实现。通过本章的学习,读者将能够掌握如何通过GPIO接口读取按键状态,进而实现各种交互功能。
本章分为如下几个小节:
12.1 KEY介绍
12.2 硬件设计
12.3 程序设计
12.4 下载验证
12.1 KEY介绍
独立按键是一种常见且简单的输入设备,广泛用于各类电子设备中,用于实现基本的用户交互操作。其工作原理基于机械开关:当按键被按下时,开关闭合,触发电路中的相应操作。独立按键的外观多样化,可以设计成不同的尺寸、形状和颜色,以便于用户识别和操作,满足不同场景下的交互需求。这种按键由于其简单性和易于实现的特性,在消费电子、家用电器和工业设备中得到了广泛应用。常见的独立按键如下图所示。
图12.1.1 常用的独立按键
12.1.1 独立按键的结构及驱动原理
独立按键的主要结构由按钮、外壳、弹簧、触点、导电片和引脚组成。其工作原理依赖于弹性体(如弹簧或金属片)和按键帽的配合。当用户按下按键时,弹性体受到压缩,按键帽推动按钮,使其顶部接触基底,从而闭合电路(见下图所示)。相反,当用户松开按键时,弹性体恢复原状,按键也随之回弹至初始位置,触点断开,电路恢复为开路状态。因此,在按键未被按下时,电路通常处于断开状态,而按下时则形成闭合电路,实现信号的传输或操作的触发。
图12.1.1.1 独立按键拆解图
在上述图示中,左侧器件的中间触点连接到MCU的GPIO引脚上,而上下两个触点通过金属弹性体导通(对应图中的第二个器件)。通常情况下,上下触点连接到检测源,比如GND或VCC。当按下按键时,金属弹性体会凹陷,触碰左侧器件的中间触点,从而使检测源与GPIO引脚导通。通过这种方式,MCU可以检测到按键的电平状态变化,进而判断按键是否被按下,触发相应的响应操作。
根据上述独立按键的工作过程,机械按键在按下或释放时确实会导致抖动。抖动是由于按键内部的机械结构导致的,当金属弹性体接触或分离时,可能不会立即稳定闭合或断开,而是会在短时间内发生多次反复的接触与断开(见下图所示)。这些快速的开关操作会使GPIO引脚产生多次的电平变化,从而导致系统误判按键的状态,即认为按键被按下或释放了多次。
图12.1.1.2 独立按键抖动波形图
图中的按下抖动和释放抖动的时间一般为5~20ms,如果在抖动阶段采样,其不稳定状态可能出现一次按键动作被认为是多次按下的情况。为了避免抖动可能带来的误操作,我们要做的措施就是给按键消抖(即采样稳定闭合阶段)。
12.1.2 独立按消抖处理
为了消除这种抖动,我们通常采用软件消抖和硬件消抖两种主要方法:
1,软件消抖
软件消抖是通过程序实现的,主要有两种方法:延迟法和计数法。延迟法是在检测到按键状态变化后,延时一段时间再进行确认,如果状态在延时后仍然保持一致,则认为按键动作有效。这种方法简单直接,但在延时时间内系统暂停其他操作,可能影响实时性。而计数法则是在检测到按键状态变化后,连续多次采样按键状态,如果在多次检测中状态保持一致,则确认按键动作有效。相比延迟法,计数法在不影响系统任务执行的情况下能够更加灵活地应对抖动现象,提高按键检测的准确性和稳定性。
2,硬件消抖
在按键电路中加入元器件如电阻、电容组成的RC低通滤波器,对按键信号进行平滑处理,降低抖动的影响。
在本书籍的例程中,采用了最简单的延时消抖方法。当检测到按键按下时,程序会先执行一个约20毫秒的延时,用于跳过按键抖动的时间段。这个延时时间是按键消抖的关键,如果消抖效果不好,可以根据实际情况调整该延时长度,因为不同类型的按键抖动时间可能略有差异。延时结束后,再次检测按键状态。如果此时按键已松开,那么可以判断之前的按下信号是由于抖动或干扰引起的;如果按键仍处于按下状态,则判断为按键确实被按下。按键释放的检测同样遵循这一原理,即通过延时避免误判,实现稳定的按键检测。
12.2 硬件设计
12.2.1 例程功能
通过按下BOOT按键控制LED0电平翻转。
12.2.2 硬件资源
1)LED灯
LED 0 - IO51
2)KEY按键
BOOT - IO35
12.2.3 原理图
KEY器件相关原理图,如下图所示。
图12.2.3.1 KEY硬件原理图
上图中,BOOT按键由ESP32-P4的GPIO35号管脚负责读取其状态,而KEY0、KEY1和KEY2则通过IO扩展芯片的第8、9、10位管脚进行控制。关于这三个通过IO扩展芯片控制的扩展按键的具体使用方法,笔者将在第十九章节中详细讲解。
12.3 程序设计
12.3.1 GPIO的IDF驱动
GPIO函数已在11.3.1章节中详细阐述,为避免重复,此处不再赘述。建议读者查阅第十一章的函数解析章节,以获取更多关于GPIO函数的信息。
12.3.2 程序流程图
图12.3.2.1 KEY实验程序流程图
12.3.3 程序解析
在02_key例程中,作者在02_key\components\BSP路径下新建KEY文件夹,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1,KEY驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。KEY驱动源码包括两个文件:key.c和key.h。
下面先解析key.h的程序。对KEY引脚做了相关定义以及功能实现。
- <font size="3">/* 引脚定义 */</font>
- <font size="3">#define BOOT_GPIO_PIN GPIO_NUM_35</font>
- <font size="3">/* IO操作*/</font>
- <font size="3">#define BOOT gpio_get_level(BOOT_GPIO_PIN)</font>
- <font size="3">/* 按键按下定义 */</font>
- <font size="3">#define BOOT_PRES 1 /* BOOT按键按下 */</font>
复制代码 上述源码中,BOOT_GPIO_PIN定义了BOOT按键所连接的GPIO引脚号,即GPIO35。通过gpio_get_level函数读取该引脚的状态,BOOT会返回按键的当前状态,值为0或1。BOOT_PRES则表示按键按下时的状态为1,用于在程序中判断按键是否被按下
下面我们再解析key.c的程序,看一下初始化函数key_init,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief 初始化按键</font>
- <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url] 无</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">void key_init(void)</font>
- <font size="3">{</font>
- <font size="3"> gpio_config_t gpio_init_struct = {0};</font>
- <font size="3"> gpio_init_struct.intr_type = GPIO_INTR_DISABLE; /* 失能引脚中断 */</font>
- <font size="3"> gpio_init_struct.mode = GPIO_MODE_INPUT; /* 输入模式 */</font>
- <font size="3"> gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; /* 使能上拉 */</font>
- <font size="3"> gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; /* 失能下拉 */</font>
- <font size="3"> gpio_init_struct.pin_bit_mask = 1ull << BOOT_GPIO_PIN; /* 设置引脚的位掩码 */</font>
- <font size="3"> ESP_ERROR_CHECK(gpio_config(&gpio_init_struct)); /* 配置使能 */</font>
- <font size="3">}</font>
复制代码 key_init函数初始化了BOOT按键所连接的GPIO引脚。通过gpio_config_t结构体配置,将引脚设置为输入模式,禁用中断,启用上拉电阻以确保未按下时电平为高,同时禁用下拉电阻。使用位掩码定位到具体的引脚(BOOT_GPIO_PIN),并通过gpio_config函数应用配置,确保按键引脚的正确初始化。
之前笔者已经讲解过,机械按键在按下和释放时常会产生抖动问题。为了确保系统能够准确识别按键的状态变化,解决这一问题,我们可以使用软件消抖的方法来过滤掉抖动信号。下面是笔者编写的按键消抖代码示例:
- <font size="3">/**</font>
- <font size="3"> * @brief 按键扫描函数</font>
- <font size="3"> * [url=home.php?mod=space&uid=60778]@note[/url] 该函数有响应优先级(同时按下多个按键): BOOT!!</font>
- <font size="3"> * @param mode:0 / 1, 具体含义如下:</font>
- <font size="3"> * @arg 0, 不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,</font>
- <font size="3"> * 必须松开以后, 再次按下才会返回其他键值)</font>
- <font size="3"> * @arg 1, 支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)</font>
- <font size="3"> * @retval 键值, 定义如下:</font>
- <font size="3"> * BOOT_PRES, 1, BOOT按下</font>
- <font size="3"> */</font>
- <font size="3">uint8_t key_scan(uint8_t mode)</font>
- <font size="3">{</font>
- <font size="3"> static uint8_t key_up = 1; /* 按键按松开标志 */</font>
- <font size="3"> uint8_t keyval = 0;</font>
- <font size="3"> if (mode) key_up = 1; /* 支持连按 */</font>
- <font size="3"> if (key_up && (BOOT == 0)) /* 按键松开标志为1, 且有任意一个按键按下了 */</font>
- <font size="3"> {</font>
- <font size="3"> esp_rom_delay_us(20000); /* 去抖动 */</font>
- <font size="3"> key_up = 0;</font>
- <font size="3"> if (BOOT == 0) keyval = BOOT_PRES;</font>
- <font size="3"> }</font>
- <font size="3"> else if (BOOT == 1) /* 没有任何按键按下, 标记按键松开 */</font>
- <font size="3"> {</font>
- <font size="3"> key_up = 1;</font>
- <font size="3"> }</font>
- <font size="3"> return keyval; /* 返回键值 */</font>
- <font size="3">}</font>
复制代码 此函数通过检测按键的状态,结合20ms的延时进行软件消抖。key_up用于标记按键是否松开,避免按键被反复检测,mode参数可以控制是否支持连续按键检测。最终返回按键的状态值,如果按下按键则返回BOOT_PRES。
2,CMakeLists.txt文件
本例程的功能实现主要依靠KEY驱动。要在main函数中,成功调用KEY文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
- <font size="3">set(src_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> KEY)</font>
- <font size="3">set(include_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> KEY)</font>
- <font size="3">set(requires</font>
- <font size="3"> driver)</font>
- <font size="3">idf_component_register( SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} </font>
- <font size="3">REQUIRES ${requires})</font>
- <font size="3">component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)</font>
复制代码
3,main.c驱动代码
在main.c里面编写如下代码。
- <font size="3">void app_main(void)</font>
- <font size="3">{</font>
- <font size="3"> esp_err_t ret;</font>
- <font size="3"> uint8_t key = 0;</font>
- <font size="3">ret = nvs_flash_init(); /* 初始化NVS */</font>
- <font size="3">if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)</font>
- <font size="3"> {</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_erase());</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_init());</font>
- <font size="3"> }</font>
- <font size="3"> led_init(); /* 初始化LED */</font>
- <font size="3"> key_init(); /* 初始化KEY */</font>
- <font size="3"> while(1)</font>
- <font size="3"> {</font>
- <font size="3"> key = key_scan(0);</font>
- <font size="3"> if (key)</font>
- <font size="3"> {</font>
- <font size="3"> if (key == BOOT_PRES)</font>
- <font size="3"> {</font>
- <font size="3"> LED0_TOGGLE();</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3"> vTaskDelay(pdMS_TO_TICKS(10));</font>
- <font size="3"> }</font>
- <font size="3">}</font>
复制代码 app_main函数中首先初始化LED和按键。通过无限循环,不断调用key_scan函数检测按键状态,如果检测到按下了BOOT按键,就切换LED的状态(点亮或熄灭)。每次循环后,通过 vTaskDelay进行10ms的延时,避免频繁检测按键。
12.4 下载验证
程序下载完之后,通过BOOT按键控制LED灯亮灭。 |
|