diff --git a/components/driver/.build-test-rules.yml b/components/driver/.build-test-rules.yml index 7aefd1f0ab..fc4e3f03bd 100644 --- a/components/driver/.build-test-rules.yml +++ b/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 diff --git a/components/driver/analog_comparator/ana_cmpr.c b/components/driver/analog_comparator/ana_cmpr.c index c042455668..1054550bb8 100644 --- a/components/driver/analog_comparator/ana_cmpr.c +++ b/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)); diff --git a/components/driver/analog_comparator/include/driver/ana_cmpr_types.h b/components/driver/analog_comparator/include/driver/ana_cmpr_types.h index d2ea26fc74..8f96c25a3c 100644 --- a/components/driver/analog_comparator/include/driver/ana_cmpr_types.h +++ b/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; /** diff --git a/components/driver/test_apps/analog_comparator/CMakeLists.txt b/components/driver/test_apps/analog_comparator/CMakeLists.txt new file mode 100644 index 0000000000..1b4500c607 --- /dev/null +++ b/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() diff --git a/components/driver/test_apps/analog_comparator/README.md b/components/driver/test_apps/analog_comparator/README.md new file mode 100644 index 0000000000..8161d00f6d --- /dev/null +++ b/components/driver/test_apps/analog_comparator/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-H2 | +| ----------------- | -------- | diff --git a/components/driver/test_apps/analog_comparator/main/CMakeLists.txt b/components/driver/test_apps/analog_comparator/main/CMakeLists.txt new file mode 100644 index 0000000000..beb627afb1 --- /dev/null +++ b/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) diff --git a/components/driver/test_apps/analog_comparator/main/test_ana_cmpr.c b/components/driver/test_apps/analog_comparator/main/test_ana_cmpr.c new file mode 100644 index 0000000000..18389f407b --- /dev/null +++ b/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)); +} diff --git a/components/driver/test_apps/analog_comparator/main/test_ana_cmpr.h b/components/driver/test_apps/analog_comparator/main/test_ana_cmpr.h new file mode 100644 index 0000000000..13566bfc84 --- /dev/null +++ b/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 +#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); diff --git a/components/driver/test_apps/analog_comparator/main/test_ana_cmpr_common.c b/components/driver/test_apps/analog_comparator/main/test_ana_cmpr_common.c new file mode 100644 index 0000000000..9db12a0d07 --- /dev/null +++ b/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); +} diff --git a/components/driver/test_apps/analog_comparator/main/test_ana_cmpr_iram.c b/components/driver/test_apps/analog_comparator/main/test_ana_cmpr_iram.c new file mode 100644 index 0000000000..3b929fb267 --- /dev/null +++ b/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)); +} diff --git a/components/driver/test_apps/analog_comparator/main/test_app_main.c b/components/driver/test_apps/analog_comparator/main/test_app_main.c new file mode 100644 index 0000000000..9b6762fcc8 --- /dev/null +++ b/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(); +} diff --git a/components/driver/test_apps/analog_comparator/pytest_ana_cmpr.py b/components/driver/test_apps/analog_comparator/pytest_ana_cmpr.py new file mode 100644 index 0000000000..1cc5cf4971 --- /dev/null +++ b/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() diff --git a/components/driver/test_apps/analog_comparator/sdkconfig.ci.iram_safe b/components/driver/test_apps/analog_comparator/sdkconfig.ci.iram_safe new file mode 100644 index 0000000000..9d68c306bf --- /dev/null +++ b/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 diff --git a/components/driver/test_apps/analog_comparator/sdkconfig.ci.release b/components/driver/test_apps/analog_comparator/sdkconfig.ci.release new file mode 100644 index 0000000000..91d93f163e --- /dev/null +++ b/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 diff --git a/components/driver/test_apps/analog_comparator/sdkconfig.defaults b/components/driver/test_apps/analog_comparator/sdkconfig.defaults new file mode 100644 index 0000000000..b308cb2ddd --- /dev/null +++ b/components/driver/test_apps/analog_comparator/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=n diff --git a/components/hal/esp32h2/include/hal/ana_cmpr_ll.h b/components/hal/esp32h2/include/hal/ana_cmpr_ll.h index 31e0026cd2..ffedbfde61 100644 --- a/components/hal/esp32h2/include/hal/ana_cmpr_ll.h +++ b/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; diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index 6d8c66f57d..28b1931547 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/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 diff --git a/examples/peripherals/analog_comparator/CMakeLists.txt b/examples/peripherals/analog_comparator/CMakeLists.txt new file mode 100644 index 0000000000..fa9f99dcd0 --- /dev/null +++ b/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) diff --git a/examples/peripherals/analog_comparator/README.md b/examples/peripherals/analog_comparator/README.md new file mode 100644 index 0000000000..8a6adb523c --- /dev/null +++ b/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. diff --git a/examples/peripherals/analog_comparator/ext_ref.png b/examples/peripherals/analog_comparator/ext_ref.png new file mode 100644 index 0000000000..e73485331e Binary files /dev/null and b/examples/peripherals/analog_comparator/ext_ref.png differ diff --git a/examples/peripherals/analog_comparator/hysteresis_ref.png b/examples/peripherals/analog_comparator/hysteresis_ref.png new file mode 100644 index 0000000000..e2707f6db7 Binary files /dev/null and b/examples/peripherals/analog_comparator/hysteresis_ref.png differ diff --git a/examples/peripherals/analog_comparator/main/CMakeLists.txt b/examples/peripherals/analog_comparator/main/CMakeLists.txt new file mode 100644 index 0000000000..e017eb4889 --- /dev/null +++ b/examples/peripherals/analog_comparator/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "ana_cmpr_example_main.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/analog_comparator/main/Kconfig.projbuild b/examples/peripherals/analog_comparator/main/Kconfig.projbuild new file mode 100644 index 0000000000..1e2f780b37 --- /dev/null +++ b/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 diff --git a/examples/peripherals/analog_comparator/main/ana_cmpr_example_main.c b/examples/peripherals/analog_comparator/main/ana_cmpr_example_main.c new file mode 100644 index 0000000000..a885fa6a76 --- /dev/null +++ b/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 +#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(); +} diff --git a/examples/peripherals/analog_comparator/pytest_ana_cmpr_example.py b/examples/peripherals/analog_comparator/pytest_ana_cmpr_example.py new file mode 100644 index 0000000000..a5bba766e5 --- /dev/null +++ b/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) diff --git a/examples/peripherals/analog_comparator/sdkconfig.ci.ext b/examples/peripherals/analog_comparator/sdkconfig.ci.ext new file mode 100644 index 0000000000..3ec04884ce --- /dev/null +++ b/examples/peripherals/analog_comparator/sdkconfig.ci.ext @@ -0,0 +1,2 @@ +CONFIG_EXAMPLE_INTERNAL_REF=n +CONFIG_EXAMPLE_EXTERNAL_REF=y diff --git a/examples/peripherals/analog_comparator/sdkconfig.ci.intl b/examples/peripherals/analog_comparator/sdkconfig.ci.intl new file mode 100644 index 0000000000..ae9cdfdb82 --- /dev/null +++ b/examples/peripherals/analog_comparator/sdkconfig.ci.intl @@ -0,0 +1,2 @@ +CONFIG_EXAMPLE_INTERNAL_REF=y +CONFIG_EXAMPLE_EXTERNAL_REF=n diff --git a/examples/peripherals/analog_comparator/static_50p_ref.png b/examples/peripherals/analog_comparator/static_50p_ref.png new file mode 100644 index 0000000000..202b0ad680 Binary files /dev/null and b/examples/peripherals/analog_comparator/static_50p_ref.png differ