本章参考资料:《STM32F10X-中文参考手册》DMA控制器章节。
学习本章时,配合《STM32F10X-中文参考手册》DMA控制器章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。
1. DMA简介
- DMA(Direct Memory Access)直接存储器存取,是单片机的一个外设,DMA这个外设,可以直接访问STM32内部的存储器的,包括运行内存SRAM、程序存储器Falsh和寄存器等等。DMA都有权限访问它们。
- DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源。不需要占用CPU, 即在传输数据的时候,CPU可以干其他的事情,好像是多线程一样。(这里外设指的是外设的数据寄存器data register,比如ADC的数据寄存器、串口的数据寄存器等,这里存储器指的就是运行内存SRAM和程序存储器Flash,是我们存储变量数组和程序代码的地方)
- 12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道), 这里的通道可以理解为传输数据的一种管道
- 每个通道都支持软件触发和特定的硬件触发。如果DMA进行的是存储器到存储器的数据转运,比如我们想把Flash里的一批数据,转运到SRAM里,那就需要软件触发。使用软件触发之后,DMA就会一股脑地把这批数据,以最快的速度,全部转运完成。如果DMA进行的是外设到存储器的数据转运,就不能一股脑转运了,因为外设的数据有一定时机的,这时候需要用硬件触发,比如转运ADC的数据,那就得ADC每个通道AD转换完成后,硬件触发一次DMA,之后DMA再转运,触发一次,转运一次。另外如果使用某个外设的硬件触发源,就得使用它连接的那个通道,而不能任意选择通道。
- STM32F103C8T6 DMA资源:DMA1(7个通道)
存储器映像地址
在这个表里,无论是flash还是SRAM还是外设寄存器,它们都是存储器的一种。包括外设寄存器实际上也是存储器,
我们前面这里说的是外设到存储器,存储器到存储器。本质上其实都是存储器之间的数据转运,说成外设的存储器只不过是stm32,它特别指定了可以转运外设的存储器而已.
存储器总共分为两大类,ROM分为了三块,第一块是程序存储器Flash,也就是主闪存,它的用途就是存储C语言编译后的程序代码,也就是下载程序的位置,运行程序,一般也是从主闪存里面开始运行的。(这一块存储器STM32给它分配的地址是0x08000000。起始地址也就是第一个字节的地址是0x08000000,然后剩余字节的地址依次增长,每个字节都分配一个独一无二的地址,就像给每个住户编门牌号一样。只有分配了独一无二的门牌号程序,才能精准的访问这个存储器,最终终止地址是多少呢?这取决于它的容量。你之后如果在软件里看到某个数据的地址是0800开头的,那你就可以确定它是属于主闪存的数据)。
接着继续看下面两块系统存储器和选项字节。这两块存储器也是ROM一种,掉电不丢失。实际上,它们的存储介质也是flash,只不过是我们一般说flash指的是主闪存flash,而不指这两块区域。它们的地址都是0x1FFFF000开头的,紧跟着0x20000000开头的就是RAM区。所以可以看出,这两块存储器的位置是在ROM的最后面,它们的用途可以看一下右边的说明。系统存储器的用途是存储boot loader用于串口下载。boot loader的程序是芯片出厂自动写入的,一般也不允许我们修改。
之后选项字节,这个是用于存储一些独立于程序代码的配置参数,它的位置是在room区的最后面。下载程序可以不刷新选项字节的内容,这样选项字节的配置就可以保持不变。选项字节里存的主要是flash的读保护,写保护,还有看门狗等等的配置,这个如果需要的话,可以了解一下。
然后看一下RAM区,首先是运行内存分配的地址是0x20000000。用途是存储运行过程中的临时变量,也就是我们在程序中定义变量,数组,结构体的地方。可以试一下,定义一个变量,再取它的地址显示出来,那这个地址肯定就是0x20000000开头的。类比于电脑的话,运行内存就是内存条。
然后RAM区剩下的还有外设寄存器。它的地址是0x40000000,外设地址是芯片厂家已经定好的,不能修改的。这块区域用途是存储各个外设的配置参数,也就是初始化各个外设,最终所读写的东西。刚才我们说了外设寄存器也是存储器的一种,它的存储介质其实也是存储器的一种,只不过我们一般习惯把运行内存叫SRAM,外设寄存器直接叫寄存器了。
接下来最后是内核外设寄存器,地址是0xE0000000。用途是存储内核各个外设的配置参数,内核外设就是NVIC和systick,因为内核外设和其他外设不是一个厂家设计的。所以他们的地址也是被分开的。内核外设是0xE0000000,其他外设是0x40000000。
那以上这些就是stm32里的存储器和他们被安排的地址。接着我们在这个图里也看一下。
在stm32中,所有的存储器都被安排到了0x00000000到0xFFFFFFFF这个地址范围内。因为CPU是32位的,所以寻址范围就是32位的范围,32位的寻址范围是非常大的,最大可以支持4GB容量的存储器。而我们stm32的存储器都是KB级别的,所以这个4GB的寻址空间,会有大量的地址都是空的。算一下地址的使用率还不到1%。在这个图里有灰色填充的,就是reserve的区域,也就是保留区域,没有使用到。然后这个0地址,实际上也是没有存储器的,它这里写的是别名到flash或者系统存储器,取决于BOOT引脚。因为程序是从零地址开始运行的,所以这里需要把我们想要执行的程序映射到0地址来,如果映射在flash区,就是从flash执行。如果映射在系统存储器区,就是从系统存储器运行boot loader。如果映射到SRAM,就是从SRAM启动。怎么选择由BOOT0和BOOT1两个引脚来决定,这就是零地址里的别名区。
接着剩下的0x8000000开始的flash区用于存储程序代码。Ox1FFFF000开始的系统存储器和选项字节是在room区的最后面,存在什么东西刚才也都介绍过。之后0x20000000开始的是SRAM区。0X 40000000开始的是外设寄存器区,里面可以展开,就是右边所有的外设,具体到每个外设,又有他们自己的起始地址,比如TIM2的地址是0x40000000,TIM3是0x40000400.
然后外设地址里面又可以具体细分到每个寄存器的地址。寄存器里每个字节的地址,最终所有字节的地址就都可以算出来了。
上面这里0xE0000000开始的区域,存放的就是内核里面的外设寄存器了。在这里相信你对stm32里面有哪些存储器?每种存储器都对应在哪个地址区间里?就应该清楚了吧。
2. DMA功能框图
DMA控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需掌握功能框图中的三部分内容即可, 具体见图 DMA框图 :DMA控制器的框图。
左上角这里是cortex-m3内核,里面包含了CPU和内核外设等等,剩下的这所有东西都可以把它看成是存储器。
所以总共就是CPU和存储器两个东西。flash是主闪存SRAM,是运行内存,各个外设都可以看成是寄存器,也是一种SRAM存储器。寄存器是一种特殊的存储器。一方面CPU可以对寄存器进行读写,就像读写运行内存一样。另一方面,寄存器的每一位背后都连接了一根导线,这些导线可以用于控制外设电路的状态。比如自引脚的高低电平,导通和断开开关,切换数据选择器。或者多位结合起来。当做计数器数据进行器等等等。
所以寄存器是连接软件和硬件的桥梁。软件读写寄存器就相当于在控制硬件的执行。回到这里啊,既然外设就是寄存器,寄存器就是存储器,那使用dma进行数据转运就都可以归为一类问题了。就是从某个地址取内容,再放到另一个地址去。看上图,为了高效有条理的访问存储器,这里设计了一个总线矩阵,总线矩阵的左端是主动单元。也就是拥有存储器的访问权,右边这些是被动单元,它们的存储器只能被左边的主动单元读写。
主动单元这里内核有DCode和系统总线可以访问右边的存储器。其中DCode总线是专门访问flash的,系统总线是访问其他东西的。
另外,由于dma要转移数据,所以dma也必须要有访问的主动权。那主动单元除了内核CPU,剩下的就是dma总线了,这里DMA 1有一条dma总线。DMA2也有一条dma中线。下面这还有一条dma中心。这是以太网外设自己私有的dma,这个可以不用管的。在dma 1和dma 2里面,可以看到dma 1有七个通道,dma 2有五个通道。各个通道可以分别设置它们转运数据的原地址和目的地址,这样它们就可以各自独立的工作了。
接着下面这里有个仲裁器。这个是因为虽然多个通道可以独立转运数据,但是最终dma总线只有一条,所以所有的通道都只能分时复用这一条dma总线。如果产生了冲突,那就会有仲裁器根据通道的优先级决定,谁先用谁后用。另外,在总线矩阵这里也会有个仲裁器,如果dma和CPU都要访问同一个目标,那么dma就会暂停CPU的访问,以防止冲突,不过总线仲裁器仍然会保证CPU得到一半的总线带宽,使CPU也能正常的工作,这就是仲裁期的作用。
然后继续看下面,这里是AHB从设备,也就是dma自身的寄存器。因为dma作为一个外设,它自己也会有相应的配置寄存器,这里连接在了总线右边的ahb总线上。所以dma既是总线矩阵的主动单元,可以读写各种存储器,也是ahb总线上的被动单元,CPU通过这一条线路就可以对dma进行配置了。
接着继续看这里是dma请求,请求就是触发的意思,这条线路右边的触发源是各个外设。所以这个dma请求就是dma的硬件触发源,比如adc转换完成,串口接收到数据。需要触发dma转运数据的时候,就会通过这条线路向dma发出硬件触发信号。之后dma就可以执行数据转运的工作了,这就是dma请求的作用。
到这里,有关dma的结构就讲的差不多了。其中包括用于访问各个存储器的dma总线,内部的多个通道可以进行独立的数据转运,仲裁器用于调度各个通道,防止产生冲突。AHB从设备用于配置dma参数,dma请求用于硬件触发dma的数据转运,这就是这个dma的各个部分和作用。
最后再额外说一个问题,就是这里的flash它是ROM只读存储器的一种,如果通过总线直接访问的话。无论是CPU还是DMA都是只读的,只能读取数据而不能写入。如果你dma的目的地址填写在flash的区域,那转运时就会出错,这个注意一下。
当然flash也不是绝对的不可写入。我们可以配置这个flash接口控制器,对flash进行写入,这个流程就比较麻烦了,要先对flash按页进行擦除,再写入数据。不过,这是另一个课题,这里就不再讨论,总之就是CPU或者dma直接访问flash的话是只可以读,而不可以写的,然后SRAM是运行内存,可以任意读写,没有问题。
外设寄存器得看参考手册里面的描述。有的寄存器是只读的,有的寄存器是只写的,不过我们主要用的是数据寄存器,数据寄存器都是可以正常读写的。
DMA基本结构图!
如果想编写代码,实际去控制dma的话,那这个图就是必不可少的了。刚才这个框图只是一个笼统的结构图,对于dma内部的执行细节,它还是没体现出来。
所以我们再来分析一下上图,看看dma具体是怎么工作的,在这个图里这两部分就是数据转运的两大站点了。左边是外设寄存器站点,右边是存储器站点,包括flash和SRAM。
在stm32手册里,所说的存储器一般是特指flash和SRAM,不包含外设寄存器,外设寄存器它一般直接称作外设。所以就是外设到存储器,存储器到存储器,这样来描述。
虽然我们刚才说了,寄存器也是存储器的一种。但是STM32还是使用了外设和存储器来作为区分,这个注意一下描述方法的不同。那在这里可以看到dma的数据转运,可以是从外设到存储器,也是可以从存储器到外设,具体是向左还是向右有一个方向的参数可以进行控制。另外还有一种转运方式就是存储器到存储器。比如flash到SRAM或者SRAM到SRAM这两种方式。由于flash是只读的,所以dma不可以进行SRAM到flash,或者flash到flash的转移操作,flash只能作为源地址使用。
然后我们继续看这两边的参数。既然要进行数据转运,那肯定就要指定从哪里转到哪里,具体怎么转呢?所以外设和存储器两个站点就都有三个参数,第一个是起始地址,有外设端的起始地址和存储器端的起始地址。这两个参数决定了数据是从哪里来到哪里去的。之后第二个参数是数据宽度。这个参数的作用是指定一次转运要按多大的数据宽度来进行,它可以选择字节Byte、半字HalfWord和字word。字节就是8位,也就是一次转运一个unit8_t大小的数据。半字是16位,就是一次转运一个uint16_t。字是32位,就是一次转运uint32_t这么大数据,比如转运adc的数据,adc的结果是uint16_t这么大,所以这个参数就要选择半字,一次转运一个unit16_t,这样才对。
然后第三个参数是地址是否自增?这个参数的作用是指定一次转运完成后,下一次转运是不是要把地址移动到下一个位置去。这就相当于是指针P++。比如adc扫描模式,用dma进行数据转运,外设地址是ADC_DR寄存器。寄存器这边显然地址是不用指针的,如果指针自增那下一次转运就跑到别的寄存器那里去了。存储器这边地址就需要指针,每转运一个数据后就往后挪个坑,要不然下次再转就把上次的覆盖掉了。这就是地址是否自增的作用,就是指定是不是要转运一次挪个坑这个意思。这就是外设站点和存储器站点各自的三个参数了。
如果要进行存储器到存储器的数据转运,那我们就需要把其中一个存储器的地址放在外设的这个站点。这样就能进行存储器到存储器的转运了,只要你在外设起始地址里写flash或者SRAM的地址。那它就会去flash或SRAM找数据。这个站点虽然叫外设存储器,但是它就只是个名字而已,并不是说这个地址只能写寄存器的地址。如果写flash的地址,那它就会去flash里找,写SRAM,它就会去SRAM里找这个没有限制。甚至你可以在外设站点写存储器的地址,存储器站点写外设的地址,然后方向参数给反过来,这样也是可以的。只是公司给它起来,这样的名字而已,所以我这里就按照它的名字来做的PPT,你也可以把它叫做站点A、站点B。从a到b或者从b到a转移数据,不必拘泥于它写的外设站点,存储器站点儿这个名字的。
接着往下面看,这里有个东西叫做传输计数器。这个东西就是用来指定总共需要转运几次的。这个传输计数器是一个自减计数器,比如你给它写一个5,那dma就只能进行五次数据转运。转运过程中,每转运一次计数器的数就会减一,当传输计数器减到零之后,DMA就不会再进行数据转运了。另外它减到零之后,之前自增的地址也会恢复到起始地址的位置,以方便之后dma开始新一轮的转换。
在传输计数器的右边,有一个自动重装器,自动重装器的作用就是传输计数器减到零之后是否要自动恢复到最初的值。比如最初传输计数器给5,如果不使用自动重装器,那转运5次后dma就结束了。如果使用自动重装器,那转运5次,计数器减到零后就会立即重装到初始值5,这个就是自动重装器。
自动重装器决定了转运的模式,如果不重装,就是正常的单次模式,如果重装就是循环模式,软件上就是设定单次模式还是循环模式决定是否自动重装。比如如果你想转运一个数组,那一般就是单次模式,转运一轮就结束了,如果是adc扫描模式加连续转换。那为了配合adc, dma也需要使用循环模式。所以这个循环模式和adc的连续模式差不多,都是指定一轮工作完成后,是不是立即开始下一轮工作。
然后继续往下看,这一块就是dma的触发控制了。触发就是决定dma需要在什么时机进行转运的。触发源由硬件触发和软件触发,具体选择哪个由M2M这个参数决定。M2M就是memory to memory,存储器到存储器的意思。当我们给M2M为1时,dma就会选择软件触发。这个软件触发并不是调用某个函数一次,触发一次。软件触发的执行逻辑是以最快的速度连续不断的触发dma,争取早日把传输计数器清零完成这一轮的转换。这里的软件触发和我们之前外部中断和adc的软件触发可能不太一样,你可以把它理解成连续触发。软件触发和循环模式不能同时用,因为软件触发就是想把传输计数器清零,循环模式是清零后自动重装,如果同时用的话,那就停不下来了,这就是软件触发。
软件触发一般适用于存储器到存储器的转运,因为存储器到存储器的转运是软件启动不需要时机,并且想尽快完成的任务,所以上面这里M2M位给1就是软件触发,就是应用在存储器到存储器转运的情况。
当M2M位给0,那就是使用硬件触发了,硬件触发源可以选择adc、串口、定时器等等。使用硬件触发的转运,一般都是与外设有关的转运,这些转运需要一定的时机,比如adc转换完成、串口收到数据、定时时间到等等。所以需要使用硬件触发,在硬件达到这些时机时,传一个信号过来,来触发dma进行转运,这就是硬件触发。
最后就是开关控制了,也就是DMA_cmd函数,当给dma使能后,dma就准备就绪,可以进行转运了。
dma进行转运有几个条件:
第一就是开关控制DMA_cmd必须使能。
第二就是传输计数器必须大于零。
第三就是触发源,必须有触发信号。触发一次转运一次传输计数器自减一次,当传输计数器等于零,且没有自动重装时,这时无论是否触发dma,都不会再进行转运了,此时就需要DMA_cmd给disable关闭dma,再为传输计数器写一个大于0的数,再DMA_cmd给ENABLE开启dma, dma才能继续工作。
注意一下,写传输计数器时必须要先关闭dma再进行,不能在dma开启时写传输计数器,这是手册里的规定。到这里dma的基本结构,就介绍完了。
DMA的细节问题
DMA请求
接下来我们再看几个细节的问题,第一个是dma请求。这张图表示的就是我们上面这里的这部分结构dma触发的部分。
我们来看一下,这张图是dma 1的请求映像,右边是dma的七个通道,每个通道都有一个数据选择器,可以选择硬件触发或软件触发。这里画的图啊,我觉得可能不太好理解。你看他把EN位画在了数据选择器的侧边,一般数据选择器的侧边是输入选择控制位。难道这里的意思是EN给1选择硬件触发,EN给0选择软件触发吗?那显然不对呀。而且它左边这里写的是软件触发(MEM2MEM),难道MEM位是软件触发吗?这个也不太好理解哈。所以这个图我重新给它布了个局,
就是上面我这里画的这样,M2M位是数据选择器的控制位,用于选择是硬件触发还是软件触发。EN位是开关控制,EN=0时不工作,EN=1时工作。这样就好理解一些了。那这里它这样画的意思,应该是EN位并不是数据选择器的控制位,而是决定这个数据选择器要不要工作,EN=0数据选择器不工作,EN=1数据选择器工作。然后软件触发后面跟个M2M位的意思应该是,当M2M=1时选择软件触发。这样理解的话,就跟我这个图里是一个意思了。
然后继续看左边的硬件触发源。这里是外设请求信号,可以看到每个通道的硬件触发源都是不同的。如果你需要用ADC1来触发的话,那就必须选择通道1,如果需要定时器2的更新事件来触发的话,那就必须选择通道2,剩下的也是同理。因为每个通道的硬件触发源都不同,所以如果你想使用某个硬件触发源的话,就必须使用它所在的通道,这就是硬件触发的注意事项,而如果使用软件触发的话,那通道就可以任意选择了。因为每个通道的软件触发都是一样的,所以在PPT的前面写的是每个通道都支持软件触发和特定的硬件触发。
这就是特定的意思,选择硬件触发是要看通道的。这里通道1的硬件触发是ADC1、定时器2的通道3和定时器4的通道1。到底是选择哪个触发源呢?看对应的外设是否开启了dma输出来决定的,比如你要使用ADC1,那会有个库函数叫ADC_DMACmd。必须使用这个库函数开启ADC1的这一路输出,它才有效。如果想选择定时器2的通道3,那也会有个TIM_DMACmd函数用来进行dma输出控制。所以这三个触发源具体使用哪个,取决于你把哪个外设的dma输出开启了,如果三个都开启了,那这边是一个或门,理论上三个硬件都可以进行触发,不过一般情况下我们都是开启其中一个。
之后这7个触发源进入到仲裁器进行优先级判断,最终产生内部的DMA请求。这个优先级的判断类似于中断的优先级,默认优先级是通道号越小,优先级越高,当然也可以在程序中配置优先级,这个其实影响并不是很大,大家了解一下就行了。
之后我们再看一个细节问题。就是数据宽度与对齐。这里有个表,这个表是用来说明什么问题的呢?我们可以看一下前面这里。
这里数据转运的两个站点都有一个数据宽度的参数,如果数据宽度都一样,那就是正常的一个个转运。如果数据宽度不一样,那会怎么处理呢?这个表就是来说明这个问题的。这里第一列是原端宽度,第二列是目标宽度,第三列是传输数目。
当远端和目标都是8位时。转运第一步,在源端的0位置读数据B0。在目标的0位置,写数据B0。就是把这个B0从左边挪到右边。之后的步骤就是把B1从左边挪到右边,接着B2、B3,这是源端和目标都是8位的情况。
接着继续源端是8位,目标是16位,那它的操作就是在原端读B0。在目标写00B0。之后读B1写00B1等等。这个意思就是如果目标的数据宽度比源端的数据宽度大,那就在目标数据前面多出来的空位补零。之后8位转运到32位也是一样的处理哈,前面空出来的都补零。
然后下面当目标数据宽度比源端数据宽度小时,比如由16位转到8位去。现象就是读b1b0只写入b0,读b3b2只写入b2,也就是把多出来的高位舍弃掉。之后的各种情况啊,也都是类似的操作。总之这个表的意思就是如果你把小的数据转到大的里面去。高位就会补零,如果把大的数据转到小的里面去,高位就会舍弃掉。
那最后我们再来看两个例子,看看在这些实际的任务下dma是如何工作的。第一个例子就是数据转运+DMA,第二个例子是扫描模式+DMA。这两个例子和我们演示的两个程序是对应的。
看一下第一个例子,这个例子的任务是将SRAM里的数组DataA转运到另一个数组dataB中。
这个基本结构里的各个参数该如何配置?首先是外设站点和存储器站点的起始地址数据,宽度地址是否自增,这三个参数。那在这个任务里,外设地址显然应该填数组的首地址。存储器地址给dataB数组的时候地址。然后数据宽度两个数组的类型都是uint8_t,所以数据宽度都是按8位的字节传输。之后地址是否自增,可以看到我们想要的效果是DataA[0]转到DataB[0],DataA[1]转到DataB[1]等等。两个数组的位置一一对应。所以转运完DataA[0]和DataB[0]之后,两个站点的地址都应该自增,都移动到下一个数据的位置。继续转运DataA[1]和DataB[1]这样来进行。
如果你左边不自增,右边自增,效果就是这样的。转运完成后DataA的所有数据都会等于DataA[0]。
如果左边自增,右边不自增,那效果就是这样的。转运完成后DataB[0]等于DataA的最后一个数。DataB其他的数不变。
如果左右都不自增,那就一直是DataA[0]转到DataB[0]。其他的数据不变。这就是地址是否自真的效果。
之后这里的方向参数,那显然就是外设站点转运到存储器站点了。如果你想把DataB的数据转运到DataA,那可以把方向参数换过来,这样就是反向转运了。
然后是传输计数器及自动重装器,在这里显然要转运7次,所以传输计数器给7,自动重装,暂时不需要。
之后触发选择部分。这里我们要使用软件触发,因为这是存储器到存储器的数据转运,是不需要等待硬件时机的,尽快转运完成就行了。
那最后调用DMA_cmd给DMA使能,这样数据就会从DataA转运到DataB了。转运7次之后,传输计数器自检到0 ,dma停止转运完成。这里的数据转运是一种复制转运,转运完成后DataA的数据并不会消失,这个过程相当于是把DataA的数据复制到了DataB的位置。这就是第一个任务,存储器到存储器的数据转运。
接着看第二个任务adc扫描模式加dma。
左边是ADC扫描模式的执行流程,在这里有7个通道。触发一次后,7个通道依次进行AD转换,
然后转换结果都放到ADC_DR数据寄存器里面。那我们要做的就是在每个单独的通道转换完成后进行一次数据转运,并且目的地址进行自增。这样数据就不会被覆盖了。
所以在这里的配置就是外设地址写入ADC_DR这个寄存器的地址。存储器的地址可以在SRAM中定义一个数组ADValue。然后把ADValue的地址当做存储器的地址。
之后数据宽度因为ADC_DR和SRAM数组,我们要的都是unit16_t的数据。所以数据宽度都是16位的半字传输。
之后是地址是否自增,那从这个图里。显然是外设地址不自增,存储器地址自增。
传输方向是外设站点到存储器站点。传输计数器,这里通道有七个,所以计数七次,计数器是否自动重装,这里可以看ADC的配置。ADC如果是单次扫描,那dma的传输计数器可以不自动重装,转换一轮就停止。如果ADC是连续扫描,那dma就可以使用自动重装。在ADC启动下一轮转换的时候,dma也启动下一轮的转运。ADC和dma同步工作。
最后是触发选择这里,ADC_DR的值是在ADC单个通道转换完成后才会有效。所以dma转运的时机需要和ADC单个通道转换完成同步,所以dma的触发要选择ADC的硬件触发。
最后,硬件触发,这里要说明一下。我们上一节说了扫描模式,在每个单独的通道转换完成后,没有任何标志位,也不会触发中断。所以我们程序不太好判断某一个通道转换完成的时机是什么时候。但是根据我的研究,虽然单个通道转换完成后不产生任何标志位和中断,但是它应该会产生DMA请求,去触发dma转运。这部分内容手册里并没有详细描述,根据我实际实验,单个通道的dma请求肯定是有的,要不然这个实验就做不成了。
这些就是adc扫描模式和dma配合使用的流程。一般来说dma最常见的用途就是配合adc的扫描模式。因为adc扫描模式有个数据覆盖的特征,或者可以说这个数据覆盖的问题是adc固有的缺陷。这个缺陷使得adc和dma成了最常见的伙伴。adc对dma的需求是非常强烈的。像其他的一些外设,使用dma可以提高效率,是锦上添花的操作,但是不使用也是可以的,顶多是损失一些性能。但是这个adc的扫描模式,如果不使用dma功能,都会受到很大的限制。所以adc和dma的结合最为常见。
2.1. DMA请求
如果外设要想通过DMA来传输数据,必须先给DMA控制器发送DMA请求,DMA收到请求信号之后,DMA控制器会给外设一个应答信号, 当外设应答后且DMA控制器收到应答信号之后,就会启动DMA的传输,直到传输完毕。
DMA有DMA1和DMA2两个控制器,DMA1有7个通道,DMA2有5个通道。
不同的DMA控制器的通道对应着不同的外设请求, 这决定了我们在软件编程上该怎么设置,具体见DMA请求映像表。
其中ADC3、SDIO和TIM8的DMA请求只在大容量产品中存在,这个在具体项目时要注意。
2.2. 通道
DMA具有12个独立可编程的通道,其中DMA1有7个通道,DMA2有5个通道,每个通道对应不同的外设的DMA请求。 虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
2.3. 仲裁器
当发生多个DMA通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。
仲裁器管理DMA通道请求分为两个阶段。
第一阶段属于软件阶段,可以在DMA_CCRx寄存器中设置,有4个等级:非常高、高、中和低四个优先级。
第二阶段属于硬件阶段, 如果两个或以上的DMA通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道0高于通道1。
在大容量产品和互联型产品中, DMA1控制器拥有高于DMA2控制器的优先级。
3. DMA数据配置
使用DMA,最核心就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单位是什么,要传多少数据,是一次传输还是循环传输等等。
3.1. 从哪里来到哪里去
我们知道DMA传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。 具体的方向DMA_CCR位[4] DIR配置:0表示从外设到存储器,1表示从存储器到外设。 这里面涉及到的外设地址由DMA_CPAR配置,存储器地址由DMA_CMAR配置。
外设到存储器
当我们使用从外设到存储器传输时,以ADC采集为例,DMA外设寄存器的地址对应的就是ADC数据寄存器的地址, DMA存储器的地址就是我们自定义的变量(用来接收存储AD采集的数据)的地址。方向我们设置外设为源地址。
存储器到外设
当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA外设寄存器的地址对应的就是串口数据寄存器的地址, DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。将内存中的数据发到串口助手工具,DMA只需传送数据到开发板的串口发送模块,开发板串口再发给串口助手工具,硬件自动实现。
存储器到存储器(这种也属于外设到存储器)
当我们使用从存储器到存储器传输时,以内部FLASH向内部SRAM复制数据为例。 DMA外设寄存器的地址对应的就是内部FLASH(我们这里把内部FALSH当作一个外设来看)的地址, DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部FLASH的数据)的地址。 方向我们设置外设(即内部FLASH)为源地址。跟上面两个不一样的是,这里需要把DMA_CCR位[14] MEM2MEM存储器到存储器模式配置为1,启动M2M模式。
3.2. 要传多少,单位是什么
当我们配置好数据要从哪里来到哪里去之后,我们还需要知道我们要传输的数据是多少,数据的单位是什么。
以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由DMA_CNDTR配置, 这是一个32位的寄存器,一次最多只能传输65535个数据。
要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是8位的, 所以我们定义的要发送的数据也必须是8位。外设的数据宽度由DMA_CCR的PSIZE[1:0]配置, 可以是8/16/32位,存储器的数据宽度由DMA_CCR的MSIZE[1:0]配置,可以是8/16/32位。
在DMA控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。 外设的地址指针由DMA_CCRx的PINC配置,存储器的地址指针由MINC配置。
以串口向电脑发送数据为例,要发送的数据很多, 每发送完一个,那么存储器的地址指针就应该加1,而串口数据寄存器只有一个, 那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。
3.3. 什么时候传输完成
数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。每个DMA通道在DMA传输过半、 传输完成和传输错误时都会有相应的标志位。如果使能了该类型的中断后,则会产生中断。有关各个标志位的详细描述请参考DMA中断状态寄存器DMA_ISR的详细描述。
传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止,要想再传输的话, 必须关断DMA使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输, 不断的重复。具体的由DMA_CCR寄存器的CIRC循环模式位控制。