STM32F103 ADC电压采集:从硬件设计到软件滤波的完整实践指南

📅 2026/8/4 3:46:48
STM32F103 ADC电压采集:从硬件设计到软件滤波的完整实践指南
1. 项目概述从零开始理解STM32的ADC电压采集拿到一块STM32F103的开发板想用它来测量一下电池电压、传感器输出的模拟信号或者只是想验证一下某个电位器的阻值变化ADC模数转换器功能几乎是绕不开的第一课。很多新手朋友一上来就照着例程配置采回来的数值却飘忽不定或者根本对不上实际的电压值最后只能对着开发板干瞪眼。其实STM32的ADC功能本身并不复杂但要想把它用稳、用准里面有不少细节需要琢磨。今天我就结合自己这些年调试各种传感器和电源监控项目的经验带你从头到尾捋一遍STM32F103的ADC电压采集把那些容易踩的坑和提升精度的技巧都摊开来讲清楚。简单来说这个项目就是利用STM32F103微控制器内部集成的ADC模块将外部连续的模拟电压信号比如0-3.3V转换成微控制器可以识别和处理的数字量。这听起来简单但要做好你需要关注几个核心点ADC的基准电压源是否稳定、输入信号是否在量程内、采样速率和精度如何权衡、以及如何对采集到的原始数据进行处理以得到真实可靠的电压值。无论是做智能家居的温湿度监测还是无人机电池管理甚至是简单的电子秤这套流程都是相通的。接下来我会从设计思路、硬件连接、软件配置到数据处理一步步拆解让你不仅能“采集到”电压更能“采集准”电压。2. 核心硬件设计与电路连接要点在动手写代码之前硬件电路的设计是保证ADC采集精度的基石。这一步没做好后面软件再怎么优化也是事倍功半。2.1 基准电压的选择与重要性STM32F103的ADC需要一个参考电压来决定其量程上限。这个参考电压的稳定性和精度直接决定了你采集结果的准确度。VDDA与VREF对于大多数STM32F103系列芯片尤其是常见的C8T6等型号ADC的参考电压正端VREF在芯片内部是与模拟电源VDDA相连的。这意味着你的VDDA供电质量就是ADC的基准。一个常见的误区是认为只要3.3V主电源稳定就行。实际上你需要确保供给VDDA引脚和VSSA的电压是干净、稳定的。注意务必从MCU的3.3V线性稳压器LDO输出端通过一个磁珠或小电阻如10Ω再配合一个10uF的钽电容和一个0.1uF的陶瓷电容组成的π型滤波电路单独给VDDA和VSSA供电。这能有效隔离数字电路噪声对ADC基准的干扰。我实测过不加这个滤波在数字IO频繁动作时ADC读数可能会有几十个LSB的跳动。外部基准源对于精度要求高的场合如电子秤、精密测量强烈建议使用外部基准电压芯片如REF3025、ADR4525等。这些芯片能提供2.5V或3.0V等高精度、低温漂的电压。你需要将外部基准源的输出连接到芯片的VREF引脚如果封装支持并断开与VDDA的内部连接通常通过配置选项字节实现。这虽然增加了成本和布局复杂度但能将系统精度提升一个数量级。2.2 输入信号调理与保护电路你不能直接把待测电压接到MCU的ADC引脚上需要经过适当的调理。分压电阻计算如果待测电压如12V电池高于ADC的量程上限VDDA必须使用电阻分压。这里的关键是分压电阻的阻值选择和精度。阻值太大会引入更多的热噪声且对输入阻抗敏感阻值太小则会消耗过多待测电路的电流。我通常选择百kΩ级别的电阻例如用100kΩ和33kΩ串联对12V分压在ADC引脚得到约3.0V的电压。实操心得分压电阻的精度至少选择1%有条件用0.1%。计算时不要只算理论值要用公式V_adc V_input * (R2/(R1R2))仔细核算并确保在输入电压最大值时V_adc仍略低于VDDA留有约0.1V余量以防过冲。网上有很多“分压电阻计算器”小程序输入你的输入电压范围和所需输出电压范围它能帮你快速算出标准阻值组合。RC低通滤波在ADC输入引脚处必须添加一个RC低通滤波器。通常是一个100Ω左右的电阻串联再并联一个100pF到1nF的电容到地VSSA。这个电路有两个作用一是滤除高频噪声二是限制输入电流在意外电压过高时保护ADC输入端口。这个电容就是所谓的“采样电容”它会在ADC采样期间被充电到输入电压。关于BAV99等保护二极管在搜索热词里看到了“adc采样保护电路能不能用bav99”。BAV99是一个双二极管封装常用于信号钳位保护。可以用但要非常小心。它的原理是将电压钳位在VCC0.7V和GND-0.7V之间。然而STM32的ADC引脚通常不允许电压超过VDDA0.3V或低于VSSA-0.3V。如果你用BAV99将信号钳位到3.3V电源和地当电源有噪声或波动时可能反而将噪声引入信号或因为二极管漏电流影响小信号测量精度。对于大多数3.3V系统内的信号一个串联电阻加对地电容的滤波组合配合MCU内部已有的钳位二极管通常已足够。只有在测量可能远超出量程的外部强干扰信号时才考虑在滤波电路前级加入BAV99并且要评估二极管漏电流的影响。2.3 电源与接地布局这是硬件上最容易忽视也最难排查的问题。模拟地与数字地芯片上有VSS数字地和VSSA模拟地引脚。正确的做法是在PCB上用较宽的走线将VSS和VSSA在芯片引脚附近单点连接在一起通常通过一个0Ω电阻或磁珠方便调试。然后这个连接点再连接到系统的主地平面。确保模拟部分VDDA、VSSA、ADC输入滤波电路的电流回路尽可能小且不要被数字部分的大电流地回路所包围。电源去耦除了前面提到的VDDA滤波每个VDD引脚到最近的VSS引脚之间都必须放置一个0.1uF的陶瓷电容且尽量贴近芯片引脚。这是抑制芯片内部数字开关噪声的关键。3. 软件配置与CubeMX实战硬件准备妥当后我们进入软件层面。使用STM32CubeMX进行初始化配置可以极大提高效率但理解每个配置项背后的意义更重要。3.1 ADC模式与参数配置详解打开CubeMX选择你的STM32F103芯片激活ADC1模块。1. 时钟配置ADC的时钟ADCCLK由PCLK2分频得到。STM32F103的ADC时钟最高不能超过14MHz。如果系统时钟是72MHz通常选择6分频得到12MHz这是一个稳定且常用的值。时钟频率直接影响转换速度频率越高转换越快但可能引入更多噪声需要权衡。2. 分辨率与对齐方式STM32F103的ADC是12位的即转换结果范围是0-4095。在“Resolution”里就选“12-bit”。对齐方式选“Right alignment”右对齐即可这样读取数据寄存器DR得到的就是一个0-4095的整数最直观。3. 扫描模式与连续转换扫描模式 (Scan Conversion Mode):如果你使能了多个ADC通道比如通道0和通道1使能扫描模式后ADC会自动按你设定的顺序在“Rank”里设置依次转换这些通道。单次测量多通道时必须开启。连续转换模式 (Continuous Conversion Mode):使能后ADC完成一次或一轮扫描转换后会自动立即开始下一次转换无需软件反复触发。对于需要实时监控的场合非常有用。如果是单通道单次采样通常禁用此模式由软件或定时器触发。4. 间断模式这个模式一般用得少。它允许你定义每次触发只转换序列中的n个通道。可以忽略。5. 数据对齐与DMA右对齐即可。如果你需要高速、连续采集多个通道的数据务必启用DMA。在“DMA Settings”里添加模式选择“Circular”循环模式这样ADC每转换完一个数据DMA就会自动把它搬运到你指定的内存数组中完全不需要CPU干预效率极高也是实现多通道同步采集的基础。6. 采样时间 (Sampling Time)这是极其关键的一个参数它决定了ADC内部采样电容连接外部信号进行充电的时间。时间太短电容充电不足转换结果会偏低且不稳定时间太长虽然稳定但降低了整体采样率。采样时间以时钟周期数为单位。高阻抗信号源如果你的信号源内阻较大比如经过了大阻值分压电阻或传感器本身输出阻抗高必须设置较长的采样时间。例如信号源阻抗为10kΩADC内部采样电容按数据手册典型值取10pF要达到1/2 LSB的精度需要的时间常数 τ R * C 10k * 10p 100ns。考虑到多个时间常数才能充饱通常需要设置采样时间在10个τ以上即1us。在12MHz ADCCLK下1个周期是83ns所以至少需要12个周期。我个人的经验是对于分压电路或传感器输出直接设置成“239.5 Cycles”这个最长时间先保证精度再考虑速度。低阻抗信号源如果信号来自运放输出阻抗很低可以用较短的采样时间如“13.5 Cycles”或“28.5 Cycles”来提升速度。3.2 通道配置与触发源选择在“Parameter Settings”标签页下方配置你需要使用的具体通道。Rank序列顺序在“Channel Selection”区域选择你要用的ADC引脚对应的通道如IN0, IN1。每选择一个下方就会增加一个“Rank”。你需要为每个启用的通道设置一个唯一的Rank编号123...这就是扫描转换时的顺序。同时为每个Rank选择采样时间如上所述。触发源 (External Trigger Conversion Source)软件触发最简单调用HAL_ADC_Start(hadc1)或HAL_ADC_Start_IT(hadc1)中断方式即可开始一次转换。适合低速、非周期性的采样。定时器触发这是实现精准定时采样的标准做法。你可以选择一个定时器如TIM2的某个通道如CC2事件作为触发源。在CubeMX的ADC配置里选择“External Trigger Conversion Source”为“Timer 2 Capture Compare 2 event”。然后在代码里启动该定时器并配置好它的自动重载值ARR和预分频PSC以设定采样频率。这样ADC就会以极其精确的时间间隔自动采样不受程序其他部分的影响对于数字信号处理如FFT至关重要。结束转换事件 (End Of Conversion Selection)单次转换每个Rank转换完成后产生EOC事件。序列转换所有Rank组成的序列转换完成后才产生一个EOC事件。如果你用DMA搬运多通道数据通常选“序列转换”这样DMA会在搬完一轮所有通道数据后才产生中断方便处理。配置完成后生成代码。CubeMX会帮你初始化好ADC、GPIO自动将ADC引脚配置为模拟输入模式以及DMA如果启用。4. 采集程序编写与数据读取策略有了CubeMX生成的初始化代码我们只需要编写业务逻辑部分。这里提供几种常见的采集模式。4.1 单次阻塞式读取这是最简单的方式适用于不频繁的采样。// 启动一次转换并等待完成 HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 10) HAL_OK) { // 读取转换结果 uint16_t adc_value HAL_ADC_GetValue(hadc1); // 将ADC值转换为电压值 (假设VDDA3.3V) float voltage (float)adc_value / 4095.0f * 3.3f; printf(ADC Value: %d, Voltage: %.3f V\r\n, adc_value, voltage); } HAL_ADC_Stop(hadc1);这种方式会阻塞程序直到转换完成10是超时时间毫秒。简单但效率低。4.2 中断方式读取适合在转换完成后需要立即处理数据的场景避免了阻塞。首先在CubeMX的NVIC设置中使能ADC的全局中断。然后在代码中// 启动中断方式的转换 HAL_ADC_Start_IT(hadc1); // 转换完成中断回调函数需重写 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc-Instance ADC1) { uint16_t adc_value HAL_ADC_GetValue(hadc1); // 处理数据... // 如果需要连续采样可以在这里再次启动转换如果配置了单次模式 // HAL_ADC_Start_IT(hadc1); } }在中断回调函数里处理数据要快进快出避免复杂运算。4.3 DMA循环采集推荐用于多通道/高速这是最强大、最常用的方式尤其适合多通道轮询。步骤CubeMX配置使能ADC的DMA请求模式选择“Circular”数据宽度都选“Word”半字也行但字对齐效率可能更高。定义缓冲区在程序中定义一个足够大的数组作为DMA搬运的目的地。例如如果你要采集3个通道Rank1,2,3并且希望每通道存储100次采样值可以定义uint16_t adc_dma_buffer[300];。DMA会按[ch1_sample1, ch2_sample1, ch3_sample1, ch1_sample2, ch2_sample2, ch3_sample2, ...]的顺序循环填充这个数组。启动DMA采集// 启动ADC并让DMA将数据搬运到指定缓冲区 if (HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_dma_buffer, 300) ! HAL_OK) { Error_Handler(); }一旦执行ADC和DMA就会在后台自动运行不停地将转换结果填入adc_dma_buffer。数据处理你可以在主循环中定期检查缓冲区中的数据或者使用DMA的“传输完成半满”和“传输完成全满”中断来通知CPU处理数据。更高效的做法是使用DMA的双缓冲Double Buffer模式但标准HAL库对ADC的DMA循环模式已封装得很好直接访问缓冲区即可。注意由于是循环覆盖你需要处理好数据同步防止处理过程中缓冲区被覆盖。5. 数据处理与滤波算法实战ADC采回来的原始数据是充满噪声的必须经过处理才能得到稳定、可信的电压值。5.1 原始值到实际电压的换算换算公式很简单但有几个细节float actual_voltage (float)raw_adc_value / 4095.0f * V_ref_actual;raw_adc_value: 读取的12位ADC原始值0-4095。4095.0f: 一定要用浮点数否则整数除法会丢失精度。V_ref_actual:这是关键它不是理想中的3.3V而是你系统中VDDA或外部基准源的实际电压。如果你的VDDA是由一个普通的LDO从5V降压得到其输出可能不是精确的3.300V可能是3.28V或3.32V。这直接会导致换算误差。校准方法内部参照电压STM32内部有一个温度传感器通道和一个内部参照电压通道VREFINT。这个VREFINT的电压值在芯片出厂时已校准存储在系统存储区对于F103地址为0x1FFFF7BA和0x1FFFF7C0。你可以通过读取这个通道的ADC值反向推算出实际的VDDA电压。这是最推荐的方法。// 1. 在CubeMX中使能VREFINT通道Channel 17。 // 2. 读取VREFINT通道的ADC原始值 raw_vrefint。 // 3. 计算实际VDDA #define VREFINT_CAL ((uint16_t*)(0x1FFFF7BA)) // 参照数据手册地址可能不同 #define VREFINT_CAL_VOLTAGE (1.20f) // 典型值1.20V以数据手册为准 float vdda_actual (float)(*VREFINT_CAL) * VREFINT_CAL_VOLTAGE / (float)raw_vrefint; // 4. 用这个vdda_actual去换算其他通道的电压外部精密电压测量用一个高精度万用表测量VDDA引脚的实际电压作为V_ref_actual代入公式。5.2 软件滤波算法即使硬件滤波做得很好软件滤波仍是必不可少的一步。这里介绍几种常用且有效的算法。1. 均值滤波最简单采集N个点求和后取平均。能有效抑制随机噪声但会引入滞后且对突发性干扰尖峰无效。#define SAMPLE_COUNT 64 uint32_t sum 0; for(int i0; iSAMPLE_COUNT; i) { sum HAL_ADC_GetValue(hadc1); // 假设是阻塞读取 // 或者从DMA缓冲区取数 } uint16_t filtered_value sum / SAMPLE_COUNT;2. 滑动平均滤波维护一个固定长度的队列每次新数据进来去掉最老的数据计算队列中所有数据的平均值。响应速度比一次性均值滤波快。#define MOVING_AVG_SIZE 16 uint16_t buffer[MOVING_AVG_SIZE] {0}; uint8_t index 0; uint32_t sum 0; // 每次获取新ADC值后调用 uint16_t moving_average_filter(uint16_t new_sample) { sum sum - buffer[index] new_sample; // 减去旧的加上新的 buffer[index] new_sample; index (index 1) % MOVING_AVG_SIZE; return (uint16_t)(sum / MOVING_AVG_SIZE); }3. 中值滤波取N个采样值排序后取中间值。对脉冲干扰尖峰噪声有奇效但计算量稍大。常用于去除偶尔的跳变。#define MEDIAN_FILTER_SIZE 5 // 通常取奇数 uint16_t median_filter(uint16_t new_sample) { static uint16_t buffer[MEDIAN_FILTER_SIZE] {0}; static uint8_t count 0; uint16_t temp[MEDIAN_FILTER_SIZE]; buffer[count] new_sample; count (count 1) % MEDIAN_FILTER_SIZE; // 复制到临时数组排序 memcpy(temp, buffer, sizeof(buffer)); // 使用简单的冒泡排序找中值对于小数组足够 for(int i0; iMEDIAN_FILTER_SIZE-1; i) { for(int ji1; jMEDIAN_FILTER_SIZE; j) { if(temp[i] temp[j]) { uint16_t t temp[i]; temp[i] temp[j]; temp[j] t; } } } return temp[MEDIAN_FILTER_SIZE/2]; // 返回中值 }4. 一阶低通滤波惯性滤波这是一种模拟硬件RC滤波的软件实现计算量小效果好。float alpha 0.1f; // 滤波系数0alpha1越小越平滑响应越慢 float filtered_value 0.0f; // 每次获取新ADC值后调用 float low_pass_filter(float new_sample) { filtered_value filtered_value alpha * (new_sample - filtered_value); return filtered_value; } // 初始化时 filtered_value 可以赋值为第一次采样的值。实操心得在实际项目中我经常采用“中值滤波滑动平均”的组合拳。先用中值滤波窗口大小3或5去掉可能的异常尖峰再将结果送入滑动平均滤波器窗口大小8或16进行平滑。这种组合既能抗干扰又能得到平滑的输出在大多数测量场景下表现都非常稳健。6. 精度提升技巧与高级话题当你掌握了基础采集和滤波后下面这些技巧可以帮助你将测量精度推向极限。6.1 过采样与分辨率提升STM32F103的ADC是12位的理论分辨率是3.3V/4096 ≈ 0.8mV。如果你需要测量更微小的电压变化比如高精度温度传感器可以通过“过采样”技术将有效分辨率提高到14位甚至16位。原理对同一信号进行多次采样并累加累加值会比单次采样值具有更多的位数。通过数学处理可以提取出这些低位信息。 例如进行16次12位采样并累加得到一个16位的数据12位 log2(16)4位。将这个16位数右移2位相当于除以4就得到了一个14位的“高精度”结果。实现STM32的ADC硬件直接支持过采样。在CubeMX的ADC参数设置中找到“OverSampling”选项并启用。你可以设置“Ratio”过采样倍数如16和“Right Bit Shift”右移位数值如2对应14位输出。硬件会自动完成累加和移位你直接读取结果寄存器即可极大地减轻了CPU负担。代价采样速度会降低为原来的1/过采样倍数。这是一种用速度换精度的经典方法。6.2 温度补偿与内部参照电压ADC的性能会受芯片结温影响。对于要求极高的应用需要考虑温度补偿。利用内部温度传感器STM32内部有一个温度传感器连接到ADC的通道16。通过读取它的电压值可以推算出芯片的大致温度。公式在数据手册中给出通常类似于Temperature (V_sense - V_25) / Avg_Slope 25其中V_25是25度时的传感器电压Avg_Slope是温度系数mV/°C。应用场景如果你测量的信号其物理特性也随温度变化如热电偶、某些压力传感器或者你需要在整个工作温度范围内保持ADC换算的绝对精度那么同时监测芯片温度并用这个温度值去修正你的测量结果或基准电压值就非常有必要。6.3 多ADC交替采样与同步规则在一些高级型号如STM32F4/F7/H7中有多个ADC模块可以配置成交替采样模式来倍增采样率。虽然STM32F103通常只有一个ADC1但了解这个概念有助以后升级。交错采集两个ADC配置为采样同一个通道但相位相差180度。当ADC1在转换时ADC2开始采样ADC2转换时ADC1开始下一次采样。这样在单个ADC的采样转换周期内系统完成了两次采样有效采样率翻倍。这需要精确的定时器触发和DMA配合。同步采集多个ADC在同一时刻触发同时采样不同的通道适用于需要同步测量多路信号的应用如三相电机电流。对于F103虽然无法硬件交错但你可以通过精心设计软件和DMA在ADC单次扫描模式配合DMA快速搬运再以较高频率触发如定时器触发也能达到较高的聚合采样率。7. 典型问题排查与调试心得即使按照上述步骤操作在实际调试中仍会遇到各种问题。这里汇总一些常见“病症”和“药方”。7.1 读数不稳定跳动大这是最常见的问题。检查硬件电源和地用示波器查看VDDA和VSSA引脚上的噪声。应该有平滑的直流电压。如果看到毛刺加强滤波电容。信号源测量ADC输入引脚本身的电压是否稳定。信号源本身是否稳定负载能力是否足够尝试在信号源近端并联一个大电容如10uF看看。采样时间最可能的原因立即将采样时间调到最大值“239.5 Cycles”看看跳动是否显著减小。如果是说明采样时间不足需要根据信号源阻抗计算并调整。检查软件滤波你应用软件滤波了吗即使硬件完美也至少要用个滑动平均。干扰源在ADC采样期间是否在频繁操作其他GPIO特别是高速翻转的IO或者开启了其他大功率外设如PWM驱动电机尝试屏蔽这些操作看ADC是否变稳。如果是需要考虑优化PCB布局或软件时序将敏感模拟部分与数字噪声源隔离。7.2 读数不准有固定偏差或比例错误基准电压不准这是首要怀疑对象。用万用表测量VDDA引脚的实际电压并与你代码中用于换算的V_ref_actual对比。使用前面提到的内部VREFINT校准法是最佳实践。分压电阻误差检查你使用的分压电阻的实际阻值用万用表量是否与标称值有较大偏差电阻精度是否足够建议1%ADC引脚配置确保GPIO已正确初始化为模拟输入模式。如果误配置为浮空输入读数会完全不可预测。量程超限输入电压是否超过了VDDA或低于0V即使有保护电路接近极限值时线性度也会变差。7.3 DMA数据错乱或搬运不全缓冲区大小与DMA配置检查你定义的DMA缓冲区大小字节数是否与HAL_ADC_Start_DMA函数中指定的数据数量匹配。注意数据宽度如果ADC是12位右对齐数据寄存器是16位DMA配置为半字16位传输那么数据数量就是数组元素个数。如果配置为字32位传输数量就要减半。内存对齐确保DMA目标缓冲区地址是对齐的通常没问题编译器会处理。DMA中断冲突如果同时使用了其他DMA通道检查优先级和是否有冲突。在DMA传输完成中断中处理数据要高效避免长时间占用导致ADC的DMA请求被堵塞。7.4 采样率达不到预期时钟计算重新核算ADC时钟ADCCLK。总转换时间 采样时间 12.5个周期固定转换时间。例如采样时间239.5周期ADCCLK12MHz则一次转换时间 (239.512.5) / 12MHz ≈ 21us即最高采样率约47.6kHz。这是单通道连续转换的理论极限。多通道扫描如果是多通道扫描总时间 通道1采样时间12.5 通道2采样时间12.5 ...。触发频率不能高于这个总时间的倒数。软件开销如果你是用软件触发或中断方式在中断回调或主循环中处理数据的时间也会限制实际采样率。使用DMA循环模式定时器触发才能接近理论极限。调试ADC问题时示波器是你最好的朋友。用它观察输入信号、电源纹波、以及ADC采样保持期间的信号波形很多问题都能直观地找到原因。从最基础的硬件电路检查起逐步验证软件配置耐心加上方法一定能让你的STM32F103的ADC采集既稳定又精准。