DMA控制器入门指南:从原理到实践-CSDN博客

DMA控制器入门指南:从原理到实践

1 直接存储器访问(DMA)概述

直接存储器访问(DMA)是STM32微控制器中一个重要的硬件功能模块,它提供了一种不需要CPU干预就能实现外设与存储器之间、存储器与存储器之间数据传输的机制。在传统的数据传输方式中,CPU需要介入每一次数据的读写过程:首先从源地址读取数据,然后将数据写入目标地址。这种方式不仅占用了大量的CPU资源,还会降低整个系统的运行效率。DMA控制器则通过硬件方式,在CPU不参与的情况下自主完成数据传输任务。

1.1 直接存储器访问(DMA)结构

STM32的DMA控制器采用多通道设计,以F1系列为例,它配备了7个独立的DMA通道。每个通道都可以与特定的外设源或目标相连接,支持独立的数据传输。这种多通道架构使得多个外设可以同时请求DMA传输,大大提高了系统的并行处理能力。在数据传输过程中,DMA控制器会暂时占用系统总线,此时CPU访问总线的请求会被暂时挂起,直到当前的DMA传输完成。

1.2 直接存储器访问(DMA)数据传送

DMA控制器的数据传输是以块为单位进行的每个传输块都有其特定的源地址和目标地址在传输过程中,DMA控制器会自动管理地址的递增,并维护传输计数器。当完成预设的传输数量后,DMA可以触发中断通知CPU,或者在循环模式下自动重新开始传输。这种自动化的传输机制特别适合处理需要持续数据流的应用场景,如ADC采样、串口通信等。

1.3 直接存储器访问(DMA)机制

为了确保数据传输的可靠性和效率,STM32的DMA控制器实现了多种保护机制。首先是优先级管理,当多个DMA通道同时请求传输时,系统会根据预先配置的优先级决定传输顺序。其次是错误检测机制,DMA控制器能够检测传输过程中的异常情况,并通过中断通知CPU进行处理。此外,DMA控制器还支持多种数据宽度的传输(8位、16位、32位),并能够自动处理源地址和目标地址的对齐要求

在实际应用中,DMA的使用能显著提升系统性能。例如在ADC连续采样中,使用DMA可以实现数据的自动传输,无需CPU干预即可将采样结果存储到指定的内存区域。在串口通信中,DMA可以自动完成数据的收发,释放CPU资源用于处理其他任务。在存储器到存储器的数据搬运中,DMA可以实现高效的数据块复制,大大提高数据处理效率。

2 DMA传输模式

DMA控制器支持三种基本的传输模式外设到存储器、存储器到外设以及存储器到存储器模式。每种模式都有其特定的应用场景和配置要求。了解这些传输模式的特点和使用方法,对于正确使用DMA功能至关重要。

2.1 外设到存储器

外设到存储器(Peripheral to Memory)模式是最常用的传输模式之一。在这种模式下,DMA控制器从外设的数据寄存器读取数据,然后将其传输到指定的存储器地址。这种模式典型的应用场景是ADC采样数据的获取:ADC完成一次转换后,会触发DMA请求,DMA控制器随即将ADC转换结果寄存器中的数据传输到预先指定的内存缓冲区。外设地址通常是固定的(外设数据寄存器地址),而存储器地址可以配置为自动递增,以便连续存储多个数据

2.2 存储器到外设

存储器到外设(Memory to Peripheral)模式与上述模式相反,数据从存储器传输到外设的数据寄存器。这种模式常用于数据发送场景,比如通过串口发送数据:将要发送的数据预先存储在内存缓冲区中,当串口发送寄存器为空时触发DMA请求,DMA控制器自动将下一个数据从内存传输到串口发送寄存器。这种模式下,外设地址同样通常是固定的,而存储器地址可以配置为递增模式,以便顺序读取发送数据。

2.3 存储器到存储器

存储器到存储器(Memory to Memory)模式是STM32 DMA控制器提供的一种特殊模式。在这种模式下,数据在两个存储器地址之间直接传输,不涉及外设。这种模式特别适合于大块数据的快速复制或移动操作。例如,在图像处理应用中,可能需要将图像数据从一个缓冲区复制到另一个缓冲区进行处理。使用DMA的存储器到存储器模式可以极大地提高数据搬运效率,释放CPU资源用于其他任务。需要注意的是,并非所有DMA通道都支持存储器到存储器模式,在使用前需要查阅具体型号的参考手册。

2.4 其他模式

在这些基本传输模式之上,DMA控制器还支持两种运行模式:普通模式和循环模式。普通模式下,DMA完成预设的传输次数后就会停止,需要软件重新配置才能开始新的传输。循环模式下,DMA在完成预设的传输次数后会自动重新装载传输计数器,继续新一轮的传输。循环模式特别适合需要持续数据流的应用,如ADC连续采样或音频数据流处理。

为了提高传输效率,DMA控制器允许配置不同的数据宽度(8位、16位、32位)和地址递增方式。源地址和目标地址的数据宽度可以不同,DMA控制器会自动处理数据对齐。同时,地址递增可以独立配置,这意味着源地址和目标地址可以采用不同的递增方式。例如,在ADC采样数据传输中,外设地址(ADC数据寄存器)保持固定,而存储器地址则需要递增以存储连续的采样值。

此外,DMA传输过程中的中断管理也是一个重要特性。DMA控制器提供了传输完成中断、半传输完成中断和传输错误中断等多种中断事件。通过合理配置这些中断,可以实现对传输过程的精确控制和监控。例如,在大块数据传输中,可以使用半传输完成中断来实现双缓冲处理,提高数据处理的实时性。

下面是一个基本的DMA配置代码示例:

void DMA_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    
    // 开启DMA时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    // 配置DMA参数
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;  // 外设地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_Value;     // 存储器地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;              // 传输方向
    DMA_InitStructure.DMA_BufferSize = 1;                           // 传输计数器
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址固定
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;         // 存储器地址递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据宽度
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         // 存储器数据宽度
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                 // 循环模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;             // 优先级
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                    // 非存储器到存储器模式
    
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    DMA_Cmd(DMA1_Channel1, ENABLE);
}

3 传输数据大小与地址增量

3.1 传输数据大小

传输数据大小是DMA配置中的一个重要参数,STM32的DMA控制器支持三种数据宽度:8位(Byte)、16位(Half-word)和32位(Word)。在DMA配置时,我们可以分别为源地址和目标地址指定不同的数据宽度。例如,在处理ADC数据时,ADC的采样结果通常是16位,而我们可能需要将其存储为32位数据以便进行后续计算。DMA控制器会自动处理这种数据宽度的转换,确保数据正确传输。这种灵活的数据宽度配置极大地提高了DMA传输的适应性。

3.2 数据宽度

数据宽度的选择需要考虑多个因素。首先是数据对齐问题,当使用不同数据宽度时,源地址和目标地址必须按照各自的数据宽度对齐。例如,16位数据传输要求地址必须是2的倍数,32位数据传输要求地址是4的倍数。其次是传输效率,一般来说,选择更大的数据宽度可以提高传输效率,因为每次传输可以搬运更多数据。但这需要权衡存储空间的使用,过大的数据宽度可能会浪费存储空间。

3.3 地址增量配置

地址增量配置是另一个关键特性,它决定了在每次数据传输后源地址和目标地址如何变化DMA控制器允许独立配置源地址和目标地址的增量模式增量模式有两种选择:固定地址模式和地址自增模式在固定地址模式下,每次传输后地址保持不变,这通常用于访问外设的数据寄存器。在地址自增模式下,每次传输后地址会自动增加,增加的值等于配置的数据宽度

地址增量的配置与实际应用场景密切相关。以ADC连续采样为例,ADC数据寄存器的地址应该配置为固定模式,因为我们总是从同一个寄存器读取数据;而存储采样结果的内存地址应该配置为自增模式,这样每个新的采样值都会存储在连续的内存位置上。再比如在存储器到存储器的数据复制中,源地址和目标地址通常都需要配置为自增模式,以实现数据的连续复制。

3.4 其他功能

DMA控制器还提供了传输计数器,用于控制传输的数据量。计数器的值表示要传输的数据项数量,而不是字节数。例如,如果配置的数据宽度是16位,计数器值为100,那么实际传输的字节数是200。在传输过程中,每完成一次数据传输,计数器值就会减1,直到减到0时停止传输或在循环模式下重新装载初值。

为了确保正确的数据传输,我们需要特别注意数据对齐和缓冲区大小的问题

  • 源地址和目标地址都必须按照各自的数据宽度对齐。
  • 在配置DMA传输时,必须确保缓冲区的大小足够容纳所有要传输的数据。特别是在使用较大数据宽度时,要注意预留足够的缓冲区空间。
  • 在使用自增模式时,要确保地址增量不会超出分配的内存范围。

在实际应用中,合理配置传输数据大小和地址增量可以显著提高数据传输的效率。例如,在处理大块数据时,可以选择较大的数据宽度来减少传输次数。在处理数据流时,可以通过合理配置地址增量来实现数据的连续存储或处理。

4 优先级管理机制

在STM32中,DMA控制器采用了一套完善的优先级管理机制,用于处理多个DMA通道同时请求传输的情况。DMA控制器为每个通道提供了四个优先级级别:低优先级(Low)、中优先级(Medium)、高优先级(High)和超高优先级(Very High)。这种多级优先级设计确保了在系统资源竞争时,重要的数据传输任务能够优先执行。

4.1 基本原则

当多个DMA通道同时请求传输时,优先级仲裁遵循两个基本原则:首先是软件优先级,即根据通道配置的优先级级别判断;其次是硬件优先级,即当软件优先级相同时,编号较小的DMA通道具有更高的优先级。例如,如果DMA1通道1和DMA1通道2都配置为高优先级,且同时发起传输请求,则通道1会先获得传输权限。这种双重优先级机制保证了传输顺序的确定性。

4.2 优先级管理

优先级的配置需要在初始化DMA通道时通过DMA_InitStructure.DMA_Priority参数设定。一旦配置完成,在传输过程中不能动态修改优先级。因此,在系统设计时需要仔细规划各个DMA通道的优先级。通常,我们会根据外设的实时性要求和数据传输的重要程度来分配优先级。例如,ADC连续采样这种需要及时处理的数据传输可以配置为高优先级,而普通的存储器到存储器的数据复制则可以使用较低的优先级。

在实际应用中,合理分配DMA通道优先级可以显著提高系统性能。以一个典型的数据采集系统为例,假设系统同时进行ADC采样和串口通信。ADC采样需要及时处理以避免数据丢失,因此其对应的DMA通道可以配置为高优先级;而串口通信的实时性要求相对较低,可以配置为中优先级。这样,当两个通道同时请求传输时,系统会优先处理ADC数据,确保采样数据的完整性。

需要注意的是,过多使用高优先级可能会导致低优先级通道长时间得不到服务,产生传输延迟或超时问题。因此,在设计系统时应当权衡各个数据传输任务的重要性和实时性需求,合理分配优先级。通常建议将真正需要快速响应的传输配置为高优先级,而将可以容忍一定延迟的传输配置为较低优先级。

对于特别重要的数据传输,除了配置最高优先级外,还可以配合使用DMA中断来监控传输状态。通过及时处理传输完成中断或错误中断,可以实现对关键数据传输的精确控制。此外,在某些场景下,也可以通过软件暂时禁用某些DMA通道,为重要的传输任务让出带宽。

5 传输模式选择

DMA控制器提供了两种基本的传输模式:普通模式(Normal Mode)和循环模式(Circular Mode)。这两种模式的设计满足了不同场景下的数据传输需求。在DMA初始化时,通过配置DMA_InitStructure.DMA_Mode参数来选择使用哪种传输模式。这个选择直接影响了DMA传输的行为和数据处理方式。

5.1 普通模式

普通模式是最基本的传输模式,适用于一次性数据传输的场景。在这种模式下,DMA控制器会按照预设的传输计数器值,执行指定次数的数据传输。当传输计数器减到零时,DMA传输自动停止,并将通道禁用。如果需要进行新的传输,必须由软件重新配置传输计数器并使能DMA通道。这种模式特别适合于数据块的一次性传输,例如将一块数据从存储器复制到另一个存储器区域,或者发送一个固定长度的数据包。

在普通模式下,DMA传输完成后停止,需要手动重新启动下一次传输。

#include "stm32f4xx_hal.h"

DMA_HandleTypeDef hdma_memtomem;

void DMA_Init(void)
{
    __HAL_RCC_DMA2_CLK_ENABLE();

    hdma_memtomem.Instance = DMA2_Stream0;
    hdma_memtomem.Init.Channel = DMA_CHANNEL_0;
    hdma_memtomem.Init.Direction = DMA_MEMORY_TO_MEMORY; // 内存到内存模式
    hdma_memtomem.Init.PeriphInc = DMA_PINC_ENABLE;     // 外设地址递增
    hdma_memtomem.Init.MemInc = DMA_MINC_ENABLE;        // 内存地址递增
    hdma_memtomem.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 外设数据对齐
    hdma_memtomem.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;    // 内存数据对齐
    hdma_memtomem.Init.Mode = DMA_NORMAL;               // 普通模式
    hdma_memtomem.Init.Priority = DMA_PRIORITY_HIGH;    // 高优先级
    hdma_memtomem.Init.FIFOMode = DMA_FIFOMODE_ENABLE;  // FIFO模式
    hdma_memtomem.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; // FIFO阈值
    hdma_memtomem.Init.MemBurst = DMA_MBURST_SINGLE;    // 内存突发单次传输
    hdma_memtomem.Init.PeriphBurst = DMA_PBURST_SINGLE; // 外设突发单次传输

    if (HAL_DMA_Init(&hdma_memtomem) != HAL_OK)
    {
        Error_Handler();
    }
}

void DMA_Transfer(uint32_t *src, uint32_t *dst, uint32_t size)
{
    if (HAL_DMA_Start(&hdma_memtomem, (uint32_t)src, (uint32_t)dst, size) != HAL_OK)
    {
        Error_Handler();
    }

    // 等待传输完成
    HAL_DMA_PollForTransfer(&hdma_memtomem, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY);
}

5.2 循环模式

循环模式则提供了一种自动循环的传输机制。在这种模式下,当传输计数器减到零时,DMA控制器会自动重新装载初始的计数值,并继续执行新一轮的数据传输。这个过程会持续进行,直到软件显式地禁用DMA通道。循环模式特别适合需要持续数据流传输的应用场景,典型的例子包括ADC连续采样、音频数据流处理等。在这些应用中,数据需要持续不断地从外设传输到存储器,或者从存储器传输到外设。

在循环模式下,DMA传输完成后会自动重新开始,适用于连续数据传输。

void DMA_Init(void)
{
    __HAL_RCC_DMA2_CLK_ENABLE();

    hdma_memtomem.Instance = DMA2_Stream0;
    hdma_memtomem.Init.Channel = DMA_CHANNEL_0;
    hdma_memtomem.Init.Direction = DMA_MEMORY_TO_MEMORY; // 内存到内存模式
    hdma_memtomem.Init.PeriphInc = DMA_PINC_ENABLE;     // 外设地址递增
    hdma_memtomem.Init.MemInc = DMA_MINC_ENABLE;        // 内存地址递增
    hdma_memtomem.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 外设数据对齐
    hdma_memtomem.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;    // 内存数据对齐
    hdma_memtomem.Init.Mode = DMA_CIRCULAR;             // 循环模式
    hdma_memtomem.Init.Priority = DMA_PRIORITY_HIGH;    // 高优先级
    hdma_memtomem.Init.FIFOMode = DMA_FIFOMODE_ENABLE;  // FIFO模式
    hdma_memtomem.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; // FIFO阈值
    hdma_memtomem.Init.MemBurst = DMA_MBURST_SINGLE;    // 内存突发单次传输
    hdma_memtomem.Init.PeriphBurst = DMA_PBURST_SINGLE; // 外设突发单次传输

    if (HAL_DMA_Init(&hdma_memtomem) != HAL_OK)
    {
        Error_Handler();
    }
}

5.3 实际使用

在实际应用中,循环模式经常与双缓冲区技术结合使用。通过配置两个缓冲区,当DMA向一个缓冲区写入数据时,CPU可以同时处理另一个缓冲区中的数据,实现数据的连续采集和处理。这种方式特别适合处理连续的数据流,如音频信号处理或高速数据采集系统。在这种情况下,通常会配合使用DMA的半传输完成中断和传输完成中断,分别标识第一个缓冲区和第二个缓冲区的数据已经准备就绪。

选择合适的传输模式需要考虑多个因素。

  • 数据传输的性质,是一次性传输还是需要持续传输。
  • 系统资源的使用,循环模式虽然便于实现持续传输,但会持续占用DMA资源。
  • 还需要考虑数据处理的实时性要求。

在一些应用中,可能需要在传输过程中动态调整参数或处理数据,这时就需要合理设计中断处理机制。

对于特定的应用场景,有时需要在两种模式之间进行权衡。在数据采集系统中,如果采样频率较低,可以使用普通模式,每次采样完成后重新配置DMA;如果采样频率较高,则更适合使用循环模式,避免频繁的软件干预。在某些情况下,还可以通过软件控制来模拟特殊的传输模式,例如在普通模式下通过中断服务程序重新启动传输,实现类似循环模式的效果。传输模式的选择还需要考虑与其他系统参数的配合,如DMA优先级、中断配置等。合理的配置组合可以充分发挥DMA控制器的性能,提高系统的数据传输效率。在系统设计时,建议根据具体应用需求,仔细评估不同传输模式的特点,选择最适合的配置方案。

6 DMA中断处理

DMA控制器提供了一套完善的中断机制,主要包括三种类型的中断事件:传输完成中断(Transfer Complete, TC)、传输错误中断(Transfer Error, TE)和传输半完成中断(Half Transfer, HT)。这些中断事件使系统能够及时响应传输过程中的各种状态变化,实现对数据传输的精确控制和监控。

6.1 传输完成中断

传输完成中断是最常用的DMA中断类型,当DMA完成预设的传输计数后触发在普通模式下,这个中断标志着整个传输过程的结束;在循环模式下,它表示一轮传输的完成。通过配置传输完成中断,我们可以在数据传输结束时立即进行后续处理,如数据分析、缓冲区切换等。典型的应用场景是串口通信,当一帧数据发送完成时,可以通过此中断来启动下一帧数据的发送,或进行通信状态的更新。

6.2 传输半完成中断

传输半完成中断在传输计数器减至一半时触发,这个特性在实现双缓冲处理时特别有用。例如,在ADC连续采样场景中,我们可以设置一个较大的缓冲区,当DMA传输到缓冲区的一半时触发半完成中断,此时可以处理前半部分数据;当传输完整个缓冲区时触发完成中断,处理后半部分数据。这种机制实现了数据的连续采集和处理,避免了数据丢失。

以下是典型的双缓冲处理代码结构:

void DMA1_Channel1_IRQHandler(void)
{
    if(DMA_GetITStatus(DMA1_IT_HT1) != RESET) // 半传输完成中断
    {
        // 处理缓冲区前半部分数据
        Process_First_Half_Data();
        DMA_ClearITPendingBit(DMA1_IT_HT1);
    }
    
    if(DMA_GetITStatus(DMA1_IT_TC1) != RESET) // 传输完成中断
    {
        // 处理缓冲区后半部分数据
        Process_Second_Half_Data();
        DMA_ClearITPendingBit(DMA1_IT_TC1);
    }
    
    if(DMA_GetITStatus(DMA1_IT_TE1) != RESET) // 传输错误中断
    {
        // 错误处理
        Error_Handler();
        DMA_ClearITPendingBit(DMA1_IT_TE1);
    }
}

6.3 传输错误中断

传输错误中断用于检测传输过程中的异常情况。当DMA传输发生错误时(如地址对齐错误、总线访问错误等),会触发此中断。在实际应用中,应该适当处理这些错误情况,以提高系统的可靠性。错误处理可能包括重新初始化DMA、记录错误日志、重新启动传输等操作。

DMA错误回调函数,当DMA传输发生错误时调用:

void HAL_DMA_ErrorCallback(DMA_HandleTypeDef *hdma)
{
    if (hdma->Instance == DMA2_Stream0)
    {
        // Handle DMA error
        Error_Handler();
    }
}

6.4 中断配置

为了使用DMA中断,需要进行正确的配置。这包括:使能NVIC中相应的中断通道、配置中断优先级、使能DMA通道的相应中断。中断处理函数应该尽可能简短,避免在中断中执行耗时操作。如果需要进行复杂的数据处理,建议只在中断中设置标志位,将实际的数据处理放在主循环中进行。

在使用DMA中断时,需要注意几个关键点:

  • 中断优先级的合理分配,确保重要的中断能够及时响应;
  • 中断处理函数的执行效率,避免在中断中执行过于复杂的操作;
  • 要正确清除中断标志位,防止重复进入中断处理程序。

特别需要注意的是,在循环模式下使用中断时,要考虑数据处理速度和传输速度的匹配问题。如果数据处理速度跟不上传输速度,可能会导致数据丢失。这种情况下,可以考虑使用更大的缓冲区,或者优化数据处理算法来提高处理速度。

以下是一个DMA中断处理的示例代码:

void DMA1_Channel1_IRQHandler(void)
{
    if(DMA_GetITStatus(DMA1_IT_TC1) != RESET)
    {
        // 传输完成处理代码
        DMA_ClearITPendingBit(DMA1_IT_TC1);
    }
    else if(DMA_GetITStatus(DMA1_IT_TE1) != RESET)
    {
        // 传输错误处理代码
        DMA_ClearITPendingBit(DMA1_IT_TE1);
    }
}

应用注意事项

  1. 在启动DMA传输前,确保正确配置外设和DMA的时钟
  2. 注意数据对齐问题,特别是在使用不同数据宽度时
  3. 合理使用中断功能,避免过度占用CPU资源
  4. 注意存储器和外设地址的有效性,防止访问非法地址

 通过合理使用DMA,可以显著提高系统性能,特别是在需要处理大量数据传输的应用场景中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值