STM32F407ZG DMA

STM32F407ZG开发板学习(11)

  • 直接存储器访问 DMA
    • 简介
    • 原理及框图
    • 寄存器
      • 中断状态寄存器 LISR HISR
      • 中断标志清零寄存器 LIFCR HIFCR
      • 数据流 x 配置寄存器 DMA_SxCR (x = 0..7)
      • 数据 x 数据项数寄存器
      • 数据流 x 外设地址寄存器 DMA_SxPAR (x = 0..7)
      • 数据流 x 存储器地址寄存器
      • 数据流 x FIFO 控制寄存器 SxFCR (x = 0..7)
  • 实验
    • DMA1 配置步骤
    • 代码
    • 实验结果

直接存储器访问 DMA

简介

直接存储器访问(Direct Memory Access,DMA)。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。
STM32F4最多有 2 个 DMA 控制器( DMA1 和 DMA2), 共 16 个数据流(每个控制器 8 个), 每
一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。

STM32F407ZG DMA_第1张图片
STM32F407ZG DMA_第2张图片

原理及框图

STM32F407ZG DMA_第3张图片
因为采用AHB主总线,DMA 可以控制 AHB 总线矩阵来启动 AHB 事务。它可以执行下列事务:

  1. 外设到存储器的传输
  2. 存储器到外设的传输
  3. 存储器到存储器的传输

这里特别注意一下,存储器到存储器需要外设接口可以访问存储器,而仅DMA2的外设接口可以访问存储器,所以仅DMA2控制器支持存储器到存储器的传输,DMA1不支持。

如图所示,经过通道选择和仲裁器,我们最多可以有8×8=64种组合。其中通道选择由下图所示。
STM32F407ZG DMA_第4张图片
两个DMA的各个通道映射不一致,实验中我使用了USART3的发送,它在DMA1中,如下图:
STM32F407ZG DMA_第5张图片

寄存器

中断状态寄存器 LISR HISR

分别管理8个通道的中断标志,两个寄存器是只读的。
STM32F407ZG DMA_第6张图片
STM32F407ZG DMA_第7张图片

中断标志清零寄存器 LIFCR HIFCR

由于ISR是只读的,需要采用其他方法清零,因此这里使用另两个中断标志清零寄存器,来对ISR的各位进行清除。

STM32F407ZG DMA_第8张图片
STM32F407ZG DMA_第9张图片

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

DMA_ SxCR 是 DMA 传输的核心控制寄存器。该寄存器控制着 DMA 的很多相关信息,包括数据宽度、外设及存储器的宽度、优先级、增量模式、传输方向、中断允许、使能等。
详情见官方文档。

STM32F407ZG DMA_第10张图片

数据 x 数据项数寄存器

这个寄存器控制 DMA 数据流 x 的每次传输所要传输的数据量。其设置范围为 0~65535 。并且该寄存器的值会随着传输的进行而减少,当该寄存器的值为 0 的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存器的值来知道当前 DMA 传输的进度。 特别注意,这里是数据项数目,而不是指的字节数。比如设置数据位宽为 16 位,那么传输一次(一个项)就是 2 个字节。

STM32F407ZG DMA_第11张图片

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

该寄存器用来存储 STM32F4 外设的地址。

数据流 x 存储器地址寄存器

包括 数据流 x 存储器 0 地址寄存器 DMA_SxM0AR数据流 x 存储器 1 地址寄存器 DMA_SxM1AR ,后者仅在双缓冲模式下有效。

数据流 x FIFO 控制寄存器 SxFCR (x = 0…7)

STM32F407ZG DMA_第12张图片
STM32F407ZG DMA_第13张图片
STM32F407ZG DMA_第14张图片

实验

DMA1 配置步骤

  1. 使能 DMA1 时钟。另外,要对配置寄存器( DMA_SxCR )进行设置,必须先等待其最低位为 0 (也就是 DMA 传输禁止了),才可以进行配置。
  2. 创建 DMA_InitTypeDef 类型的结构体初始化 DMA1 数据流 3 (USART3_TX)。
  3. 使能串口 USART3 的 DMA 发送。USA RT_DMACmd(USART3, USART_DMAReq_Tx, ENABLE);
  4. 使能 DMA1 的 数据流3 。即 DMA_Cmd(DMA1_Stream3, ENABLE);
  5. 在DMA 传输过程中,查询 DMA 传输通道的状态,使用的函数有:
    是否传输完成:FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
    当前剩余数据量:uint16_t DMA_GetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx);
    设置数据流传输的数据量:void DMA_SetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx, uint16_t Counter);

代码

dma.c

/*
    函数名:配置DMA
    参数:DMAy_Streamx 选择数据流 DMA1_Stream0~7/DMA2_Stream0~7
          channelx 通道选择 DMA_Channel_0~7
          par 外设地址
          mar 存储器地址
          ndtr 数据传输量
    返回值:无
*/
void mydma_config(DMA_Stream_TypeDef *DMAy_Streamx, uint32_t channelx, uint32_t par, uint32_t mar, uint16_t ndtr)
{
    DMA_InitTypeDef DMA_InitStructure;
    
    //根据参数使能DMA1/DMA2时钟
    if((uint32_t)DMAy_Streamx > (uint32_t)DMA2)
    {
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
    }
    else {
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
    }
    
    DMA_DeInit(DMAy_Streamx);
    //等待到可以配置
    while(DMA_GetCmdStatus(DMAy_Streamx) != DISABLE);
    
    DMA_InitStructure.DMA_BufferSize = ndtr; //数据量
    DMA_InitStructure.DMA_Channel = channelx; //DMA通道选择
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //存储器到外设模式
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //FIFO禁止
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; //FIFO阈值水平
    DMA_InitStructure.DMA_Memory0BaseAddr = mar; //存储器0地址基址
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存储器单词传输
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据长度8位
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器增量模式
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //普通模式
    DMA_InitStructure.DMA_PeripheralBaseAddr = par; //外设地址
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外设单次传输
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据长度8位
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设非增量模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级
    
    DMA_Init(DMAy_Streamx, &DMA_InitStructure);
}

void mydma_enable(DMA_Stream_TypeDef *DMAy_Streamx, uint16_t ndtr)
{
    DMA_Cmd(DMAy_Streamx, DISABLE);
    while(DMA_GetCmdStatus(DMAy_Streamx) != DISABLE);
    DMA_SetCurrDataCounter(DMAy_Streamx, ndtr);
    DMA_Cmd(DMAy_Streamx, ENABLE);
}

main.c

#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "dma.h"

#define SEND_BUF_SIZE 24000
uint8_t send_buf[SEND_BUF_SIZE];
const uint8_t send_str[] = "八个细胞 DMA 串口实验";

int main(void)
{
    uint8_t t = 0, mask = 0;
    uint8_t len;
    uint16_t i;
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置系统中断优先级分组2
    delay_init(168);		//延时初始化
    usart_init(); //串口初始化波特率为115200
    mydma_config(DMA1_Stream3, DMA_Channel_4, (uint32_t)&USART3->DR, (uint32_t)send_buf, SEND_BUF_SIZE);
    KEY_Init();
    LED_Init(); //初始化与LED连接的硬件接口
    
    len = sizeof(send_str);
    for(i=0; i < SEND_BUF_SIZE; i++) //填充 ASCII 字符集数据
    {
        if(t >= len) //加入换行符
        {
            if(mask)
            {
                send_buf[i] = 0x0a;
                t=0;
            }
            else
            {
                send_buf[i] = 0x0d;
                mask++;
            }
        }
        else
        {
            mask = 0;
            send_buf[i] = send_str[t];
            t++;
        }
    }
    
    i = 0;
    mask = 0;
    while(1)
    {
        t = KEY_Scan(0);
        if(t == KEY0_PRES)
        {
            LED0 = 0;
            USART_DMACmd(USART3, USART_DMAReq_Tx, ENABLE); //使能串口 3 的 DMA 发送
            mydma_enable( DMA1_Stream3, SEND_BUF_SIZE); //开始一次 DMA 传输!
            while(1)
            {
                if(DMA_GetFlagStatus(DMA1_Stream3, DMA_FLAG_TCIF3) != RESET)
                {
                    DMA_ClearFlag(DMA1_Stream3, DMA_FLAG_TCIF3);
                    LED1 = 1;
                    break;
                }
                mask++;
                delay_ms(10);
                if(mask == 20)
                {
                    LED1 = !LED1;
                    mask = 0;
                }
            }
        }
        i++;
        delay_ms(10);
        if(i == 20)
        {
            LED0 = !LED0;
            i = 0;
        }
        
	}
}

实验结果

当按下一次按钮0时,使能一次 DMA 发送到串口,发送期间 LED0 常亮,LED1 闪烁,非发送时间 LED0 闪烁,LED1 熄灭。
串口助手接收如下:
STM32F407ZG DMA_第15张图片

你可能感兴趣的:(STM32学习,stm32,单片机,arm)