Bootstrap

STM32之DMA

一、DMA概述

DMA:直接寄存器访问

Direction:直接        Memory:存储器        Access:访问

就是一个外设用于搬运数据,就是一个搬运工。

在串口发送数据的时候:这种效率并不高

如何想要发送大量的数据的时候可以利用DMA

1、DMA工作流程

没有DMA参与的uart的收发过程

2、有DMA参与的uart的收发过程

DMA就可以一直搬运数据,核心首先要控制以下DMA开始搬运

优点:可以解放CPU,让他可以去做别的事情

2、STM32的DMA控制器概述

直接存储器访问 (DMA) 用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样节省的 CPU 资源可供其它操作使用。DMA 控制器基于复杂的总线矩阵架构,将功能强大的双 AHB 主总线架构与独立的 FIFO 结合在一起,优化了系统带宽。两个 DMA 控制器总共有 16 个数据流每个控制器 8 ),每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每个通道都有一个仲裁器,用于处理 DMA请求间的优先级。

DMA的搬运方向:外设与存储器之间以及存储器与存储器

一共有两个DMA控制器,每个DMA控制器都有8个数据流,每个数据流有8个通道

128个通道(请求)

不能同时响应就会有先帮谁搬的问题,这个问题由仲裁器解决(它的优先级级别比较少)

如果仲裁器解决不了,就由数据流的编号解决(数字越小优先级越高)

3、STM32的DMA控制器特征

DMA 主要特性是:
双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问
仅支持 32 位访问的 AHB 从编程接口
每个 DMA 控制器有 8 个数据流,每个数据流有多达 8 个通道(或称请求
每个数据流有单独的四级 32 位先进先出存储器缓冲区 (FIFO),可用于 FIFO 模式或直接模式:
— FIFO 模式:可通过软件将阈值级别选取为 FIFO 大小的 1/4、 1/2 或 3/4
— 直接模式
    每个 DMA 请求会立即启动对存储器的传输。当在直接模式(禁止 FIFO)下将 DMA请求配置为以存储器到外设模式传输数据时, DMA 仅会将一个数据从存储器预加载到内部 FIFO,从而确保一旦外设触发 DMA 请求时则立即传输数据。

通过硬件可以将每个数据流配置为:
支持外设到存储器存储器到外设存储器到存储器(只能是DMA2上面的数据流来控制传输的常规通道(DMA搬运数据的三大方向)

 也支持在存储器方双缓冲的双缓冲区通道
8 个数据流中的每一个都连接到专用硬件 DMA 通道(请求)(厂家在出厂时已经规定好当前那个外设可以和DMA相连来搬运数据)
DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1 )
每个数据流也支持通过软件触发存储器到存储器的传输(仅限 DMA2 控制器
可供每个数据流选择的通道请求多达 8 个。此选择可由软件配置,允许几个外设启动 DMA
请求
要传输的数据项的数目(要搬多少次)可以由 DMA 控制器或外设管理:
DMA 流控制器:要传输的数据项的数目是 1 到 65535,可用软件编程
外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过
硬件发出传输结束的信号(SDIO)
独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时, DMA 自动
封装 /解封必要的传输数据来优化带宽。这个特性仅在 FIFO 模式下可用

1个字节 = 8个位

半字 = 2个字节 = 16个位

字 = 4个字节 = 32个位


对源和目标的增量或非增量寻址(一般就是存储器的地址需要递增,外设的地址不需要递增)
支持 4 个、 8 个和 16 个节拍的增量突发传输。突发增量的大小可由软件配置,通常等于外设 FIFO 大小的一半
每个数据流都支持循环缓冲区管理
5 个事件标志( DMA 半传输、 DMA 传输完成、 DMA 传输错误、 DMA FIFO 错误、直接
模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求

4、STM32的DMA控制器框架

二、DMA功能

1、通道选择

通道的配置一个时刻只能有一个请求到某一个数据流,但是在某一个时刻可能会有多个数据流请求(数据流的优先级问题)每个数据流都与一个 DMA 请求相关联,此 DMA 请求可以从 8 个可能的通道请求中选出。
此选择由 DMA_SxCR 寄存器中的 CHSEL[2:0] 位控制。

DMA1:

DMA2:

2、仲裁器

仲裁器为两个 AHB 主端口(存储器和外设端口)提供基于请求优先级的 8 个 DMA 数据流请求管理,并启动外设 /存储器访问序列。
优先级管理分为两个阶段:
软件:每个数据流优先级都可以在 DMA_SxCR 寄存器中配置。分为四个级别:

非常高优先级

高优先级
中优先级
低优先级
硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流。例如,数据流 2 的优先级高于数据流 4。

3、FIFO模式与直接模式

FIFO模式

FIFO 用于在源数据传输到目标之前临时存储这些数据。每个数据流都有一个独立的 4 FIFO,阈值级别可由软件配置为 1/4 1/23/4 。为了使能 FIFO 阈值级别,必须通过将 DMA_SxFCR 寄存器中的 DMDIS 位置 1 来禁止直接模式。FIFO 的结构随源与目标数据宽度而不同,

FIFO缓冲区的大小为4字 = 16字节

默认情况下, FIFO 以直接模式操作(将 DMA_SxFCR 中的 DMDIS 位置 1 ),不使用 FIFO阈值级别。如果在每次 DMA 请求之后,系统需要至/自存储器的立即和单独传输,这种模式非常有用。当在直接模式(禁止 FIFO)下将 DMA 配置为以存储器到外设模式传输数据时DMA 会将一 个数据从存储器预加载到内部 FIFO,从而确保一旦外设触发 DMA 请求时则立即传输数据。为了避免 FIFO 饱和,建议使用高优先级配置相应的数据流。
该模式仅限以下方式的传输:
源和目标传输宽度相等,并均由 DMA_SxCR 中的 PSIZE[1:0] 位定义(MSIZE[1:0] 位的
状态是“无关”
不可能进行突发传输(DMA_SxCR 中的 PBURST[1:0] 和 MBURST[1:0] 位的状态是“
关”

4、突发增量(仅在FIFO模式下才能使用)

选择 FIFO 阈值(DMA_SxFCR 寄存器的位 FTH[1:0])和存储器突发大小(DMA_SxCR 寄存
器的 MBURST[1:0] 位)时需要小心: FIFO 阈值指向的内容必须与整数个存储器突发传输完全 匹配。如果不是这样,当使能数据流时将生成一个 FIFO 错误( DMA_HISR 或 DMA_LISR寄存器的标志 FEIFx),然后将自动禁止数据流。

5、源与目标、传输方向

(1)、外设到存储器模式

使能这种模式(将 DMA_SxCR 寄存器中的位 EN 置 1 ) 时,每次产生外设请求,数据流都会启动数据源到 FIFO 的传输。达到 FIFO 的阈值级别时, FIFO 的内容移出并存储到目标中。如果 DMA_SxNDTR 寄存器达到零、外设请求传输终止(在使用外设流控制器的情况下)或DMA_SxCR 寄存器中的 EN 位由软件清零,传输即会停止。

在直接模式下(当 DMA_SxFCR 寄存器中的 DMDIS 值为“0”时),不使用 FIFO 的阈值级别控制:每完成一次从外设到 FIFO 的数据传输后,相应的数据立即就会移出并存储到目标中。只有赢得了数据流的仲裁后,相应数据流才有权访问 AHB 源或目标端口。系统使用在DMA_SxCR 寄存器 PL[1:0]。

(2)、存储器到外设模式

使能这种模式(将 DMA_SxCR 寄存器中的 EN 位置 1 )时,数据流会立即启动传输,从源完全填充 FIFO。每次发生外设请求,FIFO 的内容都会移出并存储到目标中。当 FIFO 的级别小于或等于预定义的阈值级别时,将使用存储器中的数据完全重载 FIFO。

如果 DMA_SxNDTR 寄存器达到零、外设请求传输终止(在使用外设流控制器的情况下)或
DMA_SxCR 寄存器中的 EN 位由软件清零,传输即会停止。
在直接模式下(当 DMA_SxFCR 寄存器中的 DMDIS 值为“0”时),不使用 FIFO 的阈值级别。一旦使能了数据流, DMA 便会预装载第一个数据,将其传输到内部 FIFO。一旦外设请求数据传输, DMA 便会将预装载的值传输到配置的目标。然后,它会使用要传输的下一个数据再次重载内部空 FIFO。预装载的数据大小为 DMA_SxCR 寄存器中 PSIZE 位字段的值。只有赢得了数据流的仲裁后,相应数据流才有权访问 AHB 源或目标端口。系统使用在DMA_SxCR 寄存器。

(3)、存储器到存储器模式

        DMA 通道在没有外设请求触发的情况下同样可以工作。此为图 30 中介绍的存储器到存储器
模式。通过将 DMA_SxCR 寄存器中的使能位 (EN) 置 1 来使能数据流时,数据流会立即开始填充 FIFO,直至达到阈值级别。达到阈值级别后, FIFO 的内容便会移出,并存储到目标中。如果 DMA_SxNDTR 寄存器达到零或 DMA_SxCR 寄存器中的 EN 位由软件清零,传输即会停止。(不允许使用直接模式)
        只有赢得了数据流的仲裁后,相应数据流才有权访问 AHB 源或目标端口。系统使用在DMA_SxCR 寄存器 PL[1:0] 位中为每个数据流定义的优先级执行仲裁。注意: 使用存储器到存储器模式时,不允许循环模式和直接模式只有 DMA2 控制器能够执行存储器到存储器的传输。

6、地址递增

根据 DMA_SxCR 寄存器中 PINC 和 MINC 位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。如果使能了递增模式,则根据在 DMA_SxCR 寄存器 PSIZEMSIZE 位中编程的数据宽度,下一次传输的地址将是前一次传输的地址递增 1 (对于字节)、 2(对于半字)或 4(对于字)。--设置的递增大小和psizeMSIZE有关
为了优化封装操作,可以不管 AHB 外设端口上传输的数据的大小,将外设地址的增量偏移大小固定下来(设置递增的大小是一个固定值)。 DMA_SxCR 寄存器中的 PINCOS 位用于将增量偏移大小与外设 AHB 端口或32 位地址(此时地址递增 4)上的数据大小对齐。 PINCOS 位仅对 AHB 外设端口有影响。如果将 PINCOS 位置 1 ,则不论 PSIZE 值是多少,下一次传输的地址总是前一次传输的地址递增 4(自动与 32 位地址对齐) 。但是, AHB 存储器端口不受此操作影响。如果 AHB 外设端口或 AHB 存储器端口分别请求突发事务,为了满足 AMBA 协议(在固定地址模式下不允许突发事务),则需要将 PINC 或 MINC 位置 1

三、DMA相关寄存器

DMA 数据流 x 配置寄存器 (DMA_SxCR) (x = 0..7)

25-27位用于通道选择,只有还没使能DMA的时候才可以配置

突发模式只能针对FIFO模式来用,只有还没使能DMA的时候才可以配置

位15有效的前提是PINC位置1,也就是PINC位如果等于0则外设地址不递增

每搬一次数据到fifo里是以什么为单位,在直接模式下,MSIZE写入的值一定是和PSIZE的大小一致。

置0时,就不递增,置1时每次递增的数据就是以 MSIZE的大小为准。

DMA 数据流 x 数据项数寄存器 (DMA_SxNDTR) (x = 0..7)

搬多少次写什么值,每搬一次数据和当前的MSIZE或者PSIZE有关。

DMA 数据流 x 外设地址寄存器 (DMA_SxPAR) (x = 0..7)

填入的时当前这个外设的基地址(填进来的是地址数据的形式)。

DMA 数据流 x 存储器 0 地址寄存器 (DMA_SxM0AR) (x = 0..7)

填入的时当前这个存储器的基地址(填进来的是地址数据的形式)。

DMA 数据流 x FIFO 控制寄存器 (DMA_SxFCR) (x = 0..7) 

位2写0代表使用直接模式,写1代表fifo模式。

0-1 位设置FIFO 的阈值级别,在直接模式下,这些位的写入没有意义。

四、例子

用USART1_TX端要连接到DMA2的数据流7的通道4上

软件实现

串口的DMA可以正常工作(响应请求)

打开DMA2的时钟

常用的配置(围绕CR寄存器)

从哪里搬到哪里? -- 方向(存储器到外设,设置当前的源地址和目标地址)

搬多少次? -- 由数据项数寄存器决定(NDTR)

每搬一次的大小?--MSIZE和PSZIE

使能数据流

存储器到外设

#include "dma.h"

/************************************
函数功能:DMA2的数据流7通道4的初始化
函数形参:
u8 *buf -- 存储器的地址
u16 len -- 数据的大小(要搬多少次)
函数返回值:void
函数说明:可以完成USART1通过DMA往外界搬运数据
存储器到外设
作者:
日期:
************************************/
void Dma2_Stream7_Init(u8 *buf,u16 len)
{
	//发送使能 DMA 模式。
	USART1->CR3 |= 0X1 << 7;
	
    //	1.打开DMA2的时钟
	RCC->AHB1ENR |= 0X1 << 22;
	
    //	2.常用的配置(围绕CR寄存器)
	
	//使用直接模式
	DMA2_Stream7->FCR &= ~(0x1 << 2);
	DMA2_Stream7->CR = 0;
	/*
		P和M都是单次传输,不使用突发传输
		当前目标存储器为存储器0
		不使用双缓冲
		低优先级
		外设地址指针固定
		MSIZE和PSZIE都是1个字节
		禁止循环模式
		DMA是流控制器
	*/
	
	//数据流7选择的是通道4
	DMA2_Stream7->CR |= (0x4 << 25);
	
	//存储器地址递增
	DMA2_Stream7->CR |= 0x1 << 10;

	//3.从哪里搬到哪里?
	//	存储器到外设
		DMA2_Stream7->CR |= 0x1 << 6;

	//设置当前的源地址和目标地址
	DMA2_Stream7->PAR = (u32)&(USART1->DR);//目标地址
	DMA2_Stream7->M0AR = (u32)buf;//源地址
	
	//4.搬多少次? -- 由数据项数寄存器决定(NDTR)
	DMA2_Stream7->NDTR = len;
	
	//5.使能数据流
	DMA2_Stream7->CR |= 0x1 << 0;
}

存储器到存储器

/************************************
函数功能:DMA2的数据流5通道2的初始化
函数形参:
u32 *par
u32 *m0ar 
u16 len -- 数据的大小(要搬多少次)
函数返回值:void
函数说明:存储器到存储器
作者:
日期:
************************************/
void Dma2_Stream5_Init(u8 *par,u8 *m0ar,u16 len)
{
	
    //	1.打开DMA2的时钟
	RCC->AHB1ENR |= 0X1 << 22;
	
    //	2.常用的配置(围绕CR寄存器)
	
	//使用fifo模式
	DMA2_Stream5->FCR |= (0x1 << 2);
	
	//阈值级别为4字节
	DMA2_Stream5->FCR &= ~(0x3 << 0);
	
	DMA2_Stream5->CR = 0;
	/*
		当前目标存储器为存储器0
		不使用双缓冲
		MSIZE和PSZIE都是1个字节
		禁止循环模式
		DMA是流控制器
	*/
	
	
	//数据流5选择的是通道2
	DMA2_Stream5->CR |= (0x2 << 25);
	
	//外设4节拍的突发
	DMA2_Stream5->CR |= 0x1 << 21;
	
	//软件优先级为高
	DMA2_Stream5->CR |= 0x2 << 16;
	
	//外设地址递增(psize)
	DMA2_Stream5->CR |= 0x1 << 9;
	
	//存储器地址递增
	DMA2_Stream5->CR |= 0x1 << 10;
	

	//3.从哪里搬到哪里?
	//	存储器到存储器
		DMA2_Stream5->CR |= 0x2 << 6;

	//设置当前的源地址和目标地址
	DMA2_Stream5->PAR = (u32)par;//目标地址
	DMA2_Stream5->M0AR = (u32)m0ar;//源地址
	
	
	//4.搬多少次? -- 由数据项数寄存器决定(NDTR)
	DMA2_Stream5->NDTR = len;
	
	//5.使能数据流
	DMA2_Stream5->CR |= 0x1 << 0;
}
;