DSP56800/E平台IIR与FIR滤波器嵌入式实现:从QEDesign Lite到Processor Expert全流程解析

发布时间:2026/6/21 17:32:23
DSP56800/E平台IIR与FIR滤波器嵌入式实现:从QEDesign Lite到Processor Expert全流程解析 1. 项目概述从设计到实现的滤波器部署之旅在嵌入式信号处理的世界里把一张理想的滤波器频响图纸变成一块DSP芯片里稳定运行的代码中间隔着的远不止是理论公式。我接触过不少工程师他们能熟练地在MATLAB里设计出漂亮的滤波器但一到往DSP56800/E这类16位定点处理器上移植就卡在了系数解释、内存对齐和防溢出这些“脏活累活”上。这感觉就像画好了精密的建筑蓝图却对如何搅拌混凝土、搭建脚手架一无所知。今天我就以飞思卡尔现恩智浦DSP56800/E平台为例结合其官方的QEDesign Lite工具和Processor Expert软件框架把IIR和FIR滤波器从设计到嵌入式实现的完整链条特别是那些手册里不会写的“坑”和技巧给大家彻底捋清楚。数字信号处理的核心任务之一就是滤波即从混杂的信号中提取出我们关心的频率成分。IIR和FIR是实现这一目标的两种基本结构。IIR滤波器因其递归结构能用较少的阶数实现陡峭的滚降但需警惕其潜在的稳定性问题和非线性相位。FIR滤波器永远是稳定的并能实现严格的线性相位代价是通常需要更高的阶数从而带来更大的计算量。在DSP56800/E这种资源受限的定点处理器上做选择更像是一场在性能、资源和实时性之间的精准权衡。QEDesign Lite这类工具帮我们跨越了理论设计的鸿沟它能根据通带截止频率、阻带衰减等指标自动计算出满足要求的滤波器系数。然而工具生成的系数文件那一串串十六进制和浮点数如何变成DSP能理解并高效执行的指令这正是工程实践中最关键的一环。本文将深入解析如何解读QEDesign Lite的输出如何利用Processor Expert的DFR16库进行静态与动态内存分配下的滤波器实现并分享在定点运算中防止溢出、保证精度的实战经验。2. 核心工具链与平台解析2.1 QEDesign Lite从指标到系数的桥梁QEDesign Lite并非功能最强大的滤波器设计工具但它对于DSP56800/E开发来说非常“对口”。它的核心价值在于其输出格式与后续Processor Expert中的DFR16数字滤波库能够无缝衔接。你输入滤波器的基本参数类型低通、高通、带通等、模拟原型如Butterworth、Chebyshev、采样频率、通带截止频率与纹波、阻带截止频率与衰减它便会采用双线性变换等方法为你生成一组可直接用于定点DSP的系数。这里有一个关键细节常被忽略系数量化。DSP56800/E内核使用16位定点数Q15格式表示小数范围在[-1, 1-2⁻¹⁵]之间。QEDesign Lite在生成系数时已经替你完成了从高精度浮点数到16位定点数的量化与缩放。在它的输出文件里你会同时看到十六进制的定点系数和对应的浮点数值这为我们验证和调试提供了极大便利。例如一个值为0x2002的系数其对应的浮点数值约为0.250061这正是在Q15格式下对0.25的近似表示。理解这种对应关系是诊断滤波器频率响应是否偏离预期的第一步。2.2 Processor Expert与DFR16库嵌入式实现的脚手架Processor ExpertPE是CodeWarrior for DSP56800/E集成开发环境中的一个高效代码生成与配置框架。你可以把它理解为一个“图形化编程”和“代码自动生成”的结合体。对于数字信号处理PE提供了DSP_Func_DFR组件Bean其中封装了经过高度优化的DFR16函数库包括IIR、FIR、FFT等常用算法。DFR16库的强大之处在于两点一是它针对DSP56800/E的硬件架构如并行移动、模寻址进行了手写汇编优化计算效率远高于直接用C语言编写的通用函数二是它提供了一套清晰的数据结构如dfr16_tIirStruct,dfr16_tFirStruct和API将滤波器的状态历史数据与系数管理起来使你的应用代码逻辑非常清晰。调用一个滤波函数本质上就是调用DFR1_dfr16IIR()或dfr16FIRC()并传入准备好的数据结构指针和输入输出数组。2.3 DSP56800/E的定点数世界Q格式与溢出管理DSP56800/E是纯粹的定点处理器没有硬件浮点单元。所有运算包括小数乘法都在整数ALU中完成。这就是Q格式定点数格式的用武之地。我们最常用的是Q15格式1位符号位15位小数位。一个16位整数N表示的Q15数值实际是N / 32768。注意在进行连续的乘加运算如卷积、差分方程计算时中间结果很容易超出Q15的表示范围-1到~0.9999导致溢出。溢出时数值会“环绕”从正最大值突然跳变到负最大值产生灾难性的失真。因此DFR16库函数内部通常包含饱和处理选项或者在设计系数时就必须进行缩放确保运算链中的信号幅度始终受控。这也是为什么QEDesign Lite会给出一个“整体增益”Overall Gain和“移位计数”Shift Count它们就是用来在滤波前或滤波后对信号进行幅度缩放防止内部溢出的安全阀。3. IIR滤波器实现全流程拆解3.1 系数文件的深度解读与转换拿到QEDesign Lite生成的.c或.txt系数文件别被里面密密麻麻的数字吓到。我们以资料中那个2阶低通Butterworth滤波器为例拆解每一部分的含义。FILTER TYPE LOW PASS ANALOG FILTER TYPE BUTTERWORTH PASSBAND RIPPLE IN -dB -.3000E01 // 通带纹波 -3dB STOPBAND RIPPLE IN -dB -.1000E02 // 阻带衰减 -10dB ... FILTER ORDER 2 NUMBER OF SECTIONS 1这告诉我们这是一个2阶滤波器采用1个二阶节Biquad实现。IIR滤波器的高阶传递函数可以分解为多个二阶节的级联这种结构在定点实现中数值稳定性更好。接下来是关键的数字部分-3 FFFFFFFD /* shift count for overall gain */ 21096 5268 /* overall gain */第一行-3是移位计数表示整体增益需要右移3位即除以8。第二行的21096是增益的定点表示值。所以该滤波器的实际直流增益为1 / (21096 * 2⁻³ / 32768) ≈ 12.424。这意味着一个幅度为1的直流信号通过这个滤波器输出会放大到约12.424倍。为了防止后续运算溢出我们必须用这个增益的倒数约0.08048去缩放输入信号或者缩放输出信号。然后是二阶节的系数1 1 /* shift count for section 1 values */ 4097 1001 /* section 1 coefficient B0 */ 8194 2002 /* section 1 coefficient B1 */ 4097 1001 /* section 1 coefficient B2 */ 25567 63DF /* section 1 coefficient -A1*/ -10502 FFFFD6FA /* section 1 coefficient -A2*/每个系数前面的数字如1是该系数的移位计数。真正的定点系数值需要做换算系数值 * 2^(移位计数)。例如B0的定点值4097移位计数为1则实际参与运算的系数应为4097 * 2¹ 8194。用Q15格式解释8194 / 32768 0.250061这与下方给出的浮点系数0.1250305175781250E00 * 2 0.250061完全吻合。这里一个常见的坑是直接使用4097作为系数忽略了移位操作导致滤波器频率响应完全错误。系数数组在C代码中需要以特定的顺序排列。对于DFR16库每个二阶节的5个系数应按-A1/2, -A2/2, B0/2, B1/2, B2/2的顺序存放。注意这里存放的是已经除以2的值。这是库函数内部实现的要求目的是在计算差分方程时将某些乘法合并优化计算速度。所以我们需要将上面计算出的实际系数如8194再除以2得到4097然后以十六进制形式0x1001存入数组。3.2 静态内存分配实现静态分配意味着在编译期就确定滤波器状态和系数所需的内存大小。这种方式简单、可靠没有运行时内存分配失败的风险适合对确定性要求极高的实时系统。首先需要根据二阶节的数量定义历史和系数数组的大小。相关常量在dfr16.h中定义#define FILT_STATES_PER_BIQ 2 // 每个二阶节需要2个历史状态y[n-1], y[n-2] #define FILT_COEF_PER_BIQ 5 // 每个二阶节有5个系数-A1/2, -A2/2, B0/2, B1/2, B2/2假设我们只有一个二阶节BIQUAD_NUMBER 1并一次处理100个样本#define BIQUAD_NUMBER 1 #define NUM_SAMPLES_IIR 100 #define HISTORY_BUFFER_SIZE (BIQUAD_NUMBER * FILT_STATES_PER_BIQ) // 2 #define IIR_COEF_LENGTH (BIQUAD_NUMBER * FILT_COEF_PER_BIQ) // 5 // 按顺序存放系数-A1/2, -A2/2, B0/2, B1/2, B2/2 const Frac16 IirCoefs[] { 0xD6FA, // {-a1/2} 对应 -10502 (十进制) - Q15值约为 -0.3205 0x63DF, // {-a2/2} 对应 25567 - Q15值约为 0.7802 0x1001, // {b0/2} 对应 4097 - Q15值约为 0.1250 0x2002, // {b1/2} 对应 8194 - Q15值约为 0.2501 0x1001 // {b2/2} 对应 4097 - Q15值约为 0.1250 }; Frac16 history[HISTORY_BUFFER_SIZE]; // 历史状态缓冲区 Frac16 iirCoefArray[IIR_COEF_LENGTH]; // 系数缓冲区库内部可能使用 dfr16_tIirStruct Iir; // IIR滤波器状态结构体 dfr16_tIirStruct *pIir Iir; // 结构体指针 Frac16* pIirCoefs (Frac16*)IirCoefs[0]; // 系数数组指针 Frac16 x[NUM_SAMPLES_IIR]; // 输入缓冲区 Frac16 y[NUM_SAMPLES_IIR]; // 输出缓冲区初始化与调用过程如下int main() { PE_low_level_init(); // Processor Expert 底层初始化 // 将结构体内部的指针指向我们分配的内存 pIir-pC (Frac16*)iirCoefArray[0]; pIir-pHistory (Frac16*)history[0]; // 关键步骤初始化IIR结构体。此函数会将pIirCoefs指向的系数拷贝到pIir-pC // 并可能进行一些内部格式转换同时清零历史缓冲区。 DFR1_dfr16IIRInit(pIir, pIirCoefs, BIQUAD_NUMBER); // 假设x数组已填充了输入数据 // 执行IIR滤波结果存入y数组 Result res DFR1_dfr16IIR(pIir, x, y, NUM_SAMPLES_IIR); // res 可用于检查函数执行状态如内存错误 }实操心得在调用DFR1_dfr16IIRInit之前务必确保pIir-pHistory指向的缓冲区已清零或包含已知的初始状态通常是全零。否则历史缓冲区中的随机值会导致滤波输出起始阶段产生不可预测的瞬态响应。3.3 动态内存分配实现动态分配在程序运行时main函数或某个初始化函数中通过malloc或库函数申请内存。它的优点是内存使用更灵活特别是在系统中有多个不同参数的滤波器需要动态创建和销毁时。缺点是存在分配失败的风险并可能产生内存碎片。DFR16库提供了dfr16IIRCreate()函数来一站式完成动态创建#define STEP_VALUE 0.3 Frac16 stepWave[100]; // 阶跃输入信号 Frac16 pZ[100]; // 输出缓冲区 const Frac16 coeff_IIR_83_gain 2637; // 整体增益的倒数 (21096/8)用于输入缩放 Frac16 IirCoefs[] {0xD6FA, 0x63DF, 0x1001, 0x2002, 0x1001}; // 系数数组 dfr16_tIirStruct *pIIR NULL; // 滤波器结构体指针 Frac16 *pC IirCoefs[0]; UInt16 nbiq 1; // 二阶节数量 UInt16 n 100; // 样本数 void main(void) { PE_low_level_init(); // 1. 缩放输入信号防止滤波器内部溢出 for(int i0; in; i) { stepWave[i] FRAC16(STEP_VALUE); // 将浮点数0.3转换为Q15格式 stepWave[i] mult(coeff_IIR_83_gain, stepWave[i]); // 乘以增益倒数进行缩放 } // 2. 动态创建IIR滤波器实例 pIIR dfr16IIRCreate(pC, nbiq); if(pIIR NULL) { // 内存分配失败处理例如点亮错误LED或进入安全状态 asm(debug); // 触发调试器中断 while(1); } // 3. 执行滤波 Result res dfr16IIRC_Fixed(pIIR, stepWave, pZ, n); // 4. 使用完毕后销毁滤波器释放内存 dfr16IIRDestroy(pIIR); }关键点解析输入缩放coeff_IIR_83_gain是滤波器整体增益的倒数2637/32768 ≈ 0.08048。在滤波前对输入信号进行缩放等价于在滤波后对输出进行缩放但前者能更有效地防止滤波器递归结构内部的中间变量溢出。dfr16IIRCreate这个函数不仅分配了dfr16_tIirStruct结构体本身的内存还根据nbiq参数为其内部的系数缓冲区(pC)和历史状态缓冲区(pHistory)分配合适大小的内存。这些内存来自一个预先配置好的“动态内存池”。dfr16IIRDestroy这是与Create配对的函数必须调用以释放内存防止泄漏。在长期运行的系统里忘记Destroy会导致内存逐渐耗尽。3.4 链接器命令文件.lcf的修改动态内存分配之所以能工作是因为链接器为“堆”Heap或动态内存池预留了空间。这需要手动修改链接器命令文件Linker Command File,.lcf。这个文件告诉链接器如何将代码、数据分配到芯片有限的内存地址中。在提供的资料中修改的核心是在MEMORY段中增加一个名为.xDynamic的段并为其分配地址空间例如从0x00000500开始长度为0x300。然后在SECTIONS段中将这个段与一些全局符号如memIMpartitionAddr,memIMpartitionSize关联起来。这些符号会被DSP_MEM组件Memory Manager Bean的初始化函数MEM1_Init()所使用从而建立起一个可被dfr16IIRCreate等函数调用的动态内存管理系统。注意事项修改链接器文件是嵌入式开发中风险较高的操作。务必清楚芯片的内存映射哪些地址是RAM哪些是外设寄存器确保动态内存区.xDynamic完全落在可读写的RAM区域内且不与栈Stack、堆Heap如果使用、全局变量等其他内存区域重叠。错误的配置会导致程序运行崩溃且难以调试。建议在修改前备份原文件并充分利用IDE的内存映射查看工具。4. FIR滤波器实现详解4.1 FIR滤波器的特点与实现结构FIR滤波器的输出仅依赖于当前和过去的输入值没有反馈回路其系统函数只有零点没有极点。这就带来了两大先天优势绝对稳定和易于实现线性相位只要系数满足对称或反对称性。代价是要达到与IIR滤波器相似的频率选择性通常需要更多的阶数更长的抽头数这意味着更多的乘加运算和更大的内存来存储输入历史。FIR的差分方程是卷积和y[n] h[0]*x[n] h[1]*x[n-1] ... h[N-1]*x[n-(N-1)]。在DSP56800/E上实现核心就是高效地完成这个乘积累加MAC运算。DFR16库的FIR函数利用处理器的模寻址Modulo Addressing功能将输入历史缓冲区组织成一个循环缓冲区无需在每次新样本到来时移动所有历史数据极大地提升了效率。4.2 静态与动态内存分配实现对比FIR的实现流程与IIR类似也分为静态和动态两种方式其API设计思想一脉相承。静态分配示例#define FIR_COEF_LENGTH 11 // 滤波器抽头数 #define NUM_SAMPLES 150 const int FirCoefs[] {-1953,-1435,1006,4646,7904,9207,7904,4646,1006,-1435,-1953}; // Q15格式系数 dfr16_tFirStruct fir; // FIR结构体 Frac16 *pFirCoefs; Frac16 firCoefArray[FIR_COEF_LENGTH * sizeof(Frac16)]; // 系数缓冲区 Frac16 history[FIR_COEF_LENGTH * sizeof(Frac16)]; // 历史缓冲区长度通常为抽头数-1需查库手册 void main(void) { PE_low_level_init(); fir.pC (Frac16 *)firCoefArray[0]; fir.pHistory (Frac16 *)history[0]; pFirCoefs (Frac16 *)FirCoefs[0]; // 初始化FIR结构体 DFR1_dfr16FIRInit((dfr16_tFirStruct*)fir, pFirCoefs, FIR_COEF_LENGTH); // 假设x是输入数组z1是输出数组 // DFR1_dfr16FIR((dfr16_tFirStruct*)fir, x[0], z1[0], NUM_SAMPLES); }动态分配示例#define FIR_COEF_LENGTH 11 const int FirCoefs[] {-1953,-1435,1006,4646,7904,9207,7904,4646,1006,-1435,-1953}; dfr16_tFirStruct* pFir NULL; Frac16 *pFirCoefs (Frac16 *)FirCoefs[0]; void main(void) { PE_low_level_init(); pFir dfr16FIRCreate(pFirCoefs, FIR_COEF_LENGTH); if(pFir NULL) { /* 错误处理 */ } // 执行块滤波 dfr16FIRC((dfr16_tFirStruct*)pFir, x, z11, NUM_SAMPLES); // 单样本实时滤波通常在中断中调用 // dfr16FIRC((dfr16_tFirStruct*)pFir, newSample, filteredOutput, 1); // ... 使用完毕后 dfr16FIRDestroy(pFir); }一个关键区别对于FIR滤波器dfr16FIRCreate函数不仅分配结构体内存还会尝试将历史缓冲区pHistory对齐到特定的内存边界边界大小是2的幂与抽头数有关。这种对齐是为了最大化利用DSP56800/E的模寻址能力从而获得最优的执行速度。这是静态分配时难以手动保证的优化点。4.3 实时采样与滤波的集成在实际的嵌入式信号处理系统中滤波往往是实时数据流处理的一部分。一个典型的流程是配置ADC定时器以固定采样率Fs触发转换。在ADC转换完成中断ISR中读取ADC结果寄存器获得一个新的样本x_newQ15格式。调用单样本滤波函数dfr16FIRC(pFir, x_new, y_out, 1)。将滤波结果y_out送入DAC或用于后续处理如幅值检测、阈值比较。这种“中断驱动单样本处理”的模式确保了滤波与采样严格同步处理延迟确定且最小。需要注意的是中断服务例程的执行时间必须小于采样间隔否则会发生数据丢失。因此选择计算量合适的滤波器阶数至关重要。5. 常见问题、调试技巧与经验实录5.1 滤波器频率响应与预期不符这是最常见的问题现象可能是截止频率偏移、通带增益不对、或阻带衰减不足。排查步骤1检查系数是否正确载入。在调试器中查看传入dfr16IIRInit或dfr16FIRCreate的系数数组指针指向的数据是否与QEDesign Lite生成的十六进制系数一致。特别注意系数顺序和是否已经除以2对于IIR。排查步骤2验证定点数转换。将十六进制系数手动转换为Q15小数除以32768并与QEDesign Lite输出的浮点系数对比。检查移位计数是否被正确应用。一个快速验证方法是在MATLAB或Python中用浮点系数构建滤波器输入一个单位阶跃信号看稳态输出是否接近1/整体增益。然后在DSP上做同样的测试对比结果。排查步骤3检查历史缓冲区初始化。确保在第一次调用滤波函数前或调用Init函数后历史缓冲区已被清零对于IIR或处于已知状态。未初始化的历史缓冲区包含随机值会影响滤波输出的前若干个点。排查步骤4确认采样频率。滤波器的频率特性如截止频率是相对于采样频率Fs归一化的。如果你在QEDesign Lite中设计的Fs是1000Hz截止频率是50Hz那么在实际DSP程序中ADC的采样率也必须严格设置为1000Hz。Fs不匹配会导致滤波器实际频响完全错位。5.2 运算溢出与饱和处理在定点DSP中溢出是无声的杀手它不一定会引发硬件异常但会导致信号严重失真。现象输入信号幅度不大但输出出现规律的、大幅度的畸变或输出被“削顶”在最大值/最小值附近。原因滤波器内部节点尤其是IIR的递归部分的信号幅度超过了Q15格式的表示范围-1 ~ 0.9999。解决方案利用整体增益进行缩放这是最根本的方法。使用QEDesign Lite提供的“整体增益”倒数在滤波前对输入信号进行衰减。资料中的示例正是这样做的pX[i] mult(coeff_IIR_83_gain, pX[i]);。启用处理器的饱和模式DSP56800/E的算术逻辑单元ALU通常支持饱和运算模式。当溢出发生时结果会被钳位Saturate到最大值或最小值而不是环绕Wrap Around。这比环绕产生的失真要好得多。可以通过设置核心状态寄存器中的饱和位SAT来启用。部分DFR16库函数在编译时可能已包含饱和选项。降低输入信号幅度在信号进入ADC或进行预处理时就通过硬件如放大器或软件乘以一个小于1的系数将其幅度控制在一定范围内为滤波器的增益留出余量。5.3 动态内存分配失败如果调用dfr16IIRCreate或dfr16FIRCreate后返回NULL指针。原因1链接器文件未正确配置动态内存段。检查.lcf文件中是否定义了.xDynamic段并且其ORIGIN和LENGTH是否在有效的RAM范围内。确保MEM1_Init()函数在main函数早期被调用以初始化内存管理。原因2内存池耗尽。如果系统中有多个动态创建的滤波器或其他动态内存对象可能总申请大小超过了.xDynamic段定义的长度。需要重新评估并增加.xDynamic的LENGTH。原因3内存碎片。长期运行中频繁地创建和销毁不同大小的对象可能导致内存池碎片化虽然总空闲内存足够但无法分配出一块连续的空间。对于长期运行的实时系统更推荐在初始化阶段一次性静态分配所有所需内存。5.4 实时性能优化与评估对于高采样率或高阶滤波器需要评估CPU负载是否过重。使用处理器专家性能分析工具CodeWarrior IDE通常提供周期计数或执行时间测量功能。在滤波函数调用前后设置断点或使用性能计数器测量执行一次块滤波或单样本滤波所需的指令周期数。计算理论负载对于一个N阶FIR滤波器每个输出样本需要N次乘加MAC运算。DSP56800/E的多数指令是单周期但MAC操作可能需要多个周期。估算出最坏情况下的处理时间确保它小于采样间隔。优化策略降低阶数在满足性能要求的前提下尽量使用阶数更低的滤波器。使用汇编优化库确保链接的是DFR16的优化版本通常是.a或.lib文件而不是未优化的C版本。利用块处理相比于单样本处理块处理一次处理一个数组可以减少函数调用的开销有时库函数内部还有进一步的循环优化。检查编译器优化等级确保在Release模式下编译并开启适当的优化选项如-O2, -O3。5.5 关于FFT实现的补充说明资料末尾提到了FFT的实现。在DSP56800/E上使用DFR16库进行FFT一个至关重要的细节是缩放。由于是定点运算FFT蝶形运算中的旋转因子乘法可能导致结果溢出。因此库函数提供了FFT_SCALE_RESULTS_BY_N选项。选择此选项后FFT的最终结果会被除以点数N从而保证输出值仍在Q15范围内。这意味着如果你想得到与MATLAB等浮点FFT工具在幅度上可比较的结果需要将DSP的输出结果再乘以N。另外FFT输出结构dfr16_sInplaceCRFFT的设计是为了节省内存。它将实数序列的N点FFT结果共N个复数点以压缩形式存储z[0]和z[N/2]是纯实数单独存放为Frac16z[1]到z[N/2-1]这N/2-1个复数点则依次存放实部和虚部。在访问结果时需要按照这个约定进行解包。6. 从理论到产品的思维跨越回顾整个流程从QEDesign Lite中填入几个频率参数到DSP芯片上流淌出经过净化的信号这中间每一个环节都要求工程师兼具信号处理理论知识和嵌入式实战技能。工具QEDesign Lite, Processor Expert极大地提升了效率但它们不是黑盒。理解系数文件的格式、内存管理的机制、定点数的局限才能在这些工具出错或结果不符合预期时有能力进行深度调试和修正。我个人在多年的项目中体会最深的一点是仿真永远不能完全替代硬件测试。在PC上用浮点数仿真完美的滤波器一到DSP上可能就因为溢出或量化误差而表现异常。因此建立一个简单的硬件测试回路至关重要——例如用DAC输出滤波后的信号用示波器或频谱仪观察或者用已知频率的正弦波作为输入在代码中打印输出序列的幅值进行验证。这种“设计-实现-测试-迭代”的闭环是确保嵌入式信号处理项目成功的唯一路径。最后关于选择IIR还是FIR除了理论上的相位和稳定性考量在DSP56800/E这样的定点平台上还需要额外权衡计算精度。IIR的递归结构对系数量化误差更敏感可能引发极限环振荡或频率响应漂移。对于要求非常严格的线性相位或需要确保在任何情况下都稳定的应用即使计算量大一些我也会更倾向于选择FIR。而对于计算资源极其紧张、且相位要求不高的场景如音频均衡经过精心设计和测试的IIR则是更经济的选