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.pswp.cn/pingmian/77135.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/77135.shtml
英文地址,請注明出處:http://en.pswp.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信號丟失時的定位空缺。 …