【STM32H7教程】第46章 STM32H7的ADC应用之DMA方式多通道采样

2023-04-07  

第1阶段,上电启动阶段:

  • 这部分在第14章进行了详细说明。

第2阶段,进入main函数:

  • 第1步,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,LED,串口和ADC。

  • 第2步,周期性的打印ADC采集的多通道数据。

46.7 实验例程说明(MDK)

配套例子:

V7-024-ADC+DMA的多通道采集

实验目的:

  1. 学习ADC + DMA的多通道采集实现。

实验内容:

  1. 例子默认用的PLL时钟供ADC使用,大家可以通过bsp_adc.c文件开头宏定义切换到AHB时钟。

  2. 采用DMA方式进行多通道采样,采集了PC0, Vbat/4, VrefInt和温度。

  3. 每隔500ms,串口会打印一次。

  4. 板子正常运行时LED2闪烁。

PC0引脚位置(稳压基准要短接3.3V):

上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1

程序设计:

系统栈大小分配:

RAM空间用的DTCM:

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

46.1 初学者重要提示

  1. 学习本章节前,务必优先学习第44章,需要对ADC的基础知识和HAL库的几个常用API有个认识。

  2. 开发板右上角有个跳线帽,可以让ADC的稳压基准接3.3V或者2.5V,本章例子是接到3.3V。

  3. STM32H7的ADC支持偏移校准和线性度校准。如果使用线性度校准的话,特别要注意此贴的问题:armbbs.cn/forum.php? 。

  4. ADC的专业术语诠释文档,推荐大家看看:armbbs.cn/forum.php? 。

  5. STM32H7的ADC多通道并不是同步采样的,本质上是通过内部的多路选择器不断切换实现的,一个采集完毕了才会采集另一个。

46.2 ADC稳压基准硬件设计

注:学习前务必优先看第14章的2.1小节,对电源供电框架有个了解。

ADC要采集的准确,就需要有一个稳定的稳压基准源,V7开发板使用的LM285D-2.5,即2.5V的基准源。硬件设计如下:

关于这个原理图要注意以下问题:

LM285D-2.5输出的是2.5V的稳压基准,原理图这里做了一个特别的处理,同时接了一个上拉电阻到VDDA(3.3V),然后用户可以使用开发板右上角的跳线帽设置Vref选择3.3V稳压还是2.5V稳压。

下面再来了解下LM285的电气特性:

通过这个表,我们要了解以下几点知识:

  1. LM285的典型值是2.5V,支持的最小值2.462V,最大值2.538V。工作电流是20uA到20mA,温飘是±20ppm/℃

  2. Iz是Reference current参考电流的意思:

    • 参考电流是20uA到1mA,温度25℃,参考电压最大变化1mV。

    • 参考电流是20uA到1mA,全范围温度(−40°C to 85°C),参考电压最大变化1.5mV。

    • 参考电流是1mA到20mA,温度25℃,参考电压最大变化10mV。

    • 参考电流是1mA到20mA,全范围温度(−40°C to 85°C),参考电压最大变化30mV。


那么问题来了,V7开发板上LM285的参考电流是多少? 简单计算就是:

(VDDA – 2.5V) / 1K =(3.3 – 2.5V) / 1K = 0.8mA。

46.3 ADC驱动设计

ADC做DMA数据传输的实现思路框图如下:

下面将程序设计中的相关问题逐一为大家做个说明。

46.3.1 ADC软件触发

ADC转换既可以选择外部触发也可以选择软件触发。我们这里选择的是软件触发方式的多通道转换,即连续转换序列,软件触发。对应的时序如下(在第44章的2.7小节有详细讲解软件触发和硬件触发的时序):。

ADSTART表示软件启动转换。

ADSTP表示停止转换。

EOC表示一个通道转换结束。

EOS表示所有通道转换结束。

关于这个时序图的解读:

  • 配置为连续转换的话,软件启动ADSTART会开启所有通道转换,全部转换完毕后,继续进行下一轮转换。调用了停止转换ADSTP后,会停止转换。

  • 每个通过转换完毕有个EOC标志,所有通道转换完毕有个EOS标志。

46.3.2 ADC时钟源选择

根据第44章2.2小节的讲解,我们知道ADC有两种时钟源可供选择,可以使用来自AHB总线的系统时钟,也可以使用PLL2,PLL3,HSE,HSI或者CSI时钟。

如果采用AHB时钟,不需要做专门的配置,而采用PLL2,PLL3时钟需要特别的配置,下面是使用AHB或者PLL2时钟的配置。

  • 通过宏定义设置选择的时钟源

使用哪个时钟源,将另一个注释掉即可:

/* 选择ADC的时钟源 */ #define ADC_CLOCK_SOURCE_AHB /* 选择AHB时钟源 */ //

#define ADC_CLOCK_SOURCE_PLL   /* 选择PLL时钟源 */

PLL2或者AHB时钟源配置

#if defined (ADC_CLOCK_SOURCE_PLL)

    /* 配置PLL2时钟为的72MHz,方便分频产生ADC最高时钟36MHz */

    RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC;

    PeriphClkInitStruct.PLL2.PLL2M = 25;

    PeriphClkInitStruct.PLL2.PLL2N = 504;

    PeriphClkInitStruct.PLL2.PLL2P = 7;

    PeriphClkInitStruct.PLL2.PLL2Q = 7;

    PeriphClkInitStruct.PLL2.PLL2R = 7;

    PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_0;

    PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;

    PeriphClkInitStruct.PLL2.PLL2FRACN = 0;

    PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2;

    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)

    {

        Error_Handler(__FILE__, __LINE__);  

    }

#elif defined (ADC_CLOCK_SOURCE_AHB)

  

  /* 使用AHB时钟的话,无需配置,默认选择*/

  

#endif

对于PLL2的时钟输出,直接使用STM32CubeMX里面的时钟树配置即可,效果如下:

选择PLL2P输出作为ADC时钟源:

  • ADC分频设置

无论是使用AHB时钟还是PLL2时钟都支持分频设置:

AHB支持下面三种分频设置:


#define ADC_CLOCK_SYNC_PCLK_DIV1   ((uint32_t)ADC_CCR_CKMODE_0)  

#define ADC_CLOCK_SYNC_PCLK_DIV2   ((uint32_t)ADC_CCR_CKMODE_1) 

#define ADC_CLOCK_SYNC_PCLK_DIV4   ((uint32_t)ADC_CCR_CKMODE)   


#define ADC_CLOCKPRESCALER_PCLK_DIV1   ADC_CLOCK_SYNC_PCLK_DIV1   /* 这三个仅仅是为了兼容,已经不推荐使用 */

#define ADC_CLOCKPRESCALER_PCLK_DIV2   ADC_CLOCK_SYNC_PCLK_DIV2   

#define ADC_CLOCKPRESCALER_PCLK_DIV4   ADC_CLOCK_SYNC_PCLK_DIV4    

PLL2支持下面几种分频设置:


#define ADC_CLOCK_ASYNC_DIV1       ((uint32_t)0x00000000)                                       

#define ADC_CLOCK_ASYNC_DIV2       ((uint32_t)ADC_CCR_PRESC_0)                                  

#define ADC_CLOCK_ASYNC_DIV4       ((uint32_t)ADC_CCR_PRESC_1)                                   

#define ADC_CLOCK_ASYNC_DIV6       ((uint32_t)(ADC_CCR_PRESC_1|ADC_CCR_PRESC_0))                 

#define ADC_CLOCK_ASYNC_DIV8       ((uint32_t)(ADC_CCR_PRESC_2))                                

#define ADC_CLOCK_ASYNC_DIV10      ((uint32_t)(ADC_CCR_PRESC_2|ADC_CCR_PRESC_0))                 

#define ADC_CLOCK_ASYNC_DIV12      ((uint32_t)(ADC_CCR_PRESC_2|ADC_CCR_PRESC_1))                 

#define ADC_CLOCK_ASYNC_DIV16      ((uint32_t)(ADC_CCR_PRESC_2|ADC_CCR_PRESC_1|ADC_CCR_PRESC_0)) 

#define ADC_CLOCK_ASYNC_DIV32      ((uint32_t)(ADC_CCR_PRESC_3))                                

#define ADC_CLOCK_ASYNC_DIV64      ((uint32_t)(ADC_CCR_PRESC_3|ADC_CCR_PRESC_0))                 

#define ADC_CLOCK_ASYNC_DIV128     ((uint32_t)(ADC_CCR_PRESC_3|ADC_CCR_PRESC_1))                

#define ADC_CLOCK_ASYNC_DIV256     ((uint32_t)(ADC_CCR_PRESC_3|ADC_CCR_PRESC_1|ADC_CCR_PRESC_0)) 

有了这些认识后再看实际的分频配置就好理解了:


#if defined (ADC_CLOCK_SOURCE_PLL)

/* 采用PLL异步时钟,2分频,即72MHz/2 = 36MHz */

    AdcHandle.Init.ClockPrescaler        = ADC_CLOCK_ASYNC_DIV2;     

/* 采用AHB同步时钟,4分频,即200MHz/4 = 50MHz */     

#elif defined (ADC_CLOCK_SOURCE_AHB)

    AdcHandle.Init.ClockPrescaler        = ADC_CLOCK_SYNC_PCLK_DIV4;      

#endif

46.3.3 ADC的DMA配置

由于函数HAL_ADC_Start_DMA封装的DMA传输函数是HAL_DMA_Start_IT。而我们这里仅需要用到DMA传输,而用不到中断,所以不开启对应的NVIC即可,这里使用的是DMA1_Stream1,测量了PC0,Vbat/4,VrefInt和温度四个通道。


1.    /*

2.    ******************************************************************************************************

3.    *    函 数 名: bsp_InitADC

4.    *    功能说明: 初始化ADC,采用DMA方式进行多通道采样,采集了PC0, Vbat/4, VrefInt和温度

5.    *    形    参: 无

6.    *    返 回 值: 无

7.    ******************************************************************************************************

8.    */

9.    void bsp_InitADC(void)

10.    {

11.        ADC_HandleTypeDef   AdcHandle = {0};

12.        DMA_HandleTypeDef   DMA_Handle = {0};

13.        ADC_ChannelConfTypeDef   sConfig = {0};

14.        GPIO_InitTypeDef          GPIO_InitStruct;

15.    

16.      /* ## - 1 - 配置ADC采样的时钟 ####################################### */

17.    #if defined (ADC_CLOCK_SOURCE_PLL)

18.        /* 配置PLL2时钟为的72MHz,方便分频产生ADC最高时钟36MHz */

19.         RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

20.        PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC;

21.        PeriphClkInitStruct.PLL2.PLL2M = 25;

22.        PeriphClkInitStruct.PLL2.PLL2N = 504;

23.        PeriphClkInitStruct.PLL2.PLL2P = 7;

24.        PeriphClkInitStruct.PLL2.PLL2Q = 7;

25.        PeriphClkInitStruct.PLL2.PLL2R = 7;

26.        PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_0;

27.        PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;

28.        PeriphClkInitStruct.PLL2.PLL2FRACN = 0;

29.        PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2;

30.        if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)

31.        {

32.            Error_Handler(__FILE__, __LINE__);  

33.        }

34.    #elif defined (ADC_CLOCK_SOURCE_AHB)

35.      

36.      /* 使用AHB时钟的话,无需配置,默认选择*/

37.      

38.    #endif

39.    

40.        /* ## - 2 - 配置ADC采样使用的时钟 ####################################### */

41.        __HAL_RCC_GPIOC_CLK_ENABLE();

42.    

43.        GPIO_InitStruct.Pin = GPIO_PIN_0;

44.        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;

45.        GPIO_InitStruct.Pull = GPIO_NOPULL;

46.        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

47.      

48.        /* ## - 3 - 配置ADC采样使用的时钟 ####################################### */

49.        __HAL_RCC_DMA1_CLK_ENABLE();

50.        DMA_Handle.Instance                 = DMA1_Stream1;            /* 使用的DMA1 Stream1 */

51.        DMA_Handle.Init.Request             = DMA_REQUEST_ADC3;         /* 请求类型采用DMA_REQUEST_ADC3 */  

52.        DMA_Handle.Init.Direction           = DMA_PERIPH_TO_MEMORY;    /* 传输方向是从外设到存储器*/  

53.        DMA_Handle.Init.PeriphInc           = DMA_PINC_DISABLE;        /* 外设地址自增禁止 */ 

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。