DMA多知识学习应用实例

DMA多知识学习应用实例

闲来无事,学习了下DMA的相关知识和使用。平时看到的DMA都是简单的存储器到寄存器或者寄存器到存储器这样单类的传输。学习完DMA后,我想写个比较综合点的DMA学习实例,不仅能增加自己对DMA的深入应用,也同时发表于此给网友提供参考。
之所以说多知识,实例涉及到存储器到寄存器和寄存器到存储器,以及DMA中断使用等相关知识。

实例内容:单片机采集AD值通过DMA传输给RAM存储器(就是定义的变量),同时又将存储器数据传输给串口输出,实现AD采集与串口输出的转换。

下面赋上一些主要的代码进行分析:
主函数部分

int main(void)
{   
    delay_init();   //延时初始化
    RCCInit();//时钟初始化
    USARTInit(USART2 ,115200);//串口初始化
    ADCInit(ADC1 , 1); //AD初始化PA1
    Get_ADCValue(ADC1 ,ADC_Channel_1 ,1);//对ADC通道配置

    DMAx_ADCx_Init(DMA1_Channel1 ,DMA_DIR_PeripheralSRC,(u32)(&adcValue), (u32)&(ADC1->DR),1);//ADC1-->存储器(adcValue)(注传入地址位要32位)

    DMAx_USARTx_Init(DMA1_Channel7 ,DMA_DIR_PeripheralDST,(u32)usart_BUFF, (u32)&(USART2->DR),bufferSize);//存储器(adcValue)-->UASART1

    NVIC_Config();//注意,实测过,DMA中断配置必须放在DMA配置后面才能用

    DMAx_Enable(DMA1_Channel1 , 1);
    ADC_DMACmd(ADC1, ENABLE);

    DMAx_Enable(DMA1_Channel7 , bufferSize);//先启动,使满标志不为0
    USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);

    printf("下面是DMA测试,AD值为:\r\n");

    while(1)
    {

        delay_ms(100);
        DMAx_Enable(DMA1_Channel1 , 1);
    }    
} 

DMA1_CH1中断函数

void DMA1_Channel1_IRQHandler()
{

    if(DMA_GetITStatus(DMA1_IT_TC1) == SET)//获取DMA1_CH1的传输完成标志位
    {
        DMA_ClearITPendingBit(DMA1_IT_TC1);//清除满标志(注意清除后,不能去等待判满)
        sprintf(usart_BUFF,"%d ",adcValue);//16位整型转化为字符串型(每个字符8位,1字节)(转换中加了个空格,方便显示)

        while(!DMA_GetFlagStatus( DMA1_FLAG_TC7));//等待传输完成(注意 等待判满前,不能清除标志)
        DMA_ClearFlag(DMA1_FLAG_TC7); //完成后清除

        DMAx_Enable(DMA1_Channel7 , bufferSize);//开始串口的DMA传输    
    }   
}

ADC初始化:

void ADCInit(ADC_TypeDef* ADCx , u8 Channel_num)
{
    //=============对ADC1的配置并校准====================//
    ADC_InitTypeDef ADC_Inittrue;
    ADC_DeInit(ADCx);//复位ADC1

    ADC_Inittrue.ADC_ContinuousConvMode = ENABLE;//连续转换
    ADC_Inittrue.ADC_DataAlign = ADC_DataAlign_Right;//右对齐。(ADC为12位精度,其数字信号存于16位的寄存器)
    ADC_Inittrue.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//触发方式,无外部触发=软件触发
    ADC_Inittrue.ADC_Mode = ADC_Mode_Independent;//独立模式
    ADC_Inittrue.ADC_NbrOfChannel = Channel_num;//顺序进行规则转换的 ADC 通道的数目(1--16)
    ADC_Inittrue.ADC_ScanConvMode = DISABLE;//无扫描模式,即工作在单通道下
    ADC_Init(ADCx, &ADC_Inittrue);//初始化配置模块ADC1 
    ADC_Cmd(ADCx, ENABLE);//使能ADC1

    ADC_ResetCalibration(ADCx);  //复位校准
    while(ADC_GetResetCalibrationStatus(ADCx));
    ADC_StartCalibration(ADCx);//开启adc校准
    while(ADC_GetCalibrationStatus(ADCx));  
}

ADC通道配置,并获取相应模块下通道的ADC值(这里只用到通道配置就够了)

u16 Get_ADCValue(ADC_TypeDef* ADCx ,u8 ADC_Channel_x ,u8 rank)
{   
    //========对ADC的规则通道的配置(选通道x)=========//
    ADC_RegularChannelConfig(ADCx,ADC_Channel_x, rank , ADC_SampleTime_28Cycles5);//ADC1的通道4,排在第1个被转换,并且采集时间为28.5个ADC1时钟周期
    ADC_SoftwareStartConvCmd(ADCx ,ENABLE);//开启软件转换

    //========对ADC转换部分=========//
    while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
    return ADC_GetConversionValue(ADCx)&0x00000fff;//换回ADC1转换后的值
}

DMA ADC初始化

//名称:DMA ADC初始化
//传入参数:通道 传输方向 存储器基地址  外设基地址 数据量
//注:传入地址位数32位
//说明:默认非循环,存储器地址不自加,外设不自加,数据宽度都为16位
void DMAx_ADCx_Init(DMA_Channel_TypeDef* DMAx_Channelx ,u32 DMA_DIR_Peripheralxxx , u32 MemoryBaseAddr, u32 PeripheralBaseAddr,u16 DMA_BufferSize)
{
    DMA_InitTypeDef DMA_InitStruct;

    if(DMA1_Channel1 <= DMAx_Channelx && DMA1_Channel7 >= DMAx_Channelx)
    {
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA位于AHB时钟上
    }
    if(DMA2_Channel1 <= DMAx_Channelx && DMA2_Channel5 >= DMAx_Channelx)
    {
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);//DMA位于AHB时钟上
    }

    DMA_DeInit(DMAx_Channelx);//

    DMA_InitStruct.DMA_BufferSize = DMA_BufferSize;//DMA传输的数据量
    DMA_InitStruct.DMA_DIR = DMA_DIR_Peripheralxxx;//传输方向:外设为目标地址(存储器到外设)
    DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //存储器到存储器传输失能
    DMA_InitStruct.DMA_MemoryBaseAddr = MemoryBaseAddr;//存储器基地址(可为sram或flash,程序变量一般位于sram)
    DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//存储器传输数据宽度16(可选8,16,32位)
    DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Disable;//存储器地址不递增
    DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;// 不循环传输模式(循环:地址递增满后,又重载从基地址开始传输)(不循环:递增满后不重载且暂停)
    DMA_InitStruct.DMA_PeripheralBaseAddr = PeripheralBaseAddr;//外设寄存器的基地址(多是数据寄存器)
    DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设寄存器传输数据宽度16(8,16,32位)
    DMA_InitStruct.DMA_PeripheralInc =  DMA_PeripheralInc_Disable;//外设地址不进行递增
    DMA_InitStruct.DMA_Priority = DMA_Priority_High;//传输优先级:高(四种:最高,高,中,低)

  DMA_Init(DMAx_Channelx, &DMA_InitStruct);
}

DMA USART初始化。
注意此初始化和ADC 的DMA初始化是不一样的,具体不一样处见函数说明, 和调用的实参。

//名称:DMA USART初始化
//传入参数:通道 传输方向 存储器基地址  外设基地址 数据量
//注;传入地址位数32位
//说明:默认非循环,存储器地址自加,外设不自加,数据宽度都为8位
void DMAx_USARTx_Init(DMA_Channel_TypeDef* DMAx_Channelx ,u32 DMA_DIR_Peripheralxxx , u32 MemoryBaseAddr, u32 PeripheralBaseAddr,u16 DMA_BufferSize)
{
    DMA_InitTypeDef DMA_InitStruct;

    if(DMA1_Channel1 <= DMAx_Channelx && DMA1_Channel7 >= DMAx_Channelx)
    {
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA位于AHB时钟上
    }
    if(DMA2_Channel1 <= DMAx_Channelx && DMA2_Channel5 >= DMAx_Channelx)
    {
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);//DMA位于AHB时钟上
    }

    DMA_DeInit(DMAx_Channelx);

    DMA_InitStruct.DMA_BufferSize = DMA_BufferSize;//DMA传输的数据量
    DMA_InitStruct.DMA_DIR = DMA_DIR_Peripheralxxx;//传输方向:外设为目标地址(存储器到外设)
    DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //存储器到存储器传输失能
    DMA_InitStruct.DMA_MemoryBaseAddr = MemoryBaseAddr;//存储器基地址(可为sram或flash,程序变量一般位于sram)
    DMA_InitStruct.DMA_MemoryDataSize =DMA_MemoryDataSize_Byte;存储器传输数据宽度8位(8,16,32位)
    DMA_InitStruct.DMA_MemoryInc =DMA_MemoryInc_Enable;//存储器地址随传输而递增(加多少与数据宽度有关,可见芯片参考手册,这里是地址自加1)
    DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;//不循环传输模式(循环:地址递增满后,又重载从基地址开始传输)(不循环:递增满后不重载且暂停)
    DMA_InitStruct.DMA_PeripheralBaseAddr = PeripheralBaseAddr;//外设寄存器的基地址(多是数据寄存器)
    DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;// DMA_PeripheralDataSize_HalfWord;//外设寄存器传输数据宽度8位(8,16,32位)
    DMA_InitStruct.DMA_PeripheralInc =  DMA_PeripheralInc_Disable;//外设地址不进行递增
    DMA_InitStruct.DMA_Priority = DMA_Priority_High;//传输优先级:高(四种:最高,高,中,低)

    DMA_Init(DMAx_Channelx, &DMA_InitStruct);
}

DMA启动传输(用于每次传输开启)

//DMA启动传输
//传入参数:通道  数据量
void DMAx_Enable(DMA_Channel_TypeDef* DMAx_Channelx ,u16 DMA_BufferSize)
{
    DMA_Cmd(DMAx_Channelx, DISABLE);//失能

    DMA_SetCurrDataCounter( DMAx_Channelx , DMA_BufferSize); //重新设置传输数据长度
    DMA_Cmd(DMAx_Channelx, ENABLE);//使能

}

中断配置函数

void NVIC_Config()
{
    NVICInit(NVIC_PriorityGroup_2, DMA1_Channel1_IRQn , 0, 1); //组别2 抢占0 从占1
    DMA_ClearFlag(DMA1_FLAG_TC1);//清满标志
    DMA_ClearITPendingBit(DMA1_IT_TC1); 

    DMA_ITConfig(DMA1_Channel1,  DMA_IT_TC, ENABLE);//开启全部传输完成中断
}

RCC时钟配置

扫描二维码关注公众号,回复: 1537119 查看本文章
void RCCInit()
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO | RCC_APB2Periph_USART1 , ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 , ENABLE);

    RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADC时钟周期 6分频
}

下面是上位机显示传输的数据:

这里写图片描述

以上就是涉及的主要相关函数,在本次调试中,总结了几个注意要点:

  • 对于串口,一次只能输出8位(一字节数),即使传进去16位数,也被截取前半段
  • ADC采集为10为精度,数据存储宽度要大于8位
  • 所以:DMA的ADC传输需要16位,而USART传输只能8位传输
  • 该例程的ADC为连续转换模式,若是单次需要每次对通道重新配置一次
  • *DMA为循环模式只需要使能一次,否则非循环下需要重新使能 来启动传输
  • 每个DMA通道对应不同外设见手册
  • DMA中断配置必须放在DMA参数配置后面才能用
  • 注意:等待判满前,不能清除标志;注意:清除后,不能去等待判满,否则死循环

猜你喜欢

转载自blog.csdn.net/ludaoyi88/article/details/52103105