前言

上一篇介绍了关于ADC转换的相关内容,在上一篇中提到了一嘴关于DMA的内容,本文的主角就是DMA(Direct Memory Access,直接内存访问)是一种可以使外设直接访问内存的技术。

DMA概述

之前的所有代码的所有的数据传输都需要CPU参与,也就是由CPU发出指令控制数据的读写。数据量不大的情况还好,但是数据量一大,CPU就会在数据传输上花很多时间,影响系统效率,而使用DMA技术可以在不经过CPU的情况下,直接在内存、外设相互之间进行数据的传输,从而提高数据传输的速度和系统的整体性能。

DMA原理

DMA技术的基本原理是:使用一个独立的DMA控制器,通过直接访问内存的方式,将外设的数据直接传输到内存中,或者将内存中的数据直接传输到外设中。在数据传输过程中,CPU只需发出一次传输指令,DMA控制器便会自动完成数据传输的过程。

STM32F407的DMA简介

STM32F407的DMA 控制器基于复杂的总线矩阵架构,将功能强大的双 AHB 主总线架构与独立的 FIFO 结合在一起,优化了系统带宽。如下图所示,DMA位于AHB总线,一共有两个,且两个都有8个数据流,带FIFO。

数据的传输对象

前面提到了DMA的工作是将数据从外设搬运到内存,或者从内存搬运到外设,或者是从内存搬运到内存;外设前面已经用过很多了,USART、ADC这些都是;那么内存在哪儿呢,在下面的整体框图中就有,两个红框内的RAM和SRAM加起来一共是64+16+112=192KB,而绿色框内的Flash是1MB的空间,之前也介绍过,STM32F407的SRAM一共是192KB,ROM是1M;这就是DMA搬运的又一个对象。

数量

两个 DMA 控制器总共有 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。也就是说,STM32的DMA数据通道一共有288=128个。具体的对应方式需要查表来对应。


优先级

每个通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
关于DMA的仲裁器,首先可以编程的优先级一共四个,分别是:低 中 高 非常高;很明晰对于八个通道来说这个优先级不够分,所以还有一个类似自然优先级的东西,当多个通道请求属于同一个软件优先级的时候,通道在上面的表中编号越低,优先级越高。
举个栗子:假设DMA2的通道6的TIM1_CH2与TIM1_CH3的软件优先级匹配一样,那么实际处理时TIM1_CH2是数据流2,而TIM_CH3的数据流是6,则TIM1_CH2的优先级高于TIM1_CH3。

DMA的特性总结

FIFO

特性中有一个关于FIFO的东西上面没有介绍到,FIFO是一个单独的四级 32 位先进先出存储器缓冲区 ;它的作用是缓存数据流上的数据,并按照我们需要的格式对数据进行拼接。
举个栗子:假设我们需要接收一个16位的数据,而外设是串口,串口每次只能接收一个八位的数据,这时候如果不使用DMA的FIFO功能,在后面的数据处理时就需要使用CPU对数据进行拼接;而使用FIFO的情况下,我们可以编程设置FIFO为接收满一个字后再传输,这样,串口上的8位数据就会在FIFO中拼接成一个字,而后再存入内存,后面再使用数据的时候就不需要CPU再做拼接了。
FIFO模式下:源只能选择字节和半字
四种情况:
字节——>字
字节—>半字
半字—>字
半字—>字节
FIFO的作用:用来控制源的数据大小和目标的数字大小
遇到了这种传输:字节——>字
禁止直接模式,设置一个FIFO阈值
达到了阈值才开始传输

源、目标和传输模式

:数据传输的起点,指的是地址
目标:数据传输的终点,指的是地址
传输模式
传输方向使用 DMA_SxCR 寄存器中的 DIR[1:0] 位进行配置,有三种可能的传输方向:
1.存储器到外设;
2.外设到存储;
3.存储器到存储器。

源地址:DMA_SxPAR 存储外设地址
目标地址:DMA_SxM0AR 存储存储器的地址

例子:串口1发送数据,使用DMA进行发送
源:要发送数据数组 u8 str[];
目标:串口1的数据寄存器

源地址:DMA_SxM0AR 写入:str (首地址)
目标地址:DMA_SxPAR 写入:&(USART1->DR)

发送:“123456” USART1->DR
那么新问题出现了,在使用DMA进行搬运的时候,每一次结束了一个字节传输后,地址是否要进行偏移?
外设地址:一般是寄存器,没有办法进行偏移,偏移了就不再是对应的外设地址了。
存储器的地址:需要DMA进行偏移。

DMA的框图

前面看了TIME、ADC的框图,再看DMA的框图就觉得没那么复杂了,从左到右来看,首先是通道和数据流的选择,这个具体的选择要看上面的数据流映射;选择通道后就是仲裁器,利用仲裁器设置优先级,再就是配置对应的内存端口和外设端口了,配置过程中可以考虑需不需要加入FIFO来缓存数据,配置好后就可以开始传输了。

寄存器介绍

寄存器的作用:
告诉DMA数据传输起点
告诉DMA数据传输终点
告诉DMA要传输多少数据
1.DMA 数据流 x 配置寄存器 (DMA_SxCR) (x = 0…7)
写法:DMA1_StreamX->CR
功能:
位 27:25 CHSEL[2:0]:通道选择 (Channel selection)
位 17:16 PL[1:0]:优先级 (Priority level)
位 14:13 MSIZE[1:0]:存储器数据大小 (Memory data size)
位 12:11 PSIZE[1:0]:外设数据大小 (Peripheral data size)
位 10 MINC:存储器递增模式 (Memory increment mode)
位 9 PINC:外设递增模式 (Peripheral increment mode)
位 7:6 DIR[1:0]:数据传输方向 (Data transfer direction)
位 5 PFCTRL:外设流控制器 (Peripheral flow controller)
位 0 EN:数据流使能/读作低电平时数据流就绪标志 (Stream enable / flag stream ready when read low)

2.DMA 数据流 x 数据项数寄存器 (DMA_SxNDTR) (x = 0…7)
写法:DMA1_StreamX->NDTR
功能:直接写入 告诉DMA要搬运多少数据

3.DMA 数据流 x 外设地址寄存器 (DMA_SxPAR) (x = 0…7)
写法:DMA1_StreamX->PAR
功能:写入外设的地址(外设数据寄存器的地址)

4.DMA 数据流 x 存储器 0 地址寄存器 (DMA_SxM0AR) (x = 0…7)
写法:DMA1_StreamX->M0AR
功能:写入存储器地址,(存数据的数组的地址)

5.DMA 数据流 x 存储器 1 地址寄存器 (DMA_SxM1AR) (x = 0…7)
功能:双缓冲区模式下 可以有两个存储器地址
才会启用M0

6.DMA 数据流 x FIFO 控制寄存器 (DMA_SxFCR) (x = 0…7)
功能:配置FIFO模式的功能

代码流程

根据上面的框图以及寄存器描述就可以总结出对应的配置流程了,伪代码如下:

DMA的配置函数
{
   //打开DMA2时钟
   //打开外设的DMA使能  在USART的CR3

   DMA控制器配置
   //关闭数据流
   //选择通道
   //优先级
   //存储器数据大小
   //外设数据大小
   //存储器递增模式
   //外设的递增模式
   //数据的传输方向
   //选择流控制器 
   //写入待发送的数据数目
   //写入外设地址    &USART1->DR
   //写入存储器地址  str
   //使能数据流
}

需求

使用DMA实现串口接收数据,并且接收后又使用DMA将数据输出到串口。
需求分析:
使用串口接收数据而且还需要将数据发送回串口传输方向分为两部分,接收数据的传输方向是从外设到内存,而发送数据是从内存到外设。
搞清楚方向后,接下来就需要查到对应的通道与数据流,如下表,USART1_RX是通道4的数据流5,而USART1_TX对应的通道4的数据流7。
由于是串口接收和发送,所以不需要使用到FIFO的功能。

以下是配置代码:

/*******************************************
*函数名    :DMA2_Steam7_IN4
*函数功能  :DMA2,USART1的发送函数
*函数参数  :u16 num(传输了多少个数) ,u32 maddr存储器地址编号 ,u32paddr外设地址
*函数返回值:无
*函数描述  :数据流7通道4
将数据搬运至串口1的发送
*********************************************/
void DMA2_Steam7_IN4(u16 num, u32 maddr,u32 paddr)
{
    RCC->AHB1ENR |=(1<<22);//打开DMA2的时钟
    //串口DMA使能
    USART1->CR3 |=(1<<7);  //串口1的发送DMA使能
    //DMA控制器
    DMA2_Stream7->CR &=~(1<<0); //关闭 DMA 传输
    DMA2_Stream7->CR &=~(7<<25);
    DMA2_Stream7->CR |=(4<<25);//选择通道4
    DMA2_Stream7->CR &=~(3<<16);//低优先级
    DMA2_Stream7->CR &=~(3<<13);//存储器数据大小为8位
    DMA2_Stream7->CR &=~(3<<11);//外设数据大小为8位
    DMA2_Stream7->CR |=(1<<10); //存储器地址递增
    DMA2_Stream7->CR &=~(1<<9);//外设地址不变
    DMA2_Stream7->CR &=~(3<<6);//清零
    DMA2_Stream7->CR |=(1<<6);//传输方向是存储器到外设地址
    DMA2_Stream7->CR &=~(1<<5);//选择DMA流

    DMA2_Stream7->NDTR = num;//传输的数据大小
    DMA2_Stream7->PAR =paddr;//外设地址32位的数据
    DMA2_Stream7->M0AR =maddr;//存储器地址32位的数据
    DMA2_Stream7->FCR &=~(1<<2);//直接模式
    DMA2_Stream7->CR |=(1<<0);//使能DMA
}
/*******************************************
*函数名    :DMA2_Steam5_IN4
*函数功能  :DMA2,USART1的接收函数
*函数参数  :u16 num(传输了多少个数) ,u32 maddr存储器地址编号 ,u32 paddr外设地址
*函数返回值:无
*函数描述  :数据流7通道4
将数据从串口搬运到内存
*********************************************/
void DMA2_Steam5_IN4(u16 num, u32 maddr,u32 paddr)
{
    RCC->AHB1ENR |=(1<<22);//打开DMA2的时钟
    //串口DMA使能
    USART1->CR3 |=(1<<6);  //串口1的接收DMA使能
    //DMA控制器
    DMA2_Stream5->CR &=~(1<<0); //关闭 DMA 传输
    DMA2_Stream5->CR &=~(7<<25);
    DMA2_Stream5->CR |=(4<<25);//选择通道4
    DMA2_Stream5->CR &=~(3<<16);//低优先级
    DMA2_Stream5->CR &=~(3<<13);//存储器数据大小为8位
    DMA2_Stream5->CR &=~(3<<11);//外设数据大小为8位
    DMA2_Stream5->CR |=(1<<10); //存储器地址递增
    DMA2_Stream5->CR &=~(1<<9);//外设地址不变
    DMA2_Stream5->CR &=~(3<<6);//清零传输方向是外设到存储器
    DMA2_Stream5->CR &=~(1<<5);//选择DMA流

    DMA2_Stream5->NDTR = num;//传输的数据大小
    DMA2_Stream5->PAR =paddr;//外设地址32位的数据
    DMA2_Stream5->M0AR =maddr;//存储器地址32位的数据
    DMA2_Stream5->FCR &=~(1<<2);//直接模式
    DMA2_Stream5->CR |=(1<<0);//使能DMA
}
//需要注意,判断传输完成要找到对应的位 DMA 低中断状态寄存器 (DMA_LISR)数据流:[0-3];DMA 高中断状态寄存器 (DMA_HISR)数据流[4-7]
    if(DMA2->HISR & (1<<11))  //传输完成
    {
        DMA2->HIFCR |=(1<<11); //清除标志位
           DMA2_Steam7_IN4(sizeof(Rceive_Str),(u32)Rceive_Str,(u32)&(USART1 -> DR));
    }
效果

总结

以上就是关于DMA数据搬运的介绍,文中如有不足之处欢迎批评指正。

M4系列目录

1.嵌入式学习笔记——概述
2.嵌入式学习笔记——基于Cortex-M的单片机介绍
3.嵌入式学习笔记——STM32单片机开发前的准备
4.嵌入式学习笔记——STM32硬件基础知识
5.嵌入式学习笔记——认识STM32的 GPIO口
6.嵌入式学习笔记——使用寄存器编程操作GPIO
7.嵌入式学习笔记——寄存器实现控制LED小灯
8.嵌入式学习笔记——使用寄存器编程实现按键输入功能
9.嵌入式学习笔记——STM32的USART通信概述
10.嵌入式学习笔记——STM32的USART相关寄存器介绍及其配置
11.嵌入式学习笔记——STM32的USART收发字符串及串口中断
12.嵌入式学习笔记——STM32的中断控制体系
13.嵌入式学习笔记——STM32寄存器编程实现外部中断
14.嵌入式学习笔记——STM32的时钟树
15.嵌入式学习笔记——SysTick(系统滴答)
16.嵌入式学习笔记——M4的基本定时器
17.嵌入式学习笔记——通用定时器
18.嵌入式学习笔记——PWM与输入捕获(上)
19.嵌入式学习笔记——PWM与输入捕获(下)
20.嵌入式学习笔记——ADC模数转换器
21.嵌入式学习笔记——DMA
22.嵌入式学习笔记——SPI通信
23.嵌入式学习笔记——SPI通信的应用
24嵌入式学习笔记——IIC通信