一、STM32 DMA模块简介
DMA(直接存储器访问)是STM32微控制器中用于实现数据高效传输的重要功能模块。它允许外设与存储器之间或存储器与存储器之间的数据传输,而无需CPU的直接干预,从而大大提高了系统的运行效率,节省了CPU资源。DMA模块广泛应用于各种需要大量数据传输的场合,例如数据采集、音频处理、图像传输等。
1.工作原理
DMA模块通过硬件机制在内存和外设之间直接传输数据。它可以在外设产生数据请求时自动启动传输,或者由软件触发。DMA模块支持多种传输模式和配置选项,能够满足不同的应用需求。
2.功能特点
-
高效率:DMA模块可以独立于CPU运行,大大减少了CPU的负担,提高了系统的整体性能。
-
灵活的配置:支持多种传输模式、数据宽度和传输方向,能够满足不同的应用需求。
-
中断支持:DMA模块可以配置中断,当传输完成或发生错误时,可以通知CPU进行处理。
-
优先级管理:DMA模块支持多个通道的优先级配置,确保高优先级的传输可以优先完成。
3.应用场景
-
数据采集:从ADC模块获取数据并存储到内存中,无需CPU干预。
-
音频处理:将音频数据从内存传输到音频接口模块,实现音频播放。
-
图像传输:在图像处理应用中,将图像数据从内存传输到显示模块。
-
通信模块:将数据从内存传输到USART、SPI等通信模块,实现高效的数据发送。
二、STM32 DMA模块的初始化与配置
1.起始地址
-
外设起始地址:当DMA从外设(如ADC、USART等)读取数据时,需要指定外设的数据寄存器地址作为源地址。这是DMA从外设获取数据的起始位置。
-
存储器起始地址:当DMA将数据写入存储器(如Flash、SRAM等)时,需要指定存储器的起始地址作为目标地址。这是DMA将数据存储的起始位置。
2. 数据宽度
数据宽度指定了每次传输的数据大小,常见的数据宽度有字节(8位)、半字(16位)、字(32位)等。数据宽度的选择会影响DMA传输的效率和速度。
3. 地址是否自增
-
源地址自增:在数据传输过程中,如果源地址需要自增,那么DMA在每次传输后会自动增加源地址的值,以便从下一个数据位置读取数据。
-
目标地址自增:同样,如果目标地址需要自增,DMA在每次传输后会自动增加目标地址的值,以便将数据写入下一个存储位置。
4. 传输方向
传输方向决定了数据是从外设到存储器,还是从存储器到外设,或者是存储器到存储器的传输。这个方向由DMA的配置决定,并且影响源地址和目标地址的设置。
5. 自动重装器
在DMA传输中,自动重装器用于在传输完成后自动重新加载传输计数器的值,这对于循环传输或连续传输非常有用。例如,在音频数据的连续播放中,DMA可以在传输完一个缓冲区后自动重新加载下一个缓冲区的起始地址和传输大小。
6. 传输计数器
传输计数器用于跟踪DMA传输的数据量。它从初始值开始递减,直到达到0,表示传输完成。传输计数器的初始值由传输大小决定,即DMA需要传输的数据总量。
7. 触发机制
DMA传输可以由硬件触发(如外设事件)或软件触发。触发机制决定了何时开始数据传输,以及是否需要CPU的干预。
8.初始化步骤
-
开启时钟:使能DMA模块和相关外设的时钟。
-
配置DMA参数:设置DMA的传输方向、数据宽度、传输大小、优先级等参数。
-
配置DMA通道:选择DMA通道,配置源地址和目标地址。
-
启动DMA:配置完成后,启动DMA通道,使其进入工作状态。
9.示例代码解析
以下是STM32 DMA模块的初始化代码示例,
#include "stm32f10x.h" // Device header
void DMA_Init()
{
// 1. 开启时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 使能DMA1时钟
// 2. 配置DMA参数
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; // 外设地址(ADC数据寄存器)
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adcBuffer; // 内存地址(存储ADC数据的缓冲区)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 传输方向:外设到内存
DMA_InitStructure.DMA_BufferSize = ADC_BUFFER_SIZE; // 传输大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据宽度:16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 内存数据宽度:16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 禁用内存到内存模式
DMA_Init(DMA1_Channel1, &DMA_InitStructure); // 配置DMA通道1
// 3. 启动DMA
DMA_Cmd(DMA1_Channel1, ENABLE); // 启动DMA通道
}
三、DMA数据传输与中断处理
1.数据传输流程
-
启动传输:通过软件指令或外设请求启动DMA传输。
-
等待传输完成:通过检查DMA的状态标志位或配置中断,等待传输完成。
-
读取或写入数据:根据配置的传输方向,从内存或外设读取或写入数据。
2.示例代码解析
以下是DMA数据传输和中断处理的代码示例,
// DMA中断处理函数
void DMA1_Channel1_IRQHandler(void)
{
if (DMA_GetITStatus(DMA1_IT_TC1) != RESET) // 检查传输完成中断
{
DMA_ClearITPendingBit(DMA1_IT_TC1); // 清除中断标志位
// 传输完成后的处理
}
}
// 启动DMA传输
void DMA_StartTransfer(void)
{
DMA_SetCurrDataCounter(DMA1_Channel1, ADC_BUFFER_SIZE); // 设置传输大小
DMA_Cmd(DMA1_Channel1, ENABLE); // 启动DMA
}
四、DMA模块的应用实例
示例:ADC数据采集与DMA传输
以下是一个完整的应用实例,通过DMA模块将ADC采集到的数据自动传输到内存,并通过串口发送。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "USART.h"
#include "DMA.h"
#include "ADC.h"
uint16_t adcBuffer[ADC_BUFFER_SIZE]; // ADC数据缓冲区
int main()
{
ADC_Init(); // 初始化ADC
DMA_Init(); // 初始化DMA
USART_Init(); // 初始化串口
DMA_StartTransfer(); // 启动DMA传输
while (1)
{
// 等待DMA传输完成
if (DMA_GetITStatus(DMA1_IT_TC1) != RESET)
{
DMA_ClearITPendingBit(DMA1_IT_TC1); // 清除中断标志位
// 将ADC数据通过串口发送
for (int i = 0; i < ADC_BUFFER_SIZE; i++)
{
USART_SendData(USART1, adcBuffer[i]);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); // 等待发送完成
}
}
}
}
代码说明
-
DMA传输:通过DMA模块将ADC采集到的数据自动传输到内存缓冲区。
-
串口发送:将DMA传输完成后的数据通过串口发送出去。
-
实时更新:在主循环中不断检查DMA传输完成标志,并处理数据。
五、总结
STM32的DMA模块是一个功能强大且高效的外设,能够实现数据的快速传输,减少CPU的负担。通过合理的配置和编程,DMA模块可以广泛应用于数据采集、音频处理、图像传输等领域。通过本文的介绍和示例代码,读者可以快速掌握STM32 DMA模块的使用方法,并将其应用于实际项目中。