ana_cmpr: add an example and test cases

This commit is contained in:
laokaiyao 2023-03-07 12:09:07 +08:00
parent c634144ac8
commit 24361f232d
28 changed files with 747 additions and 8 deletions

View File

@ -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

View File

@ -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));

View File

@ -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;
/**

View File

@ -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()

View File

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

View File

@ -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)

View File

@ -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));
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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));
}

View File

@ -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();
}

View File

@ -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()

View File

@ -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

View File

@ -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

View File

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

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

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

View File

@ -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

View File

@ -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();
}

View File

@ -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)

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB