坐标旋转数字计算方法 (CORDIC)

[English]

概述

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。其对应关系如下表所示。

CORDIC 支持函数及公式

函数

输入

输出

可缩放指数 \(n\)

输入输出范围(缩放为1时)

ESP_CORDIC_FUNC_COS 余弦

\(\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)\)

ESP_CORDIC_FUNC_SIN 正弦

\(\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)\)

ESP_CORDIC_FUNC_PHASE 相位

\(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)\)

ESP_CORDIC_FUNC_MODULUS 模长

\(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)\)

ESP_CORDIC_FUNC_ARCTAN 反正切

\(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)\)

ESP_CORDIC_FUNC_COSH 双曲余弦

\(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]\)

ESP_CORDIC_FUNC_SINH 双曲正弦

\(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]\)

ESP_CORDIC_FUNC_ARCHTANH 反双曲正切

\(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]\)

ESP_CORDIC_FUNC_LOGE 自然对数

\(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]\)

ESP_CORDIC_FUNC_SQUARE_ROOT 平方根

\(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 参数来选择输入和输出数据的格式,其支持的格式如下所示。

在 Q15 格式下,数据由一个符号位加15位小数位组成,其范围为 [-1(0x8000), 1 - 2^-15(0x7FFF)]。

Q15 格式

Q15 格式

\[value = \sum_{i=0}^{14} \frac{1}{2^{(15-i)}} \times B_i\]

在 Q31 格式下,数据由一个符号位加31位小数位组成,其范围为 [-1(0x80000000), 1 - 2^-31(0x7FFFFFFF)]。

Q31 格式

Q31 格式

\[value = \sum_{i=0}^{30} \frac{1}{2^{(31-i)}} \times B_i\]

精度

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 驱动:

API 参考

CORDIC 驱动 API

Header File

  • components/esp_driver_cordic/include/driver/cordic.h

  • 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_cordic component. To declare that your component depends on esp_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_depth small: 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_IRAM to 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

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 HAL API

Header File

  • components/esp_hal_cordic/include/hal/cordic_types.h

  • 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_cordic component. To declare that your component depends on esp_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.

Public Members

uint32_t *p_data_arg1

Pointer to input data argument 1 buffer

uint32_t *p_data_arg2

Pointer to input data argument 2 buffer (can be NULL for single argument functions)

struct cordic_output_buffer_desc_t

Output buffer descriptor for CORDIC operations.

Public Members

uint32_t *p_data_res1

Pointer to output result 1 buffer

uint32_t *p_data_res2

Pointer to output result 2 buffer (can be NULL for single result)

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)

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)

CORDIC 类型

Header File

  • components/esp_driver_cordic/include/driver/cordic_types.h

  • 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_cordic component. To declare that your component depends on esp_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.


此文档对您有帮助吗?