1. 项目概述:在DSP56307/11上驾驭EFCOP硬件滤波器
如果你正在基于Motorola(现NXP)的DSP56300系列芯片开发实时信号处理应用,比如音频编解码、通信调制解调或主动噪声控制,那么你很可能正与计算密集型滤波算法“搏斗”。传统的纯软件滤波实现,即便用汇编优化,在需要高吞吐量、低延迟的场合也常常捉襟见肘。这时,芯片内置的增强型滤波协处理器(Enhanced Filtering Coprocessor, EFCOP)就成了你的“性能倍增器”。
EFCOP本质上是一个专为滤波运算设计的硬件加速单元,它拥有独立的乘累加(MAC)单元、专用的系数与数据存储器,能与DSP56300核心并行工作。这意味着,当核心在处理逻辑、控制流或其他算法时,EFCOP可以同时在后台高效地完成FIR、IIR乃至自适应滤波的计算,将整体处理能力提升近一倍。然而,官方手册往往偏重寄存器描述,如何用高级语言(如C)高效、正确地驱动这块硬件,并融入实际项目,资料却相对零散。
本文将聚焦于DSP56307和DSP56311这两款经典芯片,以TASKING C编译器为工具,手把手带你完成EFCOP从零到一的编程实战。我们不只讲“怎么做”,更会深入剖析“为什么这么做”,涵盖初始化模式的选择逻辑、DMA与中断机制的数据搬运策略,并最终通过两个完整的工程案例——标准FIR滤波器与LMS自适应滤波器——展示如何将EFCOP的强悍算力转化为你项目中的实际性能优势。无论你是正在评估该平台,还是已经上手但被EFCOP的配置困扰,这篇文章都将提供可直接复现的代码框架和避坑指南。
2. EFCOP架构与编程模型深度解析
在动手写代码之前,我们必须先理解EFCOP在芯片内的“地理位置”和工作原理。这就像使用一个外设,你得先看懂它的数据手册和内存映射图。
2.1 核心资源:双内存库与寄存器组
EFCOP的成功,很大程度上归功于其独立且结构化的存储体系。它并非直接共享核心的X、Y内存,而是拥有自己映射到特定地址区间的两块专属内存:
- 滤波器系数存储器:位于Y内存空间底部,地址范围
Y:$0000到Y:$0FFF,共4K字(24位)。这块内存被称为FCM,专门用于存放滤波器的系数。在FIR滤波中,这就是你的w0, w1, ..., wN-1。 - 滤波器数据存储器:位于X内存空间底部,地址范围
X:$0000到X:$0FFF,同样是4K字。这块内存被称为FDM,是一个循环缓冲区,用于存放最新的N个输入数据样本x(k), x(k-1), ..., x(k-N+1)。
这种设计实现了哈佛架构的精髓:在一个时钟周期内,EFCOP可以同时从FCM取一个系数,从FDM取一个数据,然后由内置的FMAC单元完成一次24x24位的乘累加运算。对于N阶FIR滤波,理想情况下仅需N个时钟周期即可完成一次输出计算,效率极高。
除了内存,EFCOP通过一组内存映射的I/O寄存器与核心进行控制和状态交互。关键寄存器如下表所示:
| 寄存器名称 | 地址 (Y内存) | 主要功能 |
|---|---|---|
| FDIR | $FFFFB0 | 滤波器数据输入寄存器。核心或DMA将待滤波的新样本写入此处。 |
| FDOR | $FFFFB1 | 滤波器数据输出寄存器。滤波完成后,结果从此处读出。 |
| FKIR | $FFFFB2 | 滤波器K常数输入寄存器。在自适应滤波模式下,用于输入误差常数2μe(k)来触发系数更新。 |
| FCNT | $FFFFB3 | 滤波器计数寄存器。设置为滤波器阶数N-1。 |
| FCSR | $FFFFB4 | 滤波器控制/状态寄存器。最重要的寄存器,用于使能EFCOP、选择工作模式、查看状态位(如FDIBE、FDOBF)等。 |
| FACR | $FFFFB5 | 滤波器ALU控制寄存器。设置算术模式(如饱和、舍入方式、16/24位模式)。 |
| FDBA | $FFFFB6 | 滤波器数据缓冲区基地址。指向FDM缓冲区的起始地址。 |
| FCBA | $FFFFB7 | 滤波器系数缓冲区基地址。指向FCM缓冲区的起始地址。 |
| FDCH | $FFFFB8 | 滤波器抽取/通道寄存器。用于配置多通道模式或输出抽取因子。 |
注意:DSP56311的EFCOP拥有更大的10K字FCM和FDM,但其编程模型和寄存器映射与DSP56307完全兼容,本文代码可直接移植。
2.2 工作流程与数据流
理解EFCOP的滤波过程,关键在于把握其数据驱动的特性。其标准工作流程如下:
- 初始化:核心配置好所有寄存器,并将滤波器系数按逆序写入FCM(即
w[N-1]放在最低地址)。同时,通过设置FDBA和FCNT,EFCOP知道了FDM这个循环缓冲区的起点和长度。 - 馈入数据:当一个新的输入样本
x(k)被写入FDIR寄存器后,EFCOP自动将其存入FDM中FDBA所指向的位置(覆盖最旧的数据),并更新FDBA指针(自动循环)。 - 触发计算:写入FDIR这个动作本身,就触发了EFCOP开始一次滤波计算。它从FCBA指向的地址开始,依次取出系数,并与FDM中对应的历史数据相乘累加。
- 产出结果:计算完成后,结果被放入FDOR寄存器,并置位FDOBF(输出缓冲区满)状态位。核心或DMA可以读取FDOR获取结果,读取操作会清除FDOBF位。
- 准备下一次:同时,FDIBE(输入缓冲区空)位被置位,表示FDIR已准备好接收下一个样本。如此循环往复。
整个过程,核心的角色从繁重的乘累加计算中解放出来,只需负责“喂数据”和“取结果”。而“喂”和“取”的效率,就决定了EFCOP性能能被发挥出几成。
2.3 TASKING C环境下的关键配置
要让C编译器认识并使用EFCOP,需要进行一些特殊的声明和工程配置,这是很多新手的第一道坎。
内存数组声明:FCM和FDM必须在C源文件中以特定名称和属性声明,以确保链接器能将其正确放置到EFCOP专用的内存区域。
#include <reg56307.h> // 必须包含,定义了所有寄存器地址 // 声明EFCOP系数缓冲区,必须位于Y内存底部,且为循环缓冲区 _fract _Y _circ FCM_buffer[FILTER_LENGTH]; // 声明EFCOP数据缓冲区,必须位于X内存底部,且为循环缓冲区 _fract _X _circ FDM_buffer[FILTER_LENGTH];这里的_fract是TASKING编译器用于定点分数数据类型的限定符,_circ则告诉编译器该数组需要按循环缓冲区方式寻址。数组名FCM_buffer和FDM_buffer必须与链接器描述文件(如Efcopdma.dsc)中的段名严格对应。
工程配置:在TASKING EDE集成开发环境中,必须使用专为EFCOP修改的描述文件(Efcopdma.cpu,.dsc,.mem),而不是默认的EVB文件。在项目链接器选项中,需要指定“使用项目特定的定位器控制文件”,并填入efcopdma。这一步确保了上述数组能被链接到X:$0000和Y:$0000开始的地址。
寄存器访问:reg56307.h头文件通过联合体(union)和结构体(struct)定义了所有外设寄存器,使得我们可以像操作普通变量一样操作寄存器位。
// 示例:配置FACR寄存器,启用饱和与特定舍入模式 FACR.B.FSM = 1; // 启用饱和 (Filter Saturation Mode) FACR.B.FRM = 1; // 设置为二进制补码舍入 (Two‘s complement rounding) FACR.B.FSA = 0; // 使用24位算术模式 // 或者,一次性写入整个寄存器(更高效) FACR.I = 0x000035;使用.B位域访问代码可读性高,但生成的指令较多;使用.I整型访问直接写入整个寄存器,效率更高,适合在初始化完成后使用。
3. 数据搬运策略:轮询、DMA与中断的抉择
EFCOP计算很快,但如果数据供给和结果取回不及时,它就会“饿着”或“堵着”,性能无从谈起。数据搬运机制的选择,直接决定了系统整体效率和核心的占用率。
3.1 轮询:简单但低效的验证手段
轮询是最直接的方式:核心不断查询FCSR中的FDIBE或FDOBF位,根据状态决定何时读写数据。
// 轮询方式向FDIR写入一个样本 while (FCSR.B.FDIBE == 0) { /* 空循环等待 */ } // 等待FDIR空 FDIR = input_sample; // ... EFCOP计算 ... // 轮询方式从FDOR读取一个结果 while (FCSR.B.FDOBF == 0) { /* 空循环等待 */ } // 等待FDOR满 output_sample = FDOR;优点:逻辑简单,易于理解和调试,适合在算法验证或功能测试阶段使用。致命缺点:核心在等待期间被完全阻塞,无法执行其他任何任务,严重浪费了DSP强大的处理能力。在实际产品中,应尽量避免使用。
3.2 DMA:解放核心的“自动驾驶”模式
直接内存访问是EFCOP的最佳搭档。DMA控制器可以在不打扰核心的情况下,自动在内存和外设(此处是EFCOP)之间搬运数据。
配置DMA通道0(输入到FDIR):由于FDIR是一个4字深的FIFO,我们可以配置DMA进行二维(2-D)传输,每次触发(FDIBE=1时)连续写入4个样本,最大化总线利用率。
// 假设输入数据在input[]数组中 DCO0 = 0x018003; // 计数器:高16位=0x18(24次行传输-1),低6位=0x03(每行4字-1) DSR0 = (int*)input; // 源地址:输入数组首地址 DDR0 = (int*)&FDIR; // 目的地址:EFCOP数据输入寄存器 DOR0 = 1; // 源地址偏移:每次传输后+1 DCR0.I = 0x14AA04; // 关键控制字配置 /* DCR0解析: DE=0 (先禁用), DIE=0 (传输完成不中断), DTM=010 (2-D行传输,完成后禁用DE), DRS=10101 (触发源:MDRQ11,即EFCOP的FDIBE), DAM=100000 (源:2-D模式,使用DOR0偏移;目的:不更新地址), DDS=01 (目的在Y内存), DSS=00 (源在X内存) */配置DMA通道1(从FDOR输出):FDOR是单字寄存器,因此使用一维(1-D)传输即可。
DCO1 = OUTPUT_LENGTH - 1; // 计数器:传输样本数-1 DSR1 = (int*)&FDOR; // 源地址:EFCOP数据输出寄存器 DDR1 = (int*)output; // 目的地址:输出数组首地址 DCR1.I = 0x0CB2C1; // 关键控制字配置 /* DCR1解析: DTM=001 (1-D字传输), DRS=10110 (触发源:MDRQ12,即EFCOP的FDOBF), DAM=101100 (源:不更新地址;目的:每次传输后地址+1) */核心的职责:配置好DMA和EFCOP后,核心只需一句FCSR.B.FEN = 1;启动EFCOP,然后就可以去执行其他任务(如逻辑控制、其他算法模块)。通过查询DMA状态寄存器(DSTR)的DTD位或配置DMA完成中断,可以知道滤波任务何时全部完成。
实操心得:务必注意,DMA控制器无法访问EFCOP专用的底部4K内存区(FCM和FDM所在区域)。所有用于DMA传输的源和目的数据缓冲区,必须放在
X:$1000和Y:$1000以上的地址空间。
3.3 中断:响应式处理的平衡之选
中断方式介于轮询和DMA之间。核心不主动查询,而是由EFCOP在FDIR空或FDOR满时产生中断请求,核心响应中断进行数据读写。
// 在C中声明中断服务例程(ISR) void _long_interrupt(53) output_isr(void) { // 53对应FDOBF中断向量号 *output_ptr++ = FDOR; // 从FDOR读取数据 // 这里可以添加其他处理,如计算误差(用于自适应滤波) }配置步骤:
- 在IPRP寄存器中设置EFCOP中断优先级(例如
IPRP.B.E0L = 2;)。 - 清除状态寄存器SR中的相应中断屏蔽位(例如
asm(“bclr #8,SR”);和asm(“bclr #9,SR”);)。 - 在FCSR中使能所需的中断(
FCSR.B.FDOIE = 1使能输出中断)。
适用场景:当数据吞吐量不是极端高,且需要在每次滤波输出后立即进行一些处理(如自适应滤波中的误差计算和系数更新)时,中断方式是理想选择。它比轮询高效,又比纯DMA方式更灵活,允许核心在每次样本处理后介入。
4. 初始化模式:State Initialization的玄机
EFCOP的FPRC位控制着其开始滤波计算的“起跑线”,这个选择直接影响前N-1个输出的正确性,必须根据应用场景谨慎选择。
4.1 初始化数据模式
当FCSR.B.FPRC = 0时,EFCOP工作于初始化模式。在此模式下,EFCOP会等待FDM缓冲区被填满(即写入了前N个样本)后,才产生第一个有效的输出y(N-1)。
工作原理:上电或复位后,FDM中的内容是随机的。初始化模式相当于让EFCOP进行了一次“冷启动”,它用前N个输入样本完全覆盖了FDM中的随机旧数据,从而确保第一个输出是基于已知的、完整的历史数据窗计算出来的。代码体现:在启动DMA或开始写入数据前,通常需要手动将FDM缓冲区清零或初始化为某个已知值(如0)。适用场景:绝大多数情况下的推荐选择。适用于流式数据处理,如音频滤波、通信信道均衡等,可以保证输出序列从一开始就是正确的。
4.2 非初始化数据模式
当FCSR.B.FPRC = 1时,EFCOP工作于非初始化模式。在此模式下,一旦第一个样本被写入FDIR,EFCOP立即开始计算并输出。它使用FDM中当前已有的值(可能是上次运行残留的,或未初始化的随机值)作为历史数据。
潜在风险:如果FDM中的旧数据未知,那么前N-1个输出将是错误的,是旧数据与新输入样本的混合结果。正确用法:必须在使能EFCOP之前,手动将整个FDM缓冲区初始化为已知状态(例如全零)。这样,EFCOP从第一个样本开始的计算就是基于全零历史数据,其输出在初始阶段是逐步建立起来的,虽然前几个输出能量不足,但数学上是定义明确的。适用场景:适用于连续不间断的数据处理,且你希望滤波器的状态(FDM中的历史数据)在两次使能之间得以保持。例如,在一种低功耗设计中,你可能周期性地关闭EFCOP以省电,唤醒后希望它从上次停止的状态继续滤波,而不是重新初始化。这时就需要使用非初始化模式,并在每次唤醒后确保FDM状态是有效的。
注意事项:在自适应滤波应用中,滤波器系数本身会不断更新,因此对初始状态的敏感性可能降低。但即便如此,为了结果的可预测性,也强烈建议在非初始化模式下,显式地将FDM初始化为零。
5. 实战案例一:标准FIR滤波器的完整实现
我们以一个20阶、处理100个样本的FIR低通滤波器为例,展示使用DMA进行全自动数据搬运的完整流程。项目目录结构应包含efcop_fir文件夹,其中有fir.c源文件以及tvecs/(存放测试向量)和out/(存放输出)子目录。
5.1 主程序流程与代码拆解
整个程序的流程图清晰展示了核心、DMA和EFCOP的协作关系:核心是“指挥官”,负责初始化和启动;DMA是“搬运工”,负责数据流转;EFCOP是“计算员”,专心致志做乘累加。
// --- 1. 关键常量与缓冲区声明 --- #define FIR_LENGTH 20 #define INPUT_LENGTH 100 _fract _Y _circ FCM_buffer[FIR_LENGTH]; _fract _X _circ FDM_buffer[FIR_LENGTH]; _fract _X input[INPUT_LENGTH]; _fract _X output[INPUT_LENGTH - FIR_LENGTH + 1]; // 输出样本数 // --- 2. EFCOP初始化 --- void init_efcop_for_fir() { FCSR.B.FEN = 0; // 先禁用EFCOP FCNT = FIR_LENGTH - 1; // 滤波器阶数-1 FDBA = FDM_buffer; // 数据缓冲区基地址 FCBA = FCM_buffer; // 系数缓冲区基地址 // 配置ALU控制:24位模式,收敛舍入,不饱和 FACR.I = 0x000000; // 配置工作模式:单通道,实FIR,初始化模式,非自适应 FCSR.I = 0x000000; // FPRC=0 (初始化模式), FOM=00 (实FIR), FADP=0 (非自适应) // **逆序**写入滤波器系数到FCM! FCM_buffer[19] = 0.1825762; // w[0] 实际对应h[19] FCM_buffer[18] = 0.1500447; // w[1] 对应h[18] // ... 写入所有20个系数 FCM_buffer[0] = -0.0215175; // w[19] 对应h[0] // 可选:初始化FDM缓冲区为0(在初始化模式下是良好实践) for(int i=0; i<FIR_LENGTH; i++) FDM_buffer[i] = 0; }系数逆序存放的原因:这是EFCOP硬件结构决定的。在计算卷积和y[k] = Σ h[i]*x[k-i]时,EFCOP的地址生成器从FCBA开始递增取系数,同时从FDBA开始递减取数据。为了匹配h[0]与x[k]相乘,h[1]与x[k-1]相乘的顺序,必须将系数数组h逆序存放为w。
5.2 DMA配置与启动
初始化完成后,配置两个DMA通道分别负责输入和输出。
void setup_dma_for_fir() { // DMA通道0:从input[]传输数据到FDIR (2-D传输,每次4字) DCO0 = 0x018003; // 传输 (24+1)行 * (3+1)字 = 100个样本 DSR0 = (int*)input; DDR0 = (int*)&FDIR; DOR0 = 1; DCR0.I = 0x14AA04; // 关键配置见上文解析 // DMA通道1:从FDOR传输数据到output[] (1-D传输,每次1字) DCO1 = (INPUT_LENGTH - FIR_LENGTH); // 输出样本数-1 DSR1 = (int*)&FDOR; DDR1 = (int*)output; DCR1.I = 0x0CB2C1; // 关键配置见上文解析 }启动与等待:配置好DMA后,使能DMA通道和EFCOP,核心即可抽身。
// 启动顺序:先启动输出DMA,再启动输入DMA,最后启动EFCOP // 这可以确保输出端已就绪,避免数据丢失 DCR1.B.DE = 1; // 使能DMA通道1 (输出) DCR0.B.DE = 1; // 使能DMA通道0 (输入) FCSR.B.FEN = 1; // 使能EFCOP,开始滤波! // 核心此时可以执行其他任务 // ... // 等待DMA通道1(输出)传输完成 while ( DSTR.B.DTD1 == 0 ) { /* 在此处执行其他并行任务 */ }5.3 结果验证与调试技巧
程序运行后,输出文件out/output.txt应与参考文件tvecs/output.txt逐比特一致。
常见问题排查:
- 无输出或输出全零:首先检查FCSR.B.FCONT位。如果为1,表示发生了EFCOP与核心对共享内存(FCM/FDM)的访问冲突。确保在EFCOP运行期间,核心没有去读写
FCM_buffer或FDM_buffer数组。 - 输出结果错误:
- 系数顺序:确认系数是否按逆序存入
FCM_buffer。 - 数据格式:检查输入数据文件格式是否为TASKING C可识别的十六进制或定点数格式。
_fract类型通常对应24位有符号分数。 - 初始化模式:确认FPRC设置是否符合预期。如果使用初始化模式,前
FIR_LENGTH-1个输出是无效的(EFCOP在填充缓冲区),你的输出数组应该从第FIR_LENGTH个元素开始有意义。
- 系数顺序:确认系数是否按逆序存入
- DMA传输不启动:
- 检查DCRn中的DRS字段是否正确(输入为10101,输出为10110)。
- 确认EFCOP的FEN位已置1,否则FDIBE/FDOBF永远不会被置位,无法触发DMA请求。
- 在调试器中,观察DSTR寄存器的DTD位是否变化,以及FCSR中的FDIBE/FDOBF位是否在数据写入/读出后正常翻转。
6. 实战案例二:LMS自适应滤波器实现
自适应滤波器能够根据期望信号与实际输出的误差,自动调整滤波器系数,广泛应用于系统辨识、回声消除、信道均衡等领域。在EFCOP上实现LMS算法,需要将滤波计算和系数更新两个过程结合起来。
6.1 算法原理与EFCOP适配
LMS算法的核心是两条公式:
- 滤波:
y(k) = w^T(k) * x(k)(与标准FIR相同) - 系数更新:
w(k+1) = w(k) + 2 * μ * e(k) * x(k),其中e(k) = d(k) - y(k)
EFCOP的巧妙之处在于,其自适应FIR模式(FCSR.B.FADP = 1)可以硬件化第二步。当我们将误差常数Ke = 2μe(k)写入FKIR寄存器时,EFCOP会自动用当前FDM中的数据向量x(k)乘以Ke,然后累加到FCM中的系数向量w(k)上,完成一次系数更新。
因此,实现流程变为:
- DMA将输入样本
x(k)送入FDIR,触发EFCOP计算y(k)。 - EFCOP计算完成,输出
y(k)到FDOR,并产生中断(FDOBF)。 - 在中断服务例程中,核心读取
y(k),计算误差e(k) = d(k) - y(k)和Ke = 2μe(k)。 - 核心将
Ke写入FKIR,触发EFCOP内部硬件完成系数更新w(k+1) = w(k) + Ke * x(k)。 - 更新完成后,EFCOP自动等待下一个
x(k+1)输入,循环继续。
6.2 混合DMA与中断的编程实现
这里我们采用DMA负责高效的输入数据搬运(x(k)),而用中断来处理需要核心介入的误差计算和系数更新触发。
// 全局变量与中断服务例程 _fract mu2 = 0.02; // 2*μ,收敛因子需要仔细选择 _fract *d_ptr, *y_ptr, *e_ptr; // 指向期望信号、输出、误差的指针 unsigned int sample_count = TOTAL_SAMPLES; void _long_interrupt(53) lms_isr(void) { // FDOBF中断 _fract Ke; *y_ptr = FDOR; // 1. 读取滤波输出y(k) // 2. 计算误差 e(k) = d(k) - y(k) *e_ptr = (*d_ptr++) - (*y_ptr++); sample_count--; if (sample_count == 0) { FCSR.B.FDOIE = 0; // 处理完所有样本,关闭输出中断 } else { // 3. 计算并写入更新常数,触发系数更新 Ke = mu2 * (*e_ptr++); // Ke = 2μ * e(k) FKIR = Ke; // 写入FKIR,EFCOP自动开始系数更新 } }主程序配置要点:
- 模式设置:
FCSR寄存器中,除了使能自适应模式 (FADP=1),还需使能输出中断 (FDOIE=1)。 - DMA配置:只需配置DMA通道0用于输入。输出由中断服务例程处理,无需DMA。
- 启动顺序:先使能EFCOP中断并设置优先级,然后使能EFCOP (
FEN=1),最后启动输入DMA。一旦DMA开始送入第一个样本,整个自适应循环就开始了。 - 非初始化模式:在自适应滤波中,我们通常希望滤波器系数从初始值开始连续自适应。因此,设置
FPRC=1(非初始化模式),并务必在初始化时将FDM缓冲区清零,以避免初始输出受到随机历史数据影响。
6.3 参数选择与稳定性考量
LMS算法的性能高度依赖于步长参数μ。
μ过大:收敛快,但稳态误差大,甚至可能发散。μ过大:收敛慢,但稳态误差小,更稳定。
一个经验法则是μ < 1 / (N * P_x),其中N是滤波器阶数,P_x是输入信号的功率。在实际中,需要通过仿真或实验来确定一个合适的值。在EFCOP实现中,mu2变量存储的是2μ,注意换算。
调试建议:
- 将每次迭代的误差
e(k)输出到文件或绘图,观察其是否随时间衰减,这是算法收敛的直接证据。 - 对比EFCOP输出的系数
w(k)与MATLAB或Python仿真得到的系数,在稳态下它们应该非常接近。 - 注意定点数精度问题。
_fract是24位1.23格式的定点数(1位符号,23位小数)。在计算Ke = 2μ * e(k)时,确保mu2和e(k)的乘积不会严重溢出,必要时可进行缩放。
7. 高级话题与性能优化技巧
7.1 多通道滤波处理
EFCOP支持多通道模式(FCSR.B.FMLC设置),可以在多个独立的滤波器之间快速切换。这对于需要同时处理多个独立信号流的应用非常有用,例如立体声音频处理或多载波通信。在多通道模式下,FCM和FDM内存被均匀划分为多个段,每个通道有自己的系数集和数据缓冲区。通过配置FDCH寄存器,可以设定通道数。在数据搬运时,需要按通道交错组织数据,EFCOP会根据当前通道索引自动选择对应的内存段。
7.2 16位算术模式与位精确实现
在一些标准兼容性要求严格的场景(如文中提到的GSM EFR/AMR语音编解码器中的Residu函数),需要确保计算结果与标准测试向量位精确一致。这通常要求使用16位算术和特定的舍入/饱和规则。
- 启用16位模式:设置
FACR.B.FSA = 1。在此模式下,EFCOP内部使用16位数据进行乘加,但输入输出寄存器仍是24位,高8位被忽略或填充。 - 舍入与饱和:设置
FACR.B.FRM选择舍入方式(如二进制补码舍入),设置FACR.B.FSM = 1使能饱和。特别注意:为了与核心计算位精确匹配,核心也需要通过设置状态寄存器SR的相应位来启用相同的饱和和舍入模式(asm(“bset #20,SR”);和asm(“bset #21,SR”);)。 - DMA配置的坑:在16位算术模式下,如果使用类似
DCR0.I = 0x94AA04;的赋值,编译器可能会因为16位模式而截断高8位。解决方案是使用内联汇编直接写入:_asm(“movep #$94AA04,x:<<$FFFFEC”);。
7.3 核心与EFCOP的协同调度
为了榨干芯片性能,需要精心设计核心与EFCOP的任务分工。
- 计算密集型滤波:让EFCOP全权负责。核心仅做初始化和启动,然后处理其他任务。
- 自适应滤波等混合任务:EFCOP负责滤波和系数更新中的向量运算,核心负责标量误差计算和逻辑控制。利用中断实现同步。
- 避免资源冲突:核心在EFCOP运行期间,绝对不要访问FCM和FDM区域(
X:$0000-$0FFF,Y:$0000-$0FFF),否则会触发FCONT冲突,导致不可预知的结果。所有与EFCOP交换的数据都应通过FDIR/FDOR,或存放在4K以上的内存区域。
8. 常见问题与故障排除实录
在实际开发中,你可能会遇到以下问题:
程序编译通过,但运行后无输出或EFCOP不工作。
- 检查:确认工程链接器设置使用了正确的
efcopdma.dsc等描述文件,而不是默认的56307evm.dsc。 - 检查:在调试器中单步执行,查看FCSR寄存器的FEN位是否成功置1。检查DCRn寄存器的DE位(DMA使能)是否置1。
- 检查:确认
FCM_buffer和FDM_buffer数组的地址是否确实在X:$0000和Y:$0000附近(查看map文件)。
- 检查:确认工程链接器设置使用了正确的
输出结果与预期不符,或全是噪声。
- 检查:滤波器系数是否按逆序加载到
FCM_buffer。这是最容易出错的一步。 - 检查:FCNT寄存器设置是否正确(滤波器阶数N-1)。
- 检查:初始化模式(FPRC)。如果你期望从第一个样本就开始有效输出,却使用了初始化模式,那么前N-1个输出是无效的。反之亦然。
- 检查:输入数据文件的格式和值是否正确加载到了
input[]数组。
- 检查:滤波器系数是否按逆序加载到
自适应滤波器不收敛,误差越来越大。
- 检查:步长参数
mu2(2μ) 是否过大。尝试将其减小一个数量级再测试。 - 检查:在中断服务例程中,计算
Ke时是否使用了正确的mu2和e(k)。确保e(k)的计算是d(k) - y(k),顺序不能反。 - 检查:是否在非初始化模式下忘记了将FDM缓冲区初始化为零,导致初始历史数据污染了输出和误差计算。
- 检查:步长参数
使用DMA时,数据传输似乎没有完成。
- 检查:DCOn寄存器设置是否正确。对于1-D传输,
DCO = 传输字数 - 1。对于2-D传输,需要仔细计算行数和列数。 - 检查:DCRn寄存器中的DRS(DMA请求源)是否设置正确(FDIBE对应10101,FDOBF对应10110)。
- 检查:在调试器中观察DSTR寄存器的DTDn位,看DMA传输是否完成。观察FCSR的FDIBE/FDOBF位,看EFCOP是否在正常产生数据请求。
- 检查:DCOn寄存器设置是否正确。对于1-D传输,
在16位模式下,结果与核心软件计算不一致。
- 检查:核心和EFCOP的舍入(FRM)和饱和(FSM)模式设置是否完全一致。
- 检查:数据在存入
input[]数组或从output[]数组读出时,是否发生了意外的符号扩展或截断。确保所有数据都以正确的16位格式处理。
驾驭EFCOP就像为你的DSP项目装备了一台专用的滤波引擎。初期的配置虽然繁琐,但一旦打通,其带来的性能提升是质的飞跃。从固定的FIR到自适应的LMS,从简单的轮询到高效的DMA+中断,EFCOP的灵活性足以应对大多数实时滤波挑战。最关键的是理解其数据驱动的架构、内存映射的规则以及核心与协处理器之间清晰的责任边界。希望这篇结合了原理、代码和实战经验的指南,能帮助你顺利地将EFCOP集成到你的下一个DSP563xx项目中,让滤波计算从此不再是性能瓶颈。