坐标旋转数字计算方法 (CORDIC)
概述
CORDIC 是一种坐标旋转数字计算方法,用于计算三角函数、双曲函数和反正切函数等数学运算。CORDIC 算法通过迭代旋转和缩放操作,将复杂的数学运算转换为简单的加法和移位操作,从而实现高效的计算。
CORDIC 主要通过硬件进行计算,其特点主要包括:
计算速度快
计算精度高
计算资源消耗低
下表对比了使用 CORDIC 驱动和软件 iqmath 库计算 1 个随机值的余弦值的性能,单位 CPU 周期。
函数 |
iqmath |
cordic |
|---|---|---|
cos |
29640 |
8080 |
驱动介绍
在使用 CORDIC 驱动之前,需要先了解一些关于该驱动的计算方法及参数含义。
计算方法
CORDIC 驱动所计算的函数通过 cordic_calculate_config_t::function 参数进行选择,其支持的函数如下表所示。其中,有部分函数需要两个参数,有部分函数需要一个参数。有部分函数可以生成两个结果,有部分函数只能生成一个结果。
此外,CORDIC 驱动还可以通过设置 cordic_calculate_config_t::scale_exp 参数来设置输入数据的缩放因子(有效缩放为 \(2^n\)),在下面表格中用 \(n\) 表示该参数的值。
\(arg_{1}\) 和 \(arg_{2}\) 表示输入数据,分别对应 cordic_input_buffer_desc_t::p_data_arg1 和 cordic_input_buffer_desc_t::p_data_arg2 ,\(res_{1}\) 和 \(res_{2}\) 表示输出数据,分别对应 cordic_output_buffer_desc_t::p_data_res1 和 cordic_output_buffer_desc_t::p_data_res2。其对应关系如下表所示。
函数 |
输入 |
输出 |
可缩放指数 \(n\) |
输入输出范围(缩放为1时) |
|---|---|---|---|---|
\(\frac{\theta}{\pi}, \quad \theta \text{ in radians}\) |
\(res_1 = \cos(\theta)\), \(res_2 = \sin(\theta)\) |
0 |
\(arg_1, res_1, res_2 \in \left[-1, 1\right)\) |
|
\(\frac{\theta}{\pi}, \quad \theta \text{ in radians}\) |
\(res_1 = \sin(\theta)\),\(res_2 = \cos(\theta)\) |
0 |
\(arg_1, res_1, res_2 \in \left[-1, 1\right)\) |
|
\(arg_1 = x \cdot 2^{-n}\),\(arg_2 = y \cdot 2^{-n}\) |
\(res_1 = \arctan(y/x) \cdot 2^{-n}/\pi\),\(res_2 = \sqrt{x^2+y^2} \cdot 2^{-n}\) |
[0, 15] |
\(arg_{1,2} \in \left[-1, 1\right)\),\(res_1 \in \left[-1, 1\right)\),\(res_2 \in \left[0, 1\right)\) |
|
\(arg_1 = x \cdot 2^{-n}\),\(arg_2 = y \cdot 2^{-n}\) |
\(res_1 = \sqrt{x^2+y^2} \cdot 2^{-n}\),\(res_2 = \arctan(y/x) \cdot 2^{-n}/\pi\) |
[0, 15] |
\(arg_{1,2} \in \left[-1, 1\right)\),\(res_1 \in \left[0, 1\right)\),\(res_2 \in \left[-1, 1\right)\) |
|
\(arg_1 = x \cdot 2^{-n}\) |
\(res_1 = \arctan(x) \cdot 2^{-n}/\pi\) |
[0, 15] |
\(arg_1, res_1 \in \left[-1, 1\right)\) |
|
|
\(arg_1 = x \cdot 2^{-n}\) |
\(res_1 = \cosh(x) \cdot 2^{-n}\),\(res_2 = \sinh(x) \cdot 2^{-n}\) |
1 |
\(arg_1 \in \left[-0.559, 0.559\right]\),\(res_1 \in \left[0.5, 0.846\right]\),\(res_2 \in \left[-0.683, 0.683\right]\) |
|
\(arg_1 = x \cdot 2^{-n}\) |
\(res_1 = \sinh(x) \cdot 2^{-n}\),\(res_2 = \cosh(x) \cdot 2^{-n}\) |
1 |
\(arg_1 \in \left[-0.559, 0.559\right]\),\(res_1 \in \left[-0.683, 0.683\right]\),\(res_2 \in \left[0.5, 0.846\right]\) |
|
\(arg_1 = x \cdot 2^{-n}\) |
\(res_1 = \mathrm{arctanh}(x) \cdot 2^{-n}\) |
1 |
\(arg_1 \in \left[-0.403, 0.403\right]\),\(res_1 \in \left[-0.559, 0.559\right]\) |
|
\(arg_1 = (x-1)/(x+1) \cdot 2^{-1}\) |
\(res_1 = \ln(x) \cdot 2^{-2}\) |
1 |
\(arg_1 \in \left[-0.403, 0.403\right]\),\(res_1 \in \left[-0.559, 0.559\right]\) |
\(arg_1 = x \cdot 2^{-n}\) |
\(res_1 = \sqrt{x} \cdot 2^{-n}\) |
[0, 4] |
\(arg_1 \in \left[0.1069, 1\right)\),\(res_1 \in \left[0.177, 1\right)\) |
数据格式
CORDIC 驱动 通过 cordic_calculate_config_t::iq_format 参数来选择输入和输出数据的格式,其支持的格式如下所示。
ESP_CORDIC_FORMAT_Q15Q15 格式
在 Q15 格式下,数据由一个符号位加15位小数位组成,其范围为 [-1(0x8000), 1 - 2^-15(0x7FFF)]。
Q15 格式
ESP_CORDIC_FORMAT_Q31Q31 格式
在 Q31 格式下,数据由一个符号位加31位小数位组成,其范围为 [-1(0x80000000), 1 - 2^-31(0x7FFFFFFF)]。
Q31 格式
精度
CORDIC 驱动通过 cordic_calculate_config_t::iteration_count 设置迭代次数。合法取值为 4、8、12、16、20、24。开方只允许 4、8、12。其他取值会使本次计算返回 ESP_ERR_INVALID_ARG。各档位的残差如下所示。
Phase, Modulus, arctan 函数
函数 |
迭代次数 |
周期数 |
最大残差误差 (q1.31格式) |
最大残差误差 (q1.15格式) |
|---|---|---|---|---|
Phase, Modulus, arctan |
4 |
1 |
\(2^{-4}\) |
\(2^{-4}\) |
8 |
2 |
\(2^{-8}\) |
\(2^{-8}\) |
|
12 |
3 |
\(2^{-12}\) |
\(2^{-12}\) |
|
16 |
4 |
\(2^{-16}\) |
\(2^{-15}\) |
|
20 |
5 |
\(2^{-20}\) |
\(2^{-15}\) |
|
24 |
6 |
\(2^{-24}\) |
\(2^{-15}\) |
sin, cos, sinh, cosh, arctanh, ln 函数
函数 |
迭代次数 |
周期数 |
最大残差误差 (q1.31格式) |
最大残差误差 (q1.15格式) |
|---|---|---|---|---|
sin, cos, sinh, cosh, arctanh, ln |
4 |
1 |
\(2^{-3}\) |
\(2^{-3}\) |
8 |
2 |
\(2^{-7}\) |
\(2^{-7}\) |
|
12 |
3 |
\(2^{-11}\) |
\(2^{-11}\) |
|
16 |
4 |
\(2^{-14}\) |
\(2^{-14}\) |
|
20 |
5 |
\(2^{-18}\) |
\(2^{-15}\) |
|
24 |
6 |
\(2^{-22}\) |
\(2^{-15}\) |
Sqrt 函数
函数 |
迭代次数 |
周期数 |
最大残差误差 (q1.31格式) |
最大残差误差 (q1.15格式) |
|---|---|---|---|---|
Sqrt |
4 |
1 |
\(2^{-7}\) |
\(2^{-7}\) |
8 |
2 |
\(2^{-14}\) |
\(2^{-14}\) |
|
12 |
3 |
\(2^{-22}\) |
\(2^{-15}\) |
备注
迭代周期越多,计算精度越高,但计算时间也会越长。当运算精度到达数据格式所能表达的精度极限时,继续增加迭代周期将不会产生更好的效果,反而会使时间变长。
快速入门
本节将带你快速了解如何使用 CORDIC 驱动。通过实际的使用场景,展示如何获取 CORDIC 引擎并进行计算。一般的使用流程如下:
获取 CORDIC 引擎
芯片上只有一个 CORDIC 硬件模块,所有需要三角函数或双曲函数运算的中间件都必须共享它。因此 cordic_acquire_engine() 并不是单纯的构造函数:第一个调用者初始化硬件,后续的调用者只是拿到同一个引擎的又一份引用。
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_acquire_engine(&config, &engine));
备注
时钟源由第一个获取者决定,并一直生效到最后一份引用被释放。此后传入的 clock_source 会被忽略。
使用 CORDIC 引擎进行计算
使用 CORDIC 引擎进行计算的流程如下:
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_COS, // 选择计算的函数
.iq_format = ESP_CORDIC_FORMAT_Q15, // 选择输入和输出数据的格式
.iteration_count = 4, // 迭代次数:4、8、12、16、20 或 24
.scale_exp = 0, // 输入缩放指数 n,有效缩放为 2^n
};
size_t buffer_depth = 60;
uint32_t data_x[buffer_depth] = {}; // 输入数据1,长度为60
uint32_t data_y[buffer_depth] = {}; // 输入数据2,长度为60
uint32_t res1[buffer_depth] = {}; // 输出数据1,长度为60
uint32_t res2[buffer_depth] = {}; // 输出数据2,长度为60
// 配置输入数据
cordic_input_buffer_desc_t input_buffer = {
.p_data_arg1 = data_x, // 输入数据1
.p_data_arg2 = data_y, // 输入数据2
};
// 配置输出数据
cordic_output_buffer_desc_t output_buffer = {
.p_data_res1 = res1, // 输出数据1
.p_data_res2 = res2, // 输出数据2
};
// 开始计算
ESP_ERROR_CHECK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, buffer_depth));
格式转换
CORDIC 驱动中提供了定点数与浮点数之间的转换函数,用户可以通过 cordic_convert_fixed_to_float() 和 cordic_convert_float_to_fixed() 函数实现定点数与浮点数之间的转换。
// 将定点数转换为浮点数
// res1[0] res2[0] 为 cordic 输出的定点数
float res1_float = cordic_convert_fixed_to_float(res1[0], ESP_CORDIC_FORMAT_Q15);
float res2_float = cordic_convert_fixed_to_float(res2[0], ESP_CORDIC_FORMAT_Q15);
// 将浮点数转换为定点数
uint32_t res1_fixed = cordic_convert_float_to_fixed(res1_float, ESP_CORDIC_FORMAT_Q15);
uint32_t res2_fixed = cordic_convert_float_to_fixed(res2_float, ESP_CORDIC_FORMAT_Q15);
资源回收
当某个使用者不再需要 CORDIC 引擎时,调用 cordic_release_engine() 归还它的引用。只有在最后一份引用被归还后,硬件才会被反初始化,因此各使用者启动和退出的先后顺序并不重要。
ESP_ERROR_CHECK(cordic_release_engine(engine));
高级功能
共享与串行化
CORDIC 硬件由所有获取了引擎的使用者共享,同一时刻只允许一个计算占用它的寄存器。驱动在计算期间进入短临界区,调用方无需处理冲突,也不用额外加锁:
cordic_calculate_polling()会等到可以使用硬件、算完整个批次后再返回。任务、ISR 以及双核都可以直接调用。请把一次调用的点数保持较小,因为整个批次期间调用核上的中断是关闭的。cordic_acquire_engine()和cordic_release_engine()内部做了引用计数,并由互斥锁保护。它们可以从不同的 RTOS 任务调用,但可能会阻塞,因此不得从 ISR 中调用。每次成功 acquire 都持有一份引用,必须恰好归还一次。
Cache 安全
cordic_calculate_polling() 函数可以安全地在 ISR 中使用,但为了保证 ISR 在 cache 被禁用时也能正常运行,请启用 Kconfig 选项 CONFIG_CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM,将控制函数放入 IRAM 中。
Kconfig 选项
以下 Kconfig 选项可用于配置 CORDIC 驱动:
- 计算函数放入 IRAM。
API 参考
CORDIC 驱动 API
Header File
This header file can be included with:
#include "driver/cordic.h"
This header file is a part of the API provided by the
esp_driver_cordiccomponent. To declare that your component depends onesp_driver_cordic, add the following to your CMakeLists.txt:REQUIRES esp_driver_cordic
or
PRIV_REQUIRES esp_driver_cordic
Functions
-
esp_err_t cordic_acquire_engine(const cordic_engine_config_t *cordic_cfg, cordic_engine_handle_t *ret_engine)
Acquire a CORDIC engine instance.
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().
备注
This function takes a mutex and may block. Call it from a task, not from an ISR.
备注
Do not mix this function with the deprecated cordic_new_engine() / cordic_delete_engine().
备注
A later clock_source is ignored. The first acquirer decides the clock until the last release.
- 参数:
cordic_cfg -- [in] Pointer to CORDIC engine configuration structure. Must not be NULL.
ret_engine -- [out] Pointer to store the acquired CORDIC engine handle. Must not be NULL.
- 返回:
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_release_engine(cordic_engine_handle_t engine)
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.
备注
This function takes a mutex and may block. Call it from a task, not from an ISR.
- 参数:
engine -- [in] CORDIC engine handle acquired by cordic_acquire_engine(). Must not be NULL.
- 返回:
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_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)
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_depthsmall: interrupts on the calling core are masked for the duration of the batch.备注
The function can be safely used in ISR, but for the ISR run in cache-safe, please enable
CORDIC_ONESHOT_CTRL_FUNC_IN_IRAMto place the control functions into IRAM.备注
The engine handle must have been acquired with cordic_acquire_engine() and must not be released concurrently.
- 参数:
engine -- [in] CORDIC engine handle acquired by cordic_acquire_engine(). Must not be NULL.
calc_cfg -- [in] Pointer to CORDIC calculation configuration structure. Must not be NULL.
input_buffer_desc -- [in] Pointer to input buffer descriptor containing input data. Must not be NULL.
output_buffer_desc -- [out] Pointer to output buffer descriptor for storing results. Must not be NULL.
buffer_depth -- [in] Number of data points to process.
- 返回:
ESP_OK: All calculations completed successfully.
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.
-
float cordic_convert_fixed_to_float(uint32_t fixed_value, cordic_iq_format_t iq_format)
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.
- 参数:
fixed_value -- [in] Hexadecimal value from CORDIC hardware output.
iq_format -- [in] IQ data format.
- 返回:
Converted floating-point value. Returns 0.0f if iq_format is invalid.
-
uint32_t cordic_convert_float_to_fixed(float float_value, cordic_iq_format_t iq_format)
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.
- 参数:
float_value -- [in] Floating-point value to convert. Should be in the range [-1.0, 1.0].
iq_format -- [in] IQ data format.
- 返回:
Converted hexadecimal value. Returns 0 if iq_format is invalid.
Structures
-
struct cordic_engine_config_t
CORDIC engine configuration structure.
Public Members
-
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_clock_source_t clock_source
-
struct cordic_calculate_config_t
CORDIC calculation configuration structure.
Public Members
-
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
Iterations: 4, 8, 12, 16, 20 or 24. Square root allows only 4, 8 or 12
-
uint16_t scale_exp
Input scaling exponent n, effective scaling is 2^n (range depends on function type)
-
cordic_func_t function
CORDIC HAL API
Header File
This header file can be included with:
#include "hal/cordic_types.h"
This header file is a part of the API provided by the
esp_hal_cordiccomponent. To declare that your component depends onesp_hal_cordic, add the following to your CMakeLists.txt:REQUIRES esp_hal_cordic
or
PRIV_REQUIRES esp_hal_cordic
Structures
-
struct cordic_input_buffer_desc_t
Input buffer descriptor for CORDIC operations.
-
struct cordic_output_buffer_desc_t
Output buffer descriptor for CORDIC operations.
Type Definitions
-
typedef soc_periph_cordic_clk_src_t cordic_clock_source_t
CORDIC group clock source.
Enumerations
-
enum cordic_func_t
CORDIC calculation function type.
Values:
-
enumerator ESP_CORDIC_FUNC_COS
Cosine function (can have both cos, sin results)
-
enumerator ESP_CORDIC_FUNC_SIN
Sine function (can have both sin, cos results)
-
enumerator ESP_CORDIC_FUNC_PHASE
Phase function (requires two arguments) (can have both phase, modulus results)
-
enumerator ESP_CORDIC_FUNC_MODULUS
Modulus function (requires two arguments) (can have both phase, modulus results)
-
enumerator ESP_CORDIC_FUNC_ARCTAN
Arctangent function.
-
enumerator ESP_CORDIC_FUNC_COSH
Hyperbolic cosine function (can have both cosh, sinh results)
-
enumerator ESP_CORDIC_FUNC_SINH
Hyperbolic sine function (can have both sinh, cosh results)
-
enumerator ESP_CORDIC_FUNC_ARCHTANH
Hyperbolic arctangent function.
-
enumerator ESP_CORDIC_FUNC_LOGE
Natural logarithm function.
-
enumerator ESP_CORDIC_FUNC_SQUARE_ROOT
Square root function.
-
enumerator ESP_CORDIC_FUNC_MAX
Maximum function type (for boundary checking)
-
enumerator ESP_CORDIC_FUNC_COS
-
enum cordic_iq_format_t
CORDIC data format size.
Values:
-
enumerator ESP_CORDIC_FORMAT_Q15
Q15 format: 16-bit fixed-point (range: -1.0 to 1.0)
-
enumerator ESP_CORDIC_FORMAT_Q31
Q31 format: 32-bit fixed-point (range: -1.0 to 1.0)
-
enumerator ESP_CORDIC_IQ_SIZE_MAX
Maximum size type (for boundary checking)
-
enumerator ESP_CORDIC_FORMAT_Q15
CORDIC 类型
Header File
This header file can be included with:
#include "driver/cordic_types.h"
This header file is a part of the API provided by the
esp_driver_cordiccomponent. To declare that your component depends onesp_driver_cordic, add the following to your CMakeLists.txt:REQUIRES esp_driver_cordic
or
PRIV_REQUIRES esp_driver_cordic
Type Definitions
-
typedef struct cordic_engine_t *cordic_engine_handle_t
CORDIC engine handle type.