目錄
- 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;
}