35. ADC

一、ADC简介

  ADC 即模拟数字转换器,英文详称 Analog-to-digital converter,可以将外部的 模拟信号转换为数字信号。STM32F4xx 系列芯片拥有 3 个 ADC,这些 ADC 可以独立使用,其中 ADC1 和 ADC2 还可以组成双重模式(提高采样率)。STM32 的 ADC 是 12 位逐次逼近型的模拟数字转换器。它有 19 个通道,可测量 16 个外部和 2 个内部信号源和 Vbat 通道的信号 ADC 中的各个通道的 A/D 转换可以单次、连续、扫描或间断模式执行。ADC 的结果可以以左对齐或者右对齐存储在 16 位数据寄存器中。ADC 具有模拟看门狗的特性,允许应用检测输入电压是否超过了用户自定义的阈值上限或下限。

  STM32F407 的 ADC 主要特性我们可以总结为以下几条:

  • 可配置 12 位、10 位、8 位或 6 位分辨率。
  • 转换结束、注入转换结束和发生模拟看门狗事件时产生中断。
  • 单次和连续转换模式。
  • 自校准。
  • 带内嵌数据一致性的数据对齐。
  • 采样间隔可以按通道分别编程。
  • 规则转换和注入转换均有外部触发选项。
  • 间断模式。
  • 双重模式(带 2 个或以上 ADC 的器件)。
  • ADC 转换时间:最大转换速率为 2.4MHz,转换时间为 0.41us。
  • ADC 供电要求:2.4V 到 3.6V。
  • ADC 输入范围:VREF–≤VIN≤VREF+。
  • 规则通道转换期间有 DMA 请求产生。

二、ADC框图

ADC框图

输入电压

  ADC 输入范围 VREF–≤VIN≤VREF+,最终还是由 VREF–、VREF+、VDDA 和 VSSA 决定的。

输入通道

  在确定好了 ADC 输入电压后,通过 ADC 的输入通道把外部输入的电压输送到 ADC 转换器中。ADC1 有 16 个外部通道和 3 个内部通道,而 ADC2和 ADC3 只有有 16 个外部通道。ADC1 的外部通道是通道 17、通道 18 和通道 19,分别连接到内部温度传感器、内部 Vrefint 和 Vbat。外部通道对应的是图中的 ADCx_IN0、ADCx_IN1、…、ADCx_IN15。ADC 通道表如下所示:

ADC通道表

转换顺序

  当任意 ADCx 多个通道以任意顺序进行一系列转换就诞生了成组转换,这里就有两种成组转换类型:规则组注入组。规则组允许最多 16 个输入通道进行转换,而注入组允许最多 4 个输入通道进行转换。

  规则组(规则通道),“规则” 就是按照一定的顺序,相当于正常运行的程序,平常用到最多也是规则组。

  注入组(注入通道),“注入” 就是打破原来的状态,相当于中断。当程序执行的时候,中断是可以打断程序的执行。同这个类似,注入组转换可以打断规则组的转换。假如在规则组转换过程中,注入组启动,那么注入组被转换完成之后,规则组才得以继续转换。

规则组和注入组的对比图

  规则组是允许 16 个通道进行转换的,那么就需要安排通道转换的次序即规则序列。规则序列寄存器有 3 个,分别为 SQR1、SQR2 和 SQR3。SQR3 控制规则序列中的第 1 个到第 6 个转换的通道;SQR2 控制规则序列中第 7 个到第 12 个转换的通道;SQR1 控制规则序列中第 13 个到第 16 个转换的通道。规则序列寄存器 SQRx 详表如下表所示:

规则序列寄存器

  注入序列,跟规则序列差不多,都是有顺序的安排。由于注入组最大允许 4 个通道输入,所以这里就使用了一个寄存器 JSQR。注入序列寄存器 JSQR 详表如下表所示:

注入序列寄存器

  注入序列的转换顺序是从 JSQx[4:0](x=4-JL[1:0]) 开始。

触发源

  在配置好输入通道以及转换顺序后,就可以进行触发转换了。ADC 的触发转换有两种方法:分别是通过软件或外部事件(也就是硬件)触发转换。

  写软件触发转换的方法:通过写 ADC_CR2 寄存器的 ADON 这个位来控制,写 1 就开始转换,写 0 就停止转换。

  外部事件触发转换的方法,有定时器和输入引脚触发等等。这里区分规则组和注入组。方法是:通过 ADC_CR2 寄存器的 EXTSET[2:0] 选择规则组的触发源,JEXTSET[2:0] 选择注入组的触发源。通过 ADC_CR2 的 EXTTRIG 和 JEXTTRIG 这两位去激活触发源。ADC3 的触发源和 ADC1/2 不同。

规则组外部触发

注入组外部触发

转换时间

  STM32F407 的 ADC 总转换时间的计算公式如下:

\[T_{CONV} = 采样时间 + N个采样周期 \]

  采样周期与 ADC 的分辨率有关,12 位精度的采样周期是 12,10 位精度的采样周期是 10,8 位精度的采样周期是 8,16位精度的采样周期是 6。

  采样时间可通过 ADC_SMPR1 和 ADC_SMPR2 寄存器中的 SMP[2:0] 位编程,ADC_SMPR2 控制的是通道 0 ~ 9,ADC_SMPR1 控制的是通道 10 ~ 18。所有通道都可以通过编程来控制使用不同的采样时间,可选采样时间值如下:

SMP 采样事件
000 3 个 ADC 时钟周期
001 15 个 ADC 时钟周期
010 28 个 ADC 时钟周期
011 56 个 ADC 时钟周期
100 84 个 ADC 时钟周期
101 112 个 ADC 时钟周期
110 144 个 ADC 时钟周期
111 480 个 ADC 时钟周期

  12 个周期是 ADC 输入时钟 ADC_CLK 决定的。ADC_CLK 是由 APB2 经过分频产生,分频系数是由 RCC_CFGR 寄存器中的 PPRE2[2:0] 进行设置,有 2/4/6/8/16 分频选项。

ADC时钟的设置

ADC 最大时钟频率为 36MHz。

数据寄存器

  ADC 转换完成后的数据输出寄存器。根据转换组的不同,规则组的完成转换的数据输出到 ADC_DR 寄存器,注入组的完成转换的数据输出到 ADC_JDRx 寄存器。假如是使用双重模式,规则组的数据也是存放在 ADC_DR 寄存器。

数据寄存器

  由 ADCx_CR2 寄存器的 ALIGN 位设置数据对齐方式,可选择:右对齐或者左对齐。

中断

  规则和注入组转换结束时能产生中断,当模拟看门狗状态位被设置时也能产生中断。它们在 ADC_SR 中都有独立的中断使能位。

  模拟看门狗中断发生条件:首先通过 ADC_LTR 和 ADC_HTR 寄存器设置低阈值和高阈值,然后开启了模拟看门狗中断后,当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断。例如我们设置高阈值是 3.0V,那么模拟电压超过 3.0V 的时候,就会产生模拟看门狗中断,低阈值的情况类似。

  规则组和注入组的转换结束后,除了产生中断外,还可以产生 DMA 请求,把转换好的数据存储在内存里面,防止读取不及时数据被覆盖。

ADC中断事件

三、ADC工作模式

【1】、单次转换模式和连续转换模式

ADC转换模式设置

ADC转换模式

【2】、扫描模式

扫描模式设置

扫描模式

【3】、不同模式组合的作用

不同模式组合的作用

四、ADC常用寄存器

4.1、ADC状态寄存器

ADC状态寄存器

4.2、ADC控制寄存器

ADC控制寄存器1

  该寄存器本章用到位 8 SCAN 位,用于设置扫描模式,由软件设置和清除,如果设置为 1,则使用扫描模式,如果为 0,则关闭扫描模式。在扫描模式下,由 ADC_SQRx 或 ADC_JSQRx 寄存器选中的通道被转换。如果设置了 EOCIE 或 JEOCIE,只会在最后一个通道转换完成后才会产生 EOC 或 JEOC 中断。

如果开启了扫描模式,多个通道,顺序扫描,挨个转换。

如果关闭了扫描模式,只转换通道中第一个通道。

ADC控制寄存器2

  位 0 ADON 位用于开关 AD 转换器。而位 1 CONT 位用于设置是否进行连续转换。位 11 ALIGN 用于设置数据对齐。位 EXTSEL[27:24] 用于选择启动规则转换组转换的外部事件。位 30 SWSTART 位用于开始规则通道的转换,我们每次转换(单次转换模式下)都需要向该位写 1。

如果开启了连续模式,通道组内转换一轮之后,继续转换下一轮,不会停。

如果关闭了连续模式,通道组内转换一轮之后,停止转换,等待下次启动。(单次转换)

4.3、ADC采样时间寄存器

ADC采样时间寄存器1

ADC采样时间寄存器2

  这两个寄存器用于设置通道 0 ~ 18 的采样事件,每个通道占用 3 个位。对于每个要转换的通道,采样事件建议尽量长一点,以获得较高的准确度,但是这样会降低 ADC 的转换速率。

4.4、ADC规则序列寄存器

ADC规则序列寄存器1

  位 L[23:20]:用于存储规则序列的长度,取值范围:0 ~ 15。其他的 SQ13 ~ SQ16 则存储了规则序列中的第 13 ~ 16 通道的编号(编号范围:0 ~ 18)。另外两个规则序列寄存器同 ADC_SQR1 大同小异。

ADC规则序列寄存器2

ADC规则序列寄存器3

4.5、ADC规则数据寄存器

ADC规则数据寄存器

五、ADC对应的通道引脚

通道号 ADC1 ADC2 ADC3
通道 0 PA0 PA0 PA0
通道 1 PA1 PA1 PA1
通道 2 PA2 PA2 PA2
通道 3 PA3 PA3 PA3
通道 4 PA4 PA4 PF6
通道 5 PA5 PA5 PF7
通道 6 PA6 PA6 PF8
通道 7 PA7 PA7 PF9
通道 8 PB0 PB0 PF10
通道 9 PB1 PB1 PF3
通道 10 PC0 PC0 PC0
通道 11 PC1 PC0 PC1
通道 12 PC2 PC2 PC2
通道 13 PC3 PC3 PC3
通道 14 PC4 PC3 PF4
通道 15 PC5 PC5 PF5

六、单通道ADC采集

6.1、使能ADC对应的GPIO时钟

  使用 ADC 时钟:

#define __HAL_RCC_ADC1_CLK_ENABLE()     do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC1EN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC1EN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_ADC2_CLK_ENABLE()   do { \
                                      __IO uint32_t tmpreg = 0x00U; \
                                      SET_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC2EN);\
                                      /* Delay after an RCC peripheral clock enabling */ \
                                      tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC2EN);\
                                      UNUSED(tmpreg); \
                                      } while(0U)

#define __HAL_RCC_ADC3_CLK_ENABLE()   do { \
                                      __IO uint32_t tmpreg = 0x00U; \
                                      SET_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC3EN);\
                                      /* Delay after an RCC peripheral clock enabling */ \
                                      tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC3EN);\
                                      UNUSED(tmpreg); \
                                      } while(0U)

  使能对应的 GPIO 时钟:

#define __HAL_RCC_GPIOA_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_GPIOB_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_GPIOC_CLK_ENABLE()  do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_GPIOF_CLK_ENABLE()   do { \
                                       __IO uint32_t tmpreg = 0x00U; \
                                       SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOFEN);\
                                       /* Delay after an RCC peripheral clock enabling */ \
                                       tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOFEN);\
                                       UNUSED(tmpreg); \
                                       } while(0U)

6.2、配置ADC工作参数

  ADC 的初始化函数,其声明如下:

HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef *hadc);

  形参 hdacADC_HandleTypeDef 结构体类型指针变量,其定义如下:

typedef struct
{
    ADC_TypeDef *Instance;                      // ADC寄存器基地址
    ADC_InitTypeDef Init;                       // ADC参数初始化结构体变量
    __IO uint32_t NbrOfCurrentConversionRank;   // 当前转换等级的ADC数
    DMA_HandleTypeDef *DMA_Handle;              // DMA配置结构体
    HAL_LockTypeDef Lock;                       // ADC锁定对象
    __IO uint32_t State;                        // ADC工作状态
    __IO uint32_t ErrorCode;                    // ADC错误代码
} ADC_HandleTypeDef;

  Instance指向 ADC 寄存器基地址。实际上这个基地址 HAL 库已经定义好了,可以选择范围如下:

#define ADC1                ((ADC_TypeDef *) ADC1_BASE)
#define ADC2                ((ADC_TypeDef *) ADC2_BASE)
#define ADC3                ((ADC_TypeDef *) ADC3_BASE)

  InitADC初始化结构体,用于配置 ADC 的工作参数。

typedef struct
{
    uint32_t ClockPrescaler;                // 设置预分频系数,即PRESC[3:O]位
    uint32_t Resolution;                    // 配置ADC的分辨率
    uint32_t DataAlign;                     // 数据对齐方式
    uint32_t ScanConvMode;                  // 扫描模式
    uint32_t EOCSelection;                  // 转换完成标志位
    FunctionalState ContinuousConvMode;     // 是否连续转换
    uint32_t NbrOfConversion;               // 规格转换序列数目
    FunctionalState DiscontinuousConvMode;  // 不连续采样模式
    uint32_t NbrOfDiscConversion;           // 不连续采样通道
    uint32_t ExternalTrigConv;              // ADC外部触发源选
    uint32_t ExternalTrigConvEdge;          // ADC外部触发极性
    FunctionalState DMAContinuousRequests;  // DMA连续请求转换
} ADC_InitTypeDef;

  ClockPrescalerADC 预分频系数选择,可选的分频系数为 2、4、6、8。

#define ADC_CLOCK_SYNC_PCLK_DIV2    0x00000000U
#define ADC_CLOCK_SYNC_PCLK_DIV4    ((uint32_t)ADC_CCR_ADCPRE_0)
#define ADC_CLOCK_SYNC_PCLK_DIV6    ((uint32_t)ADC_CCR_ADCPRE_1)
#define ADC_CLOCK_SYNC_PCLK_DIV8    ((uint32_t)ADC_CCR_ADCPRE)

  Resolution配置 ADC 的分辨率,可选的分辨率有 12 位、10 位、8 位和 6 位。分辨率越高,转换数据精度越高,转换时间也越长;反之分辨率越低,转换数据精度越低,转换时间也越短。

#define ADC_RESOLUTION_12B  0x00000000U
#define ADC_RESOLUTION_10B  ((uint32_t)ADC_CR1_RES_0)
#define ADC_RESOLUTION_8B   ((uint32_t)ADC_CR1_RES_1)
#define ADC_RESOLUTION_6B   ((uint32_t)ADC_CR1_RES)

  DataAlign转换结果数据对齐模式,可选右对齐 ADC_DataAlign_Right 或者左对齐ADC_DataAlign_Left。一般我们选择右对齐模式。

#define ADC_DATAALIGN_RIGHT      0x00000000U
#define ADC_DATAALIGN_LEFT       ((uint32_t)ADC_CR2_ALIGN)

  ScanConvMode配置是否使用扫描。如果是单通道转换使用 DISABLE,如果是多通道转换使用 ENABLE。

typedef enum
{
  DISABLE = 0U,
  ENABLE = !DISABLE
} FunctionalState;

  EOCSelection指定转换结束时是否产生 EOS 中断或事件标志,可选值如下:

#define ADC_EOC_SEQ_CONV              0x00000000U
#define ADC_EOC_SINGLE_CONV           0x00000001U
#define ADC_EOC_SINGLE_SEQ_CONV       0x00000002U  /*!< reserved for future use */

  ContinuousConvMode配置自动连续转换还是单次转换。使用 ENABLE 配置为使能自动连续转换;使用 DISABLE 配置为单次转换,转换一次后停止需要手动控制才重新启动转换。

typedef enum
{
  DISABLE = 0U,
  ENABLE = !DISABLE
} FunctionalState;

  NbrOfConversion设置常规转换通道数目,范围是:1 ~ 16。

  DiscontinuousConvMode配置是否使用不连续的采样模式,比如要转换的通道有 1、2、5、7、8、9,那么第一次触发会进行通道 1 与通道 2,下次触发就是转换通道 5 与通道 7,这样不连续的转换,依次类推。此参数只有将 ScanConvMode 使能,还有 ContinuousConvMode失能的情况下才有效,不可同时使能。

typedef enum
{
  DISABLE = 0U,
  ENABLE = !DISABLE
} FunctionalState;

  NbrOfDiscConversion不连续采样通道数

  ExternalTrigConv外部触发方式的选择,如果使用软件触发,那么外部触发会关闭。

#define ADC_EXTERNALTRIGCONV_T1_CC1    0x00000000U
#define ADC_EXTERNALTRIGCONV_T1_CC2    ((uint32_t)ADC_CR2_EXTSEL_0)
#define ADC_EXTERNALTRIGCONV_T1_CC3    ((uint32_t)ADC_CR2_EXTSEL_1)
#define ADC_EXTERNALTRIGCONV_T2_CC2    ((uint32_t)(ADC_CR2_EXTSEL_1 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T2_CC3    ((uint32_t)ADC_CR2_EXTSEL_2)
#define ADC_EXTERNALTRIGCONV_T2_CC4    ((uint32_t)(ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T2_TRGO   ((uint32_t)(ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1))
#define ADC_EXTERNALTRIGCONV_T3_CC1    ((uint32_t)(ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T3_TRGO   ((uint32_t)ADC_CR2_EXTSEL_3)
#define ADC_EXTERNALTRIGCONV_T4_CC4    ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T5_CC1    ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_1))
#define ADC_EXTERNALTRIGCONV_T5_CC2    ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_1 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T5_CC3    ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_2))
#define ADC_EXTERNALTRIGCONV_T8_CC1    ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T8_TRGO   ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1))
#define ADC_EXTERNALTRIGCONV_Ext_IT11  ((uint32_t)ADC_CR2_EXTSEL)
#define ADC_SOFTWARE_START             ((uint32_t)ADC_CR2_EXTSEL + 1U)

  ExternalTrigConvEdge外部触发极性选择,如果使用外部触发,可以选择触发的极性,可选有禁止触发检测、上升沿触发检测、下降沿触发检测以及上升沿和下降沿均可触发检测。

  DMAContinuousRequests指定 DMA 请求是否以一次性模式执行(当达到转换次数时,DMA 传输停止)或在连续模式下(DMA 传输无限制,无论转换的数量)。注:在连续模式下,DMA 必须配置为循环模式。否则,当达到 DMA 缓冲区最大指针时将触发溢出。注意:当常规组和注入组都没有转换时(禁用 ADC,或启用 ADC,没有连续模式或可以启动转换的外部触发器),必须修改此参数。该参数可设置为 “启用” 或 “禁用”。

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

typedef enum 
{
    HAL_OK = 0x00U,             // 成功
    HAL_ERROR = 0x01U,          // 错误
    HAL_BUSY = 0x02U,           // 忙碌
    HAL_TIMEOUT = 0x03U         // 超时
} HAL_StatusTypeDef;

6.3、ADC底层初始化

  HAL 库中,提供 HAL_GPIO_Init() 函数用于配置 GPIO 功能模式,初始化 GPIO。该函数的声明如下:

void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init);

  该函数的第一个形参 GPIOx 用来指定端口号,可选值如下:

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)

  第二个参数是 GPIO_InitTypeDef 类型的结构体变量,用来设置 GPIO 的工作模式,其定义如下:

typedef struct
{
  uint32_t Pin;         // 引脚号
  uint32_t Mode;        // 模式设置
  uint32_t Pull;        // 上下拉设置
  uint32_t Speed;       // 速度设置
  uint32_t Alternate;   // 复用功能设置
}GPIO_InitTypeDef;

  成员 Pin 表示 引脚号,范围:GPIO_PIN_0 到 GPIO_PIN_15。

#define GPIO_PIN_0                 ((uint16_t)0x0001)  /* Pin 0 selected    */
#define GPIO_PIN_1                 ((uint16_t)0x0002)  /* Pin 1 selected    */
#define GPIO_PIN_2                 ((uint16_t)0x0004)  /* Pin 2 selected    */
#define GPIO_PIN_3                 ((uint16_t)0x0008)  /* Pin 3 selected    */
#define GPIO_PIN_4                 ((uint16_t)0x0010)  /* Pin 4 selected    */
#define GPIO_PIN_5                 ((uint16_t)0x0020)  /* Pin 5 selected    */
#define GPIO_PIN_6                 ((uint16_t)0x0040)  /* Pin 6 selected    */
#define GPIO_PIN_7                 ((uint16_t)0x0080)  /* Pin 7 selected    */
#define GPIO_PIN_8                 ((uint16_t)0x0100)  /* Pin 8 selected    */
#define GPIO_PIN_9                 ((uint16_t)0x0200)  /* Pin 9 selected    */
#define GPIO_PIN_10                ((uint16_t)0x0400)  /* Pin 10 selected   */
#define GPIO_PIN_11                ((uint16_t)0x0800)  /* Pin 11 selected   */
#define GPIO_PIN_12                ((uint16_t)0x1000)  /* Pin 12 selected   */
#define GPIO_PIN_13                ((uint16_t)0x2000)  /* Pin 13 selected   */
#define GPIO_PIN_14                ((uint16_t)0x4000)  /* Pin 14 selected   */
#define GPIO_PIN_15                ((uint16_t)0x8000)  /* Pin 15 selected   */

  成员 Mode 是 GPIO 的 模式选择,有以下选择项:

#define  GPIO_MODE_ANALOG                       MODE_ANALOG             // 模式功能

  成员 Pull 用于 配置上下拉电阻,有以下选择项:

#define  GPIO_NOPULL        0x00000000U     // 无上下拉
#define  GPIO_PULLUP        0x00000001U     // 上拉
#define  GPIO_PULLDOWN      0x00000002U     // 下拉

6.4、配置ADC通道

  在 HAL 库中,通过 HAL_ADC_ConfigChannel() 函数来设置配置 ADC 的通道,根据需求设置通道、序列、采样时间和校准配置单端输入模式或差分输入模式等。

HAL_StatusTypeDef HAL_ADC_ConfigChannel(ADC_HandleTypeDef *hadc, ADC_ChannelConfTypeDef *sConfig);

  形参 hdacADC_HandleTypeDef 结构体类型指针变量。

  形参 sConfigADC_ChannelConfTypeDef 结构体类型指针变量,定义如下:

typedef struct
{
    uint32_t Channel;                   //  ADC转换通道
    uint32_t Rank;                      //  ADC转换顺序
    uint32_t SamplingTime;              //  ADC采样周期
    uint32_t Offset;                    //  ADC偏移量
} ADC_ChannelConfTypeDef;

  ChannelADC 转换通道,范围:0 ~ 19。

  Rank在常规转换中的常规组的转换顺序,可以选择 1 ~ 16。

  SamplingTimeADC 的采样周期,最大 480 个 ADC 时钟周期,要求采样周期尽量大,以减少误差。

#define ADC_SAMPLETIME_3CYCLES    0x00000000U
#define ADC_SAMPLETIME_15CYCLES   ((uint32_t)ADC_SMPR1_SMP10_0)
#define ADC_SAMPLETIME_28CYCLES   ((uint32_t)ADC_SMPR1_SMP10_1)
#define ADC_SAMPLETIME_56CYCLES   ((uint32_t)(ADC_SMPR1_SMP10_1 | ADC_SMPR1_SMP10_0))
#define ADC_SAMPLETIME_84CYCLES   ((uint32_t)ADC_SMPR1_SMP10_2)
#define ADC_SAMPLETIME_112CYCLES  ((uint32_t)(ADC_SMPR1_SMP10_2 | ADC_SMPR1_SMP10_0))
#define ADC_SAMPLETIME_144CYCLES  ((uint32_t)(ADC_SMPR1_SMP10_2 | ADC_SMPR1_SMP10_1))
#define ADC_SAMPLETIME_480CYCLES  ((uint32_t)ADC_SMPR1_SMP10)

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

6.5、启动AD转换器

  配置好 ADC 通道之后,通过 HAL_ADC_Start() 函数启动 AD 转换器。

HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef *hadc);

  形参 hdacADC_HandleTypeDef 结构体类型指针变量。

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

6.6、等待规则通道转换完成

  启动 ADC 后,我们需要调用 HAL_ADC_PollForConversion() 等待上一次转换结束。

HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef *hadc, uint32_t Timeout);

  形参 hdacADC_HandleTypeDef 结构体类型指针变量。

  形参 Timeout超时时间

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

6.7、读取ADC值

  等待上一次转换结束,我可以通过 HAL_ADC_GetValue() 来读取 ADC 值。

uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef *hadc);

  形参 hdacADC_HandleTypeDef 结构体类型指针变量。

  该函数返回读取的 ADC 值。

七、原理图

ADC模块

ADC通道

  使用短接片可以将 STM_ADC(PA5)连接在电位器(R_ADC)上(开发板上 STM_ADC 对应的丝印为 ADC,R_ADC 对应的丝印是 R),调节电位器即可改变电压,通过 ADC 转换即可检测此电压值。

八、程序源码

  ADC 初始化函数:

ADC_HandleTypeDef g_adc1_handle;

/**
 * @brief ADC初始化函数
 * 
 * @param hadc ADC句柄
 * @param ADCx ADC寄存器基地址
 * @param numOfConversion 常规转换的通道数
 * @param continuousConvMode ADC是否连续转换, ENABLE:连续转换, DISABLE:单次转换
 * @param dmaContinuousRequests DMA是否连续请求, ENABLE:使用DMA, DISABLE:不使用DMA
 */
void ADC_Init(ADC_HandleTypeDef *hadc, ADC_TypeDef *ADCx,uint32_t numOfConversion, FunctionalState continuousConvMode, FunctionalState dmaContinuousRequests)
{
    hadc->Instance = ADCx;                                                      // ADC寄存器基地址
    hadc->Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;                       // ADC时钟分频因子
    hadc->Init.Resolution = ADC_RESOLUTION_12B;                                 // 12位模式
    hadc->Init.DataAlign = ADC_DATAALIGN_RIGHT;                                 // 数据右对齐
    hadc->Init.EOCSelection = DISABLE;                                          // 转换完成标志位
    hadc->Init.ScanConvMode = ENABLE;                                           // 配置是否使用扫描
    hadc->Init.ContinuousConvMode = continuousConvMode;                         // ADC是否自动连续转换
    hadc->Init.NbrOfConversion = numOfConversion;                               // 配置常规转换通道
    hadc->Init.DiscontinuousConvMode = DISABLE;                                 // 是否使用不连续的采样模式   
    hadc->Init.NbrOfDiscConversion = 0;                                         // 不连续采样通道数  
    hadc->Init.ExternalTrigConv = ADC_SOFTWARE_START;                           // 外部触发方式的选择
    hadc->Init.DMAContinuousRequests = dmaContinuousRequests;                   // 是否开启DMA连续转换
    HAL_ADC_Init(hadc);
}

  ADC 底层初始化函数:

/**
 * @brief ADC底层初始化函数
 * 
 * @param hadc ADC句柄
 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    if (hadc->Instance == ADC1)
    {
        __HAL_RCC_ADC1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();

        GPIO_InitStruct.Pin = GPIO_PIN_5;                                       // PA5引脚设置为ADC1的通道5引脚
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;                                // 模拟功能
        GPIO_InitStruct.Pull = GPIO_NOPULL;                                     // 不使用上下拉
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
}

  ADC 通道配置函数:

/**
 * @brief ADC通道配置函数
 * 
 * @param hadc ADC句柄
 * @param channel ADC通道,可选值: ADC_CHANNEL_0 ~ ADC_CHANNEL_18
 * @param rank 采样序列
 * @param samplingTime 采样时间,可选值:ADC_SAMPLETIME_xCYCLES,x: [3,15,28,56,112,144,480]
 */
void ADC_ConfigChannel(ADC_HandleTypeDef *hadc, uint32_t channel, uint32_t rank, uint32_t samplingTime)
{
    ADC_ChannelConfTypeDef ADC_ChannelConfStruct = {0};

    ADC_ChannelConfStruct.Channel = channel;                                    // ADC通道
    ADC_ChannelConfStruct.Rank = rank;                                          // 采样序列
    ADC_ChannelConfStruct.SamplingTime = samplingTime;                          // 采样时间
    HAL_ADC_ConfigChannel(hadc, &ADC_ChannelConfStruct);
}

  main() 函数:

int main(void)
{
    uint16_t value = 0;
    double result = 0;

    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);

    UART_Init(&g_usart1_handle, USART1, 115200);

    ADC_Init(&g_adc1_handle, ADC1, 1, ENABLE, DISABLE);
    ADC_ConfigChannel(&g_adc1_handle, ADC_CHANNEL_5, 1, ADC_SAMPLETIME_480CYCLES);

    HAL_ADC_Start(&g_adc1_handle);

    while (1)
    {
        HAL_ADC_PollForConversion(&g_adc1_handle, 10);
        value = HAL_ADC_GetValue(&g_adc1_handle);
        printf("value: %d\r\n", value);
        result = (double)value * 3.3 / 4096;
        printf("result: %.2lf\r\n", result);
        HAL_Delay(1000);
    }
  
    return 0;
}
posted @ 2023-12-28 18:02  星光樱梦  阅读(24)  评论(0编辑  收藏  举报