目录
一、工作原理
二、组成部分
三、优缺点
3.1 优点
3.2 缺点
四、基于STM32的DMA驱动程序
4.1 硬件准备
4.2 软件准备
4.3 使用 STM32CubeMX 进行配置
4.3.1 选择芯片
4.3.2 配置时钟
4.3.3 配置串口
4.3.4 配置 DMA
4.3.5 生成代码
4.4 编写 DMA 驱动代码
4.5.代码解释
4.6 编译和下载
4.7 DMA 中断处理(可选)
直接内存访问(Direct Memory Access,简称 DMA)是一种在计算机系统中广泛使用的技术,它允许外部设备(如磁盘驱动器、网卡等)与内存之间直接进行数据传输,而无需 CPU 进行大量的干预。下面将从工作原理、组成部分、优缺点、应用场景等方面对其进行详细介绍:
请求阶段:当外设准备好进行数据传输时,会向 DMA 控制器发出 DMA 请求信号。例如,磁盘驱动器读取完一个数据块后,就会向 DMA 控制器发送请求。
响应阶段:DMA 控制器接收到请求后,会向 CPU 发出总线请求信号,请求获取系统总线的控制权。CPU 在当前总线周期结束后,会响应 DMA 请求,并将总线控制权交给 DMA 控制器。
传输阶段:DMA 控制器获得总线控制权后,会根据预先设置的参数(如源地址、目的地址、传输数据长度等),在内存和外设之间直接进行数据传输。整个传输过程由 DMA 控制器独立完成,CPU 可以在这段时间内执行其他任务。
结束阶段:数据传输完成后,DMA 控制器会向 CPU 发出结束信号,将总线控制权交还给 CPU,并向外设和 CPU 报告传输结果。
DMA 控制器:是 DMA 技术的核心,负责管理和控制数据传输过程。它包含多个寄存器,用于存储源地址、目的地址、传输数据长度等信息,还具备总线控制逻辑,能够在 CPU 和外设之间协调总线的使用。
地址寄存器:用于存放数据传输的源地址和目的地址。在传输过程中,DMA 控制器会根据这些地址信息,从源地址读取数据,并将其写入到目的地址。
字节计数器:记录要传输的数据字节数。在每次数据传输后,字节计数器的值会自动减 1,当计数器的值减为 0 时,表示数据传输完成。
控制逻辑:负责产生各种控制信号,如读写信号、总线请求信号、总线响应信号等,以协调数据传输过程。
硬件成本高:需要额外的 DMA 控制器和相关的硬件电路,增加了系统的硬件成本和复杂度。
编程复杂:使用 DMA 进行数据传输需要对 DMA 控制器的寄存器进行编程设置,这对于一些开发者来说可能具有一定的难度。
直接内存访问(DMA)允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。在 STM32 中,DMA 可以在不占用 CPU 的情况下实现数据的快速传输,提高系统的效率。以下是一个基于 STM32Cube HAL 库的 DMA 驱动程序开发示例,以实现从内存到外设(如串口)的数据传输为例。
STM32 开发板(这里以 STM32F4 为例)
USB 转串口模块
Keil MDK 或 IAR 等开发环境
STM32CubeMX
打开 STM32CubeMX,选择你所使用的 STM32 芯片型号。
在 RCC
选项中,配置系统时钟源和时钟树,确保系统时钟正常工作。
在 Connectivity
中选择 USART1
,将模式设置为 Asynchronous
,并配置波特率等参数。
在 DMA Settings
中,点击 Add
按钮,选择 USART1_TX
,配置 DMA 通道和传输方向(从内存到外设)。
完成配置后,点击 Project Manager
,设置项目名称、保存路径和工具链,然后点击 Generate Code
生成代码。
打开生成的项目,在 main.c
文件中添加以下代码:
#include "main.h"
#include "usart.h"
#include "dma.h"
#define BUFFER_SIZE 10
uint8_t tx_buffer[BUFFER_SIZE] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A};
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
while (1)
{
// 使用 DMA 发送数据
HAL_UART_Transmit_DMA(&huart1, tx_buffer, BUFFER_SIZE);
// 等待 DMA 传输完成
while (HAL_DMA_GetState(&hdma_usart1_tx) != HAL_DMA_STATE_READY);
// 延时一段时间
HAL_Delay(1000);
}
}
定义数据缓冲区:tx_buffer
是一个包含 10 个字节数据的数组,用于存储要发送的数据。
初始化函数:在 main
函数中,调用 HAL_Init()
初始化 HAL 库,SystemClock_Config()
配置系统时钟,MX_GPIO_Init()
初始化 GPIO 引脚,MX_DMA_Init()
初始化 DMA 控制器,MX_USART1_UART_Init()
初始化串口。
DMA 数据传输:使用 HAL_UART_Transmit_DMA()
函数启动 DMA 传输,将 tx_buffer
中的数据通过串口发送出去。
等待传输完成:使用 HAL_DMA_GetState()
函数检查 DMA 传输状态,当传输完成后,DMA 状态会变为 HAL_DMA_STATE_READY
。
延时:使用 HAL_Delay()
函数延时 1 秒,然后再次发送数据。
将代码编译并下载到 STM32 开发板中,打开串口调试助手,设置好波特率等参数,即可看到每隔 1 秒接收到一次数据。
如果你需要在 DMA 传输完成后执行一些操作,可以使用 DMA 中断。在 main.c
文件中添加以下代码:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
// DMA 传输完成后的操作
// 例如点亮一个 LED
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
}
}
在 stm32f4xx_it.c
文件中,确保 DMA2_Stream7_IRQHandler()
函数调用了 HAL_DMA_IRQHandler()
:
void DMA2_Stream7_IRQHandler(void)
{
HAL_DMA_IRQHandler(&hdma_usart1_tx);
}
这样,当 DMA 传输完成后,会自动调用 HAL_UART_TxCpltCallback()
函数。