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外设子系统的重要组成部分,位于硬件驱动层和应用层之间:

应用程序
ESP外设子系统
输入类外设
按键外设
触摸外设
ADC按键外设
GPIO驱动
触摸驱动
ADC驱动

核心特性

  • 多种输入类型支持:支持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)完成,主要包括以下步骤:

  1. 创建外设句柄:调用esp_periph_create函数创建外设句柄
  2. 分配内部数据结构:分配periph_touch_t结构体内存
  3. 设置配置参数:设置触摸通道掩码、触摸阈值和长触摸时间
  4. 注册回调函数:设置初始化、运行和销毁回调函数
// 文件: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)完成,主要包括以下步骤:

  1. 分配触摸驱动结构体:分配esp_touch结构体内存
  2. 设置触摸参数:设置触摸通道掩码、长触摸时间阈值和触摸阈值百分比
  3. 初始化ESP32触摸传感器:配置触摸传感器硬件
  4. 初始化触摸通道链表:初始化触摸通道项目链表
  5. 为每个触摸通道创建触摸项:遍历触摸通道掩码,为每个启用的通道创建触摸项
  6. 配置中断处理:如果提供了中断处理函数,则配置触摸中断
// 文件: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;
}
触摸外设完整初始化时序图

下图展示了触摸外设从应用程序调用到底层驱动完成初始化的完整流程:

应用程序 periph_touch_init (periph_touch.c) esp_periph库 _touch_init (periph_touch.c) esp_touch_init (touch.c) ESP32触摸传感器 periph_touch_init(config) esp_periph_create(PERIPH_ID_TOUCH, "periph_touch") periph 分配 periph_touch_t 结构体 设置触摸通道掩码和长触摸时间 esp_periph_set_data(periph, periph_touch) esp_periph_set_function(periph, _touch_init, _touch_run, _touch_destroy) periph 当外设被添加到外设集合并启动时 _touch_init(self) esp_periph_get_data(self) periph_touch 准备touch_config_t配置 esp_touch_init(&touch_config) 分配esp_touch结构体 设置触摸参数 touch_pad_init() touch_pad_set_voltage() touch_pad_filter_start() touch_pad_isr_register() touch_pad_intr_enable() opt [配置中断] 初始化触摸通道链表 分配esp_touch_item_t结构体 touch_pad_config(touch_num, 0) 添加触摸项到链表 loop [遍历每个触摸通道] 等待触摸传感器初始化完成 touch句柄 esp_periph_start_timer(self, 150ms, touch_timer_handler) ESP_OK 应用程序 periph_touch_init (periph_touch.c) esp_periph库 _touch_init (periph_touch.c) esp_touch_init (touch.c) ESP32触摸传感器

触摸外设销毁流程

触摸外设的销毁流程同样涉及两个层次:外设层(Peripheral Layer)和底层驱动(Driver Layer)。下面分别介绍这两个层次的销毁过程。

外设层销毁过程(periph_touch.c)

外设层销毁主要通过_touch_destroy函数(位于periph_touch.c)完成,主要包括以下步骤:

  1. 获取外设数据:获取触摸外设的内部数据结构
  2. 停止定时器:停止触摸状态检测定时器
  3. 释放底层资源:调用底层驱动的销毁函数释放资源
  4. 释放内部数据结构:释放触摸外设的内部数据结构
// 文件: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)完成,主要包括以下步骤:

  1. 清理触摸传感器资源:删除触摸传感器滤波器并禁用触摸中断
  2. 注销中断处理函数:注销先前注册的中断处理函数
  3. 释放触摸项资源:遍历触摸通道链表,释放每个触摸项的资源
  4. 反初始化触摸传感器:调用触摸传感器反初始化函数
  5. 释放驱动结构体:释放触摸驱动结构体内存
// 文件: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库 _touch_destroy (periph_touch.c) esp_touch_destroy (touch.c) ESP32触摸传感器 esp_periph_destroy(periph) 当外设被销毁时,调用_touch_destroy _touch_destroy(self) esp_periph_get_data(self) periph_touch esp_periph_stop_timer(self) esp_touch_destroy(periph_touch->>touch) touch_pad_filter_delete() touch_pad_intr_disable() touch_pad_intr_disable(TOUCH_PAD_INTR_MASK_ALL) alt [ESP32] [ESP32S2] touch_pad_isr_deregister(touch_pad_isr_handler, touch) STAILQ_REMOVE(&touch->>touch_list, touch_item, esp_touch_item, entry) audio_free(touch_item) loop [遍历触摸通道链表] touch_pad_deinit() audio_free(touch) ESP_OK audio_free(periph_touch) ESP_OK 应用程序 esp_periph库 _touch_destroy (periph_touch.c) esp_touch_destroy (touch.c) ESP32触摸传感器

这个时序图展示了触摸外设销毁的完整流程,包括以下关键步骤:

  1. 应用程序调用esp_periph_destroy销毁外设
  2. 外设库调用_touch_destroy函数
  3. _touch_destroy函数停止定时器并调用底层驱动的esp_touch_destroy函数
  4. esp_touch_destroy函数执行以下操作:
    • 根据芯片类型执行不同的清理操作(ESP32或ESP32S2)
    • 注销中断处理函数
    • 释放所有触摸通道项的资源
    • 反初始化触摸传感器
    • 释放触摸驱动结构体
  5. _touch_destroy函数释放外设层的资源

这个实现确保了所有资源都被正确释放,包括硬件资源(触摸传感器、中断)和软件资源(内存、定时器)。

触摸检测算法

触摸检测算法结合了中断和定时器轮询机制,涉及外设层(Peripheral Layer)和底层驱动(Driver Layer)两个层次。下面分别介绍这两个层次的实现。

外设层检测实现(periph_touch.c)

外设层通过定时器触发触摸状态检测,并将触摸事件分发到ESP-ADF的事件系统中:

  1. 定时器处理:定期检查触摸状态(150ms周期)
  2. 状态读取:调用底层驱动读取触摸状态
  3. 事件分发:将触摸事件分发到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_stateesp_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;
}
触摸状态转换时序图

下图展示了触摸状态的转换过程,包括触摸、释放、长触摸和长触摸释放的完整状态流转:

系统启动
触摸值<阈值
触摸值>阈值
且时间<长触摸阈值
持续触摸
且时间>长触摸阈值
触摸值>阈值
状态处理完成
状态处理完成
last_tap_tick = 0
long_tapped = false
tapped = false
last_tap_tick = 当前时间
long_tapped = false
tapped = true
返回 TOUCH_TAP
last_tap_tick = 0
long_tapped = false
tapped = false
返回 TOUCH_RELEASE
last_tap_tick 保持不变
long_tapped = true
tapped = true
返回 TOUCH_LONG_TAP
last_tap_tick = 0
long_tapped = false
tapped = false
返回 TOUCH_LONG_RELEASE
触摸检测算法关键点
  1. 阈值自适应:触摸检测算法会定期更新触摸阈值,使其能够适应环境变化。

  2. 防抖动处理:通过控制读取频率(TOUCHPAD_READ_INTERVAL_MS)和使用滤波器(touch_pad_read_filtered)减少误触发。

  3. 状态机设计:使用状态机设计模式,清晰地区分不同的触摸状态,并确保状态转换的正确性。

  4. 多通道支持:支持同时检测多个触摸通道,并通过掩码方式汇总结果。

  5. 长短触摸区分:通过时间阈值(long_tap_time_ms)区分短触摸和长触摸,提供更丰富的交互方式。

  6. 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/77135.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python 读取分级目录

import osdef read_files_in_directory(root_dir):# 遍历根目录下的所有文件和目录for year_dir in os.listdir(root_dir):year_path os.path.join(root_dir, year_dir)if os.path.isdir(year_path): # 确保是目录for month_dir in os.listdir(year_path):# if month_dir in …

MongoServerError: Authentication failed.处理办法

1停止MongoDB服务&#xff1a; systemctl stop mongod2临时修改MongoDB配置&#xff0c;禁用认证&#xff1a; vim /etc/mongdb.config 在配置文件中找到 security:authorization: disabled # 临时关闭认证3.重启MongoDB服务 # 重启MongoDB服务 sudo systemctl restart mon…

ObjectInputStream 终极解析与记忆指南

ObjectInputStream 终极解析与记忆指南 一、核心本质 ObjectInputStream 是 Java 提供的对象反序列化流,继承自 InputStream,用于读取由ObjectOutputStream序列化的Java对象。 核心特性速查表 特性说明继承链InputStream → ObjectInputStream核心功能实现Java对象反序列化…

Java面试高频问题(1-5)

一、HashMap实现原理与并发问题 核心机制 1. 哈希冲突解决方案&#xff1a;采用数组链表红黑树结构&#xff08;JDK1.8&#xff09;&#xff0c;当链表长度超过阈值&#xff08;默认8&#xff09;时转为红黑树&#xff0c;提升查询效率 2. 扩容机制&#xff1a;当元素数量超过…

Genspark:重新定义AI搜索与代理的全能型工具

在当今快速发展的AI技术领域&#xff0c;搜索工具正在经历前所未有的变革。Genspark&#xff0c;这家由前百度高管景鲲和朱凯华创立的AI公司&#xff0c;为我们带来了全新的AI代理引擎体验。作为一位专注于AI工具分享的博主&#xff0c;今天我将为大家详细介绍这款强大的工具&a…

工作记录3

前言: 继续刷尚硅谷的前端视频,查漏补缺。 JS (1)apply() 方法与 call() 方法 (2)构造函数 (3)原型对象<

photo-sphere-viewer 4.8.1在vue中使用

photo-sphere-viewer 加载单张平面图 import { Viewer } from photo-sphere-viewerthis.viewer new Viewer({panorama: ‘完整的url,也可以是一个base64’,// Containercontainer: document.getElementById(viewer1),navbar: true,// Resize the panoramasize: {width: 100%,…

【PyTorch】PyTorch中的非线性激活函数详解:原理、优缺点与实战指南

目录 PyTorch中的非线性激活函数详解&#xff1a;原理、优缺点与实战指南一、核心激活函数作用、分类与数学表达1. 传统饱和型激活函数2. ReLU族&#xff08;加权和类核心&#xff09;3. 自适应改进型激活函数4. 轻量化与硬件友好型 二、优缺点对比与适用场景三、选择策略与PyT…

中间件--ClickHouse-7--冷热数据分离,解决Mysql海量数据瓶颈

在web应用中&#xff0c;当数据量非常大时&#xff0c;即使MySQL的存储能够满足&#xff0c;但性能一般也会比较差。此时&#xff0c;可以考虑使用ClickHouse存储历史数据&#xff0c;在Mysql存储最近热点数据的方式&#xff0c;来优化和提升查询性能。ClickHouse的设计初衷就是…

阿里一面:Nacos配置中心交互模型是 push 还是 pull ?(原理+源码分析)

对于Nacos大家应该都不太陌生&#xff0c;出身阿里名声在外&#xff0c;能做动态服务发现、配置管理&#xff0c;非常好用的一个工具。然而这样的技术用的人越多面试被问的概率也就越大&#xff0c;如果只停留在使用层面&#xff0c;那面试可能要吃大亏。 比如我们今天要讨论的…

DAY09:【pytorch】nn网络层

1、卷积层 1.1 Convolution 1.1.1 卷积操作 卷积运算&#xff1a;卷积核在输入信号&#xff08;图像&#xff09;上滑动&#xff0c;相应位置上进行乘加卷积核&#xff1a;又称为滤波器、过滤器&#xff0c;可认为是某种模式、某种特征 1.1.2 卷积维度 一般情况下&#xf…

Pinpoint - 大型分布式系统的 APM(应用性能管理)工具

文章目录 一、关于 Pinpoint最新版本&#xff08;2024/10/23&#xff09;-- v3.0.1PHP, PYTHON 二、概述支持的模块 一、关于 Pinpoint Pinpoint 是一个用于大型分布式系统的 APM&#xff08;应用性能管理&#xff09;工具&#xff0c;由 Java / PHP/PYTHON 编写。 受 Dapper …

设计模式实践:模板方法、观察者与策略模式详解

目录 1 模板方法1.1 模板方法基本概念1.2 实验1.2.1 未使用模板方法实现代码1.2.2 使用模板方法的代码 2 观察者模式2.1 观察者模式基本概念2.2 实验 3 策略模式3.1 策略模式基本概念3.2 实验 1 模板方法 1.1 模板方法基本概念 定义&#xff1a;一个操作中的算法的骨架 &…

Vue 2.0和3.0笔记

Vue 3 关于组件 今天回顾了下2.0关于组件的内容&#xff0c;3.0定义组件的方式多了一种就是通过单文件组件&#xff08;Single-File Component&#xff09;的方式将Vue的模板&#xff0c;逻辑和样式放到一个文件中&#xff0c;2.0则不同&#xff0c;它是将模板放到一个属性中…

前端面试-微前端

1. 什么是微前端&#xff1f;它的核心价值是什么&#xff1f; 答案&#xff1a; 微前端是一种将前端应用拆分为独立模块的架构模式&#xff0c;每个模块可由不同团队独立开发、测试、部署和运行。其核心价值包括&#xff1a; 技术栈无关性&#xff1a;支持 React、Vue、Angul…

Axure高保真AI算法训练平台

点击下载《Axure高保真AI算法训练平台(.rp) 》 原型效果&#xff1a;https://axhub.im/ax9/69fdf8f2b10b59c3/#g1 摘要 本文介绍了一款功能全面且高效的AI算法训练平台&#xff0c;旨在为数据科学家、研究人员和工程师提供从数据准备到模型部署的一站式解决方案。该平台由四大…

Ubuntu服务器日志满audit:backlog limit exceeded了会报错解决方案-Linux 审计系统 (auditd) 工具

auditd 是 Linux 系统中的审计守护进程&#xff0c;负责收集、记录和监控系统安全相关事件。以下是相关工具及其功能&#xff1a; 核心组件 auditd - 审计守护进程 系统的审计服务主程序 收集系统调用信息并写入日志文件 通常存储在 /var/log/audit/audit.log auditctl - 审计控…

Windows10系统RabbitMQ无法访问Web端界面

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 项目场景&#xff1a; 在一个基于 .NET 的分布式项目中&#xff0c;团队使用 RabbitMQ 作为消息队列中间件&#xff0c;负责模块间的异步通信。开发环境为 Windows 10 系统&#xff0c;开发人员按照官…

Qt 的 事件队列

Qt 的 事件队列 是其核心事件处理机制之一&#xff0c;用于管理和分发系统与用户生成的事件&#xff08;如鼠标点击、键盘输入、定时器、信号槽中的队列连接等&#xff09;。理解 Qt 的事件队列对多线程、界面响应以及异步处理尤为关键。 一、Qt 的事件处理模型概览 Qt 是基于…

无人机自主导航与路径规划技术要点!

一、自主导航与路径规划技术要点 1. 传感器融合 GPS/北斗定位&#xff1a;提供全局定位&#xff0c;但在室内或遮挡环境下易失效。 惯性测量单元&#xff08;IMU&#xff09;**&#xff1a;通过加速度计和陀螺仪实时追踪姿态&#xff0c;弥补GPS信号丢失时的定位空缺。 …