TMS320F28003x DMA模块深度解析:从寄存器映射到乒乓缓冲实战
1. 项目概述与DMA核心价值
在嵌入式实时控制系统的开发中,尤其是像TI C2000系列这样面向电机控制、数字电源、新能源等领域的微控制器,系统性能的瓶颈往往不在于CPU的运算速度,而在于数据搬运的效率。想象一下,一个高性能的ADC模块以每秒数百万次的采样率工作,如果每个采样点的数据都需要CPU来“亲手”从ADC结果寄存器搬到RAM,那么CPU将深陷于繁琐的“搬运工”角色,无暇执行核心的控制算法。这正是直接存储器访问(DMA)模块大显身手的地方。
DMA本质上是一个独立于CPU的“数据搬运工”。它拥有自己的地址总线和状态机,能够在接收到特定事件(如ADC转换完成、SPI接收缓冲区满)的触发后,自动在指定的源地址和目的地址之间搬运数据。这个过程完全由硬件完成,CPU只需在初始化时配置好DMA,并在数据搬运完成或达到特定量时收到一个中断通知即可。对于TMS320F28003x这款芯片,其DMA模块提供了6个独立通道,支持灵活的外设触发、复杂的数据地址编排(如乒乓缓冲、数据重排),是释放CPU算力、构建高效实时系统的关键。
本文将深入TMS320F28003x DMA模块的内核,不仅会梳理官方手册中寄存器与Driverlib库函数的映射关系(这是快速上手的钥匙),更会重点剖析其触发机制的设计哲学、数据传输流程的状态机控制,并分享在实际项目中优化DMA性能的配置心得与避坑指南。无论你是正在评估F28003x用于新项目,还是试图优化现有系统中的数据流,相信这些从一线实践中总结的细节都能为你提供直接的参考。
2. 寄存器到Driverlib函数映射:从底层到上层的桥梁
初次接触一款新的微控制器外设,开发者通常会面临两个选择:直接操作寄存器,或者使用厂商提供的库函数。对于TMS320F28003x的DMA,TI提供了功能完善的Driverlib库。理解寄存器与库函数之间的映射,并非简单的查表,而是理解芯片设计者如何将硬件功能封装成软件接口的关键。
2.1 映射关系的本质与查阅方法
手册中11.3.7节列出的多个表格(如Table 11-125至Table 11-129),其核心价值在于建立了硬件配置位(寄存器)与软件可调用函数(Driverlib API)的对应关系。这并非DMA独有,而是整个C2000 Driverlib库的设计模式。以INPUTXBAR(输入交叉开关)的配置为例:
- 寄存器
INPUT1SELECT:这是一个硬件寄存器,用于配置输入交叉开关的第一个输入引脚选择哪个外部信号源。你需要查阅芯片数据手册的GPIO复用表,找到对应位域,然后写入特定的数值。 - Driverlib函数
XBAR_setInputPin:这个函数封装了上述操作。你只需要调用XBAR_setInputPin(XBAR_INPUT1, XBAR_INPUT_GPIOx),函数内部会自动计算并写入正确的值到INPUT1SELECT寄存器。
为什么推荐使用Driverlib?
- 可读性与可维护性:
XBAR_setInputPin(XBAR_INPUT1, XBAR_INPUT_GPIO5)远比直接写*(volatile uint32_t *)0x0000 5FE4 = 5要清晰得多,后者需要开发者熟记寄存器地址和位域定义。 - 可移植性:虽然同系列芯片外设相似,但寄存器地址或位域可能有细微差别。使用Driverlib,你的代码在不同型号的C2000芯片间移植的成功率更高。
- 减少错误:库函数通常包含参数检查,能避免一些非法的输入值。
何时需要直接操作寄存器?当Driverlib库未覆盖某些新芯片的特定功能,或者你需要极致的性能优化(例如在极短的中断服务程序中内联关键寄存器操作),以及进行底层调试和故障排查时,直接操作寄存器是必要的。因此,这份映射表是你的“逃生通道”和“深度调试手册”。
2.2 关键映射模块解析与实战关联
手册中列出了INPUTXBAR、XBAR、EPWMXBAR、CLBXBAR、OUTPUTXBAR等多个模块的映射。对于DMA配置而言,我们最需要关注的是触发源的选择,这通常涉及到INPUTXBAR和XBAR模块。
XBAR_getInputFlagStatus与XBAR_clearInputFlag:这两个函数对应FLGx和CLRx寄存器。在复杂的信号联动系统中,你可能需要查询某个交叉开关路径上的信号状态,或者手动清除一个标志位。虽然DMA的触发通常自动完成,但在调试阶段,这些函数对于验证触发信号是否成功产生至关重要。XBAR_setEPWMMuxConfig与XBAR_enableEPWMMux:这是将ePWM模块的特定事件(如SOCA、SOCB)路由到DMA触发线的关键。例如,你想让ePWM1的周期匹配事件触发DMA去搬运ADC数据,就需要通过这两个函数配置EPWMXBAR。
实操心得:善用头文件进行反向工程当你对某个Driverlib函数的参数感到困惑时,不要只看手册。直接打开TI安装目录下的Driverlib头文件(如
xbar.h)和源文件(如xbar.c)。里面不仅有完整的函数原型,还有详细的注释和内部的寄存器操作逻辑。这是理解映射关系最直接、最准确的方法。例如,查看XBAR_setInputPin的实现,你能立刻明白它如何操作INPUTxSELECT寄存器,以及参数枚举值的具体含义。
3. DMA架构与触发机制深度解析
DMA的启动不是随机的,它是一台精密的“事件驱动”机器。理解其触发架构,是灵活运用DMA的第一步。
3.1 两级触发选择机制
TMS320F28003x的DMA触发源选择非常灵活,采用了两级映射机制,这也是其设计精妙之处。
系统级选择 (
DMACHSRCSELx寄存器):这是第一级,非常庞大。如图12-1和表12-1所示,芯片几乎将所有可能产生周期性或事件性数据的外设中断信号都引到了这个选择器上。从ADC的各个中断(ADCAINT1)、ePWM的SOC事件、SPI的收发FIFO中断,到外部引脚中断(XINT1-5)、CLB中断等,总计超过200个触发源。DMACHSRCSEL1和DMACHSRCSEL2寄存器为每个DMA通道(CH1-CH6)指定一个全局索引号(0-255)。通道级选择 (
CHx.MODE.PERINTSEL位域):这是第二级,相对简单。每个DMA通道的MODE寄存器中都有一个5位的PERINTSEL字段。关键点在于:这个字段的值应该设置为通道自身的编号(x=1~6)。这意味着,PERINTSEL实际上选择的是“通过DMACHSRCSELx寄存器为本通道选定的那个信号”。
工作流程比喻:想象有6个DMA通道(6个工人),一个巨大的信号调度中心(DMACHSRCSELx)。调度中心有256个信号输入端口(外设中断),每个工人(通道)在调度中心有一个专属的接线板(DMACHSRCSELx.CHx)。工人需要告诉自己的手(PERINTSEL):“去操作我专属接线板上的信号”。所以,PERINTSEL设为通道号,就是让通道去监听自己在调度中心预设好的那一路信号。
配置示例:想让ADC的转换结束中断1(ADCAINT1)触发DMA通道2。
- 查表12-1,
ADCAINT1_DMA的索引是1。 - 配置
DMACHSRCSEL1.CH2 = 1。 (意为:通道2的专属接线板,接在调度中心的1号信号口上)。 - 配置
CH2.MODE.PERINTSEL = 2。 (意为:通道2,去处理你专属接线板上的信号)。
3.2 软件强制触发与清除
除了外设事件,软件也可以随时介入:
CONTROL.CHx[PERINTFRC]:将此位置1,会立即为该通道产生一个软件触发事件,等效于一个外设触发。这在手动启动一次DMA传输或调试时非常有用。CONTROL.CHx[PERINTCLR]:将此位置1,可以清除该通道 pending 的触发标志 (PERINTFLG)。如果一个触发事件已产生但DMA还未开始处理,你可以用此位清除它。
3.3 触发标志处理与过载保护
图12-3清晰地展示了触发信号的路径:外设或软件触发信号首先经过一个锁存器,将PERINTFLG标志位置位。这个标志位会一直保持,直到DMA状态机响应该通道的请求并开始一次突发传输(Burst Transfer)时,才由硬件自动清除。
这里隐藏了两个重要的实战要点:
- “背靠背”触发处理:如果一个通道的突发传输正在进行中,此时又来了一个新的触发事件,新事件会被记录(
PERINTFLG再次置位),但必须等待当前突发传输完成,并且该通道在轮询优先级中再次获得服务时,才会处理这个新事件。这保证了数据传输的完整性。 - 过载(Overrun)检测:这是一个关键的保护机制。如果在一个触发事件尚未被服务(即
PERINTFLG已为1)时,又来了第三个触发事件,硬件会设置CONTROL.CHx[OVRFLG]过载标志。这通常意味着CPU处理数据的速度跟不上DMA搬运数据的速度,或者触发事件过于频繁。在你的中断服务程序中,必须检查这个标志,因为它意味着你可能丢失了数据。
避坑指南:ADC结果寄存器读取的“陈旧数据”风险手册在表12-1下方的“CAUTION”中特别提到了一个勘误项:“ADC:DMA Read of Stale Result”。其核心风险在于:如果DMA的触发源配置为ADC的某个中断(如
ADCAINT1),并且DMA的读取速度极快,理论上存在一种极小概率的可能性——DMA在ADC内核刚刚完成转换、但还未将新结果更新到结果寄存器(ADCRESULTx)的瞬间,去读取了该寄存器,从而读到了上一次的“陈旧”数据。解决方案:TI通常建议,在ADC中断服务程序(ISR)中,不要直接清除ADC中断标志来触发DMA,而是使用一个GPIO引脚翻转或者通过PIE触发DMA。更稳妥的通用实践是,让DMA的触发稍微滞后于ADC转换完成事件。例如,使用ePWM的SOC事件来触发ADC转换,然后使用ADC的EVT(事件)信号而非INT(中断)信号来触发DMA。EVT信号与转换完成硬同步,时序上更安全。务必查阅你所使用芯片型号的最新勘误表(Silicon Errata)以确认具体细节和官方推荐方案。
4. 地址指针与传输控制:DMA状态机的核心逻辑
这是DMA最强大也最复杂的部分。它通过多级循环和地址修改机制,实现了类似CPU寻址的灵活性。图12-4的状态图是理解这一切的蓝图,我们必须将其拆解透彻。
4.1 嵌套循环模型:突发(Burst)与传输(Transfer)
DMA的传输由两层循环构成:
- 突发循环(内循环):由
BURST_SIZE寄存器控制。一次触发事件到来,DMA会连续搬运BURST_SIZE + 1个“字”。这里的“字”可以是16位或32位,由MODE.DATASIZE决定。这是DMA响应一次触发所完成的最小工作单元。 - 传输循环(外循环):由
TRANSFER_SIZE寄存器控制。一个完整的“传输”包含TRANSFER_SIZE + 1次“突发”。完成整个传输循环后,DMA会产生一次中断(如果使能)。
为什么设计两层循环?这主要是为了平衡响应实时性和中断开销。例如,ADC以1MHz采样,每采样16个点(一个突发)就让DMA搬一次,如果每个点都触发一次传输,中断频率会高达1MHz,系统开销巨大。通过设置TRANSFER_SIZE,可以让DMA连续搬运多个突发(比如64个突发,共1024个点)后才通知一次CPU,大大降低了中断频率。
4.2 三组地址指针与步进机制
这是实现数据重排和复杂缓冲区管理的核心。每个通道有两套地址指针:源地址(SRC)和目的地址(DST),每套又包含三组寄存器:
| 寄存器组 | 功能描述 | 类比解释 |
|---|---|---|
ADDR_SHADOW / ACTIVE | 当前传输地址。SHADOW是用户设置的起始地址。每次传输开始时,SHADOW值被复制到ACTIVE,ACTIVE在传输中实时变化。 | 好比快递员(DMA)手中的“当前送货单”,上面写着正在送的这一家的地址。 |
BEG_ADDR_SHADOW / ACTIVE | 循环基地址。用于实现地址环绕(Wrap)。SHADOW是用户设置的循环起始基地址。 | 好比一个小区(缓冲区)的入口地址。快递员送完一圈(若干户)后,会回到这个门口。 |
BURST_STEP | 突发内步进。每搬运一个字后,ACTIVE地址增加(或减少)这个值。 | 送完一户到下一户的步长(如+4,表示每户占4字节)。 |
TRANSFER_STEP | 传输间步进。每次突发(Burst)完成后,在下一个突发开始前,ACTIVE地址增加(或减少)这个值。 | 送完一个楼栋的最后一户,到下一个楼栋第一户的偏移量。 |
WRAP_STEP | 环绕步进。当环绕发生时,BEG_ADDR_ACTIVE增加(或减少)这个值,然后ADDR_ACTIVE被重置为新的BEG_ADDR_ACTIVE。 | 送完整个小区一圈后,下一个循环的起始楼栋可能不是第一栋了,而是跳到了另一个区域。 |
地址修改流程(结合图12-4):
- 传输开始:
*_SHADOW值拷贝到对应的*_ACTIVE。 - 突发内:每搬一个字,
ADDR_ACTIVE += BURST_STEP。 - 突发完成:
ADDR_ACTIVE += TRANSFER_STEP。同时,WRAP_COUNT减1。 - 环绕检查:如果
WRAP_COUNT减到0,则触发环绕:BEG_ADDR_ACTIVE += WRAP_STEP,然后ADDR_ACTIVE = BEG_ADDR_ACTIVE,并将WRAP_SIZE重载到WRAP_COUNT。 - 传输完成:
TRANSFER_COUNT减到0,整个传输结束。
4.3 关键工作模式:ONESHOT与CONTINUOUS
ONESHOT模式 (
MODE.ONESHOT):- 禁用 (0):默认模式。一次触发只执行一次突发(Burst)传输。这是最常用的模式,确保单个通道不会独占DMA总线,公平响应多个触发源。
- 使能 (1):一次触发会执行完整个传输循环(所有Burst)。慎用此模式!如果
TRANSFER_SIZE很大,一次触发会导致DMA长时间占用总线,阻塞其他通道和可能的总线主设备(如CPU),破坏系统实时性。仅适用于需要一次性搬运大量连续数据且对实时性要求不高的场景。
CONTINUOUS模式 (
MODE.CONTINUOUS):- 禁用 (0):默认模式。当一次传输循环(所有Burst)完成后,该通道自动禁用(
RUNSTS=0)。需要软件重新使能RUN位才能开始下一次传输。 - 使能 (1):传输循环完成后,通道保持使能状态,等待下一个触发事件,开始新一轮传输。这是实现乒乓缓冲(Ping-Pong Buffer)的理想模式。你可以配置两个缓冲区(Buffer A和B),当DMA向Buffer A写数据时,CPU处理Buffer B;一次传输完成后,通过中断或查询标志位,交换缓冲区角色,DMA自动开始向Buffer B写入新数据。
- 禁用 (0):默认模式。当一次传输循环(所有Burst)完成后,该通道自动禁用(
4.4 数据尺寸(DATASIZE)的微妙影响
MODE.DATASIZE选择16位或32位传输。这里有一个极易混淆的要点:所有SIZE(BURST_SIZE,TRANSFER_SIZE,WRAP_SIZE)和STEP(BURST_STEP,TRANSFER_STEP,WRAP_STEP)寄存器的值,其单位都是16位(半字)地址偏移。
- 如果你设置
DATASIZE = 32-bit(字),但想每次地址增加一个32位字的长度,那么BURST_STEP应该设置为2(因为一个32位字占2个16位地址单元)。 - 表12-2清晰地展示了这种关系:当
BURSTSIZE=5时,若DATASIZE=16-bit,则传输6个16位数据;若DATASIZE=32-bit,则传输6个32位数据(但消耗的地址跨度是12个16位单元)。
配置示例:将ADC结果寄存器(16位)搬运到32位数组假设ADC结果寄存器基地址为0x0000 8800,我们想将16个转换结果(每个16位)搬运到一个32位整数数组adcResults32[8]中。
- 源配置:
SRC_ADDR_SHADOW = 0x0000 8800,SRC_BURST_STEP = 2(因为ADC结果寄存器是连续的16位寄存器,地址间隔2字节)。 - 目的配置:
DST_ADDR_SHADOW = (uint32_t*)&adcResults32[0],DST_BURST_STEP = 4(因为每个uint32_t占4字节)。 - 尺寸配置:
BURST_SIZE = 15(传输16个数据),DATASIZE = 16-bit。注意,这里虽然目的是32位数组,但DMA从源(ADC)读取的是16位数据,所以数据尺寸按源来设置。DMA会进行16位到32位的零扩展或符号扩展(取决于配置)吗?不会。TMS320F28003x的DMA在16位到32位传输时,只是简单地将16位数据放入32位目的地址的低16位,高16位保持不变(可能是旧数据)。因此,你的目的数组必须先清零,或者使用32位传输模式并配合数据重排功能。
5. 数据传输优化与实战配置策略
理解了原理,最终要落实到性能优化上。DMA的优化目标很明确:最大化数据吞吐量,最小化对CPU和总线资源的占用。
5.1 吞吐量计算与优化
手册12.4节给出了吞吐量的关键公式和影响因素。DMA采用3级流水线,理想情况下每个周期可以完成一次读或写操作,但一次完整的“读-修改-写”传输需要多个周期。
核心公式(无冲突情况下):总周期数 ≈ 突发次数 * [(3 周期/字 * 每突发字数) + 1]公式中的“+1”是每个突发开始时的1周期延迟。
优化策略:
- 增大突发尺寸(BURST_SIZE):在满足实时性要求的前提下,尽可能使用更大的
BURST_SIZE。这能摊销每次突发开始的固定延迟。例如,搬运128个字,配置成8 bursts * 16 words比16 bursts * 8 words更高效。 - 使用32位传输:如果源和目的地址都对齐到32位边界,且数据是32位有效的,务必设置
DATASIZE=32-bit。这样硬件会在一个周期内搬运32位数据,吞吐量翻倍。如上例,搬运128个16位数据,用32位模式只需200个周期,而16位模式需要392个周期。 - 避免总线冲突:理解12.5节的仲裁规则。DMA WRITE优先级最高,CPU READ优先级最低。要避免DMA长时间占用高优先级操作阻塞CPU。例如,如果DMA正在进行一个非常大的、连续的内存拷贝(高优先级写),而CPU需要频繁读取某个外设(低优先级读),CPU可能会被严重阻塞。
- 优化建议:将DMA的大规模内存操作安排在CPU相对空闲的时间窗口;或者将CPU需要频繁访问的数据放在与DMA操作不同的存储体(如GSRAM的不同Bank),利用并行总线减少冲突。
5.2 乒乓缓冲(Ping-Pong Buffer)实现详解
这是DMA最经典的高级应用,用于实现数据生产与消费的无缝衔接。
场景:ADC持续采样,CPU需要对每个数据块进行FFT处理。问题:如果DMA直接写入一个缓冲区,当CPU处理这个缓冲区时,新来的ADC数据无处存放。方案:使用两个缓冲区(Ping和Pong),DMA和CPU交替使用。
配置步骤:
- 准备缓冲区:在内存中定义两个足够大的缓冲区,例如
bufferPing[BUFFER_SIZE]和bufferPong[BUFFER_SIZE]。 - 配置DMA通道为CONTINUOUS模式:
MODE.CONTINUOUS = 1。 - 配置传输尺寸:
TRANSFER_SIZE设置为0(表示一次传输包含1个突发,即一个缓冲区的数据量)。BURST_SIZE设置为BUFFER_SIZE - 1。 - 配置地址环绕:这是关键。设置
DST_WRAP_SIZE = 0(表示每传输1个突发后环绕)。设置DST_WRAP_STEP = BUFFER_SIZE * sizeof(data_type)。设置DST_BEG_ADDR_SHADOW为bufferPing的地址。 - 使能传输完成中断:设置
MODE.CHINTMODE = 1(传输结束时产生中断)。 - 启动DMA。
- 中断服务程序(ISR):当DMA完成向
bufferPing的写入并产生中断时,在ISR中:- CPU开始处理
bufferPing。 - 关键操作:软件修改
DST_BEG_ADDR_SHADOW为bufferPong的地址。由于DMA处于CONTINUOUS模式且已完成一次传输,它会等待下一个触发。当下一个ADC触发到来时,DMA会使用新的BEG_ADDR_SHADOW(即bufferPong)作为起始地址开始写入。 - 依次轮换。这样,DMA总是在向“空闲”缓冲区写入,CPU总是在处理“已满”缓冲区,两者互不干扰。
- CPU开始处理
5.3 常见问题排查速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| DMA不启动 | 1. 通道未使能 (CONTROL.RUN=0)。2. 触发源配置错误( DMACHSRCSELx或PERINTSEL)。3. 触发信号未产生。 | 1. 确认CONTROL.RUN位已置1。2. 双重检查触发源映射表,并用示波器或GPIO翻转验证触发信号是否到达。 3. 尝试使用 CONTROL.PERINTFRC软件强制触发,看DMA是否工作。 |
| 数据传输地址错乱 | 1.BURST_STEP/TRANSFER_STEP计算错误(未考虑数据宽度)。2. 地址环绕配置错误,特别是 WRAP_SIZE和WRAP_STEP。 | 1. 牢记STEP寄存器单位是16位地址偏移。用计算器仔细核对。 2. 在调试器中单步运行,观察 SRC_ADDR_ACTIVE和DST_ADDR_ACTIVE寄存器的变化是否符合预期。绘制预期的地址变化图进行比对。 |
| 数据丢失(OVRFLG置位) | 1. 触发频率高于DMA处理能力。 2. CPU未及时处理数据,导致缓冲区满。 | 1. 降低触发频率,或增大BURST_SIZE以减少中断/触发次数。2. 检查CPU侧处理代码的效率,或增大缓冲区尺寸。实现乒乓缓冲机制。 |
| 系统偶尔卡顿 | DMA与CPU/CLA发生总线仲裁冲突,CPU被阻塞。 | 1. 使用CCS的System Analyzer或性能计数器,分析总线利用率。 2. 将CPU频繁访问的数据(如控制变量)与DMA大规模搬运的数据放置在不同的物理存储块(如不同的GSRAM bank)。 3. 调整DMA操作的时机,避开CPU的关键实时任务。 |
| 使用Driverlib函数配置后不生效 | 1. 未调用初始化函数DMA_init()。2. 寄存器配置顺序有依赖,但Driverlib函数调用顺序错误。 3. 模块时钟未使能。 | 1. 确保在配置任何通道前调用了DMA_init()。2. 参考TI例程,遵循正确的初始化序列:初始化 -> 配置通道参数 -> 设置触发源 -> 使能通道。 3. 检查系统初始化代码中是否使能了DMA模块的时钟(通常通过 SysCtl_enablePeripheral()函数)。 |
6. 与CPU/CLA的协同设计与资源仲裁
在复杂的系统中,DMA、CPU和CLA(控制律加速器)可能同时访问共享资源。手册12.5节明确了仲裁规则,我们必须据此设计软件架构。
核心原则:
- DMA WRITE > DMA READ > CLA WRITE > CLA READ > CPU WRITE > CPU READ。CPU读的优先级最低。
- ADC结果寄存器是特例:它们被复刻了多份,三方可同时无冲突读取。
- 相同类型外设冲突:例如,DMA写CAN-A和CPU读CAN-B会冲突,因为它们共享CAN外设总线接口。
设计建议:
- 内存分区:将GSRAM划分为不同区域,专用于DMA、CLA和CPU。例如,GS0用于CPU变量,GS1用于DMA缓冲区,GS2用于CLA数据。利用物理上的多端口内存优势,实现并行访问。
- 关键CPU路径避让:识别代码中实时性要求最高的关键循环(如电流环控制ISR),确保在此ISR执行期间,DMA不会发起长时间、高优先级的写操作(尤其是对共享外设的写)。可以通过在ISR入口暂停低优先级DMA通道,在出口恢复来实现。
- 监控仲裁状态:虽然芯片没有提供直��的仲裁状态寄存器,但可以通过性能计数器或软件时间戳来间接监测。如果发现某个CPU任务的执行时间异常波动,可以考虑是否是DMA活动导致的。
配置DMA不仅仅是让它动起来,更是让它在整个系统中和谐、高效地工作。它像是一个不知疲倦的搬运工,但你需要为它规划好路线(地址指针),设置好工作节奏(触发与模式),并管理好它与其他“工人”(CPU、CLA)对共享工具(总线、内存)的使用权(仲裁)。通过深入理解寄存器映射、触发机制、状态机流程和优化策略,你就能充分发挥TMS320F28003x DMA模块的威力,构建出真正高效、可靠的实时嵌入式系统。在实际项目中,我习惯在系统设计初期就绘制出主要的数据流图,明确标出哪些流由DMA负责,并据此详细设计每个DMA通道的参数,这能从根本上避免后期的性能瓶颈和资源冲突问题。