使用STM32F407ZGT6构建一个电压监测器
在本次实验中,使用了STM32F407ZGT6的模数转换器(ADC)外设来测量两个电压源:CR2032锂聚合物电池和USB 5V总线电源。由于STM32F407ZGT6的ADC只能安全接收最高达VDDA + 0.3V(绝对最大值为3.6V)的输入电压,因此设计了一个电阻分压电路,在输入信号到达MCU引脚之前将其降压。随后读取分压后的电压值,进行数字化处理,并通过固件重新缩放,以恢复原始的电源电压。
实验室还研究了元件公差对测量精度的影响,并将单个样本的ADC读数与软件平均读数进行比较,以评估降噪效果。
设计与测试
第一部分 — 硬件描述
1 — 开发板
RT-Thread RT-Spark开发板基于STM32F407ZGT6微控制器构建,该芯片为168 MHz的ARM Cortex-M4F处理器,并配备三个独立的12位ADC。本实验中使用了ADC1的通道1(PA1)作为模拟输入。
2 — 为什么需要分压器
CR2032纽扣电池的工作电压范围为2.0V至3.3V,这在ADC的安全输入范围内,理论上可直接连接。然而,USB总线工作电压为5V,若直接连接到PA1,则会超过ADC输入的绝对最大值3.6V,导致ADC输入永久损坏。因此,设计了一个单级分压电路,通过将输入信号按0.5倍的比例缩放,来同时处理两种电压源。
分隔符公式:
VADCIn = VSource × R2 / (R1 + R2)
当 R1 = R2 = 1 MΩ 时:
VADCIn = VSource × 0.5
5V USB 输入在 ADC 引脚处变为 2.5V,安全低于 3.3V 参考电压;3.0V 电池输入在 ADC 引脚处变为 1.5V。
特意选择了1 MΩ的高电阻值,以使分压器的电流消耗保持在约1.5 µA,从而在监测电池电压时限制不必要的电池放电。
3 — 电路板搭建
电路搭建在一块标准的面包板上。R1和R2串联连接,它们之间的中间点接到了面包板扩展接口上的PA1引脚。面包板的GND排线则连接到面包板的GND引脚。
4 — 信号源阻抗与采样时间
该电路的一个重要考虑因素是其较高的源阻抗。当 R1 = R2 = 1 MΩ 时,ADC 输入端所见的等效特维宁阻抗为:
Zsource = R1 ∥ R2 = 500 kΩ
ADC的内部采样保持电容(约10 pF)必须通过该阻抗完全充电,然后才能开始转换。所需的稳定时间约为5τ = 25 µs。在21 MHz的ADC时钟频率下,这相当于大约525个周期。STM32CubeMX中可提供的最大采样时间为480个周期(约22.9 µs),这一数值已足够接近,并被选用于本实验。
第二部分 — 软件设置
1 — 创建项目
•打开 STM32CubeMX → 文件 → 新建项目
•对于MCU,搜索到了STM32F407ZGT6并选中了它
•项目名称:该项目/实验室命名为 Lab7_ADC_VoltageMonitor
•工具链 / IDE:STM32CubeIDE
•点击生成代码
2 — 系统核心配置
•在系统核心 → SYS 中:
•调试:串行线(自动为SWD分配PA13/PA14)
•在系统核心 → RCC 下:
•HSE:晶体/陶瓷谐振器(RT-Spark板上使用的是8 MHz晶体)
3 — 时钟树配置
系统时钟通过板载8 MHz的HSE晶振配置为168 MHz运行。在此过程中出现了一个问题:在配置PLL设置后,系统时钟复用器意外地指向了HSE而非PLLCLK,导致SYSCLK显示为8 MHz而不是168 MHz。该问题在代码生成前被发现,并通过在系统时钟复用器中点击PLLCLK进行了修正。
4 — ADC1配置
在模拟 → ADC1 中:
5 — USART1配置
根据RT-Spark原理图,PA9(USART1_TX)和PA10(USART1_RX)被连接到STLink芯片,从而提供一个虚拟串行端口,实现无需额外硬件的免费串行调试。
在连接性 → USART1:
•模式:异步
•波特率:115200
•字长:8位,无奇偶校验,1个停止位
6 — 代码生成
代码已生成。将生成的 main.c 替换为固件后,出现了一个编译警告:
浮点格式支持未启用……请在链接器标志中手动添加 -u _printf_float。
这导致 snprintf() 通过 UART 输出空白内容而非电压值。修复方法是在链接器标志中添加 -u _printf_float:
第三部分 — 软件实现
第3.1节 — 全局变量
三个关键变量被声明为全局变量,以便 CubeIDE 调试器的变量窗口可以在断点处检查它们:
第3.2部分 — ADC_Read() — 单次转换
用于步骤4、8和9。启动一次由软件触发的单次转换,读取结果后,ADC将被干净地停止,再进行电压计算。
电压转换使用 4096.0 作为除数(12 位 ADC 的 2¹² 步),而不是 4095.0。两者之间的差异约为 0.024%,但数学上正确的值是 4096。
第3.3部分 — ADC_Read_Averaged() — 软件平均
用于步骤10、11和12。以每个样本之间间隔10毫秒进行十次独立转换,返回平均值。
取N个样本的平均值可将均方根噪声降低√N倍。对于10个样本,这大约使噪声底噪降低了3.16倍——结果已得到证实。
第3.4部分 — 主循环
while 循环每秒执行一次 ADC_Read(),通过 UART 发送读取结果,并在调试器的断点处暂停。从步骤 10 到 12 转换为 ADC_Read_Averaged() 只需注释掉一个函数调用并取消注释另一个即可。
结果
第4.1部分 — 预闪存万用表测量
在刷写固件之前,使用万用表测量了相关电压,以建立接地参考值。这些基准值对于评估ADC计算值与实际电压的匹配程度至关重要。
第4.2部分 — 调试器断点 — 第4步(标称比例因子)
闪存后,固件以调试模式(F11)运行。在while循环内的HAL_Delay(1000)行上设置了一个断点。当程序遇到断点时,使用CubeIDE中的变量窗口检查计算出的值。
ADC结果在标称增益下与万用表读数非常接近,误差仅为1.08%。对于使用±5%容差电阻且未进行校准的12位ADC而言,这一结果是合理的。
第4.3部分 — 电阻测量与校准(步骤5–7)
R1 和 R2 被从电路中移除,并使用万用表在电阻模式下分别进行测量。
两个电阻的阻值均低于其标称的1 MΩ值,且在±5%的公差范围内。由于R1和R2不相等,实际倍数因子并非精确为2.0:
尺度因子 = (R1 + R2) / R2
= (986,312 + 977,291) / 977,291
= 2.009230618
代码已更新,R1 = 986312.0f,R2 = 977291.0f,启动时缩放因子自动重新计算为2.0092。
使用校准值重新刷写后:
第4.4部分 — 无需平均的重复性(步骤8–9)
断点连续五次触发,且每次暂停时均记录了电压值。
第4.5部分 — 平均读数(步骤10–12)
固件已切换为ADC_Read_Averaged(),该函数以10毫秒间隔采集10个样本并返回平均值。重建并重新烧录后,记录了五次测量数据。
第4.6部分 — USB总线测量(步骤13–14)
CR2032电池已断开连接。电路板的5V USB电源线连接到R1的顶部。使用了相同的电压分压器、接线方式和固件,未作任何修改。
第4.7部分 — 第15步:为什么仅使用电池无法工作
问题在于,当仅使用CR2032纽扣电池供电时,测量系统为何无法正常工作。
核心问题是,ADC的参考电压(VDDA = 3.3V)由电路板上的LDO稳压器提供,而该稳压器需要输入电压高于3.3V才能正常工作。一个3.0V的CR2032电池无法驱动LDO进入稳压状态——VDDA会逐渐接近电池电压。由于固件中源电压sourceVolt被硬编码为3.3f,实际较低的VDDA会导致每次读数都系统性地被低估。系统仍可运行,但测量结果不准确。
额外加分——使用内部VREFINT进行修复:
STM32F407 内部有一个 1.21V 的带隙参考电压(VREFINT),连接到 ADC 的通道 17。通过读取 VREFINT 并将其与已知的 1.21V 值进行比较,即可动态计算出实际的 VDDA:
float vdda_actual = (1.21f * 4096.0f) / (float)vrefint_raw;
volt = ((float)value / 4096.0f) * vdda_actual * scaleFactor;
这使得电压读数能够自动校正,无论实际供电电压如何,从而使系统即使在电池供电下也能报告准确的数值。
结论
所学内容
这个实验让ADC的感觉比仅仅阅读数据手册时更加具体。源阻抗问题——即1 MΩ电阻迫使使用480周期的采样时间——只有在实际操作硬件时才会变得明显。如果采用更短的采样时间,就会持续产生错误读数,而不会有任何错误提示。
校准实验产生了意想不到的结果。人们原本认为,通过使用测量得到的电阻值更新代码可以提高精度,但实际测得的校准误差(1.67%)实际上略高于未校准时的误差(1.08%)。这是因为硬编码的3.3V VDDA在静默地补偿了电阻不匹配的问题,而仅修正比例因子就破坏了这种平衡。这提醒我们,测量误差并不总是相互独立的。
平均化的结果是最令人满意的部分。在固件中使用一个简单的for循环,将峰值到峰值的波动从160毫伏降低到5毫伏——稳定程度提高了32倍——且未增加任何额外硬件。非平均化数据集中2.870V的异常值也清楚地表明,高阻抗节点会拾取瞬态噪声,而平均化能有效抑制这些噪声。
进一步改进
当前基于采样法的实现会在每次转换时阻塞CPU。如果使用DMA将ADC采样数据连续流式写入循环缓冲区,则可在零CPU开销下实现平均计算。此外,启用VREFINT通道并动态计算VDDA,可消除因假设VDDA精确为3.3V而产生的0.7%系统误差——这一修正在电池寿命接近末期时变得越来越重要。
本文编译自hackster.io





