☰
GD32 定点数查表法计算 sin:比 sinf 快 14 倍,附 C 代码与误差分析
2026/10/13 3:04:29 网站建设 项目流程

当我在GD32F303上运行了65536次float sinf(float)和int16_t fast_sin(uint16_t)的对比测试后,看到串口打印出来的数据,我愣住了——没有FPU的时候,fast_sin()比sinf()快了14倍;就算打开了FPU,fast_sin()依然领先超3倍。

01 一切从“为什么关了FPU,sin()耗时不变”开始

事情是这样的:我在GD32F303VGT6上做算法优化实验,像往常一样打开了Keil的FPU选项,测了一下sin函数的执行时间。然后顺手关掉FPU,又测了一次。

结果几乎一样。

当时我就懵了——FPU开关竟然对sin()没有影响?

02 你猜怎么着?我踩了一个经典的坑

排查了一圈,发现原因其实很简单:Cortex-M4的FPU只支持单精度(float),而sin()函数处理的是双精度(double)。

双精度浮点运算在Cortex-M4上只能用软件模拟,跟FPU开关没有任何关系。

正确的做法是使用sinf(),也就是单精度版本的sin()函数。

在Cortex-M系列上,所有带‘f’后缀的数学函数(sinf()、sqrtf()、powf()等)才是真正能调用FPU硬件加速的版本。

03 浮点乘法验证:确认FPU真的在工作

为了确认FPU配置没有问题,先跑一个简单的浮点乘法测试:

测试结果(DWT周期计数,主频120MHz):

测试代码

DWT周期计数

耗时

FPU状态

float a = 1.1f;

for (uint32_t i = 0; i < 100000; i++) {

a = a * 1.1f;}

1200011

10.0ms

开启

8557420

71.3ms

关闭

速度提升了超7倍——硬件FPU工作正常。

04 真正的主角:定点数查表算法

既然FPU对单精度运算有加速效果,那用定点数(纯整数运算)实现的sin()函数,在FPU开启和关闭时表现如何?

我写了一个基于Q15格式的查表+线性插值算法,表格只有129个点(0°~90°,步长0.703125°),代码完全不用浮点数:

// Q15格式:-1~1映射到 -32768~32767

const int16_t sin_table[129] = {

0x0000,0x0192,0x0324,0x04B6,0x0648,0x07D9,0x096A,0x0AFB,

0x0C8C,0x0E1C,0x0FAB,0x113A,0x12C8,0x1455,0x15E2,0x176E,

// ... 省略中间数据,完整数组见Gitee

0x7FFF

};

// 输入:0~65535 对应 0°~360°,输出:Q15格式正弦值

int16_t fast_sin(uint16_t angle_q16) {

uint16_t quadrant = (angle_q16 >> 14) & 0x03;

uint16_t angle_90 = angle_q16 & 0x3FFF;

uint16_t offset = 0;

if (quadrant &0x01) {

angle_90 = 0x3FFF - angle_90;

offset = 1;

}

uint16_t index = angle_90 >>7; // 查表索引 0~90

int32_t result = (int32_t)sin_table[index + 1] - sin_table[index];

result *= (angle_90 & 0x7F)+offset;

result += 64;

result >>= 7;

result += (int32_t)sin_table[index];

if (quadrant &0x02) return -(int16_t)result;

return (int16_t)result;

}

05 巅峰对决:sinf() vs fast_sin()

测试结果(主频120MHz,DWT周期计数):

测试函数

开启FPU

关闭FPU

DWT周期计数

耗时

DWT周期计数

耗时

sinf()*65536

11536330

96.1ms

53311568

444.3ms

fast_sin()*65536

3768329

31.4ms

3768329

31.4ms

两个关键结论:

1. fast_sin()完全不受FPU开关影响——因为内部全是整数运算,根本没有浮点指令。

2. fast_sin()比FPU开启后的sinf()还快超3倍——算得准不如算得巧。

速度提升汇总:

sinf()关闭FPU vs 开启FPU:近5倍加速(硬件FPU的功劳)

开启FPU sinf() vs fast_sin():fast_sin()快超3倍(算法的胜利)

关闭FPU sinf() vs fast_sin():fast_sin()快超14倍(极端场景下的降维打击)

06 精度够用吗?

速度是快了,但精度如何?毕竟工业控制不是做科研,够用就行。

我在0°~360°范围内按0.1°步长测试了3600个值,将fast_sin()的输出与sinf()的标准值对比:

精度指标

最大绝对误差

平均绝对误差

均方根误差

数值

0.000064

0.000020

0.000025

0.000064的最大绝对误差,相对于满量程1.0,相当于 64ppm 的误差。对于PID控制、电机FOC、姿态解算等场景完全够用。

07 示波器实测:波形对比

为了更直观地对比,我用DAC输出两路正弦波:

通道1(黄色):sinf()生成的波形

通道2(绿色):fast_sin()生成的波形

两路波形几乎完全重叠,肉眼无法分辨差异

图4 示波器波形对比图

08 算法原理:Q15格式 + 查表插值

为什么用Q15?

浮点数运算慢,在Cortex-M4上虽然可以用FPU加速,但在M0/M3等不带FPU的芯片上就是灾难。Q15格式用整数表示小数,所有运算都用整数ALU完成,速度极快。

Q15的数值范围:-32768 ~ 32767 对应 -1.0 ~0.99997

为什么只用129个表?

正弦函数在0°~360°范围内具有高度对称性:

[0°~90°) → 直接查表

[90°~180°) → sin(180° - θ)

[180°~270°) → -sin(θ - 180°)

[270°~360°) → -sin(360° - θ)

所以只需要存0°~90°的129个点,配合象限映射就能算出所有角度。

插值提升精度

步长0.703125°的查表精度显然不够,所以在两个相邻表值之间做**线性插值**:

f(x) = y₀+ (y₁- y₀) × (x - x₀) / (x₁- x₀)

所有运算都是整数乘加,一个周期内完成。

09 源码与工程

完整的Keil工程已上传Gitee,包含:

GD32F303VGT6的完整工程

fast_sin完整源码

性能测试代码(使用DWT周期计数)

精度测试代码

Gitee地址:[https://gitee.com/jervis_luo/gd32_fast_sin.git]

10 总结

对比维度

sinf()(关闭FPU)

sinf()(开启FPU)

Fast_sin()(定点数)

65536次计算耗时

444.3ms

96.1ms

31.4ms

最大误差

~1e-7

~1e-7

0.000064

是否需要FPU

否

是

否

使用芯片

M0/M3/M4/M7全系列

Cortex-M4/M7

M0/M3/M4/M7全系列

三点核心收获:

1. 算法选型比硬件堆砌更重要——好的算法可以在廉价芯片上跑出旗舰性能。

2. 定点数是嵌入式工程师的核心竞争力——理解Q格式、查表、插值,就能在资源受限的场景下游刃有余。

3. 性能测试要用对方法——DWT周期计数比示波器更精确,sinf()比sin()更适合嵌入式。

这组数据告诉我们:在嵌入式世界,聪明的算法往往比昂贵的硬件更管用。希望这篇文章能帮你重新思考“性能优化”这件事。

附录:文中图表说明

序号

图表内容

图表说明

图1

FPU开/关浮点计算耗时对比柱状图

10.0ms vs 71.3ms,7.1倍差距

图2

sinf() vs fast_sin()耗时对比柱状图

96.1ms vs 444.3ms vs 31.4ms

图3

误差分布散点图

误差-角度,误差在±0.000064以内

图4

示波器波形对比图

sinf()(黄色)和fast_sin()(绿色)重叠

后记:写完这篇文章后,我顺手把fast_sin()用在了自己做的两轮平衡小车上。原本用sinf()时,角度环的更新频率只能跑到2kHz(CPU被浮点运算拖累);换上fast_sin()后,直接飙到5kHz。小车稳得像钉在地上。这大概就是优化的乐趣所在。

如果你对定点数学库感兴趣,欢迎关注我,下一期我们来聊聊fast_atan2()——这个在电机FOC控制里比sin()更常用的函数。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询