STM32F4 DMA控制器从入门到精通:嵌入式工程师的实战指南

DMA 直接内存访问 控制器 STM32F4xx 系列 外设 内存 数据传输 CPU 资源 AHB 主总线 FIFO 流 通道 仲裁器 优先级 传输模式 外设到内存 内存到外设 内存到内存 双缓冲 配置 数据宽度 突发传输 循环模式 指针增量 错误管理 流控制器 配置流程 应用指南 嵌入式开发

一、DMA核心概念与价值

在嵌入式系统开发中,CPU的资源是宝贵的。当外设(如ADC、SPI、UART)需要与内存频繁交换大量数据时,如果每次传输都让CPU参与“搬运”,会严重消耗CPU的计算能力,导致系统响应变慢或无法处理其他关键任务。

直接内存访问(DMA)技术正是为了解决这一瓶颈而生。STM32F4系列的DMA控制器就像一个高度智能的“数据搬运工”,它能够在无需CPU干预的情况下,独立完成在外设与内存之间、或者在内存不同区域之间的高速数据传输。这极大地解放了CPU,使其可以专注于算法、逻辑控制等核心任务,从而提升整个系统的效率和性能。

本指南将带你从零开始,透彻理解STM32F4 DMA的工作原理,并掌握其配置与应用方法。

二、STM32F4 DMA控制器架构总览

STM32F4系列微控制器通常配备两个DMA控制器:DMA1 和 DMA2。每个控制器拥有相同但独立的结构。

  1. 核心组成

    • 流(Stream):每个DMA控制器有8个流(Stream 0-7)。流是数据传输的执行单位,你可以把它理解为一个独立的“传输通道”。

    • 通道(Channel):每个流可以关联到最多8个可能的通道请求之一。通道决定了这个流服务于哪个具体的外设(例如,ADC1、SPI1_TX、USART1_RX)。通过软件配置选择。

    • 仲裁器(Arbiter):负责管理多个流同时发出请求时的优先级冲突。

    • FIFO(先进先出缓冲区):每个流都有一个4字(16字节)深度的内部FIFO。它用于暂存数据,是实现灵活数据传输(如数据宽度转换、突发传输)的关键。

    • AHB总线接口:包含一个用于访问内存的AHB主端口、一个用于访问外设的AHB主端口,以及一个用于CPU配置DMA控制器自身的AHB从端口。

  2. 一个重要区别

    • 只有 DMA2 控制器的外设端口连接到了总线矩阵,因此 仅DMA2支持内存到内存的传输。DMA1的外设端口仅能访问外设数据寄存器。

三、DMA传输的三种基本模式

在配置一个DMA流时,你首先需要确定传输方向。

传输模式数据流向典型应用场景可用控制器
外设到内存从外设数据寄存器到内存数组ADC采集数据存入RAM, UART接收数据DMA1, DMA2
内存到外设从内存数组到外设数据寄存器从RAM发送数据到DAC, UART发送数据DMA1, DMA2
内存到内存从一个内存区域到另一个内存区域大数据块复制、快速数据搬移仅DMA2
四、零基础配置DMA流:分步详解

以下步骤是配置和使用任何一个DMA流的通用流程。我们以“使用DMA2的Stream5,将ADC1转换的数据搬运到数组adc_buffer[]中”为例。

步骤1:前期准备与检查

  • 确认你的外设(本例中是ADC1)支持DMA,并查找其对应的DMA请求映射表(参考芯片参考手册表43,44),确定应该使用哪个DMA控制器、哪个流、哪个通道。例如,ADC1对应 DMA2, Stream0/Stream4, Channel 0

  • 在代码中使能DMA控制器的时钟:RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);

步骤2:禁用目标流
在对一个流进行任何关键配置之前,必须确保它处于禁用状态。

c

DMA_Cmd(DMA2_Stream0, DISABLE); // 先禁用流
while (DMA_GetCmdStatus(DMA2_Stream0) != DISABLE); // 等待确认流已停止

警告:在禁用连接到DMA流的外设之前,必须先执行此步骤禁用DMA流。

步骤3:清除中断标志
清除该流可能置起的所有旧状态标志,避免误触发中断。

c

DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_FEIF0 | DMA_IT_DMEIF0 | DMA_IT_TEIF0 | DMA_IT_HTIF0 | DMA_IT_TCIF0);

步骤4:配置流的基本参数
通过DMA_InitStructure结构体进行配置,这是最核心的一步。

c

DMA_InitTypeDef DMA_InitStructure;
DMA_StructInit(&DMA_InitStructure); // 初始化为默认值

DMA_InitStructure.DMA_Channel = DMA_Channel_0;          // 选择通道0 (对应ADC1)
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR); // 外设地址:ADC数据寄存器
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)adc_buffer;     // 内存地址:目标数组
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; // 传输方向:外设到内存
DMA_InitStructure.DMA_BufferSize = 1024;                // 传输数据项数量:1024个
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址固定
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据宽度:16位(ADC分辨率)
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;        // 内存数据宽度:16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;         // 模式:循环模式(持续采集)
DMA_InitStructure.DMA_Priority = DMA_Priority_High;     // 流优先级:高
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;   // 启用FIFO模式(推荐)
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; // FIFO阈值:半满
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; // 内存突发:单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; // 外设突发:单次传输

DMA_Init(DMA2_Stream0, &DMA_InitStructure);

关键参数解读

  • DMA_BufferSize:定义要传输的数据项数量。数据项的大小由DMA_PeripheralDataSizeDMA_MemoryDataSize决定。例如,两者均为HalfWord(2字节),则BufferSize=1024表示传输1024 * 2 = 2048字节。

  • DMA_PeripheralInc/MemoryInc:决定每次传输后地址是否自动增加。外设寄存器地址通常固定,内存地址通常递增。

  • DMA_Mode

    • DMA_Mode_Normal:普通模式。传输完指定数量数据后,DMA自动停止。

    • DMA_Mode_Circular循环模式。传输完成后,NDTR寄存器自动重载初始值,地址指针复位(取决于是否增量),然后重新开始传输。适用于连续数据流(如音频、实时采集)。

  • DMA_FIFOMode

    • 启用:允许使用FIFO,支持数据打包/解包(源和目标宽度不同),支持突发传输。

    • 禁用:直接模式。每次请求立即传输,但要求源和目标数据宽度必须一致。

步骤5:配置中断(可选)
如果需要在数据传输一半或完成时得到通知,需配置NVIC。

c

DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE); // 使能传输完成中断
// DMA_ITConfig(DMA2_Stream0, DMA_IT_HT, ENABLE); // 可选:使能半传输中断

NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

步骤6:使能DMA流
配置完成后,启动DMA流,使其开始等待外设的请求。

c

DMA_Cmd(DMA2_Stream0, ENABLE);

步骤7:配置并启动外设
最后,去配置你的外设(本例是ADC1),使其在需要传输数据时向DMA发出请求。

c

ADC_DMACmd(ADC1, ENABLE); // 使能ADC1的DMA请求
ADC_SoftwareStartConv(ADC1); // 开始ADC转换(假设已配置好ADC)

现在,ADC每转换完一个数据,就会触发DMA请求,DMA控制器会自动将ADC1->DR中的数据搬运到adc_buffer数组中,并自动递增数组下标。当搬运完1024个数据后,由于我们设置了循环模式,DMA会重新开始,覆盖或继续填充数组(取决于应用逻辑)。

五、高级特性与实战技巧
  1. 双缓冲模式

    • 原理:使用两个内存缓冲区(M0AR和M1AR)。当DMA向缓冲区A写入时,CPU可以处理缓冲区B中的数据,实现“乒乓操作”,有效避免数据处理和采集的冲突。

    • 配置:设置DMA_InitStructure.DMA_Mode = DMA_Mode_Circular,并设置DMA_InitStructure.DMA_MemoryBurst = DMA_Memory1BaseAddr,然后使能DMA_DoubleBufferModeCmd(STREAMx, ENABLE)。通过DMA_GetCurrentMemoryTarget()判断当前DMA正在操作哪个缓冲区。

  2. 数据打包与解包

    • 场景:当源和目标数据宽度不一致时(例如,从8位的外设接收数据存到32位的内存中),DMA的FIFO可以自动进行打包/解包。

    • 条件:必须启用FIFO模式DMA_PeripheralDataSizeDMA_MemoryDataSize可设置为不同值。NDTR的值需要是宽度比值的整数倍(例如,外设8位,内存32位,NDTR必须是4的倍数)。

  3. 突发传输

    • 目的:提高总线利用效率。通过一次请求传输一个数据块(4、8、16拍),而不是一个数据项。

    • 配置:设置DMA_PeripheralBurstDMA_MemoryBurst必须启用FIFO模式,且FIFO阈值大小必须能容纳整个突发传输的数据量(见表49)。例如,内存突发为4拍,内存数据宽度为字(4字节),则一次突发传输16字节。FIFO容量为16字节,因此阈值可以设为1/4(4字节)、半满(8字节)或全满(16字节),但不能设为3/4(12字节)。

  4. 流控制器与外设流控制

    • DMA作为流控制器(默认):传输总量由DMA_BufferSize决定。

    • 外设作为流控制器:设置DMA_InitStructure.DMA_PFCTRL = DMA_PFCTRL_PERIPH。此时传输总数未知,由外设(如SDIO)通过硬件信号通知DMA传输结束。NDTR寄存器在启动后会被强制设为0xFFFF,并在传输中递减。

六、错误诊断与调试

DMA传输失败时,应检查以下状态标志(在DMA_LISRDMA_HISR寄存器中):

  • TEIF (Transfer Error):传输错误。通常由于访问了非法地址(如未初始化的指针)或双缓冲模式下错误地写入了内存地址寄存器。

  • FEIF (FIFO Error):FIFO错误。可能因FIFO上溢/下溢,或初始配置中FIFO阈值与突发大小不匹配导致。

  • DMEIF (Direct Mode Error):直接模式错误。在直接模式下发生访问冲突。

调试建议

  1. 始终在初始化前禁用流等待其确认禁用

  2. 仔细检查外设和内存的地址是否正确,是否已对齐(例如字传输时地址需4字节对齐)。

  3. 确认DMA_BufferSize、数据宽度、地址增量模式的组合符合逻辑。

  4. 使用传输完成中断(TCIF)或半传输中断(HTIF)作为流程监控点。

  5. 对于复杂配置(如突发+双缓冲),先用简单配置(单次、直接模式)验证通路正确性。

通过本指南的系统学习,你应该已经掌握了STM32F4 DMA控制器的核心原理和配置方法。DMA是提升STM32系统性能的利器,熟练掌握它将使你设计的嵌入式系统更加高效和稳定。请结合具体的外设(如UART、SPI、ADC)参考手册和示例代码进行实践,以巩固理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值