STM32 | DMA配置和使用如此简单(超详细)

DMA配置和使用如此简单(超详细)

  • DMA传输
    • 一、查询和中断不爽吗?为什么要使用串口DMA?(借串口DMA引入)
    • 二、DMA介绍
      • 1、什么是DMA?
      • 2、STM32上的DMA资源
      • 3、DMA主要特征
      • 4、DMA请求映像
        • (1)DMA1控制器
        • (1)DMA2控制器
      • 5、DMA寄存器介绍
        • (1)DMA中断状态寄存器(DMA_ISR)
        • (2)DMA中断标志清除寄存器(DMA_IFCR)
        • (3)DMA通道x配置寄存器(DMA_CCRx)(x = 1…7)
        • (4)DMA通道x传输数量寄存器(DMA_CNDTRx)(x = 1…7)
        • (5)DMA通道x外设地址寄存器(DMA_CPARx)(x = 1…7)
        • (6)DMA通道x存储器地址寄存器(DMA_CMARx)(x = 1…7)
      • 6、DMA库函数介绍
        • (1)函数 DMA_DeInit
        • (2)函数DMA_Init
        • (3)函数DMA_Cmd
        • (4)函数 DMA_ITConfig
    • 三、DMA配置
      • 1、寄存器版
      • 2、库函数版
    • 四、DMA使用
      • 1、查询方式
      • 2、中断方式
      • 3、不定长数据传输
      • 4、双缓冲方式
    • 五、实战串口DMA
    • 六、总结

DMA传输

看过我这篇博文《 STM32 | 基于NRF24L01串口透传(不定长数据无线串口双向传输) 》的应该就清楚我为什么要写DMA传输,最终目的就是为了让nRF24L01采用串口DMA+SPI DMA方式实现串口的透传,所以本文来讲讲DMA传输。还是老样子,我认真写好本文,希望更多人了解并且会用并且了解DMA传输,希望大家多多支持,可以参与评论或点个赞,谢谢! 那么就进入正题!

测试平台:STM32F103C8T6
库版本:官方库3.5版本
关键词:DMA

一、查询和中断不爽吗?为什么要使用串口DMA?(借串口DMA引入)

如果真的是刚刚接触单片机或嵌入式,肯定会问,因为按照难易程度:DMA>中断>查询。这里我提几个反问句回答。

  • 查询方式是不是要在一个循环里反复执行判断?如果串口传输数据频率快于循环频率,请问能及时收到数据吗?
  • 中断过于频繁主程序还要不要运行?如果我只接收数据,但不用接收一个字节数据就处理一次,那么请问,有必要频繁中断吗?
  • 数据传输过程需不需要时间?需要的话,那我们是不是还要等待?

其实我们要求的很简单,就是高效,传输数据的时候我们不在等待的时间浪费CPU资源,而且数据是一个字节一个字节传送的,接收的时候只要一个数据包最后一个字节数据接收到再处理即可,发送的时候让串口自己一个字节一个字节把数据发出去即可,不用在等待一个字节发送完再发下一字节数据这样。为了提高CPU使用效率,于是就使用DMA方式

二、DMA介绍

学习只有积跬步,才能至千里。可能废话有点多,当然这是写给还不知道DMA的同学看的。已经了解的,只想学怎么配置的可以直接跳转到后面的内容

1、什么是DMA?

不太官方的理解:DMA只是个搬运工,帮助老板(CPU)搬运东西(数据),可以帮老板(CPU)把东西(数据)从家搬运到家门外(存储器→外设);可以帮老板(CPU)把到东西(数据)从家门外搬运到家里(外设→存储器);还可以帮老板(CPU)把东西(数据)从家里卧室1搬到卧室2(存储器→存储器)。

官方一点的表达:DMA,全称为:Direct Memory Access,即直接存储器访问。直接存储器存取( DMA )用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须 CPU 干预,数据可以通过 DMA 快速地移动,这就节省了 CPU 的资源来做其他操作。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能嵌入式系统算法和网络是很重要的。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。

2、STM32上的DMA资源

STM32 最多有 2 个 DMA 控制器( DMA2 仅存在大容量产品中),12个独立的可配置的通道(请求)DMA1 有 7 个通道。DMA2 有 5 个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个 DMA 请求的优先权。
小容量产品是指闪存存储器容量在16K至32K字节之间的微控制器。
中容量产品是指闪存存储器容量在64K至128K字节之间的微控制器。
大容量产品是指闪存存储器容量在256K至512K字节之间的微控制器。
互联型产品是指STM32F105xx和STM32F107xx微控制器。

3、DMA主要特征

  • 每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
  • 在同一个 DMA 模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求 0 优先于请求 1 ,依此类推,可以参考STM32数据手册)。
  • 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
  • 支持循环的缓冲器管理(会把原来的数据覆盖)。
  • 每个通道都有 3 个事件标志(DMA 半传输, DMA 传输完成和 DMA 传输出错),这 3 个事件标志逻辑或成为一个单独的中断请求。
  • 存储器和存储器间的传输(仅 DMA2 可以)
  • 外设和存储器、存储器和外设之间的传输
  • 闪存、SRAM 、外设的 SRAM 、APB1 、APB2 和 AHB 外设均可作为访问的源和目标。
  • 可编程的数据传输数目:最大为65535(216-1)

4、DMA请求映像

后续要以串口2的接收举例,由以下内容可以知道USART2_RX请求在DMA1通道6

(1)DMA1控制器

从外设(TIMx[x=1、2 、3、4] 、ADC1 、SPI1、SPI/I2S2、I2Cx[x=1、2]和USARTx[x=1、2、3])产生的7个请求,通过逻辑或输入到DMA1控制器,这意味着同时只能有一个请求有效。参见下图的DMA1请求映像。
外设的DMA请求,可以通过设置相应外设寄存器中的控制位,被独立地开启或关闭。

DMA1 请求映像
STM32 | DMA配置和使用如此简单(超详细)_第1张图片
各个通道的DMA1请求一览
STM32 | DMA配置和使用如此简单(超详细)_第2张图片

(1)DMA2控制器

从外设(TIMx[5、6、7、8]、ADC3、SPI/I2S3、UART4、DAC通道1、2和SDIO)产生的5个请求,经逻辑或输入到DMA2控制器,这意味着同时只能有一个请求有效。参见下图的DMA2请求映像。
外设的DMA请求,可以通过设置相应外设寄存器中的DMA控制位,被独立地开启或关闭。
注意: DMA2控制器及相关请求仅存在于大容量产品和互联型产品中。

DMA2 请求映像
STM32 | DMA配置和使用如此简单(超详细)_第3张图片
各个通道的DMA2请求一览
STM32 | DMA配置和使用如此简单(超详细)_第4张图片
注意:ADC3、SDIO和TIM8的DMA请求只在大容量的产品中存在。

5、DMA寄存器介绍

DMA寄存器的介绍主要参照参考手册,这里列出来方便大家阅读,使用DMA的时候可以使用库函数,方便阅读;也可直接配置寄存器,方便使用(快捷)。
注意: 在以下列举的所有寄存器中,所有与通道6和通道7相关的位,对DMA2都不适用,因为DMA2只 有5个通道。

(1)DMA中断状态寄存器(DMA_ISR)

STM32 | DMA配置和使用如此简单(超详细)_第5张图片

数据位
功能
位31:28 保留,始终读为0。
位27,23,19,15,11,7,3 TEIFx:通道x的传输错误标志(x = 1 … 7) (Channel x transfer error flag)
硬件设置这些位。在DMA_IFCR寄存器的相应位写入’1’可以清除这里对应的标志位。
0:在通道x没有传输错误(TE);
1:在通道x发生了传输错误(TE)。
位26,22,18,14,10,6,2 HTIFx:通道x的半传输标志(x = 1 … 7) (Channel x half transfer flag)
硬件设置这些位。在DMA_IFCR寄存器的相应位写入’1’可以清除这里对应的标志位。
0:在通道x没有半传输事件(HT);
1:在通道x产生了半传输事件(HT)。
位25,21,17,13, 9,5,1 TCIFx:通道x的传输完成标志(x = 1 … 7) (Channel x transfer complete flag)
硬件设置这些位。在DMA_IFCR寄存器的相应位写入’1’可以清除这里对应的标志位。
0:在通道x没有传输完成事件(TC);
1:在通道x产生了传输完成事件(TC)。
位24,20,16,12, 8,4,0 GIFx:通道x的全局中断标志(x = 1 … 7) (Channel x global interrupt flag)
硬件设置这些位。在DMA_IFCR寄存器的相应位写入’1’可以清除这里对应的标志位。
0:在通道x没有TE、HT或TC事件;
1:在通道x产生了TE、HT或TC事件。

(2)DMA中断标志清除寄存器(DMA_IFCR)

STM32 | DMA配置和使用如此简单(超详细)_第6张图片

数据位
功能
位31:28 保留,始终读为0。
位27,23,19,15,11,7,3 CTEIFx:清除通道x的传输错误标志(x = 1 … 7) (Channel x transfer error clear)
这些位由软件设置和清除。
0:不起作用
1:清除DMA_ISR寄存器中的对应TEIF标志。
位26,22,18,14,10,6,2 CHTIFx:清除通道x的半传输标志(x = 1 … 7) (Channel x half transfer clear)
这些位由软件设置和清除。
0:不起作用
1:清除DMA_ISR寄存器中的对应HTIF标志。
位25,21,17,13,9,5,1 CTCIFx:清除通道x的传输完成标志(x = 1 … 7) (Channel x transfer complete clear)
这些位由软件设置和清除。
0:不起作用
1:清除DMA_ISR寄存器中的对应TCIF标志。
位24,20,16,12,8,4,0 CGIFx:清除通道x的全局中断标志(x = 1 … 7) (Channel x global interrupt clear)
这些位由软件设置和清除。
0:不起作用
1:清除DMA_ISR寄存器中的对应的GIF、TEIF、HTIF和TCIF标志。

(3)DMA通道x配置寄存器(DMA_CCRx)(x = 1…7)

STM32 | DMA配置和使用如此简单(超详细)_第7张图片

数据位
功能
位31:15 保留,始终读为0。
位14 MEM2MEM:存储器到存储器模式 (Memory to memory mode)
该位由软件设置和清除。
0:非存储器到存储器模式;
1:启动存储器到存储器模式。
位13:12 PL[1:0]:通道优先级 (Channel priority level)
这些位由软件设置和清除。
00:低
01:中
10:高
11:最高
位11:10 MSIZE[1:0]:存储器数据宽度 (Memory size)
这些位由软件设置和清除。
00:8位
01:16位
10:32位
11:保留
位9:8 PSIZE[1:0]:外设数据宽度 (Peripheral size)
这些位由软件设置和清除。
00:8位
01:16位
10:32位
11:保留
位7 MINC:存储器地址增量模式 (Memory increment mode)
该位由软件设置和清除。
0:不执行存储器地址增量操作
1:执行存储器地址增量操作
位6 PINC:外设地址增量模式 (Peripheral increment mode)
该位由软件设置和清除。
0:不执行外设地址增量操作
1:执行外设地址增量操作
位5 CIRC:循环模式 (Circular mode)
该位由软件设置和清除。
0:不执行循环操作
1:执行循环操作
位4 DIR:数据传输方向 (Data transfer direction)
该位由软件设置和清除。
0:从外设读
1:从存储器读
位3 TEIE:允许传输错误中断 (Transfer error interrupt enable)
该位由软件设置和清除。
0:禁止TE中断
1:允许TE中断
位2 HTIE:允许半传输中断 (Half transfer interrupt enable)
该位由软件设置和清除。
0:禁止HT中断
1:允许HT中断
位1 TCIE:允许传输完成中断 (Transfer complete interrupt enable)
该位由软件设置和清除。
0:禁止TC中断
1:允许TC中断
位0 EN:通道开启 (Channel enable)
该位由软件设置和清除。
0:通道不工作
1:通道开启

(4)DMA通道x传输数量寄存器(DMA_CNDTRx)(x = 1…7)

STM32 | DMA配置和使用如此简单(超详细)_第8张图片

数据位
功能
位31:16 保留,始终读为0。
位15:0 NDT[15:0]:数据传输数量 (Number of data to transfer)
数据传输数量为0至65535。这个寄存器只能在通道不工作(DMA_CCRx的EN=0)时写入。
通道开启后该寄存器变为只读,指示剩余的待传输字节数目。寄存器内容在每次DMA传输后递减。
数据传输结束后,寄存器的内容或者变为0;或者当该通道配置为自动重加载模式时,寄存器的内容将被自动重新加载为之前配置时的数值。
当寄存器的内容为0时,无论通道是否开启,都不会发生任何数据传输。

(5)DMA通道x外设地址寄存器(DMA_CPARx)(x = 1…7)

STM32 | DMA配置和使用如此简单(超详细)_第9张图片

数据位
功能
位31:0 PA[31:0]:外设地址 (Peripheral address)
外设数据寄存器的基地址,作为数据传输的源或目标。
当PSIZE=’01’(16位),不使用PA[0]位。操作自动地与半字地址对齐。
当PSIZE=’10’(32位),不使用PA[1:0]位。操作自动地与字地址对齐。

(6)DMA通道x存储器地址寄存器(DMA_CMARx)(x = 1…7)

STM32 | DMA配置和使用如此简单(超详细)_第10张图片

数据位
功能
位31:0 MA[31:0]:存储器地址
存储器地址作为数据传输的源或目标。
当MSIZE=’01’(16位),不使用MA[0]位。操作自动地与半字地址对齐。
当MSIZE=’10’(32位),不使用MA[1:0]位。操作自动地与字地址对齐。

6、DMA库函数介绍

库函数比较好的就是便于查看,有的人会说库函数和寄存器学一个就好了,但是有时候你会发现库函数和寄存器结合起来使用的时候回比较方便。例如,你要修改一些配置的时候,寄存器会很方便,原本库函数需要几条代码才能搞定,而寄存器可能一条就解决了。所以,我建议使用DMA的时候可以库函数结合寄存器一起使用。在DMA配置的时候,使用 V3.5 库函数操作的话,我们只需要调用 DMA_Init() 函数就可以了。下面介绍一些主要的函数。

函数名
描述
DMA_DeInit 将 DMA 的通道 x 寄存器重设为缺省值
DMA_Init 根据 DMA_InitStruct 中指定的参数初始化 DMA 的通道 x 寄存器
DMA_StructInit 把 DMA_InitStruct 中的每一个参数按缺省值填入
DMA_Cmd 使能或者失能指定的通道 x
DMA_ITConfig 使能或者失能指定的通道 x 中断
DMA_GetCurrDataCounte 返回当前 DMA 通道 x 剩余的待传输数据数目
DMA_GetFlagStatus 检查指定的 DMA 通道 x 标志位设置与否
DMA_ClearFlag 清除 DMA 通道 x 待处理标志位
DMA_GetITStatus 检查指定的 DMA 通道 x 中断发生与否
DMA_ClearITPendingBit 清除 DMA 通道 x 中断待处理标志位

(1)函数 DMA_DeInit

函数名
DMA_DeInit
函数原形 void DMA_DeInit(DMA_Channel_TypeDef* DMA_Channelx)
功能描述 将 DMA 的通道 x 寄存器重设为缺省值
输入参数 DMA Channelx:x 可以是 1,2…,或者 7 来选择 DMA 通道 x
输出参数
返回值
先决条件
被调用函数 RCC_APBPeriphResetCmd()

使用例子:

/* 将 DMA 的通道 6 寄存器重设为缺省值 */ 
DMA_DeInit(DMA_Channel6);

(2)函数DMA_Init

函数名
DMA_Init
函数原形 void DMA_Init(DMA_Channel_TypeDef* DMA_Channelx, DMA_InitTypeDef* DMA_InitStruct)
功能描述 根据 DMA_InitStruct 中指定的参数初始化 DMA 的通道 x 寄存器
输入参数 1 DMA Channelx:x 可以是 1,2…,或者 7 来选择 DMA 通道 x
输入参数 2 DMA_InitStruct:指向结构 DMA_InitTypeDef 的指针,包含了 DMA 通道 x 的配置信息
输出参数
返回值
先决条件
被调用函数

这里有必要介绍一下 DMA_InitTypeDef structure
DMA_InitTypeDef 定义于文件“stm32f10x_dma.h”:

typedef struct 
{ 
	u32 DMA_PeripheralBaseAddr; 
	u32 DMA_MemoryBaseAddr; 
	u32 DMA_DIR; 
	u32 DMA_BufferSize; 
	u32 DMA_PeripheralInc; 
	u32 DMA_MemoryInc; 
	u32 DMA_PeripheralDataSize; 
	u32 DMA_MemoryDataSize; 
	u32 DMA_Mode; 
	u32 DMA_Priority; 
	u32 DMA_M2M; 
} DMA_InitTypeDef;

DMA_PeripheralBaseAddr:该参数用以定义 DMA 外设基地址
DMA_MemoryBaseAddr:该参数用以定义 DMA 内存基地址
DMA_DIR:规定外设是作为数据传输的目的地还是来源(数据传输方向),该参数的取值范围如下表。

DMA_DIR 值
描述
DMA_DIR_PeripheralDST 外设作为数据传输的目的地
DMA_DIR_PeripheralSRC 外设作为数据传输的来源

DMA_BufferSize:用以定义指定 DMA 通道的 DMA 缓存的大小,单位为数据单位。根据传输方向,数据单位等于结构中参数 DMA_PeripheralDataSize 或者参数 DMA_MemoryDataSize 的值。
DMA_PeripheralInc:用来设定外设地址寄存器递增与否。该参数的取值范围如下表。

DMA_PeripheralInc 值
描述
DMA_PeripheralInc_Enable 外设地址寄存器递增
DMA_PeripheralInc_Disable 外设地址寄存器不变

DMA_MemoryInc:用来设定内存地址寄存器递增与否。该参数的取值范围如下表。

DMA_MemoryInc 值
描述
DMA_PeripheralInc_Enable 内存地址寄存器递增
DMA_PeripheralInc_Disable 内存地址寄存器不变

DMA_PeripheralDataSize:设定了外设数据宽度。该参数的取值范围如下表。

DMA_PeripheralDataSize 值
描述
DMA_PeripheralDataSize_Byte 数据宽度为 8 位
DMA_PeripheralDataSize_HalfWord 数据宽度为 16 位
DMA_PeripheralDataSize_Word 数据宽度为 32 位

DMA_MemoryDataSize:设定了外设数据宽度。该参数的取值范围如下表。

DMA_MemoryDataSize 值
描述
DMA_MemoryDataSize_Byte 数据宽度为 8 位
DMA_MemoryDataSize_HalfWord 数据宽度为 16 位
DMA_MemoryDataSize_Word 数据宽度为 32 位

DMA_Mode:设置了 DMA 的工作模式。该参数的取值范围如下表。

DMA_Mode 值
描述
DMA_Mode_Circular 工作在循环缓存模式
DMA_Mode_Normal 工作在正常缓存模式

注意:当指定 DMA 通道数据传输配置为内存到内存时,不能使用循环缓存模式。
DMA_Priority:设定 DMA 通道 x 的软件优先级。该参数的取值范围如下表。

DMA_Priority 值
描述
DMA_Priority_VeryHigh DMA 通道 x 拥有非常高优先级
DMA_Priority_High DMA 通道 x 拥有高优先级
DMA_Priority_Medium DMA 通道 x 拥有中优先级
DMA_Priority_Low DMA 通道 x 拥有低优先级

DMA_M2M:使能 DMA 通道的内存到内存传输。该参数的取值范围如下表。

DMA_M2M 值
描述
DMA_M2M_Enable DMA 通道 x 设置为内存到内存传输
DMA_M2M_Disable DMA 通道 x 没有设置为内存到内存传输

(3)函数DMA_Cmd

函数名
DMA_Cmd
函数原形 void DMA_Cmd(DMA_Channel_TypeDef* DMA_Channelx, FunctionalState NewState)
功能描述 使能或者失能指定的通道 x
输入参数 1 DMA Channelx:x 可以是 1,2…,或者 7 来选择 DMA 通道 x
输入参数 2 NewState:DMA 通道 x 的新状态。这个参数可以取:ENABLE 或者 DISABLE
输出参数
返回值
先决条件
被调用函数

使用例子:

/* 使能DMA通道6 */ 
DMA_Cmd(DMA_Channel6, ENABLE);

(4)函数 DMA_ITConfig

函数名
DMA_ITConfig
函数原形 void DMA_ITConfig(DMA_Channel_TypeDef* DMA_Channelx, u32 DMA_IT, FunctionalState NewState)
功能描述 使能或者失能指定的通道 x 中断
输入参数 1 DMA Channelx:x 可以是 1,2…,或者 7 来选择 DMA 通道 x
输入参数 2 DMA_IT:待使能或者失能的 DMA 中断源,使用操作符“
输入参数 3 NewState:DMA 通道 x 中断的新状态。这个参数可以取:ENABLE 或者 DISABLE
输出参数
返回值
先决条件
被调用函数

DMA_IT:使能或者失能 DMA 通道 x 的中断。可以取下表的一个或者多个取值的组合作为该参数的值。

DMA_IT 值
描述
DMA_IT_TC 传输完成中断屏蔽
DMA_IT_HT 传输过半中断屏蔽
DMA_IT_TE 传输错误中断屏蔽

使用例子:

/* 使能DMA通道6传输完成中断 */ 
DMA_ITConfig(DMA_Channel6, DMA_IT_TC, ENABLE);

三、DMA配置

下面是配置DMA通道x的过程(x代表通道号):

  1. 在DMA_CPARx寄存器中设置外设寄存器的地址。发生外设数据传输请求时,这个地址将是数据传输的源或目标。
  2. 在DMA_CMARx寄存器中设置数据存储器的地址。发生外设数据传输请求时,传输的数据将从这个地址读出或写入这个地址。
  3. 在DMA_CNDTRx寄存器中设置要传输的数据量。在每个数据传输后,这个数值递减。
  4. 在DMA_CCRx寄存器的PL[1:0]位中设置通道的优先级
  5. 在DMA_CCRx寄存器中设置数据传输的方向、循环模式、外设和存储器的增量模式、外设和存储器的数据宽度、传输一半产生中断或传输完成产生中断
  6. 设置DMA_CCRx寄存器的ENABLE位,启动该通道。一旦启动了DMA通道,它即可响应连到该通道上的外设的DMA请求。当传输一半的数据后,半传输标志(HTIF)被置1,当设置了允许半传输中断位(HTIE)时,将产生一个中断请求。在数据传输结束后,传输完成标志(TCIF)被置1,当设置了允许传输完成中断位(TCIE)时,将产生一个中断请求。

1、寄存器版

上述便是DMA初始化的配置过程,为什么前文说配置寄存器方便使用(快捷)?从上述过程可以发现实际只需配置四个寄存器即可完成DMA的配置。但是不好的地方就是不便于阅读,通常大家都要对照使用手册才知道什么位配置成‘0’或‘1’。这里为什么要将,有一个很重要的因素就是
先使能要使用的DMA时钟,这里使用DMA1,USART2_RX(即使用通道6)。

	RCC->AHBENR |= 1<<0 ;							//DMA1时钟使能

接着对照流程,配置寄存器方法如下(以通道6为例):

	u8 u1rxbuf[100];								//接收数据缓冲区1
	
	//DMA_USART2_RX  USART2->RAM的数据传输
	DMA1_Channel6->CPAR  = (u32)(&USART2->DR) ;		//1.设置外设寄存器地址,注意PSIZE
	DMA1_Channel6->CMAR  = (u32)u1rxbuf ; 			//2.设置数据存储器的地址,注意MSIZE
	DMA1_Channel6->CNDTR = buffersize ;       		//3.设置要传输的数据量buffersize个
	DMA1_Channel6->CCR  |= 	  2<<12   ;        		//4.设置通道的优先级高(处于位13:12)
	DMA1_Channel6->CCR  &= ~( 1<<4  ) ;        		//5(1).设置数据传输的方向从外设读取到内存
	DMA1_Channel6->CCR  &= ~( 1<<5  ) ;        		//5(2).设置不循环
	DMA1_Channel6->CCR  &= ~( 1<<6  ) ;        		//5(3).不执行外设地址增量模式
	DMA1_Channel6->CCR  |=    1<<7    ;        		//5(4).存储器地址增量模式
	DMA1_Channel6->CCR  &= ~( 3<<8  ) ;        		//5(5).外设数据宽度8bi
	DMA1_Channel6->CCR  &= ~( 3<<10 ) ;        		//5(6).存储器数据宽度8bit
	DMA1_Channel6->CCR  &= ~( 1<<14 ) ;        		//5(7).非存储器到存储器模式
	DMA1_Channel6->CCR  |= 	  1<<1    ;        		//5(8).允许传输完成中断
	DMA1_Channel6->CCR  |=    1 << 0  ;             //6.开启DMA通道6

因为上述允许传输完成中断,所以还要配置相应的NVIC,这里使用回库函数

	NVIC_InitTypeDef NVIC_InitStructure;
	
	//DMA1通道6 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;		//NVIC通道设置
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ;		//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;				//子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;					//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);									//根据指定的参数初始化VIC寄存器

当然,使用串口DMA的话还需开启串口接收中断(通道6是串口2接收通道)

USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);		//开启串口DMA接收

2、库函数版

库函数最大的优势就是便于阅读。根据固件库手册可以写出以下配置。
要先使能要使用的DMA时钟,这里使用DMA1,USART2_RX(即使用通道6)。相应库函数前面有讲解,直接上代码(注释很清楚)。

	DMA_InitTypeDef DMA1_Init;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);					//使能DMA1时钟
	
	//DMA1通道6 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;			//NVIC通道设置
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ;			//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;					//子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;						//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);										//根据指定的参数初始化VIC寄存器
	
	//DMA_USART2_RX  USART2->RAM的数据传输
	DMA_DeInit(DMA1_Channel6);											//将DMA的通道6寄存器重设为缺省值 
	DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR);				//启动传输前装入实际RAM地址
	DMA1_Init.DMA_MemoryBaseAddr = (u32)u1rxbuf;            			//设置接收缓冲区首地址
	DMA1_Init.DMA_DIR = DMA_DIR_PeripheralSRC;							//数据传输方向,从外设读取到内存
	DMA1_Init.DMA_BufferSize = USART2_MAX_RX_LEN;						//DMA通道的DMA缓存的大小
	DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;			//外设地址寄存器不变
	DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable;						//内存地址寄存器递增
	DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;		//数据宽度为8位
	DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;				//数据宽度为8位
	DMA1_Init.DMA_Mode = DMA_Mode_Normal;								//工作在正常模式
	DMA1_Init.DMA_Priority = DMA_Priority_High; 						//DMA通道 x拥有高优先级 
	DMA1_Init.DMA_M2M = DMA_M2M_Disable;								//DMA通道x没有设置为内存到内存传输
	DMA_Init(DMA1_Channel6,&DMA1_Init); 								//对DMA通道6进行初始化
	
	DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE);						//开USART2 Rx DMA中断
	DMA_Cmd(DMA1_Channel6,ENABLE);           							//使DMA通道6开始工作
	USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);        				//开启串口DMA接收

四、DMA使用

1、查询方式

查询方式可以不使能DMA中断,通过DMA_GetFlagStatus函数判断标志位来辨别是否传输完成或过半以及出错,然后关闭DMA通道,用DMA_SetCurrDataCounter函数重设缓存大小,完成相应操作后要记得清除标志位再使能DAM通道。如果使能了循环模式,会自动重装载计数。

2、中断方式

DMA中断一般用于定长数据传输,以传输完成中断为例。
(1)当产生DMA传输完成中断后,清除中断标志位、传输完成标志位;
(2)关闭DMA通道;
(3)处理数据;
(4)重新设置DMA通道的DMA缓存的大小(可以省去);
(5)开启DMA通道

3、不定长数据传输

以串口为例,不定长数据传输的时候,可以通过串口空闲中断来判断传输是否完成(传输缓存大小要大于传输的数据大小),数据长度可以通过DMA_GetCurrDataCounter函数来计算,然后关闭DMA通道,重设DMA缓存的大小,再启用DMA通道。

4、双缓冲方式

设置两个缓冲区,设置一个缓冲区标志(用来指示当前处在哪个缓冲区),每完成一次传输就通过重新配置DMA_MemoryBaseAddr的缓冲区地址,下次传输数据就会保存到新的缓冲区中,可以通过自定义缓存区标志来判断和切换,这样可以避免缓冲区数据来不及处理就被覆盖的情况,也能为处理数据留出更多地时间(指到下次传输完成)。

五、实战串口DMA

串口DMA的配置以及使用请查看此文:《 STM32 | 串口DMA很难?其实就是如此简单!(超详细、附代码)》

六、总结

其实DMA的配置并不难,按照上面的配置过程配置即可。在使用DMA的时候,如果启用了DMA传输中断,还要写相应的中断函数,在中断函数中切记要清除中断标志位。DMA只有在传输完成或传输过半或者传输出错才会产生中断(前提打开了中断),对于DMA定长数据传输的时候,建议使用中断,对于不定长数据,以串口为例,可以结合串口空闲中断来使用。因为中断不是本文内容,这里不做讲解。**还是要说一下的就是DMA通道一旦使能便开始传输数据,如果是接收数据,可以先使能DMA通道再等待数据接收;如果是发送数据,最好配置好发送缓存再使能DMA通道。**最后说明一下,本文为了后续讲解串口DMA和SPI DMA时候方便写下,所以对于各类的DMA运用还需大家结合实际运用。本文若有不足之处,欢迎指出,可在下方评论。

你可能感兴趣的:(STM32,嵌入式,c语言,经验分享)