diff --git a/docs/en/api-reference/peripherals/ana_cmpr.rst b/docs/en/api-reference/peripherals/ana_cmpr.rst index 13a1db9c35d..382ae6a529d 100644 --- a/docs/en/api-reference/peripherals/ana_cmpr.rst +++ b/docs/en/api-reference/peripherals/ana_cmpr.rst @@ -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 ------------- diff --git a/docs/zh_CN/api-reference/peripherals/ana_cmpr.rst b/docs/zh_CN/api-reference/peripherals/ana_cmpr.rst index 8378e403d67..27d5be86df7 100644 --- a/docs/zh_CN/api-reference/peripherals/ana_cmpr.rst +++ b/docs/zh_CN/api-reference/peripherals/ana_cmpr.rst @@ -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 参考 -------- diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index 4a1dcc7d990..e87a959b546 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -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 diff --git a/examples/peripherals/analog_comparator/etm_periodic_scan/CMakeLists.txt b/examples/peripherals/analog_comparator/etm_periodic_scan/CMakeLists.txt new file mode 100644 index 00000000000..5127a906e71 --- /dev/null +++ b/examples/peripherals/analog_comparator/etm_periodic_scan/CMakeLists.txt @@ -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) diff --git a/examples/peripherals/analog_comparator/etm_periodic_scan/README.md b/examples/peripherals/analog_comparator/etm_periodic_scan/README.md new file mode 100644 index 00000000000..df381801710 --- /dev/null +++ b/examples/peripherals/analog_comparator/etm_periodic_scan/README.md @@ -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. diff --git a/examples/peripherals/analog_comparator/etm_periodic_scan/main/CMakeLists.txt b/examples/peripherals/analog_comparator/etm_periodic_scan/main/CMakeLists.txt new file mode 100644 index 00000000000..61c4ab34cad --- /dev/null +++ b/examples/peripherals/analog_comparator/etm_periodic_scan/main/CMakeLists.txt @@ -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 ".") diff --git a/examples/peripherals/analog_comparator/etm_periodic_scan/main/Kconfig.projbuild b/examples/peripherals/analog_comparator/etm_periodic_scan/main/Kconfig.projbuild new file mode 100644 index 00000000000..4b1b3eed8e5 --- /dev/null +++ b/examples/peripherals/analog_comparator/etm_periodic_scan/main/Kconfig.projbuild @@ -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 diff --git a/examples/peripherals/analog_comparator/etm_periodic_scan/main/ana_cmpr_etm_periodic_scan_main.c b/examples/peripherals/analog_comparator/etm_periodic_scan/main/ana_cmpr_etm_periodic_scan_main.c new file mode 100644 index 00000000000..cfa797b2bdf --- /dev/null +++ b/examples/peripherals/analog_comparator/etm_periodic_scan/main/ana_cmpr_etm_periodic_scan_main.c @@ -0,0 +1,200 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#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"); +} diff --git a/examples/peripherals/analog_comparator/etm_periodic_scan/pytest_ana_cmpr_etm_periodic_scan.py b/examples/peripherals/analog_comparator/etm_periodic_scan/pytest_ana_cmpr_etm_periodic_scan.py new file mode 100644 index 00000000000..217ec6973ac --- /dev/null +++ b/examples/peripherals/analog_comparator/etm_periodic_scan/pytest_ana_cmpr_etm_periodic_scan.py @@ -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') diff --git a/examples/peripherals/analog_comparator/etm_periodic_scan/sdkconfig.defaults b/examples/peripherals/analog_comparator/etm_periodic_scan/sdkconfig.defaults new file mode 100644 index 00000000000..a73029ee703 --- /dev/null +++ b/examples/peripherals/analog_comparator/etm_periodic_scan/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_EXAMPLE_SRC_GPIO_NUM=0 diff --git a/examples/peripherals/analog_comparator/etm_periodic_scan/sdkconfig.defaults.esp32s31 b/examples/peripherals/analog_comparator/etm_periodic_scan/sdkconfig.defaults.esp32s31 new file mode 100644 index 00000000000..e47a6705285 --- /dev/null +++ b/examples/peripherals/analog_comparator/etm_periodic_scan/sdkconfig.defaults.esp32s31 @@ -0,0 +1 @@ +CONFIG_EXAMPLE_SRC_GPIO_NUM=37 diff --git a/examples/peripherals/analog_comparator/etm_periodic_scan/wave.png b/examples/peripherals/analog_comparator/etm_periodic_scan/wave.png new file mode 100644 index 00000000000..00749cf6127 Binary files /dev/null and b/examples/peripherals/analog_comparator/etm_periodic_scan/wave.png differ