From c0601e512bea4c6996c5ac0f828cfbd7cc3800c0 Mon Sep 17 00:00:00 2001 From: Meet Patel Date: Mon, 15 Jun 2026 17:41:15 +0530 Subject: [PATCH 1/2] feat(ulp): add multi-channel ADC init for ULP ulp_adc_init() could only configure a single ADC channel, so monitoring several channels from the ULP on one ADC unit was not possible. Add ulp_adc_multi_channel_init() to configure multiple channels with per-channel attenuation while keeping the existing single-channel API unchanged. Closes https://github.com/espressif/esp-idf/issues/11160 --- .../test_apps/ulp_riscv/main/test_ulp_riscv.c | 22 +++++++++++++ components/ulp/ulp_common/include/ulp_adc.h | 31 ++++++++++++++++++- components/ulp/ulp_common/ulp_adc.c | 25 ++++++++++++++- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv.c b/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv.c index 7994118f2e9..9affa5d8686 100644 --- a/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv.c +++ b/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv.c @@ -471,6 +471,28 @@ TEST_CASE("ULP ADC can init-deinit-init", "[ulp]") TEST_ASSERT_EQUAL(ESP_OK, ulp_adc_deinit()); } +TEST_CASE("ULP ADC can init multiple channels", "[ulp]") +{ + /* Initialize the ADC unit and configure the first channel */ + ulp_adc_cfg_t riscv_adc_cfg = { + .adc_n = ADC_UNIT, + .channel = ADC_CHANNEL_5, + .width = WIDTH, + .atten = ADC_ATTEN_DB_2_5, + .ulp_mode = ADC_ULP_MODE_FSM, + }; + TEST_ASSERT_EQUAL(ESP_OK, ulp_adc_init(&riscv_adc_cfg)); + + /* Configure an additional channel on the same ADC unit */ + ulp_adc_chan_cfg_t chan_cfg = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = WIDTH, + }; + TEST_ASSERT_EQUAL(ESP_OK, ulp_adc_config_channel(ADC_UNIT, ADC_CHANNEL_6, &chan_cfg)); + + TEST_ASSERT_EQUAL(ESP_OK, ulp_adc_deinit()); +} + TEST_CASE("Test ULP RISCV delay", "[ulp]") { int64_t start, diff; diff --git a/components/ulp/ulp_common/include/ulp_adc.h b/components/ulp/ulp_common/include/ulp_adc.h index e838d4cdbbe..d25b7071af2 100644 --- a/components/ulp/ulp_common/include/ulp_adc.h +++ b/components/ulp/ulp_common/include/ulp_adc.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,7 @@ #include "hal/adc_types.h" #include "esp_err.h" +#include "esp_adc/adc_oneshot.h" #ifdef __cplusplus extern "C" { @@ -21,14 +22,42 @@ typedef struct { adc_ulp_mode_t ulp_mode; // ADC ULP Mode } ulp_adc_cfg_t; // ULP FSM ADC configuration parameters +/** + * @brief ULP ADC channel configuration parameters + */ +typedef adc_oneshot_chan_cfg_t ulp_adc_chan_cfg_t; + /** * @brief Initialize and calibrate the ADC for use by ULP FSM * + * @note Initializes the ADC unit for ULP use and configures the channel + * specified in cfg->channel. To monitor multiple channels, call + * ulp_adc_config_channel() afterwards to configure additional channels. + * * @param cfg Configuration parameters * @return esp_err_t ESP_OK for successful. */ esp_err_t ulp_adc_init(const ulp_adc_cfg_t *cfg); +/** + * @brief Configure and calibrate an additional ADC channel for use by ULP + * + * Configures a channel on an ADC unit that has already been initialized by + * ulp_adc_init(). This can be called multiple times to configure multiple + * channels on the same ADC unit. + * + * @note HW calibration parameters are stored per attenuation, but only one set + * is active in the ADC unit at a time. If channels use different + * attenuations, readings on channels whose attenuation differs from the + * last configured one may be less accurate. + * + * @param adc_n ADC unit (must already be initialized via ulp_adc_init()) + * @param channel ADC channel to configure + * @param chan_cfg Channel configuration (attenuation and bit width) + * @return esp_err_t ESP_OK for successful. + */ +esp_err_t ulp_adc_config_channel(adc_unit_t adc_n, adc_channel_t channel, const ulp_adc_chan_cfg_t *chan_cfg); + /** * @brief Deinitialize ADC after use with ULP, allowing it to be reclaimed * diff --git a/components/ulp/ulp_common/ulp_adc.c b/components/ulp/ulp_common/ulp_adc.c index 21fbc1e93c7..ada7aa2740d 100644 --- a/components/ulp/ulp_common/ulp_adc.c +++ b/components/ulp/ulp_common/ulp_adc.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -17,6 +17,27 @@ static const char *TAG = "ulp_adc"; static adc_oneshot_unit_handle_t s_adc_handle = NULL; +esp_err_t ulp_adc_config_channel(adc_unit_t adc_n, adc_channel_t channel, const ulp_adc_chan_cfg_t *chan_cfg) +{ + esp_err_t ret = ESP_OK; + + ESP_RETURN_ON_FALSE(chan_cfg, ESP_ERR_INVALID_ARG, TAG, "chan_cfg == NULL"); + ESP_RETURN_ON_FALSE(s_adc_handle, ESP_ERR_INVALID_STATE, TAG, "ADC unit not initialized"); + + //-------------ADC Config---------------// + ret = adc_oneshot_config_channel(s_adc_handle, channel, chan_cfg); + if (ret != ESP_OK) { + return ret; + } + + //Calibrate the ADC +#if SOC_ADC_CALIBRATION_V1_SUPPORTED + adc_set_hw_calibration_code(adc_n, chan_cfg->atten); +#endif + + return ret; +} + esp_err_t ulp_adc_init(const ulp_adc_cfg_t *cfg) { esp_err_t ret = ESP_OK; @@ -47,6 +68,8 @@ esp_err_t ulp_adc_init(const ulp_adc_cfg_t *cfg) }; ret = adc_oneshot_config_channel(s_adc_handle, cfg->channel, &config); if (ret != ESP_OK) { + adc_oneshot_del_unit(s_adc_handle); + s_adc_handle = NULL; return ret; } From d72bdfedc9d612a5b08a4f7f852052c4eb37271d Mon Sep 17 00:00:00 2001 From: Meet Patel Date: Wed, 17 Jun 2026 21:44:02 +0530 Subject: [PATCH 2/2] fix(ulp): force SW channel control in ULP-RISC-V ADC read On ESP32-S2/S3 the ULP-RISC-V ADC shares the RTC SAR controller with the main CPU. ulp_adc_init() sets sar1_en_pad_force = 1 so software selects the channel, but deep-sleep entry resets it to 0 (ULP/HW control). After that, the per-read channel selection in ulp_riscv_adc_read_channel() is ignored and every channel returns the same stale value. Single-channel worked only because the channel never changed. Re-assert RTC controller (sar1_en_pad_force = 1) before selecting the channel on each read so multi-channel sampling works after deep sleep. --- components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c index 10905529b7f..2620ee25325 100644 --- a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c @@ -12,6 +12,9 @@ int32_t ulp_riscv_adc_read_channel(adc_unit_t adc_n, int channel) adc_oneshot_ll_clear_event(event); adc_oneshot_ll_disable_all_unit(); adc_oneshot_ll_enable(adc_n); + /* Force SW control of the channel bitmap; deep-sleep entry can clear it, which would + * otherwise make the channel selection below take no effect. */ + adc_ll_set_controller(adc_n, ADC_LL_CTRL_RTC); adc_oneshot_ll_set_channel(adc_n, channel); adc_oneshot_ll_start(adc_n);