test(ulp): snapshot LP ADC raw reads after GPIO settle

HP was asserting a live LP buffer and starting firmware before ADC/GPIO setup.
That produced leftover-high values and print/assert mismatches on pull-down.

Wait 100 ms for analog settle, then wait for completed scans with a wall-clock
timeout (pdMS_TO_TICKS(1) is zero at a 10 ms tick). Copy a local snapshot and
assert it; do not retry until a reading is in range. Deinit on teardown so a
failed raw-read cannot abort the following stress case.
This commit is contained in:
Meet Patel
2026-08-27 16:53:41 +05:30
parent fbb0518b1a
commit 108db1bc12
3 changed files with 133 additions and 61 deletions

View File

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

View File

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

View File

@@ -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 <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#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--) {