feat(ana_cmpr): add ETM periodic scan example

This commit is contained in:
morris
2026-04-25 11:57:43 +08:00
parent 47863cb651
commit 65f4707d2b
12 changed files with 391 additions and 2 deletions

View File

@@ -222,7 +222,10 @@ Kconfig Options
Application Example
-------------------
* :example:`peripherals/analog_comparator` shows the basic usage of the analog comparator, and other potential usages like hysteresis comparator and SPWM generator.
.. list::
:SOC_ANA_CMPR_SUPPORT_AUTO_SCAN: - :example:`peripherals/analog_comparator/auto_scan` shows auto scan based threshold detection with internal or external reference. After enabling the comparator, hardware scans continuously and updates output in real time, while the example demonstrates interrupt-based or ETM-based monitor GPIO control depending on target capabilities.
:SOC_ANA_CMPR_SUPPORT_ETM_SCAN: - :example:`peripherals/analog_comparator/etm_periodic_scan` shows how to use GPTimer and ETM to trigger periodic comparator scans and drive a monitor GPIO from comparator crossing events.
API Reference
-------------

View File

@@ -222,7 +222,10 @@ Kconfig 选项
应用示例
--------
* :example:`peripherals/analog_comparator` 展示了模拟比较器的基本用法以及其他用途(如迟滞比较器和 SPWM 发生器)。
.. list::
:SOC_ANA_CMPR_SUPPORT_AUTO_SCAN: - :example:`peripherals/analog_comparator/auto_scan` 展示了基于自动扫描功能的阈值检测(支持内部参考或外部参考)。比较器在使能后会持续扫描并实时更新输出,示例根据目标能力演示了基于中断或 ETM 的监控 GPIO 控制。
:SOC_ANA_CMPR_SUPPORT_ETM_SCAN: - :example:`peripherals/analog_comparator/etm_periodic_scan` 展示了如何使用 GPTimer 和 ETM 周期性触发比较器扫描,并通过比较器跨越事件驱动监控 GPIO。
API 参考
--------

View File

@@ -37,6 +37,16 @@ examples/peripherals/analog_comparator/auto_scan:
- esp_hal_ana_cmpr
- soc
examples/peripherals/analog_comparator/etm_periodic_scan:
disable:
- if: SOC_ANA_CMPR_SUPPORT_ETM_SCAN != 1
depends_components:
- esp_driver_gpio
- esp_driver_ana_cmpr
- esp_driver_gptimer
- esp_hal_ana_cmpr
- soc
examples/peripherals/bitscrambler:
disable:
- if: SOC_BITSCRAMBLER_SUPPORTED != 1

View File

@@ -0,0 +1,11 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(ana_cmpr_etm_periodic_scan)

View File

@@ -0,0 +1,114 @@
| Supported Targets | ESP32-S31 |
| ----------------- | --------- |
# Analog Comparator ETM Periodic Scan Example
(See the README.md file in the upper level `examples` directory for more information about examples.)
This example shows how to use a GPTimer periodic ETM event to trigger the analog comparator scan task. The analog comparator uses the internal 50% VDD reference, and the comparator positive and negative crossing events set or clear a monitor GPIO through ETM. With an external sine wave connected to the source channel, the monitor GPIO becomes a square wave representation of the sampled input.
## Realization
This example builds the following ETM chain:
- GPTimer alarm event -> GPTimer enable-alarm task
- GPTimer alarm event -> Analog comparator start task
- Analog comparator positive cross event -> GPIO set task
- Analog comparator negative cross event -> GPIO clear task
The steady-state signal path runs without CPU intervention. The CPU is only used during one-time initialization.
## How to Use Example
### Hardware Requirement
* A development board with a supported Espressif SOC chip (see `Supported Targets` table above)
* A USB cable for power supply and programming
* A signal generator for generating the source sine wave
* An oscilloscope or logic analyzer to observe the source input and monitor GPIO
### Example Connection
The example uses a configurable comparator source input GPIO. The shipped default value matches the comparator pad0 GPIO for each supported target, and the example logs the actual source GPIO number at startup.
```
+--------------+ +--------------+
| ESP Board | | Signal Gen |
| | source signal | |
+----+GPIO Src In|<----+----------+OUT |
| | | | | |
| | GND+-----+----+-----+GND |
| | | | | | |
| +--------------+ | | +--------------+
| | |
| +--------------+ | |
| | Oscilloscope | | |
| | | | |
+--->|Probe1 Probe2|<----+ |
| | |
| GND+----------+
| |
+--------------+
```
Probe the source sine wave on the comparator source GPIO and probe the monitor GPIO at the same time.
### Configure the Project
Open the project configuration menu:
```bash
idf.py menuconfig
```
Under `Example Configuration`, you can configure:
- `Source GPIO number`
- `Monitor GPIO number`
- `Comparator scan period (us)`
The comparator reference voltage is fixed to the internal 50% VDD reference in this example.
The shipped default source GPIO value matches comparator pad0 on each supported target.
### Build and Flash
Build the project and flash it to the board, then run the monitor tool to view serial output:
```bash
idf.py -p PORT build flash monitor
```
(To exit the serial monitor, type `Ctrl-]`.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
```text
I (252) main_task: Started on CPU0
I (262) main_task: Calling app_main()
I (262) example: Monitor GPIO 4
I (262) example: Analog comparator source GPIO 37
I (262) example: Analog comparator internal reference 50% VDD
I (272) example: GPTimer scan period 50 us
I (282) example: Periodic ETM-driven comparator scan started
I (282) main_task: Returned from app_main()
```
The exact source GPIO number depends on the target and package.
## Expected Result On Hardware
Feed a sine wave into the comparator source channel. Because the comparator reference is fixed at 50% VDD, the monitor GPIO stays high while the sampled source voltage is above the threshold and low while it is below the threshold.
On an oscilloscope, the monitor GPIO appears as a square wave derived from the sampled sine wave.
![example_waveform](wave.png)
## Troubleshooting
- This example only works on targets that support analog comparator channel scan and the ETM scan-task path.
- If the monitor GPIO does not change, reduce the input frequency or shorten the scan period.
- If the square wave looks unstable, confirm the input sine wave amplitude crosses the 50% VDD threshold.
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "ana_cmpr_etm_periodic_scan_main.c"
PRIV_REQUIRES esp_driver_ana_cmpr esp_driver_gptimer esp_driver_gpio
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,23 @@
menu "Example Configuration"
config EXAMPLE_SRC_GPIO_NUM
int "Source GPIO number"
default 0
help
GPIO connected to analog comparator source channel 0.
config EXAMPLE_MONITOR_GPIO_NUM
int "Monitor GPIO number"
default 4
range 0 SOC_GPIO_OUT_RANGE_MAX
help
GPIO that outputs the square wave converted from the analog comparator result.
config EXAMPLE_SCAN_PERIOD_US
int "Comparator scan period (us)"
default 50
range 10 1000000
help
Period of the GPTimer ETM event that triggers the analog comparator scan task.
endmenu

View File

@@ -0,0 +1,200 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_etm.h"
#include "driver/gpio.h"
#include "driver/gptimer.h"
#include "driver/ana_cmpr.h"
#include "driver/gpio_etm.h"
#include "driver/gptimer_etm.h"
#include "driver/ana_cmpr_etm.h"
#define EXAMPLE_ANA_CMPR_UNIT (0)
#define EXAMPLE_MONITOR_GPIO_NUM CONFIG_EXAMPLE_MONITOR_GPIO_NUM
#define EXAMPLE_SCAN_PERIOD_US CONFIG_EXAMPLE_SCAN_PERIOD_US
#define EXAMPLE_ANA_CMPR_SRC_GPIO_NUM CONFIG_EXAMPLE_SRC_GPIO_NUM
#define TAG "example"
typedef struct {
/* ETM events are produced by peripherals and can trigger ETM tasks without CPU intervention. */
esp_etm_event_handle_t gptimer_alarm_evt;
esp_etm_event_handle_t cmpr_pos_evt;
esp_etm_event_handle_t cmpr_neg_evt;
/* ETM tasks are the hardware actions we want to execute when an event happens. */
esp_etm_task_handle_t gptimer_en_alarm_task;
esp_etm_task_handle_t cmpr_start_task;
esp_etm_task_handle_t gpio_set_task;
esp_etm_task_handle_t gpio_clr_task;
/* One ETM channel connects exactly one event source to one task target. */
esp_etm_channel_handle_t etm_realarm_handle;
esp_etm_channel_handle_t etm_scan_handle;
esp_etm_channel_handle_t etm_pos_handle;
esp_etm_channel_handle_t etm_neg_handle;
} example_etm_handles_t;
static void example_init_monitor_gpio(void)
{
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pin_bit_mask = 1ULL << EXAMPLE_MONITOR_GPIO_NUM,
};
ESP_ERROR_CHECK(gpio_config(&io_conf));
ESP_ERROR_CHECK(gpio_set_level(EXAMPLE_MONITOR_GPIO_NUM, 0));
ESP_LOGI(TAG, "Monitor GPIO %d", EXAMPLE_MONITOR_GPIO_NUM);
}
static ana_cmpr_handle_t example_init_ana_cmpr(void)
{
gpio_num_t src_gpio = -1;
ana_cmpr_handle_t cmpr = NULL;
ana_cmpr_config_t config = {
.unit = EXAMPLE_ANA_CMPR_UNIT,
.clk_src = ANA_CMPR_CLK_SRC_DEFAULT,
.ref_src = ANA_CMPR_REF_SRC_INTERNAL,
.cross_type = ANA_CMPR_CROSS_ANY,
.src_chan0_gpio = EXAMPLE_ANA_CMPR_SRC_GPIO_NUM,
/* Require several consistent samples before the scan result is updated.
* This makes the output more stable when the input is noisy. */
.resample_limit = 3,
};
ESP_ERROR_CHECK(ana_cmpr_new_unit(&config, &cmpr));
ESP_ERROR_CHECK(ana_cmpr_get_channel_gpio(cmpr, ANA_CMPR_SOURCE_CHAN, 0, &src_gpio));
ana_cmpr_internal_ref_config_t ref_cfg = {
.ref_volt = ANA_CMPR_REF_VOLT_50_PCT_VDD,
.ref_hys_level = ANA_CMPR_REF_HYS_LEVEL0,
};
/* Compare the input signal against an internal reference set to 50% of VDD. */
ESP_ERROR_CHECK(ana_cmpr_set_internal_reference(cmpr, &ref_cfg));
ana_cmpr_scan_config_t scan_cfg = {
.scan_mode = ANA_CMPR_SCAN_MODE_FULL,
/* Stay on each source channel for a short time before moving to the next one. */
.poll_period_us = 2,
};
/* Scan parameters control how the hardware walks through the enabled source channels. */
ESP_ERROR_CHECK(ana_cmpr_set_scan_config(cmpr, &scan_cfg));
ESP_LOGI(TAG, "Analog comparator source GPIO %d", src_gpio);
ESP_LOGI(TAG, "Analog comparator internal reference 50%% VDD");
return cmpr;
}
static gptimer_handle_t example_init_gptimer(void)
{
gptimer_handle_t gptimer = NULL;
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1 * 1000 * 1000, // 1 MHz, which means the timer count value will increase by 1 every microsecond
};
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
ESP_ERROR_CHECK(gptimer_set_raw_count(gptimer, 0));
ESP_ERROR_CHECK(gptimer_enable(gptimer));
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
/* The alarm period is the high-level scan period of this example.
* Every alarm will trigger one comparator scan through ETM. */
.alarm_count = EXAMPLE_SCAN_PERIOD_US,
.flags.auto_reload_on_alarm = true,
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
ESP_LOGI(TAG, "GPTimer scan period %d us", EXAMPLE_SCAN_PERIOD_US);
return gptimer;
}
static void example_init_etm(ana_cmpr_handle_t cmpr, gptimer_handle_t gptimer)
{
example_etm_handles_t handles = {};
/* Step 1: create the ETM event generated when the timer alarm fires. */
gptimer_etm_event_config_t gptimer_evt_cfg = {
.event_type = GPTIMER_ETM_EVENT_ALARM_MATCH,
};
ESP_ERROR_CHECK(gptimer_new_etm_event(gptimer, &gptimer_evt_cfg, &handles.gptimer_alarm_evt));
/* Step 2: create the timer task that rearms the alarm after each trigger. */
gptimer_etm_task_config_t gptimer_task_cfg = {
.task_type = GPTIMER_ETM_TASK_EN_ALARM,
};
ESP_ERROR_CHECK(gptimer_new_etm_task(gptimer, &gptimer_task_cfg, &handles.gptimer_en_alarm_task));
/* Step 3: create the comparator task that starts one scan sequence. */
ana_cmpr_etm_task_config_t cmpr_task_cfg = {
.task_type = ANA_CMPR_TASK_START,
};
ESP_ERROR_CHECK(ana_cmpr_new_etm_task(cmpr, &cmpr_task_cfg, &handles.cmpr_start_task));
/* Step 4: create comparator events for positive and negative threshold crossings. */
ana_cmpr_etm_event_config_t cmpr_evt_cfg = {
.event_type = ANA_CMPR_EVENT_POS_CROSS,
};
ESP_ERROR_CHECK(ana_cmpr_new_etm_event(cmpr, &cmpr_evt_cfg, &handles.cmpr_pos_evt));
cmpr_evt_cfg.event_type = ANA_CMPR_EVENT_NEG_CROSS;
ESP_ERROR_CHECK(ana_cmpr_new_etm_event(cmpr, &cmpr_evt_cfg, &handles.cmpr_neg_evt));
/* Step 5: create GPIO ETM tasks so the comparator result is visible on a normal output pin.
* Positive crossing drives the monitor GPIO high, negative crossing drives it low. */
gpio_etm_task_config_t gpio_task_cfg = {};
gpio_task_cfg.actions[0] = GPIO_ETM_TASK_ACTION_SET;
gpio_task_cfg.actions[1] = GPIO_ETM_TASK_ACTION_CLR;
ESP_ERROR_CHECK(gpio_new_etm_task(&gpio_task_cfg, &handles.gpio_set_task, &handles.gpio_clr_task));
ESP_ERROR_CHECK(gpio_etm_task_add_gpio(handles.gpio_set_task, EXAMPLE_MONITOR_GPIO_NUM));
ESP_ERROR_CHECK(gpio_etm_task_add_gpio(handles.gpio_clr_task, EXAMPLE_MONITOR_GPIO_NUM));
/* Step 6: allocate ETM channels. Each channel is an event-to-task connection. */
esp_etm_channel_config_t etm_cfg = {};
ESP_ERROR_CHECK(esp_etm_new_channel(&etm_cfg, &handles.etm_realarm_handle));
ESP_ERROR_CHECK(esp_etm_new_channel(&etm_cfg, &handles.etm_scan_handle));
ESP_ERROR_CHECK(esp_etm_new_channel(&etm_cfg, &handles.etm_pos_handle));
ESP_ERROR_CHECK(esp_etm_new_channel(&etm_cfg, &handles.etm_neg_handle));
/* Step 7: wire the ETM graph:
* - timer alarm event -> timer rearm task
* - timer alarm event -> comparator start task
* - comparator positive event -> GPIO set task
* - comparator negative event -> GPIO clear task */
ESP_ERROR_CHECK(esp_etm_channel_connect(handles.etm_realarm_handle, handles.gptimer_alarm_evt, handles.gptimer_en_alarm_task));
ESP_ERROR_CHECK(esp_etm_channel_connect(handles.etm_scan_handle, handles.gptimer_alarm_evt, handles.cmpr_start_task));
ESP_ERROR_CHECK(esp_etm_channel_connect(handles.etm_pos_handle, handles.cmpr_pos_evt, handles.gpio_set_task));
ESP_ERROR_CHECK(esp_etm_channel_connect(handles.etm_neg_handle, handles.cmpr_neg_evt, handles.gpio_clr_task));
/* Step 8: enable the channels so the hardware pipeline becomes active. */
ESP_ERROR_CHECK(esp_etm_channel_enable(handles.etm_realarm_handle));
ESP_ERROR_CHECK(esp_etm_channel_enable(handles.etm_scan_handle));
ESP_ERROR_CHECK(esp_etm_channel_enable(handles.etm_pos_handle));
ESP_ERROR_CHECK(esp_etm_channel_enable(handles.etm_neg_handle));
}
void app_main(void)
{
example_init_monitor_gpio();
ana_cmpr_handle_t cmpr = example_init_ana_cmpr();
gptimer_handle_t gptimer = example_init_gptimer();
example_init_etm(cmpr, gptimer);
ESP_ERROR_CHECK(ana_cmpr_enable(cmpr));
/* Run one software-triggered scan before the periodic timer starts.
* This gives the comparator an initial result immediately, instead of waiting for the first timer alarm. */
ESP_ERROR_CHECK(ana_cmpr_trigger_scan(cmpr));
vTaskDelay(pdMS_TO_TICKS(10));
/* After the timer starts, future scans are launched by ETM rather than by CPU code. */
ESP_ERROR_CHECK(gptimer_start(gptimer));
ESP_LOGI(TAG, "Periodic ETM-driven comparator scan started");
}

View File

@@ -0,0 +1,20 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
from pytest_embedded_idf.utils import soc_filtered_targets
@pytest.mark.generic
@idf_parametrize(
'target',
soc_filtered_targets('SOC_ANA_CMPR_SUPPORT_ETM_SCAN == 1'),
indirect=['target'],
)
def test_ana_cmpr_etm_periodic_scan(dut: Dut) -> None:
dut.expect(r'Monitor GPIO \d+')
dut.expect(r'Analog comparator source GPIO \d+')
dut.expect_exact('Analog comparator internal reference 50% VDD')
dut.expect(r'GPTimer scan period \d+ us')
dut.expect_exact('Periodic ETM-driven comparator scan started')

View File

@@ -0,0 +1 @@
CONFIG_EXAMPLE_SRC_GPIO_NUM=0

View File

@@ -0,0 +1 @@
CONFIG_EXAMPLE_SRC_GPIO_NUM=37

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB