STM32 ADC原理与单/多通道采集实战指南
ADC(模数转换器)是嵌入式系统中将物理世界模拟信号(如温度、光照、电压)转化为数字处理器可识别数据的核心接口。其工作基于采样、保持、量化、编码四步原理,关键指标包括分辨率、参考电压、采样率与量化误差。技术价值在于实现高精度、低延迟的传感数字化,支撑工业测控、智能硬件和物联网终端等场景。实际应用中需关注STM32的12位SAR型ADC特性,如通道复用、右对齐数据格式、ADC时钟配置及扫描模式多通道
1. ADC基础原理与工程意义
在嵌入式系统中,ADC(Analog-to-Digital Converter,模数转换器)是连接物理世界与数字处理单元的关键桥梁。自然界中绝大多数感知信号——光照强度、环境温度、压力变化、声音振幅、气体浓度等——本质上都是连续变化的模拟量。微控制器无法直接处理这类信号,必须通过ADC将其量化为离散的数字值,才能进入后续的计算、存储、传输或控制逻辑。
ADC的核心任务并非简单地“把电压变成数字”,而是以可预测、可重复、可校准的方式建立模拟输入与数字输出之间的映射关系。这一过程包含四个不可分割的环节: 采样(Sampling)、保持(Holding)、量化(Quantization)、编码(Encoding) 。
- 采样 :在特定时间点捕获模拟信号的瞬时电压值。采样频率必须满足奈奎斯特采样定理(≥2×信号最高频率成分),否则将产生混叠失真。
- 保持 :在量化期间维持采样电压稳定,避免因信号波动导致量化误差。STM32内部集成的采样保持电路(S&H)在此阶段起关键作用。
- 量化 :将连续的电压范围划分为有限个离散电平。这是引入 量化误差 的根本来源,其最大值为±½ LSB(Least Significant Bit)。
- 编码 :将量化后的电平编号转换为二进制数字格式(如二进制补码、格雷码等)。STM32 ADC默认输出标准二进制无符号整数。
以最常见的光敏电阻应用为例:当环境光照变化时,光敏电阻阻值随之改变,与之串联的分压电路输出一个0~3.3V范围内的模拟电压。该电压被送入MCU的ADC引脚。ADC将此电压映射为0~4095(12位)的整数。开发者无需关心具体电压值,即可通过比较数字值的相对大小判断光照强弱;若需精确物理量,则依据参考电压进行线性换算。这种“先数字化、后处理”的范式,是现代嵌入式传感系统的基石。
2. STM32F10x ADC核心特性解析
STM32F10x系列微控制器集成了功能完备的逐次逼近型(SAR)ADC,其设计充分考虑了工业与消费类应用的平衡需求。理解其硬件架构是正确配置与高效使用的前提。
2.1 基本规格与通道资源
STM32F10x拥有 三个独立的12位ADC模块(ADC1、ADC2、ADC3) ,均支持:
- 12位分辨率 :理论分辨率为$2^{12} = 4096$个量化等级。
- 18路复用输入通道 :其中16路为外部GPIO引脚(如PA0~PA7, PB0~PB1, PC0~PC5等),2路为内部信号源(内部温度传感器、内部参考电压VREFINT)。
- 灵活的触发源 :支持软件触发、定时器TRGO事件、外部中断线、其他ADC的规则/注入转换结束等多种触发方式,实现精确时序控制。
- 双模式操作 :可工作于独立模式(各ADC单独运行)或同步模式(ADC1为主,ADC2/3为从,用于提高采样率或实现差分采集)。
本章实践聚焦于 ADC1 ,因其通常作为主ADC且资源最丰富。需注意,并非所有引脚都支持ADC功能,具体映射关系须查阅对应芯片的数据手册(Datasheet)“Alternate Function Mapping”章节。例如,PA6引脚在多数F103型号中确实映射至ADC1_IN6通道,这是硬件层面的固定绑定,而非软件可配置项。
2.2 参考电压(VREF+)与测量范围
ADC的转换结果是一个相对值,其基准是 参考电压(VREF+) 。公式为:
$$
Digital\ Value = \frac{V_{IN}}{V_{REF+}} \times (2^{N} - 1)
$$
其中$N$为位数(此处为12),$V_{IN}$为待测模拟输入电压。
STM32F10x提供了专用的 VREF+引脚 (通常为PA0,但需确认具体型号封装),旨在接入高精度、低噪声的外部基准源(如2.5V或3.0V),从而提升转换精度和温度稳定性。然而,在大多数学习与原型开发场景中,开发者直接将VREF+引脚连接到主电源VDDA(模拟供电域),并将VSSA(模拟地)连接到GND。此时, VREF+ = VDDA ≈ 3.3V ,测量范围即为 0V 至 3.3V 。
由此可计算出最小可分辨电压(LSB大小):
$$
LSB = \frac{V_{REF+}}{2^{12}} = \frac{3.3}{4096} \approx 0.805\ mV
$$
这意味着,理论上ADC能区分输入电压中大于约0.8mV的变化。实际应用中,受电源噪声、PCB布局、引脚干扰等因素影响,有效分辨率(ENOB)通常略低于12位。
2.3 数据对齐与寄存器结构
ADC转换结果存储在16位的 ADC_DR(Data Register) 中。由于结果本身只有12位,剩余4位需明确其位置,这便是 数据对齐方式 。
- 右对齐(Right-aligned) :12位结果置于DR寄存器的最低12位(bit[11:0]),bit[15:12]为0。这是 最常用、最直观的模式 ,读取
ADC_DR即可获得0~4095的原始值。 - 左对齐(Left-aligned) :12位结果置于DR寄存器的最高12位(bit[15:4]),bit[3:0]为0。此模式便于快速提取高位字节,但需额外移位操作(
ADC_DR >> 4)才能得到标准数值。
HAL库函数 HAL_ADC_GetValue() 默认返回右对齐格式的16位整数,开发者可直接使用。选择右对齐的根本原因在于:它与人类直觉一致(数值0对应0V,4095对应满量程),且避免了不必要的位运算开销,符合嵌入式系统对简洁性与效率的双重追求。
2.4 时钟配置:性能与精度的权衡
ADC的性能与 ADC时钟(ADCCLK) 密切相关。STM32F10x规定:
- ADCCLK最大允许频率为14 MHz 。超过此限将导致转换精度严重下降甚至失效。
- ADCCLK由APB2总线时钟(PCLK2)经 可编程预分频器 分频得到。PCLK2在F103系列中最高可达72 MHz。
因此,分频系数的选择是关键工程决策:
- 若PCLK2 = 72 MHz,则最小分频系数为 $ \lceil 72 / 14 \rceil = 6 $,此时ADCCLK = 12 MHz(安全且常用)。
- 若PCLK2 = 36 MHz,则分频系数可选2(18 MHz,超限!)或3(12 MHz,安全)。
- 分频系数越大,ADCCLK越低,单次转换时间越长,但抗噪能力越强;反之,高频ADCCLK可提升采样率,但对电源和布线要求更苛刻。
在CubeMX中配置时,“ADC Prescaler”选项实质上就是设置这个分频系数。工程师必须根据系统主频、电源质量及实时性需求,在12MHz(推荐)与更低频率间做出务实选择。盲目追求高速度而忽略硬件约束,是初学者常踩的深坑。
3. 单通道ADC实践:摇杆X轴电压采集
双轴摇杆模块是理解ADC应用的经典教具。其本质是两个独立的 线性电位器(Potentiometer) ,分别对应X轴与Y轴。当摇杆偏转时,电位器滑动端在固定阻值的电阻体上移动,形成一个可变分压点。典型接法为:电位器一端接VCC(3.3V),另一端接GND,滑动端(Wiper)作为模拟输出引脚。
对于X轴:
- 摇杆居中时,滑动端电压约为1.65V,ADC读数≈2048。
- 摇杆推向最右(VCC方向),滑动端接近3.3V,ADC读数≈4095。
- 摇杆推向最左(GND方向),滑动端接近0V,ADC读数≈0。
此线性关系使ADC成为理想的位移/角度传感器接口。
3.1 CubeMX图形化配置
- 引脚分配 :在Pinout视图中,找到并点击PA6引脚。在弹出的菜单中,选择
ADC1_IN6。此操作在底层完成了GPIO模式(模拟输入)、复用功能(AF0)及时钟使能(RCC)的自动配置。 - ADC1参数设置 :
- Mode :Independent mode(独立模式,单ADC工作)。
- Resolution :12 Bits(默认,不建议更改)。
- Data Alignment :Right alignment(右对齐,推荐)。
- Scan Conversion Mode :Disabled(禁用扫描,因仅使用单通道)。
- Continuous Conversion Mode :Disabled(禁用连续转换,采用单次触发)。
- Discontinuous Conversion Mode :Disabled(禁用间断模式)。
- External Trigger Conversion :SWSTART(软件触发,通过HAL_ADC_Start()调用)。
- ADC Clock :PCLK2 div 6(确保ADCCLK=12MHz,符合规范)。 - 生成代码 :保存配置并生成初始化代码。CubeMX会自动生成
MX_ADC1_Init()函数,完成ADC1的全部寄存器配置。
3.2 关键HAL API与驱动逻辑
生成的代码骨架已包含ADC1的初始化。实际数据采集逻辑需在 main() 函数的主循环中实现:
// 定义存储ADC值的变量
uint32_t adc_value_x = 0;
// 1. 启动ADC转换(软件触发)
if (HAL_ADC_Start(&hadc1) != HAL_OK) {
Error_Handler(); // 处理启动失败(如ADC忙)
}
// 2. 等待转换完成(阻塞式,超时100ms)
if (HAL_ADC_PollForConversion(&hadc1, 100) != HAL_OK) {
// 超时处理:可能ADC未就绪、时钟错误或硬件故障
Error_Handler();
}
// 3. 获取转换结果(右对齐,直接读取)
adc_value_x = HAL_ADC_GetValue(&hadc1);
// 4. 停止ADC(为下一次转换做准备)
HAL_ADC_Stop(&hadc1);
关键点解析 :
- HAL_ADC_Start() :置位 ADON 位,启动ADC,但不立即开始转换。它只是让ADC进入就绪状态。
- HAL_ADC_PollForConversion() :轮询 EOC (End of Conversion)标志位。此函数是 阻塞式 的,会持续查询直到转换完成或超时。超时值(100ms)应远大于单次转换所需时间(F103在12MHz ADCCLK下,12位转换约需1.5μs),确保可靠性。
- HAL_ADC_GetValue() :读取 ADC_DR 寄存器。在右对齐模式下,该值即为0~4095的原始ADC码。
- HAL_ADC_Stop() :清除 ADON 位,关闭ADC以降低功耗。对于单次转换,每次采集后都应调用此函数。
3.3 物理量换算与串口输出
原始ADC值(0~4095)需映射回物理电压(0~3.3V)才有工程意义。换算公式为:
$$
V_{IN} = \frac{ADC_Value}{4095} \times 3.3\ V
$$
在代码中,为避免浮点运算开销(尤其在资源受限MCU上),常采用定点运算或查表法。但学习阶段,直接使用浮点更清晰:
float voltage_x = (float)adc_value_x * 3.3f / 4095.0f;
printf("X-Axis Voltage: %.3f V\r\n", voltage_x);
printf 依赖于重定向的 _write 函数(通常指向USART),需在工程中配置串口(如USART1)并启用 CMSIS 的 Retarget 功能。最终,通过USB转TTL模块连接PC串口助手,即可实时观察X轴电压随摇杆移动的动态变化:向右推,电压趋近3.3V;向左推,电压趋近0V;居中时稳定在1.65V附近。此现象直观验证了ADC硬件链路与软件驱动的正确性。
4. 多通道ADC实践:同步采集X/Y双轴数据
单通道方案虽简单,但效率低下:采集X轴后需停止ADC,再启动采集Y轴,两次转换间存在明显间隔。对于需要获取X/Y坐标对(如游戏手柄、机器人姿态)的应用, 同步或准同步采集 至关重要。STM32 ADC的 扫描模式(Scan Mode) 为此而生。
4.1 扫描模式工作原理
扫描模式允许ADC按预设顺序, 自动、连续地转换多个通道 ,并将结果依次存入同一个数据寄存器( ADC_DR )或多个寄存器(若启用DMA)。其核心机制是:
- 在ADC初始化时,通过 ADC_SQRx (Sequence Register)寄存器配置一个 通道序列 (Sequence),指定每个转换步骤(Step)对应的通道号(CHSEL)。
- 启动转换后,ADC硬件状态机自动执行序列:转换Step1通道 → 触发EOC → 转换Step2通道 → 触发EOC → ……直至序列末尾。
- 每次转换结束,新数据覆盖 ADC_DR (单寄存器模式)或存入DMA缓冲区(DMA模式)。
对于双轴摇杆,我们需将X轴(PA6→ADC1_IN6)和Y轴(PA7→ADC1_IN7)同时纳入扫描序列。这样,一次 HAL_ADC_Start() 调用,即可在极短时间内(微秒级)完成两个通道的转换,获得严格时间对齐的X/Y数据对。
4.2 CubeMX多通道配置
- 引脚分配 :除PA6外,还需在Pinout视图中将PA7配置为
ADC1_IN7。 - ADC1参数设置 (关键变更):
- Scan Conversion Mode :Enabled(启用扫描)。
- Number of Conversions :2(序列长度为2)。
- Regular Channel Configuration :Rank 1: ChannelADC_CHANNEL_6, Sampling Time13.5 Cycles(默认,足够)。Rank 2: ChannelADC_CHANNEL_7, Sampling Time13.5 Cycles。- Continuous Conversion Mode :
Disabled(仍用单次,但一次触发转换2个通道)。 - Discontinuous Conversion Mode :
Disabled(暂不启用,简化逻辑)。
- 生成代码 :CubeMX会更新
MX_ADC1_Init(),自动配置ADC_SQR1寄存器,将通道6和7写入序列。
4.3 多通道数据采集与分离
多通道扫描模式下, HAL_ADC_GetValue() 的行为不变,但它返回的是 最后一次转换(即序列中最后一个通道)的结果 。因此,要获取X/Y两个值,必须在每次转换序列完成后, 分别读取两次 :第一次读取得到Y轴(Rank2)值,第二次读取得到X轴(Rank1)值?这显然错误。
正确方法是: 利用扫描模式的“自动序列”特性,在单次启动后,通过循环调用 HAL_ADC_PollForConversion() 和 HAL_ADC_GetValue() 来依次捕获每个通道的结果 。但由于 ADC_DR 是单寄存器,后一次转换会覆盖前一次结果,因此必须在每次EOC后立即读取。
然而,HAL库的 HAL_ADC_PollForConversion() 是为单次转换设计的。为实现多通道采集,需采用以下可靠策略:
uint32_t adc_values[2]; // 存储X(Y)和Y(X)值
// 1. 启动ADC扫描转换
if (HAL_ADC_Start(&hadc1) != HAL_OK) {
Error_Handler();
}
// 2. 等待第一个通道(Rank1, X轴)转换完成
if (HAL_ADC_PollForConversion(&hadc1, 100) != HAL_OK) {
Error_Handler();
}
adc_values[0] = HAL_ADC_GetValue(&hadc1); // 获取X轴值
// 3. 等待第二个通道(Rank2, Y轴)转换完成
if (HAL_ADC_PollForConversion(&hadc1, 100) != HAL_OK) {
Error_Handler();
}
adc_values[1] = HAL_ADC_GetValue(&hadc1); // 获取Y轴值
// 4. 停止ADC
HAL_ADC_Stop(&hadc1);
为何此逻辑成立?
- HAL_ADC_PollForConversion() 检测的是 EOC 标志,而扫描模式下, 每个通道转换结束都会置位EOC 。
- 因此,第一次调用等待的是Rank1(X轴)的EOC,读取后得到X值。
- 第二次调用等待的是Rank2(Y轴)的EOC,读取后得到Y值。
- 整个过程在硬件层面是流水线式的,X与Y的转换时间间隔仅为ADC的转换周期(约1.5μs),实现了真正的准同步采集。
4.4 数据处理与验证
获取两个原始值后,可分别换算为电压:
float voltage_x = (float)adc_values[0] * 3.3f / 4095.0f;
float voltage_y = (float)adc_values[1] * 3.3f / 4095.0f;
printf("X: %.3f V, Y: %.3f V\r\n", voltage_x, voltage_y);
在串口助手中,你会观察到:
- 当摇杆静止时,X/Y电压稳定在某一组合(如X=1.65V, Y=1.65V)。
- 向右上方推动摇杆,X电压增大,Y电压也增大。
- 向左下方推动,X/Y电压均减小。
- 此时,X与Y的数值变化是严格关联的,反映了摇杆的二维运动轨迹,证明了多通道扫描模式的有效性。相较于单通道分时采集,数据对的时间一致性得到了根本保障。
5. 工程实践中的关键细节与避坑指南
理论配置与基础代码仅是起点。在真实项目中,ADC的稳定性和精度受诸多因素制约。以下是基于多年硬件调试经验总结的实战要点。
5.1 电源与接地设计:精度的物理基石
ADC的参考电压(VREF+)和模拟地(VSSA)是整个转换链路的基准。任何噪声耦合都会直接转化为读数误差。
- VDDA/VSSA独立供电 :F103芯片要求VDDA(模拟电源)与VDD(数字电源)物理隔离。务必在PCB上为VDDA提供独立的滤波路径:从VDD经一个10μF钽电容 + 100nF陶瓷电容,再连接到VDDA引脚。VSSA必须与VSS(数字地)在单点(通常是芯片底部)相连,避免数字地噪声窜入模拟地。
- 去耦电容紧邻芯片 :所有VDDA、VDD、VSSA、VSS引脚旁,必须放置100nF陶瓷电容,且走线极短。这是抑制高频噪声的最后防线。
- 避免数字信号穿越模拟区域 :PCB布线时,数字信号线(尤其是时钟、PWM、USB)严禁跨越VDDA/VSSA平面。若必须交叉,应保证90度垂直,并在下方铺满地铜皮作为屏蔽。
曾在一个工业传感器项目中,因VDDA滤波电容虚焊,导致ADC读数在满量程范围内随机跳变达±50 LSB。重新焊接并优化布局后,噪声降至±2 LSB以内。
5.2 GPIO配置与信号调理
ADC输入引脚的电气特性直接影响采样精度。
- GPIO模式必须为 ANALOG :这是CubeMX配置的默认项,但若手动修改代码,切勿误设为 INPUT 或 AF_PP 。 ANALOG 模式禁用施密特触发器和上/下拉,使引脚呈现高阻态,完美适配高阻抗传感器(如电位器)。
- 输入信号带宽限制 :ADC采样保持电路有最大输入阻抗要求(F103典型值为50kΩ)。若传感器输出阻抗过高(如某些热敏电阻分压电路),需在ADC引脚前加一级 运放电压跟随器 (Buffer),以提供低阻抗驱动。
- 过压保护 :ADC引脚绝对最大额定电压为VDDA+0.3V。若传感器可能输出高于3.3V的电压(如电池电压监测),必须加入钳位二极管(如BAT54)或电阻分压网络,防止永久性损坏。
5.3 采样时间(Sampling Time)的合理选择
采样时间决定了ADC采样电容(内部)充电至输入电压所需的时间。时间过短,电容未充满,读数偏低;时间过长,降低采样率。
- F103提供1.5, 7.5, 13.5, 28.5, 41.5, 55.5, 71.5, 239.5个ADC时钟周期可选。
- 对于低阻抗源(如电位器<10kΩ), 13.5 Cycles (约1.1μs @12MHz)已绰绰有余。
- 对于高阻抗源,应选用更长的采样时间(如 71.5 Cycles ),并相应降低ADCCLK以留出足够时间。
- 切忌盲目设为最长 :这会无谓牺牲性能,且在连续转换模式下,过长的采样时间会挤压转换时间,可能导致EOC标志丢失。
5.4 软件抗干扰策略
即使硬件完美,软件层面的干扰亦不可忽视。
- 多次采样取平均 :对同一通道连续采样N次(如8或16次),丢弃最大最小值后求平均。这能有效抑制随机脉冲噪声。注意, HAL_ADC_Start() 与 HAL_ADC_Stop() 的开销较大,应改用 HAL_ADC_Start_IT() (中断)或 HAL_ADC_Start_DMA() (DMA)实现高效批量采集。
- 中值滤波(Median Filter) :对N个采样值排序,取中间值。对消除偶发尖峰(如ESD)效果极佳,计算复杂度略高于均值滤波。
- 校准与偏移补偿 :ADC存在固有的零点偏移(Offset)和增益误差(Gain Error)。F103支持硬件校准( HAL_ADCEx_Calibration_Start() ),应在系统初始化后、正式采集前执行一次。此外,可在无输入时(悬空或短接到GND)读取一个“零点值”,后续所有读数减去此值,可消除系统偏移。
我在一个电机电流检测项目中,初始未做任何滤波,电流读数在空载时抖动达±0.2A。加入8点滑动平均后,抖动降至±0.02A,完全满足控制环路需求。这印证了“软件滤波是成本最低、见效最快的精度提升手段”。
6. 进阶思考:从单次采集到实时数据流
本章实践以单次、阻塞式采集为起点,但这仅适用于低速、非实时场景。当需求升级为:
- 高速数据记录(如音频采样),
- 实时闭环控制(如电机FOC算法),
- 低功耗休眠唤醒采集,
则必须引入更高级的ADC工作模式。
- DMA(Direct Memory Access)模式 :配置ADC在每次转换结束时,自动将
ADC_DR值搬运至指定内存数组,无需CPU干预。CPU可专注其他任务,ADC与DMA协同构成高效数据流管道。这是实现>10ksps采样率的唯一可行方案。 - 中断(IT)模式 :ADC转换结束触发中断,在ISR中读取数据并处理。适用于事件驱动型应用,如检测到电压越限时立即报警。
- 注入通道(Injected Channels) :ADC提供4个高优先级的注入序列,可被常规转换(规则通道)打断。适合对关键信号(如过压保护)进行紧急、插队式采集。
这些模式的配置与HAL API使用,已超出本章基础范畴,但它们共同指向一个核心理念: ADC不是孤立的外设,而是嵌入式系统数据流架构中的一个节点 。理解其在系统级数据通路中的角色,方能驾驭更复杂的实时应用。
摇杆实验的终点,恰是探索更广阔ADC世界的起点。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)