mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
feat(ulp): Improved ULP delay API accuracy and removed floating point operations
Closes https://github.com/espressif/esp-idf/issues/17494 Closes https://github.com/espressif/esp-idf/issues/16891
This commit is contained in:
@@ -6,7 +6,7 @@ set(ulp_sources4 "ulp/test_main_i2c.c")
|
||||
|
||||
idf_component_register(SRCS ${app_sources}
|
||||
INCLUDE_DIRS "ulp"
|
||||
REQUIRES ulp unity test_utils driver
|
||||
REQUIRES ulp unity test_utils driver esp_timer
|
||||
WHOLE_ARCHIVE)
|
||||
|
||||
set(ulp_app_name ulp_test_app)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2010-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -23,6 +23,8 @@
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "ulp_riscv_cpu_freq_shared.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
#define ULP_WAKEUP_PERIOD 1000000 // 1 second
|
||||
|
||||
@@ -468,3 +470,163 @@ TEST_CASE("ULP ADC can init-deinit-init", "[ulp]")
|
||||
TEST_ASSERT_EQUAL(ESP_OK, ulp_adc_init(&riscv_adc_cfg));
|
||||
TEST_ASSERT_EQUAL(ESP_OK, ulp_adc_deinit());
|
||||
}
|
||||
|
||||
TEST_CASE("Test ULP RISCV delay", "[ulp]")
|
||||
{
|
||||
int64_t start, diff;
|
||||
const uint32_t delay_period_us = 5000000;
|
||||
const uint32_t delta_us = 500000; // RTC FAST is not very accurate
|
||||
|
||||
volatile uint32_t *command_resp = (volatile uint32_t *)&ulp_command_resp;
|
||||
volatile uint32_t *main_cpu_command = (volatile uint32_t *)&ulp_main_cpu_command;
|
||||
volatile uint32_t *main_cpu_reply = (volatile uint32_t *)&ulp_main_cpu_reply;
|
||||
volatile uint32_t *test_data_in = (volatile uint32_t *)&ulp_riscv_test_data_in;
|
||||
|
||||
/* Load ULP RISC-V firmware and start the coprocessor */
|
||||
load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length);
|
||||
|
||||
/* Setup test data */
|
||||
*command_resp = RISCV_NO_COMMAND;
|
||||
*main_cpu_reply = RISCV_NO_COMMAND;
|
||||
*test_data_in = delay_period_us;
|
||||
*main_cpu_command = RISCV_DELAY_TEST;
|
||||
|
||||
/* Wait till we receive the correct command response */
|
||||
while (*command_resp != RISCV_DELAY_TEST) {
|
||||
}
|
||||
|
||||
start = esp_timer_get_time();
|
||||
|
||||
/* Wait till we receive COMMAND_OK reply */
|
||||
while (*main_cpu_reply != RISCV_COMMAND_OK) {
|
||||
}
|
||||
|
||||
diff = esp_timer_get_time() - start;
|
||||
|
||||
printf("Waited for %" PRIi64 "us, expected: %" PRIu32 "us\n", diff, delay_period_us);
|
||||
TEST_ASSERT_INT_WITHIN(delta_us, delay_period_us, diff);
|
||||
|
||||
/* Clear test data */
|
||||
*main_cpu_command = RISCV_NO_COMMAND;
|
||||
}
|
||||
|
||||
static void print_delay_table_header(void)
|
||||
{
|
||||
printf("%10s %10s %10s %10s %10s\n",
|
||||
"----------", "----------", "----------", "----------", "----------");
|
||||
printf("%10s %10s %10s %10s %10s\n",
|
||||
"delay(us)", "expec_tk", "avg_tk", "error_tk", "in_range");
|
||||
printf("%10s %10s %10s %10s %10s\n",
|
||||
"----------", "----------", "----------", "----------", "----------");
|
||||
}
|
||||
|
||||
static void run_delay_calibration_test(riscv_test_commands_t delay_calibration_cmd, const char *delay_api_name)
|
||||
{
|
||||
volatile uint32_t *command_resp = (volatile uint32_t *)&ulp_command_resp;
|
||||
volatile uint32_t *main_cpu_command = (volatile uint32_t *)&ulp_main_cpu_command;
|
||||
volatile uint32_t *delay_sub_command = (volatile uint32_t *)&ulp_delay_sub_command;
|
||||
volatile uint32_t *test_data_in = (volatile uint32_t *)&ulp_riscv_test_data_in;
|
||||
const uint32_t test_delays_us[] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
30, 40, 50, 70, 90,
|
||||
100, 200, 500, 1000, 2000, 5000,
|
||||
10000, 20000, 50000, 100000,
|
||||
};
|
||||
const uint32_t ulp_cpu_freq_hz = ULP_RISCV_CPU_FREQUENCY_HZ;
|
||||
const double ulp_tick_time_us = 1000000.0 / ulp_cpu_freq_hz;
|
||||
const double ulp_ticks_per_us = 1.0 / ulp_tick_time_us;
|
||||
#if CONFIG_IDF_TARGET_ESP32S2
|
||||
const uint32_t warmup_exclude_ticks = 25;
|
||||
const double error_limit_time_us = 2.0;
|
||||
#else // ESP32S3
|
||||
const uint32_t warmup_exclude_ticks = 17;
|
||||
const double error_limit_time_us = 1.0;
|
||||
#endif
|
||||
const int32_t error_limit_ticks = (int32_t)(error_limit_time_us * ulp_ticks_per_us + 0.5);
|
||||
int32_t correction_ticks_checked = 0;
|
||||
uint32_t out_of_range_count = 0;
|
||||
uint32_t checked_measurements = 0;
|
||||
uint32_t skipped_warmup_measurements = 0;
|
||||
printf("Delay API under test: %s\n", delay_api_name);
|
||||
printf("ULP CPU frequency: %" PRIu32 " Hz, tick time: %.6f us, ticks/us: %.3f\n",
|
||||
ulp_cpu_freq_hz, ulp_tick_time_us, ulp_ticks_per_us);
|
||||
printf("Tolerance: +/- %" PRId32 " ticks (%.3f us)\n", error_limit_ticks, error_limit_time_us);
|
||||
printf("Skipping delay calibration points with expected ticks <= %" PRIu32 " ticks (%.3f us) as warmup\n",
|
||||
warmup_exclude_ticks, warmup_exclude_ticks * ulp_tick_time_us);
|
||||
|
||||
load_and_start_ulp_firmware(ulp_main_bin_start, ulp_main_bin_length);
|
||||
|
||||
*delay_sub_command = RISCV_DELAY_SUBCMD_NONE;
|
||||
*command_resp = RISCV_NO_COMMAND;
|
||||
*main_cpu_command = delay_calibration_cmd;
|
||||
|
||||
while (*command_resp != delay_calibration_cmd) { };
|
||||
|
||||
print_delay_table_header();
|
||||
const uint32_t num_delays = sizeof(test_delays_us) / sizeof(test_delays_us[0]);
|
||||
for (uint32_t delay_idx = 0; delay_idx < num_delays; delay_idx++) {
|
||||
const uint32_t requested_delay_us = test_delays_us[delay_idx];
|
||||
uint64_t sum_delay_ticks = 0;
|
||||
const uint32_t samples = 3;
|
||||
const uint32_t expected_ticks = requested_delay_us * ulp_ticks_per_us;
|
||||
|
||||
for (uint32_t sample_idx = 0; sample_idx < samples; sample_idx++) {
|
||||
|
||||
*test_data_in = (delay_calibration_cmd == RISCV_DELAY_CYCLES_CALIBRATION_TEST) ? expected_ticks : requested_delay_us;
|
||||
while (*delay_sub_command != RISCV_DELAY_SUBCMD_READY) { };
|
||||
*delay_sub_command = RISCV_DELAY_SUBCMD_RUN;
|
||||
|
||||
/* Wait for the ULP to finish its delay before reading the cycle counters from RTC memory.
|
||||
* The HP core delay must be at least as long as the requested ULP delay, with some margin
|
||||
* to ensure that we do not read RTC memory while the ULP is running the delay calibration code. */
|
||||
vTaskDelay(((requested_delay_us / 1000 + portTICK_PERIOD_MS + 1) / portTICK_PERIOD_MS) * 3);
|
||||
sum_delay_ticks += ulp_delay_end_cycles - ulp_delay_start_cycles;
|
||||
}
|
||||
|
||||
const uint64_t avg_delay_ticks = sum_delay_ticks / samples;
|
||||
const int32_t error_tk = expected_ticks - avg_delay_ticks;
|
||||
const char *range_status = "PASS";
|
||||
|
||||
if (expected_ticks <= warmup_exclude_ticks) {
|
||||
skipped_warmup_measurements++;
|
||||
range_status = "SKIP";
|
||||
} else {
|
||||
checked_measurements++;
|
||||
correction_ticks_checked += error_tk;
|
||||
if ((error_tk < -error_limit_ticks) || (error_tk > error_limit_ticks)) {
|
||||
out_of_range_count++;
|
||||
range_status = "FAIL";
|
||||
}
|
||||
}
|
||||
|
||||
printf("%10" PRIu32 " %10" PRIu32 " %10" PRIu64 " %10" PRId32 " %10s\n",
|
||||
requested_delay_us, expected_ticks, avg_delay_ticks, error_tk, range_status);
|
||||
}
|
||||
print_delay_table_header();
|
||||
|
||||
// Release the ULP from the delay test loop
|
||||
*main_cpu_command = RISCV_NO_COMMAND;
|
||||
*test_data_in = 0;
|
||||
*delay_sub_command = RISCV_DELAY_SUBCMD_RUN;
|
||||
|
||||
TEST_ASSERT_MESSAGE(checked_measurements > 0, "No delay calibration points were checked");
|
||||
double total_correct_ticks = (double)correction_ticks_checked / (double)checked_measurements;
|
||||
printf("Average correction ticks: %.1f (%.3f us)\n", total_correct_ticks, total_correct_ticks * ulp_tick_time_us);
|
||||
printf("Range-check summary (%s): checked=%" PRIu32 ", skipped_warmup=%" PRIu32 ", out_of_range=%" PRIu32
|
||||
", limit=+/- %" PRId32 " ticks\n",
|
||||
delay_api_name, checked_measurements, skipped_warmup_measurements, out_of_range_count, error_limit_ticks);
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(0, out_of_range_count, "Delay calibration has out-of-range error_tk values");
|
||||
TEST_ASSERT_DOUBLE_WITHIN(7.0, 0.0, total_correct_ticks);
|
||||
}
|
||||
|
||||
TEST_CASE("Test delay calibration for ulp_riscv_delay_cycles", "[ulp]")
|
||||
{
|
||||
run_delay_calibration_test(RISCV_DELAY_CYCLES_CALIBRATION_TEST, "ulp_riscv_delay_cycles");
|
||||
}
|
||||
|
||||
TEST_CASE("Test delay calibration for ulp_riscv_delay_us", "[ulp]")
|
||||
{
|
||||
run_delay_calibration_test(RISCV_DELAY_US_CALIBRATION_TEST, "ulp_riscv_delay_us");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2010-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -18,10 +18,51 @@ volatile riscv_test_commands_t command_resp = RISCV_NO_COMMAND;
|
||||
volatile uint32_t riscv_test_data_in = 0;
|
||||
volatile uint32_t riscv_test_data_out = 0;
|
||||
volatile uint32_t riscv_counter = 0;
|
||||
volatile uint32_t delay_start_cycles = 0;
|
||||
volatile uint32_t delay_end_cycles = 0;
|
||||
volatile uint32_t delay_sub_command = RISCV_DELAY_SUBCMD_NONE;
|
||||
|
||||
volatile uint32_t riscv_incrementer = 0;
|
||||
ulp_riscv_lock_t lock;
|
||||
|
||||
static void run_delay_calibration_test(riscv_test_commands_t calibration_cmd, bool use_cycles_delay)
|
||||
{
|
||||
register uint32_t start, end, overhead = 0;
|
||||
const uint32_t iterations = 5;
|
||||
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
start = ulp_riscv_get_cpu_cycles();
|
||||
end = ulp_riscv_get_cpu_cycles();
|
||||
overhead += end - start;
|
||||
}
|
||||
overhead /= iterations;
|
||||
|
||||
/* Enter delay-test session and stay here until HP signals completion. */
|
||||
command_resp = calibration_cmd;
|
||||
|
||||
while (main_cpu_command == calibration_cmd) {
|
||||
delay_sub_command = RISCV_DELAY_SUBCMD_READY;
|
||||
while (delay_sub_command != RISCV_DELAY_SUBCMD_RUN) { };
|
||||
register uint32_t requested_delay = riscv_test_data_in;
|
||||
ulp_riscv_delay_us(1);
|
||||
|
||||
if (use_cycles_delay) {
|
||||
start = ulp_riscv_get_cpu_cycles();
|
||||
ulp_riscv_delay_cycles(requested_delay);
|
||||
end = ulp_riscv_get_cpu_cycles();
|
||||
} else {
|
||||
start = ulp_riscv_get_cpu_cycles();
|
||||
ulp_riscv_delay_us(requested_delay);
|
||||
end = ulp_riscv_get_cpu_cycles();
|
||||
}
|
||||
|
||||
delay_sub_command = RISCV_DELAY_SUBCMD_NONE;
|
||||
|
||||
delay_start_cycles = start;
|
||||
delay_end_cycles = end - overhead;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_commands(riscv_test_commands_t cmd)
|
||||
{
|
||||
riscv_counter++;
|
||||
@@ -41,6 +82,25 @@ void handle_commands(riscv_test_commands_t cmd)
|
||||
ulp_riscv_wakeup_main_processor();
|
||||
break;
|
||||
|
||||
case RISCV_DELAY_TEST:
|
||||
/* Echo the command ID back to the main CPU */
|
||||
command_resp = RISCV_DELAY_TEST;
|
||||
|
||||
ulp_riscv_delay_us(riscv_test_data_in);
|
||||
main_cpu_reply = RISCV_COMMAND_OK;
|
||||
main_cpu_command = RISCV_NO_COMMAND;
|
||||
/* Wakeup the main CPU */
|
||||
ulp_riscv_wakeup_main_processor();
|
||||
break;
|
||||
|
||||
case RISCV_DELAY_CYCLES_CALIBRATION_TEST:
|
||||
run_delay_calibration_test(RISCV_DELAY_CYCLES_CALIBRATION_TEST, true);
|
||||
break;
|
||||
|
||||
case RISCV_DELAY_US_CALIBRATION_TEST:
|
||||
run_delay_calibration_test(RISCV_DELAY_US_CALIBRATION_TEST, false);
|
||||
break;
|
||||
|
||||
case RISCV_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST:
|
||||
/* Echo the command ID back to the main CPU */
|
||||
command_resp = RISCV_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST;
|
||||
@@ -48,7 +108,7 @@ void handle_commands(riscv_test_commands_t cmd)
|
||||
/* Set the command reply status */
|
||||
main_cpu_reply = RISCV_COMMAND_OK;
|
||||
|
||||
ulp_riscv_delay_cycles(1000 * ULP_RISCV_CYCLES_PER_MS);
|
||||
ulp_riscv_delay_us(1000000);
|
||||
|
||||
/* Wakeup the main CPU */
|
||||
ulp_riscv_wakeup_main_processor();
|
||||
@@ -61,7 +121,7 @@ void handle_commands(riscv_test_commands_t cmd)
|
||||
/* Set the command reply status */
|
||||
main_cpu_reply = RISCV_COMMAND_OK;
|
||||
|
||||
ulp_riscv_delay_cycles(10000 * ULP_RISCV_CYCLES_PER_MS);
|
||||
ulp_riscv_delay_us(10000000);
|
||||
|
||||
/* Wakeup the main CPU */
|
||||
ulp_riscv_wakeup_main_processor();
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
int main(void)
|
||||
{
|
||||
// Wait for the main core in the test case to enter lightsleep
|
||||
ulp_riscv_delay_cycles(100 * ULP_RISCV_CYCLES_PER_MS);
|
||||
ulp_riscv_delay_us(100000);
|
||||
/* Make sure ULP core crashes by doing a NULL pointer access */
|
||||
uint32_t *null_ptr = NULL;
|
||||
*null_ptr = 1;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
typedef enum {
|
||||
RISCV_READ_WRITE_TEST = 1,
|
||||
RISCV_DELAY_TEST,
|
||||
RISCV_DELAY_CYCLES_CALIBRATION_TEST,
|
||||
RISCV_DELAY_US_CALIBRATION_TEST,
|
||||
RISCV_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST,
|
||||
RISCV_DEEP_SLEEP_WAKEUP_LONG_DELAY_TEST,
|
||||
RISCV_LIGHT_SLEEP_WAKEUP_TEST,
|
||||
@@ -29,3 +32,9 @@ typedef enum {
|
||||
RISCV_COMMAND_NOK,
|
||||
RISCV_COMMAND_INVALID,
|
||||
} riscv_test_command_reply_t;
|
||||
|
||||
typedef enum {
|
||||
RISCV_DELAY_SUBCMD_NONE = 0,
|
||||
RISCV_DELAY_SUBCMD_READY,
|
||||
RISCV_DELAY_SUBCMD_RUN,
|
||||
} riscv_delay_sub_command_t;
|
||||
|
||||
Reference in New Issue
Block a user