1. μDMA控制器:嵌入式系统数据搬运的“高速公路”
在嵌入式系统开发中,尤其是处理音频流、图像采集、高速通信这类数据密集型任务时,CPU如果亲自下场,一个字节一个字节地从外设搬运到内存,那效率简直惨不忍睹。这就好比让一个高级工程师去干快递分拣的活儿,大材小用不说,整个系统的响应也会被拖慢。这时,我们就需要一位“专职搬运工”——DMA(Direct Memory Access,直接内存访问)控制器。
而德州仪器(TI)在其许多微控制器中集成的μDMA控制器,可以看作是这位“搬运工”中的高级工程师。它不仅仅能完成简单的“从A搬到B”的任务,更支持多种复杂的传输模式,如乒乓缓冲、散聚传输等,能够应对各种实时、高效的数据流处理场景。理解μDMA,就是掌握了在资源受限的嵌入式环境中,榨干硬件性能、实现高效数据吞吐的关键钥匙。无论你是正在调试一个音频采集卡,还是优化网络数据包的接收效率,μDMA的合理运用都能让你事半功倍。
2. μDMA核心架构与寄存器模型解析
要驾驭μDMA,首先得摸清它的“工作台”和“工具箱”。μDMA的运作核心围绕两个部分:位于系统内存中的通道控制结构表,以及一组映射到特定地址的控制寄存器。
2.1 通道控制结构:传输任务的“蓝图”
μDMA的每个通道都有两份独立的“任务蓝图”,称为主控制结构和备用控制结构。它们被组织在一个由软件定义基地址的表格中,这个表格就是通道控制结构表。
表 2-1: 通道控制结构表内存布局示例
| 偏移量 | 通道 | 结构类型 |
|---|---|---|
| 0x0 | 通道 0 | 主结构 |
| 0x10 | 通道 1 | 主结构 |
| ... | ... | ... |
| 0x1F0 | 通道 31 | 主结构 |
| 0x200 | 通道 0 | 备用结构 |
| 0x210 | 通道 1 | 备用结构 |
| ... | ... | ... |
| 0x3F0 | 通道 31 | 备用结构 |
每个控制结构都是一个16字节对齐的条目,包含四个32位字(长字),定义了单次传输的所有参数:
- 源结束指针:指向传输源区域的最后一个地址(包含该地址)。如果源地址不递增(例如指向一个外设数据寄存器),那么这个指针就直接指向该寄存器地址本身。
- 目标结束指针:指向传输目标区域的最后一个地址(包含该地址)。同样,对于非递增的目标地址,它指向目标寄存器。
- 控制字:这是传输的“大脑”,包含了数据大小、地址增量、传输模式、仲裁大小等核心配置。关键点在于,控制字会在传输过程中被μDMA控制器修改(例如,传输完成后,传输大小字段会被清零,模式字段会被设为停止)。因此,在每次发起新传输前,软件必须重新配置控制字。而源和目标结束指针如果地址不变,则可以保持原值。
- 保留字:目前未使用,为未来功能预留。
> 注意:“结束指针”这个概念初看可能有点反直觉。我们通常习惯用“起始地址”和“数据量”来定义一个内存区域。μDMA采用“结束指针”加“传输项数”的方式,其优势在于硬件实现更简单高效。计算时,务必确保结束指针 = 起始地址 + (传输项数 * 数据项大小) - 1。
2.2 关键控制寄存器:指挥中心的“开关”
除了内存中的蓝图,CPU还需要通过一组硬件寄存器来指挥μDMA控制器。这些寄存器通常映射到固定的外设地址空间(例如TI CC32xx系列的基地址为0x400F.F000)。
表 2-2: μDMA关键寄存器功能速览
| 寄存器名称 | 偏移量 | 类型 | 核心功能 |
|---|---|---|---|
| DMA_ENASET | 0x028 | R/W | 通道使能设置。写1到对应位,使能相应通道。传输启动前必须操作。 |
| DMA_ENACLR | 0x02C | WO | 通道使能清除。写1到对应位,禁用相应通道。传输完成后控制器会自动清除。 |
| DMA_SWREQ | 0x014 | WO | 软件请求寄存器。写1到对应位,手动触发指定通道的传输(需配合自动模式使用)。 |
| DMA_CFG | 0x004 | WO | DMA全局配置。必须写1使能整个μDMA控制器模块,否则所有通道无法工作。 |
| DMA_CTLBASE | 0x008 | R | 控制表基址指针。只读,反映当前正在使用的控制结构表在内存中的基地址。 |
| DMA_ERRCLR | 0x04C | R/W | 总线错误清除。读取可判断是否发生传输错误(如访问非法地址),写1清除错误标志。 |
> 实操心得:在初始化μDMA时,务必遵循正确的顺序:先使能模块时钟,等待至少3个系统时钟周期,再配置DMA_CFG寄存器使能控制器,接着在内存中设置好控制结构表,并让DMA_CTLBASE指向它,最后才通过DMA_ENASET使能具体通道。顺序错误可能导致配置不生效或产生不可预知的行为。
3. 深入剖析传输模式:从简单搬运到智能调度
μDMA的强大之处在于其丰富的传输模式。选择正确的模式,是优化性能的关键。控制字中的XFERMODE字段决定了通道的行为模式。
3.1 基础模式与自动模式:单次任务的执行
这两种模式适用于相对简单的、一次性的传输任务。
- 基础模式:这是最“听话”的模式。控制器仅在存在传输请求且传输项数未完成时进行传输。一旦请求信号消失,即使数据没传完,传输也会暂停。这非常适合那些能够持续、稳定发出请求信号的外设,比如一个填满才请求的UART接收FIFO。但对于软件触发的单次传输,如果使用基础模式,可能只会传输
ARBSIZE指定的一小部分数据后就停止,因为软件请求是“瞬时”的。 - 自动模式:这是基础模式的“增强版”。一旦收到一个传输请求(哪怕是瞬时的软件请求),它就会一口气运行到完成,无视中途请求信号是否消失。因此,自动模式是软件触发传输的理想选择。它让CPU“踢一脚”就能完成全部搬运工作,然后通过中断得到完成通知。
> 模式选择核心原则:
- 外设持续请求-> 选基础模式。例如:ADC连续转换完成、DAC需要连续输出数据。
- 软件触发或单次请求-> 选自动模式。例如:将一块预处理好的数据发送到显示器缓冲区。
3.2 乒乓模式:实现无缝数据流
乒乓模式是处理连续数据流的“神器”,它能有效避免数据搬运和处理之间的竞争,实现“生产-消费”的无缝衔接。
其核心思想是使用两个缓冲区(对应主、备两个控制结构)和两次中断。工作流程如下:
- 初始化:CPU设置好通道的主、备两个控制结构,分别指向缓冲区A和缓冲区B,并启动传输(通常从主结构开始)。
- 第一阶段:μDMA使用主结构(缓冲区A)进行传输。同时,CPU可以处理上一次传输完成的数据(例如,从缓冲区B读取处理)。
- 第一次中断:主结构传输完成。μDMA自动切换到备用结构(缓冲区B)继续传输,并产生一个中断。
- CPU响应:CPU在中断服务程序中,重新填充或处理刚刚传输完的缓冲区A的数据,并更新其控制结构,为下一次切换做好准备。
- 循环往复:当备用结构传输完成时,控制器又切回主结构,并再次产生中断。CPU则去处理缓冲区B。如此“乒乓”切换,数据流得以持续不断。
> 避坑技巧:在乒乓模式的中断服务程序中,除了处理数据,务必检查并更新控制字中的传输项数。因为μDMA在完成传输后会修改控制字(将传输大小清零)。如果你只是修改了缓冲区指针,而忘了重置传输大小,下一次传输将无法进行。
3.3 散聚模式:驾驭非连续内存的“高级魔法”
当数据源或目的地不是一块连续的内存,而是分散在多个不同区域时,散聚模式就派上用场了。它分为内存散聚和外设散聚。
内存散聚/聚集:此模式下,CPU在内存中预先定义一个“任务列表”,列表中每一项都是一个完整的控制结构(包含源、目标指针和控制字)。μDMA通道的主控制结构被配置为:从“任务列表”中,将下一个任务项拷贝到自身的备用控制结构中,然后执行它。
- 应用场景:聚集——从网络协议栈中收集多个不连续的数据包有效载荷,拼接成一个完整的应用层报文。分散——将一个大的数据块,分散写入到多个不同的目标缓冲区。
外设散聚:与内存散聚类似,但传输的触发权交给了外设。只有外设发出请求时,μDMA才从任务列表中读取下一项并执行。这适用于外设数据就绪时间不固定的场景。
> 核心机制理解:可以把散聚模式中的“主控制结构”想象成一个“调度员”,它的工作不是直接搬数据,而是不断地从“任务清单”(内存中的列表)里取出下一个“搬运工任务单”(一个控制结构),交给“搬运工”(备用控制结构)去执行。任务清单的末尾是一个设置为基础模式的任务项,执行完它,整个散聚传输链才真正结束并产生完成中断。
4. 控制字精讲与传输配置实战
控制字是μDMA的灵魂,所有传输行为都由其中的字段精细控制。我们以DMA_CHCTL寄存器为例,拆解每一个关键位。
4.1 数据大小与地址增量
这是配置中最容易出错的地方之一。
SRCSIZE/DSTSIZE:定义单次传输操作的数据位宽,可选8、16、32位。源和目标的尺寸必须相同。SRCINC/DSTINC:定义每次传输后,地址指针的增量。可选字节、半字、字递增,或“不递增”。增量值必须大于或等于数据尺寸。- 不递增:用于访问外设寄存器(如UART数据寄存器),地址始终不变。
- 增量等于数据尺寸:最常用,用于访问连续的存储器数组。
- 增量大于数据尺寸:可用于访问结构体数组中某个特定成员,实现“间隔采样”。
表 4-1: 8位外设数据读取配置示例
| 配置字段 | 设置值 | 说明 |
|---|---|---|
| 源数据大小 | 8位 | 外设提供8位数据 |
| 目标数据大小 | 8位 | 必须与源相同 |
| 源地址增量 | 无 | 外设寄存器地址固定 |
| 目标地址增量 | 字节 | 数据存入内存字节数组 |
| 源结束指针 | 外设FIFO寄存器地址 | |
| 目标结束指针 | 内存缓冲区末尾地址 |
4.2 仲裁大小与传输大小
这两个参数共同决定了传输的“节奏”。
ARBSIZE:仲裁大小。定义μDMA控制器在让出总线控制权之前,连续执行的最大传输次数。设置为0表示1次传输后即仲裁,7表示128次。增大此值可以提高总线利用率(减少仲裁开销),但会延长其他总线主设备(如CPU)的等待时间。需要根据系统实时性要求权衡。XFERSIZE:传输大小。定义本次传输任务总的数据项数量。注意,该字段存储的值是实际项数 - 1。要传输N个数据项,此处应写入N-1。最大支持1024项。
> 参数计算示例:假设需要将ADC的200个16位采样值传输到内存数组adc_buffer中。
- 数据大小:
SRCSIZE = DSTSIZE = 1(16位半字)。 - 地址增量:
SRCINC = 3(不递增,ADC数据寄存器地址固定),DSTINC = 1(半字递增)。 - 传输大小:
XFERSIZE = 200 - 1 = 199。 - 仲裁大小:若系统对实时性要求高,可设
ARBSIZE = 2(4次传输后仲裁);若追求最高吞吐,可设ARBSIZE = 5(32次传输后仲裁)。 - 源结束指针:指向ADC数据寄存器地址。
- 目标结束指针:
&adc_buffer[199](即起始地址 + 199 * 2字节)。
4.3 传输模式字段
XFERMODE字段即我们之前讨论的模式选择,从停止(0)到外设散聚(6)。配置时需确保与传输触发源(软件或外设)相匹配。
5. 外设接口与触发机制详解
μDMA需要与外设协同工作,其接口方式主要分为两类,理解它们对正确配置至关重要。
5.1 带FIFO的外设服务
这是最常见的场景,如UART、SPI、I2S等。这些外设内部有FIFO(先入先出队列)。
- 工作流程:当外设接收FIFO中的数据达到预设水位(如半满),或发送FIFO空出位置时,会向μDMA发出传输请求。μDMA响应请求,以
ARBSIZE定义的突发大小传输数据,直到FIFO水位低于阈值或传输完成。 - UseBurst模式:通过设置
DMA_USEBURSTSET寄存器,可以强制μDMA仅以突发方式(ARBSIZE定义的大小)传输,即使FIFO中数据不足。这能提升总线效率,但对于UART这类通常以单字节或小批量传输的外设,TI不建议启用此模式,否则可能导致FIFO中残留数据无法及时传出。
5.2 触发型外设
如通用定时器(GPT)。这类外设不直接提供数据,而是在特定事件(如定时器匹配、捕获事件)发生时,向μDMA发出一个触发信号。
- 工作流程:触发信号到来,μDMA执行一次由
ARBSIZE定义的传输。如果只需要传输单个数据项,则设置ARBSIZE=1。 - 注意请求丢失:如果外设在μDMA服务当前请求期间,又连续产生了两个新的请求,那么第三个请求将会丢失。这在设计高精度定时触发传输时需要特别注意,确保处理速度跟得上触发频率。
5.3 软件请求
任何通道都可以通过写DMA_SWREQ寄存器来发起软件请求。但强烈建议为纯软件触发的传输分配专用通道,或至少使用未被外设占用的通道。软件触发传输应配合自动模式使用,以确保一次触发完成全部传输。
6. 中断、错误处理与系统集成
6.1 传输完成与错误中断
- 完成中断:当一次μDMA传输(包括散聚模式的整个任务链)完成时,控制器会向外设或软件通道发送
dma_done信号。具体哪个中断向量触发,取决于通道归属:- 外设通道:完成中断走该外设自己的中断向量。
- 软件专用通道:完成中断走μDMA专用的软件中断向量。
- 重要:中断需要在相应外设或NVIC中使能。
- 错误中断:如果μDMA在传输过程中遇到总线错误(如访问了禁止访问的内存区域),它会立即禁用该通道,并产生一个μDMA错误中断。软件可以通过读取
DMA_ERRCLR寄存器来查询和清除错误标志,并进行相应的错误恢复处理(如重置缓冲区、报告错误等)。
6.2 与GPIO联动作为触发源
μDMA的灵活性还体现在它可以被GPIO引脚的电平或边沿变化触发。通过配置APPS_GPIO_TRIG_EN等寄存器,可以将某个GPIO引脚配置为μDMA的外部触发源。
- 应用场景:外部ADC转换完成信号、另一个处理器发出的数据就绪信号、自定义硬件同步信号等。
- 配置步骤:
- 配置GPIO引脚为输入模式,并设置所需的中断检测类型(边沿或电平)。
- 在GPIO中断配置中,不使能其通向CPU的中断,而是通过特定寄存器将其映射为μDMA触发信号。
- 配置μDMA通道为自动模式或基础模式,并将其请求源关联到该GPIO触发。
- 当GPIO出现指定电平时,μDMA传输自动启动。
7. 实战配置流程与常见问题排查
7.1 标准配置流程 checklist
- 系统准备:使能μDMA模块时钟,等待至少3个系统时钟周期。
- 全局使能:写
DMA_CFG寄存器使能μDMA控制器。 - 内存规划:在内存中定义通道控制结构表(通常是一个全局数组),并确保其地址按16字节对齐(某些平台有要求)。
- 基址设置:将控制表基址赋值给
DMA_CTLBASE寄存器(通常通过写某个特定寄存器或由启动代码完成)。 - 通道结构配置:为你使用的通道填写主(和备用)控制结构:
- 设置源/目标结束指针。
- 配置控制字(大小、增量、模式、仲裁大小、传输大小)。
- 务必将控制字的模式字段初始化为
停止模式。
- 通道使能:通过
DMA_ENASET寄存器使能目标通道。 - 触发传输:
- 外设触发:配置外设使其在适当时机发出DMA请求。
- 软件触发:写
DMA_SWREQ寄存器相应位。
7.2 常见问题与调试技巧
问题1:传输根本没启动。
- 检查:模块时钟和全局配置
DMA_CFG是否已使能?控制结构表基址DMA_CTLBASE是否正确?通道是否通过DMA_ENASET使能?控制字中的传输模式是否不是停止?对于外设触发,外设的DMA请求是否已使能?
问题2:传输启动了,但没传完就停了。
- 检查:如果是软件触发,是否错误地使用了基础模式?应使用自动模式。检查外设请求信号是否在基础模式下意外撤销。检查
XFERSIZE设置是否正确(是N-1)。
问题3:传输数据错位或覆盖。
- 检查:
SRCINC/DSTINC设置是否正确?是否与SRCSIZE/DSTSIZE匹配?计算源/目标结束指针时,是否考虑了数据项大小?结束指针 = 起始地址 + (项数 * 每项字节数) - 1。
问题4:乒乓模式或散聚模式第二次传输不工作。
- 检查:在中断服务程序中,是否重新配置了控制字?μDMA会在传输完成后修改控制字(尤其是
XFERSIZE和XFERMODE)。你需要在切换缓冲区后,重新写入完整的控制字,包括传输大小和模式。
问题5:系统偶尔卡死或数据错误。
- 检查:是否发生了总线错误?查询
DMA_ERRCLR寄存器。检查源/目标地址是否有效(是否可写?是否对齐?)。在散聚模式下,检查任务列表中的控制结构地址和内容是否正确。
> 调试利器:利用完成中断和状态寄存器。在中断服务程序中,不仅可以处理数据,还可以读取通道的控制结构,查看当前的XFERSIZE剩余值,这能帮你判断传输停在了哪里。同时,监控DMA_STAT寄存器可以查看各通道的使能状态。