From 0a096a46aded19b885946199aecf4a6a5ab081fe Mon Sep 17 00:00:00 2001 From: "C.S.M" Date: Wed, 4 Mar 2026 15:36:23 +0800 Subject: [PATCH] feat(cordic): Add cordic oneshot driver for esp32s31 --- components/esp_driver_cordic/CMakeLists.txt | 24 +++ components/esp_driver_cordic/Kconfig | 10 + components/esp_driver_cordic/cordic.c | 185 ++++++++++++++++++ components/esp_driver_cordic/cordic_private.h | 47 +++++ .../esp_driver_cordic/include/driver/cordic.h | 115 +++++++++++ .../include/driver/cordic_types.h | 24 +++ components/esp_driver_cordic/linker.lf | 19 ++ 7 files changed, 424 insertions(+) create mode 100644 components/esp_driver_cordic/CMakeLists.txt create mode 100644 components/esp_driver_cordic/Kconfig create mode 100644 components/esp_driver_cordic/cordic.c create mode 100644 components/esp_driver_cordic/cordic_private.h create mode 100644 components/esp_driver_cordic/include/driver/cordic.h create mode 100644 components/esp_driver_cordic/include/driver/cordic_types.h create mode 100644 components/esp_driver_cordic/linker.lf diff --git a/components/esp_driver_cordic/CMakeLists.txt b/components/esp_driver_cordic/CMakeLists.txt new file mode 100644 index 0000000000..3d4cdf611f --- /dev/null +++ b/components/esp_driver_cordic/CMakeLists.txt @@ -0,0 +1,24 @@ +idf_build_get_property(target IDF_TARGET) + +set(srcs) +set(public_include "include") + +set(requires esp_hal_cordic) +if(${target} STREQUAL "linux") + set(priv_requires "") +else() + set(priv_requires esp_pm) +endif() + +if(CONFIG_SOC_CORDIC_SUPPORTED) + list(APPEND srcs + "cordic.c" + ) +endif() + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS ${public_include} + PRIV_REQUIRES "${priv_requires}" + REQUIRES "${requires}" + LDFRAGMENTS linker.lf + ) diff --git a/components/esp_driver_cordic/Kconfig b/components/esp_driver_cordic/Kconfig new file mode 100644 index 0000000000..363906e307 --- /dev/null +++ b/components/esp_driver_cordic/Kconfig @@ -0,0 +1,10 @@ +menu "ESP-Driver:Cordic Configurations" + depends on SOC_CORDIC_SUPPORTED + + config CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM + bool "Place cordic oneshot calculation function into IRAM" + default n + help + Place cordic oneshot function into IRAM so that cordic oneshot function + can be safely used in ISR even when cache is disabled. +endmenu diff --git a/components/esp_driver_cordic/cordic.c b/components/esp_driver_cordic/cordic.c new file mode 100644 index 0000000000..546cb39a19 --- /dev/null +++ b/components/esp_driver_cordic/cordic.c @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "esp_attr.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_err.h" +#include "cordic_private.h" +#include "driver/cordic_types.h" +#include "driver/cordic.h" +#include "hal/cordic_hal.h" +#include "hal/cordic_ll.h" +#include "soc/cordic_reg.h" +#include "soc/soc.h" +#include "hal/cordic_periph.h" + +ESP_LOG_ATTR_TAG(TAG, "cordic"); + +typedef struct cordic_platform_t { + _lock_t mutex; // platform level mutex lock. + cordic_engine_handle_t engine_handle; // array of cordic instances. +} cordic_platform_t; + +static cordic_platform_t s_cordic_platform = {}; // singleton platform + +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) +{ + 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"); + + 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_set_clock_source(clk_src); + *ret_engine = engine; + + return ESP_OK; + +err: + cordic_delete_engine(engine); + 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 + 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(); + } + + // 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); + + cordic_ll_start_calculate(engine->hal.dev); + + // Wait for result ready with timeout + 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; + } + } + // Use function pointer to get results + get_result_func(&engine->hal, output_buffer_desc, i); + } + + return ESP_OK; +} + +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; + _lock_release(&s_cordic_platform.mutex); + + heap_caps_free(engine); + + return ret; +} + +float cordic_convert_fixed_to_float(uint32_t fixed_value, cordic_iq_format_t iq_format) +{ + if (iq_format == ESP_CORDIC_FORMAT_Q15) { + return (float)(int16_t)(fixed_value & 0xFFFF) / CORDIC_Q15_SCALE_FACTOR; + } else if (iq_format == ESP_CORDIC_FORMAT_Q31) { + return (float)(int32_t)fixed_value / CORDIC_Q31_SCALE_FACTOR; + } else { + return 0.0f; + } +} + +uint32_t cordic_convert_float_to_fixed(float float_value, cordic_iq_format_t iq_format) +{ + if (iq_format == ESP_CORDIC_FORMAT_Q15) { + return (uint32_t)(int16_t)(float_value * CORDIC_Q15_SCALE_FACTOR) & 0xFFFF; + } else if (iq_format == ESP_CORDIC_FORMAT_Q31) { + return (uint32_t)(int32_t)(float_value * CORDIC_Q31_SCALE_FACTOR); + } else { + return 0; + } +} diff --git a/components/esp_driver_cordic/cordic_private.h b/components/esp_driver_cordic/cordic_private.h new file mode 100644 index 0000000000..99f4134f79 --- /dev/null +++ b/components/esp_driver_cordic/cordic_private.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "esp_attr.h" +#include "esp_heap_caps.h" +#include "hal/cordic_hal.h" +#include "esp_intr_types.h" +#include "sdkconfig.h" +#include "driver/cordic.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM +#define CORDIC_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#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 + +typedef struct cordic_engine_t cordic_engine_t; + +// Function pointer type for setting CORDIC arguments +typedef void (*cordic_set_argument_func_t)(cordic_hal_context_t *hal, const cordic_input_buffer_desc_t *input_buffer, size_t index); + +// Function pointer type for getting CORDIC results +typedef void (*cordic_get_result_func_t)(cordic_hal_context_t *hal, cordic_output_buffer_desc_t *output_buffer, size_t index); + +struct cordic_engine_t { + cordic_hal_context_t hal; // HAL context +}; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_cordic/include/driver/cordic.h b/components/esp_driver_cordic/include/driver/cordic.h new file mode 100644 index 0000000000..03682bc6b1 --- /dev/null +++ b/components/esp_driver_cordic/include/driver/cordic.h @@ -0,0 +1,115 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "driver/cordic_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief CORDIC engine configuration structure + */ +typedef struct { + cordic_clock_source_t clock_source; /*!< CORDIC clock source */ +} cordic_engine_config_t; + +/** + * @brief CORDIC calculation configuration structure + */ +typedef struct { + cordic_func_t function; /*!< CORDIC calculation function type (e.g., cosine, sine, arctan) */ + cordic_iq_format_t iq_format; /*!< IQ data format */ + uint16_t iteration_count; /*!< Internal CORDIC iteration count */ + uint16_t scale_exp; /*!< Input scaling exponent n, effective scaling is 2^n (range depends on function type) */ +} cordic_calculate_config_t; + +/** + * @brief Create a new 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. + * + * @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 invalid scale exponent). + * - 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); + +/** + * @brief Start one-shot CORDIC calculation + * + * 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. + * + * @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. + * + * @param[in] engine CORDIC engine handle created by cordic_new_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. + * @param[in] buffer_depth Number of data points to process. + * + * @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_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 + * + * This function converts a hexadecimal value from CORDIC hardware output to a floating-point number. + * The conversion depends on the IQ format specified by the iq_format parameter. + * + * @param[in] fixed_value Hexadecimal value from CORDIC hardware output. + * @param[in] iq_format IQ data format. + * + * @return Converted floating-point value. Returns 0.0f if iq_format is invalid. + */ +float cordic_convert_fixed_to_float(uint32_t fixed_value, cordic_iq_format_t iq_format); + +/** + * @brief Convert float to CORDIC hex value + * + * This function converts a floating-point number to a hexadecimal value suitable for CORDIC hardware input. + * The conversion depends on the IQ format specified by the iq_format parameter. + * + * @param[in] float_value Floating-point value to convert. Should be in the range [-1.0, 1.0]. + * @param[in] iq_format IQ data format. + * + * @return Converted hexadecimal value. Returns 0 if iq_format is invalid. + */ +uint32_t cordic_convert_float_to_fixed(float float_value, cordic_iq_format_t iq_format); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_cordic/include/driver/cordic_types.h b/components/esp_driver_cordic/include/driver/cordic_types.h new file mode 100644 index 0000000000..a75e9cd196 --- /dev/null +++ b/components/esp_driver_cordic/include/driver/cordic_types.h @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "soc/soc_caps.h" +#include "hal/cordic_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief CORDIC engine handle type + */ +typedef struct cordic_engine_t *cordic_engine_handle_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_cordic/linker.lf b/components/esp_driver_cordic/linker.lf new file mode 100644 index 0000000000..0655030326 --- /dev/null +++ b/components/esp_driver_cordic/linker.lf @@ -0,0 +1,19 @@ +[mapping:esp_driver_cordic] +archive: libesp_driver_cordic.a +entries: + if CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM = y: + cordic: cordic_calculate_polling (noflash) + +[mapping:esp_hal_cordic] +archive: libesp_hal_cordic.a +entries: + if CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM = y: + cordic_periph: cordic_hal_algorithm_allowable_scale (noflash) + cordic_hal: cordic_hal_set_argument_funcs (noflash) + cordic_hal: cordic_hal_get_result_funcs (noflash) + cordic_hal: cordic_hal_set_argument_q15_two_args (noflash) + cordic_hal: cordic_hal_set_argument_q15_one_arg (noflash) + cordic_hal: cordic_hal_set_argument_q31_two_args (noflash) + cordic_hal: cordic_hal_set_argument_q31_one_arg (noflash) + cordic_hal: cordic_hal_get_result_q15 (noflash) + cordic_hal: cordic_hal_get_result_q31 (noflash)