STM32直接存储器访问DMA

发布者:huanran最新更新时间:2021-11-03 来源: eefocus关键字:STM32  DMA 手机看文章 扫描二维码
随时随地手机看文章

第一次接触DMA是在学校学习ARM9裸板程序的时候,想起来都时隔快2年了。现在来看看STM32平台的DMA,一样,在标准外设库的支持下,STM32的DMA编程十分简单,但是既是学习,那还是花点时间看看DMA的相关概念及原理的了解下。


1. DMA简介

DMA是Direct Memory Access的简称,是直接存储器访问的意思。DMA是STM32单片机的外设之一,主要功能是用来搬移数据的。通过DMA搬移数据不需要CPU直接参与控制,也不需要中断处理方式那样保留现场和恢复现场。在传输数据的时候,CPU可以干其他事情。


无使用DMA的数据传输:

这里写图片描述

使用DMA后的数据传输:

这里写图片描述

DMA数据传输支持从外设到存储器、存储器到外设、存储器到存储器(这里所讲的存储器可以是SRAM,也可以是FLASH)。DMA控制器包含了DMA1控制器和DMA2控制器,分别由7和5个通道作为数据传输。每个通道专门用来管理来自一个或者多个外设对存储器访问的请求,还有一个仲裁器用于协调各个外设对DMA传输请求的优先权。注意,DMA2只存在于大容量或互联型的STM32单片机中。


2. DMA功能框图

这里写图片描述

2.1 STM32外设对DMA的请求及通道

请求及通道对应图中的标号1和标号2:STM32外设想要通过DMA来传输数据,需先给DMA控制器发送DMA请求,控制器在收到外设的DMA请求之后会给外设一个应答信号,外设应答且DMA控制器收到外设的应答后,DMA启动传输,直至传输完毕。

为什么需要发出请求,应答和接收应答这几个繁琐的步骤?由图中蓝色框框可以看出,DMA传输和CPU是共用系统总线的,要启动DMA传输的前提是系统总线是空闲的,换句话说是CPU没有占用系统总线,所以启动DMA传输前需要以上几个应答机制,其最底层是DMA控制器和CPU正为系统总线作出协调。DMA1有7个通道,DMA2有5个通道,不同的外设请求要通过对应的DMA通道发给DMA控制器。将不同的外设请求传输至对应的通道,这个是我们在软件编程上设置的。


DMA1开放的通道及对应请求:

这里写图片描述

DMA2开放的通道及对应请求:

这里写图片描述

虽然每个通道可以接收多个外设的请求,但是同一时间内只能接收一个。


2.2 仲裁器

仲裁器对应图中的标号3:当DMA控制器的多个通道发生DMA请求时,就需要仲裁器管理响应处理的顺序。仲裁器通过软件和硬件来管理DMA请求:软件指的是我们写的代码,在DMA_CCRx(x指通道号)寄存器中设置,有4个等级,非常高(DMA_Priority_VeryHigh)、高(DMA_Priority_High)、中(DMA_Priority_Medium)和低(DMA_Priority_Low)。硬件则是指若有两个或以上的DMA通道请求设置的优先级一样,则它们的响应顺序取决于通道编号,编号低者优先级高,在有DMA2的STM32中,DMA1控制器拥的响应优先级高于DMA2。


2.3 配置DMA控制器

配置DMA控制器,无非就是下图这几个寄存器:

这里写图片描述

前面说到,DMA数据传输机制并不需要CPU的参与,但是DMA控制器要正常工作,数据要正确传输,需有三个必要条件:源地址、目的地址和数据大小,对于数据分批传输的情况,数据大小这个条件还包含每次传输的大小及单位。


(1)源地址和目的地址

DMA的传输数据的方向有三个:从外设到存储器、从存储器到外设、从存储器到存储器。DMA_CCR的BIT[4]DIR就是用于配置数据传输方向的:

这里写图片描述

取值为0表从外设到存储器,取值为1表从存储器到外设。外设地址在DMA_CPAR寄存器配置,存储器地址在DMA_CMAR寄存器配置。

这里写图片描述

(2)传输数据的大小及单位

以串口向电脑发送数据为例(存储器->外设方向),开发板软件可以一次性给电脑发送大量数据,具体多少在DMA_CNDTR配置:

这里写图片描述

DMA_CNDTR低16位有效,一次最多只能传输65535个数据。

数据要正确传输,源、目标存储的数据宽度必须一致。串口数据寄存器是8位的,也就是外设数据宽度设置寄存器DMA_CCRx的BIT[9:8]PSIZE取值为0:

这里写图片描述

存储器的数据宽度设置寄存器DMA_CCRx的BIT[11:10]MSIZE取值也为0:

这里写图片描述

DMA传输数据,还需要设置源地址上的数据发送指针和目的地址数据存放指针的增量模式。开发板串口向电脑发送数据,假设要发送的数据很多,那么存储器(源地址)上数据发送指针每次发送完毕需要加1,而串口数据寄存器则不需要,因为该寄存器只有一个,数据寄存器上的数据传送到电脑后被清空了(就算不清空,数据直接覆盖也没关系)。外设的地址指针增量模式由DMA_CCRx的PINC配置,存储器的地址指针则由MINC配置。

这里写图片描述

(3)传输结束

DMA中断状态寄存器DMA_ISR可以设置每个DMA通道传输过半、传输完成和传输错误示产生对应标志,

这里写图片描述
这里写图片描述

在DMA_CCRx位1、2、3可以设置发生传输过半、传输完成和传输错误时产生中断:

这里写图片描述

另外补充一点,位0用于使能DMA传输

这里写图片描述

传输完成分两种模式:一次传输和循环传输,一次传输指传输一次后就停止,要再传输需要关闭DMA使能后重新配置后才能继续传输。循环传输则是一次传输完成后又恢复第一次传输时的配置循环传输,如此循环。设置位在DMA_CCRx寄存器的CIRC。


3. DMA功能模块描述结构体

标准库的一贯风格,在stm32f10x_dma.h文件中定于可DMA_InitTypeDef初始化结构体,DMA_Init()函数定义在stm32f10x_dma.c中。


typedef struct

{

  uint32_t DMA_PeripheralBaseAddr;  //外设地址

  uint32_t DMA_MemoryBaseAddr;      //存储器地址

  uint32_t DMA_DIR;                 //传输方向

  uint32_t DMA_BufferSize;          //传输的数据的数目大小

  uint32_t DMA_PeripheralInc;       //外设地址的增量模式

  uint32_t DMA_MemoryInc;           //存储器地址的增量模式

  uint32_t DMA_PeripheralDataSize;  //外设数据宽度

  uint32_t DMA_MemoryDataSize;      //存储器数据宽度

  uint32_t DMA_Mode;                //模式选择

  uint32_t DMA_Priority;            //通道优先级

  uint32_t DMA_M2M;                 //存储器到存储器模式

}DMA_InitTypeDef;


(1)DMA_PeripheralBaseAddr:外设地址,若是存储器到存储器模式,此成员设置为其中一个存储器的地址,否则设置为外设的地址。

(2)DMA_MemoryBaseAddr:存储器地址,一般设置为程序中存放数据的容器(数组)的首地址。

(3)DMA_DIR:传输方向,可设置为外设到存储器,存储器到外设。注意这里没有存储器到存储器的选项,当使用存储器到存储器时,只需要把其中一个存储器当做外设使用。

(4)DMA_BufferSize:设定待传输数据数目。(5)DMA_PeripheralInc:外设地址增量模式,若取值为DMA_PeripheralInc_Enable表使能外设地址自动递增功能。一般外设都是只有一个数据寄存器,所以不会使能该位。

(6)DMA_MemoryInc:若配置为DMA_MemoryInc_Enable表使能存储器地址自动递增功能。一般存储器都是我们自定义的,区域内存放多个数据,所以一般使能该位。

(7)DMA_MemoryDataSize:外设数据宽度,可选8位(字节)、16位(半字)、32位(字)

(8)DMA_MemoryDataSize:存储器数据宽度,可选8位(字节)、16位(半字)、32位(字)

(9)DMA_Mode:传输模式选择,一次传输或循环传输

(10)DMA_Priority:通道优先级设置,非常高、高、中、低可选

(11)DMA_M2M:存储器到存储器模式


4. 编程常用函数

4.1 DMA时钟使能

函数原型:RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState)

使用示例:RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);


4.2 初始化DMA功能模块描述结构体

函数原型:void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)

DMAy_Channelx指定哪一个DMA通道,DMA_InitStruct就是前面解析的描述结构体

使用示例:DMA_Init(DMAy_Channel1, &DMA_InitStruct);


4.3 使能外设DMA发送

以启动DMA发送功能为例:


函数原型:void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState 

NewState)

使用示例:

USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);


外设的DMA传输需要相应的设置,而存储器是不需要的。存储器到存储器,在DMA_InitTypeDef结构体中有DMA_M2M成员需要开启。


4.4 使能DMA通道,开启DMA传输

函数原型:void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

使用示例:DMA_Cmd(DMAy_Channel1, ENABLE);

使能之后,DMA控制器开始工作,在合适的时机(CPU无占据总线)开始DMA控制下的数据传输。


4.5 查询DMA传输状态

在DMA传输过程中,我们可以通过函数来查询传输通道的状态:


函数原型:FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)

假设要查询DMA通道4传输是否完成:

DMA_GetFlagStatus(DMA1_FLAG_TC4);

返回值为RESET表示传输尚未完成,SET表传输完成。


获取当前剩余数据量大小的函数:

函数原型:uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

使用示例,获取DMA通道4还有多少数据没有传输:

DMA_GetCurrDataCounter(DMAy_Channel4);


5. 编程实践

5.1 DMA传输–存储器到存储器模式

硬件平台是正点原子MiniSTM32,板载有两个LED分别为红色和绿色。

程序功能实现把STM32内置的FLASH数据拷贝到内置的SRAM中:定义一个const静态变量为源数据,使用DMA传输将源数据拷贝到目标地址中,比对源数据和目标数据是否相同,若相同亮绿色LED灯,反之亮红色LED灯。

DMA的编程核心在于

(1)使能DMA时钟

(2)配置DMA初始化结构体参数

(3)使能DMA,开始进行数据传输

(4)等待数据传输完成


工程结构为:

这里写图片描述

BSP_LED.c实现配置LED引脚:


#include


void LED_Configuration(void)

{

    GPIO_InitTypeDef GPIO_InitTypeStu;


    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD, ENABLE);


    //³õʼ»¯PA8ÍÆÍìÊä³ö

    GPIO_InitTypeStu.GPIO_Mode = GPIO_Mode_Out_PP;

    GPIO_InitTypeStu.GPIO_Pin = GPIO_Pin_8;

    GPIO_InitTypeStu.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitTypeStu);        

    GREEN_LED_OFF;


    GPIO_InitTypeStu.GPIO_Pin = GPIO_Pin_2;

    GPIO_Init(GPIOD, &GPIO_InitTypeStu);

    RED_LED_OFF;

}


BSP_USART.c实现配置USART1功能:



#include "BSP_USART.h"


#pragma import(__use_no_semihosting)                             

struct __FILE 

    int handle; 


}; 


FILE __stdout;          

_sys_exit(int x) 

    x = x; 


int fputc(int ch, FILE *f)

{      

    while((USART1->SR&0X40) == RESET);

    USART1->DR = (u8) ch;      

    return ch;

}


void NVIC_Configuration(void)

{

    NVIC_InitTypeDef NVIC_InitStu;

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);


    NVIC_InitStu.NVIC_IRQChannel = USART1_IRQn;

    NVIC_InitStu.NVIC_IRQChannelPreemptionPriority = 1;

    NVIC_InitStu.NVIC_IRQChannelSubPriority = 1;

    NVIC_InitStu.NVIC_IRQChannelCmd = ENABLE;


    NVIC_Init(&NVIC_InitStu);

}


void USART_Configuration(void)

{

    GPIO_InitTypeDef GPIO_InitStu;

    USART_InitTypeDef USART_InitStu;


    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);


    GPIO_InitStu.GPIO_Mode = GPIO_Mode_AF_PP;

    GPIO_InitStu.GPIO_Pin = GPIO_Pin_9;

    GPIO_InitStu.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStu);


    GPIO_InitStu.GPIO_Mode = GPIO_Mode_IN_FLOATING;

    GPIO_InitStu.GPIO_Pin = GPIO_Pin_10;

    GPIO_Init(GPIOA, &GPIO_InitStu);


    USART_InitStu.USART_BaudRate = 115200;

    USART_InitStu.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

    USART_InitStu.USART_Parity = USART_Parity_No;

    USART_InitStu.USART_StopBits = USART_StopBits_1;

    USART_InitStu.USART_WordLength = USART_WordLength_8b;

    USART_InitStu.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

    USART_Init(USART1, &USART_InitStu);


    NVIC_Configuration();


    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);



    USART_Cmd(USART1, ENABLE);

}


void USART_SendChar(USART_TypeDef* pUSARTx, uint8_t c)

{

    USART_SendData(pUSARTx, c);


    while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);

}


void USART_SendString(USART_TypeDef* pUSARTx, char* str)

{

    uint32_t n = 0;


    while (*(str + n) != '')

    {

        USART_SendChar(pUSARTx, *(str + n));

        n++;

    }


    while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);

}


以上在前面相关文章都已经写过,下来看BSP_LED.c,实现对DMA功能的配置:


#include


void DMA_Configuration(const uint32_t *SrcBuf, uint32_t *destBuf, uint32_t BUF_SZ)

{

    DMA_InitTypeDef DMA_InitTypeStu;


    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);


    DMA_InitTypeStu.DMA_BufferSize = BUF_SZ;

    DMA_InitTypeStu.DMA_DIR = DMA_DIR_PeripheralDST;

    DMA_InitTypeStu.DMA_M2M = DMA_M2M_Enable;

    DMA_InitTypeStu.DMA_MemoryBaseAddr = (uint32_t)SrcBuf;

    DMA_InitTypeStu.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;

    DMA_InitTypeStu.DMA_MemoryInc = DMA_MemoryInc_Enable;

    DMA_InitTypeStu.DMA_Mode = DMA_Mode_Normal;

    DMA_InitTypeStu.DMA_PeripheralBaseAddr = (uint32_t)destBuf;

    DMA_InitTypeStu.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;

    DMA_InitTypeStu.DMA_PeripheralInc = DMA_PeripheralInc_Enable;

    DMA_InitTypeStu.DMA_Priority = DMA_Priority_VeryHigh;


    DMA_Init(DMA1_Channel1, &DMA_InitTypeStu);


    DMA_Cmd(DMA1_Channel1, ENABLE);

}


uint8_t BufCmp(const uint32_t* pSrc, uint32_t* pDest, uint32_t len)

{

    int i;


    for (i = 0; i < len; i++)

    {

        if (*(pSrc + i) != *(pDest + i))

            return 1;

    }

    return 0;

}


main()函数:


#include

#include

#include


#define BUF_SZ  32


const uint32_t SrcBuf[BUF_SZ] = {0x12, 0x23, 0x45, 0x86, 0x45, 0x63, 0x89, 0x87, 

[1] [2]
关键字:STM32  DMA 引用地址:STM32直接存储器访问DMA

上一篇:STM32的DMA串口直通
下一篇:关于DMA传输外设地址的说明

推荐阅读最新更新时间:2024-11-19 14:57

CRC校验、STM32中CRC计算单元、CRC应用
从这一段时间后台反馈的问题可以看得出来,好些朋友对CRC没有什么概念,今天就在这里讲述一下关于CRC校验、STM32中CRC计算单元相关内容。 1关于CRC校验 CRC:Cyclic Redundancy Check,即循环冗余校验码。 CRC是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。 循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。 ---来自百度百科 学电子、计算机相关专业的同学都应该学习过CRC的基础原理。其原理说难不难,可以说就是一个公式。同时,
[单片机]
CRC校验、<font color='red'>STM32</font>中CRC计算单元、CRC应用
STM32 TIM触发ADC 定时采样-笔记
用TIM3的update触发adc,只需勾选Internal clock就可以了 ADC设置,关键是选TIM3 TRIGGER OUT TIM3 设置时间间隔然后关键选Update event 程序代码 记得开启TIM3 HAL_ADCEx_Calibration_Start(&hadc1); //STM32F1 HAL_TIM_Base_Start(&htim3); HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC_Value,1024); 然后重写callback 函数 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* h
[单片机]
<font color='red'>STM32</font> TIM触发ADC 定时采样-笔记
STM32开发 -- Secure CRT 自动记录日志和时间戳功能配置
使用Secure CRT 接收串口日志,有时需要添加时间戳,并自动记录日志。 该功能如何配置实现? 配置 会话选项- 日志文件 日志文件名,选择要保存的文件路径和文件名 选择,追加到文件 在每一行,填写
[单片机]
<font color='red'>STM32</font>开发 -- Secure CRT 自动记录日志和时间戳功能配置
STM32F1 _DMA_USART
前言 今天总结“STM32F103DMA_USART”,DMA学习过计算机人都明白它是什么意思,就是直接存储器存取(Direct Memory Access),很多人都知道它的意思,但基本上不知道的怎么用,或者不知道它的用途。其实DMA在计算机里面是非常重要的,它可以减少CPU的资源。大家都用U盘拷贝过数据,这就是很常见的DMA应用。假如不适用DMA传输数据,我们拷贝大文件的时候,我们的电脑操作其他应用程序的时候估计会把电脑卡死。 今天就总结一下DMA常用于串口通信的例子,实例源代码请自行下载。由于时间有限,只讲解了DMA发送及中断,接收这一块还没有来得及整理,后期我将以实例讲解,请持续关注。 下载 提供下载的“软件工程”
[单片机]
STM32F1 _<font color='red'>DMA</font>_USART
stm32读取DS2411
DS2411的读写时序和DS18B20一致,毕竟同一个公司出的。 下面是代码 源文件 ds2411.c #include ds2411.h #include delay.h #include usart.h // // //复位DS2411 void DS2411_Rst(void) { DS2411_IO_OUT(); //SET PA11 OUTPUT DS2411_DQ_OUT=0; //拉低DQ delay_us(750); //拉低750us DS2411_DQ_OUT=1; //DQ=1 delay_us(15); //15US } //等待D
[单片机]
STM32-蜂鸣器实验
一,蜂鸣器介绍 蜂鸣器分有源蜂鸣器和无源蜂鸣器,这里的源指的是震荡源 一般我们使用有源蜂鸣器,如图:   有两个引脚,较长的一端为正极,较短的一段为负极 二,蜂鸣器硬件连接: 蜂鸣器负极连接在STM32的PB8引脚: 错误的连接方法: IO口接蜂鸣器接地 错误的臆想: IO口输出高电平,蜂鸣器正负极产生电压差,蜂鸣器发出声响 错误分析: STM32IO口电流驱动能力非常有限,不能通过IO口驱动大功率硬件,需要通过三极管来驱动(三极管的放大作用),将IO口输出的小电流放大为大电流(放大倍数β) R38作用: STM32芯片复位后IO口默认为浮空状态,IO口电平是不确定的,有可能跳边的电流经过三极管被放大使蜂鸣器
[单片机]
STM32-蜂鸣器实验
stm32专题十七:深度解析 stm32 硬件iic (i2c)
首先是配置I2C的GPIO,然后配置I2C参数。就是常规配置,按流程来写不会错。 /** * @brief EEPROM IIC 配置 */ void I2C_EE_config(void) { GPIO_InitTypeDef GPIO_InitStruct; I2C_InitTypeDef I2C_InitStruct; // 开启I2C GPIO时钟 EPROM_I2C_GPIO_APBxClkCmd(EEPROM_I2C_SCL_GPIO_CLK | EEPROM_I2C_SDA_GPIO_CLK, ENABLE); // 开启I2C 外设时钟 EEPROM_I2C_APBxC
[单片机]
<font color='red'>stm32</font>专题十七:深度解析 <font color='red'>stm32</font> 硬件iic (i2c)
STM32 CRC ST库3.0.0
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState) 使能或者失能AHB外设时钟 Enables or disables the AHB peripheral clock. 例如: 使能CRC硬件校验 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE); ============================================================================ uint32_t CRC_CalcBlockCRC(uint32_t p
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved