一、ADC简介



测量电压值:在STM32F103系列里面大部分场景里面都是0到3.3V
ADC的值:为什么是从0到4095?STM32 常用 ADC 是 12 位分辨率,对应的数字范围就是 0 到 2¹²-1(即 4095)。
扩展借助 "传感器" 将亮度、湿度等非电物理量转换为 ADC 可采集的模拟电压(0~VREF+,通常 0~3.3V),再通过 ADC 采集该电压并换算为对应的物理量值

二、ADC的外设关键知识点












多通道的时候需要配合DMA数据转移,否则后来的数据会被覆盖
三、ADC程序实现


adc.c
/**
****************************************************************************************************
* @file adc.c
* @author 正点原子团队(ALIENTEK)
* @version V1.0
* @date 2020-04-23
* @brief ADC 驱动代码
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 MiniSTM32 V4开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
* 修改说明
* V1.0 20200423
* 第一次发布
*
****************************************************************************************************
*/
#include "./BSP/ADC/adc.h"
#include "./SYSTEM/delay/delay.h"
#include "stm32f1xx_hal_adc.h"
#include "stm32f1xx_hal_rcc.h"
#include "stm32f1xx_hal_def.h"
ADC_HandleTypeDef g_adc_handle; /* ADC句柄 */
/**
* @brief ADC初始化函数
* @note 本函数支持ADC1/ADC2任意通道, 但是不支持ADC3
* 我们使用12位精度, ADC采样时钟=12M, 转换时间为: 采样周期 + 12.5个ADC周期
* 设置最大采样周期: 239.5, 则转换时间 = 252 个ADC周期 = 21us
* @param 无
* @retval 无
*/
void adc_init(void) //初始化函数
{
// RCC_PeriphCLKInitTypeDef adc_clk_config;
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
// adc_clk_config.PeriphClockSelection = RCC_PERIPHCLK_ADC;
// adc_clk_config.AdcClockSelection = RCC_ADCPCLK2_DIV6 ;
RCC->CFGR &= ~RCC_CFGR_ADCPRE; // 清除ADC预分频位
RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6; // 设置预分频系数为6
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin = GPIO_PIN_3;
gpio_init_struct.Mode = GPIO_MODE_ANALOG ;
HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* 初始化LED0引脚 */
g_adc_handle.Instance = ADC1; /* 选择哪个ADC */
g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 数据对齐方式:右对齐 */
g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; /* 非扫描模式,仅用到一个通道 */
g_adc_handle.Init.ContinuousConvMode = DISABLE; /* 关闭连续转换模式 */
g_adc_handle.Init.NbrOfConversion = 1; /* 赋值范围是1~16,本实验用到1个规则通道序列 */
g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; /* 触发转换方式:软件触发 */
HAL_ADC_Init(&g_adc_handle); /* 初始化 */
HAL_ADCEx_Calibration_Start(&g_adc_handle); /* 校准ADC */
// 定义ADC通道配置结构体(用于配置ADC的采集通道、转换优先级、采样时间等参数)
ADC_ChannelConfTypeDef adc_ch_config;
// 配置要采集的ADC通道为通道3(对应GPIOA_PIN_3,与硬件接线的模拟输入引脚对应)
adc_ch_config.Channel = ADC_CHANNEL_3;
// 配置该通道在规则转换序列中的优先级(排名为1,即第一个进行转换,单通道采集时固定设为1)
adc_ch_config.Rank = 1;
// 配置该通道的采样时间为239.5个ADC时钟周期
// 采样时间越长,抗干扰能力越强,采集精度越高,但转换速度越慢(此为F1系列最大采样时间,适合高精度采集场景)
adc_ch_config.SamplingTime = ADC_SAMPLETIME_239CYCLES_5 ;
// 调用HAL库通道配置函数,将上述配置应用到ADC1(g_adc_handle为ADC1的句柄)
// 执行后,ADC1将按照配置的通道、优先级和采样时间进行模拟信号采集
HAL_ADC_ConfigChannel(&g_adc_handle,&adc_ch_config);
}
/**
* @brief ADC数据读取函数(获取12位ADC转换结果)
* @param 无
* @retval uint16_t:返回ADC转换后的原始数值(范围0~4095,对应电压0~3.3V)
*/
uint16_t adc_get_date(void) //数据读取
{
// 启动ADC规则通道的转换(软件触发,开始采集模拟信号)
HAL_ADC_Start(&g_adc_handle);
// 等待ADC转换完成,超时时间设置为10ms
// 若在10ms内转换完成,函数返回HAL_OK;超时则返回HAL_TIMEOUT,此处简化未做超时判断
HAL_ADC_PollForConversion(&g_adc_handle,10);
// 获取ADC转换后的原始数据,并强制转换为16位无符号整数返回
// 转换结果为12位精度,存储在ADC数据寄存器中,取值范围0~4095
return (uint16_t)HAL_ADC_GetValue(&g_adc_handle);
}
(1)RCC_PeriphCLKInitTypeDef 这个结构体属于F4系列,F1系列没有,所以使用
RCC->CFGR &= ~RCC_CFGR_ADCPRE; // 清除ADC预分频位
RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6; // 设置预分频系数为6 代替
(2)uint16_t adc_get_date(void)这个函数需要在adc.h中定义(在非main.c文件中自定义的函数或变量都需要在该函数或变量所处xx.c(源文件)对应的xx.h(头文件)中定义,如下例
// adc.c(源文件,放函数的实现)
#include "adc.h" // 包含对应的头文件
// 定义函数(实现ADC采集逻辑)
uint16_t adc_get_date(void)
{
HAL_ADC_Start(&g_adc_handle);
HAL_ADC_PollForConversion(&g_adc_handle, 100);
return (uint16_t)HAL_ADC_GetValue(&g_adc_handle);
}
// adc.h(头文件,放函数的声明)
#ifndef __ADC_H // 防止头文件重复包含
#define __ADC_H
// 声明函数(告诉编译器:这个函数在其他文件中定义,可被外部调用)
uint16_t adc_get_date(void);
// 若有其他函数/变量,也在这里声明(如adc_init)
void adc_init(void);
#endif
// main.c(其他文件,调用函数)
#include "adc.h" // 包含头文件,即可使用adc_get_date
int main(void)
{
uint16_t val = adc_get_date(); // 直接调用,无警告/错误
// ...
}
举一反三
// adc.c中定义变量
ADC_HandleTypeDef g_adc_handle; // 定义全局变量
// adc.h中声明变量(用extern)
extern ADC_HandleTypeDef g_adc_handle; // 声明:该变量在其他文件中定义
main.c
/**
******************************************************************************
* @file main.c
* @author 正点原子团队(ALIENTEK)
* @version V1.0
* @date 2020-08-20
* @brief 新建工程实验-HAL库版本 实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
******************************************************************************
* @attention
*
* 实验平台:正点原子 STM32F103 开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
******************************************************************************
*/
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "led.h"
#include "./BSP/ADC/adc.h"
//void led_init(void);
/* LED初始化函数声明 */
int main(void)
{
uint16_t adc_date;
float adc_vol;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200);
led_init(); /* LED初始化 */
adc_init();
while(1)
{
adc_date = adc_get_date();
printf("ADC原始数据: %d\r\n",adc_date);
adc_vol = (adc_date * 3.3)/4095;
printf("ADC电压值: %.2f\r\n",adc_vol);
delay_ms(100);
}
}
实验结果PA3不接

当前 PA3 未连接任何信号时,ADC 输出固定为 4095(对应电压 3.3V),是STM32F103 ADC 模拟输入引脚悬空的正常现象,原因及验证方法如下:
一、原因:模拟输入引脚悬空时,内部高阻抗导致采集到电源电压
STM32 的 ADC 模拟输入引脚是高阻抗输入,当引脚悬空时,没有外部信号拉低 / 拉偏电压,引脚会通过 PCB 的寄生电容 / 电阻耦合到电源电压(3.3V),因此 ADC 采集到的原始值会接近满量程(12 位 ADC 的满量程是 4095,对应 3.3V)。
二、验证方法:给 PA3 接不同信号,观察输出变化
-
接 GND(地) :将 PA3 引脚直接连接到开发板的 GND 引脚,此时 ADC 原始数据应接近
0,电压值接近0V。 -
接电位器(可变电阻) :用一个 10K 电位器,一端接 3.3V,一端接 GND,中间抽头接 PA3,旋转电位器时,ADC 原始数据会在
0~4095之间变化,电压值对应0~3.3V。 -
接传感器(如光敏电阻):若连接光敏电阻等模拟传感器,ADC 值会随外部环境(光线强度)变化。
三、总结
PA3 悬空时输出 4095 是正常的硬件特性,并非代码问题。只需给 PA3 连接实际的模拟输入信号(如 GND、电位器、传感器),即可看到 ADC 值随外部信号变化
PA3接GND
