自学内容网 自学内容网

11:(寄存器开发)DMA的使用

1、存储器ROM到存储器SRAM

案例:使用DMA1将ROM中的数据搬运到SRAM中,ROM中保存的数据一般是常量,所以在定义的时候前面加上关键词const,不可在改变。实现存储器到存储器其实就是数据的拷贝,相当于复制粘贴。
在这里插入图片描述

①DMA.c文件的代码如下:

#include "DMA.h"

/**
 * DMA1的初始化
 */
void DMA1_Init(void)
{
    /* 1、开启DMA1的时钟 */
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;

    /* 2、DMA的配置 */
    /* 2.1、搬运的方向:存储器(ROM)->存储器(SRAM),通道随意 
            DMA_CCRx_MEM2MEM = 1(存储器到存储器),DMA_CCRx_DIR = 0(从外设读:相当于将ROM映射到外设)*/
    DMA1_Channel1->CCR |= DMA_CCR1_MEM2MEM;//将DMA1的通道1配置为从ROM搬运到SRAM
    DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;//从外设读取数据到存储(ROM映射到外设,相当于外设)

    /* 2.2、通道的优先级:DMA_CCRx_PL[1:0]*/
    DMA1_Channel1->CCR &= ~DMA_CCR1_PL;//00最低

    /* 2.2、搬运的数据位宽:8位(一个字节),16位(半字),32位(字)
            被搬运的外设位宽DMA_CCRx_PSIZE[1:0],存储器位宽DMA_CCRx_MSIZE[1:0]*/
    DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE;//00,外设位宽为8位(1个字节)
    DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE;//00,存储器位宽为8位(1个字节)

    /* 2.3、搬运的地址和目的地址是否自增 
            外设地址自增DMA_CCRx_PINC = 1,存储器地址自增DMA_CCRx_MINC = 1*/
    DMA1_Channel1->CCR |= DMA_CCR1_PINC;
    DMA1_Channel1->CCR |= DMA_CCR1_MINC;

    /* 2.4、开启搬运完成的中断:DMA_CCRx_TCIE = 1*/
    DMA1_Channel1->CCR |= DMA_CCR1_TCIE;

    /* 2.5、NVIC的配置 */
    NVIC_SetPriorityGrouping(4);
    NVIC_SetPriority(DMA1_Channel1_IRQn,0);
    NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

/**
 * 搬运启动配置
 * StartAddress:起始地址
 * TargetAddress:目的地址
 * Len:搬运数据的数目
 * 
 */
void DMA1_TransportStart(uint32_t StartAddress ,uint32_t TargetAddress,uint16_t Len)
{
    /* 1、不执行循环搬运:DMA_CCRx_CIRC = 0:就相当于不执行重装载 */
    DMA1_Channel1->CCR &= ~DMA_CCR1_CIRC;

    /* 2、搬运的数据的数量:DMA_CNDTRx_NDT[15:0]:相当于计数器的值 */
    DMA1_Channel1->CNDTR = Len;

    /* 3、外设地址和存储器地址:[注]写地址在必须开启通道之前 
          外设地址:DMA_CPARx_PA[31:0],存储器地址:DMA_CMARx_MA[31:0]*/
    DMA1_Channel1->CPAR = StartAddress;
    DMA1_Channel1->CMAR = TargetAddress;

    /* 3、通道开启:*/
    DMA1_Channel1->CCR |= DMA_CCR1_EN;
}

/**
 * 中断服务函数:当搬运完成,关闭DMA通道
 */
uint8_t Flag = 0;
void DMA1_Channel1_IRQHandler(void)
{
    /* 判断中断标志位:DMA_ISR_TCIFx = 1 */
    if(DMA1->ISR & DMA_ISR_TCIF1)
    {
        /* 清除中断标志位:DMA_IFCR_CTCIFx= 1*/
        DMA1->IFCR |= DMA_IFCR_CTCIF1;

        //关闭DMA1通道1
         DMA1_Channel1->CCR &= ~DMA_CCR1_EN;
         Flag = 1;
    }
}

②主函数文件的代码如下:

#include "DMA.h"                
#include "Delay.h"
#include "OLED.h"

const uint8_t Data[] = {1,2,3,4,5};//在ROM中定义一个常量的数组
                                   //在DAM1的初始化中设置的搬运的位宽为8位,所以定义最好用uint8_t
uint8_t Buff[5];//在SRAM定义5个数组变量

int main(void)
{
OLED_Init();
    OLED_Clear();
    DMA1_Init();
    DMA1_TransportStart((uint32_t)Data,(uint32_t)Buff,5);//开始搬运吧,将ROM中的数据进行拷贝SRAM中
    

    OLED_ShowString(1,1,"StaAdd:");
    OLED_ShowHexNum(1,8,(uint32_t)Data,8);//打印出源地址,即ROM中存储常量数组的地址
    
    OLED_ShowString(2,1,"Datas:");
    for(uint8_t i = 0; i<5; i++)
    {
        OLED_ShowNum(2,7+i,Data[i],1);//打印出常量数据
    }
    
    OLED_ShowString(3,1,"TarAdd:");
    OLED_ShowHexNum(3,8,(uint32_t)Buff,8);//打印目的地址,即SRAM中存储变量数据的地址
    
    OLED_ShowString(4,1,"Buffs:");
while(1)
{
        if(Flag == 1)
        {
            for(uint8_t i = 0; i<5; i++)
            {
                OLED_ShowNum(4,7+i,Buff[i],1);//打印出SRAM中被搬运来的数据
            }            
            Flag = 0;
        }  
}
}

实物效果如下图所示:
在这里插入图片描述
如图:地址为0800 0E64为ROM的地址,地址为2000 0001为SRAM的地址
【注】
①当DMA将设置的搬运数量全部搬运完成后才会执行中断服务函数。
②将数据从ROM搬运到SRAM中,是由软件请求搬运信号,不想其他的外设会给DMA发送一个请求信号才开始搬运。即在DMA一使能,DMA就会开始搬运

2、SRAM到串口

实验:通过DMA1将SRAM中的数据搬运到串口USART1的发射数据寄存器,然后通过串口发射到上位机。
①DMA.c文件的代码如下:

#include "DMA.h"

/**
 * DMA1的初始化
 */
void DMA1_Init(void)
{
    /* 1、开启DMA1的时钟 */
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;

    /* 2、DMA的配置 */
    /* 2.1、搬运的方向:存储器(SRAM)->外设(USART1_Tx),则通道不能随意了,通道为:通道4 
            DMA_CCRx_MEM2MEM = 0(非存储器到存储器),DMA_CCRx_DIR = 1(从存储器读:相当于将存储器搬运到外设)*/
    DMA1_Channel4->CCR &= ~DMA_CCR4_MEM2MEM;
    DMA1_Channel4->CCR |= DMA_CCR4_DIR;//从存储器读取数据到外设

    /* 2.2、通道的优先级:DMA_CCRx_PL[1:0]*/
    DMA1_Channel4->CCR &= ~DMA_CCR4_PL;//00最低

    /* 2.2、搬运的数据位宽:8位(一个字节),16位(半字),32位(字)
            被搬运的外设位宽DMA_CCRx_PSIZE[1:0],存储器位宽DMA_CCRx_MSIZE[1:0]*/
    DMA1_Channel4->CCR &= ~DMA_CCR4_PSIZE;//00,外设位宽为8位(1个字节)
    DMA1_Channel4->CCR &= ~DMA_CCR4_MSIZE;//00,存储器位宽为8位(1个字节)

    /* 2.3、搬运的地址和目的地址是否自增 
            外设地址不能自增,因为只有一个DR寄存器,存储器地址自增DMA_CCRx_MINC = 1*/
    DMA1_Channel4->CCR &= ~DMA_CCR4_PINC;//外设地址不自增
    DMA1_Channel4->CCR |= DMA_CCR4_MINC;

    /* 2.4、开启搬运完成的中断:DMA_CCRx_TCIE = 1*/
    DMA1_Channel4->CCR |= DMA_CCR4_TCIE;

    /* 2.5、使能串口的DMA发送的请求:USART_CR3_DMAT = 1 */
    USART1->CR3 |= USART_CR3_DMAT;//当串口的数据寄存器里面没有数据时,就会发送一个DMA请求,
                                  //让DMA将数据搬运到串口的数据寄存器里面

    /* 2.6、NVIC的配置 */
    NVIC_SetPriorityGrouping(4);
    NVIC_SetPriority(DMA1_Channel4_IRQn,0);
    NVIC_EnableIRQ(DMA1_Channel4_IRQn);
}

/**
 * 搬运启动配置
 * StartAddress:起始地址(SRAM的地址)
 * TargetAddress:目的地址(USART的数据寄存器的地址)
 * Len:搬运数据的数目
 * 
 */
void DMA1_TransportStart(uint32_t StartAddress ,uint32_t TargetAddress,uint16_t Len)
{
    /* 1、不执行循环搬运:DMA_CCRx_CIRC = 0 */
    DMA1_Channel4->CCR &= ~DMA_CCR4_CIRC;

    /* 2、搬运的数据的数量:DMA_CNDTRx_NDT[15:0]*/
    DMA1_Channel4->CNDTR = Len;

    /* 3、外设地址和存储器地址:[注]写地址在必须开启通道(EN)之前 
          外设地址:DMA_CPARx_PA[31:0],存储器地址:DMA_CMARx_MA[31:0]*/
    DMA1_Channel4->CPAR = TargetAddress;
    DMA1_Channel4->CMAR = StartAddress;

    /* 3、通道开启:*/
    DMA1_Channel4->CCR |= DMA_CCR4_EN;
}

/**
 * 中断服务函数:当搬运完成,关闭DMA通道
 */
void DMA1_Channel4_IRQHandler(void)
{
    /* 判断中断标志位:DMA_ISR_TCIFx = 1 */
    if(DMA1->ISR & DMA_ISR_TCIF4)
    {
        /* 清除中断标志位:DMA_IFCR_CTCIFx= 1*/
        DMA1->IFCR |= DMA_IFCR_CTCIF4;

        //关闭DMA1通道1
         DMA1_Channel4->CCR &= ~DMA_CCR4_EN;
    }
}

②主函数文件的代码如下:

#include "DMA.h"                
#include "USART.h"


uint8_t Data[] = {1,2,3,4,5};//在SRAM中定义一个变量的数组
                             //在DAM1的初始化中设置的搬运的位宽为8位,所以定义最好用uint8_t
int main(void)
{
    USART1_Init();
    DMA1_Init();
    
    DMA1_TransportStart((uint32_t)Data,(uint32_t)(&(USART1->DR)),5);//搬运的准备工作(地址的对接),将SRAM中的数据进行搬运到串口的DR数据寄存器里面
    
while(1)
{
   
}
}

实物效果如下图所示:

3、ADC的连续扫描+DMA

案例:将ADC的规则组配置为多通道的连续扫描模式,通道DMA将被转换的数据搬运到SRAM内存中。
在这里插入图片描述①ADC.c文件代码如下:

#include "ADC.h"

/**
 * ADC1的初始化
 */
void ADC1_Init(void)
{
    /* 1、开启时钟:ADC1和GPIOA*/
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;

    /* 2、PA0(通道AIN1),PA1(通道AIN2),PA2(通道AIN3)的工作模式:模拟输入模式MODE = 00,CNF = 00*/
    GPIOA->CRL &= ~GPIO_CRL_MODE0;
    GPIOA->CRL &= ~GPIO_CRL_CNF0;
    
    GPIOA->CRL &= ~GPIO_CRL_MODE1;
    GPIOA->CRL &= ~GPIO_CRL_CNF1;
    
    GPIOA->CRL &= ~GPIO_CRL_MODE2;
    GPIOA->CRL &= ~GPIO_CRL_CNF2;
    

    /* 3、ADC的配置*/
      /* 3.1、设置采样时间:ADC_SMPR2_SMP0 = 001(通道1采样时间为7.5周期)*/
      ADC1->SMPR2 |= ADC_SMPR2_SMP0_0; 
      ADC1->SMPR2 &= ~(ADC_SMPR2_SMP0_1 | ADC_SMPR2_SMP0_2); 

      /* 3.2、通道组的配置:使用规则通道组 */
        /* 3.2.1:配置规则组要转换的序列个数:ADC_SQR1_L[3:0]:为0010,则3个序列需要转换 */
        ADC1->SQR1 &= ~(ADC_SQR1_L_0 | ADC_SQR1_L_2 |ADC_SQR1_L_3);
        ADC1->SQR1 |= ADC_SQR1_L_1;


        /* 3.2.2:将通道号(AI0,AI1,AI2)写入转换的序列中:
                 ADC_SQR3_SQ1[0:4] = 00000(AIN0)
                 ADC_SQR3_SQ2[0:4] = 00001(AIN1)
                 ADC_SQR3_SQ3[0:4] = 00010(AIN2),*/
        ADC1->SQR3 &= ~ADC_SQR3_SQ1;//将AI0放入规则组序列1

        ADC1->SQR3 &= ~(ADC_SQR3_SQ2_1|ADC_SQR3_SQ2_2|ADC_SQR3_SQ2_3|ADC_SQR3_SQ2_4);
        ADC1->SQR3 |= ADC_SQR3_SQ2_0;//将AIN1放入规则组序列2

        ADC1->SQR3 &= ~(ADC_SQR3_SQ3_0|ADC_SQR3_SQ3_2|ADC_SQR3_SQ3_3|ADC_SQR3_SQ3_4);
        ADC1->SQR3 |= ADC_SQR3_SQ3_1;//将AIN2放入规则组序列3

      /* 3.3、使用扫描模式:ADC_CR1_SCAN = 1*/  
      ADC1->CR1 |= ADC_CR1_SCAN;


      /* 3.4、使用连续转换模式:ADC_CR2_CONT = 1*/
      ADC1->CR2 |= ADC_CR2_CONT;

      /* 3.5、数据的对齐方式:右对齐:ADC_CR2_ALIGN = 0*/
      ADC1->CR2 &= ~ADC_CR2_ALIGN;

      /* 3.6、使用软件触发 */
      ADC1->CR2 &= ~ADC_CR2_EXTTRIG;//禁用外部事件启动转换
      ADC1->CR2 |= ADC_CR2_EXTSEL;//111,为软件触发
}
/**
 * 启动ADC1
 */
void ADC1_Start(void)
{
    /* 1、上电:把ADC从休眠模式唤醒*/
    ADC1->CR2 |= ADC_CR2_ADON;

    /* 2、执行校准 */
    ADC1->CR2 |= ADC_CR2_CAL;

    /* 3、等待校准完成 */
    while(ADC1->CR2 & ADC_CR2_CAL);//校准完成为0 

    /* 4、再次上电,开始转换*/
    ADC1->CR2 |= ADC_CR2_ADON;
    
    /* 5、开始规则组转换:给一个触发信号*/
    ADC1->CR2 |= ADC_CR2_SWSTART;
}

②DMA.c文件的代码如下:

#include "DMA.h"

/**
 * DMA1的初始化
 */
void DMA1_Init(void)
{
    /* 1、开启DMA1的时钟 */
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;

    /* 2、DMA的配置 */
    /* 2.1、搬运的方向:外设(ADC)->存储器(SRAM),则通道不能随意了,通道为:通道1 
            DMA_CCRx_MEM2MEM = 0(非存储器到存储器),DMA_CCRx_DIR = 0(从外设读:相当于将数据从外设搬运到存储器)*/
    DMA1_Channel1->CCR &= ~DMA_CCR1_MEM2MEM;
    DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;//从外设读取数据到存储器

    /* 2.2、通道的优先级:DMA_CCRx_PL[1:0]*/
    DMA1_Channel1->CCR &= ~DMA_CCR1_PL;//00最低

    /* 2.2、搬运的数据位宽:8位(一个字节),16位(半字),32位(字)
            被搬运的外设位宽DMA_CCRx_PSIZE[1:0],存储器位宽DMA_CCRx_MSIZE[1:0]*/
    DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0;//01,外设位宽为16位(半个字),因为ADC的数据是12位的
    DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE_1;

    DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0;//01,存储器位宽为16位(半个字)
    DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE_1;//01,存储器位宽为16位(半个字)

    /* 2.3、搬运的地址和目的地址是否自增 
            外设地址不能自增,因为只有一个DR寄存器,存储器地址自增DMA_CCRx_MINC = 1*/
    DMA1_Channel1->CCR &= ~DMA_CCR1_PINC;//外设地址不自增,因为始终是ADC的数据寄存器
    DMA1_Channel1->CCR |= DMA_CCR1_MINC;

    /* 2.5、使能ADC的DMA的请求[注:只有ADC1和ADC3才有DMA请求]:ADC_CR2_DMA = 1 */
    ADC1->CR2 |= ADC_CR2_DMA;//当转换好的数据保存到ADC1的数据寄存器时,
                             //就会发生一个DMA请求,让DMA将数据搬运走
}

/**
 * 搬运启动配置
 * StartAddress:起始地址(ADC的数据寄存器的地址)
 * TargetAddress:目的地址(SRAM的地址)
 * Len:搬运数据的数目
 * 
 */
void DMA1_TransportStart(uint32_t StartAddress ,uint32_t TargetAddress,uint16_t Len)
{
    /* 1、执行循环搬运:DMA_CCRx_CIRC = 1 */
    DMA1_Channel1->CCR |= DMA_CCR1_CIRC;

    /* 2、搬运的数据的数量:DMA_CNDTRx_NDT[15:0]*/
    DMA1_Channel1->CNDTR = Len;

    /* 3、外设地址和存储器地址:[注]写地址在必须开启通道(EN)之前 
          外设地址:DMA_CPARx_PA[31:0],存储器地址:DMA_CMARx_MA[31:0]*/
    DMA1_Channel1->CPAR = StartAddress;
    DMA1_Channel1->CMAR = TargetAddress;

    /* 3、通道开启:*/
    DMA1_Channel1->CCR |= DMA_CCR1_EN;
}

③主函数文件的代码如下:

#include "DMA.h" 
#include "ADC.h"
#include "OLED.h"
#include "Delay.h"

uint16_t Data[3];//在SRAM中定义一个变量的数组
                //在DAM1的初始化中设置的搬运的位宽为16位,所以定义最好用uint16_t
float Buff;
int main(void)
{
    OLED_Init();
    ADC1_Init();
    DMA1_Init();
    
    OLED_ShowString(1,1,"Value01:0.00v");
    OLED_ShowString(2,1,"Value02:0.00v");
    OLED_ShowString(3,1,"Value03:0.00v");
   
    
    /* 当ADC中的规则组的一个序列的通道转换完成后,将数据保存到数据寄存器里面,就会产生一个DMA搬运请求 
       此时ADC1:连续+扫描模式(3个通道),所以DMA1:搬运数据3+循环模式(计数器重装载,每搬完3个数据后,又开始搬运)
    */
    DMA1_TransportStart((uint32_t)&(ADC1->DR),(uint32_t)(Data),3);//DMA执行搬运准备工作(将地址对接,当寄存器里面有数据来了,硬件就会自动搬运)
    ADC1_Start();//开始转换,等待寄存器里面来数据
    
while(1)
{
        Buff = (Data[0] * 3.3) / 4095;//将通道AIO测量的数据给Buff
        OLED_ShowNum(1,9,Buff,1);//显示通道1转换的整数
        OLED_ShowNum(1,11,(uint16_t)(Buff * 100)%100,2);//显示通道1转换的整数
        
        Buff = (Data[1] * 3.3) / 4095;//将通道AI1测量的数据给Buff
        OLED_ShowNum(2,9,Buff,1);//显示通道1转换的整数
        OLED_ShowNum(2,11,(uint16_t)(Buff * 100)%100,2);//显示通道1转换的整数
        
        Buff = (Data[2] * 3.3) / 4095;//将通道AI2测量的数据给Buff
        OLED_ShowNum(3,9,Buff,1);//显示通道1转换的整数
        OLED_ShowNum(3,11,(uint16_t)(Buff * 100)%100,2);//显示通道1转换的整数
        Delay_ms(30);  
}
}

实物效果如下图所示:

DMA+ADC

【注】
主函数中搬运的准备工作(地址的对接)应该在ADC开始转换数据的前面,因为地址对接好后,开启ADC转换,转换的数据保存到数据寄存器里面,然后数据寄存器申请DMA请求,DMA就开始搬运数据,这样通道AIN0测量的数据就正好搬运到Data[0]中。


原文地址:https://blog.csdn.net/qq_51284092/article/details/142910205

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!