STM32单片机快速入门——DMA篇

目录

一. DMA简介

二. DMA结构

2.1 DMA框图

2.2 DMA请求映像

三. DMA主要特性

四. DMA相关参数

4.1 传输起始地址

4.2 传输目标地址

4.3 数据宽度

4.4 地址是否自增

4.5 传输计数器

4.6 自动重装器

4.7 触发方式

4.8 运输模式

五. 示例代码

5.1 DMA配置流程

5.2 ADC连续扫描转换+DMA

5.3 USART接收数据+DMA

一. DMA简介

直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。 两个DMA(对于F1系列而言),控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。DMA转运的本质都是存储器到存储器,转运外设也是转运外设里 数据寄存器 存储的数据。

二. DMA结构

2.1 DMA框图

  • 总体上看:

左半部分为主动单元,右半部分为被动单元,主动单元可以通过总线矩阵,对被动单元进行读写,除了内核CPU可以通过ICode,DCode,系统等总线访问被动单元外,每个DMA也有属于自己的DMA总线来访问被动单元。

  • 细节上看:

DMA1拥有7个通道,DMA2则包含5个通道,每个DMA控制器的通道通过时间复用共享同一条DMA总线。以DMA1为例,当多个通道同时产生DMA请求时,仲裁器将进行优先级判断,决定哪个通道先获得总线访问权限。总线矩阵中也设置了仲裁器,负责在DMA和CPU同时请求访问同一资源时进行冲突管理。通常,DMA拥有更高的总线优先级,若DMA正在访问总线,CPU的访问会被暂时暂停,以避免冲突。为了避免过度影响CPU操作,系统会合理分配总线带宽,确保CPU能够继续运行,并获得足够的带宽进行正常处理。

右上角的SRAM是易失性存储器(RAM),主要用于存储程序运行时的临时数据、变量以及外设寄存器配置等信息。DMA控制器位于AHB总线架构中,Cortex-M3核心的CPU也可以通过AHB总线访问并配置DMA。DMA控制器既是一个主动的数据传输单元,也能够作为响应外设请求的被动单元。系统中的硬件外设可以向DMA控制器发送数据传输请求,DMA接到请求后,便会启动并执行相应的数据传输工作。

注意事项:

  • 右上角的Flash是ROM(只读存储器)型存储器,如果通过总线直接访问,CPU和DMA都只能读,不能写,如果实在需要写入,要先对Flash接口控制器进行配置。
  • 硬件外设里的寄存器是否可读,是否可写,需要参考数据手册。

2.2 DMA请求映像

如图,每个硬件外设都有其对应的DMA通道:

选择硬件触发时,需要严格选择对应通道;选择软件触发时,选择任意通道均可。

三. DMA主要特性

  • 有12个独立的可配置的通道(请求),DMA1有7个通道,DMA2有5个通道。
  • 每个通道都支持软件触发和硬件触发。
  • 在同一个DMA模块上,同时产生多个请求时,可以通过软件编程设置优先权(共有四级:很高,高,中等,低),优先权设置相等时由硬件决定(请求0优于请求1)。
  • 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
  • 每个通道都有三个事件标志(DMA半传输,DMA传输完成和DMA传输出错),这三个事件标志或成为一个单独的中断请求。
  • 存储器和存储器之间的传输,外设和存储器之间的传输,存储器和外设之间的传输。
  • 内存,SRAM,外设的SRAM,APB1,APB2和AHB外设均可作为访问的源和目标。
  • 可编程的数据传输数目(即一次性最大传输次数)最大为65535。

四. DMA相关参数

4.1 传输起始地址

即要运输数据所在的地址,可以是外设寄存器,Flash,SRAM。

4.2 传输目标地址

即要把数据运输到哪里,可以是外设寄存器,SRAM,但不能是Flash,因为Flash是只读存储器。

4.3 数据宽度

有以下几种常见类型:

  • 字节(Byte):8位
  • 半字(half-word):16位
  • 字(word):32位

这些是比较常见的, 在一些高性能的系统中,可能可以支持到更高的数据宽度,如64位,128位等。

以下是数据传输宽度和大小端操作表:

总结来说就是:

  • 如果把宽度大的数据转运到小的,高位就会舍弃掉。
  • 如果把宽度小的数据转运到大的,高位就会自动补零。

注意事项:

  • 数据宽度的选择需要综合考虑到外设寄存器位数,SRAM目标变量位数以及DMA位数。

例如:

如果外设的寄存器是8位的,那么它只能以8位为单位进行数据传输。这就意味着,DMA传输到这个外设时,必须使用8位宽的数据。

如果DMA控制器是8位的,它每次只能传输8位数据,即使外设支持更宽的数据宽度,DMA也只能逐个字节传输数据。

如果外设寄存器支持32位,而DMA控制器只有16位宽,则每次DMA传输时需要进行两次操作,分别传输16位数据。

4.4 地址是否自增

当使能开启后,每传输一次,地址就会自增一次,起始地址,目标地址都可以单独设置。

示例场景:DMA不断将ADC采集的值运输到数组中,起始地址ADC是固定的,目标地址数组使能地址自增。每次传输时,起始地址ADC不变,而目标地址数组会自增,确保数据依次写入数组,避免数据覆盖。当目标地址自增到数组末尾时,DMA控制器会将目标地址回绕到数组的开头,形成环形缓冲区。

如果是外设到存储器,例如ADC -> 存储器,USART -> 存储器等,我们可以采用DMA空闲中断+环形缓冲区,我们提高篇进行讲解。

4.5 传输计数器

连续传输的次数,最大不可以超过65535,因为计数器是16位的,每传输一次,就自减一次,自减到0就会停止传输。减到0后,如果有自增的地址,也会恢复到刚开始的地址。

4.6 自动重装器

对传输计数值进行重装。

4.7 触发方式

  • 硬件触发:常用于外设 -> 存储器,如:ADC采集到数据触发,USART接收到数据触发,定时器触发等。
  • 软件触发:通过api函数手动触发,DMA会已最快的速度,将计数值清零,常用于存储器 -> 存储器。

4.8 运输模式

  • 单次传输模式(Normal):传输计数器自减到0后会停止传输,需要进行手动重装,在重装之前要先失能DMA,才能重装计数器
  • 循环模式(Circuler):开启后,当计数器自减到0后,可以自动重装计数值。

注意事项:

  • 软件触发和循环模式不能同时开启,否则DMA就停不下来了,会一直占用DMA总线资源。

五. 示例代码

5.1 DMA配置流程

5.2 ADC连续扫描转换+DMA

#include "stm32f10x.h"

// 定义数组用于存储DMA传输的ADC数据
#define BUFFER_SIZE 64
uint32_t adc_buffer[BUFFER_SIZE];

// ADC和DMA初始化
void ADC_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;

    // 1. 开启ADC1时钟和DMA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);     // 开启ADC1时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);        // 开启DMA1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);     // 开启GPIOA时钟

    // 2. 配置GPIOA的引脚(例如:ADC通道0)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;                // 选择GPIOA的第0引脚(ADC通道0)
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;            // 模式设置为模拟输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);                    // 初始化GPIOA引脚

    // 3. 配置ADC1
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;        // 独立模式
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;              // 启用扫描模式
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;        // 启用连续转换模式
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  // 不使用外部触发
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;    // 数据右对齐
    ADC_InitStructure.ADC_NbrOfChannel = 1;                   // 设置为1个通道
    ADC_Init(ADC1, &ADC_InitStructure);                       // 初始化ADC1

    // 4. 配置ADC通道0
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); // 配置通道0,采样时间55.5个时钟周期

    // 5. 配置DMA
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; // DMA外设地址:ADC1数据寄存器
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_buffer;    // DMA内存地址:数据存储数组
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;          // 从外设(ADC)到内存
    DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;                 // DMA传输大小
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;         // 内存地址递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; // 外设数据宽度:32位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; // 内存数据宽度:32位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                 // 设置为循环模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;             // DMA优先级:高
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                    // 禁止内存到内存模式
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);                    // 初始化DMA1通道1

    // 6. 启用ADC1 DMA
    ADC_DMACmd(ADC1, ENABLE); // 启用ADC1的DMA

    // 7. 启动ADC转换
    ADC_Cmd(ADC1, ENABLE);    // 启用ADC1
    ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 软件触发,开始ADC转换
}

// DMA传输完成中断处理
void DMA1_Channel1_IRQHandler(void)
{
    if (DMA_GetITStatus(DMA1_IT_TC1))  // 如果传输完成
    {
        // 清除中断标志
        DMA_ClearITPendingBit(DMA1_IT_TC1);
        
        // 这里可以处理传输完成后的数据,数据已经存储在adc_buffer中
    }
}

// 主程序
int main(void)
{
    // 系统初始化
    SystemInit();
    
    // 初始化ADC和DMA
    ADC_Init();

    // 启用DMA中断
    NVIC_EnableIRQ(DMA1_Channel1_IRQn); // 启用DMA1通道1中断

    // 主循环
    while (1)
    {
        // 在主循环中可以执行其他任务
        // DMA会在后台自动传输数据到adc_buffer数组
    }
}

5.3 USART接收数据+DMA

#include "stm32f10x.h"

// 定义缓冲区用于接收数据
#define BUFFER_SIZE 128
uint8_t rx_buffer[BUFFER_SIZE];

// USART和DMA初始化
void USART_Init_DMA(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;

    // 1. 开启USART1、DMA和GPIO的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);    // 开启USART1时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);        // 开启DMA1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);     // 开启GPIOA时钟

    // 2. 配置GPIOA的引脚(例如:USART1的RX引脚)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;               // 选择GPIOA的第10引脚(USART1_RX)
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;    // 模式设置为浮动输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);                    // 初始化GPIOA引脚

    // 3. 配置USART1
    USART_InitStructure.USART_BaudRate = 9600;                // 设置波特率为9600
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;  // 8位数据位
    USART_InitStructure.USART_StopBits = USART_StopBits_1;        // 1个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;           // 无奇偶校验
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 不使用硬件流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx;               // 只使能接收模式
    USART_Init(USART1, &USART_InitStructure);                    // 初始化USART1

    // 4. 配置DMA
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;  // DMA外设地址:USART1数据寄存器
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_buffer;        // DMA内存地址:接收数据的缓冲区
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;            // 从外设到内存
    DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;                    // 设置DMA传输大小
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;   // 外设地址不递增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;            // 内存地址递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据宽度:8位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;    // 内存数据宽度:8位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                    // 设置为循环模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;                // DMA优先级:高
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                       // 禁止内存到内存模式
    DMA_Init(DMA1_Channel5, &DMA_InitStructure);                       // 初始化DMA1通道5(用于USART1_RX)

    // 5. 启用USART1的DMA接收
    USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);   // 启用USART1的DMA接收请求

    // 6. 启用USART1
    USART_Cmd(USART1, ENABLE);                      // 启用USART1

    // 7. 启用DMA1通道5
    DMA_Cmd(DMA1_Channel5, ENABLE);                 // 启用DMA1通道5(启动数据接收)
}

// DMA传输完成中断处理
void DMA1_Channel5_IRQHandler(void)
{
    if (DMA_GetITStatus(DMA1_IT_TC5))  // 如果DMA传输完成
    {
        // 清除中断标志
        DMA_ClearITPendingBit(DMA1_IT_TC5);
        
        // 在此处可以处理接收到的数据,数据已经存储在rx_buffer中
    }
}

// 主程序
int main(void)
{
    // 系统初始化
    SystemInit();

    // 初始化USART和DMA
    USART_Init_DMA();

    // 启用DMA中断
    NVIC_EnableIRQ(DMA1_Channel5_IRQn); // 启用DMA1通道5中断

    // 主循环
    while (1)
    {
        // 在主循环中可以执行其他任务
        // 数据会自动传输到rx_buffer数组,DMA会在后台自动接收数据
    }
}

<think>嗯,用户问的是单片机DMA,我需要先回忆一下DMA的基本概念和作用。DMA全称是直接存储器访问,它的主要功能是在不占用CPU的情况下,实现外设和存储器之间或者存储器与存储器之间的数据传输。这对提高系统效率很重要,因为如果每次数据传输都需要CPU参与,会消耗大量资源,尤其是在处理大量数据的时候,比如音频、视频传输或者高速数据采集。 接下来,我应该解释DMA的工作原理。DMA控制器独立于CPU,当外设需要传输数据时,会向DMA发出请求,然后DMA控制器接管总线,直接进行数据传输。传输完成后,DMA会通知CPU,这样CPU就可以去做其他任务,而不是被数据传输阻塞。 然后要提到DMA的主要优点,比如减少CPU负担,提高系统效率,降低功耗,可能还有提高数据传输速率。同时,也要讲一些缺点,比如增加系统复杂性,可能引起总线竞争,需要合理配置DMA通道和优先级。 接下来可能需要介绍DMA的工作模式,常见的三种:内存到外设、外设到内存、内存到内存。每种模式的应用场景,比如串口收发数据,或者内存块复制。 然后,用户可能想知道如何在单片机中使用DMA,可能需要简要说明配置步骤,比如初始化DMA通道,设置源地址和目的地址,传输数据量,触发方式(软件触发或外设触发),以及启动传输。这里可以举一个例子,比如ADC采集数据到内存,使用DMA自动传输,这样ADC转换完成后直接通过DMA存入内存,不需要CPU干预。 另外,常见问题可能包括资源冲突,比如多个DMA通道同时使用时的优先级问题,或者配置错误导致的数据传输失败。这时候需要提醒用户注意通道分配和优先级设置,以及正确的中断处理。 最后,应用场景部分可以列举一些实际例子,比如实时音频处理、图像采集、网络数据传输等,这些场景下DMA能显著提升性能。 还要注意用户可能的背景,可能是嵌入式开发者或者学生,所以需要保持解释的易懂性,同时兼顾技术细节。确保使用正确的中文术语,避免翻译腔,结构清晰,分点说明。可能需要检查是否有遗漏的重要点,比如DMA传输完成中断的使用,或者不同单片机厂商的DMA实现差异,但可能不需要深入,保持一般性介绍即可。</think>关于单片机DMA的逐步解析: 一、DMA基础概念 DMA(Direct Memory Access,直接存储器访问)是单片机中一种无需CPU干预即可完成数据搬运的硬件模块。其核心作用是: $$数据传输速率 = \frac{数据量}{时钟周期}$$ 通过旁路CPU操作,可显著提升系统效率。 二、工作原理示意图 ``` 外设/内存 → DMA控制器 → 内存/外设 ↑配置参数 CPU ``` 三、工作模式详解 1. 外设→内存(P2M) - 应用:ADC采集数据存入数组 - 例:温度传感器连续采样 2. 内存→外设(M2P) - 应用:DAC输出波形数据 - 例:音频播放时PCM数据输出 3. 内存→内存(M2M) - 应用:快速缓冲区复制 - 例:图像处理中的帧缓存交换 四、配置关键参数(以STM32为例) 1. 通道优先级:4级可编程优先级 2. 传输模式: - 单次传输(Single) - 循环模式(Circular) - 突发传输(Burst) 3. 数据宽度:8/16/32位可选 4. 地址增量:源/目标地址自动偏移 五、典型配置流程 1. 使能DMA时钟:RCC_AHBENR |= ... 2. 配置通道参数: - 源/目标地址 - 传输方向 - 数据项数量 3. 设置外设寄存器: - 使能DMA请求 - 配置触发源 4. 使能通道: DMA_CCR |= DMA_CCR_EN; 六、优化技巧 1. 双缓冲技术:交替传输和处理数据 2. 内存对齐优化:使用32位访问提升效率 3. 传输完成中断:配合半传输中断实现流水线操作 七、常见问题排查 1. 数据错位:检查地址增量设置 2. 传输不启动:验证触发源配置 3. 数据丢失:调整DMA优先级高于外设中断 八、应用场景对比 | 场景 | 无DMA时CPU占用 | 使用DMA后CPU占用 | |------------|---------------|----------------| | 10Mbps以太网 | 85% | <5% | | 1Msps ADC | 70% | 0% | | 音频解码 | 60% | 10% | 九、发展趋势 1. 多端口DMA:支持并行传输 2. 智能DMA:集成数据预处理功能 3. 低功耗模式:休眠状态下维持传输 建议实际开发时参考具体芯片手册,不同厂商(如NXP、ST、Microchip)的DMA实现存在寄存器级差异。调试时可使用示波器监测DMA传输完成信号,结合断点调试验证配置参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值