STM32定时器DMA ADC双重模式详解

目录

 

1 前言

2 ADC介绍

2.1 多重工作模式

2.2 多重ADC框图

2.3 规则同时模式

3 程序设计

3.1 时序图

3.2 初始化流程图

3.3 初始化代码

4 结论


 

1 前言

     关于ADC,相信大家都比较了解,关于STM32的学习教程都会有所讲解,但以查询方式、单通道讲解的较多,主要告诉大家基本的原理。关于ADC多重模式讲解的较少。本文主要通过讲解ADC转换器的双重工作模式,让大家更好的理解ADC的多重模式。

参考资料《STM32F4参考手册》。

2 ADC介绍

    STM32单片机内部集成了12位ADC转换器,是逐次趋近型模数转换器。具有多达19个复用通道,可测量来自16个外部源、两个内部源和VBAT通道的信号。这些通道的AD转换可在单次、连续、扫描和不连续采样模式下进行。

2.1 多重工作模式

    在具有两个或多个ADC的器件中,可以使用两重(具有2个ADC)或三重(具有3个)ADC模式。在多重 ADC 模式下,通过 ADC1 主器件到 ADC2 和 ADC3 从器件的交替触发或同时触发来启动转换,具体取决于 ADC_CCR 寄存器中的 MULTI[4:0] 位所选的模式。在多重 ADC 模式下,配置外部事件触发转换时,应用必须设置为仅主器件触发而禁止从器件触发,以防止出现意外触发而启动不需要的从转换。

    可实现以下四种模式:
    ● 注入同时模式
    ● 规则同时模式
    ● 交替模式
    ● 交替触发模式
     也可按以下方式组合使用上述模式:
    ● 注入同时模式 + 规则同时模式
    ● 规则同时模式 + 交替触发模式

2.2 多重ADC框图

0cde10e1fecd433c908e1d44fb67521e.bmp

 

图 1 多重ADC框图

    从图中可以看出:

  1. 尽管 ADC2 和 ADC3 上存在外部触发,但它们并未显示在此图中。
  2. 在双重 ADC 模式下,不存在 ADC3 从器件部分。
  3. 在三重 ADC 模式下, ADC 通用数据寄存器 (ADC_CDR) 包含 ADC1、 ADC2 和 ADC3 的规则转换数据。按照所选的存储顺序使用全部 32 个寄存器位。在双重 ADC 模式下, ADC 通用数据寄存器 (ADC_CDR) 包含 ADC1 和 ADC2 的规则转换数据。使用全部32 个寄存器位。

2.3 规则同时模式

    此模式可用于规则通道组。外部触发源来自 ADC1 的规则组多路复用器。在规则同时模式下,必须使用同一长度来转换序列,或必须确保触发之间的间隔长于 2 个序列(双重 ADC 模式)中的较长转换时间。否则,当序列较长的ADC 完成上一次转换时,序列较短的 ADC 可能重新开始转换。必须禁止注入转换。

     双重 ADC 模式,在 ADC1 或 ADC2 转换事件结束时: 会生成一个 32 位 DMA 传输请求(如果 ADC_CCR 寄存器中的 DMA[1:0] 位等于0b10)。此请求会将存储在 ADC_CDR 32 位寄存器高位半字中的 ADC2 转换数据传输到 SRAM,然后将存储在 ADC_CCR 低位半字中的 ADC1 转换数据传输到 SRAM。

     当 ADC1/ADC2 的规则通道全部完成转换后,会生成一个 EOC 中断(如果已在两个 ADC接口中的一个接口上使能)。

    16通道规则同时模式如下图所示。

5e8d14028eb045ad99d181344c26d6f5.bmp

 

图 2 双重ADC模式

3 程序设计

3.1 时序图

ffa602975e634286bda46e4ed85fcfde.png

 

图 3定时器+DMA+ADC工作时序图

        如图 3所示,完成DMA、定时器、ADC初始化后,先使能DMA,再使能ADC,最后启动定时器Timer。当定时器发生更新事件时,触发ADC开始采样、转换,转换结束后触发DMA保存数据到缓冲区,完成一次变换。定时器的周期设定要确保完成ADC转换及DMA数据存储,否则将溢出错误。当DMA检测到转换次数达到其预设值时,触发DMA接收完成中断,关闭定时器。一个大的AD采集循环完成。

3.2 初始化流程图

        初始化过程包括ADC初始化、DMA初始化、定时器初始化。ADC初始化和大部分MCU外设初始化流程一样,先使能时钟,包括GPIO时钟、ADC时钟、ADC配置,这里的ADC配置包含ADC1和ADC2两个外设的配置。DMA初始化包含DMA时钟(本文用DMA将ADC转换结果拷贝到RAM中)使能、DMA配置,在后面的实现代码中,将DMA初始化合并在ADC_MspInit函数中完成。因为要用定时器实现定期触发ADC转化,所以在初始化时要对定时器初始化,完成时钟使能、定时器配置。

d9c2e0956e0e40b0b28de0be7378e02c.png

 

图 4 初始化流程图

3.3 初始化代码

        ADC初始化相关函数如表 1所示

表 1 ADC初始化相关函数

序号

函数名称

函数功能说明

1

HAL_ADC_MspInit ()

时钟使能、IO、DMA初始化

2

MX_ADC1_Init()

配置ADC1

3

MX_ADC2_Init()

配置ADC2

4

ADC_Trigger_Timer_Start()

启动定时器

5

ADC_Trigger_Timer_Stop()

停止定时器

6

DMA2_Stream0_IRQHandler()

DMA中断处理函数

7

HAL_ADC_ConvCpltCallback()

ADC转换完成回调函数

8

MX_Timer2_Init()

配置Timer2配置为TIM_TRGO_UPDATE输出触发模式

        1)HAL_ADC_MspInit ()代码

        在ADC1底层初始化时,使能了ADC、DMA、GPIO时钟,引脚配置为模拟输入模式。对DMA2初始化,使能DMA中断。具体如下:

void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    
    if(adcHandle->Instance==ADC1)
    {
        __HAL_RCC_ADC1_CLK_ENABLE();    //使能ADC0时钟 
        
        __HAL_RCC_DMA2_CLK_ENABLE();
        
        __HAL_RCC_GPIOA_CLK_ENABLE();
        __HAL_RCC_GPIOC_CLK_ENABLE(); 
        GPIO_InitStruct.Pin = GPIO_PIN_3;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        GPIO_InitStruct.Pin = GPIO_PIN_0;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); 

        hdma_adc1.Instance = DMA2_Stream0;
        hdma_adc1.Init.Channel = DMA_CHANNEL_0;
        hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
        hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
        hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
        hdma_adc1.Init.MemDataAlignment = DMA_PDATAALIGN_WORD;
        hdma_adc1.Init.Mode = DMA_NORMAL;                           
        hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
        hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
        HAL_DMA_Init(&hdma_adc1);
        
        __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);
        
        HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 1, 1);  //DMA中断配置
        HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);        
    }
    else if(adcHandle->Instance==ADC2)
    {
        __HAL_RCC_ADC2_CLK_ENABLE();
    }   
}

        2)MX_ADC1_Init()代码

void MX_ADC1_Init(void)
{
    ADC_MultiModeTypeDef multimode;
    ADC_ChannelConfTypeDef sConfig;

    /**配置 ADC通用参数*/
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;                 //设置分辨率
    hadc1.Init.ScanConvMode = ENABLE;                                 //扫描方式
    hadc1.Init.ContinuousConvMode = DISABLE;                      //关闭连续扫描
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; //触发模式
    hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;  //触发源
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;               //对齐方式
    hadc1.Init.NbrOfConversion = 1;
    hadc1.Init.DMAContinuousRequests = ENABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
    HAL_ADC_Init(&hadc1);

    /**配置 ADC工作模式 */
    multimode.Mode = ADC_DUALMODE_REGSIMULT;                    //规则同时模式
    multimode.DMAAccessMode =ADC_DMAACCESSMODE_2;        //ADC同步DMA模式2
    multimode.TwoSamplingDelay = ADC_TWOSAMPLINGDELAY_5CYCLES;    //
    HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode);

    /**配置 ADC采集通道*/
    sConfig.Channel = ADC_CHANNEL_VREFINT;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}

        3)MX_ADC2_Init()代码

void MX_ADC2_Init(void)
{
    ADC_ChannelConfTypeDef sConfig;

    /**配置 ADC通用参数*/
    hadc2.Instance = ADC2;
    hadc2.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
    hadc2.Init.Resolution = ADC_RESOLUTION_12B;
    hadc2.Init.ScanConvMode = ENABLE;
    hadc2.Init.ContinuousConvMode = DISABLE;
    hadc2.Init.DiscontinuousConvMode = DISABLE;
    hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc2.Init.NbrOfConversion = 1;
    hadc2.Init.DMAContinuousRequests = DISABLE;  
    hadc2.Init.EOCSelection = ADC_EOC_SEQ_CONV;
    HAL_ADC_Init(&hadc2);  

    /**配置 ADC采集通道*/
    sConfig.Channel = ADC_CHANNEL_13;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES;
    HAL_ADC_ConfigChannel(&hadc2, &sConfig);
    
    HAL_ADC_Start(&hadc2);
}

        4)ADC_Trigger_Timer_Start()代码

static void ADC_Trigger_Timer_Start(void)
{
    __HAL_TIM_ENABLE(&htim2);
}

        5)ADC_Trigger_Timer_Stop()代码

static void ADC_Trigger_Timer_Stop(void)
{
    __HAL_TIM_DISABLE(&htim2);
}

 

6)HAL_ADC_ConvCpltCallback()代码

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    ADC_Trigger_Timer_Stop();
    HAL_ADCEx_MultiModeStop_DMA(&hadc1);
    ADC_ConvCpltCallback();
}
  1. MX_Timer2_Init()代码
void MX_Timer2_Init(void)
{    
    TIM_MasterConfigTypeDef sMasterConfig;
    uint32_t uwPrescalerValue = 0;
    
    uwPrescalerValue = (uint32_t) (SystemCoreClock /2/1000000) - 1;       //timer2挂在APB2上,倍频后最大时钟为系统时钟/2,

    htim2.Instance = TIM2;
    htim2.Init.Prescaler = uwPrescalerValue;               //定时器时钟频率为1M
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 200-1;  //定时器溢出时间,1uS*100 = 200uS
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim2);
    
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);
}

4 结论

        本文介绍了定时器+DMA+ADC双重工作模式下ADC驱动设计思路,介绍了ADC初始化、配置的主要函数。文中代码截取自实际工程中部分代码,旨在为读者提供设计思路,可根据自己的程序做简单修改即可。

 

物联沃分享整理
物联沃-IOTWORD物联网 » STM32定时器DMA ADC双重模式详解

发表评论