目录
- [ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之触摸屏 Touch)](#ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之触摸屏 Touch))
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,用于初始化和配置触摸外设:
c
// 文件: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_SEL1
int 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;
外设层内部使用以下数据结构:
c
// 文件: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的触摸传感器:
c
// 文件: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);
底层驱动内部使用以下数据结构:
c
// 文件: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
结构体内存 - 设置配置参数:设置触摸通道掩码、触摸阈值和长触摸时间
- 注册回调函数:设置初始化、运行和销毁回调函数
c
// 文件: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
),该函数负责初始化底层触摸驱动并启动定时器:
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触摸传感器:配置触摸传感器硬件
- 初始化触摸通道链表:初始化触摸通道项目链表
- 为每个触摸通道创建触摸项:遍历触摸通道掩码,为每个启用的通道创建触摸项
- 配置中断处理:如果提供了中断处理函数,则配置触摸中断
c
// 文件: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_ESP32
touch_pad_config(touch_num, 0);
#elif CONFIG_IDF_TARGET_ESP32S2
touch_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_ESP32
touch_pad_isr_register(touch_pad_isr_handler, touch);
touch_pad_intr_enable();
#elif CONFIG_IDF_TARGET_ESP32S2
touch_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_ESP32
touch_pad_filter_start(TOUCHPAD_FILTER_PERIOD);
#endif
return 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
)完成,主要包括以下步骤:
- 获取外设数据:获取触摸外设的内部数据结构
- 停止定时器:停止触摸状态检测定时器
- 释放底层资源:调用底层驱动的销毁函数释放资源
- 释放内部数据结构:释放触摸外设的内部数据结构
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
)完成,主要包括以下步骤:
- 清理触摸传感器资源:删除触摸传感器滤波器并禁用触摸中断
- 注销中断处理函数:注销先前注册的中断处理函数
- 释放触摸项资源:遍历触摸通道链表,释放每个触摸项的资源
- 反初始化触摸传感器:调用触摸传感器反初始化函数
- 释放驱动结构体:释放触摸驱动结构体内存
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_ESP32
touch_pad_filter_delete(); // 删除触摸传感器滤波器
touch_pad_intr_disable(); // 禁用触摸中断
#elif CONFIG_IDF_TARGET_ESP32S2
touch_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触摸传感器
这个时序图展示了触摸外设销毁的完整流程,包括以下关键步骤:
- 应用程序调用
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的事件系统
c
// 文件: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
函数是触摸状态检测的核心,负责判断单个触摸通道的当前状态:
c
// 文件: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_ESP32
err = touch_pad_read_filtered(touch_item->touch_num, &touch_item->last_read_value);
#elif CONFIG_IDF_TARGET_ESP32S2
err = 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
函数负责读取所有触摸通道的状态,并将结果汇总到结果结构体中:
c
// 文件: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
触摸检测算法关键点
-
阈值自适应:触摸检测算法会定期更新触摸阈值,使其能够适应环境变化。
-
防抖动处理:通过控制读取频率(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:长触摸后释放事件
事件数据为触摸通道编号。
触摸外设使用示例
c
#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;
}