mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +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:
committed by
BOT
parent
329ed7ddce
commit
6fe2cf575f
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user