# 事件任务矩阵 (ETM)

[English]

## 简介

如果外设 X 需要向外设 Y 发起事件通知，一般只能通过 CPU 中断实现。在此过程中，CPU 会代表外设 X，给外设 Y 发送通知。然而，在对时间敏感的应用程序中，CPU 中断引发的延迟不容忽视。

通过引入事件任务矩阵 (ETM) 模块，部分外设可以直接通过预先设置的连接关系，将事件通知发送给其他外设，无需 CPU 中断介入。由此，外设实现精确、低延迟同步，并减轻 CPU 负担。

ETM 通道概述

ETM 模块具有多个通道，这些通道支持用户根据需要进行配置，连接特定 事件 与 任务。激活某一事件时，ETM 将自动触发相应任务。

支持 ETM 功能的外设向 ETM 提供其事件和任务的连接关系。ETM 通道可以连接任意事件和任务，事件和任务甚至可以来自于同一个外设。然而，对于一个 ETM 通道，一次只能将一个事件与一个任务连接（即 1 对 1 关系）。如果要使用不同事件触发同一任务，则需申请多条 ETM 通道。

使用 ETM 通常可实现以下功能：

- 当定时器报警事件发生时，翻转 GPIO 电平

- 当在 GPIO 上监测到脉冲边沿时，启动 ADC 转换

## 功能概述

下文将分节概述 ETM 的功能，并介绍配置和使用 ETM 模块的基本步骤：

- ETM 通道分配 - 介绍如何安装和卸载 ETM 通道。

- ETM 事件 - 介绍如何分配新的 ETM 事件句柄，以及如何从不同外设获取现有句柄。

- ETM 任务 - 介绍如何分配新的 ETM 任务句柄，以及如何从不同外设获取现有句柄。

- ETM 通道控制 - 介绍常见的 ETM 通道控制函数。

- 电源管理 - 介绍了驱动针对功耗管理提供的选项和策略。

- 线程安全 - 列出了驱动程序中始终线程安全的 API。

- Kconfig 选项 - 列出了 ETM 支持的 Kconfig 选项，这些选项对驱动程序的行为会产生不同影响。

### ETM 通道分配

在 ESP32-H2 中，存在许多相同的 ETM 通道 [1]，各通道在软件中由 `esp_etm_channel_handle_t` 表示。可用硬件资源汇集在资源池内，由 ETM 核心驱动程序管理，无需手动管理通道的分配和释放。ETM 核心驱动程序会在调用 `esp_etm_new_channel()` 时自动分配通道，在调用 `esp_etm_del_channel()` 时删除通道。分配通道的要求通过 `esp_etm_channel_config_t` 配置。

在删除 ETM 通道前，请调用 `esp_etm_channel_disable()` 禁用要删除的通道，或确保该通道尚未由 `esp_etm_channel_enable()` 启用，再继续删除操作。

### ETM 事件

ETM 事件对其事件源进行了抽象，屏蔽了具体事件源的细节，并在软件中表示为 `esp_etm_event_handle_t`，使应用程序可以更便捷地处理不同类型的事件。ETM 事件可以由各种外设产生，因此获取事件句柄的方法因外设而异。当不再需要某个事件时，请调用 `esp_etm_channel_connect()`，并传递一个 `NULL` 事件句柄，断开与事件的连接，随后调用 `esp_etm_del_event()`，释放事件资源。

#### GPIO 事件

GPIO 边沿 事件是最常见的事件类型，任何 GPIO 管脚均可触发这类事件。要创建 GPIO 事件句柄，请调用 `gpio_new_etm_event()`，并使用 `gpio_etm_event_config_t` 提供的配置信息：

- `gpio_etm_event_config_t::edge` 或 `gpio_etm_event_config_t::edges` 决定触发事件的边沿类型，支持的边沿类型已在 `gpio_etm_event_edge_t` 中列出。

接下来，请调用 `gpio_etm_event_bind_gpio()` 函数，连接 GPIO ETM 事件句柄与 GPIO 管脚。注意，要设置 GPIO 管脚，只能使用由 `gpio_new_etm_event()` 函数创建的 ETM 事件句柄。对于其他类型的 ETM 事件，调用此函数，将返回 `ESP_ERR_INVALID_ARG` 错误。该函数也无法完成 GPIO 的初始化，在使用 GPIO ETM 事件之前，仍需调用 `gpio_config()` 函数，设置 GPIO 管脚的属性，如方向、高/低电平模式等。

#### 其他外设事件

- 调用 `esp_systick_new_etm_alarm_event()` 可以从 RTOS Systick 获取 ETM 事件句柄，每个 CPU 核心可以获取一个事件句柄。

- 要了解如何从 esp_timer 获取 ETM 事件句柄，请参阅 ESP 定时器（高分辨率定时器）。

- 要了解如何从 GPTimer 获取 ETM 事件句柄，请参阅 GPTimer 的 ETM 事件与任务。

- 要了解如何从 async memcpy 获取 ETM 事件句柄，请参阅 异步内存复制。

- 要了解如何从 MCPWM 中获取 ETM 事件句柄，请参阅 电机控制脉宽调制器 (MCPWM)。

- 要了解如何从温度传感器获取 ETM 事件句柄，请参阅 温度传感器。

- 要了解如何从 I2S 获取 ETM 事件句柄，请参阅 I2S。

- 要了解如何从 LEDC 获取 ETM 事件句柄，请参阅 LED PWM 控制器。

### ETM 任务

ETM 任务对其操作进行了抽象，在软件中表示为 `esp_etm_task_handle_t`，使任务得以用同一方式管理和表示。ETM 任务可以分配给不同外设，因此获取任务句柄的方式因外设而异。当不再需要某个任务时，请调用 `esp_etm_channel_connect()`，并传递一个 `NULL` 事件句柄，断开与任务的连接，随后调用 `esp_etm_del_event()`，释放任务资源。

#### GPIO 任务

GPIO 任务是最常见的任务类型。一个 GPIO 可以采取一个或多个 GPIO 操作，而一个 GPIO 任务也可以同时管理多个 GPIO 管脚。当 ETM 通道激活任务时，任务可以同时设置管理的所有 GPIO 引脚，使其设置/清除/切换状态。要创建 GPIO 任务句柄，请调用 `gpio_new_etm_task()`，并使用 `gpio_etm_task_config_t` 提供的配置信息：

- `gpio_etm_task_config_t::action` 或 `gpio_etm_task_config_t::actions` 决定 ETM 任务将采取的 GPIO 操作，支持的操作类型在 `gpio_etm_task_action_t` 中列出。如果一个 GPIO 需要采取多个 GPIO 操作，这些操作任务的创建必须通过配置 `gpio_etm_task_config_t::actions` 的数组并在一次 `gpio_new_etm_task()` 调用中一并完成。

接下来，需要连接 GPIO ETM 任务句柄与 GPIO 管脚。为此，请调用 `gpio_etm_task_add_gpio()` 函数。如果需要任务句柄管理更多的 GPIO 管脚，可以重复调用以上函数，注意，要设置 GPIO 管脚，只能使用由 `gpio_new_etm_task()` 函数创建的 ETM 任务句柄。对于其他类型的 ETM 任务，调用此函数，将返回 `ESP_ERR_INVALID_ARG` 错误。该函数也无法完成 GPIO 的初始化，在使用 GPIO ETM 任务之前，仍需调用 `gpio_config()` 函数，设置 GPIO 管脚的属性，如方向、高/低电平模式等。

要删除 GPIO ETM 任务，请调用 `esp_etm_del_task()`。在此之前，请确保已经调用过 `gpio_etm_task_rm_gpio()`，删除了所有先前添加的 GPIO 管脚。

#### 其他外设任务

- 要了解如何从 GPTimer 获取 ETM 任务句柄，请参阅 GPTimer 的 ETM 事件与任务。

- 要了解如何从温度传感器获取 ETM 任务句柄，请参阅 温度传感器。

- 要了解如何从 I2S 获取 ETM 任务句柄，请参阅 I2S。

- 要了解如何从 LEDC 获取 ETM 任务句柄，请参阅 LED PWM 控制器。

### ETM 通道控制

#### 映射事件与任务

在调用 `esp_etm_channel_connect()` 将它们连接到同一个 ETM 通道之前，ETM 事件与 ETM 任务之间没有任何映射关系。注意，使用 `NULL` 任务/事件句柄调用该函数时，会将通道与任何任务或事件解除映射。此函数可以在通道启用之前或之后调用，但在运行时调用此函数更改映射关系存在一定风险，因为此时通道可能正处于周期的中间阶段，新的映射可能无法立即生效。

#### 启用及禁用通道

调用 `esp_etm_channel_enable()` 启用 ETM 通道，调用 `esp_etm_channel_disable()` 禁用 ETM 通道。

#### ETM 通道分析

要检查是否为 ETM 通道设置了正确的事件和任务，可以调用 `esp_etm_dump()`，输出所有工作中的 ETM 通道及其关联的事件和任务。输出格式如下：

    ===========ETM Dump Start==========
    channel 0: event 48 ==> task 17
    channel 1: event 48 ==> task 90
    channel 2: event 48 ==> task 94
    ===========ETM Dump End============

以上输出信息打印的数字 ID 在 `soc/soc_etm_source.h` 文件中定义。

### 电源管理

当启用电源管理时，即 CONFIG_PM_ENABLE 打开的时候，系统可能会调整或禁用时钟源，并在进入睡眠前关闭 ETM 外设依赖的电源。这会导致事件和任务之间的连接信息被丢失，ETM 通道在唤醒后无法正常工作。因此，默认情况下，驱动程序会获取电源管理锁，以禁止系统关闭 ETM 外设。

如果你想节省更多电量，可以将 `esp_etm_channel_config_t::etm_chan_flags::allow_pd` 设置为 `true`。ETM 寄存器将在睡眠前备份，并在唤醒后恢复。请注意，启用此选项会增加内存消耗，用于保存寄存器上下文。

### 线程安全

ETM 核心驱动程序具备线程安全性。

- 工厂函数（如 `esp_etm_new_channel()` `gpio_new_etm_task()` 以及其他 `*_new_etm_*` 创建函数）均为线程安全，可在不同 RTOS 任务中并发调用，无需担心资源冲突。

- 通道控制相关 API（如 `esp_etm_channel_connect()`， `esp_etm_channel_enable()`， `esp_etm_channel_disable()`， `esp_etm_del_channel()`）同样支持线程安全。针对不同通道的并发操作是安全的；若对同一通道进行并发操作，驱动会自动进行内部序列化。如果操作与当前通道状态不兼容，则会返回 `ESP_ERR_INVALID_STATE` 错误。

- 所有 ETM API 均不允许在中断服务程序（ISR）环境下调用。

### Kconfig 选项

- CONFIG_ETM_ENABLE_DEBUG_LOG 用于启用调试日志输出，启用此选项将增加固件的二进制文件大小。

## API 参考

### Header File

- components/esp_hw_support/etm/include/esp_etm.h

- This header file can be included with:

  >     #include "esp_etm.h"

### Functions

esp_err_t esp_etm_new_channel(const esp_etm_channel_config_t *config, esp_etm_channel_handle_t *ret_chan)

  Allocate an ETM channel.

  备注

  The channel can later be freed by `esp_etm_del_channel`

  参数:

    - config -- [in] ETM channel configuration

    - ret_chan -- [out] Returned ETM channel handle

  返回:

    - ESP_OK: Allocate ETM channel successfully

    - ESP_ERR_INVALID_ARG: Allocate ETM channel failed because of invalid argument

    - ESP_ERR_NO_MEM: Allocate ETM channel failed because of out of memory

    - ESP_ERR_NOT_FOUND: Allocate ETM channel failed because all channels are used up and no more free one

    - ESP_FAIL: Allocate ETM channel failed because of other reasons

esp_err_t esp_etm_del_channel(esp_etm_channel_handle_t chan)

  Delete an ETM channel.

  参数:

    chan -- [in] ETM channel handle that created by `esp_etm_new_channel`

  返回:

    - ESP_OK: Delete ETM channel successfully

    - ESP_ERR_INVALID_ARG: Delete ETM channel failed because of invalid argument

    - ESP_FAIL: Delete ETM channel failed because of other reasons

esp_err_t esp_etm_channel_enable(esp_etm_channel_handle_t chan)

  Enable ETM channel.

  备注

  This function will transit the channel state from init to enable.

  参数:

    chan -- [in] ETM channel handle that created by `esp_etm_new_channel`

  返回:

    - ESP_OK: Enable ETM channel successfully

    - ESP_ERR_INVALID_ARG: Enable ETM channel failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Enable ETM channel failed because the channel has been enabled already

    - ESP_FAIL: Enable ETM channel failed because of other reasons

esp_err_t esp_etm_channel_disable(esp_etm_channel_handle_t chan)

  Disable ETM channel.

  备注

  This function will transit the channel state from enable to init.

  参数:

    chan -- [in] ETM channel handle that created by `esp_etm_new_channel`

  返回:

    - ESP_OK: Disable ETM channel successfully

    - ESP_ERR_INVALID_ARG: Disable ETM channel failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Disable ETM channel failed because the channel is not enabled yet

    - ESP_FAIL: Disable ETM channel failed because of other reasons

esp_err_t esp_etm_channel_connect(esp_etm_channel_handle_t chan, esp_etm_event_handle_t event, esp_etm_task_handle_t task)

  Connect an ETM event to an ETM task via a previously allocated ETM channel.

  备注

  Setting the ETM event/task handle to NULL means to disconnect the channel from any event/task

  参数:

    - chan -- [in] ETM channel handle that created by `esp_etm_new_channel`

    - event -- [in] ETM event handle obtained from a driver/peripheral, e.g. `xxx_new_etm_event`

    - task -- [in] ETM task handle obtained from a driver/peripheral, e.g. `xxx_new_etm_task`

  返回:

    - ESP_OK: Connect ETM event and task to the channel successfully

    - ESP_ERR_INVALID_ARG: Connect ETM event and task to the channel failed because of invalid argument

    - ESP_FAIL: Connect ETM event and task to the channel failed because of other reasons

esp_err_t esp_etm_del_event(esp_etm_event_handle_t event)

  Delete ETM event.

  备注

  Although the ETM event comes from various peripherals, we provide the same user API to delete the event handle seamlessly.

  参数:

    event -- [in] ETM event handle obtained from a driver/peripheral, e.g. `xxx_new_etm_event`

  返回:

    - ESP_OK: Delete ETM event successfully

    - ESP_ERR_INVALID_ARG: Delete ETM event failed because of invalid argument

    - ESP_FAIL: Delete ETM event failed because of other reasons

esp_err_t esp_etm_del_task(esp_etm_task_handle_t task)

  Delete ETM task.

  备注

  Although the ETM task comes from various peripherals, we provide the same user API to delete the task handle seamlessly.

  参数:

    task -- [in] ETM task handle obtained from a driver/peripheral, e.g. `xxx_new_etm_task`

  返回:

    - ESP_OK: Delete ETM task successfully

    - ESP_ERR_INVALID_ARG: Delete ETM task failed because of invalid argument

    - ESP_FAIL: Delete ETM task failed because of other reasons

esp_err_t esp_etm_dump(FILE *out_stream)

  Dump ETM channel usages to the given IO stream.

  参数:

    out_stream -- [in] IO stream (e.g. stdout)

  返回:

    - ESP_OK: Dump ETM channel usages successfully

    - ESP_ERR_INVALID_ARG: Dump ETM channel usages failed because of invalid argument

    - ESP_FAIL: Dump ETM channel usages failed because of other reasons

### Structures

struct esp_etm_channel_config_t

  ETM channel configuration.

  Public Members

  struct esp_etm_channel_config_t::etm_chan_flags flags

    ETM channel flags

  struct etm_chan_flags

    Extra configuration flags for ETM channel.

    Public Members

    uint32_t allow_pd

      If set, driver allows the power domain to be powered off when system enters sleep mode. This can save power, but at the expense of more RAM being consumed to save register context.

### Type Definitions

typedef struct esp_etm_channel_t *esp_etm_channel_handle_t

  ETM channel handle.

typedef struct esp_etm_event_t *esp_etm_event_handle_t

  ETM event handle.

typedef struct esp_etm_task_t *esp_etm_task_handle_t

  ETM task handle.

### Header File

- components/esp_driver_gpio/include/driver/gpio_etm.h

- This header file can be included with:

  >     #include "driver/gpio_etm.h"

- This header file is a part of the API provided by the `esp_driver_gpio` component. To declare that your component depends on `esp_driver_gpio`, add the following to your CMakeLists.txt:

  >     REQUIRES esp_driver_gpio

  > or

  >     PRIV_REQUIRES esp_driver_gpio

### Functions

esp_err_t gpio_new_etm_event(const gpio_etm_event_config_t *config, esp_etm_event_handle_t *ret_event, ...)

  Create an ETM event object for the GPIO peripheral.

  备注

  The created ETM event object can be deleted later by calling `esp_etm_del_event`

  备注

  The newly created ETM event object is not bind to any GPIO, you need to call `gpio_etm_event_bind_gpio` to bind the wanted GPIO

  备注

  Every success call to this function will acquire a free GPIO ETM event channel

  参数:

    - config -- [in] GPIO ETM event configuration

    - ret_event -- [out] Returned ETM event handle

    - ... -- [out] Other returned ETM event handles if any (the order of the returned event handles is aligned with the array order in field `edges` in `gpio_etm_event_config_t`)

  返回:

    - ESP_OK: Create ETM event successfully

    - ESP_ERR_INVALID_ARG: Create ETM event failed because of invalid argument

    - ESP_ERR_NO_MEM: Create ETM event failed because of out of memory

    - ESP_ERR_NOT_FOUND: Create ETM event failed because all events are used up and no more free one

    - ESP_FAIL: Create ETM event failed because of other reasons

esp_err_t gpio_etm_event_bind_gpio(esp_etm_event_handle_t event, int gpio_num)

  Bind the GPIO with the ETM event.

  备注

  Calling this function multiple times with different GPIO number can override the previous setting immediately.

  备注

  Only GPIO ETM object can call this function

  参数:

    - event -- [in] ETM event handle that created by `gpio_new_etm_event`

    - gpio_num -- [in] GPIO number that can trigger the ETM event

  返回:

    - ESP_OK: Set the GPIO for ETM event successfully

    - ESP_ERR_INVALID_ARG: Set the GPIO for ETM event failed because of invalid argument, e.g. GPIO is not input capable, ETM event is not of GPIO type

    - ESP_FAIL: Set the GPIO for ETM event failed because of other reasons

esp_err_t gpio_new_etm_task(const gpio_etm_task_config_t *config, esp_etm_task_handle_t *ret_task, ...)

  Create an ETM task object for the GPIO peripheral.

  备注

  The created ETM task object can be deleted later by calling `esp_etm_del_task`

  备注

  The GPIO ETM task works like a container, a newly created ETM task object doesn't have GPIO members to be managed. You need to call `gpio_etm_task_add_gpio` to put one or more GPIOs to the container.

  备注

  Every success call to this function will acquire a free GPIO ETM task channel

  参数:

    - config -- [in] GPIO ETM task configuration

    - ret_task -- [out] Returned ETM task handle

    - ... -- [out] Other returned ETM task handles if any (the order of the returned task handles is aligned with the array order in field `actions` in `gpio_etm_task_config_t`)

  返回:

    - ESP_OK: Create ETM task successfully

    - ESP_ERR_INVALID_ARG: Create ETM task failed because of invalid argument

    - ESP_ERR_NO_MEM: Create ETM task failed because of out of memory

    - ESP_ERR_NOT_FOUND: Create ETM task failed because all tasks are used up and no more free one

    - ESP_FAIL: Create ETM task failed because of other reasons

esp_err_t gpio_etm_task_add_gpio(esp_etm_task_handle_t task, int gpio_num)

  Add GPIO to the ETM task.

  备注

  You can call this function multiple times to add more GPIOs

  备注

  Only GPIO ETM object can call this function

  参数:

    - task -- [in] ETM task handle that created by `gpio_new_etm_task`

    - gpio_num -- [in] GPIO number that can be controlled by the ETM task

  返回:

    - ESP_OK: Add GPIO to the ETM task successfully

    - ESP_ERR_INVALID_ARG: Add GPIO to the ETM task failed because of invalid argument, e.g. GPIO is not output capable, ETM task is not of GPIO type

    - ESP_ERR_INVALID_STATE: Add GPIO to the ETM task failed because the GPIO is used by other ETM task already

    - ESP_FAIL: Add GPIO to the ETM task failed because of other reasons

esp_err_t gpio_etm_task_rm_gpio(esp_etm_task_handle_t task, int gpio_num)

  Remove the GPIO from the ETM task.

  备注

  Before deleting the ETM task, you need to remove all the GPIOs from the ETM task by this function

  备注

  Only GPIO ETM object can call this function

  参数:

    - task -- [in] ETM task handle that created by `gpio_new_etm_task`

    - gpio_num -- [in] GPIO number that to be remove from the ETM task

  返回:

    - ESP_OK: Remove the GPIO from the ETM task successfully

    - ESP_ERR_INVALID_ARG: Remove the GPIO from the ETM task failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Remove the GPIO from the ETM task failed because the GPIO is not controlled by this ETM task

    - ESP_FAIL: Remove the GPIO from the ETM task failed because of other reasons

### Structures

struct gpio_etm_event_config_t

  GPIO ETM event configuration.

  If more than one kind of ETM edge event want to be triggered on the same GPIO pin, you can configure them together. It helps to save GPIO ETM event channel resources for other GPIOs.

  Public Members

  gpio_etm_event_edge_t edge

    Which kind of edge can trigger the ETM event module

  gpio_etm_event_edge_t edges[GPIO_ETM_EVENT_EDGE_TYPES]

    Array of kinds of edges to trigger the ETM event module on the same GPIO

struct gpio_etm_task_config_t

  GPIO ETM task configuration.

  If multiple actions wants to be added to the same GPIO pin, you have to configure all the GPIO ETM tasks together.

  Public Members

  gpio_etm_task_action_t action

    Action to take by the ETM task module

  gpio_etm_task_action_t actions[GPIO_ETM_TASK_ACTION_TYPES]

    Array of actions to take by the ETM task module on the same GPIO

### Macros

GPIO_ETM_EVENT_EDGE_TYPES

  GPIO ETM edge events are POS/NEG/ANY

GPIO_ETM_TASK_ACTION_TYPES

  GPIO ETM action tasks are SET/CLEAR/TOGGLE

### Enumerations

enum gpio_etm_event_edge_t

  GPIO edges that can be used as ETM event.

  Values:

  enumerator GPIO_ETM_EVENT_EDGE_POS

    A rising edge on the GPIO will generate an ETM event signal

  enumerator GPIO_ETM_EVENT_EDGE_NEG

    A falling edge on the GPIO will generate an ETM event signal

  enumerator GPIO_ETM_EVENT_EDGE_ANY

    Any edge on the GPIO can generate an ETM event signal

enum gpio_etm_task_action_t

  GPIO actions that can be taken by the ETM task.

  Values:

  enumerator GPIO_ETM_TASK_ACTION_SET

    Set the GPIO level to high

  enumerator GPIO_ETM_TASK_ACTION_CLR

    Clear the GPIO level to low

  enumerator GPIO_ETM_TASK_ACTION_TOG

    Toggle the GPIO level

### Header File

- components/esp_system/include/esp_systick_etm.h

- This header file can be included with:

  >     #include "esp_systick_etm.h"

### Functions

esp_err_t esp_systick_new_etm_alarm_event(int core_id, esp_etm_event_handle_t *out_event)

  Get the ETM event handle of systick hardware's alarm/heartbeat event.

  备注

  The created ETM event object can be deleted later by calling `esp_etm_del_event`

  参数:

    - core_id -- [in] CPU core ID

    - out_event -- [out] Returned ETM event handle

  返回:

    - ESP_OK Success

    - ESP_ERR_INVALID_ARG Parameter error

[1]

不同 ESP 芯片系列的 ETM 通道数量可能不同。要了解更多详情，请参阅 ESP32-H2 技术参考手册 > 事件任务矩阵 (ETM) [PDF]。驱动程序对通道申请数量不做限制，但当硬件资源用尽时，驱动程序将返回错误。因此，每次进行通道分配（即调用 `esp_etm_new_channel()`）时，请注意检查返回值。

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。