触摸元件
=============

:link_to_translation:`en:[English]`

概述
--------

触摸元件库是基于触摸传感器驱动设计的高度抽象的元件库，该库提供了统一且友好的软件接口，可以快速构建电容式触摸传感器的应用。有关触摸传感器驱动 API 的更多信息，请参阅 :doc:`../peripherals/touch_pad`。

架构
^^^^^^^^^^^^

触摸元件库通过触摸传感器驱动程序配置触摸传感器外设。使用时，部分必要的硬件参数需要传递给函数 :cpp:func:`touch_element_install`，但只有在调用 :cpp:func:`touch_element_start` 函数后，才会自动配置这些参数。这些参数配置会对实时系统产生很大的影响，因此步骤顺序很重要，必须在调用启动函数之后进行配置，确保系统正常运行。

上述参数包括触摸通道阈值、防水屏蔽传感器驱动级别等。触摸元件库会设置触摸传感器中断和 esp_timer 例程，并在触摸传感器中断服务例程中获取触摸传感器的硬件信息（通道状态、通道编号）。当特定通道事件发生时，硬件信息将传递给 esp_timer 回调例程，esp_timer 回调例程将触摸传感器通道信息分配给触摸元件（例如按键、滑条等）。随后，触摸元件库运行特定算法，更新触摸元件状态或计算其位置，并将结果分派给相应的处理程序。

因此，在使用触摸元件库时，你无需关注触摸传感器外设的工作细节，该库会处理大部分硬件信息，并将更有意义的信息传递给事件处理程序。

下图展示了触摸元件库的工作流程。

.. figure:: /../_static/touch_element/te_architecture.svg
    :scale: 100 %
    :align: center

    触摸元件库架构


下表展示了 {IDF_TARGET_NAME} 中与触摸元件库有关的功能。

.. list-table::
    :header-rows: 1
    :widths: 70 30
    :align: center

    * - 功能
      - {IDF_TARGET_NAME}
    * - 防水
      - ✔
    * - 按键
      - ✔
    * - 触摸滑条
      - ✔
    * - 矩阵按键
      - ✔


外设
^^^^^^^^^^

{IDF_TARGET_NAME} 集成了一个触摸传感器外设，具有多个物理通道。

.. only:: esp32s2 or esp32s3

    - 14 个物理电容触摸通道
    - 定时器或软件 FSM 触发模式
    - 高达 5 种中断（高阈值和低阈值中断、测量单通道完成和测量所有通道完成中断、测量超时中断）
    - 睡眠模式唤醒源
    - 硬件内置降噪
    - 硬件滤波器
    - 硬件防水传感器
    - 硬件近场感应传感器

这些通道的具体位置如下：

.. list-table::
    :header-rows: 1
    :widths: 50 50
    :align: center

    * - 通道
      - {IDF_TARGET_NAME}
    * - **通道 0**
      - **GPIO 0（保留）**
    * - **通道 1**
      - **GPIO 1**
    * - **通道 2**
      - **GPIO 2**
    * - **通道 3**
      - **GPIO 3**
    * - **通道 4**
      - **GPIO 4**
    * - **通道 5**
      - **GPIO 5**
    * - **通道 6**
      - **GPIO 6**
    * - **通道 7**
      - **GPIO 7**
    * - **通道 8**
      - **GPIO 8**
    * - **通道 9**
      - **GPIO 9**
    * - **通道 10**
      - **GPIO 10**
    * - **通道 11**
      - **GPIO 11**
    * - **通道 12**
      - **GPIO 12**
    * - **通道 13**
      - **GPIO 13**
    * - **通道 14**
      - **GPIO 14**


.. only:: esp32

    - 10 个物理电容触摸通道
    - 定时器或软件 FSM 触发模式
    - 2 种中断（超过阈值和低于阈值中断）
    - 睡眠模式唤醒源

这些通道的具体位置如下：

.. list-table::
    :header-rows: 1
    :widths: 50 50
    :align: center

    * - 通道
      - {IDF_TARGET_NAME}
    * - **通道 0**
      - **GPIO 0（有效）**
    * - **通道 1**
      - **GPIO 1**
    * - **通道 2**
      - **GPIO 2**
    * - **通道 3**
      - **GPIO 3**
    * - **通道 4**
      - **GPIO 4**
    * - **通道 5**
      - **GPIO 5**
    * - **通道 6**
      - **GPIO 6**
    * - **通道 7**
      - **GPIO 7**
    * - **通道 8**
      - **GPIO 8**
    * - **通道 9**
      - **GPIO 9**


术语
-----------

触摸元件库的有关术语如下：

.. list-table::
    :header-rows: 1
    :widths: 50 50
    :align: center

    * - 术语
      - 定义
    * - **触摸传感器 (touch sensor)**
      - 芯片内部的触摸传感器外设
    * - **触摸通道 (touch channel)**
      - 触摸传感器外设内的触摸通道
    * - **触摸焊盘 (touch pad)**
      - 外部物理触摸焊盘，通常位于 PCB 内部
    * - **降噪通道 (de-noise channel)**
      - 内部降噪通道，始终为通道 0 且已预留
    * - **屏蔽传感器 (shield sensor)**
      - 防水传感器之一，用于小面积的水滴检测，并补偿水滴对读数的影响
    * - **防护传感器 (guard sensor)**
      - 防水传感器之一，用于大面积的涉水检测，并临时禁用触摸传感器
    * - **屏蔽通道 (shield channel)**
      - 防水屏蔽传感器连接的通道，始终为通道 14
    * - **防护通道 (guard channel)**
      - 防水防护传感器连接的通道
    * - **屏蔽焊盘 (shield pad)**
      - 外部物理屏蔽焊盘，通常是网格状，与防水传感器相连
    * - **防护焊盘 (guard pad)**
      - 外部物理防护焊盘，通常是环状，与防护传感器相连

.. figure:: /../_static/touch_element/te_component.svg
    :scale: 100 %
    :align: center

    触摸传感器应用程序系统组件


触摸传感器信号
^^^^^^^^^^^^^^^^^^^

触摸传感器可提供以下信号：

- 原始信号：从触摸传感器获取、未经滤波的信号。
- 平滑信号：原始信号通过内部硬件滤波器滤波后的信号。
- 基准信号：经过滤波的信号，已过滤极低频噪声。

以上信号均可通过触摸传感器驱动程序 API 获取。

.. figure:: /../_static/touch_element/te_signal.png
    :scale: 40 %
    :align: center

    触摸传感器信号


触摸传感器信号阈值
^^^^^^^^^^^^^^^^^^^^^^

触摸传感器阈值支持重新配置，可用于确定触摸传感器状态。当平滑信号和基准信号间的差值大于阈值，即 ``（平滑信号 - 基准信号）> 信号阈值`` 时，触摸通道状态改变，并触发触摸中断。

.. figure:: /../_static/touch_element/te_threshold.svg
    :scale: 40 %
    :align: center

    触摸传感器信号阈值


灵敏度
^^^^^^^^^^^^^^^^

触摸传感器的一个重要性能参数，该值越大，表明触摸传感器越灵敏。可以通过以下公式计算：

.. math::

    Sensitivity = \frac{Signal_{press} - Signal_{release}}{Signal_{release}} = \frac{Signal_{delta}}{Signal_{benchmark}}


防水性能
^^^^^^^^^^^^^^^^^^

防水性能是触摸传感器的硬件功能，包括防护传感器和屏蔽传感器（始终连接到通道 14），可以抵御一定程度的水滴影响，并检测水流。


触摸按键
^^^^^^^^^^^^^^^^^^

触摸按键占用触摸传感器的一个通道，外观如下图所示：


.. figure:: /../_static/touch_element/te_button.svg
    :scale: 100 %
    :align: center

    触摸按键


触摸滑条
^^^^^^^^^^^^^^^^^^

触摸滑条占用触摸传感器的多个通道（至少三个），占用的通道越多，滑条的采样分辨率和准确度越高。触摸滑条外观如下图所示：

.. figure:: /../_static/touch_element/te_slider.svg
    :scale: 100 %
    :align: center

    触摸滑条


触摸矩阵
^^^^^^^^^^^^^^^^^^

触摸矩阵按键占用触摸传感器的多个通道（至少 2 + 2 = 4 个通道），它支持通过较少通道获取更多按键。{IDF_TARGET_NAME} 最多支持 49 个按键。触摸矩阵按键外观如下图所示：

.. figure:: /../_static/touch_element/te_matrix.svg
    :scale: 100 %
    :align: center

    触摸矩阵


使用触摸元件库
--------------

使用触摸元件库时，请遵循以下初始化流程：

1. 调用 :cpp:func:`touch_element_install`，初始化触摸元件库。
2. 调用 :cpp:func:`touch_xxxx_install`，初始化触摸元件（按键、滑条等）。
3. 调用 :cpp:func:`touch_xxxx_create`，创建新元件实例。
4. 调用 :cpp:func:`touch_xxxx_subscribe_event`，订阅事件通知。
5. 调用 :cpp:func:`touch_xxxx_set_dispatch_method`，选择事件通知的传递方式。
6. 选择使用回调函数传递事件通知时，调用 :cpp:func:`touch_xxxx_set_callback`，设置事件处理函数。
7. 调用 :cpp:func:`touch_element_start`，启用触摸元件库。
8. 选择使用回调函数传递事件通知时，若事件发生，驱动核心会调用回调函数，你无需做任何处理；选择使用事件任务传递事件通知时，你需要创建一个事件任务，并调用 :cpp:func:`touch_element_message_receive`，循环获取信息。
9. （可选）如果要暂停触摸元件的实时系统，或因某种原因无法获取触摸元件信息，应调用 :cpp:func:`touch_element_stop`，暂停触摸元件系统，然后再次调用 :cpp:func:`touch_element_start` 恢复系统。

上述流程代码如下所示：

.. code-block:: c

    static touch_xxx_handle_t element_handle; //声明一个触摸元件句柄

    //定义订阅的事件处理函数
    void event_handler(touch_xxx_handle_t out_handle, touch_xxx_message_t out_message, void *arg)
    {
        //事件处理逻辑
    }

    void app_main()
    {
        //使用默认初始化器配置触摸元件库
        touch_elem_global_config_t global_config = TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG();
        touch_element_install(&global_config);

        //使用默认初始化器配置触摸元件
        touch_xxx_global_config_t elem_global_config = TOUCH_XXXX_GLOBAL_DEFAULT_CONFIG();
        touch_xxx_install(&elem_global_config);

        //创建新的实例
        touch_xxx_config_t element_config = {
            ...
            ...
        };
        touch_xxx_create(&element_config, &element_handle);

        //通过事件掩码订阅特定事件
        touch_xxx_subscribe_event(element_handle, TOUCH_ELEM_EVENT_ON_PRESS | TOUCH_ELEM_EVENT_ON_RELEASE, NULL);

        //选择使用回调函数传递事件通知
        touch_xxx_set_dispatch_method(element_handle, TOUCH_ELEM_DISP_CALLBACK);

        //注册回调函数
        touch_xxx_set_callback(element_handle, event_handler);

        //启用触摸元件库处理
        touch_element_start();
    }


初始化
^^^^^^^^^^^^^^

1. 要初始化触摸元件库，请调用 :cpp:func:`touch_element_install` 函数，并传递一个 :cpp:type:`touch_elem_global_config_t` 类型的实例作为参数，以配置触摸传感器外设和触摸元件库。默认初始化器位于 :cpp:func:`TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG` 中，此默认配置适用于多数常见应用场景。建议在充分了解触摸传感器外设前，不要更改默认配置，以免影响系统。

2. 要初始化特定的触摸元件，需要调用其构造函数 :cpp:func:`touch_xxxx_install`。在调用此构造函数前，所有触摸元件都不会工作，以节省内存。因此，若要设置所需元件，需要为每个要使用的触摸元件分别调用构造函数。


启动触摸元件实例
^^^^^^^^^^^^^^^^

1. 要创建新的触摸元件实例，请调用 :cpp:func:`touch_xxxx_create`，选择一个通道，并将其 `灵敏度`_ 传递给新的元件实例。

2. 要订阅事件通知，请调用 :cpp:func:`touch_xxxx_subscribe_event`。触摸元件库提供了多个事件，事件掩码存放在 :idf_file:`components/touch_element/include/touch_element/touch_element.h` 中。通过使用这些事件掩码，可以订阅单个特定事件，或将单个事件组合在一起，订阅多个事件。

3. 要配置传递事件通知的方式，请调用 :cpp:func:`touch_xxxx_subscribe_event`。触摸元件库提供了两种方式: :cpp:enumerator:`TOUCH_ELEM_DISP_EVENT` 和 :cpp:enumerator:`TOUCH_ELEM_DISP_CALLBACK`，支持以不同方式获取并处理触摸元件信息。

事件处理
^^^^^^^^^^^^^^^^^

如果配置的是 :cpp:enumerator:`TOUCH_ELEM_DISP_EVENT`，需要启用一个事件处理任务获取触摸元件信息。调用 :cpp:func:`touch_element_message_receive` 可以获取所有元件的原始信息，随后通过调用相应的信息解码器 :cpp:func:`touch_xxxx_get_message`，提取特定类型元件的信息，获取有关触摸操作的详细数据。如果配置的是 :cpp:enumerator:`TOUCH_ELEM_DISP_CALLBACK`，在触摸元件开始工作之前，需要调用 :cpp:func:`touch_xxxx_set_callback`，传递一个事件处理函数，有关触摸操作的详细数据都会传递到该事件处理函数。

.. warning::

    由于事件处理函数在元件库的核心运行，即在 esp-timer 回调中运行，请避免执行可能导致阻塞或延迟的操作，如调用 :cpp:func:`vTaskDelay`。


事件处理过程代码如下所示：

.. code-block:: c

    /* ---------------------------------------------- TOUCH_ELEM_DISP_EVENT ----------------------------------------------- */
    void element_handler_task(void *arg)
    {
        touch_elem_message_t element_message;
        while(1) {
            if (touch_element_message_receive(&element_message, Timeout) == ESP_OK) {
                const touch_xxxx_message_t *extracted_message = touch_xxxx_get_message(&element_message); //信息解码
                ... //事件处理逻辑
            }
        }
    }
    void app_main()
    {
        ...

        touch_xxxx_set_dispatch_method(element_handle, TOUCH_ELEM_DISP_EVENT);  //设置以 TOUCH_ELEM_DISP_EVENT 传递事件通知
        xTaskCreate(&element_handler_task, "element_handler_task", 2048, NULL, 5, NULL);  //创建一个事件处理任务

        ...
    }
    /* -------------------------------------------------------------------------------------------------------------- */

    ...
    /* ---------------------------------------------- TOUCH_ELEM_DISP_CALLBACK ----------------------------------------------- */
    void element_handler(touch_xxxx_handle_t out_handle, touch_xxxx_message_t out_message, void *arg)
    {
        //事件处理逻辑
    }

    void app_main()
    {
        ...

        touch_xxxx_set_dispatch_method(element_handle, TOUCH_ELEM_DISP_CALLBACK);  //设置以 ``TOUCH_ELEM_DISP_CALLBACK`` 传递事件通知
        touch_xxxx_set_callback(element_handle, element_handler);  //注册事件处理函数

        ...
    }
    /* -------------------------------------------------------------------------------------------------------------- */


使用防水功能
^^^^^^^^^^^^^^^^

1. 一旦初始化触摸元件的防水功能，防水屏蔽传感器会始终处于开启状态。防水屏蔽传感器为可选项，如果不需要，可以通过配置结构体，将 ``TOUCH_WATERPROOF_GUARD_NOUSE`` 传递给 :cpp:func:`touch_element_waterproof_install`。

2. 要关联触摸元件与防护传感器，请调用 :cpp:func:`touch_element_waterproof_add`，将触摸元件句柄添加到触摸元件防水功能的掩码列表中。触摸元件与防护传感器关联后，水流触发防护传感器时会关闭触摸元件，为其提供保护。

查看使用触摸元件防水功能的示例代码，请前往 ESP-IDF 示例的 :example:`peripherals/touch_sensor/touch_element/touch_element_waterproof` 目录。

配置防水功能的代码如下所示：

.. code-block:: c

    void app_main()
    {
        ...

        touch_xxxx_install();                 //初始化实例（按键、滑条等）
        touch_xxxx_create(&element_handle);  //创建新的触摸元件

        ...

        touch_element_waterproof_install();              //初始化触摸元件防水功能
        touch_element_waterproof_add(element_handle);   //关联触摸元件与防护传感器

        ...
    }

从 Light/Deep-sleep 模式唤醒
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

仅触摸按键可配置为唤醒源。

使用触摸传感器，可以唤醒从 Light-sleep 或 Deep-sleep 模式中唤醒芯片。在 Light-sleep 模式下，任何已安装的触摸按键都可以唤醒芯片。但在 Deep-sleep 模式下，只有睡眠按键可以唤醒芯片，触摸传感器还会立即进行校准。如果手指没有及时离开，可能导致校准参考值出错。尽管在手指离开后，校准参考值会自行恢复，不会影响驱动逻辑，但如果你不想在从 Deep-sleep 模式唤醒时看到错误的校准参考值，可以调用 :cpp:func:`touch_element_sleep_enable_wakeup_calibration`，禁用唤醒校准功能。

查看使用触摸元件唤醒芯片的示例代码，请前往 ESP-IDF 示例的 :example:`system/light_sleep` 目录。

.. code-block:: c

    void app_main()
    {
        ...
        touch_element_install();
        touch_button_install();                 //初始化触摸按键
        touch_button_create(&element_handle);  //创建新的触摸元件

        ...

        // ESP_ERROR_CHECK(touch_element_enable_light_sleep(&sleep_config));
        ESP_ERROR_CHECK(touch_element_enable_deep_sleep(button_handle[0], &sleep_config));
        // ESP_ERROR_CHECK(touch_element_sleep_enable_wakeup_calibration(button_handle[0], false)); //（可选）禁用唤醒校准，防止基准值更新为错误值

        touch_element_start();

        ...
    }

应用示例
-------------------

查看使用触摸元件库的示例代码，请前往 ESP-IDF 示例的 :example:`peripherals/touch_sensor/touch_element` 目录。


API 参考 - 触摸元件核心
----------------------------------

.. include-build-file:: inc/touch_element.inc


API 参考 - 触摸按键
----------------------------------

.. include-build-file:: inc/touch_button.inc


API 参考 - 触摸滑条
----------------------------------

.. include-build-file:: inc/touch_slider.inc


API 参考 - 触摸矩阵
----------------------------------

.. include-build-file:: inc/touch_matrix.inc
