尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32 ADC实战指南:多通道采集、DMA传输与软件滤波

STM32 ADC实战指南:多通道采集、DMA传输与软件滤波 1. 项目概述从模拟世界到数字世界的桥梁在嵌入式开发尤其是基于STM32这类MCU的项目里ADC模数转换器绝对是一个绕不开的核心外设。无论你是想做一个读取电位器旋钮位置的简单控制器还是一个需要监测电池电压、采集传感器信号比如温度、光照、压力的复杂物联网节点ADC都是连接现实模拟世界与MCU数字世界的唯一通道。我刚开始接触STM32时对ADC的理解也仅仅停留在“读取电压值”的层面直到在实际项目中踩过几次坑——比如采样值跳动剧烈导致控制不稳或者采样时机不对错过关键信号——才真正明白用好ADC远不是调用一个HAL库函数HAL_ADC_GetValue()那么简单。这篇笔记我就结合自己这些年做过的几个典型项目从最基础的原理到实际开发中那些容易忽略的细节系统地梳理一下STM32中ADC的使用。我们会聊到如何根据需求选择合适的ADC工作模式单次、连续、扫描、间断如何利用DMA来解放CPU如何处理采样数据滤波算法以及如何通过定时器来精准控制采样节奏。这些内容不仅仅是寄存器配置更多的是在工程实践中积累下来的“手感”和“避坑指南”。无论你是刚入门的新手还是想深化理解的开发者希望这些实实在在的经验能帮你把ADC这个工具用得更加得心应手。2. ADC核心概念与STM32的ADC外设解析2.1 ADC到底是什么为什么它如此关键简单来说ADC就是一个“翻译官”。我们身处的物理世界绝大多数信号都是连续变化的模拟量比如温度从20度缓慢升到25度音量大小连续可调。但微控制器MCU如STM32其内核是数字的只能处理“0”和“1”这样的离散数字量。ADC的职责就是将连续的模拟电压信号例如0V到3.3V按照一定的规则转换成MCU可以理解和处理的数字值例如0到4095。这个过程的核心参数有三个分辨率、采样率和参考电压。分辨率决定了ADC有多“细腻”。常见的12位ADC意味着它能把参考电压范围分成 2^12 4096 个等级。对于3.3V的参考电压每个数字等级1LSB代表的电压是 3.3V / 4096 ≈ 0.8mV。分辨率越高对电压变化的感知就越灵敏。采样率指ADC每秒钟能完成多少次“翻译”转换。它决定了系统能捕获多快变化的信号。根据奈奎斯特采样定理要无失真地还原一个信号采样率至少需要是信号最高频率的两倍。对于音频信号20kHz采样率通常需要大于40kHz。参考电压Vref这是ADC转换的“标尺”。ADC输出的数字值表示的是输入电压相对于参考电压的比例。STM32的Vref通常可以接外部精密基准源或者直接使用VDDA模拟电源。这里有一个非常重要的注意事项VDDA的稳定性直接决定了ADC的精度。如果VDDA随着负载波动那么你的采样值也会飘。在要求高的场合务必为VDDA提供干净、稳定的电源并确保Vref连接正确。STM32的ADC外设功能非常强大且灵活。以STM32F1和F4系列常见的12位逐次逼近型ADC为例它支持多通道16个外部通道2个内部通道支持单次、连续、扫描、间断等多种转换模式并且可以与DMA、定时器无缝协作几乎能满足大部分中低速信号采集的需求。2.2 STM32 ADC的几种关键工作模式剖析理解并选对工作模式是高效使用ADC的第一步。很多人一开始只用单次转换遇到多通道或连续采样需求时就抓瞎了。单次转换模式这是最简单的模式。启动一次转换一个指定的通道或一组通道一次然后ADC就停止并等待下一次启动。它适用于不频繁的、手动触发的采样比如按键按下时才读取一次电位器值。优点是省电逻辑简单。连续转换模式一旦启动ADC就会马不停蹄地对一个通道进行连续转换。每次转换结束后立即开始下一次直到被停止。这种模式适合需要对单一信号进行持续监控的场景。但要注意它只针对一个通道要切换通道需要重新配置或使用扫描模式。扫描模式这是处理多通道采集的利器。在扫描模式下ADC会按照预先配置好的通道序列比如通道1, 5, 8, 11自动地一个接一个进行转换。扫描模式通常需要与DMA或中断结合使用因为ADC转换完序列里的所有通道后会产生一个“序列转换完成”事件你需要在这个事件里及时把数据从ADC数据寄存器DR里搬走否则会被下一次转换覆盖。间断模式这个模式比较特殊它允许你将一个长的通道序列分成若干个子组。每次由一个外部事件如定时器触发来启动一个子组的转换。这在需要将多个通道的采样与某个特定事件严格同步时非常有用比如与PWM输出同步采集电流。实操心得对于大多数多通道数据采集应用我的首选组合是“扫描模式 连续转换模式 DMA”。这样配置后ADC会自动、循环地采集所有你关心的通道并通过DMA自动将数据搬运到你指定的内存数组中。CPU完全不用干预只需要去数组里读取最新的数据即可效率极高实时性也好。在CubeMX里配置好这个组合你的主循环会清爽很多。3. 基于CubeMX的ADC配置与初始化详解现在我们以STM32F407为例使用STM32CubeMX图形化工具一步步配置一个“三通道扫描连续采集DMA传输”的典型场景。假设我们要采集通道1PA1、通道2PA2、通道5PA5上的电压。3.1 CubeMX工程基础配置首先新建工程选择你的芯片型号。在Pinout Configuration视图下在左侧边栏找到Analog下的ADC1。在ADC1的设置中进入Parameter Settings标签页。Resolution选择12-bit。根据需求也可选10位、8位或6位位数越低转换越快。Scan Conversion Mode选择Enabled。这是启用扫描模式的关键。Continuous Conversion Mode选择Enabled。启用连续转换。DMA Continuous Requests选择Enabled。这保证了DMA请求是连续的与ADC的连续转换模式匹配。End Of Conversion Selection选择EOC after each conversion每次转换后产生EOC或EOC after sequence序列转换后产生EOC。对于DMA搬运通常选EOC after each conversion这样每转换完一个通道就触发一次DMA请求搬运一次数据。进入DMA Settings标签页点击Add添加一个DMA请求。Mode通常选择Circular循环模式这样DMA会周而复始地搬运数据填满缓冲区后自动回到开头形成一个自动更新的数据池。Increment Address根据你的存储数组来选择如果数据存到一个数组里Destination的地址需要自增。进入NVIC Settings标签页如果你还需要在转换完成时做额外处理比如数据就绪标志可以开启ADC全局中断。对于纯DMA方案中断不是必须的。最后在图形化引脚图上找到PA1, PA2, PA5将它们分别设置为ADC1_IN1,ADC1_IN2,ADC1_IN5。3.2 通道配置与采样时间设定在ADC1的Configuration界面点击Channel Settings会看到我们使能的三个通道。这里需要配置两个关键参数Rank次序和Sampling Time采样时间。Rank这就是扫描的先后顺序。你可以通过上下箭头调整比如设置Channel1为Rank1 Channel2为Rank2 Channel5为Rank3。ADC就会按照1-2-5的顺序循环采样。Sampling Time采样时间这是最容易出问题的地方之一。采样时间太短ADC内部的采样电容来不及充到外部信号的电压会导致转换结果不准确时间太长又会限制整体的采样率。采样时间以ADC时钟周期为单位。STM32F4的ADC时钟最高可达36MHz取决于APB2分频。对于信号源阻抗较高的情况比如通过一个很大的电阻连接需要增加采样时间。一个经验值是如果信号源阻抗在10kΩ以下使用15 Cycles或28 Cycles通常就够了。如果不确定可以先设长一点如84 Cycles或112 Cycles确保精度再根据实际波形和需求调整。注意事项ADC的输入引脚阻抗是有限的。如果外部信号源的内阻很大与ADC内部的采样电容会形成一个RC充电电路。如果采样时间不足电容电压还没稳定到信号电压转换就开始了结果必然偏低。这就是为什么直接读取电位器中间抽头电位器本身阻抗大时如果采样时间设置过短值会跳得很厉害的原因。解决办法一是增加采样时间二是在ADC输入前加一个电压跟随器运算放大器进行缓冲降低输出阻抗。生成代码后CubeMX会帮我们初始化好ADC和DMA。我们需要在用户代码区做一点补充。4. 数据采集、DMA传输与软件滤波实战4.1 启动ADC与DMA并管理数据缓冲区在main.c的/* USER CODE BEGIN PV */区域我们定义存储数据的数组和相关的变量。/* Private variables ---------------------------------------------------------*/ #define ADC_CHANNEL_NUM 3 // 我们使用了3个通道 uint16_t adc_raw_buffer[ADC_CHANNEL_NUM]; // 用于存放DMA搬运的原始数据 uint32_t adc_filtered_values[ADC_CHANNEL_NUM] {0}; // 用于存放滤波后的值 // 可以再定义一个数组用于存放计算后的电压值 float adc_voltage[ADC_CHANNEL_NUM];在/* USER CODE BEGIN 2 */区域启动ADC和DMA。/* USER CODE BEGIN 2 */ // 启动DMA传输将ADC转换结果自动搬运到adc_raw_buffer数组 // 参数ADC句柄目标地址数据长度注意是字长对于12位ADC数据寄存器是16位的 if (HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_raw_buffer, ADC_CHANNEL_NUM) ! HAL_OK) { Error_Handler(); } /* USER CODE END 2 */这样ADC就会自动开始工作并且每转换完一个通道RankDMA就会把数据从ADC1-DR寄存器搬到adc_raw_buffer数组的对应位置。由于DMA是循环模式它会不断覆盖这个数组因此adc_raw_buffer里永远是最新的三个通道的原始数据。4.2 必须掌握的软件滤波算法直接从ADC读出来的原始值adc_raw_buffer通常是带有噪声的直接使用可能会导致控制抖动或显示数字乱跳。因此软件滤波是必不可少的一步。这里介绍几种最常用且有效的算法。算术平均滤波最简单连续采样N次取平均值。能有效抑制周期性干扰但对突发性干扰尖峰的抑制能力弱。#define FILTER_N 10 uint32_t adc_sum[ADC_CHANNEL_NUM] {0}; uint8_t filter_index 0; uint16_t adc_history[ADC_CHANNEL_NUM][FILTER_N]; // 历史数据队列 // 在定时中断或主循环中调用 void ADC_Filter_Average(void) { for(int ch0; chADC_CHANNEL_NUM; ch) { // 1. 减去最旧的数据 adc_sum[ch] - adc_history[ch][filter_index]; // 2. 存入最新的数据 adc_history[ch][filter_index] adc_raw_buffer[ch]; // 3. 加上最新的数据 adc_sum[ch] adc_raw_buffer[ch]; // 4. 计算平均值 adc_filtered_values[ch] adc_sum[ch] / FILTER_N; } filter_index (filter_index 1) % FILTER_N; // 移动索引 }中值滤波连续采样N次N为奇数将这N个值排序取中间值作为结果。对脉冲性干扰比如开关造成的毛刺有非常好的滤除效果是硬件滤波的软件补充。我常在读取按键或存在瞬间干扰的传感器时使用。#define MEDIAN_N 5 uint16_t get_median_value(uint16_t* arr, uint8_t n) { uint16_t temp[MEDIAN_N]; memcpy(temp, arr, sizeof(uint16_t)*n); // 简单的冒泡排序找中值 for(int i0; in-1; i) { for(int j0; jn-i-1; j) { if(temp[j] temp[j1]) { uint16_t t temp[j]; temp[j] temp[j1]; temp[j1] t; } } } return temp[n/2]; } // 使用时需要维护一个长度为MEDIAN_N的历史数组每次调用此函数求中值。一阶滞后滤波低通滤波适用于变化缓慢的参数如温度。公式本次滤波结果 α * 本次采样值 (1-α) * 上次滤波结果。α是滤波系数0α1α越小滤波越平滑但滞后越严重。float alpha 0.2; // 滤波系数可调 for(int ch0; chADC_CHANNEL_NUM; ch) { adc_filtered_values[ch] alpha * adc_raw_buffer[ch] (1-alpha) * adc_filtered_values[ch]; }实操心得不要追求“最完美”的滤波算法而是选择“最合适”的。我的常用组合是中值滤波 算术平均滤波。先用中值滤波比如采样5次取中值干掉可能出现的尖峰脉冲然后再对这个中值结果进行多次平均让曲线更平滑。这个组合在大多数工业测量场景下都非常稳健。滤波次数N的选择也需要权衡N越大越平滑但响应速度越慢。你需要根据信号的实际变化频率来调整。4.3 将数字量转换为实际物理量得到稳定的滤波值后我们需要将其转换为有意义的电压或物理量。转换公式很简单电压值 (ADC滤波值 / 满量程数字) * 参考电压对于12位ADC满量程数字是40952^12 - 1。假设Vref接的是3.3V。#define VREF 3.3f #define ADC_MAX 4095.0f for(int ch0; chADC_CHANNEL_NUM; ch) { adc_voltage[ch] (adc_filtered_values[ch] / ADC_MAX) * VREF; }如果你的传感器是线性变化的比如某压力传感器输出0.5V~2.5V对应0~100kPa那么再通过一次线性映射即可得到压力值float voltage_min 0.5f; float voltage_max 2.5f; float pressure_min 0.0f; float pressure_max 100.0f; float pressure; if (adc_voltage[ch] voltage_min) pressure pressure_min; else if (adc_voltage[ch] voltage_max) pressure pressure_max; else { pressure pressure_min (adc_voltage[ch] - voltage_min) * (pressure_max - pressure_min) / (voltage_max - voltage_min); }5. 高级应用定时器触发与注入通道5.1 使用定时器精确控制采样率在“扫描连续DMA”模式下采样率是由ADC的转换时间决定的。转换时间 采样时间 12.5个时钟周期12位转换时间。这个速率是固定的。但有时我们需要ADC的采样动作与外部事件严格同步比如每1毫秒准时采集一次。在电机驱动中需要在PWM波形的特定时刻如上桥臂打开的中点采样电流以计算相电流。这时就需要用到外部触发转换功能。STM32的ADC可以由多种内部定时器TIMx的TRGO事件来触发。配置步骤如下在CubeMX的ADC配置中将External Trigger Conversion Source设置为某个定时器事件例如Timer 2 Trigger Out event。将External Trigger Conversion Edge设置为上升沿、下降沿或双边沿触发。关键将Continuous Conversion Mode改为Disabled。因为我们现在希望由定时器来控制每次转换的启动。配置好对应的定时器如TIM2设置其更新频率ARR和PSC值为你期望的采样频率并使能其TRGO输出。这样定时器就会按照设定的周期定时产生一个触发信号给ADCADC收到触发信号后启动一次完整的扫描序列或多个通道。这种方式实现了等间隔采样对于后续进行数字信号处理如FFT至关重要。5.2 注入通道应对高优先级信号的插队机制STM32的ADC除了常规的规则通道序列我们前面一直在用的还有一个叫做“注入通道”的机制。你可以把它理解为一个高优先级的“VIP通道”。规则通道序列就像排队办理业务按顺序来。而注入通道则像紧急事件可以打断当前的规则序列转换。当注入通道被触发可以由另一个定时器或外部引脚触发时ADC会立即暂停当前的规则序列转换转而去转换配置好的注入通道序列转换完成后再自动回到被打断的规则序列继续。典型应用场景在电机FOC控制中规则通道用于常规的电压、温度采样而注入通道则专门用于由PWM定时器触发的、在特定时刻对三相电流进行同步采样。这保证了电流采样的时刻精度不受规则通道转换进度的影响。配置注入通道也在CubeMX的ADC配置界面完成有独立的Injected Conversions配置栏可以设置注入通道的数目、序列、采样时间以及触发源。6. 常见问题、调试技巧与精度提升6.1 ADC采样值不稳定、跳动大这是最常见的问题。除了前面提到的增加采样时间、降低信号源阻抗、添加硬件滤波在ADC输入引脚对地加一个10nF~100nF的电容外还需要检查以下几点电源噪声用示波器测量VDDA和VSSA模拟地的波形看是否有明显的毛刺或纹波。确保模拟电源部分使用了磁珠或电感与数字电源隔离并搭配了足够的去耦电容如10uF钽电容 100nF陶瓷电容。地线问题确保模拟地AGND和数字地DGND是单点连接的。PCB布局时模拟部分的地回路要尽量短、粗避免数字电流在模拟地路径上产生压降。参考电压噪声如果使用了外部参考电压芯片确保其输出干净稳定。软件滤波不足尝试增加滤波算法的窗口大小或调整滤波系数。6.2 DMA数据错位或溢出在扫描多通道时有时会发现DMA缓冲区里的数据顺序不对或者数据混乱。检查DMA配置在CubeMX中确认DMA的Data Width数据宽度与ADC的数据宽度匹配都是Half Word16位。确认Memory Increment Address内存地址自增已开启且Peripheral Increment Address外设地址自增已关闭因为ADC数据寄存器地址固定。检查缓冲区大小DMA的Data Length数据长度应设置为通道数而不是字节数。例如3个通道就设为3。DMA会搬运3次每次16位后完成一个循环。检查中断冲突如果同时开启了ADC的DMA和ADC自身的转换完成中断要小心处理中断服务程序避免在DMA搬运过程中访问数据缓冲区造成冲突。对于纯DMA方案可以关闭ADC中断。6.3 如何校准ADC以提高精度STM32的ADC内置了校准功能可以修正其内部的电容误差。上电后在第一次使用ADC前执行一次校准是很好的习惯。HAL_ADCEx_Calibration_Start(hadc1); // 对于单端输入 // 或者对于差分输入 // HAL_ADCEx_Calibration_Start(hadc1, ADC_SINGLE_ENDED);校准过程会消耗一些时间几十到几百微秒校准系数会被自动存入ADC的内部寄存器。注意校准应在ADC完成初始化、且稳定供电后进行。6.4 使用过采样提升有效分辨率如果你的信号变化很慢但需要比12位更高的分辨率比如用于高精度电子秤可以使用过采样技术。原理是通过以高于奈奎斯特频率的速率进行多次采样并求和平均可以将分辨率提高几位。例如4倍过采样可以将有效分辨率提高1位16倍过采样可提高2位。STM32的ADC硬件直接支持过采样功能可以在CubeMX中配置过采样系数和右移位数硬件会自动完成累加和平均输出一个更高位数的结果这比软件过采样效率高得多。调试ADC时最有力的工具就是示波器和逻辑分析仪。用示波器看输入信号的波形和噪声用逻辑分析仪抓取定时器的触发信号和ADC的转换完成信号EOC可以清晰地看到采样是否按时发生时序是否正确。当遇到奇怪的问题时回归到硬件信号层面去观察往往能快速定位问题根源。ADC的配置选项繁多第一次接触可能会觉得复杂但一旦理解了每种模式背后的设计意图并亲手调试通过一两个完整的多通道采集项目它就会成为你嵌入式工具箱里最得心应手的工具之一。
返回列表