前言: 本文继续分析 F28379D DMA。重点不是简单罗列寄存器,而是解释地址为什么这样变化、Burst 和 Transfer 如何嵌套、Wrap 怎样形成循环缓冲、Shadow/Active 寄存器怎样实现连续采集,以及 Driverlib 参数如何对应底层硬件。
1. DMA真正的核心是地址控制状态机
DMA 的数据读写动作并不复杂:
读取 *SRC_ADDR_ACTIVE
↓
写入 *DST_ADDR_ACTIVE
真正决定 DMA 能否完成复杂任务的,是下面这些问题:
- 当前源地址是多少;
- 当前目的地址是多少;
- 搬完一个 Word 后地址加多少;
- 搬完一个 Burst 后地址跳到哪里;
- 搬完若干 Burst 后是否回绕;
- 一次 Transfer 完成后是否自动重新开始;
- 软件修改地址时会不会破坏正在进行的传输。
F28379D 通过 Burst、Transfer、Wrap、Shadow 和 Active 寄存器共同解决这些问题。

Burst、Transfer与Wrap关系
2. DMA状态机为什么是两层循环
手册把 DMA 的基本结构描述为两个嵌套循环。
2.1 内层循环:Burst
一次 Burst 搬运若干个 Word。
若实际 Burst 长度为
B
B
B 个 16 位地址单元,则底层寄存器写入:
B
U
R
S
T
_
S
I
Z
E
=
B
−
1
BURST\\_SIZE=B-1
BURST_SIZE=B−1
每搬完一个非末尾 Word,活动地址按 Burst Step 更新:
S
R
C
_
A
D
D
R
_
A
C
T
I
V
E
←
S
R
C
_
A
D
D
R
_
A
C
T
I
V
E
+
S
R
C
_
B
U
R
S
T
_
S
T
E
P
SRC\\_ADDR\\_ACTIVE \\leftarrow SRC\\_ADDR\\_ACTIVE+SRC\\_BURST\\_STEP
SRC_ADDR_ACTIVE←SRC_ADDR_ACTIVE+SRC_BURST_STEP
D
S
T
_
A
D
D
R
_
A
C
T
I
V
E
←
D
S
T
_
A
D
D
R
_
A
C
T
I
V
E
+
D
S
T
_
B
U
R
S
T
_
S
T
E
P
DST\\_ADDR\\_ACTIVE \\leftarrow DST\\_ADDR\\_ACTIVE+DST\\_BURST\\_STEP
DST_ADDR_ACTIVE←DST_ADDR_ACTIVE+DST_BURST_STEP
这里的 Step 是有符号二进制补码,因此可以:
- 设置为正数,实现地址递增;
- 设置为 0,始终访问同一外设寄存器;
- 设置为负数,实现地址递减或回到前面的位置。
2.2 外层循环:Transfer
一次 Transfer 由若干个 Burst 构成。
若实际包含
T
T
T 个 Burst,则底层寄存器写入:
T
R
A
N
S
F
E
R
_
S
I
Z
E
=
T
−
1
TRANSFER\\_SIZE=T-1
TRANSFER_SIZE=T−1
一个 Burst 结束后,如果没有发生 Wrap,活动地址按 Transfer Step 更新:
S
R
C
_
A
D
D
R
_
A
C
T
I
V
E
←
S
R
C
_
A
D
D
R
_
A
C
T
I
V
E
+
S
R
C
_
T
R
A
N
S
F
E
R
_
S
T
E
P
SRC\\_ADDR\\_ACTIVE \\leftarrow SRC\\_ADDR\\_ACTIVE+SRC\\_TRANSFER\\_STEP
SRC_ADDR_ACTIVE←SRC_ADDR_ACTIVE+SRC_TRANSFER_STEP
D
S
T
_
A
D
D
R
_
A
C
T
I
V
E
←
D
S
T
_
A
D
D
R
_
A
C
T
I
V
E
+
D
S
T
_
T
R
A
N
S
F
E
R
_
S
T
E
P
DST\\_ADDR\\_ACTIVE \\leftarrow DST\\_ADDR\\_ACTIVE+DST\\_TRANSFER\\_STEP
DST_ADDR_ACTIVE←DST_ADDR_ACTIVE+DST_TRANSFER_STEP
因此,Transfer Step 并不是“从本 Burst 起点到下一个 Burst 起点的总跨度”,而是从本 Burst 最后一次访问的地址继续增加的偏移。
例如连续搬运数组,每个 Burst 有 8 个 Word:
Burst 0:地址 0、1、2、3、4、5、6、7
Burst 结束时当前地址停在 7
TRANSFER_STEP = 1
Burst 1 从地址 8 开始
如果误把 Transfer Step 配置为 8,下一个 Burst 会从地址 15 开始,导致中间数据被跳过。
3. Shadow地址与Active地址为什么要分开
每个 DMA 通道都包含两套地址寄存器:
- Shadow:软件配置的起始地址;
- Active:硬件传输过程中实时变化的当前地址。
包括:
- SRC_ADDR_SHADOW 与 SRC_ADDR_ACTIVE;
- DST_ADDR_SHADOW 与 DST_ADDR_ACTIVE;
- SRC_BEG_ADDR_SHADOW 与 SRC_BEG_ADDR_ACTIVE;
- DST_BEG_ADDR_SHADOW 与 DST_BEG_ADDR_ACTIVE。
一次新 Transfer 开始时,DMA 将 Shadow 值复制到 Active:
SRC_BEG_ADDR_ACTIVE = SRC_BEG_ADDR_SHADOW
DST_BEG_ADDR_ACTIVE = DST_BEG_ADDR_SHADOW
SRC_ADDR_ACTIVE = SRC_ADDR_SHADOW
DST_ADDR_ACTIVE = DST_ADDR_SHADOW
随后,DMA 只修改 Active 寄存器。Shadow 地址不会因为硬件搬运而变化。
可以用一句话记忆:
Shadow 由软件准备,Active 由硬件执行。
这种设计有两个重要作用。
3.1 软件可以查看DMA当前搬到哪里
调试时读取 SRC_ADDR_ACTIVE、DST_ADDR_ACTIVE,可以判断 DMA 当前访问位置。
3.2 软件可以提前准备下一轮地址
DMA 正在使用 Active 地址写 Buffer A 时,CPU 可以修改 Shadow 地址为 Buffer B,而不会改变当前传输。下一次 Transfer 开始时,Buffer B 地址才会复制到 Active。
这正是连续 Ping-Pong 缓冲的重要基础。
4. BEG_ADDR到底有什么作用
SRC_ADDR 和 DST_ADDR 是当前读写指针;SRC_BEG_ADDR 和 DST_BEG_ADDR 是 Wrap 使用的基准指针。
普通连续搬运中,BEG_ADDR 几乎看不出作用。只有 Wrap 发生时,状态机会执行:
B
E
G
_
A
D
D
R
_
A
C
T
I
V
E
←
B
E
G
_
A
D
D
R
_
A
C
T
I
V
E
+
W
R
A
P
_
S
T
E
P
BEG\\_ADDR\\_ACTIVE \\leftarrow BEG\\_ADDR\\_ACTIVE+WRAP\\_STEP
BEG_ADDR_ACTIVE←BEG_ADDR_ACTIVE+WRAP_STEP
A
D
D
R
_
A
C
T
I
V
E
←
B
E
G
_
A
D
D
R
_
A
C
T
I
V
E
ADDR\\_ACTIVE \\leftarrow BEG\\_ADDR\\_ACTIVE
ADDR_ACTIVE←BEG_ADDR_ACTIVE
因此,Wrap 不是简单地“当前地址减去一段长度”,而是:
这样可以实现:
- 固定大小循环缓冲区;
- 多行二维数组之间的跳转;
- 采样通道交错排列;
- 在多块缓冲区之间切换起始地址。
5. Wrap怎样形成循环缓冲
假设目标数组有 8 个 16 位元素,每个 Burst 写 2 个元素,希望 DMA 每写完 4 个 Burst 后回到数组开头。
目标地址变化为:
Burst 0:buffer[0]、buffer[1]
Burst 1:buffer[2]、buffer[3]
Burst 2:buffer[4]、buffer[5]
Burst 3:buffer[6]、buffer[7]
Wrap:回到 buffer[0]
Burst 4:重新写 buffer[0]、buffer[1]
对应思路为:
- Burst 长度:2;
- DST_BURST_STEP = 1;
- DST_TRANSFER_STEP = 1;
- 4 个 Burst 后目的地址 Wrap;
- DST_WRAP_STEP = 0,使 Begin Address 保持为原数组起点。
Driverlib 配置形式如下:
DMA_configBurst(DMA_CH1_BASE, 2U, 0, 1);
DMA_configTransfer(DMA_CH1_BASE, 8U, 0, 1);
DMA_configWrap(DMA_CH1_BASE,
65536U, 0, // 源地址不回绕
4U, 0); // 目的地址每4个Burst回到原起点
其中 65536U 表示源 Wrap 周期大于本次实际 Transfer,可视为不启用源地址 Wrap。
循环缓冲并不会自动告诉 CPU 哪些数据是新的。工程中仍需配合:
- DMA 中断;
- 读写索引;
- 半满/全满标志;
- Ping-Pong 双缓冲;
- Overrun 检测。
6. Ping-Pong双缓冲如何避免采集与处理冲突
连续采样系统中,单个缓冲区存在一个明显问题:DMA 正在写入数组时,CPU 也可能正在读取和处理同一数组,导致数据前后不属于同一采样周期。
Ping-Pong 使用两块缓冲区解决该问题:
- DMA 写 Buffer A 时,CPU 处理 Buffer B;
- DMA 写 Buffer B 时,CPU 处理 Buffer A。

DMA双缓冲工作方式
6.1 利用Shadow与Active切换缓冲区
一种典型流程如下:
6.2 为什么Ping-Pong常用“传输开始中断”
若开启 CHINTMODE = Beginning,新 Transfer 开始时:
- 本轮 Shadow 已经复制到 Active;
- 修改 Shadow 不会影响本轮正在进行的传输;
- 上一块缓冲区刚刚完成,可以交给 CPU 处理。
因此,传输开始中断很适合连续双缓冲。普通单次块复制则更常使用传输结束中断。
7. 结合DMA状态图理解完整流程
手册中的 DMA State Diagram 把触发、Shadow 加载、Burst、Transfer、Wrap、Continuous 和 OneShot 全部连接在一起。

DMA状态机流程
可以把复杂状态图压缩为下面的主流程:
等待触发
↓
开始Transfer,Shadow复制到Active
↓
装载BURST_COUNT
↓
读取源地址并写入目的地址
↓
若Burst未结束:地址加BURST_STEP,继续搬下一个Word
↓
Burst结束:地址加TRANSFER_STEP或执行Wrap
↓
若Transfer未结束:
ONESHOT=1 → 直接执行下一个Burst
ONESHOT=0 → 等待下一次触发
↓
Transfer结束:
CONTINUOUS=1 → 重新初始化并继续等待
CONTINUOUS=0 → 通道停止
7.1 HALT点为什么只出现在特定位置
DMA 不能在任意半个总线周期立即停止。收到 HALT、仿真暂停或 Channel 1 高优先级抢占后,DMA 通常会先完成当前读写访问,再在手册标出的 HALT 点暂停。
这样可以避免出现“源数据已经读取,但目的数据尚未写入”的半完成状态。
8. 16位与32位模式最容易误解的地方
8.1 总线一次搬运的数据宽度
- DATASIZE = 0:一次读写 16 位;
- DATASIZE = 1:一次读写 32 位。
8.2 Size和Step始终按16位地址单元计算
即使处于 32 位模式:
- 一个连续 32 位数据跨 2 个 16 位地址单元;
- 地址前进一个 32 位元素时,Step 应为 2;
- 搬运 3 个 32 位元素时,Size 参数对应 6 个 16 位地址单元。
寄存器直接配置和 Driverlib 配置还存在一层区别:
| 直接写 BURST_SIZE 寄存器 | 写 7 |
| DMA_configBurst() | 参数传 8 |
Driverlib 接收实际数量,并在库函数内部转换为硬件寄存器需要的“数量减 1”。
9. DMA流水线与吞吐率怎样计算
F28379D DMA 使用三级流水线,将地址产生、源数据读取和目的数据写入重叠执行。

DMA三级流水线
无仲裁冲突时,每个 Word 约需要 3 个系统时钟周期。另外,每个 Burst 开始时还会增加 1 个周期的开销。
若一共包含
N
b
N_b
Nb 个 Burst,每个 Burst 搬运
N
w
N_w
Nw 次 DMA Word,则近似周期数为:
N
c
y
c
l
e
=
N
b
(
3
N
w
+
1
)
N_{cycle}=N_b(3N_w+1)
Ncycle=Nb(3Nw+1)
手册给出的示例是搬运 128 个 16 位 Word,并配置为 8 个 Burst、每个 Burst 16 个 Word:
N
c
y
c
l
e
=
8
×
(
3
×
16
+
1
)
=
392
N_{cycle}=8\\times(3\\times16+1)=392
Ncycle=8×(3×16+1)=392
若系统时钟为 200 MHz,则理想时间为:
t
=
392
200
×
10
6
=
1.96
μ
s
t=\\frac{392}{200\\times10^6}=1.96\\ \\mu s
t=200×106392=1.96 μs
将相同数据按 32 位搬运时,一次读写可覆盖两个 16 位数据,手册示例约为 200 个周期。32 位模式不是让总线时钟翻倍,而是相同一次 DMA 操作搬运了两倍数据。
实际时间可能更长,原因包括:
- Burst 启动开销;
- CPU 或 CLA 访问相同外设帧;
- 多个 DMA 通道竞争;
- RAM 等待状态;
- Channel 1 高优先级抢占后恢复带来的额外周期。
10. CPU、CLA与DMA仲裁怎样影响实时性
DMA 与 CPU 通常可以并行工作,但并不意味着完全互不影响。
10.1 会发生冲突的情况
- CPU 访问 SPI FIFO,DMA 同时访问 PMBus;
- CLA 访问 DAC-A,DMA 同时访问 DAC-B;
- CPU 与 DMA 同时访问同一外设帧资源。
同一类型外设的不同实例也可能共享接口,因此 CAN-A 与 CAN-B 之间仍可能产生冲突。
10.2 不发生冲突的情况
- CPU 访问 ePWM,DMA 访问 SPI;
- CPU 访问 GS0,DMA 访问 GS1;
- CPU、CLA、DMA 同时读取各自的 ADC Result 访问路径。
10.3 CPU会被暂停多久
CPU 只会等待当前冲突的 DMA 访问完成,而不是等待整个 Burst 或整个 Transfer 完成。
因此,DMA 能显著降低 CPU 指令负担,但在极端总线竞争下仍可能增加 CPU 访问延迟。对严格实时的控制代码,应避免 CPU 高频访问 DMA 正在持续写入的同一外设帧或内存地址。
11. 六个DMA通道怎样分配优先级
F28379D 支持两种通道优先级模式。
11.1 Round-Robin轮询模式
默认模式下,6 个通道优先级相同,按下列顺序循环:
CH1 → CH2 → CH3 → CH4 → CH5 → CH6 → CH1 → …
每个通道完成一个 Burst 后,状态机检查下一个待处理通道。因此,即使某个通道不断收到触发,也不容易永久占用 DMA。
11.2 Channel 1高优先级模式
开启 Channel 1 高优先级后:
- CH1 高于 CH2~CH6;
- CH2~CH6 之间仍采用 Round-Robin;
- CH1 触发到来时,低优先级通道先完成当前 Word;
- 随后暂停低优先级通道,执行完整 CH1 Burst;
- CH1 Burst 完成后,原通道继续运行。
这种模式适合高速 ADC 采样。ADC 数据如果不能及时读取,可能被后续采样覆盖,而普通 RAM 块复制通常可以容忍更大延迟。
需要注意,Channel 1 高优先级模式不能与 CH1 的 ONESHOT 模式同时使用。否则 CH1 一次触发可能执行非常长的完整 Transfer,使其他通道长期得不到服务。
Driverlib 配置如下:
DMA_setPriorityMode(true); // CH1高优先级
若传入 false,则使用默认 Round-Robin。
12. Overrun到底代表什么
DMA 的触发锁存器只能表示“当前存在一个尚未处理的触发”。当 PERINTFLG 已经为 1 时,又有新的同通道触发到来,额外事件无法继续排队,DMA 会设置 OVRFLG。

DMA Overrun检测逻辑
Overrun 说明:
外设产生数据或事件的速度,已经超过 DMA 当前配置和总线条件下的服务能力。
Overrun 可能造成某次触发丢失,因此不能只清标志而不分析原因。
常见原因包括:
- Burst 太长;
- ONESHOT Transfer 太长;
- 多个通道同时高频触发;
- DMA 与 CPU/CLA 频繁争用同一总线;
- 触发周期小于最坏情况下 DMA 服务时间;
- Channel 1 未设置为高优先级;
- 软件在通道尚未处理完时连续 DMA_forceTrigger()。
Driverlib 可通过下面的函数处理:
DMA_enableInterrupt(DMA_CH1_BASE);
DMA_enableOverrunInterrupt(DMA_CH1_BASE);
if(DMA_getOverflowFlag(DMA_CH1_BASE))
{
DMA_clearErrorFlag(DMA_CH1_BASE);
}
Overrun 中断与正常传输中断共用同一通道中断输出,因此还需要在 ISR 中判断 DMA_getOverflowFlag()。
13. MODE寄存器各位控制什么

DMA MODE寄存器
MODE 中最关键的字段如下:
| CHINTE | 使能通道 CPU 中断 |
| DATASIZE | 选择 16 位或 32 位数据宽度 |
| CONTINUOUS | Transfer 结束后是否自动重新初始化 |
| ONESHOT | 一次触发执行一个 Burst 还是整个 Transfer |
| CHINTMODE | 在 Transfer 开始还是结束时产生中断 |
| PERINTE | 使能外设事件和软件强制触发 |
| OVRINTE | 使能 Overrun 中断 |
| PERINTSEL | 选择通道内部触发输入 |
对应的 Driverlib 函数主要是:
DMA_configMode();
DMA_enableTrigger();
DMA_enableInterrupt();
DMA_enableOverrunInterrupt();
DMA_setInterruptMode();
14. CONTROL寄存器各位控制什么

DMA CONTROL寄存器
CONTROL 既包含控制位,也包含运行状态位:
| OVRFLG | 检测到触发 Overrun |
| RUNSTS | 通道处于运行/待触发状态 |
| BURSTSTS | 当前 Burst 正在执行或被暂停 |
| TRANSFERSTS | 当前 Transfer 尚未结束 |
| PERINTFLG | 存在待处理触发 |
| ERRCLR | 清除错误标志 |
| PERINTCLR | 清除待处理触发 |
| PERINTFRC | 软件强制触发 |
| SOFTRESET | 通道软复位 |
| HALT | 完成当前读写后暂停通道 |
| RUN | 启动或恢复通道 |
手册指出,写 SOFTRESET 后存在一个周期延迟。软件在继续访问其他 DMA 寄存器前,应至少插入一个周期的等待,例如执行一个 NOP。
15. 每个通道为什么有这么多寄存器

DMA通道寄存器总览
通道寄存器可以按功能分为五组:
15.1 模式与控制
- MODE;
- CONTROL。
15.2 Burst参数
- BURST_SIZE;
- BURST_COUNT;
- SRC_BURST_STEP;
- DST_BURST_STEP。
15.3 Transfer参数
- TRANSFER_SIZE;
- TRANSFER_COUNT;
- SRC_TRANSFER_STEP;
- DST_TRANSFER_STEP。
15.4 Wrap参数
- SRC_WRAP_SIZE;
- SRC_WRAP_COUNT;
- SRC_WRAP_STEP;
- DST_WRAP_SIZE;
- DST_WRAP_COUNT;
- DST_WRAP_STEP。
15.5 地址参数
- Source Begin Shadow/Active;
- Source Current Shadow/Active;
- Destination Begin Shadow/Active;
- Destination Current Shadow/Active。
其中 COUNT 和 ACTIVE 主要用于观察硬件运行状态;SIZE、STEP 和 SHADOW 是软件配置的核心。
16. Driverlib配置顺序

DMA Driverlib配置顺序
推荐按下面的顺序配置:
DMA_initController();
DMA_configAddresses();
DMA_configBurst();
DMA_configTransfer();
DMA_configWrap();
DMA_configMode();
DMA_clearTriggerFlag();
DMA_clearErrorFlag();
DMA_setInterruptMode();
DMA_enableInterrupt();
DMA_enableOverrunInterrupt();
DMA_enableTrigger();
DMA_startChannel();
需要先完成所有地址和模式配置,再启动通道。运行过程中若必须重新配置,应先停止或软复位通道,避免软件修改寄存器时硬件仍在读取。
17. 实例一:GS RAM到GS RAM搬运128个16位数据
下面示例使用 DMA CH1 将 128 个 16 位数据从源数组复制到目标数组。
配置为:
- 每个 Burst:8 个 Word;
- 每个 Transfer:16 个 Burst;
- ONESHOT 关闭,因此需要 16 次软件触发;
- Transfer 结束时产生中断;
- 16 位数据宽度;
- 源地址和目的地址连续递增。
ramgs0 和 ramgs1 需要在工程链接命令文件中映射到对应 GS RAM,并保证这些 RAM 归当前 CPU 子系统访问。
#include "driverlib.h"
#include "device.h"
#include <stdbool.h>
#include <stdint.h>
#define DMA_CH_BASE DMA_CH1_BASE
#define WORDS_PER_BURST 8U
#define BURSTS_PER_TRANSFER 16U
#define TOTAL_WORDS (WORDS_PER_BURST * BURSTS_PER_TRANSFER)
#pragma DATA_SECTION(g_srcData, "ramgs0");
#pragma DATA_SECTION(g_dstData, "ramgs1");
static uint16_t g_srcData[TOTAL_WORDS];
static uint16_t g_dstData[TOTAL_WORDS];
static volatile bool g_dmaDone = false;
static volatile bool g_dmaOverrun = false;
__interrupt void dmaCh1ISR(void)
{
if(DMA_getOverflowFlag(DMA_CH_BASE))
{
g_dmaOverrun = true;
DMA_clearErrorFlag(DMA_CH_BASE);
}
else
{
g_dmaDone = true;
}
Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP7);
}
static void initDMA(void)
{
DMA_initController();
// 默认使用Round-Robin;本例只有一个通道
DMA_setPriorityMode(false);
// 注意参数顺序:目的地址在前,源地址在后
DMA_configAddresses(DMA_CH_BASE, g_dstData, g_srcData);
// 每个Burst搬8个16位Word,源和目的在Burst内均连续加1
DMA_configBurst(DMA_CH_BASE,
WORDS_PER_BURST,
1,
1);
// 一个Transfer包含16个Burst
// 每个Burst结束后,当前地址停在最后一个Word,因此再加1进入下一组
DMA_configTransfer(DMA_CH_BASE,
BURSTS_PER_TRANSFER,
1,
1);
// Wrap周期大于本次Transfer,可视为不使用Wrap
DMA_configWrap(DMA_CH_BASE,
65536U, 0,
65536U, 0);
DMA_configMode(DMA_CH_BASE,
DMA_TRIGGER_SOFTWARE,
DMA_CFG_ONESHOT_DISABLE |
DMA_CFG_CONTINUOUS_DISABLE |
DMA_CFG_SIZE_16BIT);
DMA_setInterruptMode(DMA_CH_BASE, DMA_INT_AT_END);
DMA_clearTriggerFlag(DMA_CH_BASE);
DMA_clearErrorFlag(DMA_CH_BASE);
DMA_enableInterrupt(DMA_CH_BASE);
DMA_enableOverrunInterrupt(DMA_CH_BASE);
DMA_enableTrigger(DMA_CH_BASE);
DMA_startChannel(DMA_CH_BASE);
}
int main(void)
{
uint16_t i;
bool dataCorrect = true;
Device_init();
Device_initGPIO();
Interrupt_initModule();
Interrupt_initVectorTable();
Interrupt_register(INT_DMA_CH1, &dmaCh1ISR);
Interrupt_enable(INT_DMA_CH1);
for(i = 0U; i < TOTAL_WORDS; i++)
{
g_srcData[i] = (uint16_t)(0x1000U + i);
g_dstData[i] = 0U;
}
initDMA();
EINT;
ERTM;
// ONESHOT关闭:每次软件触发只执行一个Burst
for(i = 0U; i < BURSTS_PER_TRANSFER; i++)
{
DMA_forceTrigger(DMA_CH_BASE);
// 等待本次触发被接收,并等待Burst执行结束
while(DMA_getTriggerFlagStatus(DMA_CH_BASE) ||
DMA_getBurstStatusFlag(DMA_CH_BASE))
{
}
}
while(!g_dmaDone && !g_dmaOverrun)
{
}
if(!g_dmaOverrun)
{
for(i = 0U; i < TOTAL_WORDS; i++)
{
if(g_dstData[i] != g_srcData[i])
{
dataCorrect = false;
break;
}
}
}
else
{
dataCorrect = false;
}
// 在CCS Expressions窗口观察:
// g_srcData、g_dstData、g_dmaDone、g_dmaOverrun、dataCorrect
while(1)
{
NOP;
}
}
17.1 改成一次触发完成整个Transfer
将模式改为:
DMA_CFG_ONESHOT_ENABLE
随后只执行一次:
DMA_forceTrigger(DMA_CH_BASE);
DMA 就会连续完成 16 个 Burst。该方式适合一次性 RAM 块复制,但不适合与多个高频 DMA 通道长期共享带宽。
18. 实例二:ADCARESULT0连续写入采样数组
假设 ADC 和 ADCAINT1 已经配置完成,每次 ADCAINT1 到来只保存 ADCARESULT0 一个结果。
此时一个触发对应一个 Word,因此:
- Burst 长度为 1;
- 源地址始终是同一个 ADC Result 寄存器;
- 目的地址每个 Burst 结束后前进 1;
- 所以目的地址递增应配置在 Transfer Step,而不是 Burst Step。
#define ADC_SAMPLE_COUNT 256U
#pragma DATA_SECTION(g_adcBuffer, "ramgs0");
static uint16_t g_adcBuffer[ADC_SAMPLE_COUNT];
static void initADCDMA(void)
{
const void *adcResult0 =
(const void *)(ADCARESULT_BASE + ADC_O_RESULT0);
DMA_initController();
DMA_setPriorityMode(true); // CH1用于高速ADC
DMA_configAddresses(DMA_CH1_BASE,
g_adcBuffer,
adcResult0);
// 每次触发只读一个ADC结果
DMA_configBurst(DMA_CH1_BASE,
1U,
0,
0);
// 源地址固定,目的地址每次Burst结束后加1
DMA_configTransfer(DMA_CH1_BASE,
ADC_SAMPLE_COUNT,
0,
1);
DMA_configWrap(DMA_CH1_BASE,
65536U, 0,
65536U, 0);
DMA_configMode(DMA_CH1_BASE,
DMA_TRIGGER_ADCA1,
DMA_CFG_ONESHOT_DISABLE |
DMA_CFG_CONTINUOUS_DISABLE |
DMA_CFG_SIZE_16BIT);
DMA_setInterruptMode(DMA_CH1_BASE, DMA_INT_AT_END);
DMA_clearTriggerFlag(DMA_CH1_BASE);
DMA_clearErrorFlag(DMA_CH1_BASE);
DMA_enableInterrupt(DMA_CH1_BASE);
DMA_enableOverrunInterrupt(DMA_CH1_BASE);
DMA_enableTrigger(DMA_CH1_BASE);
DMA_startChannel(DMA_CH1_BASE);
}
18.1 为什么DST_BURST_STEP不是1
因为 Burst 只有一个 Word。状态机完成这个 Word 后,Burst 已经结束,不会进入“Burst 内下一个 Word”的地址更新分支。
因此,目标数组下标的前进发生在 Burst 完成后的 DST_TRANSFER_STEP = 1。
这是 ADC 单样本 DMA 配置中最常见的错误之一。
19. 实例三:一次触发读取ADCRESULT0到ADCRESULT3
假设一次 ADC 中断代表 4 个 SOC 均已完成,需要把 RESULT0~RESULT3 连续写入数组。
每个 Burst 读取 4 个寄存器:
RESULT0 → RESULT1 → RESULT2 → RESULT3
Burst 结束时源活动地址停在 RESULT3。下一次触发应重新回到 RESULT0,因此:
S
R
C
_
T
R
A
N
S
F
E
R
_
S
T
E
P
=
−
3
SRC\\_TRANSFER\\_STEP=-3
SRC_TRANSFER_STEP=−3
目的地址需要继续写入数组下一位置,因此:
D
S
T
_
T
R
A
N
S
F
E
R
_
S
T
E
P
=
1
DST\\_TRANSFER\\_STEP=1
DST_TRANSFER_STEP=1
配置示例:
DMA_configAddresses(DMA_CH1_BASE,
g_adcBuffer,
(const void *)(ADCARESULT_BASE + ADC_O_RESULT0));
DMA_configBurst(DMA_CH1_BASE,
4U,
1,
1);
DMA_configTransfer(DMA_CH1_BASE,
ADC_SAMPLE_COUNT / 4U,
–3,
1);
地址变化如下:
第1次触发:RESULT0、RESULT1、RESULT2、RESULT3
源地址停在RESULT3,再加-3,回到RESULT0
第2次触发:RESULT0、RESULT1、RESULT2、RESULT3
源地址再次回到RESULT0
通过修改 Burst Step 和 Transfer Step,DMA 不仅能搬运数据,还能自动完成外设寄存器组的周期扫描。
20. 实例四:SPI接收寄存器写入RAM
SPI RXBUF 的地址固定,DMA 每次都从同一寄存器读取,因此源地址 Step 通常为 0;目的数组需要连续写入,因此目的 Step 为正数。
若每个 SPI DMA 触发读取一个 16 位数据:
DMA_configAddresses(DMA_CH2_BASE,
spiRxBuffer,
(const void *)(SPIA_BASE + SPI_O_RXBUF));
DMA_configBurst(DMA_CH2_BASE,
1U,
0,
0);
DMA_configTransfer(DMA_CH2_BASE,
SPI_RX_LENGTH,
0,
1);
DMA_configMode(DMA_CH2_BASE,
DMA_TRIGGER_SPIARX,
DMA_CFG_ONESHOT_DISABLE |
DMA_CFG_CONTINUOUS_DISABLE |
DMA_CFG_SIZE_16BIT);
SPI 只支持 16 位 DMA 数据宽度,不能把 DMA 通道配置为 32 位后直接读取 SPI 数据寄存器。
21. 调试DMA时应观察哪些变量和寄存器
在 CCS Expressions 或 Registers 窗口中,优先观察以下状态:
21.1 通道是否真正启动
- RUNSTS = 1:通道已经启动并等待或执行;
- RUNSTS = 0:通道未启动,或非连续 Transfer 已经完成。
21.2 触发是否到达
- PERINTFLG = 1:有触发尚未开始服务;
- 始终为 0:检查外设触发源、DMACHSRCSEL、PERINTSEL 和 PERINTE。
21.3 Burst和Transfer是否在运行
- BURSTSTS = 1:正在执行或暂停于当前 Burst;
- TRANSFERSTS = 1:完整 Transfer 尚未结束。
21.4 当前地址是否按预期变化
- SRC_ADDR_ACTIVE;
- DST_ADDR_ACTIVE;
- SRC_BEG_ADDR_ACTIVE;
- DST_BEG_ADDR_ACTIVE。
如果 Active 地址完全不变化,通常是通道未收到触发;如果地址跳变过大,通常是 Step 配置错误;如果每次 Transfer 都从错误位置开始,通常是 Shadow 地址或 Continuous 配置错误。
21.5 是否发生Overrun
- OVRFLG = 1;
- DMA_getOverflowFlag() = true。
Overrun 是重要的实时性故障信号,不应在 ISR 中无条件清除后忽略。
22. DMA参数设计的实用检查表
在写代码前,可以先把任务填写成下面的形式:
| 触发源是什么 | ADCAINT1 |
| 每次触发搬几个16位地址单元 | 1 |
| 完整缓冲区需要几个Burst | 256 |
| 源地址在Burst内怎样变化 | 固定不变 |
| 目的地址在Burst内怎样变化 | Burst为1,不变化 |
| Burst结束后源地址怎样变化 | 0 |
| Burst结束后目的地址怎样变化 | +1 |
| 是否需要Wrap | 否 |
| 一个触发执行一个Burst还是整个Transfer | 一个Burst |
| Transfer结束后是否继续 | 根据单次或连续采集决定 |
| 中断在开始还是结束产生 | 单缓冲通常在结束 |
| 是否需要CH1高优先级 | 高速ADC通常需要 |
只要这张表能够明确填写,DMA 配置通常就不会混乱。
23. 总结
F28379D DMA 的难点并不在 API 数量,而在理解三种地址更新发生的时刻:
- Word 之间使用 BURST_STEP;
- Burst 之间使用 TRANSFER_STEP;
- 达到指定 Burst 数后使用 WRAP_STEP 更新 Begin Address。
同时还需要记住:
- Shadow 保存下一次 Transfer 的软件配置;
- Active 保存当前 Transfer 的硬件运行地址;
- ONESHOT 决定一次触发覆盖一个 Burst 还是整个 Transfer;
- CONTINUOUS 决定 Transfer 结束后是否继续待命;
- Channel 1 高优先级适合高速、低延迟采样;
- Overrun 代表 DMA 服务速度跟不上触发速度;
- Driverlib 的 Size 参数是实际数量,底层 SIZE 寄存器才是数量减 1;
- 所有 Size 和 Step 均以 16 位地址单元为基准。
当这些关系理解清楚后,ADC 连续采样、SPI 收发、PWM 参数更新、RAM 块复制、循环缓冲和 Ping-Pong 双缓冲,本质上都只是不同的地址与触发组合。
参考资料
- Texas Instruments, TMS320F2837xD Dual-Core Real-Time Microcontrollers Technical Reference Manual, Direct Memory Access Chapter, SPRUHM8K, revised May 2024.
- Texas Instruments, F2837xD Driverlib API Guide – DMA Module.
