(注:如非特別声明,以下笔记内容均针对而言。不同型号,细节可能存在差别。)
在头条发技术贴是最没成就感的,为了看到更多有营养的资讯,碰到自己喜欢的技术贴请一定记得,收藏+转发+点赞。
一、DMA简介
DMA是 的简称,意为直接存储器访问,是一种提高数据传输速度,并在数据传输过程中为CPU减负的技术。常规的数据传输过程中,CPU要先将数据从存储器或外设的数据寄存器读取到缓存,然后再写入到目的地(存储器或外设数据寄存器)。DMA传输数据时则无需经CPU处理,而是直接将数据从一个地址复制到另一个地址,因此数据传输过程中极大地减少了对CPU资源的占用。
二、DMA的原理框图
STM32内置有两个DMA控制器,原理框图如图1所示。
图1. DMA原理框图
从图中可以看出,DMA1有7个通道,DMA2有5个通道,共有12个通道。DMA充当的是外设和存储器(内部SRAM,SD卡,FSMC接口的外部存储器)之间的桥梁,由外设发起DMA请求,实现数据在DMA控制下在外设和存储器,以及存储器和存储器之间传输。每个DMA通道都可以独立响应一到多个外设访问请求,由DMA内部的仲裁器()来管理DMA请求的优先级。DMA的主要特征包括:
三、DMA主要寄存器
任何外设的功能配置都是通过设置相关寄存器实现的。为了便于后续学习,将DMA相关的寄存器先列于此。DMA相关寄存器地址映射与复位值如图2所示。
图2. DMA相关寄存器地址映射与复位值表
前两个寄存器是DMA各个通道共用的寄存器,( )管理各个通道的中断状态标志,每个通道都有4个中断。( Flag Clear )用来复位的对应位。
后面的4个寄存器是各个通道专用的寄存器,也就是说每个通道都有4个这样的寄存器。后,地址偏移4个字节,然后排入下一个通道的4个寄存器,依次类推。
四、DMA主要功能及设置
DMA控制器是通过与内核共享总线来实现直接存储器访问的。总线矩阵通过轮询调度来管理CPU和DMA对总线的访问,一般会保证CPU对总线有至少一半的占用带宽。尽管如此,当出现CPU和DMA同时要访问同一个目标(存储器或外设)时,仍存在CPU对总线的访问被DMA请求妨碍几个总线时钟周期的可能。因此,程序中执行完DMA相关的指令后(例如()),一般要适当延迟,然后再去读取DMA传输过去的数据,否则可能会出现数据读取错误的现象。
DMA的最大优点是可以独立于CPU去实现数据的传输,最好将DMA传输数据的过程和CPU对数据的处理过程分开。例如,ADC扫描模式下必须使用DMA,此时可将数据采集和数据处理分开,在一个循环中进行AD转换,DMA将数据存入数组;在另一个循环中再去访问数组中的数据并进行处理。而不是在一个循环中,触发ADC一次,立即去读数组中的数据,这样容易导致程序“跑飞”。
4.1 DMA传输过程
触发DMA的某个事件发生后,先由外设向DMA控制器发送请求信号。DMA控制器会按信道的优先级响应收到的请求。当DMA控制器访问外设时,会向外设发出应答信号。外设收到应答信号后立刻释放请求,进而DMA控制器立刻释放应答,并开始传输数据。如果有更多的请求,外设可以启动下一次传输。简而言之,一次DMA传输包括一下三个操作步骤:
4.2 仲裁器
仲裁器根据通道DMA请求的优先级设置,来依次启动对外设/存储器的访问。优先权的管理分两个阶段:
上述是同一个DMA内部通道的优先级管理,由各自的仲裁器负责。另外,在有两个DMA的MCU中,在经Bus 争取总线使用权的过程中,DMA1控制器的优先级高于DMA2的。
4.3 DMA的通道管理
用DMA来传输数据时,需要约定通道两端的地址、传输数据的宽度(字节/半字/全字)、待传输数据个数、数据传输模式、缓存使用方式等。
1. 通道设置步骤
通道配置的步骤如下:(x是通道编号)
DMA通道激活后即可响应来自外设的DMA请求了。
2. 循环模式
设置寄存器中的CIRC位为1可以开启循环模式。循环模式主要用来处理连续的数据流,例如ADC扫描模式中的连续数据采集。该模式下寄存器中的值递减到0后,会重新装载回初值,缓存也会被循环利用。
3. 存储器-存储器模式
除了实现外设-存储器间的数据传输,DMA模式也可以用来在两个存储器间传输数据。通过设置寄存器中的位即可开启存储器-存储器模式。无论是外设-存储器模式,还是存储器-存储器模式,本质上来说DMA都只是将数据从一个地址搬运到另一个地址。区别之处在于:外设地址通常是固定的,而存储器地址可以人为设置且可递增;外设可以发出DMA请求,而存储器间的数据传输需要通过软件设置寄存器的EN位来触发DMA。通信双方的首地址仍然是在CPARx和CMARx两个寄存器中设置,传输方向也是由CCRx寄存器中的DIR位设定。
存储器间的数据传输不要同时开启循环模式。寄存器递减到0,传输自动停止。
4.4 数据宽度与对齐问题
DMA支持按字节、半字或全字传输数据。如果双方的数据宽度相同,DMA只需将数据依次传输过去即可。但如果源和目标端的数据宽度不同,即PSIZE和MSIZE不相等,就涉及到传输过程中的数据对齐问题。
如果目标端数据宽度比源端大,则目标端接收数据后按右对齐存储,闲置高位补0。例如,源为8位,目标为32位,传输4个数据,传输过程和结果如下:
如果目标端数据宽度比源端小,则源端高位被截掉,仅传输目标端能容纳的低位。例如,源为32位,目标为16位,传输4个数据,传输过程和结果如下:
可见,无论哪种情况,DMA都是按源端的数据宽度读取数据,然后按目标端的数据宽度按右对齐写入数据的。目标端按自己的数据宽度和传输数据个数分配内存,传输过来的数据“多去少补,对号入座”。如果条件允许,尽量双方一致,尤其要避免“大筐倒小筐”。
注意:
对于不支持字节和半字写入的AHB外设,传入的字节或半字会从低位到高位重复填满总线数据寄存器。例如0xAB在会被重复成。这种情况下,如果从内存向寄存器写入数据,内存端MSIZE应该按存储器数据宽度设置,而外设端PSIZE则设为32bit。例如向APB备份寄存器(16位有效,但占32位地址)写入,MSIZE配置位16位,PSIZE配置位32位。
4.5 错误与中断管理
在DMA读/写预设地址的过程中,如果出现错误,硬件会自动关闭出错的通道,清除中的EN位,设置中的TEIF( Error Flag)中断标志位。如果开启了CCRx中的TEIE位,则会产生中断。
DMA传输过程中会设置3个中断标志位:传输过半(HTIF)、传输完成(TCIF)、传输出错(TEIF)。如果设置了中断使能位(TCIE/HTIE/TEIE),则会产生对应的中断。
4.6 DMA请求的通道映射
来自外设的DMA请求共有7类:TIMx[1,2,3,4], ADC1, SPI1, SPI/I2S2, I2Cx[1,2] 以及
[1,2,3]。这些请求被固定的映射到了DMA1的7个通道和DMA2的5个通道。DMA1的映射关系如图3所示。DMA2的映射关系如图4所示。
图3. DMA1的通道映射
图4. DMA2的通道映射
总结:
五、DMA寄存器
DMA的寄存器非常简单,如图2所示。有2个公用寄存器:和。每个通道还有4个专用寄存器://CPARx/CMARx。
5.1 中断状态寄存器
( )用来管理DMA的中断状态。由硬件设置,软件清除(设置IFCR寄存器的对应位)。每个通道的4个中断占连续的4个位。从低到高依次为GIFx(全局中断标志)、TCIFx(传输完成中断标志)、HTIFx(传输过半中断标志)、TEIFx(传输错误中断标志)。每个通道实际只有3个中断,其中GIFx是其余3个标志位相“与”的结果,三者中任何一种中断事件发生,该位都会被置1。
5.2 中断状态清除寄存器
( Flag Clear )用来清除ISR寄存器中的对应位,清除中断状态。ISR是只读的,中断发生时由硬件设置,要清除中断标志位,需要向寄存器的对应位写入1(写0无影响)。另外,向CGIFx写入1可以清除通道x所有的中断标志位。
5.3 通道配置寄存器
( x )用来配置通道x。各功能位作用如下:
5.4 数据个数寄存器
( x of Data )用来存储待传输数据个数。每次传输数值递减1。非循环模式下,递减到0时DMA请求结束;循环模式下,递减凋0后会自动加载初值,继续DMA传输。
5.5 外设地址寄存器
( x )用来设置外设基地址。当PSIZE=01,即数据宽度为16位时,地址中的PA[0]位被忽略,数据自动对齐到半字地址;当PSIZE=10,即数据宽度为32位时,地址中的PA[1:0]位被忽略,数据自动对齐到字地址。
说明:
半字为,即2个字节。字节是最小的存储单位,即每个地址存1个字节。地址-,间隔2个字节,恰为1个半字的空间。同理,-,间隔4个字节,即一个全字的空间,所以32位时忽略地址的末2位。
5.6 内存地址寄存器
( x )用来设置存储器(数组)基地址。和CMARx实际上就是两个地址存储器,用来存储源和目标的地址。时存储的都是存储器中的地址。
六、HAL库DMA相关的函数
HAL库.h中对DMA的通道地址进行了映射,可以通过通道宏名称,访问通道的CCR/CNDTR/CPAR/CMAR寄存器。DMA相关的库函数在.h中声明,在.c中定义。分类概述如下:
1. 初始化与复位函数
DMA初始化函数。设置CCR寄存器中的
PL/MSIZE/PSIZE/MINC/PINC/CIRC/DIR,共7个功能位。
DMA复位函数。关闭DMA,然后清除CCR&CNDTR&CPAR&CMAR,清除hdma句柄中的中断函数,复位State状态机和。
2. 启动与停止函数
启动DMA。函数中完成的工作包括:中断标志位清除、设置CNDTR、CPAR、CMAR。设置这些参数前必须先将CCRx的EN位置0((hdma)),然后调用()函数进行设置,设置完成后重新开启((hdma))。
以中断模式启动DMA。除完成()的工作外,还开启了3个中断使能位。
终止DMA传输。函数中完成的工作包括:关闭中断、关闭DMA通道、中断标志位清除(向IFCR中的CGIFx写入1)。
终止DMA传输并触发相应的中断。
3. 状态查询函数
检查传输状态。通过轮询等待返回传输状态(传输过半/传输完成)标志。
查询DMA状态。返回DMA状态机的值,即hdma->State。
查询DMA错误。返回DMA错误代码,即hdma->。
4. 中断处理函数
中断请求处理函数。根据hdma的中断设置,调用相应的回调函数(传输完成/过半/出错/终止)。回调函数需要用户自己编写,一般是在外设的()函数中被关联到hdma的句柄。例如,()中会把
/back/k函数关联到DMA的句柄hdma。回调函数是weak函数,需要用户自己实现。
5. 宏库函数