DMA

发布时间 2023-08-27 11:29:10作者: 磕伴

存储器到存储器一般使用软件触发,外设到存储器用硬件触发(特定硬件)

存储器映像

运行从主闪存Flash中开始

选项字节:刷新程序时可以保持不变,存的主要是同Flash的读保护、写保护,看门狗等

 内核外设:NVIC 和  SysTick

DMA框图

总线矩阵左边是主动单元,右边是被动单元

DCode总线专门访问Flash(CPU或DMA直接访问的话一般是只读的,但可以通过配置其接口控制器进行写入【对FLASH按页进行擦除,再写入】),系统总线访问其他

DMA每个通道都可以设置他们的源地址和目的地址,仲裁器根据优先级决定哪个通道使用唯一的一条DMA总线

总线上也有一个仲裁器,如果DMA和CPU都要访问同一个目标,那么DMA就会暂停CPU的访问。以防冲突,不过总线仲裁器。仍然会保证CPU得到一半的总线带宽,使CPU正常工作

AHB从设备,是DMA自身的寄存器,这样就可以被CPU配置,既是主动单元也是被动单元;DMA的硬件触发源可触发DMA,比如APB2里面的外设数据准备完成就触发DMA执行数据转运

 

数据宽度分为8(Byte)  16(HalfWord)   32位  (Word)

外设寄存器(只是个名字,这里面的配置可以是外设也可以是存储器【flash  SRAM】) 反正两个地址转运数据    怎么设置都行

传输计数器:自减 (写几就转运计次,为0时就不再转运,而且自增的地址也会恢复到起始位置)

自动重装器:计数器值到0后是否恢复到最初设置给计数器的值(比如计数器从5计数到0,是结束呢还是重新让他从5再次计数)

M2M  存储器到存储器,

给M2M位置1时,选择软件触发(以最快的速度,连续不断触发DMA,将计数值清零,完成一轮转换),不能和循环模式一起用,一般用在存储器到存储器

0硬件触发,源可以选择ADC  定时器  串口等,与外设有关,这些转运需要一定的时机 

三个条件:CMD使能,计数器大于0,有触发条件。 当计数器等于0且没有自动重装时,无论是都触发都不再转运,此时需要CMD关闭DMA,再给计数器写个值,再次启动DMA

每个通道的硬件触发源都不一样(特定的硬件触发),软件触发的话都一样可任意选择

自动重装与软件触发不能同时使用

#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size;

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
    MyDMA_Size = Size;
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    DMA_InitTypeDef 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_DIR = DMA_DIR_PeripheralSRC;//传输方向
    DMA_InitStructure.DMA_BufferSize = Size; //缓冲区大小:计数器
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//是否使用自动重装
    DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; // 是否软件触发(否是硬件触发)
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_Init(DMA1_Channel1, &DMA_InitStructure); //选择DMAx的Channelx
    
    DMA_Cmd(DMA1_Channel1, DISABLE);
}

void MyDMA_Transfer(void)
{
    DMA_Cmd(DMA1_Channel1, DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
    DMA_Cmd(DMA1_Channel1, ENABLE);
    
    while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
    DMA_ClearFlag(DMA1_FLAG_TC1);
}
View Code

DMA转运的三个条件:计数器不为0,触发源有触发信号(软件触发或硬件触发),使能

DMA单次运转:RCC DMA_Init   DMA使能 

DMA连续运转:在单次的基础上:DMA失能,重新设置DMA计数器,使能DMA,等待转运完成后清除其标志位(为了下次转运)

若使用ADC1  则有个库函数 ADC1_DMACmd()  用来开启ADC1的这一路触发源  (开启某个外设的DMA输出)

通道号越小,优先级越高,也可以自定义配置

数据宽度与对其

转运双方的数据宽度一样的话就正常转运,不一样的话就看下表(不够就补0,超了就舍弃高位)

 

案例:复制转运,数组数据转运(左自增右不自增的时候)

 

ADC扫描+DMA  (外设地址不自增,存储器地址自增)

在每个单独的通道转换完成后,把其产生在ADC_DR(外设地址)里面的值转运到DMA目的地(存储器地址【可在SRAM中定义一个数组暂存】),并且目的地址自增

触发选择:DMA转运的时机。需要和ADC单个通道转换完成同步,所以选择ADC的硬件触发

单个通道转换完成后没有任何标志位可供查询,但是会触发单通道的DMA请求

 

 这里采用4个ADC通道,依次转换,结果会存在同一个DR寄存器中,然后由DMA及时转运出去(源地址固定,目的地递增)避免数据覆盖

步骤: 开启DMA  ADC和对应的GPIO时钟并配置CLK,初始化4个GPIO口,选择ADC4个通道,ADC初始化,DMA初始化(注意配置时候的硬件关系对照),使能

ADC和DMA都是单次的时候需要每次触发

ADC_InitStructure.ADC_ContinuousConvMode = DISABLE

DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;

当调用这个函数,ADC开始转换,连续扫描4个通道,数值依次写到DR寄存器中,DMA在数据覆盖之前会将每次的数据及时转运出去,目的地自增

都是循环模式的话就自动不用管

uint16_t AD_Value[4];

void AD_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
    
    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);
    
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);//点菜,通道0放在序列1的位置
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
        
    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;  //开启扫描模式(从序列1-序列4)
    ADC_InitStructure.ADC_NbrOfChannel = 4;//前4个序列有效
    ADC_Init(ADC1, &ADC_InitStructure);
    
    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//要DR寄存器低16位的数据
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//源地址地址不变
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//目的地地址自增
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = 4;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//可一般模式,可循环
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //不使用软件触发,使用硬件ADC触发
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_Init(DMA1_Channel1, &DMA_InitStructure); // ADC1硬件固定对应DMA1的通道1
    
    DMA_Cmd(DMA1_Channel1, ENABLE);
    ADC_DMACmd(ADC1, ENABLE);//开启ADC到DMA的输出
    ADC_Cmd(ADC1, ENABLE);
    
    ADC_ResetCalibration(ADC1);  //校准ADC
    while (ADC_GetResetCalibrationStatus(ADC1) == SET);
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1) == SET);
    
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);//软件触发ADC开始连续转换,DMA也连续转运
}
View Code

此外,还可以加个定时器,定时器触发ADC,ADC触发DMA (硬件自动化)

TODU: 串口数据,使用DMA进行存储器到外设的转运

 

位段:用来单独操作寄存器或SRAM的某一位,给他新编一个地址(在新开辟的地址区域,有大把空闲地址)

 在程序中的临时变量放在RAM区,地址以20开头;使用const修饰的常量放在flash区内,地址以08开头(常用来修饰不变的数据)

SMT32中利用结构体来访问寄存器,结构体内的成员顺序(内存)与各个寄存器的地址(内存)一一对应,比如指定结构体A的起始地址为ADC1外设寄存器的起始地址,访问结构体成员就相当于访问外设的某个寄存器;  &ADC1->DR    ADC1的结构体指针指向的是ADC1外设的起始地址,访问结构体成员就相当于加一个偏移,这样也是可以访问对应的寄存器

需要DMA的中断就调用DMA_ITConfig,配置NVIC