跳转至

fft - 快速傅里叶变换(FFT/IFFT),支持 float32 与 q15 定点内核

孟伟

一、概述

fft 模块提供高性能的快速傅里叶变换(FFT)和逆快速傅里叶变换(IFFT)功能,支持 float32 和 q15 定点两种内核,适用于信号处理、频谱分析等场景。

二、核心示例

1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;

2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;

3、更加完整和详细的 demo,请参考 此链接 中的 demo/fft

-- LuaTools 需要 PROJECT 和 VERSION_
_PROJECT = "fft_q15_test"_
_VERSION = "001.000.000"_
_
function fft_test()
    if not fft then_
_        while 1 do
            sys.wait(1000)
            log.info("bsp", "此BSP不支持fft库,请检查")
        end_
_    end_

_    -- FFT 参数配置_
_    local N = 2048      -- FFT 点数_
_    local fs = 2000     -- 采样频率 (Hz)_
_    local freq = 200    -- 测试信号频率 (Hz)_

_    log.info("fft", "q15 测试开始", "N=" .. N, "fs=" .. fs, "freq=" .. freq)_

_    -- 分配 zbuff:_
_    -- 整数缓冲(U12 以 16bit 承载,低12位有效)用于 q15 内核_
_    local real_i16 = zbuff.create(N * 2)_
_    local imag_i16 = zbuff.create(N * 2)_

_    -- 生成 U12 整数正弦写入 i16(避免浮点预处理干扰)_
_    for i = 0, N - 1 do_
_        local t = i / fs_
_        local x = math.sin(2 * math.pi * freq * t)_
_        local val = math.floor(2048 + 2047 * x + 0.5)_
_        if val < 0 then val = 0 end_
_        if val > 4095 then val = 4095 end_
_        real_i16:seek(i * 2, zbuff.SEEK_SET); real_i16:writeU16(val)_
_        imag_i16:seek(i * 2, zbuff.SEEK_SET); imag_i16:writeU16(0)_
_    end_

_    -- 生成 Q15 旋转因子到 zbuff(避免任何浮点旋转因子)_
_    local Wc_q15 = zbuff.create((N // 2) * 2)_
_    local Ws_q15 = zbuff.create((N // 2) * 2)_
_    fft.generate_twiddles_q15_to_zbuff(N, Wc_q15, Ws_q15)_

_    -- 执行 Q15 FFT(输入为 U12),显式传入 Q15 twiddle_
_    local t0 = mcu.ticks()_
_    fft.run(real_i16, imag_i16, N, Wc_q15, Ws_q15, { core = "q15", input_format = "u12" })_
_    local t1 = mcu.ticks()_
_    log.info("fft", "q15 FFT 完成", "耗时:" .. (t1 - t0) .. "ms")_

_    -- 扫描前半谱,寻找主峰_
_    local peak_k, peak_pow = 1, -1_
_    for k = 1, (N // 2) - 1 do_
_        real_i16:seek(k * 2, zbuff.SEEK_SET)_
_        imag_i16:seek(k * 2, zbuff.SEEK_SET)_
_        local rr = real_i16:readI16()_
_        local ii = imag_i16:readI16()_
_        local p = rr * rr + ii * ii_
_        if p > peak_pow then_
_            peak_pow = p_
_            peak_k = k_
_        end_
_    end_
_    local peak_freq = (peak_k) * fs / N_
_    log.info("fft", "主峰(Hz/bin)", string.format("%.2f", peak_freq), peak_k)_

_    -- 比较:使用 f32 内核处理相同输入(验证 q15 与 f32 结果一致性)_
_    -- 复制相同的 U12 输入到新的 zbuff_
_    local real_f32 = zbuff.create(N * 4)  -- f32 需要 4 字节_
_    local imag_f32 = zbuff.create(N * 4)_
_    for i = 0, N - 1 do_
_        real_i16:seek(i * 2, zbuff.SEEK_SET)_
_        local val = real_i16:readU16()_
_        real_f32:seek(i * 4, zbuff.SEEK_SET)_
_        imag_f32:seek(i * 4, zbuff.SEEK_SET)_
_        real_f32:writeF32(val)  -- 直接写入 U12 原始值_
_        imag_f32:writeF32(0.0)_
_    end_

_    -- 生成 f32 旋转因子_
_    local Wc, Ws = fft.generate_twiddles(N)_

_    -- 执行 f32 FFT(输入为 U12)_
_    local t2 = mcu.ticks()_
_    fft.run(real_f32, imag_f32, N, Wc, Ws, { input_format = "u12" })_
_    local t3 = mcu.ticks()_
_    local dt_f32 = (t3 - t2)_
_    log.info("fft", "f32 FFT 完成", "耗时:" .. dt_f32 .. "ms")_

_    -- 总结耗时对比_
_    log.info("fft", "对比(q15 vs f32, ms)", string.format("%d / %d", (t1 - t0), dt_f32))_

_    -- 注意:本测试刻意避免浮点旋转因子与f32路径,确保纯Q15链路
end_
_sys.taskInit(fft_test)_

_sys.run()

三、常量详解

核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;

每个常量对应的常量取值仅做日志打印时查询使用,不要将这个常量取值用做具体的业务逻辑判断,因为LuatOS内核固件可能会变更每个常量对应的常量取值;

如果用做具体的业务逻辑判断,一旦常量取值发生改变,业务逻辑就会出错;

fft 模块没有常量。

四、函数详解

fft.generate_twiddles(N)

功能

生成 float32 旋转因子表。

参数

N

参数含义:FFT 点数;
数据类型:number
取值范围:必须为 2 的幂次方,且 N < 65536
是否必选:必须传入此参数;
注意事项:推荐范围为 N  16384(已验证稳定运行);

返回值

local Wc, Ws = fft.generate_twiddles(N)

Wc

含义说明:cos 旋转因子表;
数据类型:table
取值范围:长度为 N/2  Lua 数组;
注意事项:返回的是 float32 精度的旋转因子;

Ws

含义说明:-sin 旋转因子表;
数据类型:table
取值范围:长度为 N/2  Lua 数组;
注意事项:返回的是 float32 精度的旋转因子;

示例

local N = 2048
local Wc, Ws = fft.generate_twiddles(N)

fft.generate_twiddles_q15_to_zbuff(N, Wc_zb, Ws_zb)

功能

生成 q15 定点旋转因子到 zbuff(零浮点)。

参数

N

参数含义:FFT 点数;
数据类型:number
取值范围:必须为 2 的幂次方,且 N < 65536
是否必选:必须传入此参数;

Wc_zb

参数含义:输出缓冲(cos旋转因子),用于存放 int16 Q15 格式的 cos 旋转因子;
数据类型:zbuff
取值范围:长度至少为 (N/2)*2 字节;
是否必选:必须传入此参数;

Ws_zb

参数含义:输出缓冲(-sin旋转因子),用于存放 int16 Q15 格式的 -sin 旋转因子(前向FFT用);
数据类型:zbuff
取值范围:长度至少为 (N/2)*2 字节;
是否必选:必须传入此参数;

返回值

无返回值

示例

local N = 2048
local Wc_q15 = zbuff.create((N//2)*2)
local Ws_q15 = zbuff.create((N//2)*2)
fft.generate_twiddles_q15_to_zbuff(N, Wc_q15, Ws_q15)

fft.run(real, imag, N, Wc, Ws[, opts])

功能

原地 FFT 计算。

参数

real

参数含义:实部容器;
数据类型:table  zbuff
取值范围:float32 路径:Lua 数组或 zbuff(float32)
         q15 路径:zbuff(int16)。当 opts.core="q15"  opts.input_format  "u12"/"u16"/"s16" 时生效;
是否必选:必须传入此参数;

imag

参数含义:虚部容器;
数据类型:table  zbuff
取值范围:float32 路径:Lua 数组或 zbuff(float32)
         q15 路径:zbuff(int16)。当 opts.core="q15"  opts.input_format  "u12"/"u16"/"s16" 时生效;
是否必选:可选;

N

参数含义:FFT 点数;
数据类型:number
取值范围:必须为 2 的幂次方;
是否必选:必须传入此参数;

Wc

参数含义:旋转因子 cos
数据类型:table  zbuff
取值范围:float32 路径:Lua 数组或 zbuff(float32)
         q15 路径:zbuff(int16),推荐配合 fft.generate_twiddles_q15_to_zbuff 生成;
是否必选:必须传入此参数;

Ws

参数含义:旋转因子 -sin
数据类型:table  zbuff
取值范围:float32 路径:Lua 数组或 zbuff(float32)
         q15 路径:zbuff(int16),推荐配合 fft.generate_twiddles_q15_to_zbuff 生成;
是否必选:必须传入此参数;

opts

参数含义:可选配置表;
数据类型:table
是否必选:可选;
参数格式:{
         _-- 参数含义:核心计算路径;_
         _-- 数据类型:string;_
         _-- 是否必选:可选;_
         _-- 取值范围:"f32"(默认)、"q15";_
                     --_f32浮点内核,精度高(32位),计算稳定,适合精密分析;_
                     --_q15定点内核,速度快(16位整数),内存省,适合实时处理但精度略低;_
         _-- 参数示例:"f32"_
         core = ,

         _-- 参数含义:输入数据格式;_
         _-- 数据类型:string;_
         _-- 是否必选:q15 路径时必填;_
         _-- 取值范围:"f32"、"u12"、"u16"、"s16";f32 路径时无需配置;_
         _-- 参数示例:"f32"_
         _-- 注意事项:--f32: 标准浮点输入,适用于已处理的信号数据;_
                     _--u12: 12位无符号整数(0~4095),常见于ADC采样,自动去直流分量;_
                     _--u16: 16位无符号整数(0~65535),适用于高精度ADC或预处理数据;_
                     _--s16: 16位有符号整数(-32768~32767),适用于已去直流的差分信号;_
         input_format = ,
         }

返回值

无返回值

示例

_-- f32 路径示例(zbuff float32)_
local N=2048
local real=zbuff.create(N*4);
local imag=zbuff.create(N*4)
local Wc,Ws=fft.generate_twiddles(N)
fft.run(real, imag, N, Wc, Ws)

_-- q15 路径示例(U12 整数输入)_
local N=2048
local real_i16=zbuff.create(N*2);
local imag_i16=zbuff.create(N*2)
local Wc_q15=zbuff.create((N//2)*2);
local Ws_q15=zbuff.create((N//2)*2)
fft.generate_twiddles_q15_to_zbuff(N, Wc_q15, Ws_q15)
_-- 写入 U12 数据到 real_i16 后:_
fft.run(real_i16, imag_i16, N, Wc_q15, Ws_q15, {core="q15", input_format="u12"})

fft.ifft(real, imag, N, Wc, Ws[, opts])

功能

原地 IFFT 计算。

注意事项

就地修改 real/imag,并在 f32 路径下包含 1/N 归一化

参数

real

参数含义:实部容器;
数据类型:table  zbuff
取值范围:float32 路径:Lua 数组或 zbuff(float32)
         q15 路径:zbuff(int16)。当 opts.core="q15"  opts.input_format  "u12"/"u16"/"s16" 时生效;
是否必选:必须传入此参数;

imag

参数含义:虚部容器;
数据类型:table  zbuff
取值范围:float32 路径:Lua 数组或 zbuff(float32)
         q15 路径:zbuff(int16)。当 opts.core="q15"  opts.input_format  "u12"/"u16"/"s16" 时生效;
是否必选:可选;

N

参数含义:FFT 点数;
数据类型:number
取值范围:必须为 2 的幂次方;
是否必选:必须传入此参数;

Wc

参数含义:旋转因子 cos
数据类型:table  zbuff
取值范围:float32 路径:Lua 数组或 zbuff(float32)
         q15 路径:zbuff(int16),推荐配合 fft.generate_twiddles_q15_to_zbuff 生成;
是否必选:必须传入此参数;

Ws

参数含义:旋转因子 -sin
数据类型:table  zbuff
取值范围:float32 路径:Lua 数组或 zbuff(float32)
         q15 路径:zbuff(int16),推荐配合 fft.generate_twiddles_q15_to_zbuff 生成;
是否必选:必须传入此参数;

opts

参数含义:可选配置表;
数据类型:table
是否必选:可选;
参数格式:{
         _-- 参数含义:核心计算路径;_
         _-- 数据类型:string;_
         _-- 是否必选:可选;_
         _-- 取值范围:"f32"(默认)、"q15";_
                     --_f32浮点内核,精度高(32位),计算稳定,适合精密分析;_
                     --_q15定点内核,速度快(16位整数),内存省,适合实时处理但精度略低;_
         _-- 参数示例:"f32"_
         core = ,

         _-- 参数含义:输入数据格式;_
         _-- 数据类型:string;_
         _-- 是否必选:q15 路径时必填;_
         _-- 取值范围:"f32"、"u12"、"u16"、"s16";f32 路径时无需配置;_
         _-- 参数示例:"f32"_
         _-- 注意事项:--f32: 标准浮点输入,适用于已处理的信号数据;_
                     _--u12: 12位无符号整数(0~4095),常见于ADC采样,自动去直流分量;_
                     _--u16: 16位无符号整数(0~65535),适用于高精度ADC或预处理数据;_
                     _--s16: 16位有符号整数(-32768~32767),适用于已去直流的差分信号;_
         input_format = ,
         }

返回值

无返回值

示例

_-- 示例:使用 IFFT 重构信号_
local N=2048
local real=zbuff.create(N*4);
local imag=zbuff.create(N*4)
local Wc,Ws=fft.generate_twiddles(N)
_-- 先执行 FFT 处理..._
fft.run(real, imag, N, Wc, Ws)
_-- 处理频域数据...-- 然后执行 IFFT 重构_
fft.ifft(real, imag, N, Wc, Ws)

fft.fft_integral(real, imag, n, df)

功能

频域积分(1/(jω))。

参数

real

参数含义:real实部
数据类型:table  zbuff
取值范围:float32 精度;
是否必选:必须传入此参数;

imag

参数含义:imag虚部
数据类型:table  zbuff
取值范围:float32 精度;
是否必选:必须传入此参数;

n

参数含义:点数;
数据类型:number
取值范围:必须为 2 的幂次方;
是否必选:必须传入此参数;

df

参数含义:频率分辨率;
数据类型:number
取值范围:fs/nfs为采样率);
是否必选:必须传入此参数;

返回值

无返回值

示例

_-- 先完成 FFT 得到谱 (real, imag),再调用积分:_
fft.fft_integral(real, imag, N, fs/N)

五、产品支持说明

支持 LuatOS 开发的所有产品都支持 fft 核心库。