前言
STM32系列微控制器,作为市场上广受欢迎的高性能、低功耗解决方案,其强大的功能集和灵活的配置选项使其成为众多开发者的首选。在这些功能中,直接内存访问(DMA)技术尤为突出,它不仅提升了数据传输的效率,还极大地减轻了中央处理器(CPU)的负担,使得系统能够执行更多的并行任务,从而显著提高了整体性能。
DMA是一种允许外围设备直接与内存进行数据交换的技术,无需CPU的介入。这种机制在处理大量数据传输时尤为重要,因为它允许CPU专注于执行其他关键任务,而不是被数据传输过程所束缚。在STM32微控制器中,DMA功能的设计和实现,使得开发者能够轻松地配置和管理数据流,无论是在处理传感器数据、执行音频处理还是进行高效的通信任务时,DMA都能发挥其独特的优势。本文将深入探讨STM32微控制器的DMA功能,包括其工作原理、配置过程、以及如何在实际应用中充分利用这一强大的工具。通过本文,你将了解到如何通过DMA技术,释放STM32微控制器的潜力,为你的项目带来更高的性能和更好的用户体验。
DMA简介
在微控制器系统中,数据传输是实现各种功能的基础。传统的数据传输方式通常依赖于CPU的指令来控制,这不仅效率低下,而且在处理大量数据时会严重占用CPU资源,限制了系统的响应速度和并行处理能力。为了解决这一问题,直接内存访问(Direct Memory Access,简称DMA)技术应运而生。DMA是一种硬件机制,它允许外围设备(如ADC、DAC、SD卡控制器等)直接与系统内存进行数据交换,而无需CPU的介入。这种机制通过专门的DMA控制器来实现,它能够独立地管理数据传输,从而释放CPU资源,让其专注于执行其他任务。
其工作原理是DMA控制器在初始化时会接收来自CPU的配置信息,包括源地址、目标地址、传输大小等。一旦配置完成,DMA控制器就会接管数据传输过程。在传输过程中,DMA控制器会根据预设的参数,自动处理数据的读取和写入,直到传输完成。传输完成后,DMA控制器会通过中断信号通知CPU,此时CPU可以执行后续处理或继续其他任务。
DMA与传统CPU控制数据传输的对比:
- 效率:DMA在数据传输期间不需要CPU的持续参与,显著提高了数据传输效率。
- 资源占用:DMA减少了CPU在数据传输过程中的资源占用,使得CPU可以处理更多任务。
- 响应时间:由于DMA的并行处理能力,系统的整体响应时间得以缩短。
- 灵活性:DMA支持多种数据传输模式,如单次传输、循环传输、链式传输等,适应不同的应用需求。
STM32微控制器系列提供了丰富的DMA通道,支持多种外设和内存之间的数据传输。这对于需要处理高速数据流的应用(如音频处理、视频传输、网络通信等)尤为重要。通过合理配置和使用DMA,开发者可以显著提升STM32微控制器的性能,实现更复杂的系统功能,同时保持系统的高效率和低功耗。
STM32 DMA架构详解
在STM32微控制器中,DMA(Direct Memory Access)控制器是一个独立的硬件模块,它负责管理外设与内存之间的数据传输。了解DMA的架构对于有效地利用这一功能至关重要。以下是STM32 DMA控制器的架构概述:
基本组成:
-
DMA控制器核心:这是DMA模块的核心,负责执行数据传输的控制逻辑。它包括状态寄存器、中断和错误状态寄存器、中断使能寄存器等,用于监控DMA传输的状态和处理中断。
-
DMA通道:STM32微控制器提供多个DMA通道,每个通道可以独立配置和控制数据传输。通道的数量和特性取决于具体的STM32系列和型号。
-
DMA流(Stream):在某些STM32系列中,DMA通道被进一步细分为流。每个流可以看作是一个独立的数据传输通道,允许同时进行多个数据传输任务。
-
内存接口:DMA控制器通过内存接口与系统内存相连,支持多种内存访问模式,如单次访问、连续访问、循环访问等。
-
外设接口:DMA控制器通过外设接口与各种外设相连,如ADC、DAC、SPI、I2C等。这些接口允许DMA控制器直接从外设读取数据或向其写入数据。
下图是STM32系统构架图,可以看到STM32F10X系列上的微控制器拥有两个DMA控制器,挂载在APB1、APB2总线上的外设和SDIO接口都能发起DMA请求,DMA1有7个通道,DMA2有5个通道,不同通道对应着不同的外设请求。
DMA配置
在配置每一个外设之前,我们可以去查看标准库中此外设的头文件,通常会有一个结构体,这个结构体里包含了初始化该外设的配置流程,我们按需配置即可,从这个结构体可以看出初始化DMA需要配置外设和内存地址,以及传输的方向(从外设到内存或从内存到外设)、数据宽度(8位、16位或32位)、传输模式(单次、循环、链式)等。
/**
* @brief DMA Init structure definition
*/
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; /*!< Specifies the peripheral base address for DMAy Channelx. */
uint32_t DMA_MemoryBaseAddr; /*!< Specifies the memory base address for DMAy Channelx. */
uint32_t DMA_DIR; /*!< Specifies if the peripheral is the source or destination.
This parameter can be a value of @ref DMA_data_transfer_direction */
uint32_t DMA_BufferSize; /*!< Specifies the buffer size, in data unit, of the specified Channel.
The data unit is equal to the configuration set in DMA_PeripheralDataSize
or DMA_MemoryDataSize members depending in the transfer direction. */
uint32_t DMA_PeripheralInc; /*!< Specifies whether the Peripheral address register is incremented or not.
This parameter can be a value of @ref DMA_peripheral_incremented_mode */
uint32_t DMA_MemoryInc; /*!< Specifies whether the memory address register is incremented or not.
This parameter can be a value of @ref DMA_memory_incremented_mode */
uint32_t DMA_PeripheralDataSize; /*!< Specifies the Peripheral data width.
This parameter can be a value of @ref DMA_peripheral_data_size */
uint32_t DMA_MemoryDataSize; /*!< Specifies the Memory data width.
This parameter can be a value of @ref DMA_memory_data_size */
uint32_t DMA_Mode; /*!< Specifies the operation mode of the DMAy Channelx.
This parameter can be a value of @ref DMA_circular_normal_mode.
@note: The circular buffer mode cannot be used if the memory-to-memory
data transfer is configured on the selected Channel */
uint32_t DMA_Priority; /*!< Specifies the software priority for the DMAy Channelx.
This parameter can be a value of @ref DMA_priority_level */
uint32_t DMA_M2M; /*!< Specifies if the DMAy Channelx will be used in memory-to-memory transfer.
This parameter can be a value of @ref DMA_memory_to_memory */
}DMA_InitTypeDef;
下面是一段配置DMA的实例,这段代码的目的是配置DMA1通道1,使其能够从ADC1的数据寄存器连续读取3个16位的数据,并将这些数据存储到名为ADC_Value
的内存地址。配置完成后,DMA通道被启用,开始执行数据传输任务。
// 声明一个结构体变量,用于存储DMA的配置参数
DMA_InitTypeDef DMA_InitStructure;
// 启用DMA1的时钟,这是为了确保DMA控制器可以正常工作
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 设置DMA的外设基地址,即ADC1的数据寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(ADC1->DR));
// 设置DMA的内存基地址,即存储ADC数据的内存地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_Value;
// 设置DMA传输方向,这里从外设(ADC)读取数据
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
// 设置要传输的数据量,这里是3个数据
DMA_InitStructure.DMA_BufferSize = 3;
// 设置外设地址在传输过程中是否自动增加,这里不增加
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 设置内存地址在传输过程中是否自动增加,这里增加
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 设置每次传输的数据大小,这里是半字(16位)
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
// 设置DMA的工作模式,这里是循环模式,传输完成后会自动重新开始
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// 设置DMA的优先级,这里是高优先级
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 设置是否进行内存到内存的传输,这里是禁用
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
// 使用上述配置初始化DMA1的通道1
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
// 启用DMA1通道1,开始数据传输
DMA_Cmd(DMA1_Channel1, ENABLE);
接下来看看DMA_Init()和DMA_Cmd()到底执行了哪些操作,跳转到函数定义处。
/**
* @brief Initializes the DMAy Channelx according to the specified
* parameters in the DMA_InitStruct.
* @param DMAy_Channelx: where y can be 1 or 2 to select the DMA and
* x can be 1 to 7 for DMA1 and 1 to 5 for DMA2 to select the DMA Channel.
* @param DMA_InitStruct: pointer to a DMA_InitTypeDef structure that
* contains the configuration information for the specified DMA Channel.
* @retval None
*/
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)
{
uint32_t tmpreg = 0;
/* Check the parameters */
assert_param(IS_DMA_ALL_PERIPH(DMAy_Channelx));
assert_param(IS_DMA_DIR(DMA_InitStruct->DMA_DIR));
assert_param(IS_DMA_BUFFER_SIZE(DMA_InitStruct->DMA_BufferSize));
assert_param(IS_DMA_PERIPHERAL_INC_STATE(DMA_InitStruct->DMA_PeripheralInc));
assert_param(IS_DMA_MEMORY_INC_STATE(DMA_InitStruct->DMA_MemoryInc));
assert_param(IS_DMA_PERIPHERAL_DATA_SIZE(DMA_InitStruct->DMA_PeripheralDataSize));
assert_param(IS_DMA_MEMORY_DATA_SIZE(DMA_InitStruct->DMA_MemoryDataSize));
assert_param(IS_DMA_MODE(DMA_InitStruct->DMA_Mode));
assert_param(IS_DMA_PRIORITY(DMA_InitStruct->DMA_Priority));
assert_param(IS_DMA_M2M_STATE(DMA_InitStruct->DMA_M2M));
/*--------------------------- DMAy Channelx CCR Configuration -----------------*/
/* Get the DMAy_Channelx CCR value */
tmpreg = DMAy_Channelx->CCR;
/* Clear MEM2MEM, PL, MSIZE, PSIZE, MINC, PINC, CIRC and DIR bits */
tmpreg &= CCR_CLEAR_Mask;
/* Configure DMAy Channelx: data transfer, data size, priority level and mode */
/* Set DIR bit according to DMA_DIR value */
/* Set CIRC bit according to DMA_Mode value */
/* Set PINC bit according to DMA_PeripheralInc value */
/* Set MINC bit according to DMA_MemoryInc value */
/* Set PSIZE bits according to DMA_PeripheralDataSize value */
/* Set MSIZE bits according to DMA_MemoryDataSize value */
/* Set PL bits according to DMA_Priority value */
/* Set the MEM2MEM bit according to DMA_M2M value */
tmpreg |= DMA_InitStruct->DMA_DIR | DMA_InitStruct->DMA_Mode |
DMA_InitStruct->DMA_PeripheralInc | DMA_InitStruct->DMA_MemoryInc |
DMA_InitStruct->DMA_PeripheralDataSize | DMA_InitStruct->DMA_MemoryDataSize |
DMA_InitStruct->DMA_Priority | DMA_InitStruct->DMA_M2M;
/* Write to DMAy Channelx CCR */
DMAy_Channelx->CCR = tmpreg;
/*--------------------------- DMAy Channelx CNDTR Configuration ---------------*/
/* Write to DMAy Channelx CNDTR */
DMAy_Channelx->CNDTR = DMA_InitStruct->DMA_BufferSize;
/*--------------------------- DMAy Channelx CPAR Configuration ----------------*/
/* Write to DMAy Channelx CPAR */
DMAy_Channelx->CPAR = DMA_InitStruct->DMA_PeripheralBaseAddr;
/*--------------------------- DMAy Channelx CMAR Configuration ----------------*/
/* Write to DMAy Channelx CMAR */
DMAy_Channelx->CMAR = DMA_InitStruct->DMA_MemoryBaseAddr;
}
需要传入DMA初始化函数的参数有:
DMAy_Channelx
:指向DMA通道的指针,y
可以是1或2(表示DMA1或DMA2),x
可以是1到7(DMA1的通道)或1到5(DMA2的通道)。DMA_InitStruct
:指向DMA_InitTypeDef
结构体的指针,包含了DMA通道的配置信息。
在DMA初始化函数内部,首先进行了一系列参数检查,确保传入的参数是有效的。这些检查通过assert_param
宏实现,如果参数不符合预期,程序会进入断言失败状态。然后把相对应的值传给对应的寄存器,即通道配置部分,这里涉及到的主要寄存器有CCR(Channel Control Register)、CNDTR(Channel Number of Data to Transfer Register)、CPAR(Channel Peripheral Address Register)和CMAR(Channel Memory Address Register),其中CCR寄存器包含了DMA通道的控制信息,如传输方向、循环模式、优先级等。代码首先读取当前的CCR值,然后清除一些位,接着根据DMA_InitStruct
中的配置信息设置相应的位。这些位包括数据传输方向(DIR)、循环模式(CIRC)、内存和外设地址增量(PINC、MINC)、数据大小(PSIZE、MSIZE)和优先级(PL)。CNDTR寄存器用于设置要传输的数据数量。这里直接将DMA_InitStruct
中的DMA_BufferSize
值写入CNDTR。CPAR寄存器设置外设的基地址,CMAR寄存器设置内存的基地址。这里将DMA_InitStruct
中的DMA_PeripheralBaseAddr
和DMA_MemoryBaseAddr
分别写入CPAR和CMAR。
/**
* @brief Enables or disables the specified DMAy Channelx.
* @param DMAy_Channelx: where y can be 1 or 2 to select the DMA and
* x can be 1 to 7 for DMA1 and 1 to 5 for DMA2 to select the DMA Channel.
* @param NewState: new state of the DMAy Channelx.
* This parameter can be: ENABLE or DISABLE.
* @retval None
*/
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_DMA_ALL_PERIPH(DMAy_Channelx));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
/* Enable the selected DMAy Channelx */
DMAy_Channelx->CCR |= DMA_CCR1_EN;
}
else
{
/* Disable the selected DMAy Channelx */
DMAy_Channelx->CCR &= (uint16_t)(~DMA_CCR1_EN);
}
}
需要传入DMA使能函数的参数有:
DMAy_Channelx
:指向DMA通道的指针,y
可以是1或2(表示DMA1或DMA2),x
可以是1到7(DMA1的通道)或1到5(DMA2的通道)。NewState
:新的状态,可以是ENABLE
(启用)或DISABLE
(禁用)。
同样的,在函数内部,首先进行参数检查,确保传入的DMA通道和状态是有效的。这些检查通过assert_param
宏实现。接下来,根据NewState
参数的值,代码会执行相应的操作来启用或禁用DMA通道:
-
如果
NewState
不是DISABLE
(即ENABLE
),则通过设置CCR寄存器的DMA_CCR1_EN
位来启用DMA通道。这是通过将CCR寄存器的值与DMA_CCR1_EN
进行按位或操作实现的。 -
如果
NewState
是DISABLE
,则通过清除CCR寄存器的DMA_CCR1_EN
位来禁用DMA通道。这是通过将CCR寄存器的值与~DMA_CCR1_EN
(DMA_CCR1_EN
的按位取反)进行按位与操作实现的。
DMA还拥有一些高级特性,例如:链式传输、内存对内存传输、流控制、流链接等。
总结
DMA在STM32项目中的应用非常广泛,尤其是在需要高速数据传输的场景中,例如在音频处理项目中,如实时音频流的录制和播放,DMA可以显著提高系统的性能,使用DMA来配置DMA通道来直接从麦克风的ADC(模数转换器)读取数据,并将数据传输到内存。这样,CPU就不需要参与数据的移动过程,可以专注于其他任务,如音频数据的处理、编码或用户界面的更新。DMA的循环传输模式(Circular Mode)还可以实现无缝的音频流录制,即使在数据缓冲区满时,DMA也会自动重新开始传输,而不会丢失任何数据。通过这些方式,DMA在音频处理和图像传输等应用中显著提高了性能和效率,总的来说,DMA是STM32微控制器系统中不可或缺的一部分,它通过优化数据传输过程,为开发者提供了强大的工具,以实现更高效、更复杂的嵌入式系统设计。