STM32G431两路ADC测量实战:从配置到滤波的国赛避坑指南
1. 项目背景与核心需求解析最近在准备蓝桥杯嵌入式国赛发现很多同学在“两路ADC测量”这个看似基础的任务上栽了跟头。这其实是一个典型的“会者不难难者不会”的考点。它考察的远不止是调用一个库函数那么简单而是对STM32 ADC模块的底层工作机制、多通道切换逻辑、以及如何在资源受限的竞赛环境中写出稳定、高效代码的综合理解。很多人以为用CubeMX配一下生成代码就能跑通结果一上板子数据要么跳得厉害要么两路数据互相干扰甚至直接卡死。这背后涉及GPIO的模拟输入配置、ADC时钟与采样时间的平衡、DMA与中断的合理选用以及至关重要的软件滤波算法。今天我就结合自己备赛和带学生训练的经验把“两路ADC测量”这个任务从原理到代码再到避坑指南彻底讲透让你在国赛现场能稳扎稳打地拿下这部分分数。简单来说这个任务的目标是基于蓝桥杯官方提供的CT117E开发板核心为STM32G431RBT6同时测量两路模拟电压信号并将结果稳定、准确地通过串口发送至上位机显示。它模拟了工业控制中常见的多路传感器信号采集场景比如同时监测电源电压和环境温度。你需要关注的不仅仅是ADC的初始化更要关注整个信号链路的可靠性。2. STM32G431 ADC模块深度剖析与配置要点蓝桥杯嵌入式国赛指定的STM32G431RBT6单片机其ADC模块是SAR逐次逼近型精度为12位。对于“两路测量”我们必须先吃透它的多通道工作模式。2.1 ADC工作模式选择扫描与连续STM32的ADC在处理多通道时主要有两种模式扫描模式Scan Mode和连续转换模式Continuous Conversion Mode。对于我们的两路测量标准的做法是启用扫描模式Scan ModeADC会按照我们预先在序列寄存器SQR中排好的顺序自动对多个通道进行转换。这是多通道采集的基础。禁用连续转换模式Continuous Conversion Mode我们通常采用单次转换Single Conversion然后由软件定时触发或者使能连续转换但配合DMA。在竞赛中为了控制功耗和简化流程更推荐使用定时器触发单次扫描的模式。这里有一个关键点采样时间Sampling Time的配置。在CubeMX的ADC配置界面你会看到一个参数“Sampling Time Common”。对于测量直流或缓变信号如电位器电压采样时间需要足够长让ADC内部的采样保持电容充放电到稳定。STM32G431的ADC时钟ADCCLK最高可达60MHz但通常我们会分频到一个较低的值如PCLK/4然后为每个通道设置一个“采样周期数”。这个周期数决定了采样时间的长短公式是采样时间 (采样周期数 12.5)个ADCCLK周期。对于板载的电位器输出阻抗较大建议将采样周期数设置为最大值例如640.5周期以确保采样准确避免因充电不足导致数据低位跳动。2.2 通道与GPIO的对应关系蓝桥杯CT117E开发板上可供ADC使用的引脚是固定的。你需要查阅板子原理图确认两路ADC具体连接到了哪个引脚。常见的分配是ADC1_IN1: 可能连接至板载电位器R37PB0。ADC1_IN2: 可能连接至板载光敏传感器PB1或预留的测试点。务必在CubeMX中将这两个GPIO的模式配置为“Analog”。这是一个新手极易忽略的步骤如果配置成浮空输入或其他模式ADC将无法正确读取到引脚上的模拟电压。2.3 转换序列与数据对齐在扫描模式下我们需要在“Rank”中设置转换序列。将通道1CH1的Rank设为1通道2CH2的Rank设为2。这样ADC就会先转换CH1再自动转换CH2。 数据对齐方式选择“右对齐”Right Alignment。这样12位的转换结果0-4095会存储在16位变量的低12位我们直接读取即可非常直观。转换后的数字值ADC_Value与输入电压V_in的关系为V_in (ADC_Value / 4095) * V_ref。其中V_ref是ADC的参考电压对于STM32G431通常就是芯片的供电电压3.3V。3. 采集策略设计轮询、中断与DMA的抉择如何获取两路转换完成的数据这里有三种主流策略其选择直接关系到代码的效率和稳定性。3.1 轮询方式Polling这是最简单粗暴的方式。启动转换后程序在一个循环里不断查询ADC标志位EOC End Of Conversion等待转换完成。HAL_ADC_Start(hadc1); // 启动ADC转换 if (HAL_ADC_PollForConversion(hadc1, 100) HAL_OK) { adc_value1 HAL_ADC_GetValue(hadc1); // 获取Rank1通道1结果 } // 注意扫描模式下一次HAL_ADC_PollForConversion等待的是整个序列两路转换完成。 // 然后需要调用两次HAL_ADC_GetValue来分别读取两个通道的数据。为什么不推荐在国赛中使用因为HAL_ADC_PollForConversion是一个阻塞函数它会死死卡住CPU直到转换完成。在这几十个微秒的时间里CPU什么都做不了无法处理按键、显示刷新等其他任务严重浪费资源且可能影响系统实时性。在国赛这种需要多任务协同采集、处理、显示、通信的场景下轮询方式是下策。3.2 中断方式Interrupt这是平衡简单性与效率的较好选择。配置ADC在每次序列转换完成后产生中断在中断服务函数中读取数据。 在CubeMX的NVIC设置中使能ADC全局中断。在代码中// 主循环中启动一次转换 HAL_ADC_Start_IT(hadc1); // ADC转换完成中断回调函数 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc-Instance ADC1) { adc_value1 HAL_ADC_GetValue(hadc1); // 实际获取的是序列中最后一个转换通道的值这里有问题 // 更严谨的做法在扫描模式下需要使用HAL_ADCEx_MultiModeStart_DMA或自行处理数据寄存器 // 对于两路更推荐使用DMA。 } }中断方式的陷阱在扫描模式下HAL_ADC_GetValue函数读取的是数据寄存器DR的值。而DR寄存器在每次通道转换后都会被覆盖。如果你在序列完成中断里只调用一次HAL_ADC_GetValue你只能得到最后一个通道Rank2的数据通道1的数据已经丢失了因此纯中断方式需要更复杂的处理来匹配数据与通道容易出错。3.3 DMA方式Direct Memory Access这是国赛等高要求场景下的首选方案也是我强烈推荐的做法。DMA可以在ADC转换完成后自动将数据从外设ADC数据寄存器搬运到我们指定的内存数组中完全不需要CPU干预。 配置步骤在CubeMX的ADC配置中添加DMA请求模式设为“Circular”循环模式数据宽度为半字Half Word对应16位。在代码中定义一个数组用于存储数据uint16_t adc_buffer[2];// 索引0放通道1索引1放通道2。启动带DMA的ADC转换HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_buffer, 2);一旦启动ADC每完成一个序列两路的转换DMA就会自动把通道1的结果放到adc_buffer[0]通道2的结果放到adc_buffer[1]。你的主程序可以随时去读取这个数组数据永远是更新好的。CPU被彻底解放出来去处理其他任务效率最高且数据不会丢失或错位。注意使用DMA时务必确保DMA和ADC的时钟都已使能且内存数组的生命周期是全局的。在竞赛中如果题目要求定时采集比如每秒100次你可以结合一个基本定时器TIM来触发ADC转换将ADC的触发源设置为该定时器更新事件然后ADC转换完成自动通过DMA传输数据形成一个全自动、精准定时、不占CPU的数据采集流水线这是最能体现专业性的实现方式。4. 从原始数据到稳定电压软件滤波算法实战ADC采上来的原始值0-4095总是伴随着噪声和抖动直接显示的话末位数字会不停跳动非常不专业。因此必须加入软件滤波。4.1 常用滤波算法对比算术平均滤波连续采样N次求和后取平均值。这是最简单有效的滤除随机噪声的方法。N越大越平滑但实时性越差。在国赛场景N取10~20次即可。#define SAMPLE_TIMES 20 uint32_t sum1 0, sum2 0; for(int i0; iSAMPLE_TIMES; i){ sum1 adc_buffer[0]; sum2 adc_buffer[1]; // 如果需要严格定时这里应加一个短延时或者直接使用DMA循环模式下的数组其本身就在不断更新。 } filtered_value1 sum1 / SAMPLE_TIMES; filtered_value2 sum2 / SAMPLE_TIMES;滑动平均滤波维护一个长度为N的队列每次新数据进来去掉最老的数据计算当前队列的平均值。实时性更好能反映信号变化趋势。#define FILTER_LEN 10 uint16_t adc_fifo1[FILTER_LEN] {0}; uint8_t index 0; uint32_t sum1 0; // 每次获取新数据时调用 adc_fifo1[index] adc_buffer[0]; // 存入新数据 sum1 sum1 - adc_fifo1[(index1)%FILTER_LEN] adc_fifo1[index]; // 更新总和减去最旧的加上最新的 filtered_value1 sum1 / FILTER_LEN; // 计算平均值 index (index 1) % FILTER_LEN; // 更新索引中位值平均滤波连续采样N次去掉一个最大值和一个最小值对剩下的N-2个数据求平均。能有效抑制脉冲干扰。一阶滞后滤波低通滤波Y(n) α * X(n) (1-α) * Y(n-1)。α是滤波系数0α1。这种方法对周期性干扰有良好抑制相位滞后。适用于变化缓慢的信号。国赛建议对于测量直流或缓变信号如电位器“滑动平均滤波”是性价比最高的选择。它实现简单效果显著且对CPU资源消耗很小。将滤波后的filtered_value代入电压计算公式就能得到稳定的电压值。4.2 电压计算与输出格式计算电压并格式化输出到串口是展示结果的最后一步。float voltage1, voltage2; voltage1 (filtered_value1 / 4095.0f) * 3.3f; // 假设Vref3.3V voltage2 (filtered_value2 / 4095.0f) * 3.3f; // 使用sprintf格式化到字符串缓冲区注意浮点数会占用较多资源也可以直接输出整型毫伏值。 char msg[64]; sprintf(msg, CH1: %.3fV, CH2: %.3fV\r\n, voltage1, voltage2); HAL_UART_Transmit(huart1, (uint8_t*)msg, strlen(msg), 100);重要提示在嵌入式竞赛中频繁使用sprintf处理浮点数会显著增加代码体积需要拉入庞大的格式化库并可能造成堆栈问题。一个更优的竞赛级做法是输出整型毫伏值uint16_t vol_mv1 filtered_value1 * 3300 / 4095; // 直接计算毫伏 // 然后通过除法/取余操作分解成千位、百位等用字符发送或者直接发送原始整型值到上位机解析。5. 系统集成与调试避坑指南将ADC模块集成到完整的国赛项目中时会遇到一些典型问题。5.1 多外设时钟与中断优先级冲突STM32G431的ADC时钟来源于APB2总线。确保在System Clock Configuration中APB2的时钟已经正确配置通常与系统主频一致或分频。如果使用定时器触发ADC还需要使能对应定时器的时钟。 如果同时使用了ADC中断、定时器中断、串口中断等必须在CubeMX的NVIC配置中合理设置中断优先级Preemption Priority。对于实时性要求高的如按键扫描优先级设高对于ADC转换完成这种可以稍后处理的优先级设低。错误的优先级配置可能导致中断无法及时响应或数据丢失。5.2 资源分配与代码结构国赛项目通常是一个综合系统包含LED、按键、串口、LCD显示等。ADC采集作为一个后台任务其代码结构要清晰。初始化在main.c的MX_ADC1_Init()函数之后尽早调用HAL_ADC_Start_DMA()启动ADC。数据读取在主循环while(1)中可以直接读取adc_buffer数组因为DMA在后台持续更新它然后进行滤波、计算。数据显示根据题目要求可能需要在LCD上实时绘制电压曲线或显示数值。注意LCD刷新频率如50ms一次和ADC采样频率的匹配避免刷新过快导致LCD闪烁或CPU过忙。5.3 典型故障排查流程完全没有数据值为0或固定值检查GPIO配置第一步确认PB0、PB1等ADC引脚在CubeMX中已设置为“Analog”模式而不是默认的“Input”或“Output”。检查电路连接用万用表测量ADC输入引脚对地电压确认电位器转动时电压在0-3.3V间变化。检查板载的ADC输入跳线帽是否接好。检查参考电压确认芯片供电稳定在3.3V。如果使用外部基准检查其连接。数据跳动剧烈增加采样时间这是最有效的措施。在CubeMX中将对应通道的“Sampling Time”调到最大如640.5 Cycles。检查电源噪声开发板的3.3V电源是否干净可以尝试在ADC输入引脚对地加一个0.1uF的瓷片电容滤除高频噪声。实施软件滤波务必加上滑动平均等滤波算法。降低ADC时钟适当增加ADC时钟分频降低ADCCLK频率有时能提高稳定性。两路数据互相干扰串扰这在STM32的ADC中不常见但如果发生检查硬件上两路输入信号之间是否有隔离。软件上确保采样时间充足让内部采样开关有足够时间切换和稳定。DMA传输不工作或数据错位确认DMA通道已正确映射到ADC1不同型号STM32的DMA通道映射可能不同。确认HAL_ADC_Start_DMA函数的第三个参数数据长度是2且目标数组大小至少为2。在调试模式下观察adc_buffer数组的内存地址看其值是否随ADC转换而改变。6. 国赛真题实战模拟与扩展思考假设一道国赛真题要求“实现两路ADC电压测量一路测量电位器电压0-3.3V一路测量光敏电阻分压反映光照强度。测量结果需每秒更新10次并通过串口以‘Volt: X.XXX V, Light: X.XXX V’的格式发送。同时当电位器电压超过2.0V时点亮红色LED。”实现思路拆解硬件连接确认查看板卡原理图确认电位器接ADC1_IN1 (PB0)光敏接ADC1_IN2 (PB1)。CubeMX配置ADC1扫描模式使能连续转换模式禁用。通道1和2的Rank分别设为1和2采样时间均设为640.5 Cycles。触发源选择定时器触发例如TIM2的更新事件。TIM2配置为产生10Hz的更新中断即周期100ms。因为要每秒10次更新这个定时器用来触发ADC转换正合适。DMA为ADC1添加DMA循环模式数据宽度半字。USART1配置串口用于打印。GPIO配置一个LED对应的GPIO为输出模式。代码编写启动在main中启动DMA (HAL_ADC_Start_DMA) 和定时器 (HAL_TIM_Base_Start)。定时器中断在HAL_TIM_PeriodElapsedCallback中可以什么都不做因为ADC已被硬件触发。或者在这里设置一个标志位主循环根据这个标志去处理数据。主循环每秒即每10次定时器触发后对adc_buffer中累积的多个数据进行滑动平均滤波计算电压值判断是否超过2.0V来控制LED并格式化字符串通过串口发送。优化为了精确控制1秒可以另用一个定时器如SysTick做秒定时或者在主循环中计数10次采集后处理一次。串口发送避免使用sprintf浮点采用整数毫伏输出可节省大量资源。扩展思考精度提升如果需要更高精度可以考虑使用过采样技术。例如进行16次采样将结果累加后右移2位相当于除以4可以得到14位分辨率的“虚拟”ADC数据。多路复用如果题目要求测量超过2路STM32G431的ADC1支持最多19个外部通道。只需在扫描序列中添加更多Rank并相应扩大DMA缓冲区即可。但要注意总的转换时间会变长需要重新评估采样率和定时器配置。与显示模块结合将电压值以进度条或数字形式实时显示在LCD上是国赛常见要求。关键在于规划好主循环的任务调度确保ADC采集、数据处理、LCD刷新、串口通信等任务都能得到及时执行互不阻塞。通常采用状态机或前后台中断主循环的架构是稳妥的选择。

相关新闻