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:
Konstantin Kondrashov
2026-02-23 18:04:14 +02:00
committed by BOT
parent 329ed7ddce
commit 6fe2cf575f
20 changed files with 680 additions and 73 deletions
@@ -53,14 +53,50 @@ static inline uint32_t ulp_lp_core_get_cpu_cycles(void)
}
/**
* @brief Makes the co-processor busy wait for a certain number of microseconds
* @brief Check whether an mcycle-based timeout has elapsed.
*
* @note A timeout value of -1 means "wait forever".
* Other values are interpreted as unsigned cycle counts.
*
* @param start_cycle_count Cycle counter value captured at timeout start.
* @param cycles_to_wait Timeout in CPU cycles, or -1 to disable timeout.
*
* @return true if timeout elapsed, false otherwise.
*/
static inline bool ulp_lp_core_is_timeout_elapsed(uint32_t start_cycle_count, int32_t cycles_to_wait)
{
if (cycles_to_wait == -1) {
return false;
}
return (ulp_lp_core_get_cpu_cycles() - start_cycle_count) >= (uint32_t)cycles_to_wait;
}
/**
* @brief Makes the co-processor busy-wait for a certain number of microseconds.
*
* @note The maximum supported delay depends on the LP core clock source and frequency.
* For values above the limits below, the computed delay may overflow and the result
* is undefined.
* - LP core @ 16 MHz (RC_FAST / default): us must be <= 134217727 (about 134.2 s)
* - LP core @ 40 MHz (XTAL 40 MHz): us must be <= 53687091 (about 53.7 s)
* - LP core @ 48 MHz (XTAL 48 MHz): us must be <= 44739242 (about 44.7 s)
*
* @param us Number of microseconds to busy-wait for
*/
void ulp_lp_core_delay_us(uint32_t us);
/**
* @brief Makes the co-processor busy wait for a certain number of cycles
* @brief Makes the co-processor busy-wait for a certain number of cycles.
*
* @note The maximum supported delay is 0x7FFFFFFF cycles.
* For larger values, the behavior is undefined. Split longer delays into smaller
* chunks if needed.
*
* For reference, this corresponds approximately to:
* - LP core @ 16 MHz (RC_FAST / default): 0x7FFFFFFF cycles ≈ 134.2 s
* - LP core @ 40 MHz (XTAL 40 MHz): 0x7FFFFFFF cycles ≈ 53.7 s
* - LP core @ 48 MHz (XTAL 48 MHz): 0x7FFFFFFF cycles ≈ 44.7 s
*
* @param cycles Number of cycles to busy-wait for
*/
+14 -21
View File
@@ -24,19 +24,7 @@
#endif
#include "esp_cpu.h"
/* LP_FAST_CLK is not very accurate, for now use a rough estimate */
#if CONFIG_RTC_FAST_CLK_SRC_RC_FAST
#define LP_CORE_CPU_FREQUENCY_HZ 16000000 // For P4 TRM says 20 MHz by default, but we tune it closer to 16 MHz
#elif CONFIG_RTC_FAST_CLK_SRC_XTAL
#if SOC_XTAL_SUPPORT_48M
#define LP_CORE_CPU_FREQUENCY_HZ 48000000
#else
#define LP_CORE_CPU_FREQUENCY_HZ 40000000
#endif
#else // Default value in chip without rtc fast clock sel option
#define LP_CORE_CPU_FREQUENCY_HZ 16000000
#endif
#include "ulp_lp_core_cpu_freq_shared.h"
static uint32_t lp_wakeup_cause = 0;
@@ -117,11 +105,14 @@ void ulp_lp_core_wakeup_main_processor(void)
*/
void ulp_lp_core_delay_us(uint32_t us)
{
uint32_t start = RV_READ_CSR(mcycle);
uint32_t end = us * (LP_CORE_CPU_FREQUENCY_HZ / 1000000);
if (us == 0) {
return;
}
uint32_t start = RV_READ_CSR(mcycle) - ULP_LP_CORE_DELAY_CALL_OVERHEAD_IN_CYCLES;
uint32_t req_delay = us * LP_CORE_CYCLES_PER_US_NUM / LP_CORE_CYCLES_PER_US_DENOM;
while ((RV_READ_CSR(mcycle) - start) < end) {
/* nothing to do */
while ((uint32_t)(RV_READ_CSR(mcycle) - start) < req_delay) {
/* busy wait */
}
}
@@ -132,11 +123,13 @@ void ulp_lp_core_delay_us(uint32_t us)
*/
void ulp_lp_core_delay_cycles(uint32_t cycles)
{
uint32_t start = RV_READ_CSR(mcycle);
uint32_t end = cycles;
if (cycles <= ULP_LP_CORE_DELAY_CALL_OVERHEAD_IN_CYCLES) {
return;
}
while ((RV_READ_CSR(mcycle) - start) < end) {
/* nothing to do */
uint32_t start = RV_READ_CSR(mcycle) - ULP_LP_CORE_DELAY_CALL_OVERHEAD_IN_CYCLES;
while ((uint32_t)(RV_READ_CSR(mcycle) - start) < cycles) {
/* busy wait */
}
}
@@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
#include "soc/soc_caps.h"
/* LP_FAST_CLK is not very accurate, for now use a rough estimate */
#if CONFIG_RTC_FAST_CLK_SRC_RC_FAST
#define LP_CORE_CPU_FREQUENCY_HZ 16000000U /* For P4 TRM says 20 MHz by default, but we tune it closer to 16 MHz */
#define LP_CORE_CYCLES_PER_US_NUM 16U
#define LP_CORE_CYCLES_PER_US_DENOM 1U
#elif CONFIG_RTC_FAST_CLK_SRC_XTAL
#if SOC_XTAL_SUPPORT_48M
#define LP_CORE_CPU_FREQUENCY_HZ 48000000U
#define LP_CORE_CYCLES_PER_US_NUM 48U
#define LP_CORE_CYCLES_PER_US_DENOM 1U
#else
#define LP_CORE_CPU_FREQUENCY_HZ 40000000U
#define LP_CORE_CYCLES_PER_US_NUM 40U
#define LP_CORE_CYCLES_PER_US_DENOM 1U
#endif
#else // Default value in chip without rtc fast clock sel option
#define LP_CORE_CPU_FREQUENCY_HZ 16000000U
#define LP_CORE_CYCLES_PER_US_NUM 16U
#define LP_CORE_CYCLES_PER_US_DENOM 1U
#endif
/**
* @brief Compensation for LP core delay function overhead in CPU cycles.
*
* @details Estimated cycles consumed by delay loop function call and measurement
* overhead. Derived from empirical "LP core delay calibration" test measurements.
* Used by delay functions to improve accuracy for short durations.
*
* @note Value is calibration-specific and may vary with compiler optimization.
*/
#define ULP_LP_CORE_DELAY_CALL_OVERHEAD_IN_CYCLES 11U
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -15,9 +15,49 @@ volatile lp_core_test_command_reply_t main_cpu_reply = LP_CORE_COMMAND_INVALID;
volatile lp_core_test_commands_t command_resp = LP_CORE_NO_COMMAND;
volatile uint32_t test_data_in = 0;
volatile uint32_t test_data_out = 0;
volatile uint32_t delay_start_cycles = 0;
volatile uint32_t delay_end_cycles = 0;
volatile uint32_t delay_sub_command = LP_CORE_DELAY_SUBCMD_NONE;
volatile uint32_t incrementer = 0;
static void run_delay_calibration_test(lp_core_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_lp_core_get_cpu_cycles();
end = ulp_lp_core_get_cpu_cycles();
overhead += end - start;
}
overhead /= iterations;
command_resp = calibration_cmd;
while (main_cpu_command == calibration_cmd) {
delay_sub_command = LP_CORE_DELAY_SUBCMD_READY;
while (delay_sub_command != LP_CORE_DELAY_SUBCMD_RUN) { };
register uint32_t requested_delay = test_data_in;
ulp_lp_core_delay_us(1);
if (use_cycles_delay) {
start = ulp_lp_core_get_cpu_cycles();
ulp_lp_core_delay_cycles(requested_delay);
end = ulp_lp_core_get_cpu_cycles();
} else {
start = ulp_lp_core_get_cpu_cycles();
ulp_lp_core_delay_us(requested_delay);
end = ulp_lp_core_get_cpu_cycles();
}
delay_sub_command = LP_CORE_DELAY_SUBCMD_NONE;
delay_start_cycles = start;
delay_end_cycles = end - overhead;
}
}
void handle_commands(lp_core_test_commands_t cmd)
{
@@ -44,6 +84,14 @@ void handle_commands(lp_core_test_commands_t cmd)
main_cpu_command = LP_CORE_NO_COMMAND;
break;
case LP_CORE_DELAY_CYCLES_CALIBRATION_TEST:
run_delay_calibration_test(LP_CORE_DELAY_CYCLES_CALIBRATION_TEST, true);
break;
case LP_CORE_DELAY_US_CALIBRATION_TEST:
run_delay_calibration_test(LP_CORE_DELAY_US_CALIBRATION_TEST, false);
break;
case LP_CORE_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST:
/* Echo the command ID back to the main CPU */
command_resp = LP_CORE_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST;
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -18,6 +18,8 @@
typedef enum {
LP_CORE_READ_WRITE_TEST = 1,
LP_CORE_DELAY_TEST,
LP_CORE_DELAY_CYCLES_CALIBRATION_TEST,
LP_CORE_DELAY_US_CALIBRATION_TEST,
LP_CORE_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST,
LP_CORE_DEEP_SLEEP_WAKEUP_LONG_DELAY_TEST,
LP_CORE_LP_UART_WRITE_TEST,
@@ -33,3 +35,9 @@ typedef enum {
LP_CORE_COMMAND_NOK,
LP_CORE_COMMAND_INVALID,
} lp_core_test_command_reply_t;
typedef enum {
LP_CORE_DELAY_SUBCMD_NONE = 0,
LP_CORE_DELAY_SUBCMD_READY,
LP_CORE_DELAY_SUBCMD_RUN,
} lp_core_delay_sub_command_t;
@@ -15,6 +15,7 @@
#include "lp_core_test_app_wake_stub.h"
#endif
#include "lp_core_test_app_isr.h"
#include "ulp_lp_core_cpu_freq_shared.h"
#if SOC_RTC_TIMER_V2_SUPPORTED
#include "lp_core_test_app_set_timer_wakeup.h"
@@ -145,6 +146,127 @@ TEST_CASE("Test LP core delay", "[lp_core]")
clear_test_cmds();
}
static void print_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(lp_core_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_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 lp_cpu_freq_hz = LP_CORE_CPU_FREQUENCY_HZ;
const double lp_tick_time_us = 1000000.0 / lp_cpu_freq_hz;
const double lp_ticks_per_us = 1.0 / lp_tick_time_us;
const double error_limit_time_us = 0.30;
const int32_t error_limit_ticks = (int32_t)(error_limit_time_us * lp_ticks_per_us + 0.5);
const uint32_t warmup_exclude_ticks = 0;
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("LP CPU frequency: %" PRIu32 " Hz, tick time: %.6f us, ticks/us: %.3f\n",
lp_cpu_freq_hz, lp_tick_time_us, lp_ticks_per_us);
printf("Tolerance: +/- %" PRId32 " ticks (%.3f us)\n", error_limit_ticks, error_limit_time_us);
/* 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_bin_start, lp_core_main_bin_end);
*delay_sub_command = LP_CORE_DELAY_SUBCMD_NONE;
*command_resp = LP_CORE_NO_COMMAND;
*main_cpu_command = delay_calibration_cmd;
while (*command_resp != delay_calibration_cmd) { };
print_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 * lp_ticks_per_us;
for (uint32_t sample_idx = 0; sample_idx < samples; sample_idx++) {
*test_data_in = (delay_calibration_cmd == LP_CORE_DELAY_CYCLES_CALIBRATION_TEST) ? expected_ticks : requested_delay_us;
while (*delay_sub_command != LP_CORE_DELAY_SUBCMD_READY) { };
*delay_sub_command = LP_CORE_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_table_header();
/* Release LP core from delay-test loop */
*main_cpu_command = LP_CORE_NO_COMMAND;
ulp_test_data_in = 0;
*delay_sub_command = LP_CORE_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 * lp_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(5.0, 0.0, total_correct_ticks);
clear_test_cmds();
}
TEST_CASE("Test delay calibration for ulp_lp_core_delay_cycles", "[lp_core]")
{
run_delay_calibration_test(LP_CORE_DELAY_CYCLES_CALIBRATION_TEST, "ulp_lp_core_delay_cycles");
}
TEST_CASE("Test delay calibration for ulp_lp_core_delay_us", "[lp_core]")
{
run_delay_calibration_test(LP_CORE_DELAY_US_CALIBRATION_TEST, "ulp_lp_core_delay_us");
}
#define LP_TIMER_TEST_DURATION_S (5)
#define LP_TIMER_TEST_SLEEP_DURATION_US (20000)
@@ -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;
@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
#if CONFIG_IDF_TARGET_ESP32S2
// ULP-RISC-V runs at 8.5MHz, which corresponds to 17/2 = 8.5 cycles per microsecond.
#define ULP_RISCV_CPU_FREQUENCY_HZ 8500000U
#define ULP_RISCV_CYCLES_PER_US_NUM 17U
#define ULP_RISCV_CYCLES_PER_US_DENOM 2U
#elif CONFIG_IDF_TARGET_ESP32S3
// ULP-RISC-V runs at 17.5MHz, which corresponds to 35/2 = 17.5 cycles per microsecond.
#define ULP_RISCV_CPU_FREQUENCY_HZ 17500000U
#define ULP_RISCV_CYCLES_PER_US_NUM 35U
#define ULP_RISCV_CYCLES_PER_US_DENOM 2U
#endif
@@ -14,6 +14,7 @@ extern "C" {
#include <stdint.h>
#include "ulp_riscv_register_ops.h"
#include "ulp_riscv_interrupt.h"
#include "ulp_riscv_cpu_freq_shared.h"
/**
* @brief Wakeup main CPU from sleep or deep sleep.
@@ -79,15 +80,8 @@ void ulp_riscv_timer_resume(void);
asm volatile("rdcycle %0;" : "=r"(__ccount)); \
__ccount; })
#if CONFIG_IDF_TARGET_ESP32S2
/* These are only approximate default numbers, the default frequency
of the 8M oscillator is 8.5MHz +/- 5%, at the default DCAP setting
*/
#define ULP_RISCV_CYCLES_PER_US 8.5
#elif CONFIG_IDF_TARGET_ESP32S3
#define ULP_RISCV_CYCLES_PER_US 17.5
#endif
#define ULP_RISCV_CYCLES_PER_MS ULP_RISCV_CYCLES_PER_US*1000
#define ULP_RISCV_CYCLES_PER_US ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENOM
#define ULP_RISCV_CYCLES_PER_MS 1000U * ULP_RISCV_CYCLES_PER_US
/**
* @brief Retrieves the current number of CPU cycles.
@@ -100,25 +94,70 @@ static inline uint32_t ulp_riscv_get_cpu_cycles(void)
}
/**
* @brief Makes the co-processor busy wait for a certain number of cycles
* @brief Check whether an mcycle-based timeout has elapsed.
*
* @note This function is not accurate for delays shorter than 20 cycles,
* as the function's own overhead may exceed the requested delay.
* @note A timeout value of -1 means "wait forever".
* Other values are interpreted as unsigned cycle counts.
*
* @param cycles Number of cycles to busy wait
* @param start_cycle_count Cycle counter value captured at timeout start.
* @param cycles_to_wait Timeout in CPU cycles, or -1 to disable timeout.
*
* @return true if timeout elapsed, false otherwise.
*/
void static inline ulp_riscv_delay_cycles(uint32_t cycles)
static inline bool ulp_riscv_is_timeout_elapsed(uint32_t start_cycle_count, int32_t cycles_to_wait)
{
if (cycles <= 20) { // the estimate of cycles for this function
if (cycles_to_wait == -1) {
return false;
}
return (ulp_riscv_get_cpu_cycles() - start_cycle_count) >= (uint32_t)cycles_to_wait;
}
/**
* @brief Makes the co-processor busy-wait for a certain number of CPU cycles.
*
* @note This function is not accurate for delays shorter than 20 cycles because the
* function overhead may exceed the requested delay.
*
* @note The maximum supported delay is 0x7FFFFFFF cycles.
* For larger values, the behavior is undefined. Split longer delays into smaller
* chunks if needed.
*
* For reference, this corresponds approximately to:
* - ESP32-S2 ULP-RISC-V @ 8.5 MHz: 0x7FFFFFFF cycles ≈ 252.645 s
* - ESP32-S3 ULP-RISC-V @ 17.5 MHz: 0x7FFFFFFF cycles ≈ 122.713 s
*
* @param cycles Number of cycles to busy-wait.
*/
static inline void ulp_riscv_delay_cycles(uint32_t cycles)
{
if (cycles <= 20U) { // estimate of cycles for this function overhead
return;
}
/* Off with the estimate of cycles to improve accuracy */
uint32_t end = ULP_RISCV_GET_CCOUNT() + cycles - 20;
while (ULP_RISCV_GET_CCOUNT() < end) {
/* Wait */
// To improve accuracy subtract (20 + 15) cycles overhead, defined by delay calibration test
uint32_t start = ULP_RISCV_GET_CCOUNT() - 20U - 15U;
while ((uint32_t)(ULP_RISCV_GET_CCOUNT() - start) < cycles) {
/* busy wait */
}
}
/**
* @brief Makes the co-processor busy-wait for a certain number of microseconds.
*
* @note This function is not accurate for short delays because the function overhead
* may exceed the requested delay. For very small delays the implementation uses
* a fixed sequence of NOPs (chip-dependent thresholds).
*
* @note The maximum supported delay depends on the ULP-RISC-V cycle counter width and on
* the internal cycles-per-microsecond conversion. For values above the limits below,
* the computed delay may overflow and the result is undefined.
* - ESP32-S2 ULP-RISC-V @ 8.5 MHz: delay_us must be <= 252645135 (about 252.6 s)
* - ESP32-S3 ULP-RISC-V @ 17.5 MHz: delay_us must be <= 122713351 (about 122.7 s)
*
* @param delay_us Number of microseconds to busy wait.
*/
void ulp_riscv_delay_us(uint32_t delay_us);
/**
* @brief Clears the GPIO wakeup interrupt bit
*
@@ -11,7 +11,7 @@
#include "ulp_riscv_uart_ulp_core.h"
/* We calculate the bit duration at compile time to speed up and avoid pulling in soft-float libs */
#define BIT_DURATION_CYCLES ( ULP_RISCV_CYCLES_PER_US * ((1000*1000) / CONFIG_ULP_RISCV_UART_BAUDRATE) )
#define BIT_DURATION_CYCLES ( (ULP_RISCV_CYCLES_PER_US_NUM * 1000000) / (ULP_RISCV_CYCLES_PER_US_DENOM * CONFIG_ULP_RISCV_UART_BAUDRATE) )
void ulp_riscv_uart_init(ulp_riscv_uart_t *uart, const ulp_riscv_uart_cfg_t *cfg)
{
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -11,6 +11,7 @@
#include "soc/rtc_cntl_reg.h"
#include "soc/soc_ulp.h"
#include "soc/sens_reg.h"
#include "ulp_riscv_cpu_freq_shared.h"
void ulp_riscv_rescue_from_monitor(void)
{
@@ -77,3 +78,71 @@ void ulp_riscv_trigger_sw_intr(void)
}
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
void ulp_riscv_delay_us(uint32_t delay_us)
{
#if CONFIG_IDF_TARGET_ESP32S3
/*
* For very small delays, entering the generic cycle-count loop adds too much fixed overhead.
* Use a short calibrated NOP path instead to improve small-delay accuracy on ESP32-S3.
*/
if (delay_us <= 5) {
goto fast_return;
}
#elif CONFIG_IDF_TARGET_ESP32S2
/*
* Same principle as S3, but with a different threshold due to target-specific timing/overhead.
*/
if (delay_us <= 10) {
goto fast_return;
}
#endif
/*
* Generic delay path:
* - Convert requested microseconds to CPU cycles using ratio macros.
* - Pre-subtract measured function overhead (55 cycles), so observed delay is closer to request.
*/
uint32_t start = ulp_riscv_get_cpu_cycles() - 55U;
uint32_t req_delay = delay_us * ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENOM;
/*
* Busy-wait until elapsed cycles reach req_delay.
* uint32_t subtraction intentionally relies on wrap-around-safe arithmetic for cycle counter rollover.
*/
while ((uint32_t)(ulp_riscv_get_cpu_cycles() - start) < req_delay) {
/* busy wait */
}
return;
fast_return:
#if CONFIG_IDF_TARGET_ESP32S3
/*
* Fast path for tiny delays:
* Use discrete NOP counts calibrated for this target.
* Note: (delay_us == 0 || delay_us <= 2) is intentionally kept as-is to avoid behavior changes.
*/
if (delay_us == 0 || delay_us <= 2) {
return;
} else if (delay_us <= 3) {
asm volatile("nop\n");
} else if (delay_us <= 4) {
asm volatile("nop\n nop\n");
} else {
asm volatile("nop\n nop\n nop\n nop\n nop\n");
}
#elif CONFIG_IDF_TARGET_ESP32S2
/*
* ESP32-S2 calibrated NOP mapping for very short delays.
*/
if (delay_us == 0) {
return;
} else if (delay_us <= 6) {
asm volatile("nop\n");
} else if (delay_us <= 8) {
asm volatile("nop\n nop\n");
} else {
asm volatile("nop\n nop\n nop\n nop\n nop\n nop\n");
}
#endif
}
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -43,7 +43,7 @@ static void ds18b20_write_bit(bool bit)
}
/* Write slot duration at least 60 us */
ulp_riscv_delay_cycles(60 * ULP_RISCV_CYCLES_PER_US);
ulp_riscv_delay_us(60);
ulp_riscv_gpio_output_level(EXAMPLE_1WIRE_GPIO, 1);
}
@@ -56,11 +56,11 @@ static bool ds18b20_read_bit(void)
ulp_riscv_gpio_output_level(EXAMPLE_1WIRE_GPIO, 1);
/* Must sample within 15 us of the failing edge */
ulp_riscv_delay_cycles(5 * ULP_RISCV_CYCLES_PER_US);
ulp_riscv_delay_us(5);
bit = ulp_riscv_gpio_get_level(EXAMPLE_1WIRE_GPIO);
/* Read slot duration at least 60 us */
ulp_riscv_delay_cycles(55 * ULP_RISCV_CYCLES_PER_US);
ulp_riscv_delay_us(55);
return bit;
}
@@ -86,15 +86,15 @@ bool ds18b20_reset_pulse(void)
bool presence_pulse;
/* min 480 us reset pulse + 480 us reply time is specified by datasheet */
ulp_riscv_gpio_output_level(EXAMPLE_1WIRE_GPIO, 0);
ulp_riscv_delay_cycles(480 * ULP_RISCV_CYCLES_PER_US);
ulp_riscv_delay_us(480);
ulp_riscv_gpio_output_level(EXAMPLE_1WIRE_GPIO, 1);
/* Wait for ds18b20 to pull low before sampling */
ulp_riscv_delay_cycles(60 * ULP_RISCV_CYCLES_PER_US);
ulp_riscv_delay_us(60);
presence_pulse = ulp_riscv_gpio_get_level(EXAMPLE_1WIRE_GPIO) == 0;
ulp_riscv_delay_cycles(420 * ULP_RISCV_CYCLES_PER_US);
ulp_riscv_delay_us(420);
return presence_pulse;
}
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -23,7 +23,7 @@ int main (void)
/* Wakeup interrupt is a level interrupt, wait 1 sec to
allow user to release button to avoid waking up the ULP multiple times */
ulp_riscv_delay_cycles(1000*1000 * ULP_RISCV_CYCLES_PER_US);
ulp_riscv_delay_us(1000000);
ulp_riscv_gpio_wakeup_clear();
/* ulp_riscv_halt() is called automatically when main exits */
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -71,7 +71,7 @@ static void bmp180_read_ut_data(int16_t *ut_data)
ulp_riscv_i2c_master_write_to_device(&cmd, 1);
/* Wait at least 4.5 milliseconds for the sensor to complete the reading */
ulp_riscv_delay_cycles(5 * ULP_RISCV_CYCLES_PER_MS);
ulp_riscv_delay_us(5000);
/* Read uncompensated temperature data */
bmp180_read16((uint16_t *)ut_data, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_MSB, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_LSB);
@@ -92,26 +92,26 @@ static void bmp180_read_up_data(int32_t *up_data, oss_mode_t oss_mode)
{
case OSS_0:
cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_0;
wait = 5; // Wait atleast 4.5 msec
wait = 5; // Wait at least 4.5 msec
break;
case OSS_1:
cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_1;
wait = 8; // Wait atleast 7.5 msec
wait = 8; // Wait at least 7.5 msec
break;
case OSS_2:
cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_2;
wait = 14; // Wait atleast 13.5 msec
wait = 14; // Wait at least 13.5 msec
break;
case OSS_3:
cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_3;
wait = 26; // Wait atleast 25.5 msec
wait = 26; // Wait at least 25.5 msec
break;
}
ulp_riscv_i2c_master_write_to_device(&cmd, 1);
/* Wait for the required amount of time for the sensor to complete the reading */
ulp_riscv_delay_cycles(wait * ULP_RISCV_CYCLES_PER_MS);
ulp_riscv_delay_us(wait * 1000);
/* Read uncompensated temperature data */
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -56,7 +56,7 @@ int main(void) {
ulp_riscv_wakeup_main_processor();
}
ulp_riscv_delay_cycles(1000 * ULP_RISCV_CYCLES_PER_MS);
ulp_riscv_delay_us(1000000);
}
return 0;
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -41,6 +41,6 @@ int main (void)
ulp_riscv_print_str("\n");
cnt++;
ulp_riscv_delay_cycles(1000 * ULP_RISCV_CYCLES_PER_MS);
ulp_riscv_delay_us(1000000);
}
}