【嵌入式系统】DMA工作原理与常用函数解析
1、DMA基本原理
直接存储器访问通道(DMA, Direct Memory Access)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。CPU只需初始化DMA,传输本身由DMA控制器来实现而无须CPU干预。DMA挂载在AHB上且数据传输前后不必保存上下文,因此数据可通过DMA高速移动。设置DMA的目的是:通过硬件为存储器和外设间开通若干个直接进行数据传输的通道,节约CPU资源。
例如图1所示,APB2上挂载的某个外设发起DMA请求,当DMA由CPU使能后开启相应的DMA通道,于是外设通过DMA通道、总线矩阵直接进行了对内存的读或写操作。
2 DMA通道与配置
大容量STM32 MCU有两个DMA控制器,共12个通道(DMA1有7个通道,DMA2有5个通道),通道的基本属性如图2所示
注
如图4所示,DMA每个通道提前规定了特定外设和存储器间的直接数据交换。例如,外设ADC1只能通过Access1与内存进行数据交换,在配置DMA源和目的基地址时要遵照图4所示的预设规定。
由于DMA控制器一次只能开启一个通道,因此若同一时间有多个来自不同通道的外设进行DMA请求,就需要通过通道优先级来使能高优先级通道(当优先级相同时,通道标号小的优先使能)。DMA控制器内部有一个仲裁器来协调各个DMA请求的优先权。
若配置内存外设数据单位相同,则从源地址处读取一个单位数据包,往目的地址出写一个相同宽度的数据包即可。若两者单位不相同,就要参考“可编程的数据宽度”对照表进行数据传输操作。
3、DMA使用流程与相关函数
由于DMA通道需要配置的参数较多,因此使用结构体来简化API输入参数。因此这里先根据需要配置结构体DMA_InitStructure,其成员变量如图2所示均为通道基本参数;再以DMA结构体指针作为DMA_Init()的输入参数,在其内部配置相应的寄存器。接下来对DMA_Init()函数作解析,其余函数类似。
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct) { uint32_t tmpreg = 0; /*--------------------------- 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_CCR。由于CCR中包含了数据传输方向、数据单位等大量信息,为便于修改和程序移植,函数定义了32bits的临时寄存器tmpreg用于存储这些信息。tmpreg首先捕获当前DMA_CCR的位向量并使用掩码CCR_CLEAR_Mask进行复位,防止置位时产生进位错误。事实上出于安全起见,置位操作前都应该掩去即将要设置的位,今后类似的做法不再赘述。将置位的tmpreg后传给对应通道的CCR即可完成寄存器对应的配置。
接下来通过DMA结构体依次配置CNDTR(记录传输数据大小)、CPAR(外设基地址)、CMAR(内存基地址)。