ana_cmpr: add programming guide

This commit is contained in:
laokaiyao 2023-03-13 12:34:53 +08:00
parent 24361f232d
commit 53584bb5a7
19 changed files with 317 additions and 85 deletions

View File

@ -287,8 +287,8 @@ menu "Driver Configurations"
bool "Place Analog Comparator control functions into IRAM"
default n
help
Place Analog Comparator control functions (like set_intl_reference) into IRAM,
so that these functions can be IRAM-safe and able to be called in the other IRAM interrupt context.
Place Analog Comparator control functions (like ana_cmpr_set_internal_reference) into IRAM,
so that these functions can be IRAM-safe and able to be called in an IRAM interrupt context.
Enabling this option can improve driver performance as well.
config ANA_CMPR_ENABLE_DEBUG_LOG

View File

@ -7,7 +7,7 @@
#include <stdlib.h>
#include <inttypes.h>
#include "sdkconfig.h"
#if CONFIG_SDM_ENABLE_DEBUG_LOG
#if CONFIG_ANA_CMPR_ENABLE_DEBUG_LOG
// The local log level must be defined before including esp_log.h
// Set the maximum log level for this source file
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
@ -49,10 +49,10 @@ 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_INTR_FLAG (ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM)
#define ANA_CMPR_INTR_FLAG (ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_IRAM) // Shared with GPIO
#else
#define ANA_CMPR_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
#define ANA_CMPR_INTR_FLAG ESP_INTR_FLAG_LEVEL1
#define ANA_CMPR_INTR_FLAG (ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_SHARED) // Shared with GPIO
#endif
/* Driver tag */
@ -86,7 +86,7 @@ static void IRAM_ATTR s_ana_cmpr_default_intr_handler(void *usr_data)
esp_err_t ana_cmpr_new_unit(const ana_cmpr_config_t *config, ana_cmpr_handle_t *ret_cmpr)
{
#if CONFIG_SDM_ENABLE_DEBUG_LOG
#if CONFIG_ANA_CMPR_ENABLE_DEBUG_LOG
esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
ANA_CMPR_NULL_POINTER_CHECK(config);
@ -120,17 +120,19 @@ esp_err_t ana_cmpr_new_unit(const ana_cmpr_config_t *config, ana_cmpr_handle_t *
CLK_TREE_SRC_FREQ_PRECISION_CACHED,
&s_ana_cmpr[unit]->src_clk_freq_hz),
err, TAG, "get source clock frequency failed");
ESP_GOTO_ON_ERROR(io_mux_set_clock_source((soc_module_clk_t)(config->clk_src)), err, TAG, "set IO MUX clock source failed");
ESP_GOTO_ON_ERROR(io_mux_set_clock_source((soc_module_clk_t)(config->clk_src)), err, TAG,
"potential clock source conflicts from other IOMUX peripherals");
/* Configure the register */
portENTER_CRITICAL(&s_spinlock);
analog_cmpr_ll_ref_source(s_ana_cmpr[unit]->dev, config->ref_src);
analog_cmpr_ll_set_cross_intr_type(s_ana_cmpr[unit]->dev, config->intr_type);
analog_cmpr_ll_set_ref_source(s_ana_cmpr[unit]->dev, config->ref_src);
analog_cmpr_ll_set_cross_type(s_ana_cmpr[unit]->dev, config->cross_type);
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, ANA_CMPR_INTR_FLAG,
s_ana_cmpr_default_intr_handler, s_ana_cmpr[unit], &s_ana_cmpr[unit]->intr_handle);
ESP_GOTO_ON_ERROR(esp_intr_alloc_intrstatus(ETS_GPIO_INTR_SOURCE, ANA_CMPR_INTR_FLAG, (uint32_t)analog_cmpr_ll_get_intr_status_reg(s_ana_cmpr[unit]->dev),
ANALOG_CMPR_LL_EVENT_CROSS, s_ana_cmpr_default_intr_handler, s_ana_cmpr[unit], &s_ana_cmpr[unit]->intr_handle),
err, TAG, "allocate interrupt failed");
if (config->ref_src == ANA_CMPR_REF_SRC_INTERNAL) {
ESP_LOGD(TAG, "unit %d allocated, source signal: GPIO %d, reference signal: internal",
@ -144,9 +146,8 @@ esp_err_t ana_cmpr_new_unit(const ana_cmpr_config_t *config, ana_cmpr_handle_t *
return ESP_OK;
err:
/* Free the resources if allocation failed */
free(s_ana_cmpr[unit]);
s_ana_cmpr[unit] = NULL;
/* Delete the unit if allocation failed */
ana_cmpr_del_unit(s_ana_cmpr[unit]);
return ret;
}
@ -164,18 +165,16 @@ esp_err_t ana_cmpr_del_unit(ana_cmpr_handle_t cmpr)
ESP_RETURN_ON_FALSE(unit >= ANA_CMPR_UNIT_0, ESP_ERR_INVALID_ARG, TAG, "wrong analog comparator handle");
ESP_RETURN_ON_FALSE(!cmpr->is_enabled, ESP_ERR_INVALID_STATE, TAG, "this analog comparator unit not disabled yet");
/* Disable the Analog Comparator interrupt */
portENTER_CRITICAL(&s_spinlock);
analog_cmpr_ll_enable_intr(cmpr->dev, ANALOG_CMPR_LL_EVENT_CROSS, false);
portEXIT_CRITICAL(&s_spinlock);
/* Delete the pm lock if the unit has */
if (cmpr->pm_lock) {
ESP_RETURN_ON_ERROR(esp_pm_lock_delete(cmpr->pm_lock), TAG, "delete pm lock failed");
}
/* Free interrupt and other resources */
esp_intr_free(cmpr->intr_handle);
if (cmpr->intr_handle) {
esp_intr_free(cmpr->intr_handle);
}
free(s_ana_cmpr[unit]);
s_ana_cmpr[unit] = NULL;
@ -184,7 +183,7 @@ esp_err_t ana_cmpr_del_unit(ana_cmpr_handle_t cmpr)
return ESP_OK;
}
esp_err_t ana_cmpr_set_intl_reference(ana_cmpr_handle_t cmpr, const ana_cmpr_intl_ref_config_t *ref_cfg)
esp_err_t ana_cmpr_set_internal_reference(ana_cmpr_handle_t cmpr, const ana_cmpr_internal_ref_config_t *ref_cfg)
{
ANA_CMPR_NULL_POINTER_CHECK_ISR(cmpr);
ANA_CMPR_NULL_POINTER_CHECK_ISR(ref_cfg);
@ -207,7 +206,7 @@ esp_err_t ana_cmpr_set_debounce(ana_cmpr_handle_t cmpr, const ana_cmpr_debounce_
ANA_CMPR_NULL_POINTER_CHECK_ISR(dbc_cfg);
/* Transfer the time to clock cycles */
uint32_t wait_cycle = (uint32_t)(dbc_cfg->wait_us * cmpr->src_clk_freq_hz) / 1000000;
uint32_t wait_cycle = (uint32_t)(dbc_cfg->wait_us * (cmpr->src_clk_freq_hz / 1000000));
/* Set the waiting clock cycles */
portENTER_CRITICAL_SAFE(&s_spinlock);
analog_cmpr_ll_set_debounce_cycle(cmpr->dev, wait_cycle);
@ -218,6 +217,21 @@ esp_err_t ana_cmpr_set_debounce(ana_cmpr_handle_t cmpr, const ana_cmpr_debounce_
return ESP_OK;
}
esp_err_t ana_cmpr_set_cross_type(ana_cmpr_handle_t cmpr, ana_cmpr_cross_type_t cross_type)
{
ANA_CMPR_NULL_POINTER_CHECK_ISR(cmpr);
ESP_RETURN_ON_FALSE_ISR(cross_type >= ANA_CMPR_CROSS_DISABLE && cross_type <= ANA_CMPR_CROSS_ANY,
ESP_ERR_INVALID_ARG, TAG, "invalid cross type");
portENTER_CRITICAL_SAFE(&s_spinlock);
analog_cmpr_ll_set_cross_type(cmpr->dev, cross_type);
portEXIT_CRITICAL_SAFE(&s_spinlock);
ESP_EARLY_LOGD(TAG, "unit %d cross type updated to %d", (int)cmpr->unit, cross_type);
return ESP_OK;
}
esp_err_t ana_cmpr_register_event_callbacks(ana_cmpr_handle_t cmpr, const ana_cmpr_event_callbacks_t *cbs, void *user_data)
{
ANA_CMPR_NULL_POINTER_CHECK(cmpr);
@ -227,7 +241,11 @@ esp_err_t ana_cmpr_register_event_callbacks(ana_cmpr_handle_t cmpr, const ana_cm
#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");
"ANA_CMPR_ISR_IRAM_SAFE enabled but the callback function is not in IRAM");
}
if (user_data) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(user_data), ESP_ERR_INVALID_ARG, TAG,
"ANA_CMPR_ISR_IRAM_SAFE enabled but the user_data is not in IRAM");
}
#endif
@ -235,11 +253,6 @@ esp_err_t ana_cmpr_register_event_callbacks(ana_cmpr_handle_t cmpr, const ana_cm
memcpy(&(cmpr->cbs), cbs, sizeof(ana_cmpr_event_callbacks_t));
cmpr->user_data = user_data;
/* Enable the Analog Comparator interrupt */
portENTER_CRITICAL(&s_spinlock);
analog_cmpr_ll_enable_intr(cmpr->dev, ANALOG_CMPR_LL_EVENT_CROSS, !!(cbs->on_cross));
portEXIT_CRITICAL(&s_spinlock);
ESP_LOGD(TAG, "unit %d event callback registered", (int)cmpr->unit);
return ESP_OK;
@ -260,6 +273,7 @@ esp_err_t ana_cmpr_enable(ana_cmpr_handle_t cmpr)
/* Enable the Analog Comparator */
portENTER_CRITICAL(&s_spinlock);
analog_cmpr_ll_enable_intr(cmpr->dev, ANALOG_CMPR_LL_EVENT_CROSS, !!(cmpr->cbs.on_cross));
analog_cmpr_ll_enable(cmpr->dev, true);
portEXIT_CRITICAL(&s_spinlock);
@ -275,6 +289,7 @@ esp_err_t ana_cmpr_disable(ana_cmpr_handle_t cmpr)
"the analog comparator not enabled yet");
/* Disable the Analog Comparator */
portENTER_CRITICAL(&s_spinlock);
analog_cmpr_ll_enable_intr(cmpr->dev, ANALOG_CMPR_LL_EVENT_CROSS, false);
analog_cmpr_ll_enable(cmpr->dev, false);
portEXIT_CRITICAL(&s_spinlock);

View File

@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
@ -26,7 +27,7 @@ typedef struct {
* For internal reference, the reference voltage should be set to `internal_ref_volt`,
* for external reference, the reference signal should be connect to `ANA_CMPRx_EXT_REF_GPIO`
*/
ana_cmpr_intr_type_t intr_type; /*!< The crossing types that can trigger interrupt */
ana_cmpr_cross_type_t cross_type; /*!< The crossing types that can trigger interrupt */
} ana_cmpr_config_t;
/**
@ -34,21 +35,21 @@ typedef struct {
*
*/
typedef struct {
ana_cmpr_ref_voltage_t ref_volt; /*!< The internal reference voltage. It can specify several dozen percent from the VDD power supply,
* currently supports 0%~70% VDD with step 10%
ana_cmpr_ref_voltage_t ref_volt; /*!< The internal reference voltage. It can be specified to a certain fixed percentage of
* the VDD power supply, currently supports 0%~70% VDD with a step 10%
*/
} ana_cmpr_intl_ref_config_t;
} ana_cmpr_internal_ref_config_t;
/**
* @brief Analog comparator debounce filter configuration
*
*/
typedef struct {
float wait_us; /*!< The wait time of re-enabling the interrupt after the last triggering,
uint32_t wait_us; /*!< The wait time of re-enabling the interrupt after the last triggering,
* it is used to avoid the spurious triggering while the source signal crossing the reference signal.
* The value should regarding how fast the source signal changes, e.g., a rapid signal requires
* a small wait time, otherwise the next crosses may be missed.
* (Unit: micro second, resolution = 1 / SRC_CLK_FREQ)
* (Unit: micro second)
*/
} ana_cmpr_debounce_config_t;
@ -99,8 +100,9 @@ esp_err_t ana_cmpr_del_unit(ana_cmpr_handle_t cmpr);
* @return
* - ESP_OK Set denounce configuration success
* - ESP_ERR_INVALID_ARG NULL pointer of the parameters
* - ESP_ERR_INVALID_STATE The reference source is not `ANA_CMPR_REF_SRC_INTERNAL`
*/
esp_err_t ana_cmpr_set_intl_reference(ana_cmpr_handle_t cmpr, const ana_cmpr_intl_ref_config_t *ref_cfg);
esp_err_t ana_cmpr_set_internal_reference(ana_cmpr_handle_t cmpr, const ana_cmpr_internal_ref_config_t *ref_cfg);
/**
* @brief Set debounce configuration to the analog comparator
@ -116,6 +118,21 @@ esp_err_t ana_cmpr_set_intl_reference(ana_cmpr_handle_t cmpr, const ana_cmpr_int
*/
esp_err_t ana_cmpr_set_debounce(ana_cmpr_handle_t cmpr, const ana_cmpr_debounce_config_t *dbc_cfg);
/**
* @brief Set the source signal cross type
* @note The initial cross type is configured in `ana_cmpr_new_unit`, this function can update the cross type
* @note This function is allowed to run within ISR context including intr callbacks
* @note This function will be placed into IRAM if `CONFIG_ANA_CMPR_CTRL_FUNC_IN_IRAM` is on,
* so that it's allowed to be executed when Cache is disabled
*
* @param[in] cmpr The handle of analog comparator unit
* @param[in] cross_type The source signal cross type that can trigger the interrupt
* @return
* - ESP_OK Set denounce configuration success
* - ESP_ERR_INVALID_ARG NULL pointer of the parameters
*/
esp_err_t ana_cmpr_set_cross_type(ana_cmpr_handle_t cmpr, ana_cmpr_cross_type_t cross_type);
/**
* @brief Register analog comparator interrupt event callbacks
* @note This function can only be called before enabling the unit

View File

@ -44,11 +44,11 @@ typedef enum {
*
*/
typedef enum {
ANA_CMPR_INTR_DISABLE, /*!< Disable the cross event interrupt */
ANA_CMPR_INTR_POS_CROSS, /*!< Enable the positive cross event interrupt */
ANA_CMPR_INTR_NEG_CROSS, /*!< Enable the negative cross event interrupt */
ANA_CMPR_INTR_ANY_CROSS, /*!< Enable the both positive & negative cross event interrupt */
} ana_cmpr_intr_type_t;
ANA_CMPR_CROSS_DISABLE, /*!< Disable the cross event interrupt */
ANA_CMPR_CROSS_POS, /*!< Positive cross can trigger event interrupt */
ANA_CMPR_CROSS_NEG, /*!< Negative cross can trigger event interrupt */
ANA_CMPR_CROSS_ANY, /*!< Any cross can trigger event interrupt */
} ana_cmpr_cross_type_t;
/**
* @brief Analog comparator internal reference voltage

View File

@ -582,6 +582,9 @@ esp_err_t gpio_isr_register(void (*fn)(void *), void *arg, int intr_alloc_flags,
gpio_isr_alloc_t p;
p.source = ETS_GPIO_INTR_SOURCE;
p.intr_alloc_flags = intr_alloc_flags;
#if SOC_ANA_CMPR_SUPPORTED
p.intr_alloc_flags |= ESP_INTR_FLAG_SHARED;
#endif
p.fn = fn;
p.arg = arg;
p.handle = handle;

View File

@ -19,8 +19,9 @@ entries:
if SDM_CTRL_FUNC_IN_IRAM = y:
sdm: sdm_channel_set_pulse_density (noflash)
if ANA_CMPR_CTRL_FUNC_IN_IRAM = y:
ana_cmpr: ana_cmpr_set_intl_reference (noflash)
ana_cmpr: ana_cmpr_set_internal_reference (noflash)
ana_cmpr: ana_cmpr_set_debounce (noflash)
ana_cmpr: ana_cmpr_set_cross_type (noflash)
if DAC_CTRL_FUNC_IN_IRAM = y:
dac_oneshot: dac_oneshot_output_voltage (noflash)
dac_continuous: dac_continuous_write_asynchronously (noflash)

View File

@ -13,7 +13,7 @@ TEST_CASE("ana_cmpr_unit_install_uninstall", "[ana_cmpr]")
.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,
.cross_type = ANA_CMPR_CROSS_ANY,
};
/* Allocate a wrong unit */
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, ana_cmpr_new_unit(&config, &cmpr));
@ -23,15 +23,15 @@ TEST_CASE("ana_cmpr_unit_install_uninstall", "[ana_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 = {
ana_cmpr_internal_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_internal_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));
TEST_ESP_OK(ana_cmpr_set_internal_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 */
@ -44,7 +44,7 @@ TEST_CASE("ana_cmpr_unit_install_uninstall", "[ana_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_ERR(ESP_ERR_INVALID_STATE, ana_cmpr_set_internal_reference(cmpr, &ref_cfg));
TEST_ESP_OK(ana_cmpr_del_unit(cmpr));
}
@ -58,13 +58,13 @@ TEST_CASE("ana_cmpr_internal_reference", "[ana_cmpr]")
.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,
.cross_type = ANA_CMPR_CROSS_ANY,
};
TEST_ESP_OK(ana_cmpr_new_unit(&config, &cmpr));
ana_cmpr_intl_ref_config_t ref_cfg = {
ana_cmpr_internal_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_internal_reference(cmpr, &ref_cfg));
ana_cmpr_debounce_config_t dbc_cfg = {
.wait_us = 10.0,
};

View File

@ -14,29 +14,23 @@ typedef struct {
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 = {
ana_cmpr_internal_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);
ana_cmpr_set_internal_reference(data->handle, &ref_cfg);
ana_cmpr_debounce_config_t dbc_cfg = {
.wait_us = 1,
};
ana_cmpr_set_debounce(data->handle, &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);
}
ana_cmpr_set_cross_type(data->handle, ANA_CMPR_CROSS_POS);
}
TEST_CASE("ana_cmpr_internal_reference_iram_safe", "[ana_cmpr]")
@ -53,15 +47,18 @@ TEST_CASE("ana_cmpr_internal_reference_iram_safe", "[ana_cmpr]")
.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,
.cross_type = ANA_CMPR_CROSS_ANY,
};
TEST_ESP_OK(ana_cmpr_new_unit(&config, &cmpr));
test_data.handle = cmpr;
ana_cmpr_intl_ref_config_t ref_cfg = {
ana_cmpr_internal_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));
TEST_ESP_OK(ana_cmpr_set_internal_reference(cmpr, &ref_cfg));
ana_cmpr_debounce_config_t dbc_cfg = {
.wait_us = 10,
};
TEST_ESP_OK(ana_cmpr_set_debounce(cmpr, &dbc_cfg));
ana_cmpr_event_callbacks_t cbs = {
.on_cross = test_ana_cmpr_on_cross_callback,
};

View File

@ -63,7 +63,7 @@ static inline float analog_cmpr_ll_get_internal_ref_voltage(analog_cmpr_dev_t *h
* @param hw Analog comparator register base address
* @param ref_src reference source, 0 for internal, 1 for external GPIO pad (GPIO10)
*/
static inline void analog_cmpr_ll_ref_source(analog_cmpr_dev_t *hw, uint32_t ref_src)
static inline void analog_cmpr_ll_set_ref_source(analog_cmpr_dev_t *hw, uint32_t ref_src)
{
hw->pad_comp_config.mode_comp = ref_src;
}
@ -78,7 +78,8 @@ static inline void analog_cmpr_ll_ref_source(analog_cmpr_dev_t *hw, uint32_t ref
* - 2: enable negative cross interrupt (input analog goes from high to low and across the reference voltage)
* - 3: enable any positive or negative cross interrupt
*/
static inline void analog_cmpr_ll_set_cross_intr_type(analog_cmpr_dev_t *hw, uint8_t type)
__attribute__((always_inline))
static inline void analog_cmpr_ll_set_cross_type(analog_cmpr_dev_t *hw, uint8_t type)
{
hw->pad_comp_config.zero_det_mode = type;
}

View File

@ -87,10 +87,6 @@ config SOC_SDM_SUPPORTED
bool
default y
config SOC_ANALOG_CMPR_SUPPORTED
bool
default y
config SOC_ETM_SUPPORTED
bool
default y

View File

@ -46,7 +46,6 @@
#define SOC_RTC_MEM_SUPPORTED 1
#define SOC_I2S_SUPPORTED 1
#define SOC_SDM_SUPPORTED 1
#define SOC_ANALOG_CMPR_SUPPORTED 1
#define SOC_ETM_SUPPORTED 1
#define SOC_RMT_SUPPORTED 1
#define SOC_PARLIO_SUPPORTED 1

View File

@ -127,6 +127,8 @@ ADC_DOCS = ['api-reference/peripherals/adc_oneshot.rst',
'api-reference/peripherals/adc_calibration.rst']
ADC_DMA_DOCS = ['api-reference/peripherals/adc_continuous.rst']
ANA_CMPR_DOCS = ['api-reference/peripherals/ana_cmpr.rst']
ESP32_DOCS = ['api-reference/system/himem.rst',
'api-guides/romconsole.rst',
'api-reference/system/ipc.rst',
@ -194,6 +196,7 @@ conditional_include_dict = {'SOC_BT_SUPPORTED':BT_DOCS,
'SOC_RTC_MEM_SUPPORTED': RTC_MEM_DOCS,
'SOC_ADC_SUPPORTED':ADC_DOCS,
'SOC_ADC_DMA_SUPPORTED':ADC_DMA_DOCS,
'SOC_ANA_CMPR_SUPPORTED': ANA_CMPR_DOCS,
'SOC_SDM_SUPPORTED':SDM_DOCS,
'SOC_WIFI_MESH_SUPPORT':WIFI_MESH_DOCS,
'esp32':ESP32_DOCS,

View File

@ -61,6 +61,8 @@ INPUT = \
$(PROJECT_PATH)/components/bt/host/nimble/esp-hci/include/esp_nimble_hci.h \
$(PROJECT_PATH)/components/bt/include/esp32/include/esp_bt.h \
$(PROJECT_PATH)/components/console/esp_console.h \
$(PROJECT_PATH)/components/driver/analog_comparator/include/driver/ana_cmpr.h \
$(PROJECT_PATH)/components/driver/analog_comparator/include/driver/ana_cmpr_types.h \
$(PROJECT_PATH)/components/driver/dac/include/driver/dac_continuous.h \
$(PROJECT_PATH)/components/driver/dac/include/driver/dac_cosine.h \
$(PROJECT_PATH)/components/driver/dac/include/driver/dac_oneshot.h \

View File

@ -0,0 +1,195 @@
Analog Comparator
=================
{IDF_TARGET_ANA_CMPR_NUM: default="NOT UPDATED", esp32h2="one"}
{IDF_TARGET_ANA_CMPR_SRC_CHAN: default="NOT UPDATED", esp32h2="GPIO11"}
{IDF_TARGET_ANA_CMPR_EXT_REF_CHAN: default="NOT UPDATED", esp32h2="GPIO10"}
Introduction
------------
Analog Comparator is a peripheral that can be used to compare a source signal with the internal reference voltage or an external reference signal.
It is a cost effective way to replace an amplifier comparator in some scenarios. But unlike the continuous comparing of the amplifier comparator, ESP Analog Comparator is driven by a source clock, which decides the sampling frequency.
Analog Comparator on {IDF_TARGET_NAME} has {IDF_TARGET_ANA_CMPR_NUM} unit(s), the channels in the unit(s) are:
**UNIT0**
- Source Channel: {IDF_TARGET_ANA_CMPR_SRC_CHAN}
- External Reference Channel: {IDF_TARGET_ANA_CMPR_EXT_REF_CHAN}
- Internal Reference Channel: Range 0% ~ 70% VDD, step 10% VDD
Functional Overview
-------------------
The following sections of this document cover the typical steps to install and operate an Analog Comparator unit:
- `Resource Allocation <#resource-allocation>`__ - covers which parameters should be set up to get a unit handle and how to recycle the resources when it finishes working.
- `Further Configurations <#further-configurations>`__ - covers the other configurations that might need to specific and what they are used for.
- `Enable and Disable Unit <#enable-and-disable-unit>`__ - covers how to enable and disable the unit.
- `Power Management <#power-management>`__ - describes how different source clock selections can affect power consumption.
- `IRAM Safe <#iram-safe>`__ - lists which functions are supposed to work even when the cache is disabled.
- `Thread Safety <#thread-safety>`__ - lists which APIs are guaranteed to be thread safe by the driver.
- `Kconfig Options <#kconfig-options>`__ - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
Resource Allocation
^^^^^^^^^^^^^^^^^^^
An Analog Comparator unit channel is represented by :cpp:type:`ana_cmpr_handle_t`. Each unit can support either an internal or an external reference.
To allocate the resource of the Analog Comparator unit, :cpp:func:`ana_cmpr_new_unit` need to be called to get the handle of the unit. Configurations :cpp:type:`ana_cmpr_config_t` need to be specified while allocating the unit:
- :cpp:member:`ana_cmpr_config_t::unit` selects the Analog Comparator unit.
- :cpp:member:`ana_cmpr_config_t::clk_src` selects the source clock for Analog Comparator, it can affect the sampling frequency. Note that the clock source of the Analog Comparator comes from the io mux, it is shared with GPIO extension peripherals like SDM (Sigma-Delta Modulation) and Glitch Filter. The configuration will fail if you specific different clock sources for multiple GPIO extension peripherals. The default clock sources of these peripherals are same, typically, we select :cpp:enumerator:`soc_periph_ana_cmpr_clk_src_t::ANA_CMPR_CLK_SRC_DEFAULT` as the clock source.
- :cpp:member:`ana_cmpr_config_t::ref_src` selects the reference source from internal voltage or external signal (from {IDF_TARGET_ANA_CMPR_EXT_REF_CHAN}).
- :cpp:member:`ana_cmpr_config_t::cross_type` selects which kind of cross type can trigger the interrupt.
The function :cpp:func:`ana_cmpr_new_unit` can fail due to various errors such as insufficient memory, invalid arguments, etc. If a previously created Analog Comparator unit is no longer required, you should recycle it by calling :cpp:func:`ana_cmpr_del_unit`. It allows the underlying HW channel to be used for other purposes. Before deleting an Analog Comparator unit handle, you should disable it by :cpp:func:`ana_cmpr_unit_disable` in advance, or make sure it has not enabled yet by :cpp:func:`ana_cmpr_unit_enable`.
.. code:: c
#include "driver/ana_cmpr.h"
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,
.cross_type = ANA_CMPR_CROSS_ANY,
};
ESP_ERROR_CHECK(ana_cmpr_new_unit(&config, &cmpr));
// ...
ESP_ERROR_CHECK(ana_cmpr_del_unit(cmpr));
Further Configurations
^^^^^^^^^^^^^^^^^^^^^^
- :cpp:func:`ana_cmpr_set_intl_reference` - Specify the internal reference voltage when :cpp:enumerator:`ana_cmpr_ref_source_t::ANA_CMPR_REF_SRC_INTERNAL` is selected as reference source.
It requires :cpp:member:`ana_cmpr_internal_ref_config_t::ref_volt` to specify the voltage. The voltage related to the VDD power supply, which can only support a certain fixed percentage of VDD. Currently on {IDF_TARGET_NAME}, the internal reference voltage can be range to 0~70% VDD with a step 10%.
.. code:: c
#include "driver/ana_cmpr.h"
ana_cmpr_internal_ref_config_t ref_cfg = {
.ref_volt = ANA_CMPR_REF_VOLT_50_PCT_VDD,
};
ESP_ERROR_CHECK(ana_cmpr_set_internal_reference(cmpr, &ref_cfg));
- :cpp:func:`ana_cmpr_set_debounce` - Set the debounce configuration.
It requires :cpp:member:`ana_cmpr_debounce_config_t::wait_us` to set the interrupt waiting time. The interrupt will be disabled temporary for :cpp:member:`ana_cmpr_debounce_config_t::wait_us` micro seconds, so that the frequent triggering can be avoid while the source signal crossing the reference signal. That is, the waiting time is supposed to be inverse ratio to the relative frequency between the source and reference. If the waiting time is set too short, it can't bypass the jitter totally, but if too long, the next crossing interrupt might be missed.
.. code:: c
#include "driver/ana_cmpr.h"
ana_cmpr_debounce_config_t dbc_cfg = {
.wait_us = 1,
};
ESP_ERROR_CHECK(ana_cmpr_set_debounce(cmpr, &dbc_cfg));
- :cpp:func:`ana_cmpr_set_cross_type` - Set the source signal cross type.
The initial cross type is set int :cpp:func:`ana_cmpr_new_unit`, this function can update the cross type, even in ISR context.
.. code:: c
#include "driver/ana_cmpr.h"
ESP_ERROR_CHECK(ana_cmpr_set_cross_type(cmpr, ANA_CMPR_CROSS_POS));
- :cpp:func:`ana_cmpr_register_event_callbacks` - Register the callbacks.
Currently it supports :cpp:member:`ana_cmpr_event_callbacks_t::on_cross`, it will be called when the crossing event (specified by :cpp:member:`ana_cmpr_config_t::cross_type`) occurs.
.. code:: c
#include "driver/ana_cmpr.h"
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)
{
// ...
return false;
}
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));
.. note::
When :ref:`CONFIG_ANA_CMPR_ISR_IRAM_SAFE` is enabled, you should guarantee the callback context and involved data to be in internal RAM by add the attribute ``IRAM_ATTR``. (See more in `IRAM Safe <#iram-safe>`__)
Enable and Disable Unit
^^^^^^^^^^^^^^^^^^^^^^^
- :cpp:func:`ana_cmpr_enable` - Enable the Analog Comparator unit.
- :cpp:func:`ana_cmpr_disable` - Disable the Analog Comparator unit.
After the Analog Comparator unit is enabled and the crossing event interrupt is enabled, a power management lock will be acquired if the power management is enabled (see `Power Management <#power-management>`__). Under the **enable** state, only :cpp:func:`ana_cmpr_set_intl_reference` and :cpp:func:`ana_cmpr_set_debounce` can be called, other functions can only be called after the unit is disabled.
Calling :cpp:func:`ana_cmpr_disable` will do the opposite.
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled (i.e. :ref:`CONFIG_PM_ENABLE` is on), the system will adjust the APB frequency before going into light sleep, thus potentially changing the resolution of the Analog Comparator.
However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_NO_LIGHT_SLEEP`. Whenever the driver creates a Analog Comparator unit instance that has selected the clock source like :cpp:enumerator:`ANA_CMPR_CLK_SRC_DEFAULT` or :cpp:enumerator:`ANA_CMPR_CLK_SRC_XTAL` as its clock source, the driver will guarantee that the power management lock is acquired when enable the channel by :cpp:func:`ana_cmpr_enable`. Likewise, the driver releases the lock when :cpp:func:`ana_cmpr_disable` is called for that channel.
IRAM Safe
^^^^^^^^^
By default, the Analog Comparator interrupt will be deferred when the Cache is disabled for reasons like programming/erasing Flash. Thus the alarm interrupt will not get executed in time, which is not expected in a real-time application.
There's a Kconfig option :ref:`CONFIG_ANA_CMPR_ISR_IRAM_SAFE` that will:
1. Enable the interrupt being serviced even when cache is disabled
2. Place all functions that used by the ISR into IRAM [1]_
3. Place driver object into DRAM (in case it's allocated on PSRAM)
This will allow the interrupt to run while the cache is disabled but will come at the cost of increased IRAM consumption.
There's a Kconfig option :ref:`CONFIG_ANA_CMPR_CTRL_FUNC_IN_IRAM` that can put commonly used IO control functions into IRAM as well. So that these functions can also be executable when the cache is disabled. These IO control functions are listed as follows:
- :cpp:func:`ana_cmpr_set_internal_reference`
- :cpp:func:`ana_cmpr_set_debounce`
- :cpp:func:`ana_cmpr_set_cross_type`
Thread Safety
^^^^^^^^^^^^^
The factory function :cpp:func:`ana_cmpr_new_unit` is guaranteed to be thread safe by the driver, which means, user can call it from different RTOS tasks without protection by extra locks.
The following functions are allowed to run under ISR context, the driver uses a critical section to prevent them being called concurrently in both task and ISR.
- :cpp:func:`ana_cmpr_set_internal_reference`
- :cpp:func:`ana_cmpr_set_debounce`
- :cpp:func:`ana_cmpr_set_cross_type`
Other functions that take the :cpp:type:`ana_cmpr_handle_t` as the first positional parameter, are not treated as thread safe. Which means the user should avoid calling them from multiple tasks.
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_ANA_CMPR_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see `IRAM Safe <#iram-safe>`__ for more information.
- :ref:`CONFIG_ANA_CMPR_CTRL_FUNC_IN_IRAM` controls where to place the Analog Comparator control functions (IRAM or Flash), see `IRAM Safe <#iram-safe>`__ for more information.
- :ref:`CONFIG_ANA_CMPR_ENABLE_DEBUG_LOG` is used to enabled the debug log output. Enabling this option will increase the firmware binary size.
Application Example
-------------------
* :example:`peripherals/analog_comparator` shows the basic usage of the analog comparator, and other potential usages like hysteresis comparator and SPWM generator.
API Reference
-------------
.. include-build-file:: inc/ana_cmpr.inc
.. include-build-file:: inc/ana_cmpr_types.inc
.. [1]
:cpp:member:`ana_cmpr_event_callbacks_t::on_cross` callback and the functions invoked by itself should also be placed in IRAM, you need to take care of them by themselves.

View File

@ -9,6 +9,7 @@ Peripherals API
:SOC_ADC_SUPPORTED: adc_oneshot
:SOC_ADC_DMA_SUPPORTED: adc_continuous
:SOC_ADC_SUPPORTED: adc_calibration
:SOC_ANA_CMPR_SUPPORTED: ana_cmpr
clk_tree
:SOC_DAC_SUPPORTED: dac
:SOC_ETM_SUPPORTED: etm

View File

@ -0,0 +1 @@
.. include:: ../../../en/api-reference/peripherals/ana_cmpr.rst

View File

@ -9,6 +9,7 @@
:SOC_ADC_SUPPORTED: adc_oneshot
:SOC_ADC_DMA_SUPPORTED: adc_continuous
:SOC_ADC_SUPPORTED: adc_calibration
:SOC_ANA_CMPR_SUPPORTED: ana_cmpr
clk_tree
:SOC_DAC_SUPPORTED: dac
:SOC_ETM_SUPPORTED: etm

View File

@ -13,25 +13,25 @@
#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_WAITE_TIME_US(freq_approx) (uint32_t)(1000000 * 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,
static bool 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 = {
static ana_cmpr_internal_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. */
/* Toggle the GPIO, monitor the gpio on an 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);
ana_cmpr_set_internal_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;
@ -59,13 +59,13 @@ void example_init_analog_comparator(void)
.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,
.cross_type = ANA_CMPR_CROSS_ANY,
};
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 = {
ana_cmpr_internal_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
@ -73,17 +73,17 @@ void example_init_analog_comparator(void)
.ref_volt = ANA_CMPR_REF_VOLT_50_PCT_VDD,
#endif
};
ESP_ERROR_CHECK(ana_cmpr_set_intl_reference(cmpr, &ref_cfg));
ESP_ERROR_CHECK(ana_cmpr_set_internal_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,
.cross_type = ANA_CMPR_CROSS_ANY,
};
ESP_ERROR_CHECK(ana_cmpr_new_unit(&config, &cmpr));
ESP_LOGI(TAG, "Allocate Analog Comparator with internal reference");
ESP_LOGI(TAG, "Allocate Analog Comparator with external reference");
#endif
/* Step 2: (Optional) Set the debounce configuration
@ -92,7 +92,7 @@ void example_init_analog_comparator(void)
* 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
/* Normally the `wait_us` is related to the relative frequency between the source and reference signal
* 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

View File

@ -22,5 +22,5 @@ def test_ana_cmpr_example(dut: Dut) -> None:
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: Allocate Analog Comparator with external reference', timeout=10)
dut.expect('ana_cmpr_example: Analog comparator enabled, external reference selected', timeout=10)