ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32两路ADC测量实战:从原理到滤波算法与国赛真题解析

2026/8/29 21:56:44 拓冰建站 浏览量
STM32两路ADC测量实战:从原理到滤波算法与国赛真题解析 1. 项目概述从国赛真题看ADC测量的核心价值在蓝桥杯嵌入式国赛的赛场上两路ADC测量这个题目可以说是一个“常客”也是一个检验选手嵌入式系统基本功的“试金石”。乍一看题目要求似乎很简单用单片机采集两路模拟电压信号。但真正做过的朋友都知道这里面门道可深了。它绝不仅仅是调用一下HAL库的HAL_ADC_Start那么简单而是综合考察了你对STM32国赛常用平台ADC外设的理解深度、对实时性与精度的平衡能力以及对嵌入式系统资源管理的全局观。为什么国赛如此青睐这个题目因为ADC是连接物理世界与数字世界的桥梁。无论是智能车的光敏电阻、角度传感器还是工业控制中的温度、压力变送器最终都需要通过ADC将连续的模拟量转换为单片机可以处理的数字量。两路测量则引入了“同步”、“交替”、“优先级”等更复杂的应用场景。你不仅要让ADC工作起来还要让它稳定、准确、高效地工作同时处理好两路数据可能存在的相互干扰、采样时机冲突等问题。这背后涉及时钟配置、采样时间计算、DMA传输、数据处理算法等一系列硬核知识点。这篇文章我就以一个过来人的身份结合国赛真题的典型要求和实际项目中的经验带你彻底吃透STM32平台下的两路ADC测量。我会从最基础的原理和配置讲起一直深入到滤波算法、误差分析和实战避坑指南。无论你是正在备赛的蓝桥杯选手还是刚接触STM32的嵌入式开发者相信这篇长文都能给你带来实实在在的帮助。2. 核心需求与方案设计拆解面对“两路ADC测量”这个任务我们首先要摒弃“拿到就写代码”的冲动。一个好的开始是成功的一半在动手之前我们必须把需求拆解清楚并据此设计一个稳健的方案。2.1 需求深度解析国赛题目通常不会直接告诉你所有细节需要我们根据描述推断出隐含的高阶需求基本功能需求这是明面上的要求。使用STM32的ADC模块对两路指定的模拟输入通道例如PA5和PA6进行电压采集并将结果通过串口发送到上位机显示或者用于后续的控制逻辑。性能与精度需求这是国赛拉开差距的关键。题目可能要求测量范围如0-3.3V、精度如误差小于±0.05V或者对电压变化的响应速度有要求。这直接决定了ADC的分辨率12位、采样率以及参考电压的稳定性。实时性与同步性需求两路测量它们之间的关系是什么独立异步采样两路信号变化缓慢且无关联可以分时复用ADC轮流采样。这是最简单的方式但无法获取同一时刻的两路信号值。定时同步采样需要获取同一时刻的两路信号状态例如计算功率时需要瞬时电压和电流。这通常需要利用ADC的扫描模式DMA或者双ADC模式如果MCU支持。触发采样ADC的启动由外部事件如定时器、引脚触发这对于需要与系统其他部分严格同步的采样至关重要。系统资源与稳定性需求在比赛中CPU资源非常宝贵。如果使用阻塞式的轮询读取ADC会大量占用CPU时间。因此采用DMA直接存储器访问将ADC数据自动搬运到内存是提升系统效率、保证其他任务如按键扫描、显示刷新流畅运行的标准做法。此外电源噪声、PCB布局也会影响ADC的稳定性这属于硬件设计范畴但在软件上我们可以通过滤波来弥补。2.2 方案选型与决策逻辑基于以上需求我们可以形成一个清晰的方案决策树第一步确定采样模式对于大多数蓝桥杯国赛场景两路信号通常不需要严格的同步到纳秒级但要求稳定的周期性采样。因此规则组扫描模式 DMA传输是首选方案。我们将两路通道配置到一个ADC的同一个规则组序列里ADC会自动按顺序转换它们转换完成后通过DMA一次性将两个结果搬运到指定的数组。这种方式不占用CPU且能保证两路采样的间隔极短取决于ADC的转换时间对于大多数应用可以近似认为是“同步”的。为什么不直接用双ADC像STM32G4、H7等系列确实支持双ADC可以实现真正的硬件同步采样。但对于国赛常用的中端型号如STM32F103、STM32G431通常只有一个ADC模块可能有多个通道。因此扫描DMA是最通用、最经济的实现方式。第二步确定触发源ADC何时开始一次转换有三种选择软件触发调用HAL_ADC_Start或HAL_ADC_Start_DMA。简单但时序控制不精确。定时器触发配置一个定时器如TIM2产生更新事件并将其连接到ADC的触发输入端。这是强烈推荐的方式。它可以产生固定频率的采样时钟是构建数据采集系统的基础。例如配置定时器每1ms触发一次ADC那么你的采样频率就是稳定的1kHz。第三步确定数据搬运与处理方式搬运方式毫无悬念选择DMA。在CubeMX中配置DMA为循环模式Circular这样ADC就会源源不断地采样DMA源源不断地将数据写入一个环形缓冲区CPU只需定期去缓冲区读取处理好的数据即可。处理方式ADC的原始值需要转换为电压值。公式很简单电压 (原始值 / 4095) * 参考电压Vref。但一次采样值噪声很大必须进行软件滤波。常用的有递推平均滤波窗口滑动平均、中值滤波、一阶滞后滤波等。在比赛中递推平均滤波实现简单、效果明显是性价比最高的选择。注意在CubeMX中配置DMA时数据宽度要匹配。ADC是12位结果通常存放在16位变量中。因此DMA的外设地址ADC数据寄存器宽度设为Half Word半字16位内存地址你的数组宽度也设为Half Word。同时要开启DMA的循环模式并配置好数据长度等于你规则序列的通道数这里是2。3. 硬件连接与CubeMX工程配置详解理论分析完毕我们进入实战环节。假设我们使用STM32G431RB蓝桥杯第十一届国赛指定板的PA5和PA6作为两路ADC输入。3.1 硬件电路注意事项在将信号接入MCU之前有几点硬件上的“坑”需要提前避开参考电压源ADC的精度极大程度依赖于参考电压VREF的稳定性。STM32G431的VREF引脚默认内部连接到VDDA模拟电源。务必确保VDDA供电干净、稳定。在板子上VDDA通常通过一个磁珠或0欧电阻从VCC3.3V隔离而来并需要搭配一个10uF钽电容和一个0.1uF陶瓷电容进行去耦。在比赛中如果发现ADC值跳动大首先检查电源。信号调理如果输入信号可能超过ADC的量程0-3.3V必须增加钳位保护电路如使用两个二极管钳位到地和VDDA。如果信号源内阻很大会导致采样电容充电时间不足产生误差。必要时可以在输入端增加一个电压跟随器运放作为缓冲。PCB布局模拟信号走线应尽量短远离数字信号线特别是时钟、PWM线避免耦合噪声。在原理图上通常会将ADC输入引脚标注为“ANALOG”以作提醒。3.2 CubeMX图形化配置全流程接下来我们一步步使用STM32CubeMX完成所有初始化代码的生成。这是现代STM32开发的基础务必熟练掌握。1. 引脚配置在Pinout Configuration视图下找到PA5和PA6将其功能设置为ADC1_IN8和ADC1_IN9具体通道号需查数据手册G431的PA5对应IN8PA6对应IN9。同时检查VREF和VDDA、VSSA的连接确保它们已正确连接到电源和地。2. ADC1配置在Analog - ADC1中Resolution选择12-bit。这是精度和速度的平衡点。Scan Conversion ModeEnable。这是多通道扫描的关键。Continuous Conversion ModeDisable。我们不希望ADC不停地转换而是由定时器触发。Discontinuous Conversion ModeDisable。DMA Continuous RequestsEnable。这样DMA请求会连续发生配合循环DMA实现不间断传输。End Of Conversion Selection选择EOC after each conversion每个通道转换后产生EOC或EOC after sequence整个序列转换后产生一个EOC。对于DMA传输通常选后者让DMA在一次扫描完成后搬运数据。Low Power Auto WaitDisable。Clock Prescaler选择PCLK2 divided by 2或by 4。ADC时钟ADCCLK不能超过手册规定的最大值例如G431是60MHz。根据系统时钟计算选择合适的分频确保ADCCLK不超频。Sampling Time这是关键参数点击Channel 8和Channel 9为它们选择采样时间。采样时间 (采样周期数 12.5) / ADCCLK。采样周期数越长ADC内部的采样电容充电越充分精度越高但转换速度越慢。对于中等阻抗10kΩ的信号源选择47.5 Cycles或92.5 Cycles是比较稳妥的。在比赛中如果时间充裕可以选长一些以求稳定。3. 规则组配置在ADC1的Regular Conversion Mode下点击Add将Channel 8和Channel 9加入规则组。可以调整它们的顺序这决定了DMA搬运数据的数组顺序。每个通道的采样时间在这里也可以单独设置。4. 触发源配置在Trigger Source中选择Timer 2 Trigger Out event。这意味着ADC将由TIM2的更新事件触发。5. DMA配置在DMA Settings中点击AddDMA Request选择ADC1。Mode选择Circular循环模式。Data Width选择Half Word与ADC数据寄存器宽度一致。6. 定时器TIM2配置我们需要一个定时器来定期触发ADC。在Timers - TIM2中选择Clock Source为Internal Clock。在Parameter Settings中Prescaler预分频器根据所需采样频率计算。假设系统时钟为80MHz我们希望ADC每秒触发1000次1kHz。计算公式定时器时钟 / ((Prescaler1) * (Counter Period1)) 触发频率。例如定时器时钟80MHz欲得1kHz。令Prescaler 7999Counter Period 9。则频率 80,000,000 / (8000 * 10) 1000 Hz。在Trigger Output (TRGO) Parameters中将Master Mode Selection设置为Update Event。这样每次定时器更新溢出时都会产生一个触发信号输出给ADC。7. 时钟树配置检查系统时钟配置确保给ADC和定时器的时钟源正确且未超频。8. 生成代码在Project Manager中设置好项目名称、路径和IDE如MDK-ARM然后点击GENERATE CODE。CubeMX会生成所有初始化代码我们在main.c的用户代码区添加应用逻辑即可。4. 软件逻辑与滤波算法实现CubeMX帮我们搭建好了舞台现在需要我们来编写“剧本”。代码主要添加在main.c的/* USER CODE BEGIN 2 */和/* USER CODE BEGIN 3 */区域。4.1 变量定义与初始化首先定义存储ADC数据的缓冲区。由于我们使用DMA循环模式需要定义一个足够大的数组作为缓冲区。但为了简化我们可以定义一个二维数组或两个一维数组用于存放滤波后的最终数据。/* USER CODE BEGIN PV */ #define ADC_BUFFER_SIZE 2 // 规则组里有2个通道 #define FILTER_WINDOW_SIZE 10 // 滤波窗口大小 volatile uint16_t adc_raw_buffer[ADC_BUFFER_SIZE]; // DMA会直接写入这个数组 uint32_t adc_channel1_sum 0, adc_channel2_sum 0; uint16_t adc_filtered_value1 0, adc_filtered_value2 0; uint8_t filter_index 0; uint16_t filter_window1[FILTER_WINDOW_SIZE] {0}; uint16_t filter_window2[FILTER_WINDOW_SIZE] {0}; /* USER CODE END PV */在main函数的初始化部分(/* USER CODE BEGIN 2 */)启动ADC和DMA/* USER CODE BEGIN 2 */ // 启动ADC配合DMA。参数ADC句柄目标数组长度转换次数 if (HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_raw_buffer, ADC_BUFFER_SIZE) ! HAL_OK) { Error_Handler(); } // 启动触发ADC的定时器 HAL_TIM_Base_Start(htim2); /* USER CODE END 2 */4.2 主循环与数据处理在主循环/* USER CODE BEGIN 3 */中我们不再需要轮询ADC是否转换完成因为DMA在后台默默工作。我们的任务是定期处理adc_raw_buffer中的数据并进行滤波。这里介绍一种高效的递推平均滤波算法。它不需要保存所有历史数据只需一个累加和每次减去最旧的值加上最新的值。/* USER CODE BEGIN WHILE */ while (1) { // 简单的延时控制数据处理频率。这里假设定时器触发ADC是1kHz我们每10ms处理一次数据100Hz HAL_Delay(10); // 1. 读取DMA缓冲区中的原始值注意DMA在持续更新它但一次读取是原子的 uint16_t raw_ch1 adc_raw_buffer[0]; // 对应规则组中第一个通道 uint16_t raw_ch2 adc_raw_buffer[1]; // 对应规则组中第二个通道 // 2. 应用递推平均滤波 // 减去窗口中最旧的值 adc_channel1_sum - filter_window1[filter_index]; adc_channel2_sum - filter_window2[filter_index]; // 存入最新的值 filter_window1[filter_index] raw_ch1; filter_window2[filter_index] raw_ch2; // 加上最新的值 adc_channel1_sum raw_ch1; adc_channel2_sum raw_ch2; // 计算当前平均值 adc_filtered_value1 adc_channel1_sum / FILTER_WINDOW_SIZE; adc_filtered_value2 adc_channel2_sum / FILTER_WINDOW_SIZE; // 更新窗口索引 filter_index; if (filter_index FILTER_WINDOW_SIZE) { filter_index 0; } // 3. 将滤波后的值转换为电压假设Vref3.3V float voltage_ch1 (float)adc_filtered_value1 / 4095.0f * 3.3f; float voltage_ch2 (float)adc_filtered_value2 / 4095.0f * 3.3f; // 4. 可以通过串口打印出来或者用于其他控制逻辑 printf(CH1: %.3f V, CH2: %.3f V\r\n, voltage_ch1, voltage_ch2); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */实操心得FILTER_WINDOW_SIZE窗口大小的选择是门艺术。窗口越大滤波效果越好噪声越小但响应速度越慢延迟增大。在蓝桥杯比赛中你需要根据题目要求权衡。如果题目要求快速响应变化窗口就设小点如5如果要求测量稳定直流电压窗口可以设大点如20或50。可以通过实验观察不同窗口大小下输出波形的平滑度和阶跃响应速度来决定。4.3 进阶定时器触发与DMA的完美配合上述代码中我们用了HAL_Delay(10)来模拟一个处理周期。在实际的高性能应用中更好的做法是使用另一个定时器中断或者DMA传输完成半满/全满中断来触发数据处理。使用DMA传输完成中断CubeMX配置DMA时可以开启传输完成中断TC或半传输完成中断HT。这样当DMA搬运了半缓冲区或整个缓冲区的数据后会进入中断服务函数。我们可以在中断中设置一个标志位然后在主循环中检查这个标志位来处理数据。这种方式数据处理更及时且能精确知道数据块的时间戳。代码片段示例中断方式// 在CubeMX中使能DMA的传输完成中断 // 在stm32g4xx_it.c中找到DMA的中断服务函数 void DMA1_Channel1_IRQHandler(void) { if(__HAL_DMA_GET_FLAG(hdma_adc1, DMA_FLAG_TC1)) { __HAL_DMA_CLEAR_FLAG(hdma_adc1, DMA_FLAG_TC1); // 设置一个数据就绪标志 adc_data_ready 1; } } // 在主循环中 while(1) { if(adc_data_ready) { adc_data_ready 0; // 处理 adc_raw_buffer 中的数据... process_adc_data(); } // ... 其他任务 }5. 精度提升技巧与常见问题排查即使配置看起来都正确ADC的读数也可能不尽如人意。下面分享一些提升精度和稳定性的实战技巧以及常见问题的排查思路。5.1 软件校准与硬件补偿偏移与增益校准STM32的ADC模块内部提供了偏移和增益校准寄存器。在上电初始化ADC之后、开始转换之前可以调用HAL_ADCEx_Calibration_Start(hadc1, ADC_SINGLE_ENDED);函数进行自校准。这个操作能显著减少内部电路引起的零漂和增益误差。过采样技术如果你不需要很高的采样率但需要更高的分辨率例如想要得到14位或16位的有效结果可以使用过采样。原理是以更高的频率采样然后对多个样本进行累加和平均。例如4倍过采样可以将信噪比提高6dB等效于增加1位分辨率。STM32的ADC硬件支持过采样可以在CubeMX中直接配置非常方便。参考电压测量我们公式里的3.3V是理想值。实际上VDDA可能不是精确的3.300V。如果你的系统对绝对精度要求极高可以使用一个外部高精度基准源如REF3033连接到VREF引脚。或者用单片机另一个已知精确的ADC通道如果有多余的去测量一个已知的精准电压例如通过电阻分压得到的1.65V然后反推出实际的VREF电压用于后续计算。这是一种“自举”校准法。5.2 典型问题排查清单当你发现ADC值跳动大、不准或者完全不变化时可以按照以下清单逐一排查问题现象可能原因排查方法ADC值始终为0或接近01. 信号未接入或接地。2. ADC通道配置错误。3. ADC时钟未使能或配置错误。4. 采样时间太短电容未充电。1. 用万用表测量输入引脚电压。2. 核对CubeMX中引脚和通道映射。3. 在System Core RCC中确认ADC时钟源已使能检查时钟树配置。4. 大幅增加采样周期数如设为640.5 Cycles测试。ADC值始终为4095满量程或接近1. 输入电压超过VDDA3.3V。2. 引脚配置为输出模式或其他功能而非模拟输入。3. VREF未正确连接浮空。1. 测量输入电压。2. 检查引脚配置模式是否为Analog。3. 检查原理图确保VREF和VDDA连接稳定电源。ADC值跳动剧烈噪声大1. 电源噪声大VDDA不干净。2. PCB布局不佳数字噪声耦合到模拟走线。3. 信号源内阻大采样时间不足。4. 未进行软件滤波。1. 用示波器观察VDDA波形增加去耦电容。2. 检查布线模拟线远离数字线。3. 增加采样周期数或在信号源和ADC引脚间加一个小的滤波电容如100pF。4. 实现并调整软件滤波算法。两路ADC值互相干扰1. ADC通道间串扰。在扫描模式下切换通道时内部电容残留电荷影响。2. 共地噪声。1. 在规则组序列中在两个通道之间插入一个额外的延迟ADC_ExternalTrigConvEdge_None不更常用的是在CubeMX中调整Sampling Time给足放电时间。或者在软件上采样后丢弃第一个值作为稳定值。2. 确保模拟地和数字地单点连接良好。DMA数据不更新或错乱1. DMA缓冲区长度配置错误。2. DMA未配置为循环模式传输一次后停止。3. 数组越界。1. 核对HAL_ADC_Start_DMA的第三个参数应等于规则序列中的通道数。2. 在CubeMX中确认DMA模式为Circular。3. 确保定义的数组大小足够且访问索引在范围内。定时器触发不工作1. 定时器未启动。2. ADC触发源选择错误。3. 定时器分频计算错误频率太高或太低。1. 确认调用了HAL_TIM_Base_Start。2. 核对CubeMX中ADC的External Trigger Source是否对应了正确的定时器如Timer 2 TRGO。3. 重新计算定时器预分频和周期值用示波器或调试器查看定时器更新标志。5.3 调试技巧利用Keil的Logic Analyzer如果你使用Keil MDK其内置的Logic Analyzer功能是调试ADC的利器。你不需要示波器就能在IDE里看到ADC数据的波形。在调试模式下运行程序。点击View - Analysis Windows - Logic Analyzer。点击Setup添加要观察的变量如adc_filtered_value1。设置类型为unsigned int。运行程序你就能看到这个变量随时间变化的曲线图。这对于观察滤波效果、测量响应速度、发现异常跳动非常直观。最后我想说的是ADC的稳定性是“软硬结合”的成果。软件滤波能解决一部分噪声但干净的硬件设计才是根本。在备赛或做项目时多动手测试用示波器看看电源和信号用逻辑分析仪看看时序用调试器看看变量遇到问题对照清单耐心排查。当你成功获得两条稳定、准确的ADC曲线时那种成就感就是嵌入式开发最纯粹的乐趣所在。希望这篇长文能成为你征服蓝桥杯ADC题目的得力助手。