CORDIC算法:不用浮点也能算三角函数?🤯

你有没有遇到过这种情况——在一块没有FPU的STM32F103上,想做个电机控制,结果发现 sin() cos() 一调用就卡住?😱
或者在低功耗MCU上跑音频合成,却发现 math.h 里的三角函数太慢、太耗电?

别慌,今天咱不靠查表、不靠泰勒展开、更不依赖浮点运算—— 用纯整数运算搞定高精度三角函数计算 。秘诀就是: CORDIC算法 !✨

这玩意儿早在1959年就被Jack Volder搞出来了,当年是为了给航空导航系统替代笨重的模拟计算机。而现在?它正悄悄藏在你的电机控制器、蓝牙耳机、甚至智能手表里,默默干活还不占资源。


🌀 它是怎么“转”出sin和cos的?

想象一下:你手里有个向量,起始指向(1, 0),也就是x轴正方向。现在你想把它旋转一个角度θ,终点坐标是不是就是(cosθ, sinθ)?对吧!

但问题来了: 我们还没算出cos/sin呢,怎么旋转?

CORDIC的天才之处就在于—— 它不用直接旋转,而是“凑”出来这个角度

方法是这样的:

  • 把目标角度拆成一系列“特殊小角”的和或差;
  • 每个小角都是 $\arctan(2^{-i})$,比如45°、26.6°、14.0°……越往后越小;
  • 每次只左转或右转这么一个小角;
  • 而且每次旋转都可以用 加减法 + 右移 完成!

是的,你没听错—— 不需要乘法器,也不需要浮点单元 ,只需要几个移位和加减操作,就能一步步逼近你要的角度🎯。

数学公式长这样:

$$
\begin{aligned}
x_{i+1} &= x_i - d_i \cdot y_i \cdot 2^{-i} \
y_{i+1} &= y_i + d_i \cdot x_i \cdot 2^{-i} \
z_{i+1} &= z_i - d_i \cdot \gamma_i
\end{aligned}
$$

其中:
- $ (x_i, y_i) $ 是当前坐标;
- $ z_i $ 是还剩多少角度没转完;
- $ d_i = \text{sign}(z_i) $,决定这次往哪边转;
- $ \gamma_i = \arctan(2^{-i}) $,预存的小角度表。

⚠️ 小贴士:每转一次都会让向量变短一点(因为旋转矩阵有缩放),总增益会收敛到约 0.60725 。所以最后得乘个 1.64676 补回来——这个也可以提前处理掉,避免运行时乘法!


💡 为什么说它特别适合嵌入式?

来,咱们列个“硬核优势清单”👇:

特性 对嵌入式的意义
✅ 仅需加减和位移 8位单片机都能扛得住
✅ 无浮点依赖 STM32F1、MSP430、nRF系列通吃
✅ 查表极小 14个值就够,RAM压力几乎为零
✅ 精度可调 多迭代几次,精度蹭蹭涨
✅ 一套代码多用途 sin/cos/atan/sqrt全拿下

换句话说: 你在资源受限的芯片上能想到的数学需求,CORDIC基本都能满足 。而且还是以一种极其优雅的方式。


🔧 上代码!纯定点实现sincos

下面这段C代码, 全程不用float/double ,专为无FPU环境设计,拿过去就能跑📌

#include <stdint.h>
#include <stdio.h>

// Q15格式:1位符号 + 15位小数,表示[-1, 1)
#define Q15_SCALE   32768
#define FLOAT_TO_Q15(f) ((int16_t)((f) * Q15_SCALE))
#define Q15_TO_FLOAT(q) ((float)(q) / Q15_SCALE)

// 预计算 arctan(2^-i),单位弧度,转成Q15存储
const int16_t cordic_atan_table[] = {
    FLOAT_TO_Q15(0.785398163397),  // i=0: ~45.0°
    FLOAT_TO_Q15(0.463647609001),  // i=1: ~26.6°
    FLOAT_TO_Q15(0.244978663127),  // i=2: ~14.0°
    FLOAT_TO_Q15(0.124354994547),  // i=3: ~7.1°
    FLOAT_TO_Q15(0.062418809996),  // i=4: ~3.6°
    FLOAT_TO_Q15(0.031239833430),  // i=5: ~1.8°
    FLOAT_TO_Q15(0.015623728620),  // i=6: ~0.9°
    FLOAT_TO_Q15(0.007812341060),  // i=7: ~0.45°
    FLOAT_TO_Q15(0.003906230130),  // i=8: ~0.22°
    FLOAT_TO_Q15(0.001953122736),  // i=9: ~0.11°
    FLOAT_TO_Q15(0.000976562190),  // i=10: ~0.056°
    FLOAT_TO_Q15(0.000488281211),  // i=11: ~0.028°
    FLOAT_TO_Q15(0.000244140620),  // i=12: ~0.014°
    FLOAT_TO_Q15(0.000122070312),  // i=13: ~0.007°
};

#define CORDIC_ITERATIONS 14
#define CORDIC_GAIN_CORRECTION_Q15 FLOAT_TO_Q15(1.64676)  // 1/K ≈ 1.64676

void cordic_sincos(int16_t theta_q15, int16_t *sin_q15, int16_t *cos_q15) {
    int16_t x = FLOAT_TO_Q15(0.607252935);  // 初始x = K * 1.0(预缩放)
    int16_t y = 0;                           // 初始y = 0
    int16_t z = theta_q15;                   // 当前剩余角度

    for (int i = 0; i < CORDIC_ITERATIONS; i++) {
        int16_t d = (z >= 0) ? 1 : -1;       // 决定旋转方向
        int16_t tx, ty;

        // 实现 * 2^-i → 用右移
        tx = (i == 0) ? x : (x >> i);
        ty = (i == 0) ? y : (y >> i);

        // 坐标更新
        x = x - d * ty;
        y = y + d * tx;

        // 角度余量更新
        z = z - d * cordic_atan_table[i];
    }

    *cos_q15 = x;
    *sin_q15 = y;
}
📌 关键细节说明:
  • Q15定点 :用 int16_t 表示小数,范围够用又高效;
  • 预缩放x :初始就把x设为K×1.0,省去最后一步乘法;
  • 移位代替乘法 >> i 就是 × $2^{-i}$,速度飞起⚡;
  • 符号判断控制方向 d = sign(z) 自动选择左旋还是右旋;
  • 输出仍为定点 :后续若需浮点,可用宏转换。

✅ 在STM32F1上实测:14次迭代,主频72MHz,执行时间约 150μs ——比调用 arm_sin_f32() 还快(如果没FPU的话)!


🛠 实际应用场景大揭秘

🔄 场景一:FOC电机控制中的实时坐标变换

在永磁同步电机(PMSM)的磁场定向控制(FOC)中,有一个关键步骤叫 Clarke → Park 变换

$$
\begin{bmatrix}
I_d \ I_q
\end{bmatrix}
=
\begin{bmatrix}
\cos\theta & \sin\theta \
-\sin\theta & \cos\theta
\end{bmatrix}
\begin{bmatrix}
I_\alpha \ I_\beta
\end{bmatrix}
$$

这里的 $\cos\theta$ 和 $\sin\theta$ 必须每一控制周期都重新计算(通常是10kHz以上!)。如果每次都调库函数?CPU直接趴下😵。

而用CORDIC呢? 固定迭代次数 + 极低延迟 + 确定性执行时间 ,完美匹配实时控制需求!

🔊 场景二:音频合成器生成纯净正弦波

想在8位AVR上做DDS信号发生器?CORDIC可以直接作为 相位到幅度的映射引擎 ,逐点生成高精度正弦样本,信噪比远超查表插值法。

🤖 场景三:机器人逆运动学解算

机械臂关节角度计算常涉及大量三角运算。用CORDIC可在无操作系统的小MCU上实现轻量级路径规划,响应更快、功耗更低。


⚖ 工程实践中要注意啥?

别以为“理论上可行”就万事大吉,实战中还有几个坑要避开👇

1. 迭代次数怎么选?
迭代次数 精度(bit) 典型用途
10 ~8-bit LED呼吸灯、简单PWM
14 ~12-bit 匹配12位ADC/PWM
16~20 ~16-bit 高端音频、精密控制

建议:先从14次开始试,看误差是否满足系统需求。

2. 定点格式选哪个?
  • 16位系统 :Q15 足够;
  • 32位系统(如STM32) :推荐Q30,动态范围更大,累积误差更小;
  • 注意溢出!尤其是中间变量。
3. 输入角度范围限制

原始CORDIC只能处理 $[-\pi/2, \pi/2]$ 范围内的角度。超出怎么办?

👉 象限映射法 来救场!

利用三角函数的周期性和对称性:
- 先模$2\pi$
- 再判断象限
- 转为第一象限等效角计算
- 最后根据象限修正符号

轻松扩展到全范围输入🌍

4. 性能优化技巧
  • 循环展开 :减少跳转开销,编译器可能自动做,也可手动展开;
  • FPGA实现 :做成流水线结构,每个时钟输出一个结果;
  • 固定角度预计算 :如果是周期性任务(如FOC),可以把常用角度的结果缓存起来,降为“查表+微调”。

🧩 更进一步:CORDIC还能干啥?

你以为它只能算sin/cos?Too young too simple 😏

CORDIC其实是个“万能计算器”,只要换种模式,就能解锁新技能:

功能 模式 应用场景
计算 atan(y/x) 向量模式(Vectoring Mode) 角度提取、相位检测
计算 sqrt(x²+y²) 同上 幅值计算、RSSI
双曲函数 sinh/cosh 双曲CORDIC 某些滤波器设计
指数/对数 改进版 数值压缩、dB转换

是不是感觉打开了新世界的大门🚪?


🎯 结语:算法才是真正的“硬件加速器”

在这个追求低功耗、低成本、国产替代的时代, 不是所有问题都要靠更强的芯片解决

有时候,换个思路,用一个聪明的算法,就能让一颗“老古董”MCU焕发第二春🌿。

CORDIC正是这样一个例子:
它不炫技,不烧钱,却能在最朴素的硬件上,稳定、高效、优雅地完成复杂计算。

下次当你面对“没FPU咋办”的困境时,不妨试试这个几十年前的老兵——
它可能比你想象的更强大,也更可靠 。💪

“最好的性能优化,往往不在芯片厂,而在工程师的脑子里。” 🧠💡


🎯 小挑战留给你 :你能试着把上面的代码改成支持Q30格式的版本吗?或者加上全象限输入处理?欢迎留言讨论~ 👇💬

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐