TMS320F28P65x DMA 功能详细总结

一、概述

F28P65是TI C2000™实时微控制器家族中的一款高性能双核器件,集成了两个6通道DMA(直接存储器访问)控制器,每个CPU子系统各配备一个。

DMA作为总线主设备,通过专用总线直接在存储器与外设之间传输数据,无需CPU干预,从而有效释放CPU带宽以处理控制算法等关键任务。通过使用DMA移动数据,CPU可以节省大量指令周期用于其他关键任务。

F28P65x在辅助C28x CPU中还添加了锁步双CPU比较器选项以及ePIE和DMA,用于检测永久性和瞬态故障。

二、DMA 架构

2.1 基本拓扑

F28P65x的DMA子系统架构如下:

  • DMA 1 控制器:6通道,专属于CPU1子系统
  • DMA 2 控制器:6通道,专属于CPU2子系统

每个DMA控制器均包含完整的寄存器组和独立的通道控制机制,支持多通道并行配置,在诸如UART通信等应用中可以同时使用6个DMA通道。

查看具体参数:

特性 F28P65x
DMA控制器数量 每个CPU一个(共2个)
每控制器通道数 6
总通道数 12
数据类型 16位/32位

2.2 总线架构

DMA作为总线主设备集成在C28x总线架构中。根据外设分配的不同,可对各外设和配置寄存器进行独立访问配置。外设可单独分配给CPU1或CPU2子系统,例如ePWM可分配给CPU1,eQEP可分配给CPU2。DMA传输通过专用总线完成,不会干扰CPU的正常指令执行。

三、关键功能特性

3.1 数据位宽

  • 支持16位和32位两种数据位宽
  • 32位传输的数据吞吐量是16位传输的2倍
  • 当设置为32位时,以下参数值的意义按32位字长考虑:Burst Size、Source Burst Step、Source Transfer Step、Destination Burst Step、Destination Transfer Step、Source Wrap Step、Destination Wrap Step
  • 为McBSP等特定外设提供DMA服务时,数据位宽须设置为16位

3.2 地址指针与寻址

  • 支持针对源地址和目标地址独立配置的起始地址、步进和回绕参数
  • 源和目标地址指针可按配置值在传输过程中实现增量、减量或固定不变
  • 支持通过BURST步进和TRANSFER步进参数控制地址指针在每帧内和每帧后的滑动
  • 错误检测机制:当SRC_WRAP_SIZE、DST_WRAP_SIZE ≥ BURST_SIZE时,可独立触发SRC_WRAP_ERROR或DST_WRAP_ERROR错误。软件必须确保SRC_WRAP_SIZE(步进数 - 1)和DST_WRAP_SIZE均为BURST_SIZE的整数倍

3.3 一维与二维传输

C2000 DMA支持二维数据传输,适合矩阵运算和数据块重组:

  • 每个BURST(子帧)可配置BURST_SIZE个16位字(1 – 32),每帧内可编程TRANSFER_SIZE个BURST(1 – 65,536)
  • 每BURST传输后按BURST步进滑动源/目标地址;完成一次完整Transfer后可选自动回绕到起始地址
  • 灵活配置BURST_SIZE、TRANSFER_SIZE、SRC_BEG_ADDR、DST_BEG_ADDR和通道链(CHAIN)可实现数据块重排

四、触发与中断

4.1 触发源

DMA传输通过外设中断事件触发。可与各种外设中断事件联动,实现全自动数据搬运。常见触发源包括:

  • ePWM事件
  • ADC转换完成中断
  • SPI中断(CPU中断信号)
  • 定时器中断(需手动配置)
  • 外部中断(通过GPIO)
  • eCAP模块DMA事件

NMA中断触发源可由PERINTSEL位(MODE寄存器的位4–0)选择,支持多种中断触发选项。对于外部中断通过GPIO触发的情况,需在External Interrupt选项卡中进行配置。

4.2 传输流程

DMA一次完整传输由以下组成:

  1. 发送源地址
  2. 读取源数据
  3. 发送目标地址
  4. 写入目标数据

传输完成后可选产生中断通知CPU。当CPU访问与DMA传输产生冲突时,CPU会暂停等待当前DMA访问完成,而无需等待整个DMA传输周期结束,显著降低通信延迟。

五、配置参数详解

DMA通道的主要配置参数如下:

参数 说明 取值范围
Data Size 数据位宽 16位 或 32位
Interrupt Source 外设中断触发源选择 视MCU型号而定
Burst Size 每BURST包含的字数 1–32(16位字)
Transfer Size 每次传输包含的BURST数 1–65536
Source Begin Address 源起始地址 22位
Destination Begin Address 目标起始地址 22位
Source Burst Step 源地址每BURST步进 -4096 至 +4095
Source Transfer Step 源地址每Transfer步进 -4096 至 +4095
Destination Burst Step 目标地址每BURST步进 -4096 至 +4095
Destination Transfer Step 目标地址每Transfer步进 -4096 至 +4095
Source Wrap Size 源地址回绕大小 可编程
Destination Wrap Size 目标地址回绕大小 可编程

六、应用示例与工作流

6.1 ADC-PWM协同采集

以下以ADC定期采样并通过DMA搬移数据为例,展示F28P65x中DMA的典型应用:

  1. 触发生成:ePWM模块以选定采样率生成PWM波形,ADC对波形进行采样
  2. 自动触发:每次ADC转换完成后自动触发DMA
  3. 乒乓缓冲:DMA将ADC样本搬移到自定义的Ping-Pong双缓冲中

实现Ping-Pong缓冲方案的优势在于,CPU总是在DMA填满一个缓冲区时处理另一个,实现无数据丢失的连续数据流处理。

6.2 UART多通道通信

F28P65x支持使用DMA实现UART多通道通信,最多可同时使用6个DMA通道。这允许系统在完全不依赖CPU干预的情况下实现高速串行数据流处理。

6.3 EtherCAT PDI访问

在F28P65x的EtherCAT功能开发中,DMA触发源不包含EtherCAT ISR,但可通过ESC SYNC信号触发DMA,实现无需CPU参与的EtherCAT PDI数据搬运。

七、典型配置流程

F28P65x中DMA通道的典型初始化与配置流程如下:

第1步:使能DMA时钟,将外设分配给对应CPU子系统

第2步:配置MODE寄存器,设置DATASIZE位(位14)和PERINTSEL触发源(位4-0)

第3步:配置BURST_SIZE寄存器(位4-0),取值范围1–32

第4步:配置TRANSFER_SIZE寄存器(位15-0),取值范围1–65536

第5步:设置源和目标地址——SRC_BEG_ADDR和DST_BEG_ADDR(均为22位地址)

第6步:配置BURST步进和TRANSFER步进参数

第7步:在CONTROL寄存器中清除错误和外设标志

第8步:使能DMA通道(置位需要的控制位)

第9步:配置外设对应触发源,在CHAIN寄存器完成通道链设置(如需)

第10步:使能DMA中断(可选,用于通知CPU传输完成)

八、常见问题与注意事项

常见问题 说明与建议
无法触发 检查触发源是否已正确选择到外设中断信号引脚
地址递增异常 确认BURST步进与TRANSFER步进是否正确配置
数据不完整 确认Transfer Size和Burst Size设定是否满足每次传输的数据需求
竞争条件 若CPU和DMA同时访问同一外设或内存区,需使用适当的标志或互斥机制
与SCI不可用 F28P65x的DMA不访问SCI寄存器,应改用UART实例;可参考TI的UART DMA例程
EtherCAT ISR缺失的DMA触发 EtherCAT ISR本身不能作为DMA触发源;解决方案是使用ESC SYNC信号来触发DMA

九、功能安全支持

F28P65x具备工业领先的实时安全特性,支持:

  • 锁步CPU(CPU2子系统),用于检测永久和瞬态硬件故障
  • DMA信息冗余技术:包括SECDED码(ECC)、奇偶校验码、CRC等,确保数据完整性
  • 设计目标满足ASIL D和SIL 3功能安全等级要求
  • 硬件ADC结果自动校验,HW BIST(硬件内置自检)

这些特性使F28P65x特别适用于需要高可靠性实时控制的应用场景,如汽车动力总成、工业电机驱动和可再生能源系统。

十、总结

TMS320F28P65x的DMA子系统以每CPU独立的6通道控制器为骨架,提供灵活的16/32位数据传输能力,支持从简单的一维外设到内存搬移到复杂的二维矩阵重组等多种传输模式,并可通过ePWM、ADC、定时器、SPI、eCAP等多种外设中断事件触发全自动数据搬运。结合Ping-Pong双缓冲、通道链(CHAIN)、功能安全冗余(锁步DMA、ECC、CRC)和实时固件更新(LFU)等高级特性,该DMA为高性能伺服驱动、机器人关节控制、电动汽车牵引逆变器、工业以太网通信等高实时性应用场景提供了强大的硬件支撑。


由于纯文本环境无法真正渲染图片,我用 ASCII 字符画 来展示。
这些图会围绕你文档中提到的 Burst SizeTransfer Size,帮你直观理解一次完整的 DMA 传输是如何组织的。


关于这Burst Size和Transfer Size的意义能不能画几张图给我说明一下

1. 基础定义(回顾)

  • Burst:DMA 收到一次触发后,连续搬运的一组数据。
  • Burst Size (BURST_SIZE):一个 Burst 里包含的 字数(16位或32位)。
  • Transfer:由多个 Burst 组成的完整搬运任务。
  • Transfer Size (TRANSFER_SIZE):Transfer 中包含的 Burst 个数

👉 总搬运字数 = BURST_SIZE × TRANSFER_SIZE


2. 图一:一次完整 DMA 传输的结构

假设:
BURST_SIZE = 4(每触发1次搬4个字)
TRANSFER_SIZE = 3(总共触发3次,即3个Burst)

      Burst 0                Burst 1                Burst 2
+-----+-----+-----+-----+  +-----+-----+-----+-----+  +-----+-----+-----+-----+
| W0  | W1  | W2  | W3  |  | W4  | W5  | W6  | W7  |  | W8  | W9  | W10 | W11 |
+-----+-----+-----+-----+  +-----+-----+-----+-----+  +-----+-----+-----+-----+
|        4 words        |  |        4 words        |  |        4 words        |
|<---- Burst Size=4 --->|  |<---- Burst Size=4 --->|  |<---- Burst Size=4 --->|

|<----------------- Transfer Size = 3 Bursts ------------------------------->|
                          总搬运 12 字
  • 每一次外设触发(如 ADC EOC)到来,DMA 就会搬完一个 Burst(W0~W3)。
  • 搬完一个 Burst 后,地址按 SRC_BURST_STEP / DST_BURST_STEP 更新。
  • 搬满 3 个 Burst 后,整个 Transfer 结束,可产生中断。

3. 图二:固定源地址 → 线性缓冲区的搬运(ADC 典型应用)

场景:ADC 结果寄存器地址不变,DMA 把采样值顺序搬到内存数组 AdcBuf[12]

配置:

  • 源地址 = &AdcResultReg (固定)
  • 目标地址 = &AdcBuf[0] (初始)
  • BURST_SIZE = 4TRANSFER_SIZE = 3
  • SRC_BURST_STEP = 0 (源地址不变)
  • DST_BURST_STEP = 1 (目标地址每搬完一个字 +1)

DMA 搬运过程(地址视图)
(每格代表一个 16/32 位字)

目标内存 (AdcBuf)                源(ADC 结果寄存器,始终保持同一地址)
+----+----+----+----+----+----+----+----+----+----+----+----+
| W0 | W1 | W2 | W3 | W4 | W5 | W6 | W7 | W8 | W9 | W10| W11|   <-- 数据
+----+----+----+----+----+----+----+----+----+----+----+----+
  ^    ^    ^    ^    ^    ^    ^    ^    ^    ^    ^    ^
  |    |    |    |    |    |    |    |    |    |    |    |
  Burst0 addr 递增1       Burst1 addr 递增1       Burst2 addr 递增1

源地址(固定):  [ ADC_RESULT ]  ← 每次触发都从同一个寄存器读
  • 第 1 次触发(Burst0):目标地址从 &AdcBuf[0] 走到 &AdcBuf[3]
  • 第 2 次触发(Burst1):接着从 &AdcBuf[4] 走到 &AdcBuf[7]
  • 第 3 次触发(Burst2):接着从 &AdcBuf[8] 走到 &AdcBuf[11]

结果:CPU 看到的就是一个完整的、按时间顺序排列的采样数组。


4. 图三:二维数据重组(比如 ADC 多通道扫描)

核心难点:如何让源地址“跑回去”?

在 ADC 多通道扫描中,物理硬件通常只有一个数据寄存器(或者一组固定的结果寄存器)。

  • 第 1 次触发:数据在地址 0x1000 (Ch0), 0x1002 (Ch1)…
  • 第 2 次触发:数据依然在地址 0x1000, 0x1002

DMA 搬完第 1 次后,源地址指针已经跑到了 0x1008。如果不加干预,第 2 次搬运就会从 0x1008 开始读,那就读错了!

解决方案:必须配置**回绕(Wrap)**功能。


详细参数配置与原理(修正版)

假设场景
  • ADC 结果寄存器:假设 Ch0~Ch3 对应 4 个连续的寄存器地址(或者一个FIFO),起始地址 SRC = 0x1000
  • 内存目标AdcBuf[12],起始地址 DST = 0x2000
  • 目标结构:3 行(3次触发)× 4 列(4个通道)。
关键参数配置表
参数 设定值 详细解释(为什么要这么配?)
BURST_SIZE 4 一次触发产生 4 个数据,所以一次突发搬运 4 个字。
TRANSFER_SIZE 3 总共要采 3 轮,所以总共触发 3 次 Burst。
SRC_BURST_STEP 1 在同一个 Burst 内,读完 Ch0 读 Ch1,地址 +1(假设寄存器是连续的)。
DST_BURST_STEP 1 在内存里,Ch0~Ch3 也是挨着存的,地址 +1。
DST_TRANSFER_STEP 0 重点:因为 BURST 已经把一行填满了,地址自动停在行尾。通常不需要额外的 Transfer Step,或者设为 0 靠 Burst 自动衔接(取决于具体 DMA 架构,通常 Burst 结束后地址就是下一个 Burst 的起点)。
SRC_TRANSFER_STEP 0 重点:我们不希望源地址在 Burst 之间增加,我们希望它回绕!所以这里设为 0(或者不使能)。
SRC_WRAP_SIZE 4 回绕的核心! 告诉 DMA:“每搬运 4 个字(2^4 = 16字节,假设字宽匹配),就把源地址强制拉回到该区域的起始地址。”

图解过程:回绕是如何发生的?

我们用您喜欢的字符图来演示这个**“读取-回绕-写入”**的过程。

设定

  • SRC_START = 0x1000 (ADC基地址)
  • DST_START = 0x2000 (内存基地址)
  • SRC_WRAP_SIZE = 4 (意味着每 4 个字,源地址回绕到 0x1000)
      Burst 0                Burst 1                Burst 2
+-----+-----+-----+-----+  +-----+-----+-----+-----+  +-----+-----+-----+-----+
| Ch0 | Ch1 | Ch2 | Ch3 |  | Ch0 | Ch1 | Ch2 | Ch3 |  | Ch0 | Ch1 | Ch2 | Ch3 |
+-----+-----+-----+-----+  +-----+-----+-----+-----+  +-----+-----+-----+-----+
|<---- SRC_WRAP=4 ---->|  |<---- SRC_WRAP=4 ---->|  |<---- SRC_WRAP=4 ---->|
|   (地址回绕复位)      |  |   (地址回绕复位)      |  |   (地址回绕复位)      |

      Burst 0                Burst 1                Burst 2
+-----+-----+-----+-----+  +-----+-----+-----+-----+  +-----+-----+-----+-----+
| W0  | W1  | W2  | W3  |  | W0  | W1  | W2  | W3  |  | W0  | W1  | W2  | W3  |
+-----+-----+-----+-----+  +-----+-----+-----+-----+  +-----+-----+-----+-----+
|<---- DST_WRAP=4 ---->|  |<---- DST_WRAP=4 ---->|  |<---- DST_WRAP=4 ---->|
|   (地址回绕覆盖)      |  |   (地址回绕覆盖)      |  |   (地址回绕覆盖)      |

|<----------------- Transfer Size = 3 Bursts ------------------------------->|
                          总搬运 12 字(循环覆盖)

总结:两个关键参数的作用

  1. SRC_WRAP_SIZE (源地址回绕大小)
    • 作用:它是**“橡皮筋”**。它定义了一个边界(这里是 4 个字)。一旦源地址在这个边界内走完了(Burst 结束),它就立刻被弹回起始点。
    • 结果:保证了每一次新的触发(Burst 1, Burst 2…),DMA 都是从 ADC 的第一个通道寄存器开始读,而不是接着上次的地址读。
  2. SRC_TRANSFER_STEP (源地址传输步长)
    • 作用:它是**“额外偏移”**。在有些复杂的 DMA 中,Transfer Step 是在 Wrap 之后额外加的偏移。
    • 本例配置:设为 0。因为我们希望完全回绕到起点,不需要额外的偏移。如果设为 1,回绕后还会再加 1,导致数据错位。

一句话概括: 通过配置 SRC_WRAP_SIZE,我们让源地址在每次 Burst 结束后“瞬移”回起点;而目标地址没有配置 Wrap,所以它会老老实实地一直往后写,从而实现了将“重复的源数据”展开成“线性的目标矩阵”。

总结:
这个例子的精髓在于 BURST_SIZE 定义了一行的宽度,而 TRANSFER_SIZE 定义了有多少行。DMA 只需要负责“一行一行”地线性铺展,就天然形成了二维矩阵。


5. 关键参数作用总结

参数 控制范围 作用时机
BURST_SIZE 1 ~ 32 决定一个 Burst 搬运的字数
TRANSFER_SIZE 1 ~ 65536 决定一个 Transfer 包含多少个 Burst
SRC/DST BURST STEP -4096 ~ +4095 每个 Burst 结束后,源/目标地址的增量
SRC/DST TRANSFER STEP -4096 ~ +4095 每个 Transfer 结束后,地址的增量(常配合回绕)
SRC/DST WRAP SIZE 可编程 当计数器达到该值时,地址回绕到起始地址

通过组合这些参数,你就能实现一维缓冲二维矩阵重组环形缓冲区等各种数据搬运模式,完全脱离 CPU 干预。


如果还想看 Ping‑Pong 双缓冲、或环形缓冲区的地址回绕图,我可以再补几张 ASCII 图。

5. DMA例子

假设配置如下(基于你的截图):

Burst Size = 2 (每次触发搬运2个字)
Transfer Size = 3 (总共搬运3次这样的突发)
Burst Step = 1
Transfer Step = 1

详细解释burst step和transfer step:

这两个参数是DMA(直接存储器访问)配置中决定数据搬运路径的关键。如果说 Burst SizeTransfer Size 决定了“搬运多少数据”,那么 Burst StepTransfer Step 则决定了“数据搬到哪里去”以及“地址如何跳跃”。

结合你提供的截图(TI C2000或类似MCU的配置界面),我将用通俗易懂的方式为你解释:

1. Source Address Burst Step(源地址突发步长)

  • 含义:在一次 Burst(突发) 传输内部,每搬运完一个数据单元后,源地址增加的数值。
  • 通俗理解:这是“车厢内货物的间距”。当火车(Burst)开始搬运时,机械臂(DMA)从第一个位置抓取货物后,下一次抓取要往哪个方向移动多少距离?
  • 截图中的设置1
    • 这意味着:每搬运一个字(Word),地址自动加1,指向下一个连续的内存单元。
    • 如果是 0:地址不变,意味着反复读取同一个寄存器的值(常用于读取FIFO)。
    • 如果是 2:地址跳着走(例如用于只读取偶数地址的数据)。

2. Source Address Transfer Step(源地址传输步长)

  • 含义:当一次 Burst(突发) 传输全部完成后,为了准备下一次 Burst,源地址需要增加的数值。
  • 通俗理解:这是“两列火车之间的发车间距”。第一列火车(第一个Burst)装完货后,指针要跳多远才能找到第二列火车的起始装货点?
  • 截图中的设置1
    • 这意味着:第一组 Burst 搬完后,地址在原有基础上加1,开始下一轮搬运。

3. 核心区别与配合(重点!)

为了彻底搞懂这两个 Step 的区别,我们需要看它们是如何配合工作的。

假设配置如下(基于你的截图):

  • Burst Size = 2 (每次触发搬运2个字)
  • Transfer Size = 3 (总共搬运3次这样的突发)
  • Burst Step = 1
  • Transfer Step = 1

DMA 搬运过程的“地址变化”推演:

阶段 动作描述 当前源地址 (假设起始为 0x100) 说明
第 1 次 Burst 搬运第 1 个字 0x100 起始位置
(内部循环) 应用 Burst Step (+1)
搬运第 2 个字 0x101 Burst Size=2 完成,第1次触发结束
第 1 次 Burst 结束 应用 Transfer Step (+1) 0x102 关键点: 指针移到 0x102 准备下一轮
第 2 次 Burst 搬运第 3 个字 0x102 第2次触发开始
(内部循环) 应用 Burst Step (+1)
搬运第 4 个字 0x103 Burst Size=2 完成
第 2 次 Burst 结束 应用 Transfer Step (+1) 0x104 指针移到 0x104
第 3 次 Burst 搬运第 5 个字 0x104 第3次触发开始

4. 为什么要区分这两个 Step?(应用场景)

你可能会问,为什么不只用一个 Step?因为它们可以实现高级的数据重组

  • 场景一:普通连续搬运(你的截图情况)

    • Burst Step = 1, Transfer Step = 1
    • 结果:数据像流水一样连续搬运 (0, 1, 2, 3, 4, 5…)。
  • 场景二:二维数组/矩阵搬运(步长不同)

    • 假设你想从一个矩阵中,每次只搬运第一列的数据。
    • 你可以设置 Burst Size = 1Transfer Step = 矩阵宽度
    • 这样每次 Burst 只搬一个数,然后地址直接跳过一行,去搬下一行的对应位置。
  • 场景三:循环缓冲(步长为0)

    • 如果 Transfer Step = 0,意味着每次 Burst 完成后,地址回到原点(或保持不变)。这常用于将外设的数据不断覆盖写入到同一个内存缓冲区中。

6. DMA过程转换成C语言过程

根据:
在这里插入图片描述
在这里插入图片描述

/**
 * @file    dma_simulator.c
 * @brief   模拟TI DMA控制器的状态机实现(基于Figure 10-4状态图)
 * @details 实现完整的DMA传输流程:
 *          - 等待触发事件
 *          - 影子寄存器到活动寄存器的复制
 *          - 突发(Burst)内逐字传输
 *          - 传输(Transfer)完成后的地址步进与回绕(Wrap)
 *          - 传输结束后的连续(Continuous)/单次(Oneshot)模式处理
 *          - 中断产生(模拟)
 */

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

/*------------------------ 模拟DMA通道的寄存器结构 ------------------------*/
typedef struct {
    // ---------- 控制寄存器 ----------
    uint8_t  CHINTMODE;     // 中断模式:0-传输开始时产生中断,1-传输结束时产生中断(图中仅演示CHINTMODE==0情况)
    uint8_t  CONTINUOUS;    // 连续模式:1-传输完成后自动重新开始,0-传输完成后停止
    uint8_t  ONESHOT;       // 单次模式:1-每次触发只执行一次完整传输(之后需等待下次触发),0-连续模式下不需触发直接重传
    uint8_t  RUNSTS;        // 通道运行状态:1-运行中,0-停止

    // ---------- 影子寄存器(用户配置) ----------
    uint32_t SRC_BEG_ADDR_SHADOW;   // 源起始地址(影子)
    uint32_t DST_BEG_ADDR_SHADOW;   // 目标起始地址(影子)
    uint32_t SRC_ADDR_SHADOW;       // 源当前地址(影子,通常用于调试)
    uint32_t DST_ADDR_SHADOW;       // 目标当前地址(影子)

    // ---------- 活动寄存器(硬件实际使用) ----------
    uint32_t SRC_BEG_ADDR_ACTIVE;   // 源起始地址(活动)
    uint32_t DST_BEG_ADDR_ACTIVE;   // 目标起始地址(活动)
    uint32_t SRC_ADDR_ACTIVE;       // 源当前地址(活动)
    uint32_t DST_ADDR_ACTIVE;       // 目标当前地址(活动)

    // ---------- 步长与尺寸 ----------
    uint32_t SRC_BURST_STEP;        // 突发内每次传输后源地址增量(单位:字/字节,按实际寻址单位)
    uint32_t DST_BURST_STEP;        // 突发内每次传输后目标地址增量
    uint32_t SRC_TRANSFER_STEP;     // 每个传输(突发)完成后源地址增量
    uint32_t DST_TRANSFER_STEP;     // 每个传输(突发)完成后目标地址增量

    uint32_t BURST_SIZE;            // 突发长度(每个突发包含的传输次数)
    uint32_t TRANSFER_SIZE;         // 传输长度(包含的突发次数)

    // ---------- 回绕(Wrap)控制 ----------
    uint32_t SRC_WRAP_SIZE;         // 源回绕周期(多少次传输后执行一次回绕)
    uint32_t DST_WRAP_SIZE;         // 目标回绕周期
    uint32_t SRC_WRAP_STEP;         // 源回绕时基地址的增量
    uint32_t DST_WRAP_STEP;         // 目标回绕时基地址的增量

    // ---------- 状态计数器 ----------
    uint32_t SRC_WRAP_COUNT;        // 源当前回绕计数(递减到0时回绕)
    uint32_t DST_WRAP_COUNT;        // 目标当前回绕计数
    uint32_t TRANSFER_COUNT;        // 当前剩余传输(突发)次数
    uint32_t BURST_COUNT;           // 当前突发内剩余传输次数

    // ---------- 状态标志(仅用于模拟) ----------
    uint8_t  BURST_STS;             // 突发进行中标志(1-进行中)
    uint8_t  TRANSFER_STS;          // 传输进行中标志(1-进行中)

    // ---------- 外部触发信号(模拟用) ----------
    uint8_t  trigger_pending;       // 是否有等待处理的触发事件
} DMA_Channel;

/* 模拟产生EPIE中断(根据CHINTMODE==0在传输开始时调用) */
static void GenerateEPIEInterrupt(DMA_Channel *ch) {
    printf("[INT] EPIE interrupt generated at %s\n",
           (ch->CHINTMODE == 0) ? "beginning of DMA transfer" : "end of transfer");
}

/* 模拟单次内存传输(实际硬件中为 *dst = *src,此处打印并模拟地址偏移) */
static void DoWordTransfer(DMA_Channel *ch) {
    // 在实际硬件中,这里完成从源地址到目标地址的数据复制
    printf("  [XFER] *0x%08X = *0x%08X  (value simulated)\n",
           ch->DST_ADDR_ACTIVE, ch->SRC_ADDR_ACTIVE);
    // 注意:真实DMA会更新内存内容,本例仅模拟地址操作
}

/* 从影子寄存器更新活动寄存器(传输开始时执行) */
static void CopyShadowToActive(DMA_Channel *ch) {
    ch->SRC_BEG_ADDR_ACTIVE = ch->SRC_BEG_ADDR_SHADOW;
    ch->DST_BEG_ADDR_ACTIVE = ch->DST_BEG_ADDR_SHADOW;
    ch->SRC_ADDR_ACTIVE     = ch->SRC_ADDR_SHADOW;
    ch->DST_ADDR_ACTIVE     = ch->DST_ADDR_SHADOW;

    printf("[INIT] Shadow -> Active: SRC_BEG=0x%08X, DST_BEG=0x%08X, SRC_CUR=0x%08X, DST_CUR=0x%08X\n",
           ch->SRC_BEG_ADDR_ACTIVE, ch->DST_BEG_ADDR_ACTIVE,
           ch->SRC_ADDR_ACTIVE, ch->DST_ADDR_ACTIVE);
}

/* 主DMA状态机:执行一次完整的传输(从触发开始直到通道停止或进入等待) */
void DMA_RunTransfer(DMA_Channel *ch) {
    /* ---------- 状态1:等待触发事件 ---------- */
    while (!ch->trigger_pending) {
        // 实际硬件在此处暂停(HALT),等待触发信号
        // 模拟中可简单循环或依赖外部设置trigger_pending
    }
    // 清除触发标志,开始一次传输
    ch->trigger_pending = 0;
    printf("\n[DMA] Trigger received, starting transfer...\n");

    /* ---------- 状态2:传输开始 ---------- */
    // 若CHINTMODE==0,在传输开始时产生中断
    if (ch->CHINTMODE == 0) {
        GenerateEPIEInterrupt(ch);
    }

    // 复制所有影子寄存器到活动寄存器
    CopyShadowToActive(ch);

    // 初始化回绕计数器(WRAP_COUNT = WRAP_SIZE)
    ch->SRC_WRAP_COUNT = ch->SRC_WRAP_SIZE;
    ch->DST_WRAP_COUNT = ch->DST_WRAP_SIZE;

    // 初始化传输计数器(TRANSFER_COUNT = TRANSFER_SIZE)
    ch->TRANSFER_COUNT = ch->TRANSFER_SIZE;

    // 设置进行中标志(对应图中 TRANSFER_STS = 1)
    ch->TRANSFER_STS = 1;
    printf("[DMA] TRANSFER_STS=1, TRANSFER_COUNT=%d\n", ch->TRANSFER_COUNT);

    /* ---------- 状态3:传输循环(处理每一个突发) ---------- */
    while (ch->TRANSFER_COUNT > 0) {
        // 初始化突发计数器(BURST_COUNT = BURST_SIZE)
        ch->BURST_COUNT = ch->BURST_SIZE;
        ch->BURST_STS = 1;          // 突发进行中(图中 BURST_IN_PROGRESS)
        printf("[DMA] Starting new burst, BURST_COUNT=%d\n", ch->BURST_COUNT);

        /* ---------- 状态4:突发内逐字传输循环 ---------- */
        while (ch->BURST_COUNT > 0) {
            // 执行一次字传输:*DST_ADDR_ACTIVE = *SRC_ADDR_ACTIVE
            DoWordTransfer(ch);

            // 更新源和目标地址(加突发步长)
            ch->SRC_ADDR_ACTIVE += ch->SRC_BURST_STEP;
            ch->DST_ADDR_ACTIVE += ch->DST_BURST_STEP;

            // 突发计数器减1
            ch->BURST_COUNT--;

            if (ch->BURST_COUNT > 0) {
                printf("  Burst continues, %d words left\n", ch->BURST_COUNT);
            }
        } // 突发完成

        // 突发完成,清除BURST_STS标志
        ch->BURST_STS = 0;
        printf("[DMA] Burst complete. Updating transfer step...\n");

        // 一个传输(突发)完成后,更新地址(加传输步长)
        ch->SRC_ADDR_ACTIVE += ch->SRC_TRANSFER_STEP;
        ch->DST_ADDR_ACTIVE += ch->DST_TRANSFER_STEP;

        // 传输计数器减1
        ch->TRANSFER_COUNT--;
        printf("[DMA] Transfer count now = %d\n", ch->TRANSFER_COUNT);

        /* ---------- 状态5:地址回绕(Wrap)处理 ---------- */
        // 根据图2说明,每次burst结束后有两种地址更新方法:
        // Method 1(默认): WRAP_SIZE > TRANSFER_SIZE - 地址简单递增,不回绕
        // Method 2(回绕): WRAP_SIZE < TRANSFER_SIZE - 支持多次回绕
        
        // 源地址处理
        if (ch->SRC_WRAP_SIZE > ch->TRANSFER_SIZE) {
            // Method 1: 回绕禁用,地址已在前面(152行)简单递增
            printf("[ADDR] Method 1 (No Wrap): SRC_ADDR_ACTIVE = 0x%08X\n", ch->SRC_ADDR_ACTIVE);
        } else {
            // Method 2: 回绕使能,支持多次回绕
            if (ch->SRC_WRAP_COUNT > 0) {
                ch->SRC_WRAP_COUNT--;
            }
            if (ch->SRC_WRAP_COUNT == 0) {
                printf("[WRAP] Method 2 (Wrap): Source wrap triggered\n");
                ch->SRC_BEG_ADDR_ACTIVE += ch->SRC_WRAP_STEP;
                ch->SRC_ADDR_ACTIVE = ch->SRC_BEG_ADDR_ACTIVE;
                ch->SRC_WRAP_COUNT = ch->SRC_WRAP_SIZE;
            }
        }

        // 目标地址处理
        if (ch->DST_WRAP_SIZE > ch->TRANSFER_SIZE) {
            // Method 1: 回绕禁用,地址已在前面(153行)简单递增
            printf("[ADDR] Method 1 (No Wrap): DST_ADDR_ACTIVE = 0x%08X\n", ch->DST_ADDR_ACTIVE);
        } else {
            // Method 2: 回绕使能,支持多次回绕
            if (ch->DST_WRAP_COUNT > 0) {
                ch->DST_WRAP_COUNT--;
            }
            if (ch->DST_WRAP_COUNT == 0) {
                printf("[WRAP] Method 2 (Wrap): Destination wrap triggered\n");
                ch->DST_BEG_ADDR_ACTIVE += ch->DST_WRAP_STEP;
                ch->DST_ADDR_ACTIVE = ch->DST_BEG_ADDR_ACTIVE;
                ch->DST_WRAP_COUNT = ch->DST_WRAP_SIZE;
            }
        }

        // 如果还有剩余传输,则继续下一个突发;否则退出循环
    } // 传输循环结束

    // 传输完成,清除TRANSFER_STS标志
    ch->TRANSFER_STS = 0;
    printf("[DMA] TRANSFER_STS=0, all transfers completed.\n");

    /* ---------- 状态6:传输结束后的连续/单次模式处理 ---------- */
    if (ch->CONTINUOUS) {
        // 连续模式使能
        if (ch->ONESHOT) {
            // 单次模式:等待下一次DMA触发事件
            printf("[DMA] CONTINUOUS=1, ONESHOT=1 -> waiting for next trigger.\n");
            // 通道保持运行状态(RUNSTS保持不变),但需要重新等待触发
            // 状态机将回到最开始的“等待触发事件”状态
            // 注意:此处不清除影子到活动的复制,下次触发时会重新复制
        } else {
            // 非单次模式(即连续自动重传):立即重新开始,无需等待触发
            printf("[DMA] CONTINUOUS=1, ONESHOT=0 -> restarting immediately without trigger.\n");
            // 重新初始化所有活动寄存器(但保留影子配置不变)
            // 相当于自动产生一个内部触发
            ch->trigger_pending = 1;   // 设置一个内部触发标志,让下一次循环立即开始
        }
    } else {
        // 非连续模式:通道停止
        ch->RUNSTS = 0;
        printf("[DMA] CONTINUOUS=0, channel disabled (RUNSTS=0).\n");
    }
}

/* 模拟外部触发DMA通道(例如PWM、ADC等外设触发) */
void DMA_Trigger(DMA_Channel *ch) {
    if (ch->RUNSTS) {
        ch->trigger_pending = 1;
        printf("[EVENT] External DMA trigger received.\n");
    } else {
        printf("[EVENT] Trigger ignored, channel is disabled (RUNSTS=0).\n");
    }
}

/* ---------------------------- 示例使用 ---------------------------- */
int main() {
    // 初始化DMA通道(配置参数)
    DMA_Channel myDMA = {0};

    // 配置控制模式
    myDMA.CHINTMODE  = 0;      // 传输开始时产生中断
    myDMA.CONTINUOUS = 1;      // 使能连续模式
    myDMA.ONESHOT    = 1;      // 单次模式(每次触发执行一轮完整传输,之后等待下次触发)
    myDMA.RUNSTS     = 1;      // 使能通道

    // 设置地址(模拟内存区域)
    myDMA.SRC_BEG_ADDR_SHADOW = 0x20000000;   // 源缓冲区起始
    myDMA.DST_BEG_ADDR_SHADOW = 0x30000000;   // 目标缓冲区起始
    myDMA.SRC_ADDR_SHADOW     = 0x20000000;
    myDMA.DST_ADDR_SHADOW     = 0x30000000;

    // 步长配置(以32位字为单位)
    myDMA.SRC_BURST_STEP    = 1;   // 突发内每次传输后源地址+1字
    myDMA.DST_BURST_STEP    = 1;   // 目标地址+1字
    myDMA.SRC_TRANSFER_STEP = 4;   // 每个突发完成后源地址额外+4字
    myDMA.DST_TRANSFER_STEP = 4;   // 每个突发完成后目标地址+4字

    // 尺寸配置
    myDMA.BURST_SIZE    = 3;       // 每个突发包含3次字传输
    myDMA.TRANSFER_SIZE = 2;       // 总共2个突发(即传输6个字)

    // 回绕配置(每完成1次传输后回绕一次,基地址步进8字)
    myDMA.SRC_WRAP_SIZE = 1;       // 每1个传输后执行回绕
    myDMA.SRC_WRAP_STEP = 8;       // 回绕时源基地址增加8字
    myDMA.DST_WRAP_SIZE = 1;
    myDMA.DST_WRAP_STEP = 8;

    // 运行状态机(首次传输需要外部触发)
    printf("=== DMA State Machine Simulation ===\n");
    printf("Configuration: BURST_SIZE=%d, TRANSFER_SIZE=%d, CONTINUOUS=%d, ONESHOT=%d\n\n",
           myDMA.BURST_SIZE, myDMA.TRANSFER_SIZE, myDMA.CONTINUOUS, myDMA.ONESHOT);

    // 模拟外部触发第一次传输
    DMA_Trigger(&myDMA);
    DMA_RunTransfer(&myDMA);   // 执行传输过程(会阻塞直到传输完成或进入等待)

    // 由于配置了CONTINUOUS=1, ONESHOT=1,传输完成后通道会等待下一次触发
    // 再次触发第二次传输
    printf("\n--- Second trigger ---\n");
    DMA_Trigger(&myDMA);
    DMA_RunTransfer(&myDMA);

    // 若修改为CONTINUOUS=1, ONESHOT=0,则会自动循环永不停止(本例未演示)
    printf("\n=== End of simulation ===\n");
    return 0;
}

总结

  • Burst Step微观调整:管的是“这一趟里,数据怎么排”。
  • Transfer Step宏观调整:管的是“这一趟跑完了,下一趟从哪里开始”。

在你的截图中,两者都设为 1,代表最标准的线性连续搬运模式,即数据在内存中是一个挨着一个紧密排列的。

参考资料

  • TMS320F28P65x Real-Time Microcontrollers Technical Reference Manual (SPRUIZ1B)
  • TMS320F28P65x Real-Time Microcontrollers Data Sheet (SPRSP69)
  • TMS320F28P65x Functional Safety Manual(SFFS700)
  • C2000™ F28P65x Real-Time Microcontrollers Errata (SPRT766)
  • TI E2E支持论坛
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐