前言
在嵌入式系统中,模拟信号与数字信号的转换是连接物理世界与数字系统的核心环节。ADC(Analog-to-Digital Converter,模数转换器)作为实现这一转换的关键外设,被广泛应用于传感器数据采集(如温湿度、光照、压力)、电池电压监测、音频信号处理等场景。STM32系列芯片集成了高性能ADC外设,支持多通道、高分辨率、多种转换模式,能满足从低速高精度到高速实时采集的多样化需求。
本文将从ADC基础原理出发,系统解析STM32 ADC的外设结构、工作模式、硬件设计要点与软件配置方法,通过实战案例(单通道采集、多通道扫描、DMA传输、注入通道等)展示不同场景下的应用,并提供精度优化与调试技巧,帮助嵌入式开发者从入门到精通STM32 ADC的使用。
一、ADC基础原理
1.1 什么是ADC?
ADC是将连续变化的模拟信号(如电压、电流)转换为离散数字信号的器件。其核心指标包括:
- 分辨率:表示ADC能区分的最小模拟信号变化,通常以位数表示(如12位ADC,可将满量程分为2¹²=4096个等级);
- 转换速率:单位时间内完成的转换次数(如1MHz表示每秒转换100万次);
- 精度:转换结果与真实值的偏差(包括非线性误差、偏移误差等);
- 量程:可测量的模拟信号范围(如0~3.3V)。
STM32的ADC为逐次逼近型ADC,通过内部比较器与DAC逐位逼近输入信号,平衡了转换速度与精度,适合中高速采集场景。
1.2 ADC转换流程
一次完整的ADC转换包括采样-保持 和量化-编码两个阶段:
- 采样-保持:通过采样开关将模拟信号接入采样电容,在采样结束后断开开关,保持电容上的电压稳定,确保转换期间信号不变;
- 量化-编码 :将保持的模拟电压与基准电压比较,转换为对应的数字量(如12位ADC中,03.3V对应04095)。
公式 :数字量 = (输入电压 / 参考电压) × (2^分辨率 - 1)
例如:12位ADC,参考电压3.3V,输入电压1.65V时,数字量 = (1.65/3.3)×4095 = 2047。
1.3 STM32 ADC的核心特性
STM32不同系列的ADC性能略有差异(以主流的F103、F407、H7为例),共性特性包括:
- 分辨率:12位(部分型号支持10/8/6位可调);
- 转换速率:最高可达2.4MHz(F103)、2.8MHz(F407)、36MHz(H7);
- 通道数量:16个外部通道(GPIO引脚)+ 内部通道(如温度传感器、参考电压、电池电压监测);
- 工作模式:单通道单次转换、多通道扫描、连续转换、间断模式等;
- 触发方式:软件触发、定时器触发、外部中断触发;
- 数据对齐:左对齐或右对齐(影响数字量的存储格式);
- 校准功能:支持自校准,降低偏移误差。
二、STM32 ADC外设结构
STM32的ADC外设采用"多通道共享转换器"架构,通过通道选择器切换输入信号,核心结构包括模拟多路开关、采样保持电路、ADC核心、数据寄存器、触发控制器等。
2.1 通道配置
STM32 ADC的通道分为外部通道 和内部通道:
- 外部通道 :通过GPIO引脚输入(如F103的PA0PA7、PB0PB1等),每个通道对应特定引脚(需参考数据手册的"ADC通道引脚映射表");
- 内部通道 :集成在芯片内部,无需外部引脚,包括:
- 温度传感器(ADC1_IN16,F103系列);
- 内部参考电压VREFINT(ADC1_IN17);
- 电池电压监测VBAT(通过ADC1_IN18,需配置VBAT引脚)。
示例:STM32F103C8T6的ADC1外部通道映射:
- PA0 → ADC1_IN0
- PA1 → ADC1_IN1
- ...
- PA7 → ADC1_IN7
- PB0 → ADC1_IN8
- PB1 → ADC1_IN9
2.2 转换模式
STM32 ADC支持多种转换模式,适应不同采集需求:
- 单次转换模式:启动一次转换后,仅转换一次指定通道,转换完成后停止;
- 连续转换模式:启动后持续转换指定通道,转换完成后自动重新开始;
- 扫描模式:对多个通道按顺序依次转换(需配置通道序列),适用于多传感器采集;
- 间断模式:将通道序列分为多组,每组转换后暂停,等待下一次触发(适合分阶段采集)。
2.3 触发方式
ADC转换可通过软件或硬件触发启动:
- 软件触发:通过写入ADC控制寄存器的SWSTART位启动(灵活,适合低频率采集);
- 硬件触发:由内部定时器(TIM1~TIM8)、外部中断线等触发(精确控制转换时刻,适合同步采集)。
常用硬件触发源(以F103为例):
- TIM1_CH1 → ADC1_EXTRIG0
- TIM2_CH3 → ADC1_EXTRIG1
- TIM3_TRGO → ADC1_EXTRIG2
- ...
2.4 数据寄存器与对齐方式
ADC转换结果存储在16位数据寄存器(ADC_DR)中,支持两种对齐方式:
- 右对齐:转换结果的最低位对齐寄存器的0位(12位数据存储在[11:0]位,默认方式);
- 左对齐:转换结果的最高位对齐寄存器的15位(12位数据存储在[15:4]位,便于截断为8位数据)。
示例:12位ADC,输入电压对应数字量2047(0x7FF):
- 右对齐:ADC_DR = 0x07FF
- 左对齐:ADC_DR = 0x7FF0
二、STM32 ADC外设详解
2.1 外设结构(以STM32F103为例)
STM32F103系列通常包含2个ADC(ADC1、ADC2),部分型号(如F103ZET6)有3个ADC(增加ADC3),共享16个外部通道。其结构框图如下(简化版):
[模拟输入通道] → [多路开关] → [采样保持电路] → [ADC核心] → [数据寄存器]
↑
[触发控制器] → [转换控制逻辑] → [校准电路]
- 多路开关:选择当前转换的通道(支持扫描模式下的自动切换);
- 采样保持电路:在转换期间保持输入电压稳定(采样时间可配置);
- ADC核心:12位逐次逼近型转换器,完成量化与编码;
- 触发控制器:接收软件或硬件触发信号,启动转换;
- 校准电路:通过内部校准程序降低偏移误差。
2.2 关键参数配置
2.2.1 分辨率
STM32F1/F4系列ADC默认分辨率为12位,部分高端型号(如H7)支持可调分辨率(6/8/10/12位)。12位分辨率下,量化误差为满量程的1/4096(约0.024%)。
2.2.2 采样时间
采样时间是ADC对输入信号的采样持续时间,需根据输入信号的带宽配置(带宽越高,需越长采样时间)。STM32 ADC的采样时间可配置为:1.5、7.5、13.5、28.5、41.5、55.5、71.5、239.5个ADC时钟周期。
采样时间计算 :
总转换时间 = 采样时间 + 12.5个ADC时钟周期(转换时间)
例如:ADC时钟为12MHz,采样时间7.5周期:
总转换时间 = (7.5 + 12.5) / 12MHz = 20 / 12e6 ≈ 1.67μs(转换速率约600kHz)。
2.2.3 ADC时钟
ADC时钟由APB2总线时钟分频得到(F103中APB2时钟最高72MHz),分频系数可配置为2、4、6、8,因此ADC时钟最高为72MHz/2=36MHz(但实际使用中,12位分辨率下建议≤14MHz以保证精度,高速模式可放宽至36MHz)。
2.3 注入通道与规则通道
STM32 ADC引入"规则通道"与"注入通道"的概念,用于区分常规与紧急采集:
- 规则通道:常规转换通道,最多支持16个通道(需配置转换序列);
- 注入通道:优先于规则通道的紧急通道(如过压保护),最多支持4个通道,可打断规则通道转换,完成后自动恢复。
应用场景:规则通道采集正常传感器数据,注入通道监测异常信号(如电池过压),确保异常信号优先处理。
三、ADC硬件设计要点
ADC的硬件设计直接影响采集精度与稳定性,需重点关注输入信号调理、参考电压、抗干扰等环节。
3.1 输入信号范围与分压电路
STM32 ADC的输入电压范围为0VREF+(通常VREF+接3.3V,因此输入信号需限制在03.3V)。若测量超量程信号(如05V、012V),需通过分压电路降压:
示例:测量0~5V电压,分压电路设计:
- 采用两个10kΩ电阻串联,输入5V时,分压后为2.5V(≤3.3V);
- 计算公式:V_ADC = V_IN × R2/(R1+R2),其中R1=R2=10kΩ;
- 硬件需在ADC引脚处并联100nF电容滤波,减少高频噪声。
3.2 参考电压(VREF)
ADC的转换精度依赖参考电压的稳定性:
- 多数场景下,VREF+直接接3.3V电源(与VDD共享),但电源噪声会影响精度;
- 高精度场景下,可外接低噪声参考电压源(如TL431),通过VREF+引脚输入(需参考芯片数据手册,部分型号VREF+不可外接)。
3.3 抗干扰设计
- 布线要求:ADC输入线远离高频信号线(如SPI的SCK、电机驱动线),避免电磁干扰;
- 滤波电路:在ADC引脚与信号源之间串联100Ω电阻+并联100nF电容(RC低通滤波),截止频率约1.6MHz,滤除高频噪声;
- 接地处理:ADC输入信号的地应与数字地单点连接,避免地环路噪声;
- 电源滤波:在3.3V电源端并联10μF电解电容+100nF陶瓷电容,减少电源纹波。
3.4 温度传感器与内部参考电压
使用内部温度传感器时,需注意:
- 温度传感器的输出电压随温度变化(约2.5mV/℃),需通过公式换算:
温度(℃) = [(V25 - V_TEMP) / Avg_Slope] + 25
其中:V25=25℃时的电压(约1.43V),Avg_Slope=平均斜率(约4.3mV/℃); - 需使能内部温度传感器:通过ADC_CR2的TSVREFE位使能,且转换前需等待传感器上电稳定(约10μs)。
四、ADC软件配置步骤
本节以STM32F103 ADC1为例,分别介绍寄存器级与HAL库的配置方法,实现基础ADC采集功能。
4.1 寄存器级配置(单通道单次转换,PA0/ADC1_IN0)
步骤1:使能时钟
c
// 使能ADC1和GPIOA时钟(ADC1挂载APB2总线)
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN | RCC_APB2ENR_IOPAEN;
// 配置ADC时钟(APB2=72MHz,分频系数2,ADC时钟=36MHz)
RCC->CFGR &= ~RCC_CFGR_ADCPRE;
RCC->CFGR |= RCC_CFGR_ADCPRE_DIV2;
步骤2:配置GPIO为模拟输入
ADC输入引脚需配置为模拟输入模式(无上下拉,不输出):
c
// 配置PA0为模拟输入
GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
// MODE0=00(输入模式),CNF0=00(模拟输入)
步骤3:ADC校准
ADC上电后需进行校准,降低偏移误差:
c
// 复位ADC
ADC1->CR2 |= ADC_CR2_RSTCAL;
while (ADC1->CR2 & ADC_CR2_RSTCAL); // 等待复位完成
// 校准ADC
ADC1->CR2 |= ADC_CR2_CAL;
while (ADC1->CR2 & ADC_CR2_CAL); // 等待校准完成
步骤4:配置ADC参数(单通道单次转换)
c
// 配置ADC:单通道、单次转换、右对齐、软件触发
ADC1->CR1 &= ~(ADC_CR1_SCAN | ADC_CR1_CONT); // 关闭扫描和连续模式
ADC1->CR2 &= ~(ADC_CR2_ALIGN | ADC_CR2_EXTTRIG); // 右对齐,禁止外部触发
ADC1->CR2 |= ADC_CR2_EXTSEL; // 选择软件触发
// 配置转换序列:1个通道(通道0),排名1
ADC1->SQR1 &= ~ADC_SQR1_L; // 转换序列长度=1(0000)
ADC1->SQR3 &= ~ADC_SQR3_SQ1; // 第1个转换通道为通道0
ADC1->SQR3 |= ADC_SQR3_SQ1_0;
// 配置采样时间:通道0,采样时间7.5周期
ADC1->SMPR2 &= ~ADC_SMPR2_SMP0;
ADC1->SMPR2 |= ADC_SMPR2_SMP0_1; // 7.5周期
步骤5:实现ADC转换函数
c
// 单次转换并返回结果(12位右对齐)
uint16_t ADC1_ReadChannel0(void) {
// 使能ADC
ADC1->CR2 |= ADC_CR2_ADON;
// 启动转换(软件触发)
ADC1->CR2 |= ADC_CR2_SWSTART;
// 等待转换完成(EOC位为1)
while (!(ADC1->SR & ADC_SR_EOC));
// 读取结果(右对齐,取[11:0]位)
return (uint16_t)(ADC1->DR & 0x0FFF);
}
3.2 HAL库配置(基于STM32CubeMX)
步骤1:创建工程与时钟配置
- 打开STM32CubeMX,选择芯片(如STM32F103C8T6);
- 配置RCC:HSE时钟,系统时钟72MHz,APB2时钟72MHz;
- 配置ADC时钟:APB2分频2,ADC时钟36MHz。
步骤2:配置ADC1通道0
- 在"Pinout & Configuration"中,左侧选择"Analog"→"ADC1";
- 勾选"IN0"(对应PA0);
- 配置参数:
- Mode:Independent ADC(独立模式);
- Data Alignment:Right Alignment(右对齐);
- Scan Conversion Mode:Disabled(关闭扫描);
- Continuous Conversion Mode:Disabled(关闭连续转换);
- Discontinuous Conversion Mode:Disabled;
- External Trigger Conversion Source:Software trigger(软件触发);
- 点击"ADC1_IN0",配置Sampling Time为7.5 Cycles。
步骤3:生成代码
- 配置工程路径与IDE(如Keil MDK);
- 生成代码,确保
MX_ADC1_Init()
函数正确初始化ADC。
步骤4:HAL库转换函数实现
c
// 单通道单次转换(阻塞式)
uint16_t ADC1_ReadChannel0(void) {
uint16_t adc_value = 0;
// 启动ADC转换
HAL_ADC_Start(&hadc1);
// 等待转换完成(超时100ms)
if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) {
// 读取转换结果(右对齐)
adc_value = HAL_ADC_GetValue(&hadc1);
}
// 停止ADC
HAL_ADC_Stop(&hadc1);
return adc_value;
}
四、实战案例
4.1 案例1:单通道电压采集(电池电压监测)
功能:通过ADC采集电池电压(0~4.2V),转换为实际电压值并通过UART输出。
硬件设计
- 电池电压通过10kΩ+20kΩ分压(4.2V → 4.2×20/(10+20)=2.8V ≤3.3V);
- 分压后接入PA0(ADC1_IN0),并并联100nF滤波电容。
软件实现(HAL库)
c
// 初始化ADC和UART
void System_Init(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
}
// 转换ADC值为实际电压(单位:mV)
uint16_t ADC_ConvertToVoltage(uint16_t adc_value) {
// 3.3V对应4095,分压系数3(10k+20k)
return (uint16_t)(adc_value * 3300.0f / 4095.0f * 3);
}
int main(void) {
System_Init();
uint16_t adc_val, voltage;
while (1) {
adc_val = ADC1_ReadChannel0();
voltage = ADC_ConvertToVoltage(adc_val);
printf("ADC值:%d,电池电压:%d mV\r\n", adc_val, voltage);
HAL_Delay(1000); // 每秒采集一次
}
}
测试方法
- 用稳压电源模拟电池电压(0~4.2V),接入分压电路;
- 打开串口助手,观察输出的电压值是否与实际输入一致(误差应≤50mV)。
4.2 案例2:多通道扫描模式(环境传感器采集)
功能:通过ADC1的通道0(PA0)、通道1(PA1)、通道2(PA2)分别采集温度、湿度、光照传感器的模拟信号,采用扫描模式连续采集。
软件配置(HAL库)
-
在CubeMX中配置ADC1:
- Scan Conversion Mode:Enabled;
- Continuous Conversion Mode:Enabled;
- Number Of Conversion:3;
- 配置转换序列:Channel0(Rank1)、Channel1(Rank2)、Channel2(Rank3);
- 采样时间均为13.5 Cycles。
-
多通道读取函数:
c
// 多通道扫描模式采集(连续转换)
void ADC1_ReadMultiChannels(uint16_t *buf) {
// 启动ADC
HAL_ADC_Start(&hadc1);
// 连续读取3个通道的转换结果
for (uint8_t i = 0; i < 3; i++) {
// 等待转换完成
if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) {
buf[i] = HAL_ADC_GetValue(&hadc1);
} else {
buf[i] = 0; // 超时错误
}
}
HAL_ADC_Stop(&hadc1);
}
- 主函数调用:
c
int main(void) {
System_Init();
uint16_t adc_buf[3]; // 存储3个通道的ADC值
while (1) {
ADC1_ReadMultiChannels(adc_buf);
printf("温度ADC:%d,湿度ADC:%d,光照ADC:%d\r\n",
adc_buf[0], adc_buf[1], adc_buf[2]);
HAL_Delay(500);
}
}
4.3 案例3:DMA传输(高速连续采集)
当需要高速连续采集(如音频信号、振动数据)时,使用DMA传输可避免CPU频繁读取ADC数据,提高效率。
配置步骤(CubeMX)
-
配置ADC1为连续扫描模式,启用DMA:
- Continuous Conversion Mode:Enabled;
- DMA Continuous Requests:Enabled;
- 在"DMA Settings"中添加ADC1_DMA,方向Peripheral to Memory,模式Circular(循环模式),数据宽度Half Word(16位)。
-
DMA缓冲区定义与初始化:
c
#define ADC_BUF_SIZE 1024 // 缓冲区大小
uint16_t adc_dma_buf[ADC_BUF_SIZE]; // DMA接收缓冲区
// 初始化ADC+DMA
void ADC1_DMA_Init(void) {
MX_ADC1_Init();
MX_DMA_Init();
// 启动DMA循环采集
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_dma_buf, ADC_BUF_SIZE);
}
- DMA传输完成回调(可选):
c
// DMA半传输/全传输完成回调(循环模式下触发)
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc) {
if (hadc == &hadc1) {
// 处理前半缓冲区数据(0~511)
process_adc_data(adc_dma_buf, 0, ADC_BUF_SIZE/2);
}
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
if (hadc == &hadc1) {
// 处理后半缓冲区数据(512~1023)
process_adc_data(adc_dma_buf, ADC_BUF_SIZE/2, ADC_BUF_SIZE/2);
}
}
- 数据处理函数:
c
// 处理ADC数据(如计算平均值、峰值)
void process_adc_data(uint16_t *buf, uint16_t start, uint16_t len) {
uint32_t sum = 0;
for (uint16_t i = start; i < start + len; i++) {
sum += buf[i];
}
uint16_t avg = sum / len; // 计算平均值
printf("ADC平均值:%d\r\n", avg);
}
4.4 案例4:注入通道(紧急信号监测)
功能:规则通道采集正常温度数据,注入通道监测高温报警信号(超过阈值时优先处理)。
配置步骤
-
在CubeMX中配置ADC1:
- 规则通道:Channel0(温度传感器),连续转换;
- 注入通道:Channel1(高温监测),配置为"AutoInjected"(自动注入),触发条件为规则通道转换完成。
-
注入通道中断处理:
c
// 注入通道转换完成中断回调
void HAL_ADC_InjectedConvCpltCallback(ADC_HandleTypeDef* hadc) {
if (hadc == &hadc1) {
uint16_t inject_val = HAL_ADC_GetInjectedValue(hadc, ADC_INJECTED_RANK_1);
if (inject_val > 3000) { // 假设阈值为3000(对应高温)
printf("高温报警!注入通道值:%d\r\n", inject_val);
// 执行报警操作(如点亮LED、触发蜂鸣器)
}
}
}
- 主函数:
c
int main(void) {
System_Init();
// 启动规则通道和注入通道转换
HAL_ADCEx_InjectedStart_IT(&hadc1); // 使能注入通道中断
HAL_ADC_Start(&hadc1);
while (1) {
// 读取规则通道数据(温度)
if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) {
uint16_t temp_val = HAL_ADC_GetValue(&hadc1);
printf("温度值:%d\r\n", temp_val);
}
HAL_Delay(100);
}
}
4.5 案例5:内部温度传感器采集
功能:通过STM32内部温度传感器(ADC1_IN16)采集芯片温度,并转换为摄氏度。
配置步骤(HAL库)
-
在CubeMX中启用内部温度传感器:
- 打开ADC1配置,勾选"IN16"(Temperature Sensor);
- 配置采样时间为239.5 Cycles(温度传感器需要较长采样时间)。
-
温度转换函数:
c
// 读取内部温度传感器值(℃)
float ADC1_ReadTemperature(void) {
uint16_t adc_val;
float temp;
// 启动ADC
HAL_ADC_Start(&hadc1);
// 等待转换完成
if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) {
adc_val = HAL_ADC_GetValue(&hadc1);
// 温度计算公式(参考STM32F1数据手册)
// V25=1.43V,Avg_Slope=4.3mV/℃,VREF+=3.3V
temp = (1.43f - (adc_val * 3.3f / 4095.0f)) / 0.0043f + 25.0f;
}
HAL_ADC_Stop(&hadc1);
return temp;
}
- 主函数调用:
c
int main(void) {
System_Init();
float temp;
while (1) {
temp = ADC1_ReadTemperature();
printf("芯片温度:%.1f ℃\r\n", temp);
HAL_Delay(1000);
}
}
五、ADC高级特性与精度优化
5.1 校准与偏移补偿
STM32 ADC的精度可通过校准进一步优化:
- 初始化校准:上电后执行一次复位校准(RSTCAL)和ADC校准(CAL),减少偏移误差;
- 周期性校准:环境温度变化较大时,建议定期校准(如每小时一次);
- 手动偏移补偿:通过测量零点电压(输入0V时的ADC值),在数据处理时减去偏移量。
c
// 测量偏移值(输入0V时的ADC值)
uint16_t adc_offset = 0;
void ADC1_CalibrateOffset(void) {
// 短接ADC输入到GND(实际应用中需硬件支持)
adc_offset = ADC1_ReadChannel0();
}
// 带偏移补偿的ADC读取
uint16_t ADC1_ReadWithOffset(void) {
uint16_t val = ADC1_ReadChannel0();
return (val > adc_offset) ? (val - adc_offset) : 0;
}
5.2 滤波算法(减少噪声)
ADC采集数据受噪声影响时,可通过软件滤波优化:
- 滑动平均滤波:取最近N次采样的平均值(适合缓慢变化信号);
- 中值滤波:取最近N次采样的中间值(适合剔除脉冲干扰);
- 加权平均滤波:近期采样赋予较高权重(适合快速变化信号)。
滑动平均滤波示例:
c
#define AVG_N 10 // 平均次数
uint16_t adc_buf[AVG_N];
uint8_t adc_idx = 0;
// 滑动平均滤波
uint16_t ADC1_SmoothFilter(uint16_t new_val) {
uint32_t sum = 0;
adc_buf[adc_idx++] = new_val;
if (adc_idx >= AVG_N) adc_idx = 0;
// 计算平均值
for (uint8_t i = 0; i < AVG_N; i++) {
sum += adc_buf[i];
}
return sum / AVG_N;
}
5.3 提高转换速率的技巧
- 减少采样时间:在信号带宽允许的情况下,选择较短采样时间(如1.5周期);
- 提高ADC时钟:在精度允许范围内,提高ADC时钟(如36MHz);
- 使用DMA传输:避免CPU等待,实现连续高速采集;
- 多ADC同步模式:高端STM32型号(如F4、H7)支持多ADC同步转换,并行采集多个通道。
5.4 低功耗模式下的ADC应用
在电池供电设备中,需优化ADC的功耗:
- 关闭未使用的ADC通道:减少内部电路功耗;
- 单次转换模式 :转换完成后立即关闭ADC(
HAL_ADC_Stop()
); - 低功耗模式唤醒:在STM32休眠时,通过外部触发(如定时器)唤醒ADC进行单次转换,完成后继续休眠。
c
// 低功耗模式下的ADC采集
void ADC1_LowPowerRead(void) {
// 唤醒ADC
HAL_ADC_Start(&hadc1);
// 等待转换
HAL_ADC_PollForConversion(&hadc1, 100);
uint16_t val = HAL_ADC_GetValue(&hadc1);
// 关闭ADC
HAL_ADC_Stop(&hadc1);
// 进入休眠模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
}
六、常见问题与调试技巧
6.1 采集数据跳变剧烈(噪声大)
- 原因 :
- 输入信号未滤波(高频噪声);
- 电源纹波过大(VREF+不稳定);
- 布线靠近干扰源(如电机、射频模块);
- 解决 :
- 增加RC滤波电路(100Ω+100nF);
- 电源端并联10μF+100nF电容;
- 软件添加滑动平均滤波;
- 信号线远离干扰源,铺地平面。
6.2 转换精度低(与实际值偏差大)
- 原因 :
- 未进行ADC校准(偏移误差大);
- 分压电路电阻精度低(如使用5%误差电阻);
- 参考电压VREF+不准确(如3.3V实际为3.2V);
- 解决 :
- 上电后执行校准,定期重新校准;
- 使用1%精度的分压电阻;
- 测量实际VREF+电压(如3.28V),修正转换公式:
实际电压 = (ADC值 × VREF实际值) / 4095 × 分压系数。
6.3 DMA传输数据错误
- 原因 :
- DMA缓冲区大小与转换次数不匹配;
- ADC与DMA时钟不同步;
- 未启用DMA连续请求(Circular模式);
- 解决 :
- 确保DMA缓冲区大小 ≥ 转换通道数 × 连续转换次数;
- 检查ADC和DMA的时钟配置(ADC时钟≤36MHz);
- 在CubeMX中勾选"DMA Continuous Requests"。
6.4 注入通道不触发
- 原因 :
- 未正确配置注入通道序列;
- 触发源选择错误;
- 未使能注入通道中断;
- 解决 :
- 检查注入通道的Rank配置(1~4);
- 确认触发源与规则通道不冲突;
- 调用
HAL_ADCEx_InjectedStart_IT()
使能中断。
6.5 调试工具与方法
- 示波器测量 :
- 测量ADC输入引脚的模拟信号,确认信号是否稳定;
- 观察VREF+电压,检查是否有纹波;
- ADC自校验 :
- 将ADC输入短接至GND或VREF+,检查读数是否接近0或4095;
- 日志输出 :
- 通过UART输出原始ADC值与转换后的物理量,对比理论值;
- CubeMonitor工具 :
- 使用STM32CubeMonitor实时监控ADC数据,绘制波形(适合动态信号分析)。
七、总结与扩展
STM32 ADC作为核心模拟采集外设,其灵活性与性能满足了多样化的嵌入式应用需求。本文从基础原理到实战案例,系统讲解了ADC的工作模式、配置方法与优化技巧,核心要点包括:
- ADC的分辨率、转换速率与采样时间是关键参数,需根据场景平衡(如高精度场景选择长采样时间,高速场景选择短采样时间);
- 硬件设计需重视滤波、分压与抗干扰,直接影响采集质量;
- 软件配置需根据需求选择模式(单通道/多通道、单次/连续、DMA/中断);
- 精度优化需结合校准、滤波与硬件设计,多维度提升稳定性。
未来学习可扩展至:
- 多ADC同步采集(如F4系列的ADC1与ADC2同步模式);
- 过采样技术(通过多次采样提高有效分辨率);
- 基于ADC的触摸按键设计(利用RC充放电时间测量);
- 与DMA+定时器结合的高频数据采集系统(如音频采集)。
掌握STM32 ADC的使用,不仅能解决传感器数据采集问题,更能理解模拟与数字世界的接口设计思想,为复杂嵌入式系统开发奠定基础。实践中需结合具体硬件与场景,不断调试优化,才能充分发挥ADC的性能。
附录:常用代码片段
- ADC分辨率切换(适用于支持可调分辨率的型号):
c
// 配置ADC为10位分辨率(部分型号支持)
void ADC1_SetResolution10bit(void) {
ADC1->CR1 &= ~ADC_CR1_RES;
ADC1->CR1 |= ADC_CR1_RES_0; // 10位分辨率
}
- 外部触发转换(定时器触发):
c
// 配置ADC1由TIM3_TRGO触发
void ADC1_ConfigTimerTrigger(void) {
// 使能TIM3时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
// 配置TIM3为1kHz触发(周期1ms)
TIM3->PSC = 7199; // 72MHz/7200=10kHz
TIM3->ARR = 9; // 10kHz/10=1kHz
TIM3->CR2 |= TIM_CR2_MMS_1; // TRGO=更新事件
TIM3->CR1 |= TIM_CR1_CEN; // 启动定时器
// 配置ADC外部触发
ADC1->CR2 |= ADC_CR2_EXTTRIG; // 使能外部触发
ADC1->CR2 &= ~ADC_CR2_EXTSEL; // 选择TIM3_TRGO(EXTSEL[2:0]=010)
ADC1->CR2 |= ADC_CR2_EXTSEL_1;
}
- 双通道交替采样(提高转换效率):
c
// ADC1通道0和通道1交替采样(连续模式)
void ADC1_AlternateChannels(void) {
// 配置扫描模式,2个通道
ADC1->CR1 |= ADC_CR1_SCAN;
ADC1->CR2 |= ADC_CR2_CONT; // 连续模式
ADC1->SQR1 |= ADC_SQR1_L_0; // 序列长度=2
// 序列1:通道0,序列2:通道1
ADC1->SQR3 |= ADC_SQR3_SQ1_0; // SQ1=0
ADC1->SQR3 |= ADC_SQR3_SQ2_1; // SQ2=1
}