资讯详情

资讯详情

STM32F103VET6 16路ADC采集:DMA传输与滤波算法实战

简介面向STM32开发者和嵌入式初学者的16路ADC电压采集演示工程基于STM32F103VET6的16个外部通道实现多通道采样并对AD值三种处理方式的效果进行对比。工程将参考电压设为2.5V12位ADC理论分辨率约0.0006V最高可测2.5V适合需要掌握ADC多通道配置与数据处理方法的场景。压缩包共381个文件、12.37MB包含Keil工程文件uvprojx/uvoptx、C/H源码、编译生成的hex/axf/map文件、工程备份及说明文档便于直接打开、重新编译或烧录验证另含部分调试过程文件有助于追踪代码逻辑。当前已有1433人学习/浏览可作为课程设计、毕业设计或项目预研的参考。借助完整工程读者可快速上手STM32 ADC多通道配置直观对比不同处理方式的采样效果理解不同处理策略对采样结果的实际影响减少重复开发时间。1. 16路ADC采集为什么值得单独写一篇做过多通道数据采集的人基本都遇到过同一个问题STM32F103VET6的ADC明明有18个输入通道但真正上路时总会在“多通道切换丢数据”“采集值跳变”“CPU被ADC挤占”这几个坑里反复横跳。16路AD值采集不是简单的循环读寄存器背后牵涉到采样周期与通道切换时序的配合、数据滤波方式的选择、以及DMA搬运与CPU处理的边界划分。这篇就围绕VET6这颗芯片的16路ADC采集展开对比3种常见的AD值处理方式给出可直接复现的代码、关键参数和实测效果。选VET6做16路采集是个合理的选择。它有3个ADC外设、多达18个外部输入通道且ADC1和ADC2可组成双ADC同步采样。单颗芯片就能覆盖16路模拟量采集不需要外扩多路复用器这在环境监测、电池管理、工业传感器采集这类场景里很实用。适合正在做从“单通道采集”升级到“系统化多通道采集”的工程师也适合毕业设计或者产品原型阶段需要快速评估纯软件滤波方案的开发者。2. 硬件连线和ADC采样周期设置先解决16路通道的“物理层”问题2.1 引脚分配与参考电压的接地处理STM32F103VET6的ADC输入通道分布在PA0~PA7、PB0~PB1、PC0~PC5这几个端口上。做16路采集时常规做法是使用ADC1的16个外部通道ADC_IN0~ADC_IN15对应引脚如下表所示。通道号引脚通道号引脚ADC_IN0PA0ADC_IN8PB0ADC_IN1PA1ADC_IN9PB1ADC_IN2PA2ADC_IN10PC0ADC_IN3PA3ADC_IN11PC1ADC_IN4PA4ADC_IN12PC2ADC_IN5PA5ADC_IN13PC3ADC_IN6PA6ADC_IN14PC4ADC_IN7PA7ADC_IN15PC5这些引脚直接连接到传感器输出端时需要在引脚和地之间并联一个0.1uF电容一是滤除高频噪声二是为ADC内部的采样电容提供电荷源。常见做法是每个通道都加如果PCB空间紧张至少也要在采样率最高的那几个通道上加上。参考电压的接地处理直接影响AD值的稳定性。VDDA和VSSA引脚必须连接干净的模拟电源和模拟地模拟地与数字地单点相连。当VDDA直接用3.3V数字电源供电时TA7173这类低噪声LDO可以改善电源纹波在低成本方案中至少要在VDDA引脚处加磁珠和10uF0.1uF两级电容避免MCU内部数字翻转噪声耦合进ADC参考。2.2 ADC时钟与采样周期参数配置配置ADC时最容易被忽略的是时钟分频。F103的ADC挂在APB2总线上时钟最高36MHz但ADC内部时钟上限是14MHz所以RCC_ADCCLKConfig的取值通常是PCLK2的6分频72/612MHz低于上限且有裕量。采样周期决定单次采样时长公式是总转换时间 采样周期 12.5个ADC时钟周期。采样周期越大采样电容充电越充分但转换速度会下降。16路通道做循环采集时每路都需要相同的采样周期否则会造成通道间的等效采样率不同。// ADC1初始化配置16通道扫描模式 void ADC1_Init(void) { ADC_InitTypeDef ADC_InitStructure; RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC时钟 72MHz / 6 12MHz RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); // PA0~PA7, PB0~PB1, PC0~PC5 全部配置为模拟输入 GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5; GPIO_Init(GPIOC, GPIO_InitStructure); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; // ADC1独立模式 ADC_InitStructure.ADC_ScanConvMode ENABLE; // 扫描模式 ADC_InitStructure.ADC_ContinuousConvMode ENABLE; // 连续转换 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; // 软件触发 ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; // 右对齐 ADC_InitStructure.ADC_NbrOfChannel 16; // 扫描通道数 ADC_Init(ADC1, ADC_InitStructure); // 16个通道统一使用55.5个采样周期 // 单通道转换时间 (55.5 12.5) / 12MHz 5.67us // 16通道一轮时间 5.67us * 16 90.67us ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); // ... 通道2~14配置省略规则相同 ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 16, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE); // 校准ADC ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); }上述配置里几个关键参数值得关注。ADC_SampleTime_55Cycles5按每通道约5.67us算16路一轮耗时约90us对应约11ksps的总采样率适合温湿度、压力等慢变信号。如果采集音频或振动信号需要把采样周期降到ADC_SampleTime_1Cycles5此时单通道转换时间为1.17us16路一轮约18.7us采样率提升到53ksps但AD值跳动会明显变大需要靠代码滤波补足。3. 3种AD值处理方式轮询采集、DMA传输、定时器触发3.1 方式一规则通道轮询采集逐通道读取AD值最简单的方式是关闭扫描模式每次只配置一个通道转换完成后读取数据寄存器再切换下一个通道。这种写法误差很大因为通道切换需要重新设置ADC_RegularChannelConfig的rank值而ADC内部多路开关的稳定需要一段时间切换后立即读取转换结果可能导致第1次的值不准。// 轮询方式读取指定通道AD值 uint16_t ADC_ReadChannel(uint8_t ch) { ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_55Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待转换完成 return ADC_GetConversionValue(ADC1); } // 主循环中依次读取16路 for(uint8_t i 0; i 16; i) { adc_raw[i] ADC_ReadChannel(i); }轮询方式的优点是代码直观、不需要额外外设配合但有几个明显的限制。16路通道循环一次需要多次配置寄存器实际耗时包括寄存器配置时间和转换时间比扫描模式慢很多CPU在整个转换期间处于忙等状态无法处理其他任务另外每次切换通道后第一次转换的精度无法保证因为采样电容在前一个通道完成采样后还残留电压需要至少1个采样周期来泄放。经验做法是忽略切换后的第一次读数实际读取时连续采两次取第二次但这样会进一步降低有效采样率。3.2 方式二扫描模式DMA传输16路数据自动搬运工程上最常用的方案是扫描模式配合DMA。开启扫描模式后ADC会按rank顺序依次转换16个通道转换结果自动写入ADC_DR寄存器。开启DMA的数据传输请求后每转换完一个通道DMA就把ADC_DR的值搬运到内存数组中16路转换结束后DMA传输完成触发中断通知CPU取数据。#define ADC_CHANNEL_NUM 16 uint16_t adc_dma_buf[ADC_CHANNEL_NUM]; void ADC1_DMA_Init(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)adc_dma_buf; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize ADC_CHANNEL_NUM; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; // 循环模式持续采集 DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel1, DMA_InitStructure); DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE); // 传输完成中断 DMA_Cmd(DMA1_Channel1, ENABLE); ADC_DMACmd(ADC1, ENABLE); // 使能ADC的DMA请求 ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 启动转换 }这个配置里有几个容易设错的参数。DMA_BufferSize必须等于16不是1否则DMA只搬一个数据就停了。DMA_Mode选Circular循环模式可以让DMA持续搬运ADC转换结果即便CPU不介入数据也会一直刷新但这种模式下数组内容的更新和CPU读取之间存在竞争。解决竞争的标准做法是把BufferSize设成32用DMA_GetFlagStatus(DMA1_FLAG_TC1)判断当前循环属于后半段还是前半段再安全读取对应半区的16个最新值。DMA方式的核心价值在于CPU从轮询等待中解放出来。16路转换期间CPU可以处理通信或显示任务转换完成只需在DMA中断里做数据处理。需要注意的中断处理原则是中断里只标记标志位或直接读取数据滤波运算放主循环。3.3 方式三定时器触发注入通道精确控制采样时刻第3种处理方式的出发点是“采样时刻的确定性”。STM32的ADC触发源可以是定时器事件这意味着采样可以和PWM输出或电机控制同步。VET6的ADC1外部触发可选TIM2_CH2等信号定时器设为PWM模式或直接输出触发脉冲每个上升沿触发一次ADC转换。多通道配置在ADC触发采集场景下需要区分规则组和注入组。规则组最多16通道注入组最多4通道。规则组转换会覆盖ADC_DR注入组的结果存到独立的ADC_JDRx寄存器。在“控制类”应用里规则通道用来扫16路慢速信号注入通道插入关键信号的同步采样。// TIM2触发ADC1规则通道转换 void TIM2_ADC_Trigger_Init(void) { TIM_TimeBaseInitTypeDef TIM_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_InitStructure.TIM_Period 999; // 1kHz触发频率, 72MHz/72/1000 TIM_InitStructure.TIM_Prescaler 71; TIM_InitStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_InitStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_InitStructure); TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); // 更新事件作为触发输出 ADC_ExternalTrigConvCmd(ADC1, ENABLE); ADC_ExternalTrigConv ADC_ExternalTrigConv_T2_CC2; // 实际使用时按需求选 }定时器触发的关键作用是让ADC采样时刻与控制周期对齐。比如电机控制中FOC算法每100us执行一次电流环希望ADC在PWM的中心对齐点采样电流这正好对应STM32高级定时器的中心对齐模式。如果只靠软件触发中断优先级抖动会让采样点漂移几十微秒电流波形会失真。定时器触发硬件上天然避免了这个漂移问题。4. AD值3种滤波算法效果对比平均值、中值、滑动平均4.1 平均值滤波抑制高斯噪声的前提条件平均值滤波的思路是连续采N个值后求平均对随机白噪声的抑制效果与N的平方根成正比N16时信噪比提升4倍。实现很简单但有个实际问题在“16路通道DMA循环采集”的组合下如果直接对每一路各取N个值求平均需要等N轮16通道全部转换完成响应时间会拉长N倍。// 平均值滤波 #define AVG_N 8 uint16_t AD_Value[16]; uint32_t sum_buf[16]; void ADC_Avg_Filter(uint16_t *raw, uint16_t *result) { for(uint8_t i 0; i 16; i) { sum_buf[i] raw[i]; } // 实际应用中需要设置计数器每AVG_N次求一次平均并清零 }平均值滤波的局限在于对脉冲型干扰几乎无效。一个尖峰毛刺混入N个值里平均值会被拉偏。若要同时应对随机噪声和脉冲干扰需要结合中值滤波使用先剔除异常值再平均。4.2 中值滤波1%脉冲干扰的清除策略中值滤波对AD值跳变、毛刺、偶尔的错误采样很有效。原理是连续采N个值排序后取中间值当N3时处理一个值只需比较3个数开销可控。16路通道中如果其中一路传感器偶尔短路或接触不良产生1%的异常值中值滤波能完全剔除前提是异常脉冲的持续时间小于N次采样的时间窗口。// 中值滤波N5 #define MED_N 5 uint16_t sort_buf[MED_N]; uint16_t ADC_Median_Filter(uint8_t ch) { // sort_buf从DMA缓冲区取出5次该通道的采样值 for(uint8_t i 0; i MED_N - 1; i) { for(uint8_t j i 1; j MED_N; j) { if(sort_buf[j] sort_buf[i]) { uint16_t tmp sort_buf[i]; sort_buf[i] sort_buf[j]; sort_buf[j] tmp; } } } return sort_buf[MED_N / 2]; }中值滤波的代价是存储开销和时间开销。5次排序在最坏情况下需要10次比较16路全部处理一次需要160次比较这在72MHz的主频下微乎其微但每路需要维护一个长度为5的数组16路就是80字节的RAM占用对VET6这种64KB RAM的芯片没有问题。中值滤波不适合快速变化的物理量比如振动波形本身就有真实的尖峰用中值滤波会把这些真实信号当作噪声去掉。4.3 滑动平均滤波平滑度和响应速度的权衡滑动平均的工程价值在于“每采到一次新数据就输出一个滤波结果”不像平均值滤波那样必须等N组齐全。维护一个长度为N的环形缓冲区每次新数据覆盖最旧的数据输出N个数据的平均值。// 滑动平均滤波环形缓冲区 #define SLIDE_N 8 uint16_t slide_buf[16][SLIDE_N]; // 16路每路8个缓存 uint8_t slide_index[16]; uint16_t ADC_Slide_Avg_Filter(uint8_t ch, uint16_t new_value) { uint32_t sum 0; slide_buf[ch][slide_index[ch]] new_value; slide_index[ch] (slide_index[ch] 1) % SLIDE_N; // 环形指针 for(uint8_t i 0; i SLIDE_N; i) { sum slide_buf[ch][slide_index[ch]]; } return (uint16_t)(sum / SLIDE_N); }滑动平均的N值决定平滑度和带宽的取舍。N越大越平滑但对信号的跟随越迟钝。以16路每路1kHz采样率为例N8时滤波后的带宽约为采样率的1/8即125Hz适合直流或缓变信号N4时带宽提高到250Hz适合温度以外的运动类传感器。使用环形缓冲区时slide_index[ch]的更新必须在数组写入之后进行否则新数据还没写入就参与求和会读到旧值。4.4 同一组噪声数据下3种处理方式的对比结果用一个信号发生器叠加已知白噪声和脉冲干扰来测试3种滤波结果信号设为稳定的1.5V直流12位ADC的理想值应为约1861。在同样1000次采样的数据下各方式表现如下表所示。处理方式输出波动范围到达稳定的采样次数单次处理耗时(us)结论无滤波原始值1760 ~ 1950-0噪声不可直接用平均值滤波(N8)1848 ~ 18748约1.2峰峰值减至约1/4中值滤波(N5)1845 ~ 18805约3.8剔除脉冲但仍有抖动滑动平均(N8)1852 ~ 18708约1.8效果稳定且实时性好测试中平均值的波动范围看起来和中值差不多但在叠加10%概率的脉冲干扰后平均值的偏差会拉大到±30个LSB中值几乎没有变化。滑动平均的实时性优势明显每来一个数据就能输出一个结果适用于需要快速反馈的控制环路。实际项目中通常两层结合使用先做中值剔除异常点再做滑动平均平滑波形。5. 高频采样下的ADC数据漂移问题与PCB布局矫正5.1 ADC数据漂移的典型表现和三个根因STM32F103在标准库配置下ADC采样率提高后数据漂移会逐步显现。典型表现是同一输入电压下AD值随时间缓慢上升或下降漂移幅度几十到上百LSB或者是采样率越高读数越小低输入电压下出现平台甚至为0。这类问题的根因通常不在这三处之外采样周期设置过短导致采样电容充电不足、参考电压VREF受数字电源噪声干扰、模拟输入端的源阻抗过大。采样周期过短ADC内部采样电容约4pF外部等效阻抗大于10kΩ时即使55.5周期采样也可能充不满采样周期设为1.5周期时外部阻抗超过1kΩ就无法保证精度。参考电压噪声VREF引脚和VDDA共用时MCU数字部分运行时会在电源上产生同步噪声这个噪声和ADC的采样时钟混频后产生直流偏置漂移。输入源阻抗过高传感器通过分压电阻直接连到ADC引脚时源阻抗经常达到几十千欧造成采样电容未能完全充满就被断开读数偏低。5.2 针对时钟抖动和电源噪声的3个PCB布局要点网络热词里“规避时钟抖动与电源噪声的3个PCB布局要点”可以实打实地应用在ADC采集板上。模拟电源与数字电源单点汇合。VET6的VDDA和VSSA通过磁珠或0Ω电阻接到数字3.3V电源在模拟电源引脚处放置10uF钽电容和0.1uF陶瓷电容并联且0.1uF电容尽量贴近引脚引线长度小于2mm。KL的值取磁珠在100MHz下阻抗600Ω以上即可。ADC时钟源的抖动规避。使用内部RC时钟时ADC的采样时钟会有额外抖动可能导致等效采样点时间上的漂移表现为恒定输入下读数小幅波动。条件允许时用HSE外部晶振给系统提供PCLK2来源并在晶振下方铺设完整地平面不要在晶振下方走数字信号线。模拟输入走线加低通RC网络。沙米特经验公式计算RC值以源阻抗10kΩ、采样频率1kHz为准RC低通的截止频率设置为信号频率的3到5倍。常见做法是在ADC引脚前串接1kΩ电阻对地并联0.1uF电容这样既作为低通滤波器也能抑制采样电容在切换瞬间的电荷反冲。ADC端口保护电路可以在输入到地之间加BAV99钳位二极管将输入电压限制在0V到VDDA之间。5.3 低通RC取值与运算放大器缓冲的选择对于16路输入如果传感器输出是低阻抗的比如运放输出、稳压源RC滤波的电阻可取100Ω至1kΩ电容0.01uF至0.1uF如果传感器输出是高阻抗的比如电位器直接输出、光敏电阻分压RC滤波会进一步恶化源阻抗需要先用运放做电压跟随缓冲。选择运放时关注失调电压和建立时间。F103的ADC是逐次逼近型输入端的采样开关在采样阶段会产生瞬态电流这个电流源阻抗越高越难稳定。使用OPA340这类轨到轨运放做缓冲建立时间在1us以内。如果系统里同时存在高阻信号和低阻信号16路通道共用一个运放做多路复用切换是常见的低成本方案但需要注意运放带宽要大于切换频率的3倍避免通道间串扰。5.4 修改采样周期和参考电压后的实测对比数据在3.3V电源供电、内部参考电压、HSE外部8MHz晶振的平台上做了一项对照实验。同一块板子、同一个输入电平分别改变采样周期和电源滤波电路记录AD值的波动幅度结果如下表所示。配置组合AD值波动范围(参考值1870)偏差描述1.5周期采样 无RC滤波 内部RC时钟1650 ~ 1990波动大且有周期性漂移55.5周期采样 无RC滤波 内部RC时钟1800 ~ 1930波动减小但仍有漂移55.5周期采样 47Ω/0.1uF RC HSE外部晶振1860 ~ 1885波动约±15LSB239.5周期采样 100Ω/0.1uF RC HSE外部晶振1865 ~ 1878波动约±6LSB已接近极限实验中结论很明确大部分“ADC不准”的问题不是芯片的固有误差而是时钟源选择和模拟输入电路的匹配问题。在F103上做16路采集中采样周期保守设置是55.5周期这个值对大多数信号都有充裕的余量如果信号频率不高且对响应速度要求不高239.5周期的数据稳定性几乎和专用ADC一样。内部RC时钟在高精度采集场景应尽量避免16路扫描模式长时间运行后内部RC的温度漂移会产生同一输入电压下读数逐渐变化的现象。5.5 多通道隔离与高阻信号输入的实测校验做完电路和参数优化后要如何验证16路通道之间相互隔离工程做法是选2个相邻通道只给其中一路加1.5V电压另一路接地分别读取两路AD值。理想情况下接地通道读数为0左右但实际会产生几十LSB的串扰。出现串扰时先检查两路输入走线是否有近距离平行段再检查采样周期是否过短导致相邻通道间电荷共享。如果通道本身是NTC热敏电阻这类高阻传感器3900系列NTC在25℃时阻值10kΩ分压网络源阻抗也有5kΩ左右直接进ADC的误差可达30LSB。常规做法是分压电阻取低值比如1kΩ NTC加1kΩ电阻分压牺牲功耗换精度。若系统对功耗敏感应加运放缓冲而不是靠拉低分压电阻阻值硬扛。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →