一、DMA简介
- DMA(Direct Memory Access)直接存储器存取 。
DMA是一个数据转运小助手,主要用来协助CPU完成数据转运的工作;DMA这个外设可以直接访问STM32内部的存储器(运行内存SRAM(存储变量数组),程序存储器FLash(存储程序代码)和寄存器等等)。
- DMA可以提供外设(外设寄存器,一般指外设的数据寄存器DR,Data Register)和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
- 12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
- 每个通道都支持软件触发(存储器和存储器之间)和特定的硬件触发(外设和存储器之间,每个DMA通道的硬件触发源是不一样的)
- STM32F103C8T6 DMA资源:DMA1(7个通道)
二、存储器映像
STM32中所有存储器的类型以及存储器的地址
ROM:只读存储器,是一种非易失性、掉电不丢失的存储器。
RAM:随机存储器,是一种易失性、掉电丢失的存储器。
ROM分为三块:
- 程序存储器Flash,也就是主闪存,下载程序的位置,一般也从主闪存里运行程序。
- 系统存储器:用于串口下载。
- 选项字节:下载程序可以不刷新选项字节的内容,这样选项字节的配置就可以保持不变,存的主要是Flash的读保护和写保护,还有看门狗等等的配置。
RAM:
运行内存SRAM:我们在程序中定义变量、数组、结构体的地方。
外设寄存器:初始化各种外设后最终读写的东西。
内核外设寄存器:NVIC和SysTick。
三、DMA框图
左上角是Cortex-M3内核:包含了CPU和内核外设等。
剩下的所有东西都可以当作是存储器。
Flash是主闪存,SRAM是运行内存,各个外设可以看成是寄存器,也是一种SRAM存储器。
寄存器是一种特殊的存储器,一方面,CPU可以对寄存器进行读写,就像读写内存一样,另一方面,寄存器的每一位背后,都连接了一根导线,这些导线可以用于控制外设电路的状态,比如置引脚的高低电平,导通和断开开关、切换数据选择器或者多位结合起来,当作计数器、数据寄存器,等等。寄存器是连接软件和硬件的桥梁,软件读写寄存器,就相当于在控制硬件的执行。
总线矩阵(高效有条理的访问存储器)左边是主动单元,拥有存储器的访问权。右边是被动单元,它们的存储器只能被左边的主动单元读写。
主动单元:内核有DCode和系统总线,可以访问右边的存储器,其中DCode总线是专门访问Flash的,系统总线是访问其他东西的,另外由于DMA要转运数据,所以DMA也必须要有访问的主动权,主动单元除了内核的CPU剩下的就是DMA总线,DMA1(7个通道)和DMA2(5个通道)都对应有一条总线,各个通道可以分别设置它们转运数据的源地址和目的地址,这样它们就能独立工作了。
仲裁器:虽然多通道可以独立转运数据,但是DMA只有一条总线,所以所有的通道都只能分时复用这一条DMA总线,如果发生冲突会由仲裁器根据通道的优先级来决定谁先用,谁后用,在总线矩阵也会有一个仲裁器,如果DMA和CPU都要访问同一个目标 ,那么DMA就会暂停CPU的访问,避免冲突,不过总线仲裁器仍然会保证CPU得到一半的总线带宽,使CPU也能正常工作。
DMA请求:请求就是触发的意思,这条线路右边的触发源是各个外设,所以DMA请求就是DMA的硬件触发源(比如ADC转换完成、串口收到数据),需要触发DMA转运数据的时候1,就会通过这条线路,向DMA发出硬件触发信号,之后DMA就可以执行数据转运的工作了。
注意:用CPU/DMA直接访问Flash,只读不写,访问SRAM,可读可写。外设寄存器,有的只读,有的只写,有的可读写。
四、DMA基本结构
外设和存储器的地址:决定了数据从哪里来到哪里去。
数据宽度:指定一次转运要按多大的数据宽度来进行。(可选字节Byte,半字HalfWord和字Word,字节就是8位,也就是一次转运一个uint8_t大的数据,半字是16位,也就是一次转运uint16_t大的数据,字是32位,也就是一次转运uint32_t大的数据)
地址是否自增:指定一次转运完成后,下一次转运是否要把地址移动到下一个位置去,相当于指针p++的意思。
传输计数器(自减计数器):用来指定转运次数。没转运一次,计数器的数减1,当传输计数器转到0时,DMA就不会再进行数据转运了。另外,减到0后,之前自增的地址,也会恢复到起始地址的位置,以方便DMA开始新一轮的转运。
自动重装器:传输计数器减到0之后,是否自动恢复到最初的值,比如最初传输计数器给5,如果不使用自动重装器,那么转运5次后,DMA就结束了,若使用,转运5次后计数器减到0,就会立即重装到初始值5,决定了转运的模式,如果不重装就是单次模式,如果重装就是循环模式。
触发:决定DMA需要在什么时机进行转运。触发源有硬件和软件,具体选择哪个,由M2M(Memory to Memory,从存储器到存储器)决定。
当给M2M位1时,DMA选择软件触发(以最快的速度,连续不断地触发DMA),争取早日把计数器清零,完成这一轮转换。软件触发和重装器不能同时使用,如果同时用,DMA就停不下来了,软件触发一般用于从存储器到存储器的转运,是软件启动,不需要时机,并且想要尽快完成任务。
当M2M位给0,那就是使用硬件触发了,硬件触发可以选择ADC、串口、定时器等等。一般与外设有关的转运使用硬件触发,这些转运需要一定的时机(比如ADC转化完成,串口收到数据,定时时间到等等),当硬件达到这些时机时,传一个信号过来,来触发DMA进行转运。
DMA转运的条件:
1.开关控制,DMA_Cmd必须使能。
2.传输计数器必须大于0.
3.触发源必须有触发信号,触发一次转运一次,传输计数器自减一次。当传输计数器为0,没有重装器时,无论是否触发,DMA都不会进行转运,此时就需要DMA_Cmd给DISABLE,关闭DMA,再为传输计数器写一个大于0的数,再DMA_Cmd,给ENABLE,开始DMA,DMA才能继续工作。
注意:写传输计数器时,必须要先关闭DMA,再进行,不能在DMA开启时写传输计数器。
五、DMA请求
每个通道都有一个数据选择器:可以选择硬件触发/软件触发。
M2M位是数据选择器的控制位(用于选择硬件触发/软件触发)
EN是开关控制(EN=0不工作,EN=1工作)
每个硬件触发源都不同,所以如果你想使用某个硬件触发源,就必须使用它所在的通道。
通道一有三个触发源,那到底要选择哪个触发源呢:由对应的外设是否开启了DMA输出来决定。
最终七个通道进入仲裁器进行优先级(优先级的通道号越小,优先级越高,也可以自行配置优先级)判断,最终产生内部的DMA1请求。
六、数据宽度与对齐
如果数据宽度一样,那就是正常的一个个转运。
数据宽度不一样:如果目标数据宽度比源端数据宽度大,那就再目标数据前多出来的空位补0。
如果目标数据宽度比源端数据宽度小,那目标数据把多出来的高位舍弃掉!
(与uint8_t,uint16_t和uint32_t变量之间相互赋值一样,不够就补0,超了就舍弃高位)
七、数据转运+DMA(例子)
任务:将SRAM里的数组DataA转运到另一个数组DataB中
外设地址:DataA数组的首地址,存储器地址:DataB数组的首地址。
数据宽度:两个数组的类型都是uint8_t,按8位的字节传输。
地址自增:两个站点的地址都应该自增,都移动到下一个数据的位置(DataA[0]对应DataB[0],DataA[1]对应DataB[1],以此类推)
传输计数器:给7,要转运7次。
选择软件触发:从存储器到存储器的数据转运。
用DMA_Cmd给DMA使能
八、ADC扫描模式+DMA(例子)
任务:左边有七个通道,触发一次后依次进行AD转换,然后转换结果都放到ADC_DR数据寄存器里,在每一个单独的通道转换完成后,进行一个DMA数据转运,并且目的地址进行自增,这样数据就不会被覆盖了。
外设地址:写入ADC_DR的地址
存储器的地址:在SRAM中定义一个数组ADValue,把其地址当作存储器的地址。
数据宽度:两个数组的类型都是uint16_t,按16位的半字传输。
地址自增:外设地址不自增,存储器地址自增。
传输方向:外设站点到存储器站点。
传输计数器:给17,要转运7次。
是否重装:ADC如果是单次扫描,可不用;ADC是连续扫描,用自动重装。
触发选择:这里ADC_DR的值是在ADC单个通道转换完成后才会有效,所以DMA转运的时机,需要和ADC单个通道转换完成同步,所以DMA的触发要选择ADC的硬件触发。