diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_adc.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_adc.c index 4bc1b602204..b253bbd2c80 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_adc.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_adc.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,6 +7,7 @@ #include "ulp_lp_core_lp_adc_shared.h" volatile int adc_raw[8]; +volatile uint32_t adc_scan_seq; int main(void) { @@ -14,6 +15,7 @@ int main(void) for (int i = 0; i < 8; i++) { lp_core_lp_adc_read_channel_raw(ADC_UNIT_1, i, (int *)&adc_raw[i]); } + adc_scan_seq++; } return 0; diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_app_main.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_app_main.c index d8a9a98ccc0..7e58c3a5fde 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_app_main.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_app_main.c @@ -1,9 +1,10 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ +#include "soc/soc_caps.h" #include "unity.h" #include "unity_test_runner.h" #include "esp_heap_caps.h" @@ -21,6 +22,10 @@ static void check_leak(size_t before_free, size_t after_free, const char *type) TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); } +#if SOC_LP_ADC_SUPPORTED +void lp_adc_test_teardown(void); +#endif + void setUp(void) { before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); @@ -29,6 +34,9 @@ void setUp(void) void tearDown(void) { +#if SOC_LP_ADC_SUPPORTED + lp_adc_test_teardown(); +#endif 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"); diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_adc.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_adc.c index 117f7526415..bebc081c5f9 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_adc.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_adc.c @@ -1,9 +1,12 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ +#include +#include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "hal/adc_types.h" @@ -15,6 +18,7 @@ #include "driver/gpio.h" #include "driver/rtc_io.h" #include "driver/temperature_sensor.h" +#include "esp_timer.h" #include "unity.h" @@ -31,6 +35,14 @@ extern const uint8_t lp_core_main_adc_bin_end[] asm("_binary_lp_core_test_app_ #define ADC_GET_IO_NUM(unit, channel) (adc_channel_io_map[unit][channel]) +#define ADC_SETTLE_DELAY_MS 100 /* RC settle of the weak internal pulls */ +#define ADC_SCAN_TIMEOUT_MS 100 /* upper bound on LP scan completion */ +#define ADC_SCANS_TO_WAIT 2 /* completed LP scans before taking a snapshot */ + +static bool s_lp_adc_inited; +static bool s_lp_firmware_started; +static adc_unit_t s_lp_adc_unit; + static void test_adc_set_io_level(adc_unit_t unit, adc_channel_t channel, bool level) { TEST_ASSERT(channel < ADC_LL_CHANNEL_NUM(unit) && "invalid channel"); @@ -50,28 +62,103 @@ static void test_adc_set_io_level(adc_unit_t unit, adc_channel_t channel, bool l #endif } +static void test_adc_set_all_io_level(adc_unit_t unit, bool level) +{ + for (int ch = 0; ch < ADC_LL_CHANNEL_NUM(unit); ch++) { + test_adc_set_io_level(unit, ch, level); + } +} + static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) { TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, (firmware_end - firmware_start)) == ESP_OK); TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK); + s_lp_firmware_started = true; +} +static uint32_t adc_scan_seq_get(void) +{ + return *((volatile uint32_t *)&ulp_adc_scan_seq); +} + +static void snapshot_adc_raw(int *dst, int n) +{ + volatile int *src = (volatile int *)&ulp_adc_raw; + for (int i = 0; i < n; i++) { + dst[i] = src[i]; + } +} + +static void lp_adc_init_tracked(adc_unit_t unit_id) +{ + ESP_ERROR_CHECK(lp_core_lp_adc_init(unit_id)); + s_lp_adc_unit = unit_id; + s_lp_adc_inited = true; +} + +static esp_err_t lp_adc_hw_deinit(void) +{ + esp_err_t ret = ESP_OK; + if (s_lp_adc_inited) { + ret = lp_core_lp_adc_deinit(s_lp_adc_unit); + s_lp_adc_inited = false; + } + return ret; +} + +void lp_adc_test_teardown(void) +{ + (void)lp_adc_hw_deinit(); + if (s_lp_firmware_started) { + ulp_lp_core_stop(); + s_lp_firmware_started = false; + } +} + +static void delay_poll_tick(void) +{ + /* pdMS_TO_TICKS(1) is 0 at a 10 ms FreeRTOS tick, so wait one tick. */ + vTaskDelay(1); +} + +static void wait_for_adc_scans(uint32_t count) +{ + uint32_t start_seq = adc_scan_seq_get(); + const int64_t deadline_us = esp_timer_get_time() + (int64_t)ADC_SCAN_TIMEOUT_MS * 1000; + while ((adc_scan_seq_get() - start_seq) < count && esp_timer_get_time() < deadline_us) { + delay_poll_tick(); + } + TEST_ASSERT_MESSAGE((adc_scan_seq_get() - start_seq) >= count, + "LP ADC did not complete the expected number of scans"); +} + +/* Every sample is taken after the GPIO change. Values may span two adjacent + * LP scans; that is fine because the pull state is stable. Do not search + * for a passing window. + */ +static void snapshot_after_gpio_change(int *dst, int n) +{ + vTaskDelay(pdMS_TO_TICKS(ADC_SETTLE_DELAY_MS)); + wait_for_adc_scans(ADC_SCANS_TO_WAIT); + snapshot_adc_raw(dst, n); } void test_lp_adc(adc_unit_t unit_id) { - /* Load ULP firmware and start the coprocessor */ + int adc_raw[8]; + const int n = ADC_LL_CHANNEL_NUM(unit_id); + TEST_ASSERT(n <= (int)(sizeof(adc_raw) / sizeof(adc_raw[0]))); ulp_lp_core_cfg_t cfg = { .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, }; - load_and_start_lp_core_firmware(&cfg, lp_core_main_adc_bin_start, lp_core_main_adc_bin_end); + /* Init ADC and GPIO before starting the LP firmware so the first scan + * is not taken on unconfigured channels / floating pins. + */ + lp_adc_init_tracked(unit_id); - /* LP ADC Init */ - ESP_ERROR_CHECK(lp_core_lp_adc_init(unit_id)); - - /* LP ADC channel config */ const lp_core_lp_adc_chan_cfg_t config = { .atten = ADC_ATTEN_DB_12, .bitwidth = ADC_BITWIDTH_DEFAULT, @@ -92,46 +179,24 @@ void test_lp_adc(adc_unit_t unit_id) TEST_ASSERT(lp_core_lp_adc_config_channel(unit_id, ADC_CHANNEL_7, &config) == ESP_OK); } - /* Set all the ADC channel IOs to low */ - test_adc_set_io_level(unit_id, ADC_CHANNEL_0, 0); - test_adc_set_io_level(unit_id, ADC_CHANNEL_1, 0); - test_adc_set_io_level(unit_id, ADC_CHANNEL_2, 0); - test_adc_set_io_level(unit_id, ADC_CHANNEL_3, 0); - test_adc_set_io_level(unit_id, ADC_CHANNEL_4, 0); - test_adc_set_io_level(unit_id, ADC_CHANNEL_5, 0); - test_adc_set_io_level(unit_id, ADC_CHANNEL_6, 0); - test_adc_set_io_level(unit_id, ADC_CHANNEL_7, 0); + test_adc_set_all_io_level(unit_id, 0); + load_and_start_lp_core_firmware(&cfg, lp_core_main_adc_bin_start, lp_core_main_adc_bin_end); + snapshot_after_gpio_change(adc_raw, n); - vTaskDelay(10); - - int *adc_raw = (int *)&ulp_adc_raw; - - /* Verify that the LP ADC values reflect a low-state of the input pins */ - for (int i = 0; i < ADC_LL_CHANNEL_NUM(unit_id); i++) { + for (int i = 0; i < n; i++) { printf("LP ADC low[%d] = %d\n", i, adc_raw[i]); TEST_ASSERT_LESS_THAN_INT(ADC_TEST_LOW_VAL, adc_raw[i]); } - /* Set all the ADC channel IOs to high */ - test_adc_set_io_level(unit_id, ADC_CHANNEL_0, 1); - test_adc_set_io_level(unit_id, ADC_CHANNEL_1, 1); - test_adc_set_io_level(unit_id, ADC_CHANNEL_2, 1); - test_adc_set_io_level(unit_id, ADC_CHANNEL_3, 1); - test_adc_set_io_level(unit_id, ADC_CHANNEL_4, 1); - test_adc_set_io_level(unit_id, ADC_CHANNEL_5, 1); - test_adc_set_io_level(unit_id, ADC_CHANNEL_6, 1); - test_adc_set_io_level(unit_id, ADC_CHANNEL_7, 1); + test_adc_set_all_io_level(unit_id, 1); + snapshot_after_gpio_change(adc_raw, n); - vTaskDelay(10); - - /* Verify that the LP ADC values reflect a high-state of the input pins */ - for (int i = 0; i < ADC_LL_CHANNEL_NUM(unit_id); i++) { + for (int i = 0; i < n; i++) { printf("LP ADC high[%d] = %d\n", i, adc_raw[i]); TEST_ASSERT_GREATER_THAN_INT(ADC_TEST_HIGH_VAL, adc_raw[i]); } - /* Deinit LP ADC */ - ESP_ERROR_CHECK(lp_core_lp_adc_deinit(unit_id)); + TEST_ESP_OK(lp_adc_hw_deinit()); } TEST_CASE("LP ADC 1 raw read test", "[lp_core]") @@ -147,18 +212,17 @@ TEST_CASE("LP ADC 1 raw read test", "[lp_core]") static void test_lp_adc_stress(adc_unit_t unit_id) { - /* Load ULP firmware and start the coprocessor */ ulp_lp_core_cfg_t cfg = { .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, }; + int adc_raw; - load_and_start_lp_core_firmware(&cfg, lp_core_main_adc_bin_start, lp_core_main_adc_bin_end); - + /* Firmware is started once and left running across init/deinit cycles so + * the LP core keeps converting while HP has released the ADC. + */ for (int i = 0; i < 100; i++) { - /* LP ADC Init */ - ESP_ERROR_CHECK(lp_core_lp_adc_init(unit_id)); + lp_adc_init_tracked(unit_id); - /* LP ADC channel config */ const lp_core_lp_adc_chan_cfg_t config = { .atten = ADC_ATTEN_DB_12, .bitwidth = ADC_BITWIDTH_DEFAULT, @@ -166,14 +230,14 @@ static void test_lp_adc_stress(adc_unit_t unit_id) TEST_ASSERT(lp_core_lp_adc_config_channel(unit_id, ADC_CHANNEL_0, &config) == ESP_OK); - /* Set LP ADC channel IO and read raw value */ test_adc_set_io_level(unit_id, ADC_CHANNEL_0, 1); - vTaskDelay(10); - int *adc_raw = (int *)&ulp_adc_raw; - TEST_ASSERT_NOT_EQUAL(0, adc_raw[0]); + if (!s_lp_firmware_started) { + load_and_start_lp_core_firmware(&cfg, lp_core_main_adc_bin_start, lp_core_main_adc_bin_end); + } + snapshot_after_gpio_change(&adc_raw, 1); + TEST_ASSERT_NOT_EQUAL(0, adc_raw); - /* De-init LP ADC */ - ESP_ERROR_CHECK(lp_core_lp_adc_deinit(unit_id)); + TEST_ESP_OK(lp_adc_hw_deinit()); } } @@ -204,33 +268,31 @@ TEST_CASE("Test temperature sensor does not affect LP ADC", "[lp_core]") TEST_ESP_OK(temperature_sensor_disable(temp_sensor)); } - /* Load ULP firmware and start the coprocessor */ ulp_lp_core_cfg_t cfg = { .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, }; - load_and_start_lp_core_firmware(&cfg, lp_core_main_adc_bin_start, lp_core_main_adc_bin_end); + lp_adc_init_tracked(ADC_UNIT_1); - /* LP ADC Init */ - ESP_ERROR_CHECK(lp_core_lp_adc_init(ADC_UNIT_1)); - - /* LP ADC channel config */ const lp_core_lp_adc_chan_cfg_t config = { .atten = ADC_ATTEN_DB_12, .bitwidth = ADC_BITWIDTH_DEFAULT, }; - /* Configure ADC channel 0 */ TEST_ASSERT(lp_core_lp_adc_config_channel(ADC_UNIT_1, ADC_CHANNEL_0, &config) == ESP_OK); + load_and_start_lp_core_firmware(&cfg, lp_core_main_adc_bin_start, lp_core_main_adc_bin_end); - int *adc_raw = (int *)&ulp_adc_raw; + int adc_raw; cnt = 2; while (cnt--) { - printf("LP ADC%d Channel[%d] Raw Data: %d\n", ADC_UNIT_1 + 1, 0, adc_raw[0]); + wait_for_adc_scans(ADC_SCANS_TO_WAIT); + snapshot_adc_raw(&adc_raw, 1); + printf("LP ADC%d Channel[%d] Raw Data: %d\n", ADC_UNIT_1 + 1, 0, adc_raw); vTaskDelay(pdMS_TO_TICKS(100)); } - TEST_ESP_OK(lp_core_lp_adc_deinit(ADC_UNIT_1)); + TEST_ESP_OK(lp_adc_hw_deinit()); + lp_adc_test_teardown(); cnt = 2; while (cnt--) {