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 输入范围 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 通道表如下所示:
③ 转换顺序
当任意 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 总转换时间的计算公式如下:
采样周期与 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 最大时钟频率为 36MHz。
⑥ 数据寄存器
ADC 转换完成后的数据输出寄存器。根据转换组的不同,规则组的完成转换的数据输出到 ADC_DR 寄存器,注入组的完成转换的数据输出到 ADC_JDRx 寄存器。假如是使用双重模式,规则组的数据也是存放在 ADC_DR 寄存器。
由 ADCx_CR2 寄存器的 ALIGN 位设置数据对齐方式,可选择:右对齐或者左对齐。
⑦ 中断
规则和注入组转换结束时能产生中断,当模拟看门狗状态位被设置时也能产生中断。它们在 ADC_SR 中都有独立的中断使能位。
模拟看门狗中断发生条件:首先通过 ADC_LTR 和 ADC_HTR 寄存器设置低阈值和高阈值,然后开启了模拟看门狗中断后,当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断。例如我们设置高阈值是 3.0V,那么模拟电压超过 3.0V 的时候,就会产生模拟看门狗中断,低阈值的情况类似。
规则组和注入组的转换结束后,除了产生中断外,还可以产生 DMA 请求,把转换好的数据存储在内存里面,防止读取不及时数据被覆盖。
三、ADC工作模式
【1】、单次转换模式和连续转换模式
【2】、扫描模式
【3】、不同模式组合的作用
四、ADC常用寄存器
4.1、ADC状态寄存器
4.2、ADC控制寄存器
该寄存器本章用到位 8 SCAN 位,用于设置扫描模式,由软件设置和清除,如果设置为 1,则使用扫描模式,如果为 0,则关闭扫描模式。在扫描模式下,由 ADC_SQRx 或 ADC_JSQRx 寄存器选中的通道被转换。如果设置了 EOCIE 或 JEOCIE,只会在最后一个通道转换完成后才会产生 EOC 或 JEOC 中断。
如果开启了扫描模式,多个通道,顺序扫描,挨个转换。
如果关闭了扫描模式,只转换通道中第一个通道。
位 0 ADON 位用于开关 AD 转换器。而位 1 CONT 位用于设置是否进行连续转换。位 11 ALIGN 用于设置数据对齐。位 EXTSEL[27:24] 用于选择启动规则转换组转换的外部事件。位 30 SWSTART 位用于开始规则通道的转换,我们每次转换(单次转换模式下)都需要向该位写 1。
如果开启了连续模式,通道组内转换一轮之后,继续转换下一轮,不会停。
如果关闭了连续模式,通道组内转换一轮之后,停止转换,等待下次启动。(单次转换)
4.3、ADC采样时间寄存器
这两个寄存器用于设置通道 0 ~ 18 的采样事件,每个通道占用 3 个位。对于每个要转换的通道,采样事件建议尽量长一点,以获得较高的准确度,但是这样会降低 ADC 的转换速率。
4.4、ADC规则序列寄存器
位 L[23:20]:用于存储规则序列的长度,取值范围:0 ~ 15。其他的 SQ13 ~ SQ16 则存储了规则序列中的第 13 ~ 16 通道的编号(编号范围:0 ~ 18)。另外两个规则序列寄存器同 ADC_SQR1 大同小异。
4.5、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);
形参 hdac 是 ADC_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)
Init:ADC初始化结构体,用于配置 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;
ClockPrescaler:ADC 预分频系数选择,可选的分频系数为 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);
形参 hdac 是 ADC_HandleTypeDef 结构体类型指针变量。
形参 sConfig 是 ADC_ChannelConfTypeDef 结构体类型指针变量,定义如下:
typedef struct
{
uint32_t Channel; // ADC转换通道
uint32_t Rank; // ADC转换顺序
uint32_t SamplingTime; // ADC采样周期
uint32_t Offset; // ADC偏移量
} ADC_ChannelConfTypeDef;
Channel:ADC 转换通道,范围:0 ~ 19。
Rank:在常规转换中的常规组的转换顺序,可以选择 1 ~ 16。
SamplingTime:ADC 的采样周期,最大 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);
形参 hdac 是 ADC_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);
形参 hdac 是 ADC_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);
形参 hdac 是 ADC_HandleTypeDef 结构体类型指针变量。
该函数返回读取的 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;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 百万级群聊的设计实践
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期