8-DMA直接存储器读取

发布时间:2023年12月24日

DMA直接存储器可以直接访问STM32内部的存储器,包括外设寄存器(一般指外设的数据寄存器DR,如ADC的数据寄存器、串口数据寄存器等)、运行内存SRAM(存储运行变量)、程序存储器FLASH(存储程序代码)等。DMA可以提供 外设寄存器和存储器 或者 存储器和存储器之间 的高速数据传输,无须CPU干预,节省了CPU的资源。翻译成人话就是,是一个数据转运小助手,主要用来协助CPU完成数据转运的工作。下面是stm32中DMA的一些配置:

  • stm32系列芯片共有12个独立可配置的通道:DMA1(7个通道),DMA2(5个通道)。
  • 每个通道都支持软件触发和特定的硬件触发。
  • 软件触发应用场景:数据源中的数据已确定。如将FLASH中的数据转运到SRAM中(存储器–>存储器),一次触发后会将数据以最快的速度全部转运完毕。
  • 硬件触发应用场景:数据源中的数据没有全部确定,需要在特定的时机转运数据。如将ADC数据转运到存储器(外设寄存器–>存储器),等对应ADC通道的数据转换完成后,才硬件触发一次DMA转运数据。
  • STM32F103C8T6型号的DMA资源:DMA1(7个通道)。

存储映像:

计算机有五大组成部分:运算器,控制器,存储器,输入设备,输出设备。计算机的核心关键部分就是CPU和存储,上述的运算器和控制器回合在一起组成CPU,而存储器主要关心存储器的内容和地址?。

  • ?程序存储器FLASH:下载程序的位置,程序一般也是从主闪存里开始运行。若某变量地址为0x0800_xxxx,那么它就是属于主闪存的数据。
  • 系统存储器:存储BootLoader程序(俗称“刷机”),芯片出厂时自动写入,一般不允许修改。
  • 选项字节:存储的主要是FLASH的读保护、写保护、看门狗等配置。下载程序可以不刷新选项字节的内容,从而保持相应配置不变。
  • 运行内存SRAM:在程序中定义变量、数组、结构体的地方,类似于电脑的内存条。
  • 外设寄存器:初始化各种外设的过程中,最终所读写的寄存器就属于这个区域。
  • 内核外设寄存器:就是NVIC和SysTick。由于不是同一个厂家设计,所以专门留出来内核外设的地址空间,和其他外设的地址空间不一样。
  • 注:由于stm32是32位的系统,所以寻址空间最大可达4GB(每个地址都代表1Byte),而stm32的存储器硬件最多也就是KB级别的,所以实际上4GB的寻址空间使用率远远低于1%。
    注:上表中前三者存储介质都是FLASH,但是一般说“FLASH”就是代指“主闪存FLASH”,而不是另外两块区域。
    注:上表中后三者存储介质也是SRAM,但是一般将“SRAM”就是代指“运行内存”,“外设寄存器”就直接叫“寄存器”。
    ?

DMA电路框图:?

将从以下加粗的四大部分介绍DMA的电路结构。

  • 总线矩阵: 为了高效有条理的访问存储器,设置了总线矩阵。左端是主动单元,拥有存储器的访问权;右端是被动单元,它们的存储器只能被左端的主动单元读写。
  • 总线矩阵内部的仲裁器:如果DMA和CPU都要访问同一个目标,那么DMA就会暂停CPU的访问,以防止冲突。但是总线仲裁器仍然会保证CPU得到一半的总线带宽,以确保CPU正常工作。
  • 主动单元:
  1. Cortex-M3核心(左上角):包含了CPU和内核外设。剩下的所有东西都可以看成是存储器,比如Flash是主闪存、SRAM是运行内存、各种外设寄存器也都可以看成是一种SRAM存储器。
  2. ICode总线:指令总线。加载程序指令。
  3. DCode总线:数据总线,专门用来访问Flash。
  4. 系统总线:是访问其他东西的。
  5. DMA总线:用于访问各个存储器,包括DMA1总线(7个通道)、DMA2总线(5通道)、以太网外设的私有DMA总线。由于DMA要转运数据,所以DMA也有访问的主动权。
  6. DMA1、DMA2:各个通道可以分别设置转运数据的源地址和目的地址,所以各个通道可以独立的进行数据转运。
  • 仲裁器:调度各个通道,防止产生冲突。虽然多个通道可以独立地转运数据,但是DMA总线只有一条,所以所有的通道都只能 分时复用 这一条DMA总线,若通道间产生冲突,就会由仲裁器根据通道的优先级决定使用顺序
  • AHB从设备:用于配置DMA参数,也就是DMA自身的寄存器。DMA的外设配置寄存器直接连接在了被动单元侧的AHB总线上。所以DMA既是总线矩阵上的主动单元,可以读写各种寄存器;同时也是AHB总线上的被动单元。CPU配置DMA的线路:“系统”总线–>总线矩阵–>AHB总线–>DMA中的AHB从设备。
  • 被动单元:
  1. Flash:主闪存,只读存储器。若直接通过总线访问(无论是CPU还是DMA),都只能读取数据而不能写入。若DMA的目的地址为FLASH区域,那么转运就会出错。要想对Flash写入,可以通过“Flash接口控制器”。
  2. SRAM:运行内存,通过总线可以任意读写。
  3. 各种外设寄存器(右侧两个方框):需要对比参考手册中的描述,这些寄存器的类型可能为 只读/只写/读写。不过日常主要使用的数据寄存器,都是可以正常读写的。
  4. DMA请求: 用于硬件触发DMA的数据转运。“请求”就是“触发”的意思,此线路右侧的触发源是各个外设,所以这个“DMA请求”就是 DMA的硬件触发源,如ADC转换完成、串口接收到数据等信号。

?

所以寄存器是一种特殊的存储器,寄存器的两大作用:

  1. 存储数据。被CPU或DMA等读写,就像读写运行内存一样。
  2. 控制电路。寄存器的每一位都接了一个导线,可以用于控制外设电路的状态,比如置引脚的高低电平、导通或断开开关、切换数据选择器,或者多位结合起来当做计数器、数据寄存器等。

?

所以寄存器是连接软件和硬件的桥梁,软件读写寄存器,就相当于在控制硬件的执行。既然外设相当于寄存器,寄存器又是存储器,那么使用DMA转运数据,本质上就是从某个地址取数据,再放到另一个地址去。?

例1:DMA数据转运

?

任务是将SRAM数组DataA转运到另一个SRAM数组DataB(存储器到存储器)。

  • 下面给出各参数的配置说明:
  • 两个站点的参数:外设地址->DataA数组首地址、存储器地址->DataB数组首地址;数据宽度都是8位;为保证数据的一一对应,“外设”和“存储器”都设置为地址自增。
  • “方向”参数:默认是“外设”–>“存储器”,当然也可以将方向反过来。
  • 传输计数器:数组大小为7,所以计数器为7。
  • 自动重装:不需要。
  • 触发源:软件触发。由于是“存储器->存储器”的触发,所以不需要等待转运时机。
  • 开关控制:最后调用DMA_Cmd开启DMA转运。
  • 注:上述为“复制转运”,转运完成后DataA数据不会消失。

?

例2:ADC扫描模式+DMA

期望将外设ADC多通道(扫描模式)的数据,依次搬运到SRAM中的ADValue数组中(外设到存储器)。
注意下图左侧给出了ADC的扫描模式示意图,ADC每触发一次,7个通道依次进行ADC数据转换,每个通道转换完成时,都会将转换结果放到ADC_DR数据寄存器中,也就是ADC的所有通道共用一个ADC数据寄存器。所以每个通道转换完成后,都需要DMA立即转运一次,防止新来的通道数据将之前的通道数据覆盖掉。
?

?

  • 下面给出各参数的配置说明:
  • 起始地址:“外设站点”地址设置为ADC_DR寄存器的地址;“存储器站点”地址为ADValue(SRAM)的首地址。
  • 数据宽度:“外设站点”和“存储器站点”都设置为16位(HalfWord)。
  • 地址自增:“外设站点”不自增,“存储器站点”自增。
  • 方向:“外设”站点为发送端。
  • 传输计数器:按照ADC需要扫描的通道数来,即为7。
  • 计数器是否自动重装:ADC单次扫描,可以不自动重装;ADC连续扫描,DMA就使用自动重装,此时ADC启动下一轮转换,DMA同时也启动下一轮转运,可以实现同步工作。
  • 触发选择:选择硬件触发——ADC单通道转换完成。虽然前面说过,只有当所有通道都转换完成后,才会触发转换完成EOC标志,其余时间没有任何中断/标志,但实际上,硬件中保留了单个通道针对DMA的请求(虽然参考手册只字不提)。

?

实验1:DMA数据转运-存储器到存储器?

?

main.c

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "DMA_User.h"
#include "Delay.h"

uint8_t DataA[] = {0x11,0x22,0x33,0x44};//源端数组
uint8_t DataB[] = {0x00,0x00,0x00,0x00};//目的端数组

    
int main(void){
    //OLED显示屏初始化
    OLED_Init();
    OLED_ShowString(1,1,"DataA:");
    OLED_ShowHexNum(1,7,(uint32_t)DataA,8);
    OLED_ShowHexNum(2,1,DataA[0],2);
    OLED_ShowHexNum(2,4,DataA[1],2);
    OLED_ShowHexNum(2,7,DataA[2],2);
    OLED_ShowHexNum(2,10,DataA[3],2);
    OLED_ShowString(3,1,"DataB:");
    OLED_ShowHexNum(3,7,(uint32_t)DataB,8);
    OLED_ShowHexNum(4,1,DataB[0],2);
    OLED_ShowHexNum(4,4,DataB[1],2);
    OLED_ShowHexNum(4,7,DataB[2],2);
    OLED_ShowHexNum(4,10,DataB[3],2);
    
//    //验证存储器映像-注意srm32中地址都是32位的
//    uint8_t aa = 0x66;//存储在运行内存SRAM中
//    const uint8_t bb = 0x55;//存储在Flash中
//    OLED_ShowHexNum(1,1,aa,2);
//    OLED_ShowHexNum(1,4,(uint32_t)&aa,8);//SRAM地址0x2000开头
//    OLED_ShowHexNum(2,1,bb,2);
//    OLED_ShowHexNum(2,4,(uint32_t)&bb,8);//Flash地址0x0800开头
//    OLED_ShowHexNum(3,1,(uint32_t)&ADC1->DR,8);//ADC外设寄存器地址
    
    //DMA 初始化
    DMA_User_Init((uint32_t)&DataA, (uint32_t)&DataB, 4);
    
    while(1){
        //改变数据并显示
        DataA[0]++;
        DataA[1]++;
        DataA[2]++;
        DataA[3]++;
        OLED_ShowHexNum(2,1,DataA[0],2);
        OLED_ShowHexNum(2,4,DataA[1],2);
        OLED_ShowHexNum(2,7,DataA[2],2);
        OLED_ShowHexNum(2,10,DataA[3],2);
        OLED_ShowHexNum(4,1,DataB[0],2);
        OLED_ShowHexNum(4,4,DataB[1],2);
        OLED_ShowHexNum(4,7,DataB[2],2);
        OLED_ShowHexNum(4,10,DataB[3],2);
        Delay_ms(1000);
        
        //转运数据并显示
        DMA_User_Transfer();
        OLED_ShowHexNum(2,1,DataA[0],2);
        OLED_ShowHexNum(2,4,DataA[1],2);
        OLED_ShowHexNum(2,7,DataA[2],2);
        OLED_ShowHexNum(2,10,DataA[3],2);
        OLED_ShowHexNum(4,1,DataB[0],2);
        OLED_ShowHexNum(4,4,DataB[1],2);
        OLED_ShowHexNum(4,7,DataB[2],2);
        OLED_ShowHexNum(4,10,DataB[3],2);
        Delay_ms(1000);
    };
}

?DMA_User.h

#ifndef __DMA_USER_H
#define __DMA_USER_H

void DMA_User_Init(uint32_t AddrA, uint32_t AddrB, uint16_t BuffSize);
void DMA_User_Transfer(void);

#endif

- DMA_User.c

#include "stm32f10x.h"                  // Device header

uint16_t DMA_User_BuffSize;//传输计数器

//DMA初始化-DMA1_Channel1-从AddrA到AddrB转运,不使能
void DMA_User_Init(uint32_t AddrA, uint32_t AddrB, uint16_t BuffSize){
    
    DMA_User_BuffSize = BuffSize;
    
    //1.开启RCC
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    //2.初始化DMA
    DMA_InitTypeDef DMA_InitStructure;
    DMA_StructInit(&DMA_InitStructure);
    DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Enable;
    DMA_InitStructure.DMA_MemoryBaseAddr     = AddrB;
    DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_BufferSize         = BuffSize;//传输计数器
    DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralSRC;//方向:外设作为源端
    DMA_InitStructure.DMA_M2M                = DMA_M2M_Enable;//软件触发
    DMA_InitStructure.DMA_Mode               = DMA_Mode_Normal;//是否使用自动重装
    DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;//DMA多通道才会用到
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    //3.开关控制使能
    DMA_Cmd(DMA1_Channel1, DISABLE);
    //4.开启硬件触发源(按需求选做)
}

//手动触发一次DMA的转运
void DMA_User_Transfer(void){
    //触发转运
    DMA_Cmd(DMA1_Channel1, DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1,DMA_User_BuffSize);
    DMA_Cmd(DMA1_Channel1, ENABLE);
    //判断转运完成,并清除相应标志位
    while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
    DMA_ClearFlag(DMA1_FLAG_TC1);
    DMA_Cmd(DMA1_Channel1, DISABLE);
}

实验2:DMA+AD多通道-外设到存储器

?

??main.c

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "ADC_User.h"
    
int main(void){
    uint16_t ADC_value[4] = {0,0,0,0};
    //OLED显示屏初始化
    OLED_Init();
    OLED_ShowString(1,1,"C1:+0.00V");
    OLED_ShowString(2,1,"C2:+0.00V");
    OLED_ShowString(3,1,"C3:+0.00V");
    OLED_ShowString(4,1,"C4:+0.00V");
    
    //ADC扫描模式初始化
    ADC_User_InitMuti((uint32_t)&ADC_value,4);
    ADC_User_Start();
    
    while(1){    
        OLED_ShowFloat(1,4,(float)ADC_value[0]*3.3/4095,1,2);
        OLED_ShowFloat(2,4,(float)ADC_value[1]*3.3/4095,1,2);
        OLED_ShowFloat(3,4,(float)ADC_value[2]*3.3/4095,1,2);
        OLED_ShowFloat(4,4,(float)ADC_value[3]*3.3/4095,1,2);
    };
}

ADC_User.c新增函数

//ADC多通道初始化-ADC1的通道0~3-PA0~PA3共四个通道
void ADC_User_InitMuti(uint32_t AddrB, uint16_t BuffSize){
    //1.开启外设时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);//6分频使得ADC时钟为12MHz
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    //2.配置GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AIN;//模拟输入
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    //3.配置ADC多路开关,选择通道进入规则组
    ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_1Cycles5);
    ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_1Cycles5);
    ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_1Cycles5);
    ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_1Cycles5);
    //4.配置ADC转换器
    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_DataAlign          = ADC_DataAlign_Right;//数据右对齐
    ADC_InitStructure.ADC_ExternalTrigConv   = ADC_ExternalTrigConv_None;//不使用外部触发(软件触发)
    ADC_InitStructure.ADC_Mode               = ADC_Mode_Independent;//独立模式
    ADC_InitStructure.ADC_NbrOfChannel       = BuffSize;//通道总数(非扫描模式,此参数不起作用)
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换
    ADC_InitStructure.ADC_ScanConvMode       = ENABLE;//扫描模式
    ADC_Init(ADC1, &ADC_InitStructure);
    //5.配置ADC开关控制
    ADC_Cmd(ADC1, ENABLE);
    //6.进行ADC校准
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1)==SET);
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1)==SET);
    //7.开启ADC1硬件触发源
    ADC_DMACmd(ADC1, ENABLE);
    //8.配置DMA
    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryBaseAddr     = AddrB;//(uint32_t)ADC_Value;
    DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_BufferSize         = BuffSize;//传输计数器
    DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralSRC;//方向:外设作为源端
    DMA_InitStructure.DMA_M2M                = DMA_M2M_Disable;//硬件触发
    DMA_InitStructure.DMA_Mode               = DMA_Mode_Circular;//是否使用自动重装
    DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;//DMA多通道才会用到
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    //9.DMA开关控制使能
    DMA_Cmd(DMA1_Channel1, ENABLE);

}
//记得在头文件中声明

文章来源:https://blog.csdn.net/hvbkbb/article/details/135163834
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。