ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之触摸屏 Touch)
目录
- ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之触摸屏 Touch)
- 简介
- 模块概述
- 功能定义
- 架构位置
- 核心特性
- 触摸(Touch)外设
- 触摸外设概述
- 触摸外设API和数据结构
- 外设层API(periph_touch.h/periph_touch.c)
- 底层驱动API(touch.h/touch.c)
- 触摸外设初始化流程
- 外设层初始化过程(periph_touch.c)
- 底层驱动初始化过程(touch.c)
- 触摸外设完整初始化时序图
- 触摸外设销毁流程
- 外设层销毁过程(periph_touch.c)
- 底层驱动销毁过程(touch.c)
- 触摸外设完整销毁时序图
- 触摸检测算法
- 外设层检测实现(periph_touch.c)
- 底层驱动检测实现(touch.c)
- touch_get_state函数
- esp_touch_read函数
- 触摸状态转换时序图
- 触摸检测算法关键点
- 触摸事件处理
- 触摸外设使用示例
ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之触摸屏 Touch)
版本信息: ESP-ADF v2.7-65-gcf908721
简介
本文档详细分析ESP-ADF中的输入类外设实现机制,包括按键(button)、触摸(touch)和ADC按键(adc_button)等输入类外设的设计模式、接口规范、初始化流程和事件处理机制。ESP-ADF输入类外设基于统一的外设框架设计,通过事件驱动模型实现用户输入的检测和处理,为应用程序提供了灵活且易用的输入接口。
模块概述
功能定义
ESP-ADF输入类外设主要负责检测和处理用户的物理输入操作,将物理信号转换为应用程序可处理的事件。主要功能包括:
- 物理输入信号检测(按键按下/释放、触摸触发/释放、ADC电平变化等)
- 输入事件生成(短按、长按、触摸等事件)
- 事件过滤和防抖处理
- 向应用程序传递输入事件
架构位置
输入类外设是ESP-ADF外设子系统的重要组成部分,位于硬件驱动层和应用层之间:
核心特性
- 多种输入类型支持:支持GPIO按键、电容触摸、ADC按键等多种输入方式
- 统一事件模型:所有输入外设使用统一的事件模型和接口
- 丰富的事件类型:支持按下、释放、长按、长按释放等多种事件类型
- 防抖处理:内置输入信号防抖处理机制
- 可配置参数:支持灵活配置长按时间、触发阈值等参数
- 中断和轮询结合:结合中断和定时器轮询提高响应速度和可靠性
触摸(Touch)外设
触摸外设概述
触摸外设基于ESP32的触摸传感器功能实现,支持多个触摸通道同时使用,可以检测触摸、释放、长触摸等多种事件。触摸外设通过定时器定期采样触摸传感器值,并根据阈值判断触摸状态。
触摸外设API和数据结构
触摸外设的实现分为两个层次:外设层(Peripheral Layer)和底层驱动(Driver Layer)。
外设层API(periph_touch.h/periph_touch.c)
外设层提供了以下公共API,用于初始化和配置触摸外设:
// 文件:components/esp_peripherals/include/periph_touch.h
// 触摸外设初始化函数
esp_periph_handle_t periph_touch_init(periph_touch_cfg_t* config);// 触摸通道选择枚举
// 每个通道对应一个位,通过位或组合使用
// 例如:TOUCH_PAD_SEL0 | TOUCH_PAD_SEL1 表示同时启用两个通道
typedef enum {TOUCH_PAD_SEL0 = BIT(0),TOUCH_PAD_SEL1 = BIT(1),TOUCH_PAD_SEL2 = BIT(2),TOUCH_PAD_SEL3 = BIT(3),TOUCH_PAD_SEL4 = BIT(4),TOUCH_PAD_SEL5 = BIT(5),TOUCH_PAD_SEL6 = BIT(6),TOUCH_PAD_SEL7 = BIT(7),TOUCH_PAD_SEL8 = BIT(8),TOUCH_PAD_SEL9 = BIT(9),
} esp_touch_pad_sel_t;// 触摸配置结构体
typedef struct {int touch_mask; // 触摸通道掩码,如TOUCH_PAD_SEL0 | TOUCH_PAD_SEL1int tap_threshold_percent; // 触摸阈值百分比int long_tap_time_ms; // 长触摸时间阈值,默认为2000ms
} periph_touch_cfg_t;// 触摸事件类型
typedef enum {PERIPH_TOUCH_UNCHANGE = 0, // 无事件PERIPH_TOUCH_TAP, // 触摸PERIPH_TOUCH_RELEASE, // 触摸释放PERIPH_TOUCH_LONG_TAP, // 长触摸PERIPH_TOUCH_LONG_RELEASE, // 长触摸后释放
} periph_touch_event_id_t;
外设层内部使用以下数据结构:
// 文件:components/esp_peripherals/periph_touch.c
// 触摸外设内部结构体
typedef struct periph_touch {esp_touch_handle_t touch; // 底层触摸驱动句柄int touch_mask; // 触摸通道掩码int long_tap_time_ms; // 长触摸时间阈值int tap_threshold_percent; // 触摸阈值百分比touch_result_t result; // 触摸结果
} periph_touch_t;
底层驱动API(touch.h/touch.c)
底层驱动提供了以下API,用于直接操作ESP32的触摸传感器:
// 文件:components/esp_peripherals/lib/touch/touch.h
// 触摸状态枚举
typedef enum {TOUCH_UNCHANGE = 0, // 无变化TOUCH_TAP, // 触摸TOUCH_RELEASE, // 释放TOUCH_LONG_TAP, // 长触摸TOUCH_LONG_RELEASE, // 长触摸后释放
} touch_status_t;// 触摸结果结构体
typedef struct {int tap_mask; // 触摸的通道掩码int release_mask; // 释放的通道掩码int long_tap_mask; // 长触摸的通道掩码int long_release_mask; // 长触摸后释放的通道掩码
} touch_result_t;// 触摸驱动句柄
typedef struct esp_touch *esp_touch_handle_t;// 中断处理函数类型
typedef void (*touch_intr_handler)(void *);// 默认配置常量
#define DEFAULT_LONG_TAP_TIME_MS (2*1000)
#define DEFAULT_TOUCH_THRESHOLD_PERCENT (70)// 触摸配置结构体
typedef struct {int long_tap_time_ms; // 长触摸时间阈值int touch_mask; // 触摸通道掩码int tap_threshold_percent; // 触摸阈值百分比touch_intr_handler touch_intr_handler; // 中断处理函数void *intr_context; // 中断上下文
} touch_config_t;// 初始化触摸驱动
esp_touch_handle_t esp_touch_init(touch_config_t *config);// 读取触摸状态
bool esp_touch_read(esp_touch_handle_t touch, touch_result_t *result);// 销毁触摸驱动
esp_err_t esp_touch_destroy(esp_touch_handle_t touch);
底层驱动内部使用以下数据结构:
// 文件:components/esp_peripherals/lib/touch/touch.c
// 触摸项结构体(单个触摸通道)
typedef struct esp_touch_item {int touch_num; // 触摸通道编号long long last_tap_tick; // 上次触摸时间戳long long update_threshold_tick;// 上次更新阈值时间戳long long last_read_tick; // 上次读取时间戳uint16_t last_read_value; // 上次读取的值uint16_t untouch_value; // 未触摸时的值uint16_t threshold_value; // 触摸阈值bool long_tapped; // 长触摸标志bool tapped; // 触摸标志STAILQ_ENTRY(esp_touch_item) entry; // 链表项
} esp_touch_item_t;// 触摸控制结构体
struct esp_touch {int long_tap_time_ms; // 长触摸时间阈值int touch_mask; // 触摸通道掩码int tap_threshold_percent; // 触摸阈值百分比touch_intr_handler intr_fn; // 中断处理函数void *intr_context; // 中断上下文STAILQ_HEAD(esp_touch_list, esp_touch_item) touch_list; // 触摸通道链表
};
触摸外设初始化流程
触摸外设的初始化流程涉及两个层次:外设层(Peripheral Layer)和底层驱动(Driver Layer)。下面分别介绍这两个层次的初始化过程。
外设层初始化过程(periph_touch.c)
外设层初始化主要通过periph_touch_init
函数(位于periph_touch.c
)完成,主要包括以下步骤:
- 创建外设句柄:调用
esp_periph_create
函数创建外设句柄 - 分配内部数据结构:分配
periph_touch_t
结构体内存 - 设置配置参数:设置触摸通道掩码、触摸阈值和长触摸时间
- 注册回调函数:设置初始化、运行和销毁回调函数
// 文件:components/esp_peripherals/periph_touch.c
esp_periph_handle_t periph_touch_init(periph_touch_cfg_t *config)
{// 1. 创建外设句柄esp_periph_handle_t periph = esp_periph_create(PERIPH_ID_TOUCH, "periph_touch");AUDIO_MEM_CHECK(TAG, periph, return NULL);// 2. 分配内部数据结构periph_touch_t *periph_touch = audio_calloc(1, sizeof(periph_touch_t));AUDIO_MEM_CHECK(TAG, periph_touch, {audio_free(periph);return NULL;});// 3. 设置配置参数periph_touch->touch_mask = config->touch_mask;periph_touch->long_tap_time_ms = config->long_tap_time_ms;periph_touch->tap_threshold_percent = config->tap_threshold_percent;// 4. 注册回调函数esp_periph_set_data(periph, periph_touch);esp_periph_set_function(periph, _touch_init, _touch_run, _touch_destroy);return periph;
}
当外设被添加到外设集合并启动时,会调用_touch_init
函数(位于periph_touch.c
),该函数负责初始化底层触摸驱动并启动定时器:
// 文件:components/esp_peripherals/periph_touch.c
static esp_err_t _touch_init(esp_periph_handle_t self)
{// 验证触摸外设VALIDATE_TOUCH(self, ESP_FAIL);// 获取触摸外设数据periph_touch_t *periph_touch = esp_periph_get_data(self);// 准备底层驱动配置touch_config_t touch_config = {.touch_mask = periph_touch->touch_mask,.long_tap_time_ms = periph_touch->long_tap_time_ms,.tap_threshold_percent = periph_touch->tap_threshold_percent,};// 调用底层驱动初始化函数periph_touch->touch = esp_touch_init(&touch_config);// 启动定时器用于触摸状态检测(150ms周期)esp_periph_start_timer(self, 150 / portTICK_PERIOD_MS, touch_timer_handler);return ESP_OK;
}
底层驱动初始化过程(touch.c)
底层触摸驱动初始化通过esp_touch_init
函数(位于touch.c
)完成,主要包括以下步骤:
- 分配触摸驱动结构体:分配
esp_touch
结构体内存 - 设置触摸参数:设置触摸通道掩码、长触摸时间阈值和触摸阈值百分比
- 初始化ESP32触摸传感器:配置触摸传感器硬件
- 初始化触摸通道链表:初始化触摸通道项目链表
- 为每个触摸通道创建触摸项:遍历触摸通道掩码,为每个启用的通道创建触摸项
- 配置中断处理:如果提供了中断处理函数,则配置触摸中断
// 文件:components/esp_peripherals/lib/touch/touch.c
esp_touch_handle_t esp_touch_init(touch_config_t *config)
{// 1. 分配触摸驱动结构体esp_touch_handle_t touch = audio_calloc(1, sizeof(struct esp_touch));AUDIO_MEM_CHECK(TAG, touch, return NULL);// 验证触摸通道掩码if (config->touch_mask <= 0) {ESP_LOGE(TAG, "required at least 1 touch");return NULL;}// 2. 设置触摸参数touch->touch_mask = config->touch_mask;touch->long_tap_time_ms = config->long_tap_time_ms;touch->tap_threshold_percent = config->tap_threshold_percent;// 使用默认值(如果未设置)if (touch->long_tap_time_ms == 0) {touch->long_tap_time_ms = DEFAULT_LONG_TAP_TIME_MS;}if (touch->tap_threshold_percent == 0) {touch->tap_threshold_percent = DEFAULT_TOUCH_THRESHOLD_PERCENT;}// 3. 初始化ESP32触摸传感器bool _success = (touch_pad_init() == ESP_OK);AUDIO_MEM_CHECK(TAG, _success, {audio_free(touch);return NULL;});// 4. 初始化触摸通道链表int touch_mask = touch->touch_mask;int touch_num = 0;int touch_index = 0;STAILQ_INIT(&touch->touch_list);// 5. 为每个触摸通道创建触摸项while (touch_mask) {if (touch_mask & 0x01) {ESP_LOGD(TAG, "Mask = %x, current_mask = %x, idx=%d", touch->touch_mask, touch_mask, touch_num);// 分配触摸项内存esp_touch_item_t *new_touch = audio_calloc(1, sizeof(esp_touch_item_t));AUDIO_MEM_CHECK(TAG, new_touch, {esp_touch_destroy(touch);audio_free(touch);return NULL;});new_touch->touch_num = touch_num;new_touch->last_read_tick = tick_get() + touch_index * 10;// 配置触摸通道
#if CONFIG_IDF_TARGET_ESP32touch_pad_config(touch_num, 0);
#elif CONFIG_IDF_TARGET_ESP32S2touch_pad_config(touch_num);
#endif// 设置触发阈值(如果有中断处理函数)if (config->touch_intr_handler) {touch_pad_set_thresh(touch_num, TOUCHPAD_TRIGGER_THRESHOLD);}// 将触摸项添加到链表STAILQ_INSERT_TAIL(&touch->touch_list, new_touch, entry);touch_index++;}touch_mask >>= 1;touch_num++;}// 6. 设置中断处理函数和上下文touch->intr_fn = config->touch_intr_handler;touch->intr_context = config->intr_context;// 7. 配置中断处理(如果有中断处理函数)if (config->touch_intr_handler) {
#if CONFIG_IDF_TARGET_ESP32touch_pad_isr_register(touch_pad_isr_handler, touch);touch_pad_intr_enable();
#elif CONFIG_IDF_TARGET_ESP32S2touch_pad_isr_register(touch_pad_isr_handler, touch, TOUCH_PAD_INTR_MASK_ALL);touch_pad_intr_enable(TOUCH_PAD_INTR_MASK_ALL);
#endif}// 8. 启动触摸传感器滤波器
#if CONFIG_IDF_TARGET_ESP32touch_pad_filter_start(TOUCHPAD_FILTER_PERIOD);
#endifreturn touch;
}
触摸外设完整初始化时序图
下图展示了触摸外设从应用程序调用到底层驱动完成初始化的完整流程:
触摸外设销毁流程
触摸外设的销毁流程同样涉及两个层次:外设层(Peripheral Layer)和底层驱动(Driver Layer)。下面分别介绍这两个层次的销毁过程。
外设层销毁过程(periph_touch.c)
外设层销毁主要通过_touch_destroy
函数(位于periph_touch.c
)完成,主要包括以下步骤:
- 获取外设数据:获取触摸外设的内部数据结构
- 停止定时器:停止触摸状态检测定时器
- 释放底层资源:调用底层驱动的销毁函数释放资源
- 释放内部数据结构:释放触摸外设的内部数据结构
// 文件:components/esp_peripherals/periph_touch.c
static esp_err_t _touch_destroy(esp_periph_handle_t self)
{// 1. 获取触摸外设数据periph_touch_t *periph_touch = esp_periph_get_data(self);// 2. 停止定时器esp_periph_stop_timer(self);// 3. 释放底层触摸驱动资源esp_touch_destroy(periph_touch->touch);// 4. 释放触摸外设数据结构audio_free(periph_touch);return ESP_OK;
}
底层驱动销毁过程(touch.c)
底层触摸驱动销毁通过esp_touch_destroy
函数(位于touch.c
)完成,主要包括以下步骤:
- 清理触摸传感器资源:删除触摸传感器滤波器并禁用触摸中断
- 注销中断处理函数:注销先前注册的中断处理函数
- 释放触摸项资源:遍历触摸通道链表,释放每个触摸项的资源
- 反初始化触摸传感器:调用触摸传感器反初始化函数
- 释放驱动结构体:释放触摸驱动结构体内存
// 文件:components/esp_peripherals/lib/touch/touch.c
esp_err_t esp_touch_destroy(esp_touch_handle_t touch)
{// 1. 声明临时变量esp_touch_item_t *touch_item, *tmp;// 2. 清理触摸传感器资源
#if CONFIG_IDF_TARGET_ESP32touch_pad_filter_delete(); // 删除触摸传感器滤波器touch_pad_intr_disable(); // 禁用触摸中断
#elif CONFIG_IDF_TARGET_ESP32S2touch_pad_intr_disable(TOUCH_PAD_INTR_MASK_ALL); // 禁用所有触摸中断
#endif// 3. 注销中断处理函数touch_pad_isr_deregister(touch_pad_isr_handler, touch);// 4. 释放触摸项资源STAILQ_FOREACH_SAFE(touch_item, &touch->touch_list, entry, tmp) {STAILQ_REMOVE(&touch->touch_list, touch_item, esp_touch_item, entry);audio_free(touch_item);}// 5. 反初始化触摸传感器touch_pad_deinit();// 6. 释放驱动结构体audio_free(touch);return ESP_OK;
}
触摸外设完整销毁时序图
下图展示了触摸外设从应用程序调用到底层驱动完成销毁的完整流程:
这个时序图展示了触摸外设销毁的完整流程,包括以下关键步骤:
- 应用程序调用
esp_periph_destroy
销毁外设 - 外设库调用
_touch_destroy
函数 _touch_destroy
函数停止定时器并调用底层驱动的esp_touch_destroy
函数esp_touch_destroy
函数执行以下操作:- 根据芯片类型执行不同的清理操作(ESP32或ESP32S2)
- 注销中断处理函数
- 释放所有触摸通道项的资源
- 反初始化触摸传感器
- 释放触摸驱动结构体
_touch_destroy
函数释放外设层的资源
这个实现确保了所有资源都被正确释放,包括硬件资源(触摸传感器、中断)和软件资源(内存、定时器)。
触摸检测算法
触摸检测算法结合了中断和定时器轮询机制,涉及外设层(Peripheral Layer)和底层驱动(Driver Layer)两个层次。下面分别介绍这两个层次的实现。
外设层检测实现(periph_touch.c)
外设层通过定时器触发触摸状态检测,并将触摸事件分发到ESP-ADF的事件系统中:
- 定时器处理:定期检查触摸状态(150ms周期)
- 状态读取:调用底层驱动读取触摸状态
- 事件分发:将触摸事件分发到ESP-ADF的事件系统
// 文件:components/esp_peripherals/periph_touch.c
static void touch_timer_handler(xTimerHandle tmr)
{// 获取外设句柄esp_periph_handle_t periph = (esp_periph_handle_t) pvTimerGetTimerID(tmr);// 获取触摸外设数据periph_touch_t *periph_touch = esp_periph_get_data(periph);// 调用底层驱动读取触摸状态,如果有状态变化,发送命令到外设任务if (esp_touch_read(periph_touch->touch, &periph_touch->result)) {ESP_LOGD(TAG, "Touch event, tap %x, release_mask: %x, long_tap_mask: %x, long_tap_mask: %x",periph_touch->result.tap_mask, periph_touch->result.release_mask,periph_touch->result.long_tap_mask, periph_touch->result.long_release_mask);// 发送命令到外设任务esp_periph_send_cmd(periph, 0, NULL, 0);}
}static esp_err_t _touch_run(esp_periph_handle_t self, audio_event_iface_msg_t *msg)
{// 获取触摸外设数据periph_touch_t *periph_touch = esp_periph_get_data(self);// 发送各类触摸事件到ESP-ADF事件系统touch_send_event(self, PERIPH_TOUCH_TAP, periph_touch->result.tap_mask);touch_send_event(self, PERIPH_TOUCH_RELEASE, periph_touch->result.release_mask);touch_send_event(self, PERIPH_TOUCH_LONG_TAP, periph_touch->result.long_tap_mask);touch_send_event(self, PERIPH_TOUCH_LONG_RELEASE, periph_touch->result.long_release_mask);return ESP_OK;
}static void touch_send_event(esp_periph_handle_t self, int event_id, int mask)
{// 遍历掩码,为每个触发的通道发送事件int touch_num = 0;while (mask) {if (mask & 0x01) {esp_periph_send_event(self, event_id, (void *)touch_num, 0);}mask >>= 1;touch_num ++;}
}
底层驱动检测实现(touch.c)
底层触摸驱动实现在touch.c
中,负责具体的触摸状态检测和事件生成。核心功能由两个函数实现:touch_get_state
和esp_touch_read
。
touch_get_state函数
touch_get_state
函数是触摸状态检测的核心,负责判断单个触摸通道的当前状态:
// 文件:components/esp_peripherals/lib/touch/touch.c
/*** @brief 获取触摸通道当前状态* * 该函数是触摸状态检测的核心,通过检测触摸传感器值和触摸时长,判断触摸的当前状态。* 触摸状态机如下:* 1. 初始状态:未触摸,last_tap_tick = 0* 2. 触摸状态:检测到触摸值小于阈值,记录触摸时间,返回TOUCH_TAP* 3. 释放状态:检测到触摸值大于阈值且触摸时间小于长触摸阈值,返回TOUCH_RELEASE* 4. 长触摸状态:触摸持续时间超过长触摸阈值,返回TOUCH_LONG_TAP(只触发一次)* 5. 长触摸释放:长触摸后检测到触摸值大于阈值,返回TOUCH_LONG_RELEASE*/
static touch_status_t touch_get_state(esp_touch_handle_t touch, esp_touch_item_t *touch_item, long long tick)
{// 控制读取频率,避免过于频繁读取if (tick - touch_item->last_read_tick < TOUCHPAD_READ_INTERVAL_MS) {return TOUCH_UNCHANGE;}// 更新最后读取时间touch_item->last_read_tick = tick;// 读取触摸传感器值esp_err_t err = ESP_OK;
#if CONFIG_IDF_TARGET_ESP32err = touch_pad_read_filtered(touch_item->touch_num, &touch_item->last_read_value);
#elif CONFIG_IDF_TARGET_ESP32S2err = ESP_OK;
#endif// 如果读取失败,返回无变化状态if (err != ESP_OK) {return TOUCH_UNCHANGE;}// 首次读取,初始化未触摸值和阈值if (touch_item->untouch_value == 0) {touch_item->untouch_value = touch_item->last_read_value;int threshold_value = touch_item->untouch_value * touch->tap_threshold_percent / 100;touch_item->threshold_value = threshold_value;}// 检测触摸状态变化if (!touch_item->tapped && touch_item->last_read_value < touch_item->threshold_value) {// 从未触摸变为触摸touch_item->tapped = true;} else if (touch_item->tapped && touch_item->last_read_value > touch_item->threshold_value) {// 从触摸变为未触摸touch_item->tapped = false;}// 定期更新触摸阈值(仅在未触摸状态下)// 这是一种自适应机制,可以根据环境变化调整触摸阈值if (tick - touch_item->update_threshold_tick > UPDATE_THRESHOLD_PERIOD_MS && !touch_item->tapped) {touch_item->update_threshold_tick = tick;touch_item->untouch_value += touch_item->last_read_value;touch_item->untouch_value /= 2; // 取平均值,平滑变化int threshold_value = touch_item->untouch_value * touch->tap_threshold_percent / 100;touch_item->threshold_value = threshold_value;// ESP_LOGD(TAG, "UPDATE THRESHOLD[%d]=%d", touch_item->touch_num, threshold_value);}// 状态机实现:根据当前状态和条件返回相应的触摸事件// 情况1:开始触摸 - 从未触摸状态变为触摸状态if (touch_item->last_tap_tick == 0 && touch_item->tapped) {touch_item->last_tap_tick = tick_get(); // 记录触摸开始时间touch_item->long_tapped = false; // 重置长触摸标志ESP_LOGD(TAG, "TOUCH_TAPPED[%d] %d, threshold %d",touch_item->touch_num, touch_item->last_read_value, touch_item->threshold_value);return TOUCH_TAP; // 返回触摸事件}// 情况2:长触摸后释放 - 触摸时间超过长触摸阈值后释放if (!touch_item->tapped && touch_item->last_tap_tick && tick_get() - touch_item->last_tap_tick > touch->long_tap_time_ms) {touch_item->last_tap_tick = 0; // 清除触摸时间记录touch_item->long_tapped = false; // 重置长触摸标志ESP_LOGD(TAG, "TOUCH_LONG_RELEASE[%d] %d, threshold %d",touch_item->touch_num, touch_item->last_read_value, touch_item->threshold_value);return TOUCH_LONG_RELEASE; // 返回长触摸释放事件}// 情况3:短触摸释放 - 触摸时间未超过长触摸阈值就释放if (!touch_item->tapped && touch_item->last_tap_tick) {touch_item->last_tap_tick = 0; // 清除触摸时间记录touch_item->long_tapped = false; // 重置长触摸标志ESP_LOGD(TAG, "TOUCH_RELEASE[%d] %d, threshold %d",touch_item->touch_num, touch_item->last_read_value, touch_item->threshold_value);return TOUCH_RELEASE; // 返回释放事件}// 情况4:长触摸 - 触摸持续时间超过长触摸阈值if (touch_item->long_tapped == false && touch_item->tapped && tick_get() - touch_item->last_tap_tick > touch->long_tap_time_ms) {touch_item->long_tapped = true; // 设置长触摸标志,防止重复触发ESP_LOGD(TAG, "TOUCH_LONG_TAP[%d] %d, threshold %d",touch_item->touch_num, touch_item->last_read_value, touch_item->threshold_value);return TOUCH_LONG_TAP; // 返回长触摸事件}// 情况5:无状态变化或其他情况return TOUCH_UNCHANGE;
}
esp_touch_read函数
esp_touch_read
函数负责读取所有触摸通道的状态,并将结果汇总到结果结构体中:
// 文件:components/esp_peripherals/lib/touch/touch.c
/*** @brief 读取所有触摸通道的状态* * 该函数遍历所有触摸通道,调用touch_get_state获取每个通道的状态,* 并将结果汇总到result结构体中。如果有任何通道状态发生变化,返回true。* * @param touch 触摸驱动句柄* @param result 用于存储结果的结构体指针* @return 如果有任何通道状态发生变化,返回true;否则返回false*/
bool esp_touch_read(esp_touch_handle_t touch, touch_result_t *result)
{esp_touch_item_t *touch_item;touch_status_t touch_status;bool changed = false; // 标记是否有状态变化// 清空结果结构体memset(result, 0, sizeof(touch_result_t));int tmp;long long tick = tick_get(); // 获取当前时间// 遍历所有触摸通道STAILQ_FOREACH(touch_item, &touch->touch_list, entry) {// 获取当前触摸通道的状态touch_status = touch_get_state(touch, touch_item, tick);// 根据状态设置对应的掩码位switch (touch_status) {case TOUCH_UNCHANGE:// 无变化,不做处理break;case TOUCH_TAP:// 触摸事件changed = true;tmp = 0x01;tmp <<= touch_item->touch_num; // 将位移到对应通道的位置result->tap_mask |= tmp; // 设置触摸掩码break;case TOUCH_RELEASE:// 释放事件changed = true;tmp = 0x01;tmp <<= touch_item->touch_num; // 将位移到对应通道的位置result->release_mask |= tmp; // 设置释放掩码break;case TOUCH_LONG_RELEASE:// 长触摸释放事件changed = true;tmp = 0x01;tmp <<= touch_item->touch_num; // 将位移到对应通道的位置result->long_release_mask |= tmp; // 设置长触摸释放掩码break;case TOUCH_LONG_TAP:// 长触摸事件changed = true;tmp = 0x01;tmp <<= touch_item->touch_num; // 将位移到对应通道的位置result->long_tap_mask |= tmp; // 设置长触摸掩码break;}}// 返回是否有状态变化return changed;
}
触摸状态转换时序图
下图展示了触摸状态的转换过程,包括触摸、释放、长触摸和长触摸释放的完整状态流转:
触摸检测算法关键点
-
阈值自适应:触摸检测算法会定期更新触摸阈值,使其能够适应环境变化。
-
防抖动处理:通过控制读取频率(TOUCHPAD_READ_INTERVAL_MS)和使用滤波器(touch_pad_read_filtered)减少误触发。
-
状态机设计:使用状态机设计模式,清晰地区分不同的触摸状态,并确保状态转换的正确性。
-
多通道支持:支持同时检测多个触摸通道,并通过掩码方式汇总结果。
-
长短触摸区分:通过时间阈值(long_tap_time_ms)区分短触摸和长触摸,提供更丰富的交互方式。
-
ESP32和ESP32S2兼容:代码中包含了对不同芯片的兼容处理,确保在不同平台上都能正常工作。
通过这种设计,触摸检测算法能够准确地检测各种触摸事件,并将其传递给应用程序进行处理,为用户提供良好的交互体验。
触摸事件处理
触摸外设产生以下事件类型:
- PERIPH_TOUCH_TAP:触摸事件
- PERIPH_TOUCH_RELEASE:触摸释放事件
- PERIPH_TOUCH_LONG_TAP:长触摸事件
- PERIPH_TOUCH_LONG_RELEASE:长触摸后释放事件
事件数据为触摸通道编号。
触摸外设使用示例
#include "esp_peripherals.h"
#include "periph_touch.h"void app_main()
{// 初始化外设管理器esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);// 配置触摸外设periph_touch_cfg_t touch_cfg = {.touch_mask = TOUCH_PAD_SEL0 | TOUCH_PAD_SEL1, // 使用触摸通道0和1.tap_threshold_percent = 70, // 触摸阈值70%.long_tap_time_ms = 1500, // 长触摸阈值1.5秒};// 初始化触摸外设并添加到外设集合esp_periph_handle_t touch_handle = periph_touch_init(&touch_cfg);esp_periph_start(touch_handle);esp_periph_set_add_periph(set, touch_handle);// 注册事件回调esp_periph_set_register_callback(set, touch_event_callback, NULL);// 主循环while (1) {vTaskDelay(1000 / portTICK_RATE_MS);}
}// 触摸事件回调函数
static esp_err_t touch_event_callback(audio_event_iface_msg_t *event, void *context)
{switch (event->source_type) {case PERIPH_ID_TOUCH:if (event->cmd == PERIPH_TOUCH_TAP) {printf("Touch pad %d tapped\n", (int)event->data);} else if (event->cmd == PERIPH_TOUCH_RELEASE) {printf("Touch pad %d released\n", (int)event->data);} else if (event->cmd == PERIPH_TOUCH_LONG_TAP) {printf("Touch pad %d long tapped\n", (int)event->data);} else if (event->cmd == PERIPH_TOUCH_LONG_RELEASE) {printf("Touch pad %d long released\n", (int)event->data);}break;}return ESP_OK;
}
相关文章:
ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之触摸屏 Touch)
目录 ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之触摸屏 Touch)简介模块概述功能定义架构位置核心特性 触摸(Touch)外设触摸外设概述触摸外设API和数据结构外设层API(periph_touch.h/periph_touch…...
Python高级爬虫之JS逆向+安卓逆向1.5节: 控制结构
目录 引言: 1.5.1 Python中的控制结构 1.5.2 条件控制 1.5.3 循环控制 1.5.4 跳转控制 1.5.5 爬虫不要接学生单 引言: 大神薯条老师的高级爬虫安卓逆向教程: 这套爬虫教程会系统讲解爬虫的初级,中级,高级知识&a…...
Spine-Leaf 与 传统三层架构:全面对比与解析
本文将详细介绍Spine-Leaf架构,深入对比传统三层架构(Core、Aggre、Access),并探讨其与Full-mesh网络和软件定义网络(SDN)的关联。通过通俗易懂的示例和数据中心网络分析,我将帮助您理解Spine-L…...
微信小程序文字混合、填充动画有效果图
效果图 .wxml <view class"text" style"--deg:{{deg}}deg;"><view>混合父级颜色</view> </view> <view class"fill {{status?action:}}">文字颜色填充</view> <button bind:tap"setStatus"…...
山东大学软件学院创新项目实训开发日志(15)之中医知识问答历史对话查看bug处理后端信息响应成功但前端未获取到
在开发中医知识问答历史对话查看功能的时候,出现了前后端信息获取异同的问题,在经过非常非常非常艰难的查询之后终于解决了这一问题,而这一问题的罪魁祸首就是后端没有setter和getter方法!!!!&a…...
HttpSessionBindingListener 的用法笔记250417
HttpSessionBindingListener 的用法笔记250417 HttpSessionBindingListener 是 Java Servlet 规范中 唯一 由 被存储对象自身实现 的会话监听接口, 1. 核心功能 HttpSessionBindingListener 是一个由 会话属性对象自身实现 的接口,用于监听该对象被绑定…...
EuroCropsML:首个面向少样本时间序列作物分类的多国基准数据集
2025-04-15,由慕尼黑工业大学等机构创建的 EuroCropsML 数据集,这是一个结合了农民报告的作物数据与 Sentinel-2 卫星观测的时间序列数据集,覆盖了爱沙尼亚、拉脱维亚和葡萄牙。该数据集为解决遥感应用中作物类型数据空间不平衡问题提供了新的…...
《如何用 Function 实现动态配置驱动的处理器注册机制?》
大家好呀!👋 今天我们来聊聊一个超实用的技术话题 - 如何用Java的Function接口实现动态配置驱动的处理器注册机制。听起来很高大上?别担心,我会用最简单的方式讲清楚!😊 一、为什么要用Function实现处理器…...
PyTorch:学习 CIFAR-10 分类
🔍 开始你的图像分类之旅:一步一步学习 CIFAR-10 分类 图像分类是计算机视觉中最基础的任务之一,如果你是初学者,那么以 CIFAR-10 为训练场是一个不错的选择。本文一步一步带你从零开始,学习如何用深度学习模型实现图…...
SpringBoot整合Thymeleaf模板:构建现代化Web视图层的完整指南
在Java Web开发领域,Thymeleaf作为一款自然模板引擎,凭借其优雅的语法和与Spring生态的无缝集成,已成为替代传统JSP的首选方案。本文将从技术整合、核心原理到生产实践,深度解析SpringBoot与Thymeleaf的协同工作方式。 一、Thymel…...
学习笔记十五——rust柯里化,看不懂 `fn add(x) -> impl Fn(y)` 的同学点进来!
🧠 Rust 柯里化从零讲透:看不懂 fn add(x) -> impl Fn(y) 的同学点进来! 🍔 一、什么是柯里化?先用一个超好懂的生活比喻 假设你在点一个汉堡: 你说:我要点一个鸡腿汉堡! 店员…...
软件安装包-yum
yum:软件管理的得力助手 yum是一个软件下载安装管理的一个客户端,例如:小米应用商城、华为应用商城... Linux中软件包可能有依赖关系——yum会帮我们解决依赖关系的问题! 1、软件包是什么? 在Linux下安装软件, 一个通…...
C++面试
C面试 c面试100题 1、封装多态继承 2、数据集合 3、 4、便于外部文件访问 5、只能通过对象访问 6、通过类名 7、构造函数、析构函数、拷贝构造函数、拷贝复制函数 8、将一个对象复制给新建的对象 9、没有返回值 10、类的对象中有指针,防止多个指针指向同…...
Java SpringBoot设置自定义web的图片本地路径
一,设置配置文件:application.properties my.config.image-pathD:\\Download\\images二,新增配置类:MyImagesConfig import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springfr…...
Python HTTP库——requests
文章目录 简介安装基本概念RESTfulAPIOAuth2.0Cookie和Session 初试GET请求POST请求PUT请求DELETE请求HEAD请求OPTIONS请求传递查询参数响应内容自定义响应头传递表单参数传递文件响应状态码响应头Cookies重定向和历史记录超时错误和异常Session对象请求和响应对象预处理请求SS…...
用idea配置springboot+mybatis连接postersql数据库
从socket开始,我们就要开始部署前后端的交互了,所以今天带来一份热度比较高的框架springboot,并教大家如何连接数据库。 框架 先给大家看一下目录结构,因为有些需要调用文件路径: 创建项目: 新版本可以…...
【补充篇】Davinci工具要求的dbc格式
1 简介 DBC文件是一种用于描述CAN(Controller Area Network,控制器局域网络)通信协议中报文和信号的格式化文件,其全称为“Database CAN”。DBC文件的核心作用是定义和解析CAN网络中的通信数据,包括节点、报文、信号及其属性等信息。 对于不同角色的工程师,DBC文件有着…...
IT资产管理(一)之GLPI安装及部署
一、GLPI 介绍 GLPI:Gestionnaire Libre de Parc Informatique 是一个免费的资产和 IT 管理软件包,提供 ITIL 服务台功能、许可证跟踪和软件审计。 GLPI 的主要功能: 服务资产和配置管理 (SACM):管理您的 IT 资产和配置,跟踪计算机、外围设备、网络打印机及其相关组件…...
RPCRT4!OSF_CCALL::ActivateCall函数分析之RPCRT4!OSF_CCALL结构中的Bindings--RPC源代码分析
第一部分: 1: kd> t RPCRT4!OSF_CCALL::ActivateCall: 001b:77bf5789 55 push ebp 1: kd> kc # 00 RPCRT4!OSF_CCALL::ActivateCall 01 RPCRT4!OSF_CASSOCIATION::AllocateCCall 02 RPCRT4!OSF_BINDING_HANDLE::AllocateCCall 03 RPCRT4!OS…...
docker登录AWS ECR拉取镜像
1、配置AWS 登录key [rootip-172-31-13-6 ~]# aws configure AWS Access Key ID [None]: XXXXXXXXXXX AWS Secret Access Key [None]: %%YYYDSRGTHFGFSGRTHTHE$RHTSG Default region name [None]: ap-southeast-1 Default output format [None]: json2、登录AWS ECR镜像仓库 …...
IntelliJ IDEA download JDK
IntelliJ IDEA download JDK 自动下载各个版本JDK,步骤 File - Project Structure (快捷键 Ctrl Shift Alt S) 如果下载失败,换个下载站点吧。一般选择Oracle版本,因为java被Oracle收购了 好了。 花里胡哨&#…...
MQTT协议与HTTP协议的对比分析
以下是MQTT协议与HTTP协议的对比分析,从协议特性到应用场景的系统性对比: 一、协议层级与设计目标对比 维度MQTTHTTP协议层级应用层协议(基于TCP/IP)应用层协议(基于TCP/IP)核心设计目标机器间轻量级消息通…...
jenkins凭据管理(配置github密钥)
1.凭据管理 添加两种类型的凭据,Username with password和Secret text(填的token) Username with password是github登录的用户名和密码,Secret text填的github生成的token,权限的限制更细,安全性更高一些 Dashboard -> Manag…...
B端小程序如何突破常规,成为企业获客新利器?
数据驱动的用户旅程优化 在当今竞争激烈的市场环境中,了解并优化用户的交互路径对于吸引和保留客户至关重要。B端小程序可以通过收集用户行为数据来分析用户偏好和使用习惯。例如,应用热图分析工具可以直观展示用户点击最频繁的区域,帮助企业…...
25.4.17学习总结
关于bcrypt算法 BCrypt 的主要特点和优点: 加盐 (Salting): BCrypt 会自动为每个密码生成一个随机的盐值 (salt) 并将其与密码组合在一起,然后再进行哈希。 盐值是随机数据,用于防止彩虹表攻击。 这意味着即使两个用户使用相同的密码&#x…...
java 设计模式 策略模式
简介 策略模式(Strategy Pattern)是一种行为设计模式,旨在定义一系列算法,并将每一个算法封装起来,使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户端。换句话说,策略模式通过将不同的算法…...
游戏盾和高防ip有什么区别
游戏盾和高防IP都是针对网络攻击的防护方案,但核心目标、技术侧重点和应用场景存在显著差异。以下是两者的详细对比分析: 一、核心定位与目标 维度高防IP游戏盾核心目标抵御大流量网络攻击(…...
关于 雷达(Radar) 的详细解析,涵盖其定义、工作原理、分类、关键技术、应用场景、挑战及未来趋势,结合实例帮助理解其核心概念
以下是关于 雷达(Radar) 的详细解析,涵盖其定义、工作原理、分类、关键技术、应用场景、挑战及未来趋势,结合实例帮助理解其核心概念: 一、雷达的定义与核心功能 1. 定义 雷达(Radar,Radio D…...
机器学习 Day11 决策树
1.决策树简介 原理:思想源于程序设计的 if - else 条件分支结构 ,是一种树形结构。内部节点表示属性判断,分支是判断结果输出,叶节点是分类结果 。案例:以母亲给女儿介绍男朋友为例。女儿依次询问年龄(≤3…...
【HFP】深入解析蓝牙 HFP 协议中呼叫转移、呼叫建立及保持呼叫状态的机制
目录 一、核心指令概述 1.1 ATCMER:呼叫状态更新的 “总开关” 1.2 ATBIA:指示器的 “精准控制器” 1.3 指令对比 1.4 指令关系图示 二、CIEV 结果码:状态传递的 “信使” 2.1 工作机制 2.2 三类核心指示器 三、状态转移流程详解 3…...
音频识别优化(FFT)
整合多频段检测、动态阈值调整和持续时长验证的完整代码实现,包含详细注释: #include "esp_dsp.h" #include "driver/i2s.h" #include "esp_log.h" #include "math.h" static const char* TAG "ADV_FRE…...
【Redis】Redis基本命令(1)
KEYS 返回所有满足样式(pattern)的key。 KEY * 返回所有key,不简易使用 性能问题:当 Redis 存储百万级键时,会消耗大量 CPU 和内存资源,Redis 是单线程模型,KEYS * 执行期间会阻塞其他所有命令…...
IDEA2024 pom.xml依赖文件包报红解决
异常: 原因: 本地的Maven Repository库中不存在对应版本的dependency依赖,所以导致报红。 解决: 方法1:找到对应项目,右键Sync Project 就可以了 方法2:修改setting中maven的自动更新…...
Qt 信号与槽复习
Qt 信号与槽复习 Qt 信号与槽(Signals and Slots)机制是 Qt 框架的核心特性之一,用于实现对象之间的通信。它提供了一种松耦合的方式,使得组件可以独立开发和复用,广泛应用于 GUI 编程、事件处理和跨模块交互。本文将…...
RestControllerAdvice 和 ControllerAdvice 两个注解的区别与联系
它们都用于实现全局的通用处理逻辑,主要应用在以下三个方面: 全局异常处理: 使用 ExceptionHandler 注解的方法。全局数据绑定: 使用 InitBinder 注解的方法。全局数据预处理: 使用 ModelAttribute 注解的方法。 联系: 核心功能相同: 两者都提供了上述…...
最快打包WPF 应用程序
在 Visual Studio 中右键项目选择“发布”,目标选“文件夹”,模式选“自包含”,生成含 .exe 的文件夹,压缩后可直接发给别人或解压运行,无需安装任何东西。 最简单直接的新手做法: 用 Visual Studio 的“…...
Java NIO Java 虚拟线程(微线程)与 Go 协程的运行原理不同 为何Go 能在低配机器上承接10万 Websocket 协议连接
什么是Java NIO? Java NIO(New Input/Output) 是Java 1.4(2002年)引入的一种非阻塞、面向缓冲区的输入输出框架,旨在提升Java在高性能和高并发场景下的I/O处理能力。它相比传统的 Java IO(java…...
C# 对列表中的元素的多个属性进行排序
目录 前言一、OrderBy、OrderByDescending、ThenBy、ThenByDescending二、Sort 前言 在开发过程中,我们经常需要 根据列表中的元素的某个属性进行排序,下面我们将简单介绍常用的排序函数。 例如此处有一个类,拥有的元素为编号和值 public …...
OpenCV颜色变换cvtColor
OpenCV计算机视觉开发实践:基于Qt C - 商品搜索 - 京东 颜色变换是imgproc模块中一个常用的功能。我们生活中看到的大多数彩色图片都是RGB类型的,但是在进行图像处理时需要用到灰度图、二值图、HSV(六角锥体模型,这个模型中颜色的…...
java IO/NIO/AIO
(✪▽✪)曼波~~~~!让曼波用最可爱的赛马娘方式给你讲解吧!(⁄ ⁄•⁄ω⁄•⁄ ⁄) 🎠曼波思维导图大冲刺(先看框架再看细节哦): 📚 解释 Java 中 IO、NIO、AIO 的区别和适用场景: …...
如何深入理解引用监视器,安全标识以及访问控制模型与资产安全之间的关系
一、核心概念总结 安全标识(策略决策的 “信息载体) 是主体(如用户、进程)和客体(如文件、数据库、设备)的安全属性,用于标记其安全等级、权限、访问能力或受保护级别,即用于标识其安全等级、权限范围或约束…...
宜搭与金蝶互通——连接器建立
一、 进入连接器工厂 图1 连接器入口 二、 新建连接器 图2 新建连接器第一步 1、 连接器显示名,如图2中①所示; 2、 图2中②域名,是金蝶系统API接口里面的“完整服务地址”com之前的信息,不含“https”,如图3中①所示; 3、 Base Url通常为“/”,如图2…...
中间件--ClickHouse-7--冷热数据分离,解决Mysql海量数据瓶颈
在web应用中,当数据量非常大时,即使MySQL的存储能够满足,但性能一般也会比较差。此时,可以考虑使用ClickHouse存储历史数据,在Mysql存储最近热点数据的方式,来优化和提升查询性能。ClickHouse的设计初衷就是…...
1.1 设置电脑开机自动用户登录exe开机自动启动
本文介绍两个事情: 1.Windows如何开机自动登录系统(不用输密码) 2. 应用程序(.exe)如何开机自动启动 详细解释如下: 一、Windows如何开机自动登录系统(不用输密码) 设备上的工控机,如果开机后都需要操作人员输入密码&…...
vscode stm32 variable uint32_t is not a type name 问题修复
问题 在使用vscodekeil开发stm32程序时,发现有时候vscode的自动补全功能失效,且problem窗口一直在报错。variable “uint32_t” is not a type name uint32_t 定义位置 uint32_t 实际是在D:/Keil_v5/ARM/ARMCC/include/stdint.h中定义的。将D:/Keil_v5…...
动态规划与记忆化搜索的区别与联系
记忆化搜索(Memoization)和动态规划(Dynamic Programming, DP)都是解决重叠子问题的高效算法技术,但它们有着不同的实现方式和特点。 1. 基本概念 记忆化搜索(自顶向下) 本质:带有…...
html+js+clickhouse环境搭建
实验背景: 我目前有一台服务器A,和一台主机B,两台设备属于同一局域网,相互之间可以通讯。服务器A中部署着clickhouse,我在主机B中想直接通过javascript代码访问服务器中的clickhouse数据库并获取数据。 ClickHouse 服务…...
生命护航行动再启航!
温州好人陈飞携防溺水课堂,为乡村少年宫筑起安全防线 图文作者:华夏之音/李望 随着夏日热浪的滚滚而来,楠溪江畔的安全警钟再次响起。在这片如诗如画的土地上,一场旨在保护青少年生命安全的防溺水课堂活动拉开了…...
Android Compose Activity 页面跳转动画详解
下面我将全面详细地介绍在 Compose 中实现 Activity 跳转动画的各种方法,包括基础实现、高级技巧和最佳实践。 一、基础 Activity 过渡动画 1. overridePendingTransition 传统方式 这是最基础且兼容性最好的方法,适用于所有 Android 版本。 实现步骤…...
Android启动初始化init.rc详解
1. Android启动与init.rc简介 1.1 Android启动过程 一张图简单阐述一下 (网络图片,侵删) 1.2 init.rc 简介 Linux的重要特征之一就是一切都是以文件的形式存在的,例如,一个设备通常与一个或多个设备文件对应。这些…...