esp32课设记录(一)按键的短按、长按与双击
课程用的esp32的板子上只有一个按键,引脚几乎都被我用光了,很难再外置按键。怎么控制屏幕的gui呢?这就得充分利用按键了,比如说短按、长按与双击,实现不同的功能。
咱们先从短按入手讲起。
通过查看原理图,可见按键按下会接地,因此只需要把gpio口配置为input模式,上拉(确保不按下是高电平)即可,使用ESP-IDF框架提供的函数配置一下。
// 定义按钮引脚,当前使用GPIO9连接按钮(按下时接GND)
#define BUTTON_PIN 9/*** @brief 按钮初始化函数** 配置GPIO为输入模式,使用内部上拉电阻*/
void button_init(void)
{// 配置GPIO参数gpio_config_t io_conf = {.pin_bit_mask = (1ULL << BUTTON_PIN), // 设置GPIO引脚位掩码.mode = GPIO_MODE_INPUT, // 设置为输入模式.pull_up_en = GPIO_PULLUP_ENABLE, // 启用内部上拉电阻.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用内部下拉电阻.intr_type = GPIO_INTR_DISABLE, // 禁用GPIO中断};// 应用GPIO配置gpio_config(&io_conf);
}
ESP-IDF框架是基于FreeRTOS的。我打算使用轮询,不用外部中断,直接在app_main()的循环里面判断按键是否按下就行,和裸片编程差不多。
vTaskDelay是FreeRTOS中使当前任务进入阻塞状态一段时间的函数,函数参数是系统节拍数。我们要将10ms转换为系统节拍数,而portTICK_PERIOD_MS表示一个系统时钟节拍(tick)对应的毫秒数,举个例子,假如portTICK_PERIOD_MS=2,意味着2ms一个系统节拍,10ms就是有5个系统节拍。因此10 / portTICK_PERIOD_MS就是将10毫秒转换为系统节拍数,vTaskDelay(10 / portTICK_PERIOD_MS);自然就是延迟10ms的意思。
// 在main.c的app_main()的主循环中
while (1) {// 检测按钮点击if (button_is_clicked()) {// 按钮被点击,执行相应操作}// 短暂延时,避免CPU占用过高vTaskDelay(10 / portTICK_PERIOD_MS);
}
之后写一下button_is_clicked的逻辑。先记录last_state(上次按钮状态)和last_change_time(上次状态变化时间戳),再获取current_state(当前按钮状态)和now(当前状态变化时间戳)。如果不一样则更新状态与时间戳,此时理论上已经按下了。再次松手后,状态还会变化,此时判定当前时间与上次记录的时间,当大于20ms才返回true。
为什么要记录last_state(上次按钮状态)和last_change_time(上次状态变化时间戳)呢?若不比较上次按钮状态和本次按钮状态,那我长按按键岂不是每10ms都被判定一次按下。上次状态变化时间戳纯粹为了消抖。
那为何要释放瞬间才认为按键被触发了?这种设计有以下几个重要优势:
1. 完整性验证
确保用户完成了完整的按下-释放周期,而不是误触或抖动触发
可以区分有意识的点击和意外的触碰
2. 防止重复触发
如果在按下瞬间触发,按住按钮时会因轮询多次而重复触发
释放时触发保证每次物理点击只产生一次软件事件
3. 为长按和短按区分做铺垫
在释放时检测可以测量按钮被按下的持续时间
// 添加消抖延时函数
static void button_debounce_delay(void)
{vTaskDelay(20 / portTICK_PERIOD_MS);
}// 检测按钮单击事件(轮询方式)
bool button_is_clicked(void)
{static bool last_state = false; // 记录上次按钮状态static int64_t last_change_time = 0; // 上次状态变化时间// 获取当前按钮状态bool current_state = !gpio_get_level(BUTTON_PIN);int64_t now = esp_timer_get_time() / 1000; // 当前时间(毫秒)// 如果状态变化太快(小于消抖时间),忽略它if (now - last_change_time < 20) {return false;}// 检测按钮从按下到释放的过程(完整点击)if (last_state && !current_state) {last_state = current_state;last_change_time = now;return true; // 按钮刚被释放,返回点击事件}// 更新按钮状态if (last_state != current_state) {last_state = current_state;last_change_time = now;}return false;
}
下面进入长按与双击的代码写作。这需要用到状态机。
我们先梳理一下逻辑。首先事件肯定是空闲、短按、长按、双击。状态定义为空闲状态、按下状态、释放状态、双击状态。短按长按比较好写,直接记录按下到抬起的时间即可,也就是前面的now - last_change_time是否满足某一阈值。双击只要判断单击后是否在某个阈值内再次点击即可。
因此,总体逻辑应该是这样的:首先是空闲状态。当按钮按下,进入按下状态。当按钮松开后,测量按下的时间,到达阈值认为触发长按事件,直接进入空闲状态,防止长按后还触发双击事件。没有到达阈值则进入释放状态。此时等待一段时间,在此时间内按键再次按下,判定触发双击事件,当释放后进入空闲状态。未按下则判定进入单击事件,随后进入空闲状态。
先定义事件与状态。
// 按钮事件类型
typedef enum
{BUTTON_EVENT_NONE, // 无事件BUTTON_EVENT_SHORT_PRESS, // 短按BUTTON_EVENT_LONG_PRESS, // 长按BUTTON_EVENT_DOUBLE_PRESS // 双击
} button_event_t;
// 按钮状态类型
typedef enum
{BUTTON_STATE_IDLE, // 空闲状态BUTTON_STATE_PRESS, // 按下状态BUTTON_STATE_RELEASE, // 释放状态BUTTON_STATE_DOUBLE_CLICK // 双击状态
} button_state_t;
编写获取按钮事件的函数,直接输出按钮事件类型。
//true - 表示之前的事件已被处理完毕,可以检测新事件
//false - 表示有一个事件已被检测到但尚未处理
bool event_processed = true;/*** @brief 获取按钮事件** @return 按钮事件类型*/
button_event_t button_get_event(void)
{static bool last_pressed = false;bool current_pressed = !gpio_get_level(BUTTON_PIN);int64_t current_time = esp_timer_get_time() / 1000;// 如果上一个事件尚未处理,则继续返回该事件if (!event_processed){return last_event;}// 状态机逻辑switch (button_state){case BUTTON_STATE_IDLE:if (current_pressed && !last_pressed){// 按钮从释放状态变为按下状态press_time = current_time;button_state = BUTTON_STATE_PRESS;}break;case BUTTON_STATE_PRESS:if (!current_pressed && last_pressed){// 按钮从按下状态变为释放状态release_time = current_time;// 检查是长按还是可能的短按/双击if (release_time - press_time >= BUTTON_LONG_PRESS_TIME){// 长按事件last_event = BUTTON_EVENT_LONG_PRESS;event_processed = false;button_state = BUTTON_STATE_IDLE; // 长按后直接回到IDLE状态}else{// 可能是短按或双击的第一次点击button_state = BUTTON_STATE_RELEASE;}}else if (current_pressed && (current_time - press_time >= BUTTON_LONG_PRESS_TIME)){// 长按事件(按住未释放但已达到时间阈值)last_event = BUTTON_EVENT_LONG_PRESS;event_processed = false;button_state = BUTTON_STATE_IDLE;}break;case BUTTON_STATE_RELEASE:if (current_pressed && !last_pressed){// 可能是双击的第二次按下if (current_time - release_time <= BUTTON_DOUBLE_CLICK_TIME){button_state = BUTTON_STATE_DOUBLE_CLICK;}}else if (current_time - release_time > BUTTON_DOUBLE_CLICK_TIME){// 超过双击时间窗口,确认为短按last_event = BUTTON_EVENT_SHORT_PRESS;event_processed = false;button_state = BUTTON_STATE_IDLE;}break;case BUTTON_STATE_DOUBLE_CLICK:if (!current_pressed && last_pressed){// 双击的第二次释放,确认为双击last_event = BUTTON_EVENT_DOUBLE_PRESS;event_processed = false;button_state = BUTTON_STATE_IDLE;}break;}// 更新上一次按钮状态last_pressed = current_pressed;// 返回事件if (!event_processed){return last_event;}return BUTTON_EVENT_NONE;
}
一样在while循环里面写逻辑,直接获取button_get_event的事件,根据事件来执行相应操作。
// 获取按钮事件button_event_t event = button_get_event();// 处理按钮事件if (event != BUTTON_EVENT_NONE){// 标记事件已处理event_processed = true;switch (event){case BUTTON_EVENT_SHORT_PRESS:// 短按break;case BUTTON_EVENT_LONG_PRESS:// 长按break;case BUTTON_EVENT_DOUBLE_PRESS:// 双击break;case BUTTON_EVENT_NONE:// 这种情况不应该发生// 但为了满足编译器的要求,添加此casebreak;}}// 短暂延时vTaskDelay(10 / portTICK_PERIOD_MS);
结束!烧录代码,能够实现短按、长按与双击的检测。
相关文章:

esp32课设记录(一)按键的短按、长按与双击
课程用的esp32的板子上只有一个按键,引脚几乎都被我用光了,很难再外置按键。怎么控制屏幕的gui呢?这就得充分利用按键了,比如说短按、长按与双击,实现不同的功能。 咱们先从短按入手讲起。 通过查看原理图,…...
使用AI 生成PPT 最佳实践方案对比
文章大纲 一、专业AI生成工具(推荐新手)**1. 推荐工具详解****2. 操作流程优化****3. 优势与局限**二、代码生成方案(开发者推荐)**1. Python-pptx进阶用法****2. GitHub推荐**三、混合工作流(平衡效率与定制)**1. 工具链升级****2. 示例Markdown结构**四、网页转换方案(…...

React19源码系列之 API(react-dom)
API之 preconnect preconnect – React 中文文档 preconnect 函数向浏览器提供一个提示,告诉它应该打开到给定服务器的连接。如果浏览器选择这样做,则可以加快从该服务器加载资源的速度。 preconnect(href) 一、使用例子 import { preconnect } fro…...

supervisorctl守护进程
supervisorctl守护进程 1 安装 # ubuntu安装: sudo apt-get install supervisor 完成后可以在/etc/supervisor文件夹,找到supervisor.conf。 如果没有的话可以用如下命令创建配置文件(注意必须存在/etc/supervisor这个文件夹) s…...

下载的旧版的jenkins,为什么没有旧版的插件
下载的旧版的jenkins,为什么没有旧版的插件,别急 我的jenkins版本: 然后我去找对应的插件 https://updates.jenkins.io/download/plugins/ 1、Maven Integration plugin: Maven 集成管理插件。 然后点击及下载成功 然后 注意&…...

【ALINX 实战笔记】FPGA 大神 Adam Taylor 使用 ChipScope 调试 AMD Versal 设计
本篇文章来自 FPGA 大神、Ardiuvo & Hackster.IO 知名博主 Adam Taylor。在这里感谢 Adam Taylor 对 ALINX 产品的关注与使用。为了让文章更易阅读,我们在原文的基础上作了一些灵活的调整。原文链接已贴在文章底部,欢迎大家在评论区友好互动。 在上篇…...
出现 Uncaught ReferenceError: process is not defined 错误
在浏览器环境中,process 对象是 Node.js 环境特有的,因此当你在浏览器中运行代码时,会出现 Uncaught ReferenceError: process is not defined 错误。这个错误是因为代码里使用了 process.env.BASE_URL,而浏览器环境下并没有 proc…...

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】附录-A. PostgreSQL常用函数速查表
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 PostgreSQL常用函数速查表:从数据清洗到分析的全场景工具集引言一、字符串处理函数1.1 基础操作函数1.2 模式匹配函数(正则表达式)二、数值计算函数2.1 基础运算函数2.2 统计相关函数三、日期与时间函…...

【时空图神经网络 交通】相关模型2:STSGCN | 时空同步图卷积网络 | 空间相关性,时间相关性,空间-时间异质性
注:仅学习使用~ 前情提要: 【时空图神经网络 & 交通】相关模型1:STGCN | 完全卷积结构,高效的图卷积近似,瓶颈策略 | 时间门控卷积层:GLU(Gated Linear Unit),一种特殊的非线性门控单元目录 STSGCN-2020年1.1 背景1.2 模型1.2.1 问题背景:现有模型存在的问题1.2…...
零基础学Java——第十一章:实战项目 - 微服务入门
第十一章:实战项目 - 微服务入门 随着互联网应用的复杂性不断增加,单体应用(Monolithic Application)在可扩展性、可维护性、技术栈灵活性等方面逐渐暴露出一些问题。微服务架构(Microservices Architectureÿ…...

docker 学习记录
docker pull nginx docker 将本地nginx快照保存到当前文件夹下 docker save -o nginx.tar nginx:latestdocker 将本地nginx 加载 docker load -i nginx.tar docker运行nginx在80端口 docker run --name dnginx -p 80:80 -d nginxredis启动 docker run --name mr -p 6379:6379 -…...
自媒体工作室如何矩阵?自媒体矩阵养号策略
一、自媒体工作室矩阵搭建方法 1.纵向矩阵:在主流平台都开设账号,覆盖不同用户触达场景。 短视频:抖音、快手、视频号(侧重私域沉淀) 2.主账号导流:通过关联账号、评论区跳转链接实现流量互通 本地生活…...

南京邮电大学金工实习答案
一、金工实习的定义 金工实习是机械类专业学生一项重要的实践课程,它绝非仅仅只是理论知识在操作层面的简单验证,而是一个全方位培养学生综合实践能力与职业素养的系统工程。从本质上而言,金工实习是学生走出教室,亲身踏入机械加…...
【C++进阶篇】C++容器完全指南:掌握set和map的使用,提升编码效率
C容器的实践与应用:轻松掌握set、map与multimap的区别与用法 一. 序列式容器与关联式容器1.1 序列式容器 (Sequential Containers)1.2 关联式容器 (Associative Containers) 二. set系列使用2.1 set的构造和迭代器2.2 set的增删查2.2.1 插入2.2.2 查找2.2.3 删除 2.…...

世界模型+大模型+自动驾驶 论文小汇总
最近看了一些论文,懒得一个个写博客了,直接汇总起来 文章目录 大模型VLM-ADVLM-E2EOpenDriveVLAFASIONAD:自适应反馈的类人自动驾驶中快速和慢速思维融合系统快系统慢系统快慢结合 世界模型End-to-End Driving with Online Trajectory Evalu…...
物联网设备远程管理:基于代理IP的安全固件更新通道方案
在物联网设备远程管理中,固件更新的安全性直接关系到设备功能稳定性和系统抗攻击能力。结合代理IP技术与安全协议设计,可构建安全、高效的固件更新通道。 一、代理IP在固件更新中的核心作用 网络层隐匿与路由优化 隐藏更新源服务器:通过代理I…...
MyBatis 延迟加载与缓存
一、延迟加载策略:按需加载,优化性能 1. 延迟加载 vs 立即加载:核心区别 立即加载:主查询(如查询用户)执行时,主动关联加载关联数据(如用户的所有账号)。 场景…...

C++函数三剑客:缺省参数·函数重载·引用的高效编程指南
前引:在C编程中,缺省参数、函数重载、引用是提升代码简洁性、复用性和效率的三大核心机制。它们既能减少冗杂的代码,又能增强接口设计的灵活性。本文将通过清晰的理论解析与实战案列,带你深入理解这三者的设计思想、使用场景以及闭…...
ORACLE 11.2.0.4 数据库磁盘空间爆满导致GAP产生
前言 昨天晚上深夜接到客户电话,反应数据库无法正常使用,想进入服务器检查时,登录响应非常慢。等两分钟后进入服务器且通过sqlplus进入数据库也很慢。通过检查服务器磁盘空间发现数据库所在区已经爆满,导致数据库在运行期间新增审…...
面试题总结一
第一天 1. 快速排序 public class QuickSort {public static void quickSort(int[] arr, int low, int high) {if (low < high) {// 分区操作,获取基准元素的最终位置int pivotIndex partition(arr, low, high);// 递归排序基准元素左边的部分quickSort(arr, …...

SWUST数据结构下半期实验练习题
1068: 图的按录入顺序深度优先搜索 #include"iostream" using namespace std; #include"cstring" int visited[100]; char s[100]; int a[100][100]; int n; void dfs(int k,int n) {if(visited[k]0){visited[k]1;cout<<s[k];for(int i0;i<n;i){i…...
专业版降重指南:如何用Python批量替换同义词?自动化操作不香嘛?
还在手动一个个改词降重?👀 是兄弟就别再CtrlF了,来试试Python自动同义词替换批量降重法,简直是论文改写效率神器! 这篇我们来一波实操干货: 👉 如何用Python写出一个自动替换论文关键词的脚本…...
一:操作系统之操作系统结构
深入浅出:一文读懂操作系统的五种核心结构 操作系统,作为计算机硬件与应用软件之间的桥梁,其内部组织结构是决定其性能、稳定性、可维护性和安全性的关键。就像建造房屋需要选择不同的建筑结构一样,设计操作系统也需要选择或混合…...

机器学习 Day18 Support Vector Machine ——最优美的机器学习算法
1.问题导入: 2.SVM定义和一些最优化理论 2.1SVM中的定义 2.1.1 定义 SVM 定义:SVM(Support Vector Machine,支持向量机)核心是寻找超平面将样本分成两类且间隔最大 。它功能多样,可用于线性或非线性分类…...
IIS入门指南:原理、部署与实战
引言:Web服务的基石 在Windows Server机房中,超过35%的企业级网站运行在IIS(Internet Information Services)之上。作为微软生态的核心Web服务器,IIS不仅支撑着ASP.NET应用的运行,更是Windows Server系统管…...
Linux运维——Shell脚本读取配置文件
Shell脚本读取配置文件 一、键值对格式配置文件(最常用)1.1、配置文件示例1.2、source命令导入1.3、sed解析1.4、解析数组 二、INI格式配置文件1.1、配置文件示例1.2、sed解析1.3、ini配置带数组(显式声明数组)1.4、ini配置带数组…...

答题pk小程序道具卡的获取与应用
道具卡是答题PK小程序中必不可少的一项增加趣味性的辅助应用,那么道具卡是如何获取与应用的呢,接下来我们来揭晓答案: 一、道具卡的获取: 签到获取:在每日签到中签到不仅可获得当日的签到奖励积分,同时连…...

leetcode3265. 统计近似相等数对 I-medium
1 题目:统计近似相等数对 I 官方标定难度:中 给你一个正整数数组 nums 。 如果我们执行以下操作 至多一次 可以让两个整数 x 和 y 相等,那么我们称这个数对是 近似相等 的: 选择 x 或者 y 之一,将这个数字中的两个…...

【架构篇】代码组织结构设计
代码组织结构设计:模块化分层与高效协作实践 摘要 本文以Java项目为例,解析后端代码组织的标准化结构,涵盖模块划分原则、依赖管理策略及实际应用场景。通过模块化设计提升代码可维护性、团队协作效率及系统扩展能力。 一、模块化设计的核心…...
2_Spring【IOC容器中获取组件Bean】
Spring中IOC容器中获取组件Bean 实体类 //接口 public interface TestDemo {public void doSomething(); } // 实现类 public class HappyComponent implements TestDemo {public void doSomething() {System.out.println("HappyComponent is doing something...")…...