目录
- FFT 简介:什么是FFT,为什么用它?
- CCS C 语言实现 FFT 的核心要素
- 库函数 vs. 自定义代码
- 数据类型:定点数 vs. 浮点数
- 内存管理:数据缓冲区
- 实践步骤:在 CCS 中使用 TI 官方库
- 步骤 1:创建新工程
- 步骤 2:添加头文件和库文件
- 步骤 3:编写 C 语言主程序
- 步骤 4:配置和编译
- 步骤 5:调试与分析结果
- 完整示例代码 (C 语言)
- 常见问题与注意事项
FFT 简介
- DFT (离散傅里叶变换):将时域信号(如一个声音波形的采样点)转换到频域,从而分析出信号包含哪些频率成分,以及各成分的强度,直接计算 DFT 的复杂度是 O(N²),对于 N 个点,计算量非常大。
- FFT (快速傅里叶变换):是一种高效计算 DFT 的算法,其复杂度降为 O(N log N),当 N 很大时(1024, 2048),FFT 的速度优势是巨大的,是现代数字信号处理的基石。
核心思想:FFT 通过“分治”策略,将一个大的 DFT 分解成若干个小的 DFT 来计算,并利用旋转因子的周期性和对称性来减少重复计算。
CCS C 语言实现 FFT 的核心要素
在 CCS(通常用于 TI 的 C2000, C6000 等系列 DSP)中实现 FFT,主要有两种方式:
a. 库函数 vs. 自定义代码
- 强烈推荐使用官方库:对于大多数应用,直接使用 TI 提供的高度优化、经过验证的 DSP 库是最佳选择,这些库是用汇编或特定 DSP C 语言扩展编写的,效率极高,并且已经处理好了很多细节(如位反转、内存对齐等)。
- 自定义代码:学习目的或特殊算法需求时,可以自己用 C 语言实现 FFT,这有助于理解算法原理,但性能通常不如官方库。
b. 数据类型:定点数 vs. 浮点数
DSP 芯片通常在处理速度上有优势,这取决于其硬件架构。
-
定点数:
- 优点:计算速度快,占用资源少,绝大多数 TI DSP 都有专门的硬件乘法器和累加器来支持定点运算。
- 缺点:动态范围有限,需要小心处理溢出和量化噪声,你需要手动管理小数点的位置(即 Q 格式,Q15, Q31)。
- 库:
dsplib(DSP Library) 提供了定点 FFT 函数,如DSPF_sp_fft16xx()(用于 16 位定点数)。
-
浮点数:
- 优点:动态范围大,编程简单,不易溢出,更接近数学公式。
- 缺点:计算速度通常比定点数慢,占用更多内存和 CPU 资源。
- 库:
dsplib(DSP Library) 提供了浮点 FFT 函数,如DSPF_sp_fft32xx()(用于 32 位浮点数),如果你的 DSP 是 C674x 或 C67x 系列的浮点 DSP,这是首选。
选择建议:
- 如果你的 DSP 是 C28x, C55x 等以定点运算为主的,且对性能要求苛刻,选择 定点 FFT。
- 如果你的 DSP 是 C674x, C6000 等支持浮点运算的,或者为了开发方便,选择 浮点 FFT。
c. 内存管理:数据缓冲区
FFT 运算需要两个主要的缓冲区:
- 输入缓冲区:存放时域的采样数据,通常为复数,即使你的输入是实数信号,也需要将虚部设为 0。
- 输出缓冲区:存放频域的计算结果,也是一个复数数组,输出数组的模(
sqrt(real² + imag²))代表该频率分量的幅度。
重要:FFT 运算通常是“原地”进行的,即输入和输出可以共用同一个缓冲区,以节省内存,但为了清晰起见,初学者可以使用两个独立的缓冲区。
实践步骤:在 CCS 中使用 TI 官方库
这里我们以 浮点 FFT 为例,因为它更通用,且步骤与定点 FFT 类似。
步骤 1:创建新工程
- 打开 CCS。
- 选择
File -> New -> CCS Project。 - 输入工程名称(如
FFT_Test),选择目标设备(TMS320C6748)。 - 在 "Project Type" 中,选择
Empty Project。 - 点击
Finish。
步骤 2:添加头文件和库文件
TI 的库文件和头文件通常位于 CCS 安装目录下的 ti 文件夹中。
C:\ti\ccs1230\ccs\tools\compiler\ti-cgt-c6000_8.3.1\include
C:\ti\ccs1230\ccs\bios_7_01_00_08\packages\ti\dsplib\c674x\lib
操作方法:
- 在左侧的 "Project Explorer" 中,右键点击你的工程。
- 选择
Properties。 - 在左侧导航栏中,展开
C/C++ Build -> Include Options。 - 点击
Add...,将上述include路径添加进去,这样你才能#include <dsplib.h>。 - 在左侧导航栏中,展开
C/C++ Build -> Library Search Path。 - 点击
Add...,将上述lib路径添加进去,这样链接器才能找到dsplib.lib。 - 在左侧导航栏中,展开
C/C++ Build -> Libraries。 - 点击
Add...,在右侧选择File system,然后浏览并添加dsplib.lib文件。
步骤 3:编写 C 语言主程序
在工程中创建一个新的 C 源文件(main.c),并粘贴以下代码。
步骤 4:配置和编译
- 确保
main.c是工程的一部分。 - 点击工具栏上的锤子图标(
Build Project)进行编译。 - 如果没有错误,你会在控制台看到 "Build completed successfully"。
步骤 5:调试与分析结果
- 加载数据:为了让 FFT 有输入,你需要一个数据源,最简单的方法是在代码中生成一个测试信号(如正弦波)。
- 运行和断点:
- 在
DSPF_sp_fft32()函数调用之后,FFT_Output计算完成之后,设置一个断点。 - 点击 "Debug" 按钮。
- 程序会在断点处暂停。
- 在
- 查看结果:
- 内存浏览器:打开
View -> Memory Browser。 - 在地址栏中输入
FFT_Output的地址(可以在变量窗口中查看),你将看到复数数组。 - 频率点是按顺序排列的(从 0Hz 到采样率),你需要计算每个点的频率值。
- 图表:CCS 提供了强大的图表功能来可视化结果。
- 在变量窗口中右键点击
FFT_Output_Mag(我们将在代码中计算幅度)。 - 选择
Graph -> Single Time...。 - 在弹出的对话框中,设置
Graph Display的属性:- Start Address:
FFT_Output_Mag - Acquisition Buffer Size:
FFT_SIZE(1024) - Index Increment:
1 - Display Data Size:
FFT_SIZE / 2(因为 FFT 输出是对称的,我们只需要看一半) - Data Type:
16-bit Unsigned Integer(如果你的幅度值做了缩放)
- Start Address:
- 点击
OK,你将看到一个频谱图,峰值处就是你输入信号的频率。
- 在变量窗口中右键点击
- 内存浏览器:打开
完整示例代码 (C 语言)
这个例子将生成一个包含 50Hz 和 120Hz 两个频率成分的合成信号,然后进行 1024 点 FFT,并计算幅度谱。
#include <stdio.h>
#include <math.h>
#include "dsplib.h" // TI DSP 库头文件
// 定义 FFT 相关参数
#define FFT_SIZE 1024
#define SAMPLE_RATE 1000.0 // 采样率 1kHz
// 定义输入和输出缓冲区
// 注意:FFT 函数通常需要输入和输出缓冲区,并且它们的大小必须是 2 的幂次方
// 我们使用两个独立的缓冲区以便于理解
float32_t input_buffer[FFT_SIZE * 2]; // 复数输入,实部和虚部交替
float32_t output_buffer[FFT_SIZE * 2]; // 复数输出,实部和虚部交替
float32_t fft_output_mag[FFT_SIZE]; // 存储幅度谱
// 旋转因子/系数数组,由库函数初始化
float32_t *twiddle_factors;
// 位反转表,由库函数初始化
uint16_t *bit_rev_table;
void main(void)
{
int i;
double temp_real, temp_imag;
// --- 1. 初始化 ---
// 分配并初始化旋转因子和位反转表
// 这些是 FFT 计算必需的预计算表
twiddle_factors = (float32_t *)malloc(FFT_SIZE * 2 * sizeof(float32_t));
bit_rev_table = (uint16_t *)malloc(FFT_SIZE * sizeof(uint16_t));
// 调用库函数来生成这些表
DSPF_sp_cfftr2_fr16(twiddle_factors, bit_rev_table, FFT_SIZE);
// --- 2. 准备输入数据 ---
// 生成一个复合信号: 50Hz + 120Hz 正弦波
// 采样点 n 上的值: sin(2*pi*f*n/fs)
for (i = 0; i < FFT_SIZE; i++)
{
// 实部
input_buffer[2 * i] = (float32_t)(sin(2 * 3.14159 * 50.0 * i / SAMPLE_RATE) +
0.5 * sin(2 * 3.14159 * 120.0 * i / SAMPLE_RATE));
// 虚部 (对于实数信号,虚部设为0)
input_buffer[2 * i + 1] = 0.0f;
}
// --- 3. 执行 FFT ---
// 调用 TI 的 FFT 函数
// DSPF_sp_fft32: 32-bit 浮点 FFT
// 参数:
// 1. 输入/输出缓冲区
// 2. 旋转因子表
// 3. 位反转表
// 4. FFT 点数
// 5. 标志位 (0 表示正向 FFT)
DSPF_sp_fft32(input_buffer, twiddle_factors, bit_rev_table, FFT_SIZE, 0);
// 注意:DSPF_sp_fft32 是“原地”运算,计算结果会写回 input_buffer
// 为了清晰,我们将结果复制到 output_buffer
for (i = 0; i < FFT_SIZE * 2; i++)
{
output_buffer[i] = input_buffer[i];
}
// --- 4. 计算幅度谱 ---
// FFT 输出是复数,幅度 = sqrt(real^2 + imag^2)
// 为了优化,通常计算幅度的平方: mag^2 = real^2 + imag^2
for (i = 0; i < FFT_SIZE; i++)
{
temp_real = output_buffer[2 * i];
temp_imag = output_buffer[2 * i + 1];
// 计算幅度的平方,并缩放以避免数值过大
// 缩放因子 1.0 / FFT_SIZE 用于将幅度归一化
fft_output_mag[i] = (float32_t)(temp_real * temp_real + temp_imag * temp_imag) / (float32_t)FFT_SIZE;
}
// --- 5. 结果 (在实际应用中,这里会将结果发送到串口或显示) ---
// 在调试器中,你可以查看 fft_output_mag 数组
// 预期结果: 在频率 50Hz 和 120Hz 处会出现两个峰值
// 对应的数组索引: index = frequency * FFT_SIZE / SAMPLE_RATE
// 50Hz -> index = 50 * 1024 / 1000 = 51.2 -> 索引 51 或 52
// 120Hz -> index = 120 * 1024 / 1000 = 122.88 -> 索引 122 或 123
// 在实际应用中,你可能需要在这里添加代码来处理结果,
// 例如通过串口打印出来,或者用于控制算法。
// 示例:打印第一个和第二个峰值点的幅度
// printf("Magnitude at bin 51: %f\n", fft_output_mag[51]);
// printf("Magnitude at bin 122: %f\n", fft_output_mag[122]);
while (1); // 主循环,保持程序运行以便调试
}
常见问题与注意事项
- FFT 点数必须是 2 的幂次方:这是基-2 FFT 的基本要求,如果你的数据点数不是 2 的幂,需要用 0 填充到下一个 2 的幂次方。
- 数据格式和缩放:
- 输入信号幅度:确保输入信号的幅度在 DSP 的处理范围内,避免溢出,对于定点数,这尤为重要。
- 输出幅度缩放:FFT 的输出幅度与输入幅度和 FFT 点数有关,通常需要将结果除以
N/2(对于实数输入)或N(对于复数输入)来得到正确的幅度值,在上面的代码中,我们除以了N。
- 频谱对称性:对于实数输入信号,FFT 输出是共轭对称的,这意味着你只需要分析前半部分频谱(从 0 到
Fs/2,即奈奎斯特频率)即可。 - 加窗:如果输入信号不是整数个周期,频谱会出现“泄漏”,即能量分散到其他频率点上,为了减少泄漏,通常在 FFT 之前对信号进行“加窗”(如汉宁窗、汉明窗等)。
- 链接错误:如果编译时出现链接错误(如 "undefined reference to `DSPF_sp_fft32'"),请检查第 2 步中是否正确添加了库文件路径和库文件本身。
- 性能分析:CCS 自带的 实时分析工具 可以帮助你评估 FFT 函数执行所占用的 CPU 资源,这对于优化系统性能非常有用。
希望这份详细的指南能帮助你在 CCS C 语言环境中成功实现 FFT!
