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

: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();

        ...
    }

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

- :example:`peripherals/touch_sensor/touch_element/touch_element_waterproof` 演示了如何使用触摸元件库来设置带有防水保护的电容式触摸传感器。
- :example:`peripherals/touch_sensor/touch_element/touch_slider` 演示了如何使用触摸元件库来设置和操作触摸滑条。
- :example:`peripherals/touch_sensor/touch_element/touch_elements_combination` 演示了如何使用触摸元件库来设置多种类型的触摸元件，并在一个任务中处理所有的事件消息。
- :example:`peripherals/touch_sensor/touch_element/touch_matrix` 演示了如何通过触摸元件库，在 {IDF_TARGET_NAME} 开发板上使用电容式触摸传感器来构建触摸矩阵。
- :example:`peripherals/touch_sensor/touch_element/touch_button` 演示了如何通过触摸元件库，在 {IDF_TARGET_NAME} 开发板上设置和使用触摸按键。


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
