直接用VDDA当参考电压会出错,因为VDDA受电池掉电、LDO负载瞬态和PCB压降影响而不稳定(如从3.3V降至3.0V),导致ADC值虚高、换算结果偏移超8%;应通过VREFINT通道(ADC_IN17)结合出厂校准值VREFINT_CAL反推实时VDDA:VDDA_Actual = VDDA_Charac × VREFINT_DATA / VREFINT_CAL,并需启用VREFEN、足够采样时间及多次均值滤波。

为什么直接用VDDA当参考电压会出错
ADC转换公式是 ADC_Value = (VIN / VREF) * FULL_SCALE,很多人默认VREF就是VDDA。但VDDA不是稳压源——电池掉电、LDO负载瞬态、PCB压降都会让它从3.3V掉到2.9V。这时哪怕输入电压没变,ADC_Value也会变大,换算结果就偏高。实测过一个温控板,VDDA从3.3V降到3.0V,同一热敏电阻读数跳变超8%,远超12-bit ADC的±0.5 LSB理论误差。
如何用VREFINT通道反推当前VREF(即VDDA)
VREFINT是芯片内部带隙基准,物理上连到ADC第17通道(VIN[17]),出厂时已在标准条件(如3.3V/25°C)下测得其真实值,并存为VREFINT_CAL,地址通常是0x1FFF75AA(STM32F4)或0x1FFFF7BA(GD32F407)。它不随VDDA线性变化,但VREFINT_CAL / VREFINT_DATA ≈ VDDA_Charac / VDDA_Actual这个比值关系稳定。
所以实时VDDA可估算为:VDDA_Actual = VDDA_Charac * VREFINT_DATA / VREFINT_CAL
关键步骤:
- 必须先置位
VREFEN(比如在ADC_CCR寄存器中),否则读VIN[17]永远是0或随机值 - 采样
VIN[17]前,确保ADC已就绪(ADRDY置位)、且采样时间足够长(建议≥112个周期,尤其对高阻抗内部源) -
VREFINT_DATA要取多次平均(如32次),避免单次噪声干扰 - 公式里
VDDA_Charac查数据手册“Electrical Characteristics”章节,不是“3.3V”这种标称值,而是3300 mV这类精确值
C++代码怎么写才不翻车
别用浮点除法做实时计算——嵌入式C++里float开销大,且ARM Cortex-M软浮点可能引入不可控延迟。推荐整数运算+定点缩放:
// 假设FULL_SCALE = 4095, VDDA_Charac = 3300 mV uint16_t vrefint_data = adc_read_channel(17); // 已含均值滤波 uint16_t vrefint_cal = *(uint16_t*)0x1FFF75AA; // 防除零 & 溢出保护 if (vrefint_data == 0 || vrefint_cal == 0) return 0; uint32_t vdda_mv = (uint32_t)3300 * vrefint_data / vrefint_cal; // 然后用这个vdda_mv去算实际通道:vout_mv = vdda_mv * chx_data / 4095;
容易漏的坑:
- 没关中断就调
adc_read_channel(),DMA或定时器触发冲突导致EOC标志误判 - 把
VREFINT_CAL当成常量硬编码,不同批次芯片偏差可达±3%,必须读Flash - 在
VDDA 时(如电池快耗尽),VREFINT输出开始非线性,此时公式失效,需加电压阈值检测
校准数据存在哪?怎么安全写入Flash
VREFINT_CAL是只读的出厂值,但你实测的vrefint_data(对应当前VDDA)需要保存供后续使用。不能往系统存储区写——那是ST/GD锁死的。得自己划一块用户Flash页(如最后1KB),按页擦除、半字编程。
操作要点:
- 写Flash前务必调用
FLASH_Unlock(),写完立刻FLASH_Lock() - 每次写入前校验目标地址是否为0xFF(未擦除则写失败)
- 建议存两个备份地址,写入时交替更新,防突然断电导致数据损坏
- GD32F407等型号有
FLASH_ProgramWord()函数,但注意参数是32位地址+32位数据,别传错类型
最常被忽略的一点:VREFINT校准只解决参考电压漂移,不修正ADC自身的增益误差和INL。如果应用要求ENOB > 10-bit,必须配合硬件RC滤波+软件滑动平均,否则温度漂移带来的误差会盖过校准收益。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











