فهرست منبع

ana_cmpr: add an example and test cases

laokaiyao 2 سال پیش
والد
کامیت
24361f232d
28فایلهای تغییر یافته به همراه747 افزوده شده و 8 حذف شده
  1. 4 0
      components/driver/.build-test-rules.yml
  2. 20 7
      components/driver/analog_comparator/ana_cmpr.c
  3. 1 1
      components/driver/analog_comparator/include/driver/ana_cmpr_types.h
  4. 18 0
      components/driver/test_apps/analog_comparator/CMakeLists.txt
  5. 2 0
      components/driver/test_apps/analog_comparator/README.md
  6. 11 0
      components/driver/test_apps/analog_comparator/main/CMakeLists.txt
  7. 86 0
      components/driver/test_apps/analog_comparator/main/test_ana_cmpr.c
  8. 43 0
      components/driver/test_apps/analog_comparator/main/test_ana_cmpr.h
  9. 39 0
      components/driver/test_apps/analog_comparator/main/test_ana_cmpr_common.c
  10. 76 0
      components/driver/test_apps/analog_comparator/main/test_ana_cmpr_iram.c
  11. 54 0
      components/driver/test_apps/analog_comparator/main/test_app_main.c
  12. 19 0
      components/driver/test_apps/analog_comparator/pytest_ana_cmpr.py
  13. 7 0
      components/driver/test_apps/analog_comparator/sdkconfig.ci.iram_safe
  14. 5 0
      components/driver/test_apps/analog_comparator/sdkconfig.ci.release
  15. 2 0
      components/driver/test_apps/analog_comparator/sdkconfig.defaults
  16. 4 0
      components/hal/esp32h2/include/hal/ana_cmpr_ll.h
  17. 4 0
      examples/peripherals/.build-test-rules.yml
  18. 8 0
      examples/peripherals/analog_comparator/CMakeLists.txt
  19. 144 0
      examples/peripherals/analog_comparator/README.md
  20. BIN
      examples/peripherals/analog_comparator/ext_ref.png
  21. BIN
      examples/peripherals/analog_comparator/hysteresis_ref.png
  22. 2 0
      examples/peripherals/analog_comparator/main/CMakeLists.txt
  23. 28 0
      examples/peripherals/analog_comparator/main/Kconfig.projbuild
  24. 140 0
      examples/peripherals/analog_comparator/main/ana_cmpr_example_main.c
  25. 26 0
      examples/peripherals/analog_comparator/pytest_ana_cmpr_example.py
  26. 2 0
      examples/peripherals/analog_comparator/sdkconfig.ci.ext
  27. 2 0
      examples/peripherals/analog_comparator/sdkconfig.ci.intl
  28. BIN
      examples/peripherals/analog_comparator/static_50p_ref.png

+ 4 - 0
components/driver/.build-test-rules.yml

@@ -1,5 +1,9 @@
 # Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
 
+components/driver/test_apps/analog_comparator:
+  disable:
+    - if: SOC_ANA_CMPR_SUPPORTED != 1
+
 components/driver/test_apps/dac_test_apps/dac:
   disable:
     - if: SOC_DAC_SUPPORTED != 1

+ 20 - 7
components/driver/analog_comparator/ana_cmpr.c

@@ -20,6 +20,7 @@
 #include "esp_pm.h"
 #include "esp_heap_caps.h"
 #include "esp_intr_alloc.h"
+#include "esp_memory_utils.h"
 #include "soc/periph_defs.h"
 #include "soc/ana_cmpr_periph.h"
 #include "hal/ana_cmpr_ll.h"
@@ -30,6 +31,7 @@
 struct ana_cmpr_t {
     ana_cmpr_unit_t             unit;               /*!< Analog comparator unit id */
     analog_cmpr_dev_t           *dev;               /*!< Analog comparator unit device address */
+    ana_cmpr_ref_source_t       ref_src;            /*!< Analog comparator reference source, internal or external */
     bool                        is_enabled;         /*!< Whether the Analog comparator unit is enabled */
     ana_cmpr_event_callbacks_t  cbs;                /*!< The callback group that set by user */
     intr_handle_t               intr_handle;        /*!< Interrupt handle */
@@ -46,9 +48,11 @@ struct ana_cmpr_t {
 
 /* Memory allocation caps which decide the section that memory supposed to allocate */
 #if CONFIG_ANA_CMPR_ISR_IRAM_SAFE
-#define ANA_CMPR_MEM_ALLOC_CAPS      (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
+#define ANA_CMPR_MEM_ALLOC_CAPS         (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
+#define ANA_CMPR_INTR_FLAG              (ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM)
 #else
-#define ANA_CMPR_MEM_ALLOC_CAPS      MALLOC_CAP_DEFAULT
+#define ANA_CMPR_MEM_ALLOC_CAPS         MALLOC_CAP_DEFAULT
+#define ANA_CMPR_INTR_FLAG              ESP_INTR_FLAG_LEVEL1
 #endif
 
 /* Driver tag */
@@ -99,6 +103,7 @@ esp_err_t ana_cmpr_new_unit(const ana_cmpr_config_t *config, ana_cmpr_handle_t *
 
     /* Assign analog comparator unit */
     s_ana_cmpr[unit]->dev = ANALOG_CMPR_LL_GET_HW();
+    s_ana_cmpr[unit]->ref_src = config->ref_src;
     s_ana_cmpr[unit]->is_enabled = false;
     s_ana_cmpr[unit]->pm_lock = NULL;
 
@@ -124,7 +129,7 @@ esp_err_t ana_cmpr_new_unit(const ana_cmpr_config_t *config, ana_cmpr_handle_t *
     portEXIT_CRITICAL(&s_spinlock);
 
     /* Allocate the interrupt, currently the interrupt source of Analog Comparator is shared with GPIO interrupt source */
-    esp_intr_alloc(ETS_GPIO_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1,
+    esp_intr_alloc(ETS_GPIO_INTR_SOURCE, ANA_CMPR_INTR_FLAG,
                    s_ana_cmpr_default_intr_handler, s_ana_cmpr[unit], &s_ana_cmpr[unit]->intr_handle);
 
     if (config->ref_src == ANA_CMPR_REF_SRC_INTERNAL) {
@@ -183,11 +188,13 @@ esp_err_t ana_cmpr_set_intl_reference(ana_cmpr_handle_t cmpr, const ana_cmpr_int
 {
     ANA_CMPR_NULL_POINTER_CHECK_ISR(cmpr);
     ANA_CMPR_NULL_POINTER_CHECK_ISR(ref_cfg);
+    ESP_RETURN_ON_FALSE_ISR(cmpr->ref_src == ANA_CMPR_REF_SRC_INTERNAL, ESP_ERR_INVALID_STATE,
+                            TAG, "the reference channel is not internal, no need to configure internal reference");
 
     /* Set internal reference voltage */
-    portENTER_CRITICAL_ISR(&s_spinlock);
+    portENTER_CRITICAL_SAFE(&s_spinlock);
     analog_cmpr_ll_set_internal_ref_voltage(cmpr->dev, ref_cfg->ref_volt);
-    portEXIT_CRITICAL_ISR(&s_spinlock);
+    portEXIT_CRITICAL_SAFE(&s_spinlock);
 
     ESP_EARLY_LOGD(TAG, "unit %d internal voltage level %"PRIu32, (int)cmpr->unit, ref_cfg->ref_volt);
 
@@ -202,9 +209,9 @@ esp_err_t ana_cmpr_set_debounce(ana_cmpr_handle_t cmpr, const ana_cmpr_debounce_
     /* Transfer the time to clock cycles */
     uint32_t wait_cycle = (uint32_t)(dbc_cfg->wait_us * cmpr->src_clk_freq_hz) / 1000000;
     /* Set the waiting clock cycles */
-    portENTER_CRITICAL_ISR(&s_spinlock);
+    portENTER_CRITICAL_SAFE(&s_spinlock);
     analog_cmpr_ll_set_debounce_cycle(cmpr->dev, wait_cycle);
-    portEXIT_CRITICAL_ISR(&s_spinlock);
+    portEXIT_CRITICAL_SAFE(&s_spinlock);
 
     ESP_EARLY_LOGD(TAG, "unit %d debounce wait cycle %"PRIu32, (int)cmpr->unit, wait_cycle);
 
@@ -217,6 +224,12 @@ esp_err_t ana_cmpr_register_event_callbacks(ana_cmpr_handle_t cmpr, const ana_cm
     ANA_CMPR_NULL_POINTER_CHECK(cbs);
     ESP_RETURN_ON_FALSE(!cmpr->is_enabled, ESP_ERR_INVALID_STATE, TAG,
                         "please disable the analog comparator before registering the callbacks");
+#if CONFIG_ANA_CMPR_ISR_IRAM_SAFE
+    if (cbs->on_cross) {
+        ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_cross), ESP_ERR_INVALID_ARG, TAG,
+                            "ANA_CMPR_ISR_IRAM_SAFE enabled but the callback function not in IRAM");
+    }
+#endif
 
     /* Save the callback group */
     memcpy(&(cmpr->cbs), cbs, sizeof(ana_cmpr_event_callbacks_t));

+ 1 - 1
components/driver/analog_comparator/include/driver/ana_cmpr_types.h

@@ -90,7 +90,7 @@ typedef int ana_cmpr_clk_src_t;
  *
  */
 typedef struct {
-    // Currently no data
+    // No data for now
 } ana_cmpr_cross_event_data_t;
 
 /**

+ 18 - 0
components/driver/test_apps/analog_comparator/CMakeLists.txt

@@ -0,0 +1,18 @@
+# This is the project CMakeLists.txt file for the test subproject
+cmake_minimum_required(VERSION 3.16)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(test_ana_cmpr)
+
+if(CONFIG_COMPILER_DUMP_RTL_FILES)
+    add_custom_target(check_test_app_sections ALL
+                      COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
+                      --rtl-dir ${CMAKE_BINARY_DIR}/esp-idf/driver/
+                      --elf-file ${CMAKE_BINARY_DIR}/test_ana_cmpr.elf
+                      find-refs
+                      --from-sections=.iram0.text
+                      --to-sections=.flash.text
+                      --exit-code
+                      DEPENDS ${elf}
+                      )
+endif()

+ 2 - 0
components/driver/test_apps/analog_comparator/README.md

@@ -0,0 +1,2 @@
+| Supported Targets | ESP32-H2 |
+| ----------------- | -------- |

+ 11 - 0
components/driver/test_apps/analog_comparator/main/CMakeLists.txt

@@ -0,0 +1,11 @@
+set(srcs "test_app_main.c"
+         "test_ana_cmpr_common.c"
+         "test_ana_cmpr.c")
+
+if(CONFIG_ANA_CMPR_ISR_IRAM_SAFE)
+    list(APPEND srcs "test_ana_cmpr_iram.c")
+endif()
+
+idf_component_register(SRCS ${srcs}
+                       INCLUDE_DIRS "."
+                       WHOLE_ARCHIVE)

+ 86 - 0
components/driver/test_apps/analog_comparator/main/test_ana_cmpr.c

@@ -0,0 +1,86 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "test_ana_cmpr.h"
+
+TEST_CASE("ana_cmpr_unit_install_uninstall", "[ana_cmpr]")
+{
+    ana_cmpr_handle_t cmpr = NULL;
+    ana_cmpr_config_t config = {
+        .unit = SOC_ANA_CMPR_NUM,   // Set a wrong unit
+        .clk_src = ANA_CMPR_CLK_SRC_DEFAULT,
+        .ref_src = ANA_CMPR_REF_SRC_INTERNAL,
+        .intr_type = ANA_CMPR_INTR_ANY_CROSS,
+    };
+    /* Allocate a wrong unit */
+    TEST_ESP_ERR(ESP_ERR_INVALID_ARG, ana_cmpr_new_unit(&config, &cmpr));
+    /* Allocate a correct unit  */
+    config.unit = ANA_CMPR_UNIT_0;
+    TEST_ESP_OK(ana_cmpr_new_unit(&config, &cmpr));
+    /* Try to allocate a existed unit */
+    TEST_ESP_ERR(ESP_ERR_INVALID_STATE, ana_cmpr_new_unit(&config, &cmpr));
+    /* Set the internal reference before enable */
+    ana_cmpr_intl_ref_config_t ref_cfg = {
+        .ref_volt = ANA_CMPR_REF_VOLT_50_PCT_VDD,
+    };
+    TEST_ESP_OK(ana_cmpr_set_intl_reference(cmpr, &ref_cfg));
+    /* Enable the unit */
+    TEST_ESP_OK(ana_cmpr_enable(cmpr));
+    /* Set the internal reference after enable */
+    ref_cfg.ref_volt = ANA_CMPR_REF_VOLT_30_PCT_VDD;
+    TEST_ESP_OK(ana_cmpr_set_intl_reference(cmpr, &ref_cfg));
+    /* Try tp delete unit after enable */
+    TEST_ESP_ERR(ESP_ERR_INVALID_STATE, ana_cmpr_del_unit(cmpr));
+    /* Disable the unit */
+    TEST_ESP_OK(ana_cmpr_disable(cmpr));
+    /* Try to delete the unit with a wrong handle */
+    TEST_ESP_ERR(ESP_ERR_INVALID_STATE, ana_cmpr_del_unit((void *)&cmpr));
+    /* Delete the unit */
+    TEST_ESP_OK(ana_cmpr_del_unit(cmpr));
+
+    /* Try to set internal reference for a external unit */
+    config.ref_src = ANA_CMPR_REF_SRC_EXTERNAL;
+    TEST_ESP_OK(ana_cmpr_new_unit(&config, &cmpr));
+    TEST_ESP_ERR(ESP_ERR_INVALID_STATE, ana_cmpr_set_intl_reference(cmpr, &ref_cfg));
+    TEST_ESP_OK(ana_cmpr_del_unit(cmpr));
+}
+
+TEST_CASE("ana_cmpr_internal_reference", "[ana_cmpr]")
+{
+    int src_chan = test_init_src_chan_gpio();
+    uint32_t cnt = 0;
+
+    ana_cmpr_handle_t cmpr = NULL;
+    ana_cmpr_config_t config = {
+        .unit = ANA_CMPR_UNIT_0,
+        .clk_src = ANA_CMPR_CLK_SRC_DEFAULT,
+        .ref_src = ANA_CMPR_REF_SRC_INTERNAL,
+        .intr_type = ANA_CMPR_INTR_ANY_CROSS,
+    };
+    TEST_ESP_OK(ana_cmpr_new_unit(&config, &cmpr));
+    ana_cmpr_intl_ref_config_t ref_cfg = {
+        .ref_volt = ANA_CMPR_REF_VOLT_50_PCT_VDD,
+    };
+    TEST_ESP_OK(ana_cmpr_set_intl_reference(cmpr, &ref_cfg));
+    ana_cmpr_debounce_config_t dbc_cfg = {
+        .wait_us = 10.0,
+    };
+    TEST_ESP_OK(ana_cmpr_set_debounce(cmpr, &dbc_cfg));
+    ana_cmpr_event_callbacks_t cbs = {
+        .on_cross = test_ana_cmpr_on_cross_callback,
+    };
+
+    TEST_ESP_OK(ana_cmpr_register_event_callbacks(cmpr, &cbs, &cnt));
+    TEST_ESP_OK(ana_cmpr_enable(cmpr));
+    cnt = 0;
+    for (int i = 1; i <= 10; i++) {
+        test_simulate_src_signal(src_chan, i % 2);
+        esp_rom_delay_us(100);
+        TEST_ASSERT(cnt == i);
+    }
+    TEST_ESP_OK(ana_cmpr_disable(cmpr));
+    TEST_ESP_OK(ana_cmpr_del_unit(cmpr));
+}

+ 43 - 0
components/driver/test_apps/analog_comparator/main/test_ana_cmpr.h

@@ -0,0 +1,43 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdio.h>
+#include "sdkconfig.h"
+#include "esp_attr.h"
+#include "unity.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_rom_sys.h"
+#include "soc/soc_caps.h"
+#include "driver/ana_cmpr.h"
+
+/**
+ * @brief Test default on cross callback
+ *
+ * @param cmpr      Analog Comparator handle
+ * @param edata     Event data
+ * @param user_ctx  User context, need to input a unint32_t counter
+ * @return
+ *      - true      Need to yield
+ *      - false     Don't need yield
+ */
+bool test_ana_cmpr_on_cross_callback(ana_cmpr_handle_t cmpr, const ana_cmpr_cross_event_data_t *edata, void *user_ctx);
+
+/**
+ * @brief Initialize Analog Comparator source channel GPIO
+ *
+ * @return
+ *      - int   Source channel GPIO number
+ */
+int test_init_src_chan_gpio(void);
+
+/**
+ * @brief Simulate source channel signal
+ *
+ * @param src_chan  Source channel GPIO number
+ * @param val       0 to set low, others to set high
+ */
+void test_simulate_src_signal(int src_chan, uint32_t val);

+ 39 - 0
components/driver/test_apps/analog_comparator/main/test_ana_cmpr_common.c

@@ -0,0 +1,39 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "test_ana_cmpr.h"
+#include "hal/gpio_ll.h"
+#include "driver/gpio.h"
+#include "esp_attr.h"
+
+bool IRAM_ATTR test_ana_cmpr_on_cross_callback(ana_cmpr_handle_t cmpr, const ana_cmpr_cross_event_data_t *edata, void *user_ctx)
+{
+    uint32_t *count = (uint32_t *)user_ctx;
+    (*count)++;
+    return false;
+}
+
+int test_init_src_chan_gpio(void)
+{
+    int src_chan_num = -1;
+    TEST_ESP_OK(ana_cmpr_get_gpio(ANA_CMPR_UNIT_0, ANA_CMPR_SOURCE_CHAN, &src_chan_num));
+    TEST_ASSERT(src_chan_num > 0);
+    gpio_config_t io_conf = {
+        .intr_type = GPIO_INTR_DISABLE,
+        .mode = GPIO_MODE_OUTPUT,
+        .pin_bit_mask = (1ULL << src_chan_num),
+        .pull_down_en = false,
+        .pull_up_en = false,
+    };
+    TEST_ESP_OK(gpio_config(&io_conf));
+    TEST_ESP_OK(gpio_set_level(src_chan_num, 0));
+    return src_chan_num;
+}
+
+void IRAM_ATTR test_simulate_src_signal(int src_chan, uint32_t val)
+{
+    gpio_set_level(src_chan, val);
+}

+ 76 - 0
components/driver/test_apps/analog_comparator/main/test_ana_cmpr_iram.c

@@ -0,0 +1,76 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "test_ana_cmpr.h"
+#include "unity_test_utils.h"
+#include "unity_test_utils_cache.h"
+
+typedef struct {
+    ana_cmpr_handle_t handle;
+    uint32_t count;
+    int src_chan;
+} test_ana_cmpr_data_t;
+
+/**
+ * @brief Global debounce configuration
+ * @note  Why it is global?
+ *        If we declare a local variable in 'float' type when cache disabled,
+ *        it may be allocated in the flash, which can lead crash when trying to access it.
+ */
+static ana_cmpr_debounce_config_t IRAM_ATTR s_dbc_cfg = {
+    .wait_us = 1.0,
+};
+
+static void IRAM_ATTR test_ana_cmpr_iram_safety(void *args)
+{
+    test_ana_cmpr_data_t *data = (test_ana_cmpr_data_t *)args;
+    ana_cmpr_intl_ref_config_t ref_cfg = {
+        .ref_volt = ANA_CMPR_REF_VOLT_50_PCT_VDD,
+    };
+    ana_cmpr_set_intl_reference(data->handle, &ref_cfg);
+    ana_cmpr_set_debounce(data->handle, &s_dbc_cfg);
+    data->count = 0;
+    for (int i = 1; i <= 10; i++) {
+        test_simulate_src_signal(data->src_chan, i % 2);
+        esp_rom_delay_us(100);
+    }
+}
+
+TEST_CASE("ana_cmpr_internal_reference_iram_safe", "[ana_cmpr]")
+{
+    test_ana_cmpr_data_t test_data = {
+        .handle = NULL,
+        .count = 0,
+        .src_chan = -1,
+    };
+    test_data.src_chan = test_init_src_chan_gpio();
+
+    ana_cmpr_handle_t cmpr = NULL;
+    ana_cmpr_config_t config = {
+        .unit = ANA_CMPR_UNIT_0,
+        .clk_src = ANA_CMPR_CLK_SRC_DEFAULT,
+        .ref_src = ANA_CMPR_REF_SRC_INTERNAL,
+        .intr_type = ANA_CMPR_INTR_ANY_CROSS,
+    };
+    TEST_ESP_OK(ana_cmpr_new_unit(&config, &cmpr));
+    test_data.handle = cmpr;
+    ana_cmpr_intl_ref_config_t ref_cfg = {
+        .ref_volt = ANA_CMPR_REF_VOLT_50_PCT_VDD,
+    };
+    TEST_ESP_OK(ana_cmpr_set_intl_reference(cmpr, &ref_cfg));
+    TEST_ESP_OK(ana_cmpr_set_debounce(cmpr, &s_dbc_cfg));
+    ana_cmpr_event_callbacks_t cbs = {
+        .on_cross = test_ana_cmpr_on_cross_callback,
+    };
+    TEST_ESP_OK(ana_cmpr_register_event_callbacks(cmpr, &cbs, &test_data.count));
+    TEST_ESP_OK(ana_cmpr_enable(cmpr));
+
+    unity_utils_run_cache_disable_stub(test_ana_cmpr_iram_safety, &test_data);
+    TEST_ASSERT_EQUAL_INT(test_data.count, 10);
+
+    TEST_ESP_OK(ana_cmpr_disable(cmpr));
+    TEST_ESP_OK(ana_cmpr_del_unit(cmpr));
+}

+ 54 - 0
components/driver/test_apps/analog_comparator/main/test_app_main.c

@@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "unity.h"
+#include "unity_test_runner.h"
+#include "esp_heap_caps.h"
+
+// Some resources are lazy allocated in analog comparator driver, the threshold is left for that case
+#define TEST_MEMORY_LEAK_THRESHOLD (-300)
+
+static size_t before_free_8bit;
+static size_t before_free_32bit;
+
+static void check_leak(size_t before_free, size_t after_free, const char *type)
+{
+    ssize_t delta = after_free - before_free;
+    printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\\n", type, before_free, after_free, delta);
+    TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
+}
+
+void setUp(void)
+{
+    before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
+    before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
+}
+
+void tearDown(void)
+{
+    size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
+    size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
+    check_leak(before_free_8bit, after_free_8bit, "8BIT");
+    check_leak(before_free_32bit, after_free_32bit, "32BIT");
+}
+
+void app_main(void)
+{
+
+//      _    _   _    _       ____ __  __ ____  ____    _____         _
+//     / \  | \ | |  / \     / ___|  \/  |  _ \|  _ \  |_   _|__  ___| |_
+//    / _ \ |  \| | / _ \   | |   | |\/| | |_) | |_) |   | |/ _ \/ __| __|
+//   / ___ \| |\  |/ ___ \  | |___| |  | |  __/|  _ <    | |  __/\__ \ |_
+//  /_/   \_\_| \_/_/   \_\  \____|_|  |_|_|   |_| \_\   |_|\___||___/\__|
+
+    printf("     _    _   _    _       ____ __  __ ____  ____    _____         _   \n");
+    printf("    / \\  | \\ | |  / \\     / ___|  \\/  |  _ \\|  _ \\  |_   _|__  ___| |_ \n");
+    printf("   / _ \\ |  \\| | / _ \\   | |   | |\\/| | |_) | |_) |   | |/ _ \\/ __| __|\n");
+    printf("  / ___ \\| |\\  |/ ___ \\  | |___| |  | |  __/|  _ <    | |  __/\\__ \\ |_ \n");
+    printf(" /_/   \\_\\_| \\_/_/   \\_\\  \\____|_|  |_|_|   |_| \\_\\   |_|\\___||___/\\__|\n");
+    printf("\n");
+    unity_run_menu();
+}

+ 19 - 0
components/driver/test_apps/analog_comparator/pytest_ana_cmpr.py

@@ -0,0 +1,19 @@
+# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: CC0-1.0
+
+import pytest
+from pytest_embedded import Dut
+
+
+@pytest.mark.esp32h2
+@pytest.mark.generic
+@pytest.mark.parametrize(
+    'config',
+    [
+        'iram_safe',
+        'release',
+    ],
+    indirect=True,
+)
+def test_ana_cmpr(dut: Dut) -> None:
+    dut.run_all_single_board_cases()

+ 7 - 0
components/driver/test_apps/analog_comparator/sdkconfig.ci.iram_safe

@@ -0,0 +1,7 @@
+CONFIG_COMPILER_DUMP_RTL_FILES=y
+CONFIG_ANA_CMPR_ISR_IRAM_SAFE=y
+CONFIG_ANA_CMPR_CTRL_FUNC_IN_IRAM=y
+CONFIG_GPIO_CTRL_FUNC_IN_IRAM=y
+CONFIG_COMPILER_OPTIMIZATION_NONE=y
+# place non-ISR FreeRTOS functions in Flash
+CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y

+ 5 - 0
components/driver/test_apps/analog_comparator/sdkconfig.ci.release

@@ -0,0 +1,5 @@
+CONFIG_PM_ENABLE=y
+CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
+CONFIG_COMPILER_OPTIMIZATION_SIZE=y
+CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
+CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y

+ 2 - 0
components/driver/test_apps/analog_comparator/sdkconfig.defaults

@@ -0,0 +1,2 @@
+CONFIG_FREERTOS_HZ=1000
+CONFIG_ESP_TASK_WDT=n

+ 4 - 0
components/hal/esp32h2/include/hal/ana_cmpr_ll.h

@@ -38,6 +38,7 @@ static inline void analog_cmpr_ll_enable(analog_cmpr_dev_t *hw, bool en)
  * @param hw Analog comparator register base address
  * @param volt_level The voltage level of the internal reference, range [0.0V, 0.7VDD], step 0.1VDD
  */
+__attribute__((always_inline))
 static inline void analog_cmpr_ll_set_internal_ref_voltage(analog_cmpr_dev_t *hw, uint32_t volt_level)
 {
     hw->pad_comp_config.dref_comp = volt_level;
@@ -90,6 +91,7 @@ static inline void analog_cmpr_ll_set_cross_intr_type(analog_cmpr_dev_t *hw, uin
  * @param hw Analog comparator register base address
  * @param cycle The debounce cycle
  */
+__attribute__((always_inline))
 static inline void analog_cmpr_ll_set_debounce_cycle(analog_cmpr_dev_t *hw, uint32_t cycle)
 {
     hw->pad_comp_filter.zero_det_filter_cnt = cycle;
@@ -116,6 +118,7 @@ static inline void analog_cmpr_ll_enable_intr(analog_cmpr_dev_t *hw, uint32_t ma
  *
  * @param hw Analog comparator register base address
  */
+__attribute__((always_inline))
 static inline uint32_t analog_cmpr_ll_get_intr_status(analog_cmpr_dev_t *hw)
 {
     return hw->int_st.val;
@@ -127,6 +130,7 @@ static inline uint32_t analog_cmpr_ll_get_intr_status(analog_cmpr_dev_t *hw)
  * @param hw Analog comparator register base address
  * @param mask Interrupt status word
  */
+__attribute__((always_inline))
 static inline void analog_cmpr_ll_clear_intr(analog_cmpr_dev_t *hw, uint32_t mask)
 {
     hw->int_clr.val = mask;

+ 4 - 0
examples/peripherals/.build-test-rules.yml

@@ -8,6 +8,10 @@ examples/peripherals/adc/oneshot_read:
   disable:
     - if: SOC_ADC_SUPPORTED != 1
 
+examples/peripherals/analog_comparator:
+  disable:
+    - if: SOC_ANA_CMPR_SUPPORTED != 1
+
 examples/peripherals/dac:
   disable:
     - if: SOC_DAC_SUPPORTED != 1

+ 8 - 0
examples/peripherals/analog_comparator/CMakeLists.txt

@@ -0,0 +1,8 @@
+# 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.16)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(analog_comparator_example)

+ 144 - 0
examples/peripherals/analog_comparator/README.md

@@ -0,0 +1,144 @@
+| Supported Targets | ESP32-H2 |
+| ----------------- | -------- |
+
+# Analog Comparator Example
+
+(See the README.md file in the upper level 'examples' directory for more information about examples.)
+
+This example is going to show how to use the Analog Comparator.
+
+## How to Use Example
+
+### Hardware Requirement
+
+* A development board with any supported Espressif SOC chip (see `Supported Targets` table above)
+* A USB cable for power supply and programming
+* A device to generate the source signal. For example, you can use a ESP SoC that support DAC peripheral (like ESP32 or ESP32S2) to generate source signal, or just use a signal generator.
+
+#### Required Additionally for External Reference
+
+* One more external signal for the reference. You can input a static voltage or a wave, for example, the static voltage can be gotten by the resistor divider, and the wave can be generated by either ESP SoC or a signal generator.
+
+### Example Connection
+
+Let's take ESP32-H2 and ESP32 for example, and we use the DAC on ESP32 as the signal generator (you can use a real signal generator instead if you have one).
+
+#### Internal Reference
+
+Download this example into ESP32-H2 and any DAC examples in `examples/peripherals/dac` directory into ESP32.
+
+```
+     ┌──────────────┐                ┌──────────────┐
+     │   ESP32-H2   │                │     ESP32    │
+     │              │  source signal │              │
+┌────┤GPIO0   GPIO11│◄────┬──────────┤GPIO25        │
+│    │              │     │          │              │
+│    │           GND├─────┼────┬─────┤GND           │
+│    │              │     │    │     │              │
+│    └──────────────┘     │    │     └──────────────┘
+│                         │    │
+│    ┌──────────────┐     │    │
+│    │ Oscilloscope │     │    │
+│    │              │     │    │
+└───►│Probe1  Probe2│◄────┘    │
+     │              │          │
+     │           GND├──────────┘
+     │              │
+     └──────────────┘
+```
+
+#### External Reference
+
+For the static external reference, we can use resistor divider to get the static voltage.
+
+```
+     ┌──────────────┐                ┌──────────────┐
+     │   ESP32-H2   │                │     ESP32    │
+     │              │  source signal │              │
+┌────┤GPIO0   GPIO11│◄────┬──────────┤GPIO25        │
+│    │              │  ref│signal    │              │
+│    │        GPIO10│◄────┼──────┐ ┌─┤GND           │
+│    │              │     │      │ │ │              │
+│    │           GND├─────┼─┬────┼─┘ └──────────────┘
+│    │              │     │ │    │         VDD
+│    └──────────────┘     │ │    │         ─┬─
+│                         │ │    │          │
+│                         │ │    │          ├┐
+│    ┌──────────────┐     │ │    │          ││R1
+│    │ Oscilloscope │     │ │    │          ├┘
+│    │              │     │ │    └──────────┤
+└───►│Probe1  Probe2│◄────┘ │               │
+     │              │       │               ├┐
+     │           GND├───────┤               ││R2
+     │              │       │               ├┘
+     └──────────────┘       │               │
+                            └───────────────┤
+                                            │
+                                           ─┴─
+                                           GND
+```
+
+Also, we can generate a different signal on another DAC channel on ESP32, you can customize your DAC wave using `examples/peripherals/dac/dac_continuous/signal_generator` example.
+
+```
+     ┌──────────────┐                ┌──────────────┐
+     │   ESP32-H2   │                │     ESP32    │
+     │              │  source signal │              │
+┌────┤GPIO0   GPIO11│◄────┬──────────┤GPIO25        │
+│    │              │  ref│signal    │              │
+│    │        GPIO10│◄────┼──────────┤GPIO26        │
+│    │              │     │          │              │
+│    │           GND├─────┼───┬──────┤GND           │
+│    │              │     │   │      │              │
+│    └──────────────┘     │   │      └──────────────┘
+│                         │   │
+│                         │   │
+│    ┌──────────────┐     │   │
+│    │ Oscilloscope │     │   │
+│    │              │     │   │
+└───►│Probe1  Probe2│◄────┘   │
+     │              │         │
+     │           GND├─────────┘
+     │              │
+     └──────────────┘
+```
+
+### Configure the Project
+
+You can decide to adopt internal reference or external reference in the example menu config, and you can also enable hysteresis comparator for the internal reference in the menu config.
+
+### Build and Flash
+
+Build the project and flash it to the board, then run monitor tool to view serial output:
+
+```
+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
+
+### Static Internal Reference
+
+The internal voltage is set to 50% VDD, and input a 50 Hz sine wave as source signal (blue line), the output GPIO toggles every time the sine wave crossing the reference voltage (yellow line)
+
+![static_intl_ref](./static_50p_ref.png)
+
+### Hysteresis Internal Reference
+
+The internal voltage is set to 30% VDD and 70% VDD alternately in this case, the source signal is a 100 Hz sine wave (blue line), the output GPIO toggles every time the source signal exceed 70% VDD and lower than 30% VDD.
+
+![hysteresis_cmpr](./hysteresis_ref.png)
+
+### External Reference
+
+Here we input a 100 Hz sine wave (blue line) as the source signal and input a 1 KHz triangle wave as the reference signal, the output wave (yellow line) can be regarded as a SPWM (Sinusoidal PWM) wave.
+
+![ext_ref](./ext_ref.png)
+
+## Troubleshooting
+
+For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

BIN
examples/peripherals/analog_comparator/ext_ref.png


BIN
examples/peripherals/analog_comparator/hysteresis_ref.png


+ 2 - 0
examples/peripherals/analog_comparator/main/CMakeLists.txt

@@ -0,0 +1,2 @@
+idf_component_register(SRCS "ana_cmpr_example_main.c"
+                    INCLUDE_DIRS ".")

+ 28 - 0
examples/peripherals/analog_comparator/main/Kconfig.projbuild

@@ -0,0 +1,28 @@
+menu "Analog Comparator Example Configuration"
+
+    choice EXAMPLE_REFERENCE_SOURCE
+        prompt "Analog Comparator reference source"
+        default EXAMPLE_INTERNAL_REF
+        help
+            Decide the reference signal comes from internal or external
+
+        config EXAMPLE_INTERNAL_REF
+            bool "Internal reference"
+            help
+                The source signal will refer to an internal voltage, which related to VDD.
+
+        config EXAMPLE_EXTERNAL_REF
+            bool "External reference"
+            help
+                The source signal will refer to the external signal on a specific GPIO.
+    endchoice
+
+    config EXAMPLE_HYSTERESIS_COMPARATOR
+        depends on EXAMPLE_INTERNAL_REF
+        bool "Enable hysteresis comparator"
+        default n
+        help
+            The internal reference voltage will be set to 30% VDD and 70% VDD alternately
+            every time the interrupt triggered.
+
+endmenu

+ 140 - 0
examples/peripherals/analog_comparator/main/ana_cmpr_example_main.c

@@ -0,0 +1,140 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+
+#include <stdio.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "driver/gpio.h"
+#include "driver/ana_cmpr.h"
+#include "esp_log.h"
+
+#define EXAMPLE_ANA_CMPR_UNIT               ANA_CMPR_UNIT_0     // Analog Comparator unit
+#define EXAMPLE_WAIT_TIME_PROP              (0.1)               // The wait time proportion in one relative signal period
+#define EXAMPLE_WAITE_TIME_US(freq_approx)  (1000000.0 * EXAMPLE_WAIT_TIME_PROP / (freq_approx))
+
+#define EXAMPLE_MONITOR_GPIO_NUM            (0)                 // The gpio to monitor the on cross callback
+
+static const char *TAG = "ana_cmpr_example";
+
+static bool IRAM_ATTR example_ana_cmpr_on_cross_callback(ana_cmpr_handle_t cmpr,
+                                                         const ana_cmpr_cross_event_data_t *edata,
+                                                         void *user_ctx)
+{
+#if CONFIG_EXAMPLE_HYSTERESIS_COMPARATOR
+    static ana_cmpr_intl_ref_config_t ref_cfg = {
+        .ref_volt = ANA_CMPR_REF_VOLT_70_PCT_VDD,
+    };
+    bool is_70p = ref_cfg.ref_volt == ANA_CMPR_REF_VOLT_70_PCT_VDD;
+    /* Toggle the GPIO, monitor the gpio on a oscilloscope. */
+    gpio_set_level(EXAMPLE_MONITOR_GPIO_NUM, is_70p);
+    /* Set the internal reference voltage to 30% VDD and 70 %VDD alternately */
+    ana_cmpr_set_intl_reference(cmpr, &ref_cfg);
+    ref_cfg.ref_volt = is_70p ? ANA_CMPR_REF_VOLT_30_PCT_VDD : ANA_CMPR_REF_VOLT_70_PCT_VDD;
+#else
+    static int lvl = 0;
+    /* Toggle the GPIO, monitor the gpio on a oscilloscope. */
+    gpio_set_level(EXAMPLE_MONITOR_GPIO_NUM, lvl);
+    lvl = !lvl;
+#endif
+    return false;
+}
+
+void example_init_analog_comparator(void)
+{
+    /* Step 0: Show the source channel and reference channel GPIO */
+    int src_gpio =  -1;
+    int ext_ref_gpio = -1;
+    ESP_ERROR_CHECK(ana_cmpr_get_gpio(EXAMPLE_ANA_CMPR_UNIT, ANA_CMPR_SOURCE_CHAN, &src_gpio));
+    ESP_ERROR_CHECK(ana_cmpr_get_gpio(EXAMPLE_ANA_CMPR_UNIT, ANA_CMPR_EXT_REF_CHAN, &ext_ref_gpio));
+    ESP_LOGI(TAG, "Analog Comparator source gpio %d, external reference gpio %d", src_gpio, ext_ref_gpio);
+
+    ana_cmpr_handle_t cmpr = NULL;
+
+#if CONFIG_EXAMPLE_INTERNAL_REF
+    /* Step 1: Allocate the new analog comparator unit */
+    ana_cmpr_config_t config = {
+        .unit = EXAMPLE_ANA_CMPR_UNIT,
+        .clk_src = ANA_CMPR_CLK_SRC_DEFAULT,
+        .ref_src = ANA_CMPR_REF_SRC_INTERNAL,
+        .intr_type = ANA_CMPR_INTR_ANY_CROSS,
+    };
+    ESP_ERROR_CHECK(ana_cmpr_new_unit(&config, &cmpr));
+    ESP_LOGI(TAG, "Allocate Analog Comparator with internal reference");
+
+    /* Step 1.1: As we are using the internal reference source, we need to configure the internal reference */
+    ana_cmpr_intl_ref_config_t ref_cfg = {
+#if CONFIG_EXAMPLE_HYSTERESIS_COMPARATOR
+        /* Set the initial internal reference voltage to 70% VDD, it will be updated in the callback every time the interrupt triggered */
+        .ref_volt = ANA_CMPR_REF_VOLT_70_PCT_VDD
+#else
+        .ref_volt = ANA_CMPR_REF_VOLT_50_PCT_VDD,
+#endif
+    };
+    ESP_ERROR_CHECK(ana_cmpr_set_intl_reference(cmpr, &ref_cfg));
+#else
+     /* Step 1: Allocate the new analog comparator unit */
+    ana_cmpr_config_t config = {
+        .unit = EXAMPLE_ANA_CMPR_UNIT,
+        .clk_src = ANA_CMPR_CLK_SRC_DEFAULT,
+        .ref_src = ANA_CMPR_REF_SRC_EXTERNAL,
+        .intr_type = ANA_CMPR_INTR_ANY_CROSS,
+    };
+    ESP_ERROR_CHECK(ana_cmpr_new_unit(&config, &cmpr));
+    ESP_LOGI(TAG, "Allocate Analog Comparator with internal reference");
+#endif
+
+    /* Step 2: (Optional) Set the debounce configuration
+     * It's an optional configuration, if the wait time is set in debounce configuration,
+     * the cross interrupt will be disabled temporary after it is triggered, and it will be enabled
+     * automatically enabled after `wait_us`, so that the duplicate interrupts
+     * can be suppressed while the source signal crossing the reference signal. */
+    ana_cmpr_debounce_config_t dbc_cfg = {
+        /* Normally the `wait_us` is related to how fast the source signal or reference signal changes
+         * comparing to another one. This example adopts an approximate frequency as the relative signal
+         * frequency, and set the default wait time to EXAMPLE_WAIT_TIME_PROP of the relative signal period.
+         * We need to estimate an appropriate `freq_approx` and EXAMPLE_WAIT_TIME_PROP
+         * to make sure the interrupt neither triggers duplicate interrupts, nor misses the next crossing interrupt.
+         * Here we take 1 KHz for example */
+        .wait_us = EXAMPLE_WAITE_TIME_US(1000),
+    };
+    ESP_ERROR_CHECK(ana_cmpr_set_debounce(cmpr, &dbc_cfg));
+
+    /* Step 3: Register the event callbacks */
+    ana_cmpr_event_callbacks_t cbs = {
+        .on_cross = example_ana_cmpr_on_cross_callback,
+    };
+    ESP_ERROR_CHECK(ana_cmpr_register_event_callbacks(cmpr, &cbs, NULL));
+
+    /* Step 4: Enable the analog comparator unit */
+    ESP_ERROR_CHECK(ana_cmpr_enable(cmpr));
+
+#if CONFIG_EXAMPLE_INTERNAL_REF
+    ESP_LOGI(TAG, "Analog comparator enabled, reference voltage: %d%% * VDD", (int)ref_cfg.ref_volt * 10);
+#else
+    ESP_LOGI(TAG, "Analog comparator enabled, external reference selected");
+#endif
+}
+
+void example_init_monitor_gpio(void)
+{
+    gpio_config_t io_conf = {
+        .intr_type = GPIO_INTR_DISABLE,
+        .mode = GPIO_MODE_OUTPUT,
+        .pin_bit_mask = (1ULL << EXAMPLE_MONITOR_GPIO_NUM),
+        .pull_down_en = false,
+        .pull_up_en = false,
+    };
+    gpio_config(&io_conf);
+    gpio_set_level(EXAMPLE_MONITOR_GPIO_NUM, 0);
+}
+
+void app_main(void)
+{
+    /* Initialize GPIO to monitor the comparator interrupt */
+    example_init_monitor_gpio();
+    /* Initialize Analog Comparator */
+    example_init_analog_comparator();
+}

+ 26 - 0
examples/peripherals/analog_comparator/pytest_ana_cmpr_example.py

@@ -0,0 +1,26 @@
+# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: CC0-1.0
+
+import pytest
+from pytest_embedded import Dut
+
+
+@pytest.mark.esp32h2
+@pytest.mark.generic
+@pytest.mark.parametrize(
+    'config',
+    [
+        'intl',
+        'ext',
+    ],
+    indirect=True,
+)
+def test_ana_cmpr_example(dut: Dut) -> None:
+    sdkconfig = dut.app.sdkconfig
+    dut.expect('ana_cmpr_example: Analog Comparator source gpio 11, external reference gpio 10', timeout=10)
+    if sdkconfig['EXAMPLE_INTERNAL_REF']:
+        dut.expect('ana_cmpr_example: Allocate Analog Comparator with internal reference', timeout=10)
+        dut.expect(r'ana_cmpr_example: Analog comparator enabled, reference voltage: [0-9]+% \* VDD', timeout=10)
+    elif sdkconfig['EXAMPLE_EXTERNAL_REF']:
+        dut.expect('ana_cmpr_example: Allocate Analog Comparator with internal reference', timeout=10)
+        dut.expect('ana_cmpr_example: Analog comparator enabled, external reference selected', timeout=10)

+ 2 - 0
examples/peripherals/analog_comparator/sdkconfig.ci.ext

@@ -0,0 +1,2 @@
+CONFIG_EXAMPLE_INTERNAL_REF=n
+CONFIG_EXAMPLE_EXTERNAL_REF=y

+ 2 - 0
examples/peripherals/analog_comparator/sdkconfig.ci.intl

@@ -0,0 +1,2 @@
+CONFIG_EXAMPLE_INTERNAL_REF=y
+CONFIG_EXAMPLE_EXTERNAL_REF=n

BIN
examples/peripherals/analog_comparator/static_50p_ref.png