什么是DMA
DMA,全称直接存储器访问(Direct Memory Access),是一种允许硬件子系统直接读写系统内存的技术,无需中央处理单元(CPU)的介入。下面是DMA的工作原理概述:
数据传输触发:DMA传输可以由CPU指令触发,也可以由硬件事件(如一个外设准备好数据)自动触发。
外设和内存之间的数据交换:一旦DMA传输开始,DMA控制器会将数据从源地址直接传送到目的地址。源地址常常是外设的数据寄存器,目的地址通常是内存中的一个缓冲区;反之亦然。
CPU解放:在没有DMA的情况下,CPU需要执行多个指令来完成数据的传送,包括数据的读取和写入操作。当使用DMA时,CPU只需初始化传输,之后DMA控制器将自动处理数据传送,CPU则可以执行其他任务。
传输完成中断:当所有数据都被传输至目的地之后,DMA控制器会向CPU发送一个中断信号。这个中断告知CPU数据已经成功传输完毕,CPU随后可以进行后续处理,比如停止DMA,处理数据,或重新初始化另一次DMA传输。
优先级和通道:DMA控制器可能有多个通道,每个通道可以独立配置并与特定的外设关联。在多通道DMA系统中,可能会有优先级设置,以决定哪个DMA请求被优先处理。
DMA是提高数据传输效率、减少CPU负荷、优化系统性能的有效手段,特别是在处理高速数据流或频繁数据传输时。在嵌入式系统和计算机架构中,有了DMA,CPU就可以更有效地处理逻辑计算和数据处理任务,而不是花费大量时间在数据移动上。
DMA可以用于哪些数据传输方式?
DMA 可以用于多种数据传输方式,包括:
1、内存到内存:数据可以直接从一个内存地址复制到另一个内存地址,无需CPU的介入。
2、外设到内存:数据从外设(如ADC,UART接收缓冲区,SPI等)直接传输到内存。这通常用于从外设读取数据时。
3、内存到外设:数据从内存直接传输到外设(如DAC,UART发送缓冲区,SPI等)。这通常在向外设写入数据时使用。
4、外设到外设:虽然不是所有DMA控制器都支持这个功能,但有些高级系统允许直接从一个外设到另一个外设的数据传输,不经过内存。
这些传输方式中,最常见的使用场景是外设到内存和内存到外设。因为这可以大幅度减轻CPU的工作负担,尤其是在数据流量较大时,如从网络接口接收数据包,或向图形处理器发送图像数据。
DMA初始化的步骤有哪些?
DMA初始化的步骤通常如下所示:
1、DMA控制器时钟使能:
通过 RCC(Reset and Clock Control)模块使能 DMA 控制器的时钟。
2、DMA通道配置:
a、为目标 DMA 通道配置传输方向(从内存到外设、从外设到内存或者内存到内存)。
b、设置源地址和目的地址及其增量模式(地址在传输后是否递增)。
c、配置数据大小(传输的数据宽度,一般为8位、16位或32位)。
d、设定传输数据的数量(数据块大小)。
3、外设配置:
如果外设(如 ADC, UART, SPI)使用 DMA 进行数据传输,需要在外设的配置中使能相应的 DMA 传输请求。
4、中断配置(可选):
根据实际需要,可以配置并使能传输完成、半传输完成及传输错误中断。
5、DMA流控制(如果适用):
在某些情况下,特别是在使用双缓冲或循环模式时,可能需要配置 DMA 流控制。
6、启动DMA传输:
使用相应的库函数或直接通过控制寄存器启动 DMA 传输。
重要寄存器
STM32微控制器中的DMA(直接存储器访问)寄存器包括几组关键的控制和状态寄存器,用于管理和监控DMA的操作。具体的寄存器会根据STM32的不同系列及其内部架构的差异而有所不同,但一般而言,一个DMA通道会涉及以下几个主要寄存器:
1、DMA控制寄存器 (DMA_CCRx):
控制传输的基本配置,包括传输方向、传输模式(正常或循环模式)、优先级、内存和外设大小、内存增量模式、外设增量模式等。
2、DMA数量寄存器 (DMA_CNDTRx):
包含被传输数据的数量。一次DMA操作开始前,此寄存器需要被加载。
3、DMA外设地址寄存器 (DMA_CPARx):
保存外部外设的基地址。
4、DMA内存地址寄存器 (DMA_CMARx):
保存内存的基地址。这是数据要被传输到或从中传输出的内存位置。
5、DMA状态寄存器 (DMA_ISR):
表示每个通道的状态,包括传输完成、半传输、传输错误和全局中断标志。
6、DMA标志清除寄存器 (DMA_IFCR):
用于清除特定通道的中断标志位。
DMA的状态寄存器具体表示了哪些通道的状态?
DMA的状态寄存器提供了关于各通道传输状态的具体信息,主要包括:
1、传输完成(TC):指示相应DMA通道是否已经完成所配置的数据传输任务。
2、半传输(HT):指示相应DMA通道是否已经完成一半的数据传输(对于某些应用可能需要在传输一半时进行处理)。
3、传输错误(TE):如果DMA通道在传输过程中遇到错误,该标志会被置位。
这些状态可以用来触发中断服务程序(Interrupt Service Routines, ISRs), 允许软件响应DMA传输的完成或错误。开发者通常会在对应的中断处理函数中检查这些状态,以确定传输是否成功,或者是否需要采取措施来处理错误情况。
DMA的中断服务程序是如何触发的?
DMA的中断服务程序(ISR)是通过设置DMA控制寄存器中的中断使能位来触发的。具体触发步骤如下:
1、使能中断:在初始化DMA时,你需要在DMA通道的控制寄存器(DMA_CCRx)中使能传输完成中断、半完成中断和/或传输错误中断。
2、配置NVIC:在嵌套向量中断控制器(NVIC)中,你需要为对应的DMA通道中断配置优先级,并使能中断。
3、中断发生:
a、当DMA传输完指定数量的数据后,如果启用了传输完成中断,DMA控制器将设置状态寄存器(DMA_ISR)的传输完成标志(TC)。
b、 如果启用了半传输中断,当传输完成一半的数据量时,DMA控制器将设置状态寄存器的半传输完成标志(HT)。
c、如果在传输过程中发生任何错误,如FIFO溢出或者总线错误等,DMA控制器将设置状态寄存器的传输错误标志(TE)。
4、中断处理:一旦相应的标志位被置位,如果NVIC中对应的中断已使能,CPU将暂停当前任务,跳转到对应的中断服务程序执行中断处理代码。
5、中断标志清除:在中断服务程序中,需要手动清除DMA的状态寄存器中的中断标志位,以防止重复进入中断服务程序。
通过以上步骤,STM32 中的DMA中断服务程序可以在特定的事件发生时被触发,以便进行如数据处理、错误处理等操作。
代码演示
下面是一个简单的STM32程序例子,用于演示如何使用DMA将数据从一个数组复制到另一个数组(内存到内存的DMA传输)
#include "stm32f4xx.h"
// 定义数组大小
#define ARRAYSIZE 5
// 定义源数组和目标数组
uint32_t srcArray[ARRAY_SIZE] = {1, 2, 3, 4, 5};
uint32_t destArray[ARRAY_SIZE] = {0, 0, 0, 0, 0};
void DMA_Config(void)
{
// 手动使能DMA1时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
// 创建DMA初始化结构体并填入配置
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Stream0); // 将DMA Stream恢复到初始状态
DMA_InitStructure.DMA_Channel = DMA_Channel_0;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&srcArray; // 设置DMA源地址
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&destArray; // 设置DMA目标地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory; // 设置内存到内存模式
DMA_InitStructure.DMA_BufferSize = ARRAY_SIZE; // 传输的数据大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; // 外设地址递增
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_Normal; // 设置为正常模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Low; // 设置优先级为低
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; // FIFO模式禁用
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; // FIFO阈值设置
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; // 单次突发模式
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; // 单次突发模式
// 初始化DMA
DMA_Init(DMA1_Stream0, &DMA_InitStructure);
// 使能DMA流
DMA_Cmd(DMA1_Stream0, ENABLE);
// 如果都是定期的规律的检查传输状态,可以留空这里
// 如果你需要传输完成的通知,可能需要使用中断和相应的服务函数
}
int main(void)
{
// 系统初始化
SystemInit();
// 配置DMA
DMA_Config();
// 在这里,我们假定系统只是简单地执行一次内存到内存的复制
// 复制完后,可以添加代码检查destArray数组中的值
// 这里以简单的检查第一个元素是否等于1为例
if (destArray[0] == 1)
{
// 如果传输成功,将会进入到这里
}
// 主循环,程序可以继续执行或者进入睡眠
while (1)
{
}
}