STM32之DMA

1.0  DMA的简介

1) DMA:直接存储器存取(direct memory access)。作用:主要是实现数据的高速搬运,为CPU完成简单性重复性数据搬运工作。这个过程无需CPU干预。当数据搬运完成后,会有相应的状态标识位来告知CPU。

2) 特性:

双AHB 主总线架构,一个用于存储器访问,另一个用于外设访问;

STM32F4共有两个DMA,两个DMA 控制器总共有16 个数据流(每个控制器8 个);

每个数据流有单独的四级32 位先进先出存储器缓冲区(FIFO);

DMA 流控制器:要传输的数据项的数目是1 到65535,可用软件编程;

DMA数据搬运方向可以灵活设置,可以实现三种数据搬运:

       从芯片内部搬运到→芯片外部

       从芯片外部搬运到→芯片内部

       从芯片内部搬到芯片内部

  注意:芯片内部:指STM32内部存储器.    芯片外部:指STM32片上外设,如串口。

3) 弊端:如果需要接收大量的数据时,需要频繁触发中断,而且这个过程需要CPU参与。

1.1 DMA框图

 

1.2 DMA请求通道映射表

1.3 DMA数据流的配置编程步骤

1)        使能外设的DMA功能。举例:使能串口1的DMA发送或者DMA接收。

2)        使能DMA时钟

3)        DMA_SxCR 寄存器中的 EN 位清零,禁止DMA数据流

4)        阻塞判断EN位是否置0了。

5)        在 DMA_SxPAR 寄存器中设置外设寄存器地址

6)        在 DMA_SxM0AR 寄存器中设置存储器地址。

7)        在 DMA_SxNDTR 寄存器中配置要传输的数据个数的总数

8)        使用 DMA_SxCR 寄存器中的 CHSEL[2:0] 选择 DMA 通道

9)        使用 DMA_SxCR 寄存器中的 PL[1:0] 位配置数据流优先级

10)     禁止FIFO

11)     配置数据流传输方向

12)     设置外设地址是增量模式还是固定模式

13)     设置存储器地址是增量模式还是固定模式

14)     设置外设数据宽度

15)     设置存储器数据宽度

16)     通过将 DMA_SxCR 寄存器中的 EN 位置 1 激活数据流。

注意:只有“禁止了数据流”,才可以更改寄存器的配置。

1.4 源和目标的地址设置

1.5 DMA控制器相关寄存器

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

每个数据流都有一个自己的“配置寄存器”,举例:

1)        DMA1的数据流0的“配置寄存器”在写代码是应该写:DMA1_Stream0->CR

2)        DMA2的数据流5的“配置寄存器”在写代码是应该写:DMA2_Stream5->CR

3)        DMA1的数据流3的“配置寄存器”在写代码是应该写:DMA1_Stream3->CR

4)        DMA2的数据流6的“配置寄存器”在写代码是应该写:DMA2_Stream6->CR

27:25 CHSEL[2:0]:通道选择 (Channel selection)
这些位将由软件置 1 和清零。
    000:选择通道 0001:选择通道 1010:选择通道 2011:选择通道 3100:选择通道 4101:选择通道 5110:选择通道 6111:选择通道 7

提问:数据流7的请求通道选择为通道4,即USART1_TX功能,对应代码:DMA2_Stream7->CR |= 4<<25;
17:16 PL[1:0]:优先级 (Priority level)
设置数据流的优先级
这些位将由软件置 1 和清零。
    00:低
    01:中
    10:高
    11:非常高

举例:数据流7的请求通道选择为通道4,即USART1_TX功能,数据流7的优先级设置为“中”,对应代码:DMA2_Stream7->CR |= 1<<16;
14:13 MSIZE[1:0]:存储器数据大小 (Memory data size)
    00:字节(8 位)
    01:半字(16 位)
    10:字(32 位)
    11:保留
举例:
如果存储器是自定的一个数组,自定的数组是:u8 dataBuf1[2] = {1,2};那么存储器的数据大小是8位
如果存储器是自定的一个数组,自定的数组是:u16 dataBuf2[2] = {1,2};那么存储器的数据大小是16位
如果存储器是自定的一个数组,自定的数组是:u32 dataBuf2[2] = {1,2};那么存储器的数据大小是32位

举例:数据流7的请求通道选择为通道4,即USART1_TX功能,如果想实现串口1发送自定的一个数组,
   自定的数组是:u8 dataBuf1[
2] = {1,2}; ,那么存储器的数据大小要设置为“8位”,DMA2_Stream7->CR &=~(3<<13);
12:11 PSIZE[1:0]:外设数据大小 (Peripheral data size)
    00:字节(8 位)
    01:半字(16 位)
    10:字(32 位)
    11:保留

举例:
如果外设是USART1_DR寄存器,由于USART1_DR寄存器是8位有效的,所以外设大小需要设置为"8位"

举例:数据流7的请求通道选择为通道4,即USART1_TX功能,如果想实现串口1发送自定的一个数组,自定义的数组是:u8 dataBuf1[2] = {1,2}; ,
   那么外设的数据大小要设置为“8位”,DMA2_Stream7->CR &=~(3<<11);
10 MINC:存储器递增模式 (Memory increment mode)
    0:存储器地址是固定的
    1:每次传输完一个数据,存储器地址递增(增量为 MSIZE 值)

举例:数据流7的请求通道选择为通道4,即USART1_TX功能,如果想实现串口1发送自定的一个数组,自定的数组是:u8 dataBuf1[2] = {1,2}; 
   需要告诉DMA控制器,每发送一个数据后,需要进行地址偏移(地址递增),对应代码:DMA2_Stream7
->CR |=1<<10; 类比:串口发送数据 u8 dataBuf1[2] = {1,2}; for(i=0;i<2;i++) { USART1->DR = dataBuf1[i]; // 数组下标偏移→地址偏移 }
9 PINC:外设递增模式 (Peripheral increment mode)
    0:外设地址固定
    1:每次传输完一个数据,外设地址递增(增量为 PSIZE 值)

举例:数据流7的请求通道选择为通道4,即USART1_TX功能,如果想实现串口1发送自定的一个数组,自定的数组是:u8 dataBuf1[2] = {1,2};  
   由于一个芯片设置定型后,外设的地址都是固定的,所以一般都将外设地址设置为固定的。对应的代码:DMA2_Stream7->CR &=~(1<<9);
7:6 DIR[1:0]:数据传输方向 (Data transfer direction)
这些位将由软件置 1 和清零。
    00:外设到存储器
    01:存储器到外设
    10:存储器到存储器
    11:保留

举例:数据流7的请求通道选择为通道4,即USART1_TX功能,如果想实现串口1DMA发送数据,数据传输方向需要配置为“存储器到外设”。 对应代码:DMA2_Stream7->CR |= 1<<6;
4 TCIE:传输完成中断使能 (Transfer complete interrupt enable)
    DMA搬运完成后,可以触发DMA中断,前提是将该位置1
    0:禁止 TC 中断
    1:使能 TC 中断
0 EN:数据流使能/读作低电平时数据流就绪标志 (Stream enable / flag stream ready when read low)
    功能1:用于使能数据流
        0:禁止数据流
        1:使能数据流
    功能2:用于指示状态
        读取该位,如果为0,则允许"对器寄存器进行配置"
        读取该位,如果为1,则不允许"对器寄存器进行配置"
注意:只有“禁止了数据流”,才可以更改寄存器的配置。

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

15:0 NDT[15:0]:要传输(发送或接收)的数据个数 (Number of data items to transfer)
    写入的值是要传输的数据个数(065535)。
    如果读取该寄存器,则可以知道剩余需要传输的数据有多少个。
    DMA每传输一个数据后,此寄存器将递减。

举例:数据流7的请求通道选择为通道4,即USART1_TX功能,如果想实现串口1的DMA发送自定的一个数组,自定的数组是:u8 dataBuf[ ] = {“hello world”};  
   对应的代码:DMA2_Stream7->NDTR = sizeof(dataBuf);

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

31:0 PAR[31:0]:外设地址 (Peripheral address)
    写入的的是外设寄存器,作用是:用于告诉DMA控制将数据搬运到哪个外设寄存器或者从哪个外设寄存器取数据。

DMA发送:将数据搬运到USART1_DR
u8 dataBuf[ ] = {“hello world”};  → DMA发送 →USART1_DR
DMA接收:从USART1_DR取数据,搬运到dataBuf
u8 dataBuf[50 ] = {0};             ←DMA 接收←USART1_DR
这两种搬运,都需要将USART1_DR的地址写入到PAR寄存器。

举例:数据流7的请求通道选择为通道4,即USART1_TX功能,如果想实现串口1的DMA发送自定的一个数组,自定的数组是:u8 dataBuf[ ] = {“hello world”}; 
   需要将USART1_DR的地址写入到PAR寄存器,对应代码:DMA2_Stream7
->PAR = (u32)&USART1->DR;

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

需要写入的是:存储器的首地址(可以简单的理解为数组首地址)。

举例:数据流7的请求通道选择为通道4,即USART1_TX功能,如果想实现串口1的DMA发送自定的一个数组,自定的数组是:u8 dataBuf[ ] = {“hello world”}; 
需要将数组的地址写入到M0AR寄存器,对应代码:DMA2_Stream7
->M0AR = (u32)&dataBuf[0];

1.5.4 DMA 低中断状态寄存器 (DMA_LISR)

1.5.5 DMA 高中断状态寄存器 (DMA_HISR)

TCIFx:数据流 x 传输完成标志位 (Stream x transfer complete interrupt flag) (x=0..7)
此位将由硬件置 1,由软件清零,清零方法:将 1 写入 DMA_HIFCR或者 DMA_LIFCR 寄存器的相应位。
    0:数据流 x 上无传输完成事件
    1:数据流 x 上发生传输完成事件,即:数据搬运完成

注意:清零不是直接操作LISR、HISR。而是操作HIFCR、LIFCR
举例:数据流7的请求通道选择为通道4,即USART1_TX功能,如果想实现串口1的DMA发送自定的一个数组,自定的数组是:u8 dataBuf[ ] = {“hello world”};如何判断是否发送完成?
方法1:阻塞判断法→while(!(DMA2->HISR&(1<<27)));
方法2:中断方法

1.5.6 DMA 低中断标志清零寄存器 (DMA_LIFCR)

1.5.7 DMA 高中断标志清零寄存器 (DMA_HIFCR)

CTCIFx:数据流 x 传输完成中断标志清零 (Stream x clear transfer complete interrupt flag) (x = 0..7)
    将 1 写入此位时, DMA_LISR 和DMA_HISR寄存器中相应的 TCIFx 标志将清零

举例:数据流7的请求通道选择为通道4,即USART1_TX功能,如果想实现串口1的DMA发送自定的一个数组,自定的数组是:u8 dataBuf[ ] = {“hello world”};如何判断是否发送完成?
阻塞判断法→while(!(DMA2->HISR&(1<<27)));
清零代码:DMA2->HIFCR |= 1<<27;

1.6 DMA实例代码

从开发板发一串信息到电脑串口助手并显示助手软件屏幕上。

  PA9----TXD

  PA10---RXD

1.6.0 main.c

#include "stm32f4xx.h"
#include "usart1.h"
#include "dma.h"
int main(void)
{
    u8 recvData;
        Usart1_Init(115200);//初始化串口
    DMA2Stream7CH4_Init();
    DMA2Stream7_EnableTransfer();
    while(1)
    {

    }
}
View Code

1.6.1 usart1.c

#include "usart1.h"
void Usart1_Init(u32 baudRate)
{
    u16 integer ;
    u16 fraction;
    float USARTDIV;
    RCC->AHB1ENR |= 1<<0;//使能GPIOA的时钟  RCC_AHB1ENR
    GPIOA->MODER &= ~(3<<18);//清零
    GPIOA->MODER |= 2<<18;//设置PA9为复用功能模式MODER
    GPIOA->AFR[1]&= ~(0XF<<4);//清零
    GPIOA->AFR[1]|= (7<<4);//设置PA9的复用功能为第7复用功能 TXD, AFRH
    GPIOA->MODER &= ~(3<<20);//清零
    GPIOA->MODER |= 2<<20;//设置PA10为复用功能模式MODER
    GPIOA->AFR[1]&= ~(0XF<<8);//清零
    GPIOA->AFR[1]|= (7<<8);//设置PA10的复用功能为第7复用功能 RXD, AFRH
    
    RCC->APB2ENR |= 1<<4;//使能串口1模块时钟 RCC_APB2ENR
    USART1->CR1  |= 1<<15;//设置串口OVER8为1
    USART1->CR1  &= ~(1<<12);//设置串口数据位长度 :1 起始位, 8 数据位
    USART1->CR2 &=~(3<<12);//设置串口停止位长度 :1 个停止位
    USART1->CR1 &= ~(1<<10);//无校验
    //中断不使能
    USART1->CR1 |= 1<<3;//发送使能
    USART1->CR1 |= 1<<2;//接收使能
    USART1->CR3 |= 1<<7;//使能DMA发送
    USARTDIV = 84000000/8/baudRate;//串口波特率设置:USARTDIV = fCK/(8*(2-OVER8) /波特率
    integer = (u16)USARTDIV;
    fraction = ((u16)(USARTDIV-integer))<<4;
    USART1->BRR |= integer<<4 | fraction;
    USART1->CR1 |= 1<<13;//使能串口
}

/******************** 串口打印函数 ***************************************/
/* fputc是printf最底层的调用函数 */
int fputc(int data,FILE *file)
{
    
    while( !(USART1->SR & (1 << 6)) );//等待发送完成
    USART1->DR = data;   //发送数据
    return data;
}
View Code

1.6.1 dma.c

#include "stm32f4xx.h"
#include "dma.h"
u8 test_buf[]="hello world!!\r\n\
早上好,地球人^_^\r\n";
//DMA2数据流7的ch4用于USART1串口数据输出
void DMA2Stream7CH4_Init()
{
    RCC->AHB1ENR |= 1<<22;//使能DMA2时钟
    DMA2_Stream7->CR &=~(1<<0);//DMA_SxCR 寄存器中的 EN 位清零,禁止DMA2数据流
    while(DMA2_Stream7->CR&(1<<0));//阻塞判断EN位是否置0了。
    DMA2_Stream7->PAR = (u32)&USART1->DR;//在 DMA_SxPAR 寄存器中设置外设寄存器地址为USART1_DR
    DMA2_Stream7->M0AR = (u32)&test_buf[0];//在 DMA_SxM0AR 寄存器中设置存储器地址。
    DMA2_Stream7->NDTR = sizeof(test_buf);//在 DMA_SxNDTR 寄存器中配置要传输的数据个数的总数
    DMA2_Stream7->CR |= 4<<25;//使用 DMA_SxCR 寄存器中的 CHSEL[2:0] 选择 DMA2的数据流7的通道4
    DMA2_Stream7->CR |= 1<<16;//使用 DMA_SxCR 寄存器中的 PL[1:0] 位配置数据流优先级
    //禁止FIFO
    DMA2_Stream7->CR |= 1<<6;//配置数据流传输方向:存储器→外设。
    DMA2_Stream7->CR &=~(1<<9);//设置外设地址是固定模式
    DMA2_Stream7->CR |=1<<10;//设置存储器地址是增量模式
    DMA2_Stream7->CR &=~(3<<11);//设置外设数据宽度为8位
    DMA2_Stream7->CR &=~(3<<13);//设置存储器数据宽度为8位

}

void DMA2Stream7_EnableTransfer()
{
    DMA2_Stream7->CR |=(1<<0);//通过将 DMA_SxCR 寄存器中的 EN 位置 1 激活数据流。
    while(!(DMA2->HISR&(1<<27))); //阻塞判断数据是否发送完成
    DMA2->HIFCR |= 1<<27; //清零
}
View Code

1.6.1 dma.h

#ifndef DMA_H
#define DMA_H
#include "stm32f4xx.h"
void DMA2Stream7CH4_Init();
void DMA2Stream7_EnableTransfer();
#endif
View Code

1.6.1 usart1.h

#ifndef USART1_H
#define USART1_H
#include "stm32f4xx.h"
#include "stdio.h"
void Usart1_Init(u32 baudRate);
void Usart_Send_Str(char * dat);
#endif
View Code

 

posted @ 2019-10-18 18:19  千浦千钰  阅读(1306)  评论(1编辑  收藏  举报