尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32C562 ADC电压采集实战:从原理到CubeMX配置与排错

STM32C562 ADC电压采集实战:从原理到CubeMX配置与排错 继续咱们 STM32C562 开发连载这一篇聊 ADC 电压采集。做过嵌入式的人都有体会ADC 是 MCU 感知外部世界最直接的窗口单片机只懂 0 和 1但现实里不管是电池电压、温度传感器、电位器旋钮还是变频器输出的模拟信号最终都要被转换成数字量才能参与逻辑判断。STM32C562 内置的 ADC 模块正好承担这个翻译工作12 位分辨率、支持多通道配置足以覆盖绝大多数电压采集场景。这篇就围绕电压采集这件事从原理、参数计算、CubeMX 配置到实际排错完整走一遍适合正在用 C 系列做项目又不想在模拟信号上栽跟头的开发朋友。1. 电压采集的整体链路从模拟信号到数字码值1.1 STM32C562 内置 ADC 的定位与适用场景STM32C562 的 ADC 是逐次逼近型也就是常说的 SAR 型。它的工作方式很像小时候用的天平称重先放最大的砝码试试重了换小一号再不行就再小一号这样逐位逼近真实重量。SAR ADC 里的“砝码”是内部电容阵列按二进制位加权12 位分辨率意味着要比较 12 次每次比较消耗一个 ADC 时钟周期。这个原理决定了它的速度和精度转换速度可以做到很快但精度天然受到电容匹配精度和比较器噪声的限制。很多刚接触的人会纠结一个问题STM32C562 内置的 ADC 够用吗我的答案是大多数电压采集场景它不但够用而且相当好用。12 位分辨率对应的码值范围是 04095如果参考电压是 3.3V那么每个 LSB 大约 0.8mV。电池电压监测、电位器刻度读取、NTC 温度采集、模拟量传感器输出这些常见应用对精度的需求普遍在毫伏级以上12 位完全能接得住。相比之下外接 16 位甚至 24 位的高精度 ADC 虽然能把分辨率做上去但要多花一颗芯片、多写驱动、多处理通信时序成本和复杂度都上来了。除非你做的项目是精密称重、微弱电流检测、高精度数据采集仪这类对分辨率有硬性要求的场景否则先用片内 ADC 是性价比最高的选择。1.2 采样前先看硬件源阻抗、参考电压与去耦ADC 采集从来不只是写代码的事。我见过不少新人代码写得挺溜数据照样飘最后查来查去发现是前端电路出了问题。硬件上第一个要关注的是源阻抗。ADC 内部采样保持电容在采样阶段需要充电如果信号源的输出阻抗太高电容充不满读数自然不准。举个典型例子用两个电阻分压测 025V 的直流电压R1 取 100kΩR2 取 15kΩ分压系数 15 / (100 15) ≈ 0.130最高输入 25V 时分压输出约 3.26V刚好在 3.3V 量程内。但问题在于从 ADC 引脚看进去信号源等效阻抗是 R1 和 R2 的并联值大约 13kΩ。对于几 pF 的采样电容来说这个阻抗偏高了如果不加漂白处理采样时间必须拉得很长否则转换误差肉眼可见。参考电压和供电同样关键。STM32C562 的 ADC 一般以 VDDA 或者独立的 VREF 作为参考这个电压稍有波动转换结果就会跟着抖。常规做法是在 VDDA 和 VREF 引脚旁边就近放 0.1μF 陶瓷电容加 1μF 或 10μF 电容而且摆放位置要尽量贴近引脚走线要粗。另外模拟电路和数字电路共用电源时建议用磁珠或小电感把模拟电源单独隔离出来地平面尽量保持完整模拟地和数字地单点连接别让地回流噪声干扰采样。这里把 PCB 布局的要点总结成三条第一ADC 采样走线要尽量短并且远离时钟线、PWM 输出线这类高速翻转信号避免容性耦合把噪声串进来第二VDDA 和 VREF 建议加独立的 LC 或 RC 滤波采样瞬间电流突变没有本地储能电容的话电压会被瞬间拉低第三ADC 参考电压的去耦电容要紧贴引脚放置而且模拟地和数字地的连接点最好选在 ADC 附近形成一个干净的回流路径。做项目时先把这三条落实了后面就算遇到精度问题排查方向也会清晰很多。2. 采样周期、分辨率与滤波先把参数选明白2.1 ADC 时钟与采样时间怎么配STM32C562 的 ADC 时序可以分成两段采样阶段和转换阶段。转换阶段由 SAR 算法决定12 位分辨率大约需要 12.5 个 ADC 时钟周期采样阶段可以通过采样时间寄存器调整典型档位有 2.5、6.5、12.5、24.5、47.5、92.5 个周期等具体档位以手册为准。很多人不理解为什么要把采样时间做成可以配置的其实就是给前端电路一个“充电时间弹性”信号源阻抗低、走线短充电快可以用短一点的采样时间信号源阻抗高、走线长充电慢就得把采样时间拉长。我们实际算一笔账。假设总线时钟配置为 80MHzADC 预分频设置为 4那么 ADC 时钟就是 20MHz也就是 1 个周期 50ns。采样时间选 24.5 个周期总转换周期数就是 24.5 12.5 37 个周期单次采样耗时 37 × 50ns 1.85μs。如果把采样时间拉到 92.5 个周期耗时变成 (92.5 12.5) × 50ns 5.25μs。对于 100ms 才读一次电池电压的应用来说5μs 和 1.85μs 差别无所谓但如果你要做 100kHz 以上的连续采样就必须用短采样时间。所以参数怎么配核心取决于两件事前端的输出阻抗大小以及系统对采样速率的要求。建议先用短采样时间测量如果发现码值跳动偏大再逐步加大采样时间看改善情况。2.2 码值到电压的换算、LSB 与校准拿到 ADC 原始码值之后要换算成实际电压。12 位分辨率参考电压 Vref那么 1 个 LSB 等于 Vref / 4096。以 3.3V 参考为例1 LSB 大约是 0.8059mV。在代码里最常用的换算写法是voltage (float)raw * 3.3f / 4095.0f用 4095 是因为满量程码值是 4095也有人写 4096两者差异不到 0.03%大多数工程无感。但如果你做的是精度敏感项目建议查一下数据手册里满量程对应的精确码值定义严谨处理。另外STM32 系列 ADC 普遍带有校准功能。芯片出厂时内部电容阵列的匹配误差、比较器的失调电压都是有差异的校准的目的就是把这些工艺偏差修掉。使用 CubeMX 生成代码后建议在 ADC 初始化完成之后主动调用校准函数HAL_ADCEx_Calibration_Start(hadc1);校准要在 ADC 上电之后、开始转换之前调用一次。有的工程师新板子数据不准查来查去是这块漏了校准前后读数可能差好几个 LSB。如果项目对绝对精度有更高要求可以考虑外接精密基准源比如 2.5V 或 3.0V 的基准芯片把参考电压从 VDDA 上独立出来这样即使主电源在负载波动时被拉偏ADC 参考也不会跟着抖。2.3 软件滤波三板斧中值、滑动平均、一阶低通硬件做完了软件滤波是最后一道保险。ADC 原始值里常见的干扰是电源纹波、电磁辐射和采样瞬间的开关噪声它们表现为个别异常的毛刺点或者整体小幅抖动。针对不同噪声形态我一般用三种滤波方案。第一种是中值滤波。思路很简单连续采 N 个值排序后取中间值。N 取 3、5 或 7能非常有效地剔除孤立毛刺适合信号本身比较稳定、偶尔出现尖峰的场景。uint16_t middle_value(uint16_t *buf, uint8_t count) { /* 简单选择排序只排到中间位置为止 */ for (uint8_t i 0; i count - 1; i) { for (uint8_t j i 1; j count; j) { if (buf[j] buf[i]) { uint16_t tmp buf[i]; buf[i] buf[j]; buf[j] tmp; } } } return buf[count / 2]; }第二种是滑动平均适合信号有轻微随机噪声、变化速率不快的场景。维护一个环形队列每来一个新值就丢一个最旧的值然后求平均。窗口长度建议选 2 的幂次比如 8、16、32方便用移位代替除法。第三种是一阶低通滤波也叫加权递推平均公式是y_n alpha * x_n (1 - alpha) * y_{n-1}。其中 alpha 越大滤波越迟钝跟随越慢alpha 越小反应越快但噪声也多。电池电压这种慢变信号alpha 取 0.10.2 很合适如果是快速变化的电流信号alpha 可能要放到 0.3 以上。这类滤波会引入相位延迟所以采样频率至少要高于信号带宽的 10 倍否则测出来的“实时值”会明显滞后。3. 基于 STM32CubeMX 的 ADC 电压采集实操3.1 CubeMX 里的关键配置项用 STM32CubeMX 配置 ADC 其实很快重点检查几个位置。首先是 ADC1 的设置页把分辨率选为 12 位。其次是扫描模式和连续转换模式单通道轮询模式不需要开扫描多通道采集才需要打开扫描连续转换模式开了之后ADC 会不停地把结果写进数据寄存器配合 DMA 用非常顺手。然后是采样周期前面已经讲过怎么选这里按你的阻抗情况填。时钟树里要确认 ADC 时钟来源和分频系数。有的工程里 ADC 时钟被配置得很高已经超出了外设允许的最大频率但编译运行并不报错表现出来的现象是转换结果不稳定这种坑很难查。建议直接把 CubeMX 给出的 ADC 时钟值跟数据手册核对一遍。DMA 配置在多通道场景下尤其重要建议选择循环模式数据宽度设成 Half Word因为 STM32 的 ADC 数据寄存器是 16 位的但有效数据只占低 12 位。3.2 单通道轮询采样适合电池电压这类慢信号对于低速率、单通道的电压采集最简单的实现是轮询。先初始化 ADC然后在主循环里启动转换等待转换完成读取数据寄存器。参考代码如下uint16_t read_adc_single(void) { HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 100) HAL_OK) { return HAL_ADC_GetValue(hadc1); } return 0xFFFF; }实际使用中可以在 main 函数里先做一次校准然后再进入主循环每 100ms 读一次电压把码值换算成电压后通过串口打印出来。轮询模式的优点是代码直观、逻辑清楚缺点是一旦转换开始CPU 得一直等着结果转换期间没法干别的。对于读取电池电压这种低频需求完全够用。需要注意一点如果 ADC 引脚配置成模拟输入CubeMX 会自动把引脚的上下拉关掉这没问题但如果是自己手动配置 GPIO千万别把引脚设置成带上拉或者下拉否则会改变采样点电压导致读数偏移。另外在读取 DMA 数据数组时如果数组类型是uint32_t但 DMA 数据宽度是 Half Word也会出现数据错位务必保持类型和宽度一致。3.3 多通道 DMA 采集不阻塞 CPU 的连续采样多通道场景下轮询就有点力不从心了。假设要同时采集 4 路模拟量如果用轮询你得切换通道、逐个读取、再来回切换中间 CPU 被拖死而且切换瞬间数据容易串味。这时用 DMA 自动搬运数据才是正道。CubeMX 里配置多通道 ADC 时先把需要使用的通道按顺序加到 Conversion Sequence 里Rank 顺序就是转换顺序。打开扫描模式打开连续转换模式DMA 选择循环模式数据宽度 Half Word。然后在代码里定义一个环形数组比如 4 个通道就定义uint16_t adc_buf[4]调用启动函数HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, 4);DMA 会自动按扫描顺序把转换结果搬进数组。转换完成后在回调函数里置一个标志位主循环检测到这个标志后再处理数据volatile uint8_t adc_dma_done 0; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { adc_dma_done 1; } }有一点必须特别提醒STM32C562 基于 Cortex-M33 内核如果你的工程启用了 TrustZone 安全隔离ADC、DMA 这些外设必须在设备配置里被正确分配到安全或者非安全侧而且 DMA 通道和外设之间的连接要符合安全规则。很多人在非安全应用里访问 ADC 外设发现寄存器读不到数据或者 DMA 中断不触发十有八九就是 Safe/Nonsafe 分配没搞对。如果没开 TrustZone那就忽略这部分但排查问题时要记得这条线。多通道 DMA 模式下有一个很典型的坑是数据错位ADC 的扫描顺序和 DMA 缓冲区数组的顺序对不上或者 Rank 配置变化之后数组看起来还是按旧顺序排列。遇到这种情况先停下来确认 CubeMX 里的 Rank 顺序和代码里对数组下标的解释是否一致别动不动就怀疑 DMA 坏了。另一种情况是启动 DMA 采集之后数组只更新一次就不再变化多半是把 DMA 配成了单次模式改成循环模式就好。4. 踩坑实录ADC 数据跳动、漂移与串扰的排查思路4.1 读数像心电图一样乱跳多半在电源现象同一路电压ADC 读出来的值上下跳动几十到几百个 LSB用万用表测一下源电压其实很稳。排错第一步用示波器看 VDDA 引脚旁边的电源纹波如果纹波大先把模拟电源的滤波做好。第二步确认 ADC 时钟是否超标我见过有人把 ADC 时钟配到 30MHz 以上转换结果就是跳降下来立刻稳定。第三步检查参考电压源VDDA 和 VREF 上如果没有就近的 0.1μF 电容加载瞬间电压会被拉出毛刺。经验之谈ADC 数据像心电图一样跳第一怀疑对象永远是电源和基准而不是软件。4.2 码值整体偏高或偏低考虑分压电阻误差现象换算后的电压和万用表读数之间差了一小截而且差值随输入电压近似线性放大。最常见的原因是分压电阻精度不够。以 1% 精度的电阻为例R1 和 R2 各自有 ±1% 的偏差分压比例的综合误差可能到 2% 左右反映到 3.3V 量程上就是几十 mV 级别的偏差。解决办法是换 0.1% 精密电阻或者在软件里做两点校准。两点校准的具体操作是给系统输入一个已知的低电压 V1记录 ADC 码值 C1再输入已知高电压 V2记录码值 C2。根据这两组数据算出增益和偏移float gain (float)(V2 - V1) / (float)(C2 - C1); float offset V1 - gain * C1; /* 校准后电压 */ voltage gain * raw offset;这个方法能把电阻误差、参考电压偏差一次性修正掉工程上非常实用。注意校准时输入的参考电压本身要用高精度万用表测量否则校准基准不准后面全白搭。4.3 多通道串扰切换后第一次读数不能用现象多通道扫描模式下某个通道的读数总是比实际值高或者低尤其是相邻通道一个悬空、一个接着高阻抗源时串扰特别明显。原因是采样保持电容在上一次转换时充到了上一个通道的电压切到新通道后需要时间重新充电如果采样时间不够残留电荷还没放完就开始比较了结果自然不准。解决手段有三板斧第一延长采样时间第二软件上把每一轮扫描的第一次结果丢弃从第二次开始采信第三在 Rank 序列最前面加一个内部伪通道或者 GND 通道专门承担“脏充电”任务真正要用的通道排在它后面。实操下来这几招基本能把串扰问题压下去。4.4 DMA 数据错位、唤醒后不更新的处理DMA 相关的问题我整理了一张速查表调试时可以照着走问题现象可能原因处理办法DMA 数组里各通道数据顺序错乱扫描顺序和程序中对数组下标的解释不一致核对 CubeMX 里 Rank 顺序与数组下标对齐数组只有前几帧更新之后再也不动DMA 配置成单次模式DMA 模式改为循环模式低功耗唤醒后 DMA 停止或数据错乱唤醒后 DMA 未重新启动外设在低功耗下被复位或关闭唤醒后重新调用HAL_ADC_Start_DMA必要时重新初始化 ADC高通道采样值偶发为 0 或满量程转换期间缓冲区被 CPU 同时访问增加帧完成标志主循环判断标志位后再读数组处理完清除标志DMA 中断不触发TrustZone 开启时外设访问权限配置错误在设备配置里把 ADC/DMA 分配到正确的安全域多通道 DMA 还有一个容易忽略的点HAL_ADC_Start_DMA之后ADC 是连续转换DMA 也是循环搬运CPU 如果随时去读数组可能读到一半被更新的数据导致半个旧值半个新值。主循环里最好先关中断取数据或者用帧标志位同步确保读出来的是一帧完整结果。如果项目里 DMA 数据量很大还需要考虑内存一致性虽然 C 系列很多型号没有大缓存但为了稳妥建议 DMA 缓冲区定义在不会被缓存影响的内存段。5. 一点个人心得做 ADC 这些年我最大的感受是软件永远只是数据质量的最后一道保险前面的硬件和参数没做好后面写再多滤波代码也是治标不治本。经常有人拿着跳动的 ADC 数据来问我的第一个建议从来不是改滤波算法而是先拿万用表和示波器去现场测信号、测电源。很多时候问题最后都出在最基础的电源去耦、参考电压、接地布局上。如果你刚上手 STM32C562 的 ADC建议先跑通单通道轮询确认硬件链路正常再上多通道 DMA避免一上来就踩两个坑。等基础链路稳定了后面还能扩展很多玩法比如用 NTC 加查表法做温度采集用 ADC 过采样技术把有效分辨率往上再顶一顶或者用定时器触发 ADC 实现固定频率的精确采样这些在电机控制、电源监控项目里都有实战价值。这块内容如果大家感兴趣后面我继续写。
返回列表