TMS320C5000 DMA高级应用实战:数据排序、程序分页与乒乓缓冲详解

发布时间:2026/7/23 17:23:00
TMS320C5000 DMA高级应用实战:数据排序、程序分页与乒乓缓冲详解 1. 项目概述解锁TMS320C5000 DMA的工程级应用在嵌入式DSP系统开发中CPU的算力是宝贵的核心资源。当系统需要处理高速、连续的数据流时如果让CPU亲自去搬运每一个数据样本无异于让一位顶尖的数学家去干快递员的活效率低下且浪费才华。这时直接内存访问DMA控制器就扮演了那个高效的“数据搬运工”角色。它独立于CPU运行能在内存与内存、内存与外围设备之间直接传输数据让CPU得以专注于算法运算。TMS320C5000系列DSP如经典的C5410、C5402等集成了一个功能强大的多通道DMA控制器。它远不止是一个简单的搬运工通过其灵活的地址修改、自动重载和同步触发机制我们可以实现许多高级的数据流管理策略。本文不是一份枯燥的寄存器手册翻译而是基于一份经典的TI应用报告SPRA641结合我多年在通信和音频处理项目中的实战经验为你深入剖析DMA的三个核心高级应用数据排序、程序分页和乒乓缓冲。我会带你从“为什么要这么做”的原理出发一步步拆解寄存器配置的每一个比特位最后分享那些在官方文档里找不到的调试心得和避坑指南。无论你是正在优化一个现有DSP项目的性能还是为新产品选型评估这篇文章都能提供直接的、可落地的参考。2. DMA控制器架构与核心寄存器精讲在动手写代码之前我们必须先摸清手中“武器”的构造。TMS320C5000的DMA控制器是一个高度可编程的模块其配置精髓全在于一组内存映射寄存器。理解这些寄存器的设计哲学和工作机制是玩转后续所有高级应用的基础。2.1 子地址访问机制高效配置的钥匙C5000的DMA寄存器数量众多如果全部直接映射到内存空间会非常浪费。TI采用了一种巧妙的子地址Sub-bank访问机制。你可以把它想象成一个带索引的文件柜DMSA寄存器就是索引号而两个数据寄存器DMSDI和DMSDN则是你存取文件的手。DMSA子地址寄存器这是一个指针。你需要先向DMSA写入一个子地址值例如0x00代表通道0的源地址寄存器告诉DMA控制器“我接下来要操作的是哪个寄存器”。DMSDI带自动递增的子地址数据寄存器向DMSDI读写数据后DMSA中的子地址会自动加1。这是连续配置多个寄存器时的首选。例如配置一个DMA通道通常需要按顺序设置源地址、目的地址、元素计数等寄存器使用DMSDI可以让你用一条stlm指令接一条stlm指令快速完成无需反复设置DMSA。DMSDN不带自动递增的子地址数据寄存器向DMSDN读写数据不会改变DMSA的值。当你只需要单独访问或修改某一个特定寄存器时比如在中断服务程序中只修改目的地址就用DMSDN。实操心得在初始化阶段我几乎总是使用DMSDI。我会在代码开头用stm指令一次性设置好DMSA的起始子地址然后像操作一个数组一样用stlm指令将配置参数依次“推入”DMSDI。这种方式代码紧凑执行效率高。而DMSDN则更多地用于运行时的动态调整。2.2 通道核心寄存器组定义一次传输每个DMA通道都拥有独立的一套核心寄存器它们共同定义了一次数据传输任务的“蓝图”。DMSRCn源地址寄存器。数据从哪里来可以是内部DARAM/SARAM、外部存储器甚至是某些外设如McBSP的DRR寄存器。DMDSTn目的地址寄存器。数据到哪里去同样支持多种内存和外设空间。DMCTRn元素计数寄存器16位。这里有个关键细节你需要写入的是期望传输的元素数量减1。如果你想传输100个数据就写入99。这是因为计数器是从设定值向下计数到0的。最大可传输元素数为65536个。DMSFCn同步选择与帧计数寄存器。这个寄存器功能复合DSYN[3:0]同步事件选择指定什么事件来触发一次DMA传输。可以是外部中断、串口接收/发送事件、定时器中断等。设为0表示无同步立即启动或由CPU手动启动。FRAME COUNT帧计数8位定义有多少“帧”。同样写入值是帧数减1。一个帧包含多个元素。DMA支持“多帧”传输模式这是实现数据排序等复杂操作的关键。最大帧数为256。DBLW双字模式置1时每个“元素”是32位双字置0时是16位单字。注意在元素和帧计数中一个“元素”指的是一个DBLW定义的数据单位。DMMCRn传输模式控制寄存器。这是DMA的“大脑”决定了传输的行为模式。CTMOD传输计数模式0 多帧模式1 自动缓冲模式ABU。我们讨论的应用主要基于多帧模式。SIND/DIND源/目的地址索引模式这是DMA的“灵魂”所在它决定了每次传输后源或目的地址如何变化。常见模式有000b不修改固定地址。001b后递增1线性增加。010b后递减1线性减少。101b后递增偏移量由元素索引寄存器DMIDX0/1提供。110b后递增偏移量由帧索引寄存器DMFRI0/1提供。DMS目的/源空间选择选择目的和源是在程序空间还是数据空间。对于C5000访问程序空间通常需要配合扩展页寄存器。AUTOINIT自动初始化这是一个极其有用的功能。当一次块传输所有元素和帧完成后如果此位置1DMA会自动从全局重载寄存器DMGSA DMGDA DMGCR DMGFR中重新加载源地址、目的地址、元素计数和帧计数然后立即开始新一轮传输。这是实现乒乓缓冲和循环数据采集的核心。避坑指南务必注意元素计数和帧计数的“减1”规则。这是新手最常见的错误之一会导致传输的数据量总是比你预期的少一个。我习惯在代码中这样写stm #(BUFFER_SIZE-1) DMCTR0 将BUFFER_SIZE定义为宏这样意图更清晰。2.3 全局重载与地址索引寄存器实现复杂模式为了实现循环、乒乓等高级数据流DMA提供了额外的“辅助”寄存器。全局重载寄存器DMGSA DMGDA DMGCR DMGFR当AUTOINIT使能且一次传输完成后DMA控制器会自动从这四个寄存器中读取新的配置覆盖对应通道的DMSRC、DMDST、DMCTR和DMSFC帧计数部分。这让你可以预设好下一块数据的来源和去向实现无缝衔接。元素/帧索引寄存器DMIDX0/1 DMFRI0/1当SIND或DIND模式设置为使用索引时这些寄存器提供了地址修改的步进值。例如设置DIND101b使用DMIDX0并设置DMIDX04那么每传输一个元素后目的地址就增加4个单元字或双字。负值也是允许的这能实现地址的回绕或特殊跳转是数据排序算法的数学基础。扩展页寄存器DMSRCP DMDSTP对于C54x这类地址线有限的DSP要访问超过64K字的内存需要使用扩展页。这两个寄存器分别指定了源和目的地址所在的程序空间页号0-127。当DMS位设置为程序空间访问时实际的23位地址由{DMSRCP/DMDSTP DMSRC/DMDST}拼接而成。2.4 通道控制寄存器最后的开关DMPREC通道优先级与使能控制寄存器是DMA的总开关。在这里你可以DE[5:0]分别使能或禁用6个DMA通道。DPRC[5:0]设置每个通道的优先级0低1高。当多个通道同时请求时高优先级通道先服务。INTOSEL[1:0]选择DMA中断映射到CPU的哪个中断向量。因为CPU的中断线有限DMA中断是与其他外设中断复用的必须根据具体芯片型号的数据手册正确配置。FREE在仿真器调试时如果遇到断点此位决定DMA是否继续运行1继续0停止。一个至关重要的经验DMPREC必须是最后配置的寄存器在你通过DMSA/DMSDI小心翼翼地把所有通道参数都设置妥当之前绝对不要打开通道使能位DE。否则DMA可能会用半截子的、错误的配置立即开始传输导致数据错乱甚至系统崩溃。我的代码模板总是把stlm配置DMPREC的指令放在初始化序列的末尾。3. 应用一数据排序Data Sorting—— 重组数据流的艺术在很多信号处理算法中数据的排列方式直接影响访问效率和缓存命中率。例如一个来自ADC的连续数据流可能以“帧”的形式组织每帧包含多个通道的数据元素。原始存储顺序可能是[帧1元素A 帧1元素B 帧1元素C 帧2元素A 帧2元素B 帧2元素C...]。但我们的算法可能需要按通道处理即希望数据是[帧1元素A 帧2元素A 帧3元素A... 帧1元素B 帧2元素B...]。手动用CPU重排这些数据会消耗大量周期而DMA可以优雅地完成这个任务。3.1 排序原理与索引计算数据排序的核心是利用DMA的多帧传输模式和地址索引寄存器。我们通过精心计算元素索引DMIDX和帧索引DMFRI让目的地址在每次传输后按照我们想要的模式“跳转”。假设我们有一个数据块结构为N个帧每帧有M个元素。在源内存中数据是连续存放的帧优先。我们的目标是将其转置在目的内存中变成元素优先。关键公式直接记忆并应用元素索引DMIDX帧的数量N注意DMSFC.FRAME_COUNT中配置的值是N-1但计算索引时用N。帧索引DMFRI-元素索引 * 元素数量 - 1-(N * M - 1)。为什么是这个公式元素索引DMIDX我们希望同一个元素在不同帧中的位置在目的地址上是连续的。假设每个元素占1个字16位那么同一元素在相邻两帧中的地址差就是N个字因为目的内存是按元素优先排列的存完所有帧的第一个元素才存第二个元素。所以每传输完一个元素目的地址需要增加N以便为下一个帧的同一元素腾出位置。帧索引DMFRI当一帧内的所有M个元素都传输完毕后完成了一次“帧传输”DMA会应用帧索引。此时目的地址已经通过M次“增加N”的操作向前移动了N * M个位置。为了回到这一帧起始元素的下一个位置即下一个元素的起始地址我们需要一个巨大的负向跳转其值就是-(N * M - 1)。减1是因为地址已经停在最后一个位置需要回到下一个元素的开始。3.2 实战配置与代码解析让我们结合原文的例子4帧N4每帧4个元素M4。源数据在地址0x1000开始连续存放。目标是排序后存放到0x1F00。寄存器配置拆解基本参数DMCTR0 3因为M4 4-13DMSFC0.FRAME_COUNT 3因为N4 4-13DMSRC0 0x1000DMDST0 0x1F00地址修改模式DMMCR0SIND 001b源地址后递增1。因为源数据是连续的我们按顺序读即可。DIND 101b目的地址后递增使用DMIDX0寄存器提供的偏移量。CTMOD 0多帧模式。索引值计算DMIDX0 N 4DMFRI0 -(N * M - 1) -(4*4 -1) -15传输过程模拟传输帧1元素1到0x1F00 目的地址4 - 0x1F04。传输帧1元素2到0x1F04 目的地址4 - 0x1F08。... 传输完帧1元素4后目的地址0x1F0C。完成一帧应用帧索引0x1F0C (-15) 0x1F0D不对注意单位是字2字节计算时要用字节地址思考。实际上-15个字的偏移等于-30字节。0x1F0C字节地址为0x3E18减去300x1E等于0x3DFA即字地址0x1EFD。这正好是帧2元素1应该存放的位置0x1F00 1个字 0x1F02。这里原文计算似乎有笔误或简化。正确的理解是经过M次加N地址指针从起始点A移动到A N*(M-1)。应用帧索引-N*M1后指针回到A1。即从第一个元素的起始点移动到第二个元素的起始点。对于我们的例子起始0x1F00 加4三次后到0x1F0C 减15后到0x1EFD字地址即0x1F00的下一个字位置。这符合“元素优先”排列。对应的C代码片段基于原文main.cdma.element_count 3; // M-1 M4 dma.DMSFC.frame_count 3; // N-1 N4 dma.DMMCR.dma_src_addr_index_mode 1; // 001b 后递增1 dma.DMMCR.dma_dest_addr_index_mode 5; // 101b 后递增使用DMIDX0 // ... 其他配置汇编代码关键部分dma_datasort.asmld *AR0, B ; 从结构体获取DMSFC值包含帧计数 stlm B, DMSDI and #0ffh, B ; 提取低8位帧计数 add #1, B ; B 实际帧数 N frame_count 1 stl B, dmidx ; 保存到DMIDX0 mpyu dmidx, B ; B N * M (M已在T寄存器) sub #1, B, B ; B N*M - 1 neg B ; B -(N*M - 1) 得到帧索引 stl B, dmfri stm DMIDX0, DMSA mvdk dmidx, DMSDN ; 写入DMIDX0 stm DMFRI0, DMSA mvdk dmfri, DMSDN ; 写入DMFRI0调试心得数据排序的调试难点在于地址轨迹的跟踪。我强烈建议在仿真器中单步执行DMA初始化代码后不要立即运行而是先手动检查所有DMA通道寄存器的值特别是DMIDX和DMFRI。然后在目的内存区域设置一个数据观察窗口并让程序运行一小段时间比如完成一次帧传输。观察数据是否按照你预期的“转置”顺序填入。如果顺序不对十有八九是索引值算错了回头仔细核对N和M的值。4. 应用二程序分页Program Paging—— 突破片内内存限制对于复杂的DSP算法代码体积可能远超片内RAM的容量。全部放在外部慢速存储器中运行又会严重拖累性能。程序分页是一种经典的“缓存”策略将程序划分为多个“页”块将当前需要执行的页加载到快速的片内RAM中运行同时利用DMA在后台预取下一页。4.1 分页系统设计思路一个典型的双缓冲区程序分页系统工作流程如下初始化CPU从片内RAM的Section A开始执行Page 1的代码。同时DMA通道被配置为从外部存储器加载Page 2到Section B。执行与加载并行CPU执行Page 1时DMA在后台默默地将Page 2搬运至Section B。切换Page 1执行完毕触发一个事件如函数调用结束、特定标志位。此时CPU跳转到Section B执行Page 2。同时CPU立即或通过DMA中断重新配置DMA的全局重载寄存器让它从外部存储器加载Page 3到刚刚释放的Section A。循环如此往复形成“乒乓”执行。CPU总是在一个缓冲区执行DMA向另一个缓冲区加载。关键挑战与解决方案代码位置无关性分页的代码必须编译为位置无关代码PIC或者其加载地址和运行地址必须在链接时正确指定使用load和run指令。中断向量表重定位如果分页代码可能使用中断或者DMA传输完成本身会产生中断那么中断向量表必须放在一个固定且CPU始终能访问的位置通常是片内RAM的开头或结尾并且中断服务例程ISR的地址必须是绝对的或可通过某种机制解析。DMA自动初始化这是实现流畅分页的关键。在配置DMA时设置AUTOINIT1并预先在全局重载寄存器DMGSA DMGDA DMGCR中填好下一页代码的源地址、目的地址和大小。当DMA完成当前页传输后它会自动用这些值重新初始化自己开始加载下一页完全无需CPU干预。4.2 链接器命令文件.cmd的奥秘程序分页的成功一半取决于正确的DMA配置另一半取决于正确的内存布局和链接脚本。以下是原文示例中.cmd文件的精髓部分解析SECTIONS { .text : P_DARAM PAGE 0 /* 主程序和DMA初始化代码常驻片内 */ UNION: run PSARAM1 PAGE 0 /* 定义联合体1运行地址在PSARAM1 */ { test1: load PDARAM1 PAGE 0 /* test1段加载在外部PDARAM1 */ test3: load PDARAM3 PAGE 0 /* test3段也加载在外部但与test1共享运行地址 */ } UNION: run PSARAM2 PAGE 0 /* 定义联合体2运行地址在PSARAM2 */ { test2: load PDARAM2 PAGE 0 test4: load PDARAM4 PAGE 0 } ... // 其他段 }UNION指令这是实现分页的魔法关键字。它告诉链接器test1和test3这两个代码段共享同一块运行地址空间PSARAM1。它们被分别加载到不同的外部地址PDARAM1和PDARAM3但在运行时通过DMA搬运它们会轮流占据PSARAM1这块物理内存。test2和test4同理共享PSARAM2。load和runload指定了代码在.out文件中的存储位置通常是外部慢速存储器run指定了代码的实际执行地址必须是片内高速RAM。4.3 核心流程与中断协同原文的示例流程非常经典主程序初始化DMA通道0配置其从外部0x1_0100test1的加载地址搬运代码到片内0x1_8000PSARAM1运行地址。使能DMA中断然后执行idle指令让CPU休眠。DMA传输完成DMA将test1搬运完毕触发中断。中断服务程序ISR在pgm_page_isr.asm中ISR根据预设的“下一个目的地地址”在全局变量中维护计算并跳转到刚刚加载完毕的代码块test1的入口执行。代码块执行test1.asm开始执行。在其开头它立即做一件重要的事重新配置DMA的全局重载寄存器指向test3的加载地址和PSARAM1的运行地址。这样当CPU在执行test1时DMA已经在后台加载test3到另一个缓冲区但运行地址相同覆盖未来的test1这里需要仔细设计避免冲突。实际上原文示例中test1和test3都运行在PSARAM1这意味着test1执行时DMA不能向PSARAM1加载test3否则会破坏正在执行的代码。更安全的双缓冲设计是test1和test3分别运行在PSARAM1和PSARAM2 DMA在两者之间交替加载。原文示例可能做了简化实际工程中必须保证执行体和加载目标不重叠。循环test1执行到最后再次idle等待DMA加载test2完成并触发中断然后跳转到test2以此类推。重要注意事项等待状态配置访问外部存储器必须根据存储器速度正确配置软件等待状态寄存器SWSR。这在初始化代码或GEL脚本中完成。内存映射与OVLY位确保PMST寄存器中的OVLY片内RAM覆盖使能和MP/MC微处理器/微计算机模式位设置正确使得片内RAM在程序空间可见且可执行。中断向量表由于代码可能在扩展程序空间地址超过64K中断向量表中的跳转指令可能需要使用FAR跳转如FBACC来指定完整的23位地址。5. 应用三乒乓缓冲Ping-Pong Scheme—— 实现零等待流水线这是DMA应用中最经典、最高效的模式之一广泛应用于需要连续数据流处理的场景如音频编解码、图像处理、通信基带处理等。其核心思想是用空间换时间消除CPU的等待。5.1 为何需要乒乓缓冲假设一个简单的处理流程ADC采样数据 - DSP处理 - 输出。如果只有一个缓冲区DMA将数据填满缓冲区A。DMA停止通知CPU。CPU开始处理缓冲区A的数据。在CPU处理期间ADC的新数据无处存放可能丢失。CPU处理完毕通知DMA。DMA重新开始填充缓冲区A... 显然CPU和DMA是串行工作的存在大量空闲等待时间系统吞吐量受限于两者中较慢的一方。乒乓缓冲使用两个或更多缓冲区缓冲区APingDMA正在填充。缓冲区BPongCPU正在处理。 当DMA填满A时它自动通过自动初始化切换到填充B同时触发中断通知CPU“A满了快来处理”。CPU收到中断开始处理A的数据。此时DMA已经在填充B。当CPU处理完A而DMA可能还没填满BCPU可以等待或处理其他任务。当DMA填满B时又触发中断CPU切换去处理BDMA切换回填充A。如此循环实现了CPU处理和DMA数据采集的全并行流水线。5.2 配置详解与自动初始化的妙用乒乓缓冲的实现极度依赖DMA的自动初始化AUTOINIT和全局重载寄存器。配置步骤初始化阶段设置通道的DMSRCDMDST指向缓冲区ADMCTRDMSFC。设置DMMCR.AUTOINIT 1。设置全局重载寄存器DMGSA 相同的源地址如果源是固定的如ADC数据寄存器DMGDA 缓冲区B的地址DMGCR 相同的元素计数。使能DMA通道及其中断。第一轮传输DMA开始向缓冲区A传输数据。传输完成与自动切换当缓冲区A填满元素计数耗尽DMA自动做两件事 a. 触发中断如果使能。 b. 从全局重载寄存器加载新参数将DMGDA缓冲区B地址加载到DMDST 将DMGSA通常是原地址加载到DMSRC。同时它还会交换将当前的DMDST缓冲区A地址自动加载到DMGDA中这是实现“乒乓”的关键。下一次传输完成时目的地址又会在A和B之间切换。中断服务程序ISRCPU在ISR中知道哪个缓冲区通过查询一个标志或地址变量刚刚被填满然后开始处理该缓冲区的数据。同时ISR不需要重新配置DMA的源/目的地址因为自动初始化已经完成了切换。ISR可能只需要重置一个缓冲区状态标志。原文示例代码分析 在pingpong_isr.asm中ISR的核心操作是交换addr当前目的地址和global_addr全局重载目的地址这两个内存变量然后将新的global_addr写入DMGDA寄存器。这样就为再下一次的自动初始化准备好了“乒乓”的另一个地址。pingpong_isr: stm #addr, AR2 stm DMGDA, DMSA ld *AR2, B stlm B, DMSDN ; 将当前目的地址写入DMGDA为下下次准备 ; 交换addr和global_addr的值 stm #global_addr, AR3 stm #temp, AR4 mvdd *AR2, *AR4 ; addr - temp mvdd *AR3, *AR2 ; global_addr - addr mvdd *AR4, *AR3 ; temp - global_addr ; 此时addr中已是下一个缓冲区的地址可供CPU处理使用 ... // CPU处理addr指向的缓冲区数据这个设计非常巧妙ISR写入DMGDA的值并不是即将使用的而是为再下一轮准备的。当前DMA已经在使用另一个缓冲区了由自动初始化在上一次完成时切换。5.3 工程实践中的增强与变体基础的乒乓缓冲可以衍生出多种高级用法多级流水线对于更复杂的处理链如采集-滤波-FFT-输出可以设计三级甚至更多级缓冲区形成更深的流水线每一级由一个DMA或CPU处理单元负责。链式DMA利用DMA通道的链接触发一个通道传输完成可以自动启动另一个通道实现更复杂的数据搬运和处理流程自动化。与外设同步将DMSFC的同步事件DSYN设置为McBSP的接收事件REVT或发送事件XEVT。这样每个来自串口的数据字/帧都会自动触发DMA传输到乒乓缓冲区实现与数据流完全同步的、无CPU干预的数据采集。一个常见的坑缓冲区大小和DMA传输块大小的匹配。如果你的处理算法每次需要固定大小的数据块比如256点FFT那么DMA的DMCTR元素计数就应该设置为255256-1并且缓冲区大小就是256字。确保ISR中的处理例程能在下一个缓冲区被填满之前完成计算否则会发生数据覆盖。这就需要根据系统最坏执行时间WCET来设计缓冲区大小和DMA传输速率。6. 常见问题、调试技巧与性能优化即使理解了原理实际调试DMA时也难免遇到各种“灵异事件”。下面分享一些我踩过的坑和总结的技巧。6.1 DMA传输不启动或数据错误检查清单时钟与低功耗模式确认DMA控制器所在的模块时钟已经使能通过PLL或时钟配置寄存器。在低功耗模式下外设时钟可能被关闭。寄存器配置顺序务必最后配置DMPREC的通道使能位DE。确保所有参数特别是源/目的地址、计数、索引寄存器都已正确写入后再“点火”。同步事件如果使用了同步事件DSYN非0确认该事件是否真的发生了。例如如果配置为McBSP接收同步检查McBSP是否已正确初始化并开始接收数据。内存映射与等待状态确保你访问的源和目的地址是有效的、可访问的内存区域。访问外部存储器时等待状态数是否足够访问外设寄存器地址是否正确中断冲突检查DMPREC中的INTOSEL位确保DMA中断映射到了正确的CPU中断线并且该中断在IMR寄存器中未被屏蔽同时ST1寄存器中的INTM位为0全局中断使能。调试工具仿真器Emulator这是最强大的工具。可以单步执行在任何时刻暂停CPU然后查看DMA控制器的所有寄存器状态。检查DMA通道状态寄存器如果有看是否有错误标志。内存观察窗口在源和目的地址设置观察点查看数据传输前后内存内容的变化。可以快速判断数据是否传输、传输是否正确。CCS的Event Analyzer或RTOS Analyzer一些高级仿真器支持查看DMA传输事件的时间线有助于分析时序和并发问题。6.2 性能优化考量总线仲裁与优先级C5000的DMA与CPU共享内部总线和外部总线。高优先级的DMA通道可以抢占总线。对于实时性要求极高的数据流如音频输出可以赋予其DMA通道高优先级DPRC1。但要注意这可能会阻塞CPU访问内存导致CPU性能下降。需要根据系统整体负载进行权衡。块传输 vs 单元素传输DMA传输本身有开销。一次性传输一个大块如256个字比分成256次单字传输效率高得多因为减少了总线仲裁和DMA控制器自身的调度开销。尽量使用多帧或自动缓冲模式进行大块传输。内存对齐虽然C5000 DMA不一定要求严格的内存对齐但将源和目的地址对齐到偶地址对于16位访问或4字节边界对于32位访问当DBLW1时有时可以利用总线特性获得更好的性能。与CPU缓存协作如果DSP有缓存如C55x系列需要注意DMA写入的内存区域是否被缓存。如果CPU缓存了某个内存区域而DMA直接向该物理内存写入数据会造成缓存不一致。通常需要将DMA缓冲区所在的内存区域标记为“非缓存”或“直写”或者在DMA操作后执行缓存无效化invalidate操作。6.3 中断服务程序ISR编写要点上下文保存原文示例的ISR为了简洁没有保存上下文。在实际产品代码中必须保存和恢复所有在ISR中使用的寄存器至少包括ACC P T ARs ST0 ST1等。否则从中断返回后主程序的状态会被破坏。精简高效DMA中断频率可能很高例如每个数据块完成一次。ISR应该只做最必要的工作设置标志、交换缓冲区指针、启动下一个处理任务。复杂的计算应放到主循环或后台任务中。避免重入确保DMA中断处理期间不会被同一DMA通道的中断再次打断。可以通过在ISR入口处禁用该中断在IMR中清除相应位在退出前再恢复。清除中断标志有些DMA控制器在触发中断后需要手动清除中断悬挂标志。查阅具体芯片的数据手册确认是否需要以及如何清除。最后DMA的灵活性和强大性能是以配置的复杂性为代价的。最好的学习方式就是动手实验。从一个简单的内存到内存的传输开始逐步增加同步事件、自动初始化、地址索引等特性。利用仿真器仔细观察每一步的执行结果和寄存器状态变化。当你成功让DMA像瑞士钟表一样精确地管理起数据流而CPU可以专注于核心算法时你会感受到这种硬件协作带来的巨大成就感。