前言
DMA(Direct Memory Access)我只知道是直接点对点的搬运数据的一个功能,不用CPU干预,节省CPU资源,但是具体怎么使用忘得差不多了,重新来学一遍。
DMA介绍
DMA通道:DMA有12个通道,DMA1有7个通道,DMA2有5个通道,可以实现并行的传输,互不干扰,但总线只有一条,实际是仲裁器每次选一个通道搬一个数据项,微观上是分时复用总线;
每个通道都支持软件触发和特定的硬件触发。有些板子只有DMA1,比如STM32F103C8T6
存储器映射

DMA框图介绍

首先我们要理解一个概念,外设寄存器本质还是存储器 ,所以我们常说的外设到寄存器(peripheral to memory )其实本质也是存储器到存储器(memory to memory)。所以上面那个框图只是为了区分,才写的是外设寄存器与存储器之间的转运。
我们在使用DMA时,要注意转运方向,需要我们设置,还有数据转运的起始地址和目标地址,转运的数据宽度(字节,半字,字),地址是否自增(地址自增按照数据宽度计算)等
然后注意传输计数器,用来限制DMA的转运次数,比如我需要转运一个长度为5,类型为uint16_t的数组,那么就需要设置传输计数器为5,转运的数据宽度为半字,这样每转运一个数据,传输计数器自动减1,减到0之后DMA停止转运。
自动重装器就是当传输计数器为0时,需不需要再重新设置传输计数器,重新开始转运,一般配合ADC扫描模式+连续转换使用,一组数组数据快速更新,然后立刻转运出去,防止堵塞。
往下看是M2M,谐音M to M,意思是memory to memory,用来设置是软件触发还是硬件触发。
如果是软件触发,那么DMA就会一直转运数据,知道数据转运完成为止,常用于大块数据块的转运,比如图像数组转运到其他地方,或者说一个大数组的数据传输到另外一个大数组里。
如果是硬件触发,那么DMA就会根据STM32外设发来的信号,等到信号之后再开始转运,比如ADC转换或者定时器信号,等到数据处理完毕了,ADC或者定时器再通知DMA过来把数据搬走。
注意⚠️:软件触发和自动重装器不要同时启用,否则DMA会一直运行,停不下来。
最后就是开启DMA,最后再开启。
DMA1触发源
以DMA1为例,DMA1一共有7个通道,每个通道都有软件触发源和对应的硬件触发源,这些信号怎么来的,都是在对应外设的内部自己配置的。
拿定时器举例,我想每次CNT=ARR时,都使用DMA转运数据,那么就需要在TIMx->DIER 开启DMA请求。其他外设也会有对应的DMA请求的使能。然后触发之后,信号就会进入对应的通道。
各种DMA信号触发后,都需要进入仲裁,硬件触发源仲裁原理与中断相似,优先级位数小的,优先级高。软件触发源仲裁原理为优先级位数越高,优先级越高。
如果DMA1通道1有ADC1和TIM2_CH3同时发生DMA请求,这种情况DMA只会选取其中一个进行DMA转运,比如TIM2_CH3 的请求触发了按 ADC1 配置的搬运 → 读 ADC1->DR 写进内存
会出现丢失的可能,最好的方案就尽量不要DMA通道冲突

DMA转运时,端侧数据宽度与目标数据宽度不符
如果遇到起始地址的要转运的数据宽度与目标地址要转运的数据宽度不一样的情况,比如起始地址的要转运的数据宽度为8位,但是目标地址要转运的数据宽度为16位,那么被转运的数据就会在转运到目标地址时,自动在高位补0;如果起始地址的要转运的数据宽度为32位,但是目标地址要转运的数据宽度为16位,那么被转运的数据就会自动舍弃高位的数据。
可以用不同类型的变量之间赋值理解,uint8_t类型的数据赋值给uint16_t的数据,就会在高位补0;uint32_t类型的数据赋值给uint16_t的数据,高16位的数据就会被截断,只留下低16位的数据.
具体的数据手册的参考图:

DMA框架图
Cortex‑M3 内核有 ICode、DCode、系统三条总线,它平时可以直接访问 Flash 和 SRAM,但当外设或者内存之间需要大批量搬运数据的时候,就交给 DMA 来做,不用 CPU 插手。
当挂在 APB1、APB2 上的外设,比如 USART、ADC、TIM 这些,产生 DMA 请求信号,这个请求信号会送到 DMA1 或者 DMA2 的仲裁器,仲裁器会根据通道优先级选出当前要响应的通道,然后在 DMA 总线上发起传输。
DMA 总线是独立的一条 AHB 总线,它可以直接连接 SRAM、Flash 还有各个外设,传输的时候数据走 DMA 总线,不占用 CPU 的总线,数据在存储器和外设之间、存储器和存储器之间直接流转。
传输完成之后 DMA 会给 CPU 发中断通知,CPU 只需要在传输结束后处理后续事情,整个搬运过程里 CPU 可以去跑别的任务。
DMA1 和 DMA2 是两套独立的控制器,各自有仲裁器和通道,DMA2 专门对接以太网 MAC、USB OTG FS 这类高速外设,它们的 DMA 请求也是先送到自身的仲裁器,再抢占 DMA 总线完成数据搬运
总线竞争:DMA高频使用时,会频繁向总线矩阵申请使用权,导致和CPU抢用总线矩阵,使得CPU速度变慢。只有 DMA 和 CPU 同时访问不同的从设备(比如 DMA 读外设、CPU 读 Flash),矩阵可以并行分发,才不会冲突;只要两者同时要访问同一个从设备(比如都读写 SRAM),争抢就一定会出现。

寄存器层理解
我们先搞懂DMA的中断链路:传输事件(完成/半完成/出错) → ISR 标志 → CCR 里的中断使能 → NVIC → ISR 函数。
与定时器不一样,定时器的中断链路有一点点差别:UEV更新时间(上溢、下溢、UG等) →置SR标志位 →检查UIF(或者其他中断标志位)是否开启 →NVIC →ISR函数
配置DMA的顺序严格按照:先关 EN → 填 CPAR/CMAR/CNDTR → 写 CCR 模式位 → 最后开 EN。
DMA中断状态寄存器(DMA_ISR)
这个寄存器不是中断使能寄存器,只是一个状态寄存器,ISR(中断状态寄存器)不要搞混了。
GIFx:global interrupt flag,全局中断标志,用来目前通道判断是否有中断发生,只要该通道对应的TE(传输错误)、TH(传输一半)、TC(传输完成)三个,有一个标志位为1了,就会硬件置1。
TEx、THx、TCx:同理,通道x的传输错误、传输一半、传输完成中断标志位。


DMA中断标志清除寄存器(DMA_IFCR)
DMA_IFCR 是只写寄存器,作用是"清除 ISR 寄存器里的标志"。
哪一位写1,对应ISR寄存器里的那一位就清0。如果是CGIFx写1,那么就是该通道的GIF + TCIF + HTIF + TEIF,四个位全部清零

DMA通道x****配置寄存器(DMA_CCRx))(x = 1...7)
用来设置通道为x的DMA的传输模式、数据宽度、地址是否自增、优先级、三种中断使能、总开关,用于初始化DMA的某个通道。
注意:PSIZE和MSIZE的配置,绝大多数场景直接设成一样,省心。而且 MEM2MEM 模式下手册强制要求 PSIZE = MSIZE。

DMA通道x传输数量寄存器(DMA_CNDTRx)(x = 1...7)
这个就是重装寄存器,存DMA还剩多少次需要转运,每转运一次数据就减1。

DMA_CPARx与DMA_CMARx
其实这2个寄存器存的都是地址。CPAR寄存器,官方说是存外设寄存器的地址,CMAR寄存器,官方说是存内存的地址,然后靠CCR寄存器里的DIR位控制转运方向,但是其实地址反着放,然后DIR置1,和默认的转运结果是一样的,只是不方便其他人理解,所以尽量按照官方的来。
注意!!:CMAR 按 MSIZE 对齐,CPAR 按 PSIZE 对齐。32 位宽度下,起始地址必须是 4 的倍数,否则数据错位甚至触发 TEIF。

