作者:junziyang


(注:如非特別声明,以下笔记内容均针对stm32f103ZET6而言。不同型号,细节可能存在差别)

8.1 DMA简介

DMA是Direct memory access的简称,意为直接存储器访问,是一种提高数据传输速度,并在数据传输过程中为CPU减负的技术。常规的数据传输过程中,CPU要先将数据从存储器或外设的数据寄存器读取到缓存,然后再写入到目的地(存储器或外设数据寄存器)。DMA传输数据时则无需经CPU处理,而是直接将数据从一个地址复制到另一个地址,因此数据传输过程中极大地减少了对CPU资源的占用。

8.2 DMA的原理框图

stm32内置有两个DMA控制器,原理框图如图1所示。

stm32的dma工作原理(自学STM32-08)(1)

图1. DMA原理框图

从图中可以看出,DMA1有7个通道,DMA2有5个通道,共有12个通道。DMA充当的是外设和存储器(内部SRAM,SD卡,FSMC接口的外部存储器)之间的桥梁,由外设发起DMA请求,实现数据在DMA控制下在外设和存储器,以及存储器和存储器之间传输。每个DMA通道都可以独立响应一到多个外设访问请求,由DMA内部的仲裁器(Arbiter)来管理DMA请求的优先级。DMA的主要特征包括:

8.3 DMA主要寄存器

任何外设的功能配置都是通过设置相关寄存器实现的。为了便于后续学习,将DMA相关的寄存器先列于此。DMA相关寄存器地址映射与复位值如图2所示。

stm32的dma工作原理(自学STM32-08)(2)

图2. DMA相关寄存器地址映射与复位值表

前两个寄存器是DMA各个通道共用的寄存器,DMA_ISR(Interrupt Status Register)管理各个通道的中断状态标志,每个通道都有4个中断。DMA_IFCR(Interrupt Flag Clear Register)用来复位DMA_ISR的对应位。

后面的4个寄存器是各个通道专用的寄存器,也就是说每个通道都有4个这样的寄存器。DMA_CMAx后,地址偏移4个字节,然后排入下一个通道的4个寄存器,依次类推。

8.4 DMA主要功能及设置

DMA控制器是通过与内核共享总线来实现直接存储器访问的。总线矩阵通过轮询调度来管理CPU和DMA对总线的访问,一般会保证CPU对总线有至少一半的占用带宽。尽管如此,当出现CPU和DMA同时要访问同一个目标(存储器或外设)时,仍存在CPU对总线的访问被DMA请求妨碍几个总线时钟周期的可能。因此,程序中执行完DMA相关的指令后(例如HAL_ADC_START_DMA()),一般要适当延迟,然后再去读取DMA传输过去的数据,否则可能会出现数据读取错误的现象。

DMA的最大优点是可以独立于CPU去实现数据的传输,最好将DMA传输数据的过程和CPU对数据的处理过程分开。例如,ADC扫描模式下必须使用DMA,此时可将数据采集和数据处理分开,在一个循环中进行AD转换,DMA将数据存入数组;在另一个循环中再去访问数组中的数据并进行处理。而不是在一个循环中,触发ADC一次,立即去读数组中的数据,这样容易导致程序“跑飞”。

8.3.1 DMA传输过程

触发DMA的某个事件发生后,先由外设向DMA控制器发送请求信号。DMA控制器会按信道的优先级响应收到的请求。当DMA控制器访问外设时,会向外设发出应答信号。外设收到应答信号后立刻释放请求,进而DMA控制器立刻释放应答,并开始传输数据。如果有更多的请求,外设可以启动下一次传输。简而言之,一次DMA传输包括一下三个操作步骤:

8.3.2 仲裁器

仲裁器Arbiter根据通道DMA请求的优先级设置,来依次启动对外设/存储器的访问。优先权的管理分两个阶段:

上述是同一个DMA内部通道的优先级管理,由各自的仲裁器负责。另外,在有两个DMA的MCU中,在经Bus Matrix争取总线使用权的过程中,DMA1控制器的优先级高于DMA2的。

8.3.3 DMA的通道管理

用DMA来传输数据时,需要约定通道两端的地址、传输数据的宽度(字节/半字/全字)、待传输数据个数、数据传输模式、缓存使用方式等。

1. 通道设置步骤

通道配置的步骤如下:(x是通道编号)

DMA通道激活后即可响应来自外设的DMA请求了。

2. 循环模式

设置DMA_CCRx寄存器中的CIRC位为1可以开启循环模式。循环模式主要用来处理连续的数据流,例如ADC扫描模式中的连续数据采集。该模式下DMA_CNDTRx寄存器中的值递减到0后,会重新装载回初值,缓存也会被循环利用。

3. 存储器-存储器模式

除了实现外设-存储器间的数据传输,DMA模式也可以用来在两个存储器间传输数据。通过设置DMA_CRx寄存器中的MEM2MEM位即可开启存储器-存储器模式。无论是外设-存储器模式,还是存储器-存储器模式,本质上来说DMA都只是将数据从一个地址搬运到另一个地址。区别之处在于:外设地址通常是固定的,而存储器地址可以人为设置且可递增;外设可以发出DMA请求,而存储器间的数据传输需要通过软件设置DMA_CCRx寄存器的EN位来触发DMA。通信双方的首地址仍然是在CPARx和CMARx两个寄存器中设置,传输方向也是由CCRx寄存器中的DIR位设定。

存储器间的数据传输不要同时开启循环模式。DMA_CNDTRx寄存器递减到0,传输自动停止。

8.3.4 数据宽度与对齐问题

DMA支持按字节、半字或全字传输数据。如果双方的数据宽度相同,DMA只需将数据依次传输过去即可。但如果源和目标端的数据宽度不同,即PSIZE和MSIZE不相等,就涉及到传输过程中的数据对齐问题。

如果目标端数据宽度比源端大,则目标端接收数据后按右对齐存储,闲置高位补0。例如,源为8位,目标为32位,传输4个数据,传输过程和结果如下:

stm32的dma工作原理(自学STM32-08)(3)

如果目标端数据宽度比源端小,则源端高位被截掉,仅传输目标端能容纳的低位。例如,源为32位,目标为16位,传输4个数据,传输过程和结果如下:

stm32的dma工作原理(自学STM32-08)(4)

可见,无论哪种情况,DMA都是按源端的数据宽度读取数据,然后按目标端的数据宽度按右对齐写入数据的。目标端按自己的数据宽度和传输数据个数分配内存,传输过来的数据“多去少补,对号入座”。如果条件允许,尽量双方一致,尤其要避免“大筐倒小筐”。

注意:

对于不支持字节和半字写入的AHB外设,传入的字节或半字会从低位到高位重复填满总线数据寄存器HWDATA。例如0xAB在会被重复成0xABABABAB。这种情况下,如果从内存向寄存器写入数据,内存端MSIZE应该按存储器数据宽度设置,而外设端PSIZE则设为32bit。例如向APB备份寄存器(16位有效,但占32位地址)写入,MSIZE配置位16位,PSIZE配置位32位。

8.3.5 错误与中断管理

在DMA读/写预设地址的过程中,如果出现错误,硬件会自动关闭出错的通道,清除DMA_CCRx中的EN位,设置DMA_IFR中的TEIF(Transfer Error Interrupt Flag)中断标志位。如果开启了CCRx中的TEIE位,则会产生中断。

DMA传输过程中会设置3个中断标志位:传输过半(HTIF)、传输完成(TCIF)、传输出错(TEIF)。如果设置了中断使能位(TCIE/HTIE/TEIE),则会产生对应的中断。

8.3.6 DMA请求的通道映射

来自外设的DMA请求共有7类:TIMx[1,2,3,4], ADC1, SPI1, SPI/I2S2, I2Cx[1,2] 以及

USARTx[1,2,3]。这些请求被固定的映射到了DMA1的7个通道和DMA2的5个通道。DMA1的映射关系如图3所示。DMA2的映射关系如图4所示。

stm32的dma工作原理(自学STM32-08)(5)

图3. DMA1的通道映射

stm32的dma工作原理(自学STM32-08)(6)

图4. DMA2的通道映射

总结:

8.4 DMA寄存器

DMA的寄存器非常简单,如图2所示。有2个公用寄存器:DMA_ISR和DMA_IFCR。每个通道还有4个专用寄存器:DMA_CCRx/CNDTRx/CPARx/CMARx

8.4.1 DMA_ISR中断状态寄存器

DMA_ISR(Interrupt Status Register)用来管理DMA的中断状态。由硬件设置,软件清除(设置IFCR寄存器的对应位)。每个通道的4个中断占连续的4个位。从低到高依次为GIFx(全局中断标志)、TCIFx(传输完成中断标志)、HTIFx(传输过半中断标志)、TEIFx(传输错误中断标志)。每个通道实际只有3个中断,其中GIFx是其余3个标志位相“与”的结果,三者中任何一种中断事件发生,该位都会被置1。

8.4.2 DMA_IFCR中断状态清除寄存器

DMA_IFCR(Interrupt Flag Clear Register)用来清除ISR寄存器中的对应位,清除中断状态。ISR是只读的,中断发生时由硬件设置,要清除中断标志位,需要向DMA_IFCR寄存器的对应位写入1(写0无影响)。另外,向CGIFx写入1可以清除通道x所有的中断标志位

8.4.3 DMA_CCRx通道配置寄存器

DMA_CCR(Channel x Configuration Register)用来配置通道x。各功能位作用如下:

8.4.4 DMA_CNDTRx数据个数寄存器

DMA_CNDTRx(Channel x Number of Data Register)用来存储待传输数据个数。每次传输数值递减1。非循环模式下,递减到0时DMA请求结束;循环模式下,递减凋0后会自动加载初值,继续DMA传输。

8.4.5 DMA_CPARx外设地址寄存器

DMA_CPARx(Channel x Peripheral Address Register)用来设置外设基地址。当PSIZE=01,即数据宽度为16位时,地址中的PA[0]位被忽略,数据自动对齐到半字地址;当PSIZE=10,即数据宽度为32位时,地址中的PA[1:0]位被忽略,数据自动对齐到字地址。


说明:

半字为16bits,即2个字节。字节是最小的存储单位,即每个地址存1个字节。地址0b000x-0b001x,间隔2个字节,恰为1个半字的空间。同理,0b01xx-0b02xx,间隔4个字节,即一个全字的空间,所以32位时忽略地址的末2位。


8.4.6 DMA_CMARx内存地址寄存器

DMA_CMARx(Channel x Memory Address Register)用来设置存储器(数组)基地址。DMA_CPARx和CMARx实际上就是两个地址存储器,用来存储源和目标的地址。MEM2MEM时存储的都是存储器中的地址。

8.5 HAL库DMA相关的函数

HAL库stm32f103xe.h中对DMA的通道地址进行了映射,可以通过通道宏名称,访问通道的CCR/CNDTR/CPAR/CMAR寄存器。DMA相关的库函数在stm32f1xx_hal_dma.h中声明,在stm32f1xx_hal_dma.c中定义。分类概述如下:

1. 初始化与复位函数

DMA初始化函数。设置CCR寄存器中的PL/MSIZE/PSIZE/MINC/PINC/CIRC/DIR,共7个功能位。

DMA复位函数。关闭DMA,然后清除CCR&CNDTR&CPAR&CMAR,清除hdma句柄中的中断函数,复位State状态机和ErrorCode。

2. 启动与停止函数

启动DMA。函数中完成的工作包括:中断标志位清除、设置CNDTR、CPAR、CMAR。设置这些参数前必须先将CCRx的EN位置0(__HAL_DMA_DISABLE(hdma)),然后调用DMA_SetConfig()函数进行设置,设置完成后重新开启(__HAL_DMA_ENABLE(hdma))。

以中断模式启动DMA。除完成HAL_DMA_Start()的工作外,还开启了3个中断使能位。

终止DMA传输。函数中完成的工作包括:关闭中断、关闭DMA通道、中断标志位清除(向IFCR中的CGIFx写入1)。

终止DMA传输并触发相应的中断。

3. 状态查询函数

检查传输状态。通过轮询等待返回传输状态(传输过半/传输完成)标志。

查询DMA状态。返回DMA状态机的值,即hdma->State。

查询DMA错误。返回DMA错误代码,即hdma->ErrorCode。

4. 中断处理函数

中断请求处理函数。根据hdma的中断设置,调用相应的回调函数(传输完成/过半/出错/终止)。回调函数需要用户自己编写,一般是在外设的HAL_PPP_Stat_DMA()函数中被关联到hdma的句柄。例如,HAL_ADC_Start_DMA()中会把HAL_ADC_ConvHalfCpltCallback/HAL_ADC_ConvCpltCallback/HAL_ADC_ErrorCallback函数关联到DMA的句柄hdma。回调函数是weak函数,需要用户自己实现。

5. 宏库函数

,