1. 什么是DMA?为什么它如此重要?
直接内存访问(DMA)是一种允许硬件子系统直接读写系统内存的技术,而无需中央处理器(CPU)的介入。想象一下,你正在搬家,如果每件物品都需要你亲自搬运,你会累得筋疲力尽,而且效率低下。但如果你雇了一个搬运工,你只需要告诉他从哪里搬、搬到哪里、搬多少东西,剩下的工作就交给他了,你可以腾出手来做其他更重要的事情。DMA就是这个“搬运工”,它负责在内存和外设之间高效地搬运数据,让CPU专注于计算和控制任务。
在嵌入式系统中,DMA的重要性不言而喻。比如,当你使用STM32微控制器处理传感器数据时,如果没有DMA,每次传感器采集到数据都需要CPU介入,将数据从外设寄存器复制到内存中。这不仅会占用大量的CPU资源,还会降低系统的实时性。而有了DMA,数据可以直接从传感器(外设)传输到内存,CPU只需要在数据准备好后进行处理,大大提高了系统的效率和响应速度。
DMA的核心优势在于其高效性和低CPU占用率。它特别适合处理大量数据的传输任务,比如音频流、图像处理、网络数据包传输等。在这些场景中,DMA可以显著减少CPU的负担,让系统更加高效地运行。
2. DMA的工作原理与传输模式
DMA的工作原理可以概括为“地址映射+数据传输”。它通过专用的DMA控制器来管理数据传输过程,CPU只需要初始化DMA控制器,告诉它源地址、目标地址、传输数据量以及传输模式,剩下的工作就交给DMA控制器了。
DMA的传输模式主要有两种:
- 单次传输模式(Normal Mode):DMA完成一次数据传输后就会停止,需要重新初始化才能进行下一次传输。这种模式适用于不需要连续传输数据的场景,比如一次性读取传感器数据。
- 循环传输模式(Circular Mode):DMA在完成一次传输后会自动重新初始化,继续下一次传输。这种模式适用于需要持续数据传输的场景,比如音频流或实时数据采集。
DMA的数据传输方向也有四种情况:
- 外设到内存(例如从ADC读取数据到数组)
- 内存到外设(例如从数组发送数据到UART)
- 内存到内存(例如数组之间的数据拷贝)
- 外设到外设(较少使用)
在实际应用中,DMA控制器会通过总线矩阵协调数据传输,确保数据能够高效地在不同组件之间流动。例如,当ADC采集到数据时,DMA控制器会直接将这些数据搬运到指定的内存区域,而无需CPU参与。
3. DMA的核心参数与配置要点
要正确使用DMA,必须理解其核心参数。这些参数决定了DMA如何执行数据传输任务,包括源地址、目标地址、传输数据量、传输宽度和地址递增模式等。
3.1 源地址和目标地址
源地址是数据的起始位置,可以是外设寄存器地址(如ADC的数据寄存器)或内存地址(如数组的首地址)。目标地址是数据的目的地,同样可以是外设寄存器或内存地址。在配置时,需要确保这些地址是正确的,否则DMA可能会访问错误的内存区域,导致数据损坏或系统崩溃。
3.2 传输数据量
传输数据量指定了DMA需要传输的数据项数量。例如,如果你需要传输100个字节的数据,就将传输数据量设置为100。DMA控制器会在每次传输后递减这个值,当它变为0时,传输完成。
3.3 传输宽度
传输宽度定义了每次传输的数据大小,可以是字节(8位)、半字(16位)或字(32位)。需要注意的是,源和目标的传输宽度可以不同,DMA控制器会自动处理数据对齐和打包。例如,如果源是字节宽度,目标是半字宽度,DMA会将两个字节组合成一个半字进行传输。
3.4 地址递增模式
地址递增模式决定了DMA传输后地址是否自动增加。如果启用递增模式,DMA在每次传输后会根据传输宽度增加地址,从而访问连续的内存区域。如果禁用递增模式,DMA会一直访问同一个地址,适用于固定寄存器的数据传输。
3.5 传输模式与优先级
DMA支持单次传输和循环传输模式,可以根据需求选择。此外,每个DMA通道都有可配置的优先级,当多个DMA请求同时发生时,优先级高的通道会先被处理。
4. STM32中的DMA资源与通道分配
STM32微控制器中的DMA资源因型号而异。对于大容量的STM32芯片(如STM32F4系列),通常有两个DMA控制器:DMA1和DMA2。DMA1有7个通道,DMA2有5个通道,每个通道可以分配给特定的外设。
4.1 DMA1通道分配
DMA1的通道通常用于以下外设:
- 通道1:TIM2、TIM3、TIM4
- 通道2:ADC1、SPI1
- 通道3:SPI/I2S2
- 通道4:I2C1、I2C2
- 通道5:USART1、USART2
- 通道6:USART3
- 通道7:保留或特定外设
4.2 DMA2通道分配
DMA2的通道通常用于以下外设:
- 通道1:TIM5、TIM6、TIM7、TIM8
- 通道2:ADC3
- 通道3:SPI/I2S3
- 通道4:UART4、DAC
- 通道5:SDIO
需要注意的是,DMA2通常支持内存到内存的传输,而DMA1不支持。因此,如果你需要在两个内存区域之间传输数据,必须使用DMA2。
4.3 通道优先级与仲裁
当多个DMA请求同时发生时,DMA控制器会根据通道的优先级来处理请求。优先级可以通过软件配置,分为四个等级:很高、高、中等和低。如果两个通道的软件优先级相同,编号较小的通道具有更高的硬件优先级。
5. DMA的初始化与寄存器配置
DMA的配置涉及多个寄存器,包括配置寄存器、中断状态寄存器、地址寄存器和数据量寄存器等。下面我们以STM32为例,详细讲解DMA的初始化流程和寄存器配置。
5.1 DMA初始化步骤
-
使能DMA时钟:在使用DMA之前,需要先使能DMA控制器的时钟。例如,对于DMA1,可以使用以下代码:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); -
配置DMA通道参数:设置源地址、目标地址、传输数据量、传输宽度、地址递增模式和传输模式等参数。这些参数通过DMA_InitTypeDef结构体配置。
-
初始化DMA通道:调用DMA_Init函数将配置应用到DMA通道。
-
使能DMA通道:最后,使能DMA通道,开始数据传输。
5.2 关键寄存器详解
- DMA_ISR(中断状态寄存器):用于查询DMA传输状态,如传输完成、半传输完成或传输错误。
- DMA_IFCR(中断标志清除寄存器):用于清除DMA_ISR中的标志位。
- DMA_CCRx(通道配置寄存器):控制通道的传输方向、优先级、传输宽度等。
- DMA_CNDTRx(通道数据量寄存器):存储剩余传输数据量,传输完成后变为0。
- DMA_CPARx(外设地址寄存器):存储外设地址,如UART的数据寄存器地址。
- DMA_CMARx(内存地址寄存器):存储内存地址,如数组的首地址。
5.3 代码示例:配置DMA进行内存到外设传输
以下代码展示了如何配置DMA1的通道4用于UART1的数据发送:
void DMA_Config(void) {
DMA_InitTypeDef DMA_InitStructure;
// 使能DMA1时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 配置DMA通道4
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; // 外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuffer; // 内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 传输方向:内存到外设
DMA_InitStructure.DMA_BufferSize = 100; // 传输数据量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据宽度:字节
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据宽度:字节
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 单次传输模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 禁用内存到内存模式
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
// 使能DMA通道4
DMA_Cmd(DMA1_Channel4, ENABLE);
}
6. 使用库函数简化DMA配置
STM32的标准外设库提供了一系列函数来简化DMA的配置和使用。这些函数封装了底层寄存器的操作,使开发者能够更专注于应用逻辑。
6.1 关键库函数介绍
- DMA_DeInit:将DMA通道寄存器恢复为默认值。
- DMA_Init:初始化DMA通道,配置传输参数。
- DMA_Cmd:使能或禁用DMA通道。
- DMA_ITConfig:配置DMA中断,如传输完成中断或半传输中断。
- DMA_SetCurrDataCounter:设置传输数据量。
- DMA_GetCurrDataCounter:获取剩余传输数据量。
6.2 库函数配置流程
- 使能DMA时钟。
- 初始化DMA通道参数。
- 使能外设的DMA请求(例如,使能UART的DMA发送)。
- 使能DMA通道。
- 查询传输状态或使用中断处理传输完成事件。
6.3 代码示例:使用库函数实现DMA传输
以下代码展示了如何使用库函数配置DMA进行ADC数据采集:
void DMA_ADC_Config(void) {
DMA_InitTypeDef DMA_InitStructure;
// 使能DMA1时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 配置DMA通道1用于ADC
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; // ADC数据寄存器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADCBuffer; // 内存缓冲区地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 传输方向:外设到内存
DMA_InitStructure.DMA_BufferSize = 100; // 传输数据量
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通道1
DMA_Cmd(DMA1_Channel1, ENABLE);
// 使能ADC的DMA请求
ADC_DMACmd(ADC1, ENABLE);
}
7. 实际应用案例:UART与ADC的DMA传输
DMA在实际项目中应用广泛,下面我们通过两个常见案例来展示DMA的使用方法。
7.1 UART DMA传输
UART DMA传输可以用于高效地发送和接收数据。例如,当需要发送大量数据时,使用DMA可以避免CPU频繁介入,提高系统效率。
代码示例:UART DMA发送
void UART_DMA_Send(uint8_t *data, uint16_t len) {
// 等待上一次传输完成
while (DMA_GetFlagStatus(DMA1_FLAG_TC4) == RESET);
// 禁用DMA通道
DMA_Cmd(DMA1_Channel4, DISABLE);
// 设置传输数据量
DMA_SetCurrDataCounter(DMA1_Channel4, len);
// 设置内存地址
DMA1_Channel4->CMAR = (uint32_t)data;
// 使能DMA通道
DMA_Cmd(DMA1_Channel4, ENABLE);
// 使能UART的DMA发送
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
}
7.2 ADC DMA采集
ADC DMA采集适用于需要连续采集数据的场景,比如实时监测传感器数据。
代码示例:ADC DMA采集
void ADC_DMA_Init(void) {
// 初始化ADC和DMA
// ...
// 启动ADC转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
// 中断处理函数:数据传输完成时调用
void DMA1_Channel1_IRQHandler(void) {
if (DMA_GetITStatus(DMA1_IT_TC1)) {
// 处理采集完成的数据
ProcessData(ADCBuffer);
// 清除中断标志
DMA_ClearITPendingBit(DMA1_IT_TC1);
}
}
8. DMA的常见问题与调试技巧
在使用DMA时,可能会遇到各种问题,比如数据传输错误、地址对齐问题或中断无法触发等。下面分享一些常见问题的解决方法
1679




被折叠的 条评论
为什么被折叠?



