feat(cordic): share the engine with acquire/release

The hardware is a singleton, so the driver now hands out references
instead of exclusive creates. new/delete stay as a deprecated pair.
csr_cfg is programmed in one RMW, and the wait path no longer queries
the RTOS tick on every poll.
This commit is contained in:
morris
2026-09-29 14:04:58 +08:00
parent 86419ad55d
commit 89ce14eaee
22 changed files with 583 additions and 358 deletions
+5 -15
View File
@@ -1,25 +1,15 @@
idf_build_get_property(target IDF_TARGET)
set(srcs)
set(public_include "include")
if(${target} STREQUAL "linux")
set(requires "")
set(priv_requires "")
else()
set(requires esp_hal_cordic)
set(priv_requires esp_pm)
endif()
set(requires esp_hal_cordic)
set(priv_requires)
if(CONFIG_SOC_CORDIC_SUPPORTED)
list(APPEND srcs
"cordic.c"
)
list(APPEND srcs "cordic.c")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS ${public_include}
PRIV_REQUIRES "${priv_requires}"
REQUIRES "${requires}"
PRIV_REQUIRES ${priv_requires}
REQUIRES ${requires}
LDFRAGMENTS linker.lf
)
+139 -85
View File
@@ -9,11 +9,11 @@
#include <stdint.h>
#include <stdbool.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "cordic_private.h"
#include "driver/cordic_types.h"
#include "driver/cordic.h"
@@ -25,140 +25,194 @@
ESP_LOG_ATTR_TAG(TAG, "cordic");
// Per result; generous margin over the maximum 15 CORDIC iterations.
#define CORDIC_ONESHOT_CALCULATE_MAX_RETRIES (1024)
typedef struct cordic_platform_t {
_lock_t mutex; // platform level mutex lock.
cordic_engine_handle_t engine_handle; // array of cordic instances.
_lock_t mutex; // protects the reference count and engine lifecycle
portMUX_TYPE spinlock; // serializes CORDIC register access from tasks and ISRs
cordic_engine_handle_t engine_handle; // the live engine, NULL while the hardware is down
uint32_t ref_count; // references handed out by cordic_acquire_engine
} cordic_platform_t;
static cordic_platform_t s_cordic_platform = {}; // singleton platform
static cordic_platform_t s_cordic_platform = {
.spinlock = portMUX_INITIALIZER_UNLOCKED,
};
static bool cordic_engine_occupied(void)
{
return s_cordic_platform.engine_handle != NULL;
}
esp_err_t cordic_new_engine(const cordic_engine_config_t *cordic_cfg, cordic_engine_handle_t *ret_engine)
// Allocates the engine and brings the hardware up. Does not touch the reference count.
// Caller must hold s_cordic_platform.mutex;
static esp_err_t cordic_internal_new_engine(const cordic_engine_config_t *cordic_cfg, cordic_engine_handle_t *ret_engine)
{
esp_err_t ret = ESP_OK;
cordic_engine_handle_t engine = NULL;
ESP_RETURN_ON_FALSE(cordic_cfg, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(ret_engine, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
cordic_clock_source_t clk_src = cordic_cfg->clock_source ? cordic_cfg->clock_source : CORDIC_CLK_SRC_DEFAULT;
engine = (cordic_engine_handle_t)heap_caps_calloc(1, sizeof(cordic_engine_t), CORDIC_MEM_ALLOC_CAPS);
cordic_engine_handle_t engine = (cordic_engine_handle_t)heap_caps_calloc(1, sizeof(cordic_engine_t), CORDIC_MEM_ALLOC_CAPS);
ESP_RETURN_ON_FALSE(engine, ESP_ERR_NO_MEM, TAG, "no memory for cordic engine");
bool engine_found = false;
_lock_acquire(&s_cordic_platform.mutex);
if (cordic_engine_occupied() == false) {
s_cordic_platform.engine_handle = engine;
engine_found = true;
}
_lock_release(&s_cordic_platform.mutex);
ESP_GOTO_ON_FALSE(engine_found, ESP_ERR_NOT_FOUND, err, TAG, "no free controller");
cordic_hal_init(&engine->hal);
cordic_ll_enable_bus_clock(true);
cordic_ll_reset_module_register();
cordic_ll_enable_clock(true);
cordic_clock_source_t clk_src = cordic_cfg->clock_source ? cordic_cfg->clock_source : CORDIC_CLK_SRC_DEFAULT;
cordic_ll_reset_module();
cordic_ll_set_clock_source(clk_src);
cordic_ll_set_clock_div(1, 0, 0);
s_cordic_platform.engine_handle = engine;
*ret_engine = engine;
return ESP_OK;
}
err:
cordic_delete_engine(engine);
// Tears the hardware down and frees the engine. Does not touch the reference count.
// Caller must hold s_cordic_platform.mutex;
static void cordic_internal_del_engine(cordic_engine_handle_t engine)
{
cordic_ll_enable_clock(false);
cordic_ll_enable_bus_clock(false);
cordic_hal_deinit(&engine->hal);
s_cordic_platform.engine_handle = NULL;
heap_caps_free(engine);
}
// Deprecated exclusive create. Pairs with cordic_delete_engine(). Mutually exclusive with
// acquire/release: a second create of the same hardware instance is rejected.
esp_err_t cordic_new_engine(const cordic_engine_config_t *cordic_cfg, cordic_engine_handle_t *ret_engine)
{
ESP_RETURN_ON_FALSE(cordic_cfg, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(ret_engine, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(cordic_cfg->instance_id < CORDIC_LL_INST_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid instance_id");
esp_err_t ret = ESP_OK;
_lock_acquire(&s_cordic_platform.mutex);
ESP_GOTO_ON_FALSE(!cordic_engine_occupied(), ESP_ERR_NOT_FOUND, unlock, TAG,
"engine already created for this instance");
ret = cordic_internal_new_engine(cordic_cfg, ret_engine);
unlock:
_lock_release(&s_cordic_platform.mutex);
return ret;
}
esp_err_t cordic_acquire_engine(const cordic_engine_config_t *cordic_cfg, cordic_engine_handle_t *ret_engine)
{
ESP_RETURN_ON_FALSE(cordic_cfg, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(ret_engine, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(cordic_cfg->instance_id < CORDIC_LL_INST_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid instance_id");
esp_err_t ret = ESP_OK;
_lock_acquire(&s_cordic_platform.mutex);
// The first acquirer allocates and initializes. Later acquirers just take another reference to the same engine
if (s_cordic_platform.ref_count == 0) {
ESP_GOTO_ON_FALSE(!cordic_engine_occupied(), ESP_ERR_INVALID_STATE, unlock, TAG,
"engine was created by cordic_new_engine");
ESP_GOTO_ON_ERROR(cordic_internal_new_engine(cordic_cfg, ret_engine), unlock, TAG, "failed to create engine");
} else {
*ret_engine = s_cordic_platform.engine_handle;
}
s_cordic_platform.ref_count++;
unlock:
_lock_release(&s_cordic_platform.mutex);
return ret;
}
esp_err_t cordic_calculate_polling(cordic_engine_handle_t engine, const cordic_calculate_config_t *calc_cfg, cordic_input_buffer_desc_t *input_buffer_desc, cordic_output_buffer_desc_t *output_buffer_desc, size_t buffer_depth)
{
ESP_RETURN_ON_FALSE_ISR(engine && calc_cfg && input_buffer_desc && input_buffer_desc->p_data_arg1 && output_buffer_desc && output_buffer_desc->p_data_res1 && buffer_depth > 0, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE_ISR(calc_cfg->function < ESP_CORDIC_FUNC_MAX, ESP_ERR_INVALID_ARG, TAG, "invalid function");
ESP_RETURN_ON_FALSE_ISR(calc_cfg->iq_format < ESP_CORDIC_IQ_SIZE_MAX, ESP_ERR_INVALID_ARG, TAG, "invalid iq_format");
ESP_RETURN_ON_FALSE_ISR(calc_cfg->scale_exp >= cordic_hal_algorithm_allowable_scale[calc_cfg->function][0] && calc_cfg->scale_exp <= cordic_hal_algorithm_allowable_scale[calc_cfg->function][1], ESP_ERR_INVALID_ARG, TAG, "invalid scale_exp");
ESP_RETURN_ON_FALSE_ISR(calc_cfg->iteration_count <= CORDIC_LL_PRECISION_MAX && calc_cfg->iteration_count > 0, ESP_ERR_INVALID_ARG, TAG, "invalid iteration_count");
// Determine if function requires two arguments
// No error logs: this path can run from an ISR, and the return code is enough.
if (!(engine && calc_cfg && input_buffer_desc && input_buffer_desc->p_data_arg1 &&
output_buffer_desc && output_buffer_desc->p_data_res1 && buffer_depth > 0)) {
return ESP_ERR_INVALID_ARG;
}
if (calc_cfg->function >= ESP_CORDIC_FUNC_MAX ||
calc_cfg->iq_format >= ESP_CORDIC_IQ_SIZE_MAX ||
calc_cfg->scale_exp < cordic_hal_algorithm_allowable_scale[calc_cfg->function][0] ||
calc_cfg->scale_exp > cordic_hal_algorithm_allowable_scale[calc_cfg->function][1] ||
calc_cfg->iteration_count == 0 ||
calc_cfg->iteration_count > CORDIC_LL_PRECISION_MAX) {
return ESP_ERR_INVALID_ARG;
}
bool is_two_args = (calc_cfg->function == ESP_CORDIC_FUNC_PHASE || calc_cfg->function == ESP_CORDIC_FUNC_MODULUS);
ESP_RETURN_ON_FALSE_ISR(is_two_args == (input_buffer_desc->p_data_arg2 != NULL), ESP_ERR_INVALID_ARG, TAG, "invalid argument");
BaseType_t in_isr = xPortInIsrContext();
// Convert timeout from milliseconds to ticks
uint32_t timeout_ms = (buffer_depth / 100 + 1) * CORDIC_ONESHOT_CALCULATE_TIMEOUT_MS;
TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms);
TickType_t start_tick = 0;
if (in_isr) {
start_tick = xTaskGetTickCountFromISR();
} else {
start_tick = xTaskGetTickCount();
if (is_two_args != (input_buffer_desc->p_data_arg2 != NULL)) {
return ESP_ERR_INVALID_ARG;
}
// Configure hardware registers based on calculation config
cordic_ll_set_calculate_function(engine->hal.dev, calc_cfg->function);
cordic_ll_set_calculate_mode(engine->hal.dev, CORDIC_LL_MODE_REG);
cordic_ll_set_calculate_precision(engine->hal.dev, calc_cfg->iteration_count - 1);
cordic_ll_set_calculate_scale(engine->hal.dev, calc_cfg->scale_exp);
cordic_ll_set_calculate_result_number(engine->hal.dev, 2);
if (is_two_args) {
cordic_ll_set_calculate_argument_number(engine->hal.dev, 2);
} else {
cordic_ll_set_calculate_argument_number(engine->hal.dev, 1);
}
cordic_ll_set_calculate_result_format(engine->hal.dev, calc_cfg->iq_format);
cordic_ll_set_calculate_argument_format(engine->hal.dev, calc_cfg->iq_format);
// Get function pointers from HAL layer arrays using format and argument count as indices
size_t arg_num = is_two_args ? 1 : 0;
cordic_set_argument_func_t set_argument_func = cordic_hal_set_argument_funcs[calc_cfg->iq_format][arg_num];
cordic_get_result_func_t get_result_func = cordic_hal_get_result_funcs[calc_cfg->iq_format];
for (size_t i = 0; i < buffer_depth; i++) {
// Use function pointer to set arguments
set_argument_func(&engine->hal, input_buffer_desc, i);
esp_err_t ret = ESP_OK;
// Short critical section: typical callers pass a few points, so masking interrupts for the
// batch is acceptable and callers do not have to handle a busy return.
portENTER_CRITICAL_SAFE(&s_cordic_platform.spinlock);
// polling mode doesn't use DMA, so set the mode to register mode.
// All csr_cfg fields are programmed in one RMW for performance's sake.
cordic_ll_set_calculate_config(engine->hal.dev,
calc_cfg->function,
CORDIC_LL_MODE_REG,
calc_cfg->iteration_count - 1,
calc_cfg->scale_exp,
is_two_args ? 2 : 1,
2,
calc_cfg->iq_format,
calc_cfg->iq_format);
for (size_t i = 0; i < buffer_depth; i++) {
set_argument_func(&engine->hal, input_buffer_desc, i);
cordic_ll_start_calculate(engine->hal.dev);
// Wait for result ready with timeout
uint32_t retries = 0;
while (cordic_ll_is_calculate_result_ready(engine->hal.dev) == 0) {
TickType_t current_tick = 0;
if (in_isr) {
current_tick = xTaskGetTickCountFromISR();
} else {
current_tick = xTaskGetTickCount();
}
// TickType_t is unsigned, so subtraction handles overflow correctly
TickType_t elapsed_ticks = current_tick - start_tick;
if (elapsed_ticks >= timeout_ticks) {
ESP_DRAM_LOGE(TAG, "CORDIC calculation timeout after %d ms", timeout_ms);
cordic_ll_reset_module_register();
return ESP_ERR_TIMEOUT;
if (++retries >= CORDIC_ONESHOT_CALCULATE_MAX_RETRIES) {
cordic_ll_reset_module();
ret = ESP_ERR_TIMEOUT;
goto exit;
}
}
// Use function pointer to get results
get_result_func(&engine->hal, output_buffer_desc, i);
}
return ESP_OK;
exit:
portEXIT_CRITICAL_SAFE(&s_cordic_platform.spinlock);
return ret;
}
esp_err_t cordic_release_engine(cordic_engine_handle_t engine)
{
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(engine, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
_lock_acquire(&s_cordic_platform.mutex);
// Covers a stale handle, a double release, and a handle that does not belong to this instance
ESP_GOTO_ON_FALSE(s_cordic_platform.engine_handle == engine, ESP_ERR_INVALID_STATE, unlock, TAG, "engine was not acquired");
ESP_GOTO_ON_FALSE(s_cordic_platform.ref_count > 0, ESP_ERR_INVALID_STATE, unlock, TAG, "no reference is held");
if (--s_cordic_platform.ref_count == 0) {
cordic_internal_del_engine(engine);
}
unlock:
_lock_release(&s_cordic_platform.mutex);
return ret;
}
// Deprecated exclusive destroy. Pairs with cordic_new_engine(). Mutually exclusive with
// acquire/release; refuses if the engine is currently held via acquire.
esp_err_t cordic_delete_engine(cordic_engine_handle_t engine)
{
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(engine, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
cordic_ll_enable_bus_clock(false);
cordic_ll_enable_clock(false);
cordic_hal_deinit(&engine->hal);
_lock_acquire(&s_cordic_platform.mutex);
s_cordic_platform.engine_handle = NULL;
ESP_GOTO_ON_FALSE(s_cordic_platform.engine_handle == engine, ESP_ERR_INVALID_STATE, unlock, TAG, "engine was not created");
ESP_GOTO_ON_FALSE(s_cordic_platform.ref_count == 0, ESP_ERR_INVALID_STATE, unlock, TAG, "engine is still acquired");
cordic_internal_del_engine(engine);
unlock:
_lock_release(&s_cordic_platform.mutex);
heap_caps_free(engine);
return ret;
}
@@ -8,7 +8,6 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "hal/cordic_hal.h"
@@ -26,7 +25,6 @@ extern "C" {
#define CORDIC_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
#endif
#define CORDIC_ONESHOT_CALCULATE_TIMEOUT_MS (2) // 2ms is enough for cordic one shot calculation api
#define CORDIC_Q15_SCALE_FACTOR (32768.0f) // 2^15, scale factor for Q15 format conversion
#define CORDIC_Q31_SCALE_FACTOR (2147483648.0f) // 2^31, scale factor for Q31 format conversion
@@ -18,7 +18,8 @@ extern "C" {
* @brief CORDIC engine configuration structure
*/
typedef struct {
cordic_clock_source_t clock_source; /*!< CORDIC clock source */
cordic_clock_source_t clock_source; /*!< CORDIC clock source, 0 means CORDIC_CLK_SRC_DEFAULT */
uint32_t instance_id; /*!< CORDIC hardware instance ID, index from 0 */
} cordic_engine_config_t;
/**
@@ -32,32 +33,62 @@ typedef struct {
} cordic_calculate_config_t;
/**
* @brief Create a new CORDIC engine instance
* @brief Acquire a CORDIC engine instance
*
* This function creates and initializes a CORDIC engine with the specified configuration.
* The engine handle returned can be used for subsequent CORDIC calculations.
* The CORDIC hardware is shared by everyone who needs trigonometric or hyperbolic math, so this
* function does not necessarily create anything: the first caller initializes the hardware and
* every later caller just gets one more reference to the very same engine. The hardware stays up
* until the last reference is given back with cordic_release_engine().
*
* @note This function takes a mutex and may block. Call it from a task, not from an ISR.
* @note Do not mix this function with the deprecated cordic_new_engine() / cordic_delete_engine().
* @note A later clock_source is ignored. The first acquirer decides the clock until the last release.
*
* @param[in] cordic_cfg Pointer to CORDIC engine configuration structure. Must not be NULL.
* @param[out] ret_engine Pointer to store the created CORDIC engine handle. Must not be NULL.
* @param[out] ret_engine Pointer to store the acquired CORDIC engine handle. Must not be NULL.
*
* @return
* - ESP_OK: CORDIC engine created successfully.
* - ESP_ERR_INVALID_ARG: Invalid argument (NULL pointer or invalid scale exponent).
* - ESP_OK: CORDIC engine acquired successfully.
* - ESP_ERR_INVALID_ARG: Invalid argument (NULL pointer or unsupported instance_id).
* - ESP_ERR_INVALID_STATE: Engine was created by cordic_new_engine().
* - ESP_ERR_NO_MEM: Failed to allocate memory for the engine.
*/
esp_err_t cordic_new_engine(const cordic_engine_config_t *cordic_cfg, cordic_engine_handle_t *ret_engine);
esp_err_t cordic_acquire_engine(const cordic_engine_config_t *cordic_cfg, cordic_engine_handle_t *ret_engine);
/**
* @brief Start one-shot CORDIC calculation
* @brief Release a CORDIC engine instance
*
* This function gives back one reference to the CORDIC engine. The hardware is deinitialized and the
* engine memory freed once the last reference is released; until then the handle stays valid for the
* other users.
*
* @note This function takes a mutex and may block. Call it from a task, not from an ISR.
*
* @param[in] engine CORDIC engine handle acquired by cordic_acquire_engine(). Must not be NULL.
*
* @return
* - ESP_OK: Engine reference released successfully.
* - ESP_ERR_INVALID_ARG: Invalid argument (NULL pointer).
* - ESP_ERR_INVALID_STATE: No reference is currently held, or the handle is not the live engine.
*/
esp_err_t cordic_release_engine(cordic_engine_handle_t engine);
/**
* @brief Perform one-shot CORDIC calculation, polling the hardware until each point completes
*
* This function performs CORDIC calculations on a batch of data points. It processes each data point
* sequentially: sets arguments, starts calculation, waits for completion, and retrieves results.
*
* The chip has a single CORDIC. Register access is serialized with a short critical section, so
* tasks, ISRs and both cores can call this function without extra locking. Keep ``buffer_depth``
* small: interrupts on the calling core are masked for the duration of the batch.
*
* @note The function can be safely used in ISR, but for the ISR run in cache-safe, please
* enable ``CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM`` to place the control functions into IRAM.
* @note This function is not guaranteed to be thread-safe.
* @note The engine handle must have been acquired with cordic_acquire_engine() and must not be
* released concurrently.
*
* @param[in] engine CORDIC engine handle created by cordic_new_engine(). Must not be NULL.
* @param[in] engine CORDIC engine handle acquired by cordic_acquire_engine(). Must not be NULL.
* @param[in] calc_cfg Pointer to CORDIC calculation configuration structure. Must not be NULL.
* @param[in] input_buffer_desc Pointer to input buffer descriptor containing input data. Must not be NULL.
* @param[out] output_buffer_desc Pointer to output buffer descriptor for storing results. Must not be NULL.
@@ -65,25 +96,11 @@ esp_err_t cordic_new_engine(const cordic_engine_config_t *cordic_cfg, cordic_eng
*
* @return
* - ESP_OK: All calculations completed successfully.
* - ESP_ERR_INVALID_ARG: Invalid argument (NULL pointer, zero buffer_depth, or invalid timeout).
* - ESP_ERR_TIMEOUT: Calculation timeout.
* - ESP_ERR_INVALID_ARG: Invalid pointer, zero buffer_depth, or invalid calculation configuration.
* - ESP_ERR_TIMEOUT: Calculation did not complete within the driver's polling retry limit.
*/
esp_err_t cordic_calculate_polling(cordic_engine_handle_t engine, const cordic_calculate_config_t *calc_cfg, cordic_input_buffer_desc_t *input_buffer_desc, cordic_output_buffer_desc_t *output_buffer_desc, size_t buffer_depth);
/**
* @brief Delete a CORDIC engine instance
*
* This function deletes a CORDIC engine and frees all associated resources including the mutex.
* After calling this function, the engine handle becomes invalid and should not be used.
*
* @param[in] engine CORDIC engine handle to delete. Must not be NULL.
*
* @return
* - ESP_OK: Engine deleted successfully.
* - ESP_ERR_INVALID_ARG: Invalid argument (NULL pointer).
*/
esp_err_t cordic_delete_engine(cordic_engine_handle_t engine);
/**
* @brief Convert CORDIC hex value to float
*
@@ -110,6 +127,54 @@ float cordic_convert_fixed_to_float(uint32_t fixed_value, cordic_iq_format_t iq_
*/
uint32_t cordic_convert_float_to_fixed(float float_value, cordic_iq_format_t iq_format);
/** @cond */ /* Hide deprecated APIs from Doxygen programming docs */
/**
* @brief Create a CORDIC engine instance.
*
* @deprecated Use cordic_acquire_engine() and cordic_release_engine() instead.
*
* This function initializes the CORDIC hardware and stores its handle in ``ret_engine``.
* The engine handle can be used for subsequent CORDIC calculations. Destroy it with cordic_delete_engine().
*
* @note This function fails if the same hardware instance is already created,
* whether by a previous cordic_new_engine() or by cordic_acquire_engine().
* A second create is not a way to allocate another engine.
* @note Do not mix this function with cordic_acquire_engine() / cordic_release_engine().
* @note Pair this function with cordic_delete_engine() to destroy the engine.
*
* @param[in] cordic_cfg Pointer to CORDIC engine configuration structure. Must not be NULL.
* @param[out] ret_engine Pointer to store the created CORDIC engine handle. Must not be NULL.
*
* @return
* - ESP_OK: CORDIC engine created successfully.
* - ESP_ERR_INVALID_ARG: Invalid argument (NULL pointer or unsupported instance_id).
* - ESP_ERR_NOT_FOUND: Engine is already created for this hardware instance.
* - ESP_ERR_NO_MEM: Failed to allocate memory for the engine.
*/
esp_err_t cordic_new_engine(const cordic_engine_config_t *cordic_cfg, cordic_engine_handle_t *ret_engine)
__attribute__((deprecated("Please use cordic_acquire_engine instead")));
/**
* @brief Delete a CORDIC engine instance
*
* @deprecated Use cordic_acquire_engine() and cordic_release_engine() instead.
*
* @note This function is the counterpart of the cordic_new_engine(), it tears the engine down immediately.
* @note This function fails if the handle is not the live engine, if it was already deleted,
* or if the engine is currently held via cordic_acquire_engine().
* @note Do not mix this function with cordic_acquire_engine() / cordic_release_engine().
*
* @param[in] engine CORDIC engine handle created by cordic_new_engine(). Must not be NULL.
*
* @return
* - ESP_OK: Engine deleted successfully.
* - ESP_ERR_INVALID_ARG: Invalid argument (NULL pointer).
* - ESP_ERR_INVALID_STATE: Handle is not the live engine, already deleted, or still acquired.
*/
esp_err_t cordic_delete_engine(cordic_engine_handle_t engine)
__attribute__((deprecated("Please use cordic_acquire_engine and cordic_release_engine instead")));
/** @endcond */
#ifdef __cplusplus
}
#endif
@@ -1,6 +1,6 @@
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
components/esp_driver_cordic/test_apps/cordic_test_apps:
components/esp_driver_cordic/test_apps/cordic_driver_ut:
disable:
- if: SOC_CORDIC_SUPPORTED != 1
depends_components:
@@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.22)
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(cordic_test)
project(cordic_driver_ut)
idf_build_get_property(elf EXECUTABLE)
if(CONFIG_COMPILER_DUMP_RTL_FILES)
@@ -21,7 +21,7 @@ if(CONFIG_COMPILER_DUMP_RTL_FILES)
add_custom_target(check_test_app_sections ALL
COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
--rtl-dirs ${CORDIC_RTL_DIRS_JOINED}
--elf-file ${CMAKE_BINARY_DIR}/cordic_test.elf
--elf-file $<TARGET_FILE:${elf}>
find-refs
--from-sections=.iram0.text
--to-sections=.flash.text,.flash.rodata
@@ -0,0 +1,9 @@
set(srcs "test_app_main.c")
if(CONFIG_SOC_CORDIC_SUPPORTED)
list(APPEND srcs "test_cordic_common.c" "test_cordic_q15.c" "test_cordic_q31.c")
endif()
idf_component_register(SRCS ${srcs}
PRIV_REQUIRES unity esp_driver_cordic esp_driver_gptimer spi_flash
WHOLE_ARCHIVE)
@@ -1,5 +1,5 @@
dependencies:
ccomp_timer: "^1.0.0"
iqmath: "^1.0.0"
iqmath: "^2.0.0"
test_utils:
path: ${IDF_PATH}/tools/test_apps/components/test_utils
@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "unity.h"
#include "driver/cordic.h"
TEST_CASE("cordic acquire-release test", "[cordic]")
{
cordic_engine_handle_t engine = NULL;
cordic_engine_handle_t engine2 = NULL;
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
// Multiple users share the one and only hardware, everyone gets the same handle
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine2));
TEST_ASSERT_EQUAL(engine, engine2);
// Hardware is only torn down when the last reference is gone
TEST_ESP_OK(cordic_release_engine(engine2));
TEST_ESP_OK(cordic_release_engine(engine));
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, cordic_release_engine(engine));
// There is a single CORDIC instance on this chip
cordic_engine_config_t invalid_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
.instance_id = 100, // Invalid instance ID
};
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, cordic_acquire_engine(&invalid_config, &engine));
}
TEST_CASE("new/delete must not mix with acquire/release", "[cordic]")
{
cordic_engine_handle_t engine = NULL;
cordic_engine_handle_t other = NULL;
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, cordic_new_engine(&engine_config, &other));
TEST_ESP_OK(cordic_release_engine(engine));
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, cordic_acquire_engine(&engine_config, &other));
TEST_ESP_OK(cordic_delete_engine(engine));
#pragma GCC diagnostic pop
}
@@ -7,11 +7,13 @@
#include <stdio.h>
#include <inttypes.h>
#include <math.h>
#include <stdatomic.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "unity.h"
#include "unity_test_utils.h"
#include "soc/cordic_reg.h"
#include "hal/cordic_types.h"
#include "hal/cordic_ll.h"
@@ -28,24 +30,13 @@
#define PI 3.14159265358979323846
static uint32_t iq_pi_x = _IQ15(PI);
TEST_CASE("cordic install-uninstall test", "[cordic]")
{
cordic_engine_handle_t engine = NULL;
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_delete_engine(engine));
}
TEST_CASE("test cosine and sine q15", "[cordic]")
{
cordic_engine_handle_t engine = NULL;
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_COS,
@@ -106,7 +97,7 @@ TEST_CASE("test cosine and sine q15", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.01f, sw_sine, hw_sine);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test phase and module q15", "[cordic]")
@@ -115,7 +106,7 @@ TEST_CASE("test phase and module q15", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_PHASE,
@@ -181,7 +172,7 @@ TEST_CASE("test phase and module q15", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_module, hw_module);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test arctan q15", "[cordic]")
@@ -190,7 +181,7 @@ TEST_CASE("test arctan q15", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_ARCTAN,
@@ -245,7 +236,7 @@ TEST_CASE("test arctan q15", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_arctan_normalized, hw_arctan_normalized);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test hyperbolic cosine and sine q15", "[cordic]")
@@ -254,7 +245,7 @@ TEST_CASE("test hyperbolic cosine and sine q15", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_COSH,
@@ -317,7 +308,7 @@ TEST_CASE("test hyperbolic cosine and sine q15", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_sinh, hw_sinh);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test arctanh q15", "[cordic]")
@@ -326,7 +317,7 @@ TEST_CASE("test arctanh q15", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_ARCHTANH,
@@ -385,7 +376,7 @@ TEST_CASE("test arctanh q15", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_arctanh_normalized, hw_arctanh_normalized);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test natural logarithm q15", "[cordic]")
@@ -394,7 +385,7 @@ TEST_CASE("test natural logarithm q15", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_LOGE,
@@ -458,7 +449,7 @@ TEST_CASE("test natural logarithm q15", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_lnx, hw_lnx);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test square root q15", "[cordic]")
@@ -467,7 +458,7 @@ TEST_CASE("test square root q15", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_SQUARE_ROOT,
@@ -524,7 +515,7 @@ TEST_CASE("test square root q15", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_sqrtx, hw_sqrtx);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test arctan scale 2 q15", "[cordic]")
@@ -533,7 +524,7 @@ TEST_CASE("test arctan scale 2 q15", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_ARCTAN,
@@ -603,7 +594,7 @@ TEST_CASE("test arctan scale 2 q15", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_arctan_normalized, hw_arctan_normalized);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test cordic performance should quicker than software iqmath", "[cordic]")
@@ -612,7 +603,7 @@ TEST_CASE("test cordic performance should quicker than software iqmath", "[cordi
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_COS,
@@ -668,8 +659,111 @@ TEST_CASE("test cordic performance should quicker than software iqmath", "[cordi
// Use the volatile variables to prevent dead code elimination
(void)dummy_cos;
(void)dummy_sin;
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
/*---------------------------------------------------------------
CORDIC shared by concurrent users
---------------------------------------------------------------*/
#define CONCURRENT_TASK_NUM (SOC_CPU_CORES_NUM)
#define CONCURRENT_DEPTH (16)
typedef struct {
cordic_engine_handle_t engine;
esp_err_t ret;
atomic_bool done;
uint32_t arg[CONCURRENT_DEPTH];
uint32_t res1[CONCURRENT_DEPTH];
uint32_t res2[CONCURRENT_DEPTH];
} concurrent_task_ctx_t;
static atomic_uint s_concurrent_ready;
static atomic_bool s_concurrent_go;
static void concurrent_calculate_task(void *arg)
{
concurrent_task_ctx_t *ctx = (concurrent_task_ctx_t *)arg;
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_COS,
.iq_format = ESP_CORDIC_FORMAT_Q15,
.iteration_count = 4,
.scale_exp = 0,
};
cordic_input_buffer_desc_t input_buffer = {
.p_data_arg1 = ctx->arg,
.p_data_arg2 = NULL,
};
cordic_output_buffer_desc_t output_buffer = {
.p_data_res1 = ctx->res1,
.p_data_res2 = ctx->res2,
};
atomic_fetch_add(&s_concurrent_ready, 1);
// Must yield: the task on core 0 would otherwise starve the Unity task that releases the barrier.
while (!atomic_load(&s_concurrent_go)) {
vTaskDelay(1);
}
ctx->ret = ESP_FAIL;
for (int i = 0; i < 50; i++) {
ctx->ret = cordic_calculate_polling(ctx->engine, &calc_config, &input_buffer, &output_buffer, CONCURRENT_DEPTH);
if (ctx->ret != ESP_OK) {
break;
}
}
atomic_store(&ctx->done, true);
// Don't self-delete: idle may not reclaim the TCB/stack before Unity's leak check.
vTaskSuspend(NULL);
}
TEST_CASE("cordic results stay correct when shared by multiple tasks", "[cordic]")
{
cordic_engine_handle_t engine = NULL;
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
static concurrent_task_ctx_t task_ctx[CONCURRENT_TASK_NUM] = {};
atomic_store(&s_concurrent_ready, 0);
atomic_store(&s_concurrent_go, false);
for (uint32_t t = 0; t < CONCURRENT_TASK_NUM; t++) {
task_ctx[t].engine = engine;
task_ctx[t].ret = ESP_FAIL;
atomic_store(&task_ctx[t].done, false);
for (int i = 0; i < CONCURRENT_DEPTH; i++) {
float float_value = -1.0f + (i * 2.0f / (CONCURRENT_DEPTH - 1));
task_ctx[t].arg[i] = cordic_convert_float_to_fixed(float_value, ESP_CORDIC_FORMAT_Q15);
}
}
TaskHandle_t task_handles[CONCURRENT_TASK_NUM] = {};
for (uint32_t t = 0; t < CONCURRENT_TASK_NUM; t++) {
xTaskCreatePinnedToCore(concurrent_calculate_task, "cordic_calc", 4096, &task_ctx[t], 5, &task_handles[t], t % configNUM_CORES);
}
while (atomic_load(&s_concurrent_ready) < CONCURRENT_TASK_NUM) {
vTaskDelay(1);
}
atomic_store(&s_concurrent_go, true);
for (uint32_t t = 0; t < CONCURRENT_TASK_NUM; t++) {
while (!atomic_load(&task_ctx[t].done)) {
vTaskDelay(1);
}
unity_utils_task_delete(task_handles[t]);
}
// Every task fed the same angles, so a hardware race would show up as a wrong result here.
for (uint32_t t = 0; t < CONCURRENT_TASK_NUM; t++) {
TEST_ESP_OK(task_ctx[t].ret);
for (int i = 0; i < CONCURRENT_DEPTH; i++) {
float angle_rad = cordic_convert_fixed_to_float(task_ctx[t].arg[i], ESP_CORDIC_FORMAT_Q15) * M_PI;
TEST_ASSERT_FLOAT_WITHIN(0.01f, cosf(angle_rad), cordic_convert_fixed_to_float(task_ctx[t].res1[i], ESP_CORDIC_FORMAT_Q15));
TEST_ASSERT_FLOAT_WITHIN(0.01f, sinf(angle_rad), cordic_convert_fixed_to_float(task_ctx[t].res2[i], ESP_CORDIC_FORMAT_Q15));
}
}
TEST_ESP_OK(cordic_release_engine(engine));
}
#if CONFIG_CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM
@@ -680,6 +774,7 @@ TEST_CASE("test cordic performance should quicker than software iqmath", "[cordi
typedef struct {
TaskHandle_t task_handle; //Task handle
cordic_engine_handle_t engine_handle; // engine handle
esp_err_t ret;
uint32_t res1;
uint32_t res2;
} test_cordic_isr_ctx_t;
@@ -711,8 +806,9 @@ static bool IRAM_ATTR s_alarm_callback(gptimer_handle_t timer, const gptimer_ala
.iteration_count = 4,
.scale_exp = 0,
};
// TEST_ESP_OK lives in flash; do not assert while the cache is down.
spi_flash_disable_interrupts_caches_and_other_cpu();
TEST_ESP_OK(cordic_calculate_polling(test_ctx->engine_handle, &calc_config, &input_buffer, &output_buffer, 1));
test_ctx->ret = cordic_calculate_polling(test_ctx->engine_handle, &calc_config, &input_buffer, &output_buffer, 1);
spi_flash_enable_interrupts_caches_and_other_cpu();
test_ctx->res1 = res1;
@@ -726,12 +822,13 @@ TEST_CASE("cordic can calculation in ISR", "[cordic]")
{
static test_cordic_isr_ctx_t isr_test_ctx = {};
isr_test_ctx.engine_handle = NULL;
isr_test_ctx.ret = ESP_FAIL;
isr_test_ctx.task_handle = xTaskGetCurrentTaskHandle();
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &isr_test_ctx.engine_handle));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &isr_test_ctx.engine_handle));
//-------------GPTimer Init & Config---------------//
gptimer_handle_t timer = NULL;
@@ -756,12 +853,13 @@ TEST_CASE("cordic can calculation in ISR", "[cordic]")
TEST_ESP_OK(gptimer_enable(timer));
TEST_ESP_OK(gptimer_start(timer));
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
TEST_ESP_OK(isr_test_ctx.ret);
esp_rom_printf(DRAM_STR("CORDIC calculation done, cos is 0x%lx, sine is 0x%lx\n"), isr_test_ctx.res1, isr_test_ctx.res2);
TEST_ESP_OK(gptimer_stop(timer));
TEST_ESP_OK(gptimer_disable(timer));
TEST_ESP_OK(gptimer_del_timer(timer));
TEST_ESP_OK(cordic_delete_engine(isr_test_ctx.engine_handle));
TEST_ESP_OK(cordic_release_engine(isr_test_ctx.engine_handle));
}
@@ -24,7 +24,7 @@ TEST_CASE("test cosine and sine q31", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_COS,
@@ -76,7 +76,7 @@ TEST_CASE("test cosine and sine q31", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_sine, hw_sine);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test phase and module q31", "[cordic]")
@@ -85,7 +85,7 @@ TEST_CASE("test phase and module q31", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_PHASE,
@@ -150,7 +150,7 @@ TEST_CASE("test phase and module q31", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_module, hw_module);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test arctan q31", "[cordic]")
@@ -159,7 +159,7 @@ TEST_CASE("test arctan q31", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_ARCTAN,
@@ -213,7 +213,7 @@ TEST_CASE("test arctan q31", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_arctan_normalized, hw_arctan_normalized);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test hyperbolic cosine and sine q31", "[cordic]")
@@ -222,7 +222,7 @@ TEST_CASE("test hyperbolic cosine and sine q31", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_COSH,
@@ -279,7 +279,7 @@ TEST_CASE("test hyperbolic cosine and sine q31", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_sinh, hw_sinh);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test arctanh q31", "[cordic]")
@@ -288,7 +288,7 @@ TEST_CASE("test arctanh q31", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_ARCHTANH,
@@ -343,7 +343,7 @@ TEST_CASE("test arctanh q31", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.0001f, sw_arctanh_normalized, hw_arctanh_normalized);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test natural logarithm q31", "[cordic]")
@@ -352,7 +352,7 @@ TEST_CASE("test natural logarithm q31", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_LOGE,
@@ -416,7 +416,7 @@ TEST_CASE("test natural logarithm q31", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_lnx, hw_lnx);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test square root q31", "[cordic]")
@@ -425,7 +425,7 @@ TEST_CASE("test square root q31", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_SQUARE_ROOT,
@@ -480,7 +480,7 @@ TEST_CASE("test square root q31", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.00001f, sw_sqrtx, hw_sqrtx);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test arctan scale 2 q31", "[cordic]")
@@ -489,7 +489,7 @@ TEST_CASE("test arctan scale 2 q31", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_ARCTAN,
@@ -558,7 +558,7 @@ TEST_CASE("test arctan scale 2 q31", "[cordic]")
TEST_ASSERT_FLOAT_WITHIN(0.001f, sw_arctan_normalized, hw_arctan_normalized);
printf("\n");
}
TEST_ESP_OK(cordic_delete_engine(engine));
TEST_ESP_OK(cordic_release_engine(engine));
}
TEST_CASE("test reconfig from cos q15 to log q31", "[cordic]")
@@ -569,7 +569,7 @@ TEST_CASE("test reconfig from cos q15 to log q31", "[cordic]")
cordic_engine_config_t engine_config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT,
};
TEST_ESP_OK(cordic_new_engine(&engine_config, &engine));
TEST_ESP_OK(cordic_acquire_engine(&engine_config, &engine));
cordic_calculate_config_t calc_config_cos = {
.function = ESP_CORDIC_FUNC_COS,
@@ -678,5 +678,5 @@ TEST_CASE("test reconfig from cos q15 to log q31", "[cordic]")
printf("\n");
}
cordic_delete_engine(engine);
cordic_release_engine(engine);
}
@@ -0,0 +1,20 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
from pytest_embedded_idf.utils import soc_filtered_targets
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
[
'release',
'iram_safe',
],
indirect=True,
)
@idf_parametrize('target', soc_filtered_targets('SOC_CORDIC_SUPPORTED == 1'), indirect=['target'])
def test_cordic(dut: Dut) -> None:
dut.run_all_single_board_cases()
@@ -1,13 +0,0 @@
set(srcs "test_app_main.c"
)
if(CONFIG_SOC_CORDIC_SUPPORTED)
list(APPEND srcs
"test_cordic_q15.c"
"test_cordic_q31.c"
)
endif()
idf_component_register(SRCS ${srcs}
PRIV_REQUIRES unity test_utils esp_driver_cordic ccomp_timer esp_driver_gptimer spi_flash
WHOLE_ARCHIVE)
-3
View File
@@ -1,7 +1,4 @@
idf_build_get_property(target IDF_TARGET)
if(${target} STREQUAL "linux")
return() # This component is not supported by the POSIX/Linux simulator
endif()
set(includes)
@@ -15,13 +15,14 @@
#include "hal/cordic_types.h"
#include "soc/hp_sys_clkrst_struct.h"
#define CORDIC_LL_GET_HW() (&CORDIC)
#define CORDIC_LL_PRECISION_MAX 0xF
#define CORDIC_LL_INST_NUM 1U /*!< Number of CORDIC hardware instances on this chip */
#ifdef __cplusplus
extern "C" {
#endif
#define CORDIC_LL_GET_HW() (&CORDIC)
#define CORDIC_LL_PRECISION_MAX (0xF)
/**
* @brief CORDIC operation mode
*/
@@ -31,7 +32,7 @@ typedef enum {
} cordic_ll_mode_t;
/**
* @brief Enable the hardware clock for CORDIC module
* @brief Enable the APB bus clock for CORDIC register access
*
* @param enable True to enable; false to disable
*/
@@ -41,22 +42,28 @@ static inline void cordic_ll_enable_bus_clock(bool enable)
}
/**
* @brief Enable the CORDIC module clock
* @brief Enable the CORDIC system and functional clocks
*
* @param enable True to enable; false to disable
*/
static inline void cordic_ll_enable_clock(bool enable)
{
HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_sys_clk_en = enable;
HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_clk_en = enable;
}
/**
* @brief Reset the CORDIC module
* @brief Reset the CORDIC module (APB, system clock domain, and computation core)
*/
static inline void cordic_ll_reset_module_register(void)
__attribute__((always_inline))
static inline void cordic_ll_reset_module(void)
{
HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_apb_rst_en = 1;
HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_apb_rst_en = 0;
HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_sys_rst_en = 1;
HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_sys_rst_en = 0;
HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_core_rst_en = 1;
HP_SYS_CLKRST.cordic_ctrl0.reg_cordic_core_rst_en = 0;
}
/**
@@ -83,146 +90,94 @@ static inline void cordic_ll_set_clock_source(cordic_clock_source_t source)
}
/**
* @brief Set the calculation function type (e.g., cosine, sine, arctan)
* @brief Set the CORDIC functional clock divider
*
* @param hw Pointer to the CORDIC hardware register structure
* @param function Function type to calculate
* The output clock is: src / (integer + numerator / denominator).
* integer is programmed as (integer - 1). numerator = 0 and denominator = 0
* means no fractional part.
*
* @param integer Integral divider, range [1, CORDIC_LL_CLK_DIV_INTEGER_MAX]
* @param numerator Fractional numerator, range [0, CORDIC_LL_CLK_DIV_FRACT_MAX]
* @param denominator Fractional denominator, range [0, CORDIC_LL_CLK_DIV_FRACT_MAX]
*/
static inline void cordic_ll_set_clock_div(uint32_t integer, uint32_t numerator, uint32_t denominator)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(HP_SYS_CLKRST.cordic_ctrl1, reg_cordic_clk_div_num, integer - 1);
HAL_FORCE_MODIFY_U32_REG_FIELD(HP_SYS_CLKRST.cordic_ctrl1, reg_cordic_clk_div_numerator, numerator);
HAL_FORCE_MODIFY_U32_REG_FIELD(HP_SYS_CLKRST.cordic_ctrl1, reg_cordic_clk_div_denonimator, denominator);
}
/**
* @brief Program the calculation configuration fields of csr_cfg with one RMW
*
* @param hw Pointer to the CORDIC hardware register structure
* @param function Function type to calculate
* @param mode Data transfer mode (register or DMA)
* @param precision Iteration count programmed into press (already decremented by the caller)
* @param scale Input scale exponent
* @param arg_num Number of input arguments (1 or 2)
* @param res_num Number of result outputs (1 or 2)
* @param arg_format IQ format of the input arguments
* @param res_format IQ format of the results
*/
__attribute__((always_inline))
static inline void cordic_ll_set_calculate_function(cordic_dev_t *hw, cordic_func_t function)
static inline void cordic_ll_set_calculate_config(cordic_dev_t *hw,
cordic_func_t function,
cordic_ll_mode_t mode,
uint16_t precision,
uint16_t scale,
uint8_t arg_num,
uint8_t res_num,
cordic_iq_format_t arg_format,
cordic_iq_format_t res_format)
{
cordic_csr_cfg_reg_t csr;
csr.val = hw->csr_cfg.val;
switch (function) {
case ESP_CORDIC_FUNC_COS:
hw->csr_cfg.func = 0;
csr.func = 0;
break;
case ESP_CORDIC_FUNC_SIN:
hw->csr_cfg.func = 1;
csr.func = 1;
break;
case ESP_CORDIC_FUNC_PHASE:
hw->csr_cfg.func = 2;
csr.func = 2;
break;
case ESP_CORDIC_FUNC_MODULUS:
hw->csr_cfg.func = 3;
csr.func = 3;
break;
case ESP_CORDIC_FUNC_ARCTAN:
hw->csr_cfg.func = 4;
csr.func = 4;
break;
case ESP_CORDIC_FUNC_COSH:
hw->csr_cfg.func = 5;
csr.func = 5;
break;
case ESP_CORDIC_FUNC_SINH:
hw->csr_cfg.func = 6;
csr.func = 6;
break;
case ESP_CORDIC_FUNC_ARCHTANH:
hw->csr_cfg.func = 7;
csr.func = 7;
break;
case ESP_CORDIC_FUNC_LOGE:
hw->csr_cfg.func = 8;
csr.func = 8;
break;
case ESP_CORDIC_FUNC_SQUARE_ROOT:
hw->csr_cfg.func = 9;
csr.func = 9;
break;
default:
HAL_ASSERT(false);
}
}
/**
* @brief Set the calculation mode (register mode or DMA mode)
*
* @param hw Pointer to the CORDIC hardware register structure
* @param mode Operation mode (REG_MODE or DMA_MODE)
*/
__attribute__((always_inline))
static inline void cordic_ll_set_calculate_mode(cordic_dev_t *hw, cordic_ll_mode_t mode)
{
if (mode == CORDIC_LL_MODE_REG) {
hw->csr_cfg.work_mode = 0;
} else if (mode == CORDIC_LL_MODE_DMA) {
hw->csr_cfg.work_mode = 1;
} else {
HAL_ASSERT(false);
}
}
/**
* @brief Set the calculation precision (iteration count)
*
* @param hw Pointer to the CORDIC hardware register structure
* @param precision Precision value (number of iterations)
*/
__attribute__((always_inline))
static inline void cordic_ll_set_calculate_precision(cordic_dev_t *hw, uint16_t precision)
{
hw->csr_cfg.press = precision;
}
/**
* @brief Set the input data scale factor
*
* @param hw Pointer to the CORDIC hardware register structure
* @param scale Scale factor for input data (range depends on function type)
*/
__attribute__((always_inline))
static inline void cordic_ll_set_calculate_scale(cordic_dev_t *hw, uint16_t scale)
{
hw->csr_cfg.scale = scale;
}
/**
* @brief Set the number of result outputs (one or two)
*
* @param hw Pointer to the CORDIC hardware register structure
* @param number Number of result outputs (ONE_NUM or TWO_NUM)
*/
__attribute__((always_inline))
static inline void cordic_ll_set_calculate_result_number(cordic_dev_t *hw, uint8_t number)
{
hw->csr_cfg.res_num = number - 1;
}
/**
* @brief Set the number of input arguments (one or two)
*
* @param hw Pointer to the CORDIC hardware register structure
* @param number Number of input arguments (ONE_NUM or TWO_NUM)
*/
__attribute__((always_inline))
static inline void cordic_ll_set_calculate_argument_number(cordic_dev_t *hw, uint8_t number)
{
hw->csr_cfg.arg_num = number - 1;
}
/**
* @brief Set the result data format size (Q15 or Q31)
*
* @param hw Pointer to the CORDIC hardware register structure
* @param size Result data format (Q15_SIZE or Q31_SIZE)
*/
__attribute__((always_inline))
static inline void cordic_ll_set_calculate_result_format(cordic_dev_t *hw, cordic_iq_format_t format)
{
if (format == ESP_CORDIC_FORMAT_Q15) {
hw->csr_cfg.res_size = 0;
} else {
hw->csr_cfg.res_size = 1;
}
}
/**
* @brief Set the argument data format size (Q15 or Q31)
*
* @param hw Pointer to the CORDIC hardware register structure
* @param size Argument data format (Q15_SIZE or Q31_SIZE)
*/
__attribute__((always_inline))
static inline void cordic_ll_set_calculate_argument_format(cordic_dev_t *hw, cordic_iq_format_t format)
{
if (format == ESP_CORDIC_FORMAT_Q15) {
hw->csr_cfg.arg_size = 0;
} else {
hw->csr_cfg.arg_size = 1;
}
csr.work_mode = mode;
csr.press = precision;
csr.scale = scale;
csr.arg_num = arg_num - 1;
csr.res_num = res_num - 1;
csr.res_size = (res_format == ESP_CORDIC_FORMAT_Q15) ? 0 : 1;
csr.arg_size = (arg_format == ESP_CORDIC_FORMAT_Q15) ? 0 : 1;
csr.update_flag = 0;
hw->csr_cfg.val = csr.val;
}
/**
+17 -18
View File
@@ -195,20 +195,25 @@ The number of iterations (precision) is set via :cpp:member:`cordic_calculate_co
Quick Start
===========
This section shows how to create a CORDIC engine and run calculations. Typical usage flow:
This section shows how to acquire a CORDIC engine and run calculations. Typical usage flow:
Create CORDIC Engine
--------------------
Acquire CORDIC Engine
---------------------
Create a CORDIC engine as follows:
The chip has a single CORDIC hardware module, and every middleware that needs trigonometric or hyperbolic math has to share it. :cpp:func:`cordic_acquire_engine` is therefore not a plain constructor: the first caller initializes the hardware, and every later caller receives one more reference to the very same engine.
.. code:: c
cordic_engine_handle_t engine = NULL;
cordic_engine_config_t config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT, // Select clock source
.instance_id = 0, // Hardware instance, see CORDIC_LL_INST_NUM
};
ESP_ERROR_CHECK(cordic_new_engine(&config, &engine));
ESP_ERROR_CHECK(cordic_acquire_engine(&config, &engine));
.. note::
The clock source is decided by the first acquirer and stays in effect until the last reference is released. A later ``clock_source`` is ignored.
Run CORDIC Calculation
-----------------------
@@ -266,28 +271,22 @@ The driver provides conversion between fixed-point and floating-point via :cpp:f
Resource Cleanup
----------------
When the CORDIC engine is no longer needed, call :cpp:func:`cordic_delete_engine` to release the underlying hardware.
When a user no longer needs the CORDIC engine, call :cpp:func:`cordic_release_engine` to give its reference back. The hardware is only deinitialized once the last reference is released, so the order in which the users start or stop does not matter.
.. code:: c
ESP_ERROR_CHECK(cordic_delete_engine(engine));
ESP_ERROR_CHECK(cordic_release_engine(engine));
Advanced
========
Thread Safety
-------------
Sharing and Serialization
-------------------------
The following CORDIC APIs are thread-safe and can be called from different RTOS tasks without extra locking:
The CORDIC hardware is shared by everyone who acquires the engine, and only one calculation may occupy its registers at a time. The driver serializes access with a short critical section, so the caller does not have to handle contention:
Factory functions:
- :cpp:func:`cordic_new_engine`
- :cpp:func:`cordic_delete_engine`
.. note::
:cpp:func:`cordic_calculate_polling` is **not** thread-safe. Do not call it from multiple tasks without mutex protection.
- :cpp:func:`cordic_calculate_polling` waits until it can use the hardware, computes the batch, and then returns. Tasks, ISRs and both cores can call it without extra locking. Keep the number of points in one call small: interrupts on the calling core are masked for the duration of the batch.
- :cpp:func:`cordic_acquire_engine` and :cpp:func:`cordic_release_engine` are reference counted and protected by a mutex. They can be called from different RTOS tasks but may block, so they must not be called from an ISR. Each successful acquire owns one reference and must be given back exactly once.
Cache Safety
------------
+22 -23
View File
@@ -136,7 +136,7 @@ CORDIC 驱动 通过 :cpp:member:`cordic_calculate_config_t::iq_format` 参数
value = \sum_{i=0}^{30} \frac{1}{2^{(31-i)}} \times B_i
精度
----------------
----
CORDIC 驱动通过 :cpp:member:`cordic_calculate_config_t::iteration_count` 参数来设置计算的迭代周期(精度),其支持的精度如下所示。
@@ -193,25 +193,30 @@ CORDIC 驱动通过 :cpp:member:`cordic_calculate_config_t::iteration_count` 参
迭代周期越多,计算精度越高,但计算时间也会越长。当运算精度到达数据格式所能表达的精度极限时,继续增加迭代周期将不会产生更好的效果,反而会使时间变长。
快速入门
==========
========
本节将带你快速了解如何使用 CORDIC 驱动。通过实际的使用场景,展示如何创建 CORDIC 引擎并进行计算。一般的使用流程如下:
本节将带你快速了解如何使用 CORDIC 驱动。通过实际的使用场景,展示如何获取 CORDIC 引擎并进行计算。一般的使用流程如下:
创建 CORDIC 引擎
------------------
获取 CORDIC 引擎
----------------
首先,需要创建一个 CORDIC 引擎。以下代码展示了如何创建一个 CORDIC 引擎:
芯片上只有一个 CORDIC 硬件模块,所有需要三角函数或双曲函数运算的中间件都必须共享它。因此 :cpp:func:`cordic_acquire_engine` 并不是单纯的构造函数:第一个调用者初始化硬件,后续的调用者只是拿到同一个引擎的又一份引用。
.. code:: c
cordic_engine_handle_t engine = NULL;
cordic_engine_config_t config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT, // 选择时钟源
.instance_id = 0, // 硬件实例编号,取值范围见 CORDIC_LL_INST_NUM
};
ESP_ERROR_CHECK(cordic_new_engine(&config, &engine));
ESP_ERROR_CHECK(cordic_acquire_engine(&config, &engine));
.. note::
时钟源由第一个获取者决定,并一直生效到最后一份引用被释放。此后传入的 ``clock_source`` 会被忽略。
使用 CORDIC 引擎进行计算
----------------------------
------------------------
使用 CORDIC 引擎进行计算的流程如下:
@@ -248,7 +253,7 @@ CORDIC 驱动通过 :cpp:member:`cordic_calculate_config_t::iteration_count` 参
ESP_ERROR_CHECK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, buffer_depth));
格式转换
-----------
--------
CORDIC 驱动中提供了定点数与浮点数之间的转换函数,用户可以通过 :cpp:func:`cordic_convert_fixed_to_float` 和 :cpp:func:`cordic_convert_float_to_fixed` 函数实现定点数与浮点数之间的转换。
@@ -264,30 +269,24 @@ CORDIC 驱动中提供了定点数与浮点数之间的转换函数,用户可
uint32_t res2_fixed = cordic_convert_float_to_fixed(res2_float, ESP_CORDIC_FORMAT_Q15);
资源回收
---------
--------
当不再需要之前安装的 CORDIC 引擎,请调用 :cpp:func:`cordic_delete_engine` 来回收资源,以释放底层硬件。
当某个使用者不再需要 CORDIC 引擎时,调用 :cpp:func:`cordic_release_engine` 归还它的引用。只有在最后一份引用被归还后,硬件才会被反初始化,因此各使用者启动和退出的先后顺序并不重要。
.. code:: c
ESP_ERROR_CHECK(cordic_delete_engine(engine));
ESP_ERROR_CHECK(cordic_release_engine(engine));
高级功能
========
线程安全
---------
共享与串行化
------------
CORDIC 驱动程序的以下函数是线程安全的,可以从不同的 RTOS 任务调用,无需额外的锁保护:
CORDIC 硬件由所有获取了引擎的使用者共享,同一时刻只允许一个计算占用它的寄存器。驱动在计算期间进入短临界区,调用方无需处理冲突,也不用额外加锁:
工厂函数:
- :cpp:func:`cordic_new_engine`
- :cpp:func:`cordic_delete_engine`
.. note::
请注意 :cpp:func:`cordic_calculate_polling` 函数是非线程安全的,在没有设置互斥锁保护的任务中,应避免从多个任务中调用此函数。
- :cpp:func:`cordic_calculate_polling` 会等到可以使用硬件、算完整个批次后再返回。任务、ISR 以及双核都可以直接调用。请把一次调用的点数保持较小,因为整个批次期间调用核上的中断是关闭的。
- :cpp:func:`cordic_acquire_engine` 和 :cpp:func:`cordic_release_engine` 内部做了引用计数,并由互斥锁保护。它们可以从不同的 RTOS 任务调用,但可能会阻塞,因此不得从 ISR 中调用。每次成功 acquire 都持有一份引用,必须恰好归还一次。
Cache 安全
-----------