ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

C54x DSP上FIR低通滤波器的设计与CCS实现验证

2026/9/13 19:36:19 拓冰建站 浏览量
C54x DSP上FIR低通滤波器的设计与CCS实现验证 简介面向数字信号处理与嵌入式开发学习者的FIR滤波实验资源基于CCS开发环境编写完整演示了有限冲激响应滤波器的设计、实现与调试流程适合正在学习数字信号处理或CCS开发的学生作为参考。压缩包共16个文件涵盖C源程序、头文件、链接命令文件、预编译库、输入输出数据文件以及工程配置等整体体积仅26KB结构清晰便于查阅。已有268人学习下载。实验围绕低通滤波场景展开完整包含滤波器指标规划、系数生成、量化转换、卷积运算、边界处理以及时域频域性能评估等关键环节并附带波形数据以便对照验证。通过学习该资源既能理解有限冲激响应滤波器的原理与逐点卷积实现也能熟悉CCS工程管理、编译链接和调试分析方法为后续独立开展数字信号处理开发打下基础。1. 为什么这份 C54x 的 lpassfir 工程值得从 FIR 底层看起在 TMS320C5416 这类 C54x DSP 上做数字滤波很多人第一次是直接从 CCS 里把 lpassfir.pjt 拖进工作区编译、load、运行然后盯着图形窗口里的两条曲线发呆。这份 fir.rar 实验包表面上只是 exp5 的一次作业但它把 FIR 滤波器从设计到上板验证的每个环节都压缩在了不到十个文件里test.c 负责主逻辑c54fir.H 放系数C54FIR.LIB 提供运算例程wave1k500.dat 是叠加了 500Hz 和 1kHz 的混合信号fwave1k500.dat 是经过低通后的输出。对于想搞懂数字信号处理但又不满足于在桌面软件里画框图的人来说这个工程是很好的解剖对象。本文按设计、编译、验证、量化的顺序拆一遍。2. FIR 低通滤波器的参数推导与 C54FIR.LIB 调用2.1 为什么是 FIR 而不是 IIR 或滑动平均FIR 滤波器的输出只是输入信号的加权平均没有反馈项因此必然稳定。这个实验的数据在 wave1k500.dat 中按常见课程设计的假设采样率是 8000Hz输入由 500Hz 和 1kHz 两个正弦叠加目标是保留 500Hz、滤除 1kHz。此时选择一个通带截止 750Hz、阻带截止 1kHz 的低通滤波器即可。相比一阶低通滤波和滑动窗口滤波FIR 能用有限阶数做出线性相位不破坏信号到达时间的相对关系而滑动窗口滤波本质上是系数均为 1/N 的 FIR阻带衰减固定只有约 13dB无法同时满足过渡带和阻带的要求。窗函数法的选型理由很直接Hamming 窗过渡带约为 3.3/N归一化频率在 8kHz 采样下750Hz 到 1kHz 的过渡带为 250Hz归一化后是 0.03125所以 N 大约需要 106。不过实验包里 c54fir.H 中的系数是提前生成的课程演示常常取 64 或 65 阶会让阻带衰减略有下降。我一般会先跑一遍脚本观察频谱再决定阶数而不是直接套默认值。2.2 用 Python 生成定点系数在 C54x 这种 16 位定点 DSP 上浮点系数必须转为 Q15 格式。用 Python 的 scipy.signal.firwin 可以快速得到一组 Hamming 窗低通系数并导出为 C 数组。在设计系数之前先确认采样率和目标频率下面这段代码能直接带出 Q15 数组。import numpy as np from scipy.signal import firwin fs 8000.0 # 采样率实验包通常按 8kHz 处理 cutoff 750.0 # 低通截止频率 numtaps 65 # 阶数取 65奇数便于线性相位 h firwin(numtaps, cutoff, fsfs, windowhamming) h_q15 np.round(h * 32768).astype(np.int16) print(f// {numtaps} taps, fs{fs}, cutoff{cutoff}) for i in range(0, numtaps, 8): row , .join(f{v:6d} for v in h_q15[i:i8]) print(f {row},)这段代码先用 firwin 计算浮点系数h 是长度为 65 的数组乘上 32768 并取整就得到了 Q15 定点表示。打印出来的数组可以直接粘贴到 c54fir.H 中替换原来的系数。参数上numtaps 越大过渡带越窄但在 C54x 上计算开销线性增加cutoff 取 750Hz 是为了让 500Hz 在通带内、1kHz 在阻带内。如果实验包里原始波形是其它频率只需改这两项后面编译和调试流程完全不受影响。导出后检查系数和低通滤波器系数和应接近 256Q15 下约 0.9922偏离明显说明量化误差较大。2.3 C54FIR.LIB 的调用约定与非零初值的坑C54FIR.LIB 是 TI 为 C54x 提供的 DSP 例程库里面包含优化过的 FIR 函数。test.c 中通常这样调用#include c54fir.H short x[NSAMP]; short y[NSAMP]; short h[Nh]; fir(x, y, h, NSAMP, Nh); /* 实际函数名以 DSPLIB 头文件为准 */这个调用把 x 的 NSAMP 个样点与 h 的 Nh 个系数做卷积输出写入 y。C54x 的库函数内部使用循环寻址要求 h 所在缓冲区起始地址按 Nh 对齐否则会进入慢速路径甚至产生错误结果。我一般会在链接命令文件 test.cmd 中对 .coeff 段做对齐.coeff : {} RAM PAGE 1, align(64)另外要注意DSPLIB 的 fir 函数不会自动清空内部延迟线。如果程序连续多次调用 fir第二次调用时上次残留的延迟线数据会叠加到新输出上。常见做法是在调用前用 memset 把状态数组清零。test.c 如果只是单次处理 wave1k500.dat这个坑不容易被发现但一旦放到实时采集循环里输出前几十个点会带有明显偏置。定点 C54x 做 FIR 不用浮点不是因为浮点不能跑而是库函数和编译器的定点路径快很多这也解释了为什么实验包的系数表全部是整型。3. 在 CCS 中复现实验工程文件、编译配置与边界处理3.1 解压 fir.rar 之后这些文件各自干什么拿到 fir.rar 后先别急着全选复制到工程目录先理解每个文件在编译链中的位置。下面这张表是这套实验最常见的文件角色。文件类型在本工程中的作用lpassfir.pjt工程文件记录编译选项和源文件列表test.c源文件主程序读取输入、调用 fir、输出结果test.cmd链接命令文件定义 C54x 内存页与段分配c54fir.H头文件FIR 阶数定义和系数表C54FIR.LIB库文件TI DSPLIB提供 fir 等函数RTS.LIB库文件C 运行支持库TMS320.H头文件C54x 寄存器和外设定义test.dat数据文件可能是输入激励或初始化数据wave1k500.dat数据文件输入混合信号常见为 CCS 格式fwave1k500.dat输出文件滤波后的结果Debug.lkv / Debug.lkf中间文件编译生成的链接映射不必手工编辑cc_build_Debug.log日志编译日志排错时查看lpassfir.paf 是工程参数文件记录编译器版本、优化级别等。wave1k500.dat 和 test.dat 的格式需要留意C54x 的 .dat 文件常见第一行是1651 1 8000 0 0这样的头部后面每行是“地址 数据”。如果直接用文本编辑器的二进制视图或者普通 load 命令读取只会读到一串地址无法得到正确的样点序列。3.2 导入、编译和加载的步骤CCS 版本不同导入方式略有变化。CCS 3.x 直接 File-Open Project指定 lpassfir.pjtCCS 4 及以上用 Project-Import Legacy CCS/Eclipse Project选中 .pjt 后 Toolchain 选择 TI C5000/C5400。导入后会看到源文件和库右键工程选择 Build Projectconsole 里应生成 lpassfir.out。如果编译失败cc_build_Debug.log 是最直接的线索通常错误集中在 c54fir.H 的系数表超范围、test.cmd 的内存段重叠以及 C54FIR.LIB 和 RTS.LIB 的路径。加载 .out 前需要配置目标。纯软件验证用 CCS 内置的 C54x Simulator 即可不用连开发板。在 Target Configuration 中新建一个 C5416 Simulator 配置然后 Debug-Connect最后 Debug-Load Program。加载后打开 Memory 窗口观察 .bss 段中的 x 缓冲区用 File-Load Data 加载 wave1k500.dat 到数组起始地址。这一步容易出错.dat 文件中第一个地址必须与 CCS 里加载到的地址一致否则数据会被写到随机位置。3.3 test.cmd 与生成的 Debug.lkftest.cmd 是用户写的链接脚本决定代码和数据落在哪个内存区。C54x 有两套独立地址空间PAGE 0 是程序空间PAGE 1 是数据空间。一个典型的 test.cmd 片段如下MEMORY { PAGE 0: VEC: org 0x0080, len 0x0080 PROG: org 0x0100, len 0x3F00 PAGE 1: DATA: org 0x1000, len 0x1000 } SECTIONS { .text : PROG PAGE 0 .data : DATA PAGE 1 .bss : DATA PAGE 1 .stack : DATA PAGE 1 .coeff : DATA PAGE 1, align(64) }这里把程序段 .text 放到 0x0100 开始的 0x3F00 字数据段放到 0x1000 开始的数据区。Debug.lkf 是 CCS 从工程设置自动生成的链接信息文件等价于一个由图形界面生成的 cmd当你在工程属性里改了堆栈大小CCS 会同步改写 .lkf。调试时如果程序跑到异常地址第一步对比 .map 文件里的实际段地址与 test.cmd 期望值通常问题出在 .stack 和数据缓冲区重叠。3.4 边界处理前 64 个输出点为什么不可信FIR 的卷积要求完整的历史输入。在第 n 个输出点需要 x[n]、x[n-1]...x[n-Nh1]起始阶段 n Nh-1 时历史样点不存在。test.c 里如果直接写双层循环最直接的做法是补零#define NX 256 #define NH 65 short x[NX], y[NX]; short h[NH]; void fir_naive(int nx, int nh) { int i, j; for (i 0; i nx; i) { long acc 0; for (j 0; j nh; j) { if (i - j 0) acc (long)x[i - j] * h[j]; } y[i] (short)(acc 15); } }当 i-j0 时跳过累加相当于给输入补零。这样前 64 点幅度偏小后面逐步正常。观察频域波形时如果看到起始段有诡异变化不是滤波器坏了而是补零效应。C54FIR.LIB 的 fir 函数用循环缓冲维护 Nh-1 个历史样点首次调用前需要把延迟线清零否则第 0 个输出会带入上次残留。实验包里只有一次滤波过程所以效果不明显把同一段输入连续滤波两遍差异立刻可见。4. 从 wave1k500.dat 到 fwave1k500.dat波形与频域验证4.1 解析 CCS 的 .dat 文件C54x 的 .dat 文件常见格式如下1651 1 8000 0 0 0x1200 0x1234 0x1201 0x5678第一行以 1651 开头表示十六进制整数数据第二列 1 表示数据宽度为一个字第三列 8000 是起始地址。读取时不能直接当作纯样点数组需要跳过第一行并提取第二列。用 Python 解析import numpy as np def read_ccs_dat(path): with open(path, r, encodingascii) as f: lines f.readlines() samples [] for line in lines[1:]: line line.strip() if not line: continue parts line.split() if len(parts) 2: try: val int(parts[1], 0) samples.append(val) except ValueError: continue return np.array(samples, dtypenp.int16)这里 int(parts[1], 0) 能同时识别十六进制和十进制。如果文件中只有一列纯数据则直接读取。拿到样点后把 Q15 值除以 32768 得到浮点归一化信号然后做 FFT。对于这个低通实验1kHz 分量应下降 40dB 以上而 500Hz 分量保持原样。4.2 验证频域指标的数值标准下表列出一组调 FIR 实验时常用的最低标准低于这个需要回头检查系数或输入数据格式。指标期望值检查方法通带增益0.95 ~ 1.0-0.5 ~ 0 dB500Hz 峰幅度与输入比较阻带衰减 35 dB1kHz 峰幅度差过渡带中心750Hz 附近频谱峰值位置群延迟(Nh-1)/2 个采样点时域波形峰值位置差直流偏移小于 -60 dB0Hz 处频谱幅度群延迟对 FIR 非常关键。65 阶线性相位 FIR 的群延迟是 32 个采样点在 8kHz 采样下正好是 4ms。对比输入和输出波形时如果只看峰值对齐会发现两者差 32 个点这不是滤波器引入的相位失真而是线性相位 FIR 的正常延迟。验证方法是对齐后取前 512 点做互相关峰值位置应在 32 附近。4.3 在 CCS 图形窗口里直接看信号如果不想导出到 PythonCCS 自带图形工具也能定位问题。View-Graph-Time/Frequency选择 Single Time起始地址填 x 数组的地址Acquisition Buffer Size 填 256Display Data Size 填 256DSP Data Type 选 16-bit signed integerQ-Value 设 15。此时看到的输入波形是两条正弦叠加再启一个 Graph 窗口指向 y 数组运行后就能看到 500Hz 正弦。如果波形出现截顶说明 Q15 系数或输入数据超出 ±1 范围需要缩放。频域验证用 FFT 图形同样指向 x 和 y 地址FFT Frame Size 取 256。对比两个频谱窗口的峰值能更直观地看到 1kHz 分量被抑制。若发现频谱底噪很高先检查 .dat 文件是否被解析成了有符号数C54x 的 0x8000 到 0xFFFF 代表负数如果按无符号读频谱会产生镜像。5. 定点实现里容易忽略的直流偏置与系数和偏差5.1 为什么系数和不是 1Q15 量化后低通滤波器的直流增益是 h_q15 之和除以 32768。由于每个系数都被四舍五入到整数累加和可能偏大或偏小。比如 65 阶 Hamming 窗设计出的浮点系数和为 0.9998量化后乘 32768 取整和可能变成 32766 或 32770。对应直流增益分别是 0.99994 和 1.00006单看不严重但级联多级滤波或进行 DC 去除时误差会被放大。更麻烦的是量化后不再严格对称线性相位特性会被轻微破坏。解决方法是设计时保证系数数组左右对称然后用一个短循环强制系数和为 2^15long sum 0; int i; for (i 0; i Nh; i) sum h[i]; h[Nh/2] (short)(32768 - sum);这段代码把误差集中到中心系数上。中心系数对应的是输入信号的当前样点对它做微小修正对频响影响最小因为它位于冲激响应的对称中心。修完后再跑一次频谱检查阻带通常不会受影响直流增益则精确等于 1.0。5.2 用模拟器对照定点与浮点结果CCS 的 C54x Simulator 支持 C/C 调试但默认会忽略整数溢出标志。在 test.c 中把累加器 long acc 强制赋给 short y[i] 时如果 acc 超过 16 位范围低 16 位会被截断而不报错。调试时用 CCS 的 Watch 窗口监视 acc 的最大值或者用断点记录每个样点的最大值。若 acc 最大值接近 32767*Nh说明系数和输入动态范围重叠需要对输入数据右移一位。正确的做法是先用 Python 计算理论峰值再反推软件里的移位量。5.3 关键操作滤波前检查单位冲激响应无论是修改窗函数还是调整阶数先不要急着看输出波形而是把系数数组当作输入滤波一个单位脉冲观察输出是否对称、峰值是否在第 32 个样点。这一步能快速暴露系数粘贴错误、数组越界和延迟线未清零三类问题。单位冲激响应正常后再用 wave1k500.dat 做全流程验证。本文还有配套的精品资源点击获取