DMA(Direct Memory Access,直接内存访问)是一种计算机数据传输方式,允许外围设备直接访问系统内存,而无需CPU的干预。
文章目录
- Part 1: DMA的工作原理
- 配置阶段:
- 数据传输阶段:
 
- Part 2: DMA数据组成
- Part 3: DMA传输过程的实现
- Part 4: DMA中断处理和性能优化
- DMA中断处理:
- DMA性能优化:
 
- Part 5: STM32实现DMA
- 基于标准库
- 基于HAL库
 
 
Part 1: DMA的工作原理
DMA(Direct Memory Access,直接内存访问)是一种计算机数据传输方式,允许外围设备直接访问系统内存,而无需CPU的干预。下面详细介绍DMA的工作原理:
配置阶段:
-  配置源地址(Source Address):通过指定源地址,DMA可以知道需要传输数据的起始位置。 
-  配置目标地址(Destination Address):指定目标地址,将数据传输到系统内存中的相应位置。 
-  配置数据长度(Data Length):DMA需要知道需要传输的数据长度,以便正确地读取和写入数据。 
-  配置控制信息(Control Information):例如传输模式、中断使能等参数,用于指定传输的具体配置。 
数据传输阶段:

-  外设发起传输请求:外围设备(如网络接口卡、硬盘控制器)向DMA控制器发起传输请求。 
-  DMA控制器响应请求:DMA控制器接收到传输请求后,暂停CPU的访问,并通过请求信号(如DMA请求信号)获取对系统总线的控制权。 
-  读取数据:DMA控制器从外设读取数据,并存储在内部缓冲区中。 
-  数据传输:DMA控制器将数据从内部缓冲区传输到系统内存中的目标地址。 
-  传输完成通知:当数据传输完成后,DMA控制器会释放对系统总线的控制权,并发出传输完成的中断信号,通知CPU。 
-  CPU处理中断:CPU接收到传输完成的中断信号后,会执行相应的中断处理程序。 
Part 2: DMA数据组成
DMA传输涉及的数据主要有以下几种组成:
-  源地址(Source Address):源地址表示数据传输的起始地址,即外设设备中数据缓冲区的地址。DMA将从这个地址开始读取数据。 
-  目标地址(Destination Address):目标地址表示数据传输的目的地址,即系统内存中的指定地址。DMA将数据传输到这个地址。 
-  数据长度(Data Length):数据长度表示需要传输的数据大小。它可以以字节、字或者其他单位进行表示。 
-  控制信息(Control Information):控制信息包括传输模式、中断使能等参数。在传输过程中,DMA根据这些参数来控制数据的传输行为。 
此外,还有一些额外的参数和寄存器与DMA相关,用于配置和控制DMA的操作,例如:
-  DMA通道选择(DMA Channel Selection):在具有多个DMA通道的系统中,选择要使用的DMA通道。 
-  DMA传输模式(DMA Transfer Mode):指定DMA传输的模式,如单次传输模式、循环传输模式等。 
-  DMA中断使能(DMA Interrupt Enable):用于控制DMA传输完成时是否产生中断。 
Part 3: DMA传输过程的实现
DMA的传输过程涉及多个步骤,包括启动DMA、请求传输、数据读取和写入等操作。
 
下面是DMA传输过程的一个简单实现示例:
- 配置DMA参数:
 在开始DMA传输之前,需要先配置DMA相关的参数,如源地址、目标地址、数据长度和控制信息等。这些参数通常通过设置相应的寄存器来实现。
// 配置DMA
void configureDMA(uint32_t sourceAddr, uint32_t destAddr, uint32_t dataLength) {
    // 配置源地址和目标地址
    writeDMARegister(SOURCE_ADDRESS_REG, sourceAddr);
    writeDMARegister(DESTINATION_ADDRESS_REG, destAddr);
    
    // 配置数据长度
    writeDMARegister(DATA_LENGTH_REG, dataLength);
    
    // 配置控制信息,如传输模式、中断使能等
    writeDMARegister(CONTROL_INFO_REG, controlInfo);
}
- 启动DMA传输:
 配置完成后,通过设置相应的使能寄存器,启动DMA传输。
// 启动DMA传输
void startDMA() {
    // 设置DMA使能位
    writeDMARegister(ENABLE_REG, 1);
    
    // 发送传输请求
    sendDMARequest();
}
- 请求传输:
 外设设备发出DMA请求,请求DMA控制权,开始数据传输过程。DMA控制器收到传输请求后,暂停CPU的访问,并通过请求信号(如DMA请求信号)获取对系统总线的控制权。
// 发送DMA传输请求
void sendDMARequest() {
    // 发送DMA请求信号给DMA控制器
    setDMARequestSignal();
}
- 数据读取和写入:
 DMA控制器根据配置的参数,从外设设备中读取数据,并将其写入系统内存中的目标地址。
// 读取数据并写入内存
void transferData() {
    // 从外设读取数据
    uint32_t data = readDataFromPeripheral();
    
    // 写入内存
    writeDataToMemory(data);
}
- 传输完成通知:
 当数据传输完成后,DMA控制器会释放对系统总线的控制权,并发送传输完成的中断信号,通知CPU。
// DMA中断处理函数
void handleDMAInterrupt() {
    // 处理传输完成的中断信号
    // ...
}
Part 4: DMA中断处理和性能优化
DMA中断处理:
在DMA传输完成时,DMA控制器可以触发一个中断,通知CPU传输已完成。CPU可以相应地执行中断处理程序,进行必要的操作。
-  中断使能设置: 
 在配置DMA参数时,通过设置相应的控制信息,可以选择是否使能DMA传输完成中断。如果使能了中断,DMA传输完成时会产生中断请求信号。否则,传输完成后不会触发中断。
-  中断处理程序: 
 在CPU侧,需要编写中断处理程序来处理DMA传输完成中断。中断处理程序负责执行相应的操作,如处理传输完成的数据、清除中断标志等。
DMA性能优化:
为了提高DMA传输的效率和性能,可以采取以下优化技术:
-  数据对齐(Data Alignment): 
 尽可能地对齐数据可以提高DMA的传输效率。许多硬件平台在DMA传输时对数据对齐有限制,所以确保数据在传输过程中的对齐是重要的。
-  数据块传输(Block Transfer): 
 DMA支持以块为单位的数据传输,逐次传输多个数据块,并在传输完成后给出一个中断通知。这种方式比每次传输一个数据更高效,减少了中断的开销和系统总线访问的次数。
-  通道优先级(Channel Priority): 
 在具有多个DMA通道的系统中,可以通过设置不同的通道优先级,来决定DMA通道之间的数据传输优先级。这样可以在多个外设设备同时请求传输时,对优先级较高的设备进行优先处理。
-  多重缓冲区(Double Buffering): 
 使用多个缓冲区来存储数据可以提高DMA传输效率。当DMA从一个缓冲区传输数据时,CPU可以同时向另一个缓冲区写入新的数据,从而实现并行操作。
Part 5: STM32实现DMA
基于标准库
示例代码:
#include "stm32f10x.h"
#define BUFFER_SIZE 100
uint32_t sourceBuffer[BUFFER_SIZE];
uint32_t destinationBuffer[BUFFER_SIZE];
void DMA_Configuration(void) {
    DMA_InitTypeDef DMA_InitStructure;
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    DMA_DeInit(DMA1_Channel1);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)destinationBuffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
    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);
}
int main() {
    // 初始化源缓冲区
    for (int i = 0; i < BUFFER_SIZE; i++) {
        sourceBuffer[i] = i;
    }
    
    // 配置DMA
    DMA_Configuration();
    
    while (1) {
        // 等待DMA传输完成
        while (!DMA_GetFlagStatus(DMA1_FLAG_TC1));
        
        // 处理传输完成的数据
        for (int i = 0; i < BUFFER_SIZE; i++) {
            // 处理destinationBuffer中的数据
            // ...
        }
        
        // 清除DMA传输完成标志位
        DMA_ClearFlag(DMA1_FLAG_TC1);
    }
}
在这个示例代码中,首先通过DMA_Configuration函数进行DMA的配置。然后在主循环中等待DMA传输完成的标志位,处理传输完成的数据,并清除传输完成标志位。
基于HAL库
示例代码:
#include "stm32f1xx_hal.h"
#define BUFFER_SIZE 100
DMA_HandleTypeDef hdma_adc1;
uint32_t sourceBuffer[BUFFER_SIZE];
uint32_t destinationBuffer[BUFFER_SIZE];
void DMA_Configuration(void) {
    __HAL_RCC_DMA1_CLK_ENABLE();
    
    hdma_adc1.Instance = DMA1_Channel1;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc1.Init.Mode = DMA_CIRCULAR;
    hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
    
    HAL_DMA_Init(&hdma_adc1);
    
    __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
    
    HAL_DMA_Start(&hdma_adc1, (uint32_t)&(ADC1->DR), (uint32_t)destinationBuffer, BUFFER_SIZE);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
    // 处理传输完成的数据
    for (int i = 0; i < BUFFER_SIZE; i++) {
        // 处理destinationBuffer中的数据
        // ...
    }
}
int main() {
    // 初始化源缓冲区
    for (int i = 0; i < BUFFER_SIZE; i++) {
        sourceBuffer[i] = i;
    }
    
    // 配置DMA
    DMA_Configuration();
    
    // 启动ADC转换
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)sourceBuffer, BUFFER_SIZE);
    
    while (1) {
        // 主循环中不需要额外的处理
        
        // 在需要使用CPU的其他任务中加入适当的延时或等待DMA传输完成的标志位
        // ...
    }
}
这个示例使用了STM32Cube HAL库提供的HAL库函数进行DMA的配置和控制。在DMA_Configuration函数中,使用HAL_DMA_Init函数进行DMA的初始化,并且通过__HAL_LINKDMA宏将DMA与ADC关联起来。在HAL_ADC_ConvCpltCallback函数中,处理传输完成的数据。



















![P1535 [USACO08MAR] Cow Travelling S(dfs+剪枝 or 记忆化搜索)](https://img-blog.csdnimg.cn/a4db5aab80ee4a0fb939c524bb97912e.png)
