EFR32BG22 ADC测量AVDD的隐秘陷阱:为何禁用内部LDO是电池监测的关键一步?
在开发基于EFR32BG22的电池供电设备时,比如智能门锁、资产标签或者便携医疗设备,准确监测电池电压几乎是每个工程师的必修课。这直接关系到用户体验——没人希望门锁在毫无预警的情况下突然断电,或者医疗设备因为电量误判而提前关机。很多开发者会自然地想到使用芯片内置的12位ADC直接测量电源电压(AVDD),代码看起来简单直接,但实际跑起来却可能遇到一个令人困惑的现象:读数要么溢出,要么严重失真。
如果你正在调试一个电池电压监测功能,发现ADC读数与万用表测量值对不上,尤其是在电池电压较高时(比如超过2.5V),那么你很可能已经踩进了这个“坑”。问题的根源,并不在于你的代码逻辑,而在于EFR32BG22系列2芯片与它的前辈(系列0/1)在模拟前端设计上的一个关键差异。这个差异被官方文档隐藏在某个角落,却足以让一个功能从“能用”变成“精准可靠”。今天,我们就来彻底拆解这个陷阱,并给出一个经过实战验证的解决方案。
1. 理解EFR32BG22的ADC参考电压架构:系列2的静默变革
要搞清楚为什么直接测量AVDD会出问题,我们必须先深入EFR32BG22的模拟子系统内部。这颗芯片属于Silicon Labs无线Gecko平台的系列2(Series 2),而很多工程师的既有经验,可能还停留在系列0(如EFR32BG13)或系列1上。正是这种经验迁移,导致了最初的误判。
1.1 系列0/1的“经典”设计:内置升压与灵活参考
在早期的EFR32系列芯片中,ADC测量电源电压有一个相对直观的路径。以EFR32BG13为例,其内部ADC的参考电压源(VREF)可以选择多种来源,包括内部低噪声参考(如1.25V)、外部引脚输入,或者一个经过倍压的内部LDO输出。这个设计非常巧妙:
- 核心机制:当配置ADC去测量芯片的供电电压(VDD或DVDD)时,内部逻辑会自动启用一个电荷泵,将内部LDO产生的参考电压(例如1.25V)倍增至大约2.5V。
- 目的:这样做的目的是为了确保ADC的参考电压(VREF)始终高于或等于待测的输入电压(VDD),避免量程溢出。因为VDD的典型范围是1.8V到3.3V,而1.25V的参考电压显然不够。
- 开发者体验:对于开发者来说,这个过程几乎是透明的。你只需要在代码中设置输入通道为
iadcPosInputVdd或类似选项,SDK或底层驱动会自动处理好参考电压的切换和倍压,你读取到的ADC值可以直接通过一个固定的系数换算成真实的VDD电压。
这种设计在系列0/1中工作良好,也成为了许多开发者心中的“标准操作”。然而,当切换到追求极致成本和功耗优化的EFR32BG22(系列2)时,情况发生了变化。
1.2 系列2(EFR32BG22)的“简化”设计:成本与精度的权衡
EFR32BG22作为面向海量、低成本电池供电设备的SoC,在模拟前端做了显著的简化以降低芯片面积和静态功耗。其中一个关键变化,就发生在ADC的参考电压系统上。
最大的区别在于:EFR32BG22的ADC在测量AVDD时,默认不再提供自动的参考电压倍压功能。
它的内部参考电压源主要是一个1.21V的带隙基准(Bandgap Reference)。这个电压精度高、温漂小,是ADC进行高精度测量的理想基础。但是,当你想用这个1.21V的基准去测量一个可能高达3.8V(芯片最大工作电压)的AVDD时,问题就来了:输入信号电压超过了参考电压,ADC的输出会直接饱和(达到满量程),你得到的永远是一个最大值,毫无意义。
那么,Silicon Labs的工程师是如何解决这个矛盾的呢?答案是一个简单而有效的硬件设计:内置分压器。
关键提示:EFR32BG22的ADC输入通道
iadcPosInputAvdd在内部连接到一个固定的 1/4 分压网络。这意味着,实际进入ADC采样保持电路的电压是AVDD / 4。
这个设计变革带来了深远的影响,我们可以通过下表来清晰对比:
| 特性 | EFR32BG (系列0/1) | EFR32BG22 (系列2) | 对开发者的影响 |
|---|---|---|---|
| ADC参考电压 (测量VDD时) | 可自动切换至倍压后的内部LDO输出(~2.5V) | 固定为内部1.21V带隙基准 | 必须手动选择正确的参考源 |
| 输入信号路径 | VDD直接或通过可选分压进入ADC | AVDD通道固定内置1/4分压器 | 计算电压时必须乘以4 |
| 内部LDO角色 | 在测量VDD时,其输出被用作倍压源 | 在测量AVDD时,必须禁用,以避免干扰 | 配置步骤的关键差异 |
| 代码迁移复杂度 | 旧代码逻辑相对直接 | 旧代码直接迁移会导致测量错误 | 需要理解新架构并调整配置 |
这个分压设计非常精妙:它将可能高达3.8V的AVDD,降低到了0.95V(当AVDD=3.8V时)以下,这个值低于1.21V的参考电压,从而完美适配了ADC的量程。但代价是,开发者需要清楚地知道这个“隐藏”的分压比,并在软件计算中予以补偿。
1.3 内部LDO:从“助手”到“干扰源”的角色转变
在系列0/1中,内部LDO在VDD测量中扮演了积极的“助手”角色。但在EFR32BG22的AVDD测量场景下,如果它没有被正确管理,就会变成一个“干扰源”。
EFR32BG22的模拟部分(包括ADC)通常由内部LDO供电,以提供更干净、稳定的模拟电源。当你选择内部1.21V作为ADC参考电压时,这个参考源本身可能就源自或依赖于某个内部电压域。如果此时用于为模拟部分供电的LDO仍然活跃,并且其输出电压(例如1.8V)与待测的AVDD(例如3.0V)不同,就可能通过电源路径引入不可预测的耦合噪声或偏移,严重降低测量精度和可重复性。
因此,在EFR32BG22上使用内部1.21V参考源进行AVDD测量时,一个最佳实践是禁用为ADC供电的内部LDO,让ADC的模拟前端直接由AVDD通过分压器供电。这消除了一个潜在的误差来源,确保了测量基准的“纯净度”。这个操作通常通过配置特定的寄存器位来实现,是系列2芯片独有的注意事项。
2. 实战:从错误配置到精准测量的代码演进
理解了原理,我们来看代码。很多从旧平台移植过来的代码,或者基于不完整示例编写的代码,往往会掉入以下几个陷阱。让我们一步步修复它。
2.1 典型的错误配置与现象分析
首先,我们看一段常见的、但会导致问题的初始化代码片段(基于Gecko SDK):
// 有潜在问题的配置(适用于系列0/1,但不适用于系列2测量AVDD)
IADC_Init_t init = IADC_INIT_DEFAULT;
IADC_AllConfigs_t initAllConfigs = IADC_ALLCONFIGS_DEFAULT;
IADC_InitSingle_t initSingle = IADC_INITSINGLE_DEFAULT;
IADC_SingleInput_t initSingleInput = IADC_SINGLEINPUT_DEFAULT;
// 选择内部1.21V作为参考(这本身是对的)
initAllConfigs.configs[0].reference = iadcCfgReferenceInt1V2;
initAllConfigs.configs[0].vRef = 1210; // 单位毫伏
// 错误:直接选择了AVDD输入,但没有意识到内部有1/4分压
// 更严重的是,没有禁用可能影响测量的内部LDO
initSingleInput.posInput = iadcPosInputAvdd; // 问题点1
initSingleInput.negInput = iadcNegInputGnd;
// 初始化并开始转换...
IADC_init(IADC0, &init, &initAllConfigs);
IADC_initSingle(IADC0, &initSingle, &initSingleInput);
IADC_command(IADC0, iadcCmdStartSingle);
当电池电压(AVDD)为3.0V时,这段代码会发生什么?
- 实际进入ADC的电压是
3.0V / 4 = 0.75V。 - ADC以1.21V为参考,对0.75V进行采样。
- 理论上,ADC输出码值应为
(0.75V / 1.21V) * 4095 ≈ 2538(12位ADC)。 - 如果你错误地按照没有分压的公式计算:
电压 = (码值 / 4095) * 1.21V,你会得到约0.75V,然后你可能困惑“为什么测出来的电压这么低”。 - 如果你再错误地认为参考电压是自动倍压的(像系列0/1那样),并用3.0V去反推,逻辑会完全混乱。
而如果内部LDO未被禁用,你可能还会观察到读数在不同温度或芯片工作状态下有几十毫伏的波动,难以校准。
2.2 正确的配置步骤与代码实现
以下是经过修正的、适用于EFR32BG22的AVDD测量初始化流程。关键步骤已用注释标出。
步骤一:时钟与GPIO使能 这是标准操作,确保ADC和所需的外设时钟正常工作。
#include "em_device.h"
#include "em_cmu.h"
#include "em_iadc.h"
#include "em_gpio.h"
// 使能GPIO和IADC时钟
CMU_ClockEnable(cmuClock_GPIO, true);
CMU_ClockEnable(cmuClock_IADC0, true);
步骤二:初始化结构体并配置参考电压 这里明确选择内部1.21V带隙基准作为参考源。
IADC_Init_t init = IADC_INIT_DEFAULT;
IADC_AllConfigs_t initAllConfigs = IADC_ALLCONFIGS_DEFAULT;
IADC_InitSingle_t initSingle = IADC_INITSINGLE_DEFAULT;
IADC_SingleInput_t initSingleInput = IADC_SINGLEINPUT_DEFAULT;
// 复位IADC,确保干净的状态
IADC_reset(IADC0);
// 配置初始化参数,根据需求设置暖机模式等
init.warmup = iadcWarmupKeepWarm; // 保持待机模式以快速启动
// --- 核心配置1: 参考电压 ---
initAllConfigs.configs[0].reference = iadcCfgReferenceInt1V2;
initAllConfigs.configs[0].vRef = 1210; // 1.21V = 1210 mV
// 注意:vRef值应与数据手册中带隙基准的典型值一致,用于内部计算
步骤三:关键操作——禁用内部模拟LDO
这是系列2芯片测量AVDD时的关键步骤。通过设置 iadcCfgReferenceInt1V2 参考源并配合特定的输入选择,SDK底层或硬件可能会自动处理部分LDO配置,但为了绝对可靠,特别是在追求极致精度时,建议查阅芯片参考手册,确认是否需要手动设置 IADC0->CTRL 或 IADC0->CFG 寄存器中的特定位来确保测量AVDD时,相关的模拟LDO处于已知状态(通常是旁路或禁用)。由于SDK版本不同,具体操作可能封装在高级API内或需要直接寄存器访问。务必以你所用版本SDK的示例和参考手册为准。
步骤四:配置输入通道与采样 正确选择AVDD输入通道,硬件会自动接入1/4分压。
// --- 核心配置2: 输入通道 ---
// 选择AVDD作为正输入,内部1/4分压会自动生效
initSingleInput.posInput = iadcPosInputAvdd;
initSingleInput.negInput = iadcNegInputGnd; // 单端测量,负端接地
// 配置单次转换模式、数据对齐方式等
initSingle.dataValidLevel = iadcFifoCfgDvlValid1;
initSingle.triggerAction = iadcTriggerActionOnce;
initSingle.alignment = iadcAlignRight12; // 12位数据右对齐
步骤五:计算时钟预分频并完成初始化 根据所需的ADC时钟和采样率设置预分频器。
// 计算时钟预分频(示例:目标ADC时钟10kHz,源时钟5MHz)
uint32_t adcClkPrescale = IADC_calcAdcClkPrescale(IADC0,
10000, // CLK_ADC 目标频率
0,
iadcCfgModeNormal,
init.srcClkPrescale);
initAllConfigs.configs[0].adcClkPrescale = adcClkPrescale;
// 执行初始化
IADC_init(IADC0, &init, &initAllConfigs);
IADC_initSingle(IADC0, &initSingle, &initSingleInput);
// 分配模拟总线(如果使用特定GPIO则需配置,对于AVDD内部通道通常不需要额外GPIO配置)
// GPIO->IADC_INPUT_BUSALLOC = ...;
步骤六:启动转换并读取结果
// 启动单次转换
IADC_command(IADC0, iadcCmdStartSingle);
// 等待转换完成(这里以轮询标志位为例,实践中常用中断)
while ((IADC0->STATUS & IADC_STATUS_SINGLEFIFODV) == 0) {
// 等待数据有效
}
// 读取FIFO结果
IADC_Result_t result = IADC_pullSingleFifoResult(IADC0);
uint16_t raw_adc_value = result.data; // 12位原始数据
2.3 电压计算:还原真实的AVDD
拿到原始ADC码值后,正确的计算至关重要。公式推导如下:
-
ADC转换公式(理想):
采样电压 = (原始码值 / 满量程码值) * 参考电压对于12位右对齐数据,满量程码值是2^12 - 1 = 4095。 所以,ADC实际采样到的电压V_sample=(raw_adc_value / 4095.0) * 1.21 -
考虑1/4分压: 这个
V_sample是分压后的电压,即V_sample = AVDD / 4。 -
反推AVDD: 因此,真实的电源电压
AVDD=V_sample * 4=(raw_adc_value / 4095.0) * 1.21 * 4
简化后的C代码实现:
float calculate_avdd_from_adc(uint16_t raw_adc_value) {
// 使用浮点数计算以获得更高精度
float v_sample = (float)raw_adc_value / 4095.0f * 1.21f;
float avdd_voltage = v_sample * 4.0f;
return avdd_voltage; // 单位:伏特
}
// 或者使用整数运算避免浮点库(适用于低功耗场景)
uint16_t calculate_avdd_mv(uint16_t raw_adc_value) {
// 公式: AVDD(mV) = raw * 1210 * 4 / 4095
// 先乘后除以避免精度损失
uint32_t temp = (uint32_t)raw_adc_value * 1210UL * 4UL;
uint16_t avdd_mv = (uint16_t)(temp / 4095UL);
return avdd_mv; // 单位:毫伏
}
注意:这里的1.21是一个典型值。对于需要高精度的应用,建议在实际板子上用精密电压源校准这个值。芯片的带隙基准存在个体差异和温漂,通过校准可以显著提升系统级的测量精度。
3. 在智能门锁等电池设备中的实战应用与优化
掌握了正确的测量方法后,我们来看看如何在真实的电池供电产品中应用它,并以智能门锁为例,探讨如何构建一个鲁棒的电池管理系统(BMS)。
3.1 系统集成:周期测量与低功耗权衡
在智能门锁中,MCU大部分时间处于深度睡眠模式(EM2或EM3)以节省电量。电池电压监测需要周期性地唤醒系统进行。设计时需要考虑几个关键点:
- 测量频率:过于频繁会浪费电量,过于稀疏则可能错过电压陡降。一个常见的策略是:
- 正常工作时(如每小时一次)。
- 在执行耗电操作(如电机驱动开锁、蓝牙广播)前后立即测量。
- 当电压接近预警阈值时,提高测量频率。
- ADC初始化开销:每次测量都完整初始化、校准ADC再关闭,功耗较高。EFR32BG22的ADC支持在EM2模式下保持部分状态。可以利用
init.warmup = iadcWarmupKeepWarm配置,让ADC核心在睡眠时保持“温热”,唤醒后能更快开始转换,节省整体能耗。 - 中断处理:配置ADC在转换完成后产生中断,MCU在中断服务程序(ISR)中读取数据、计算电压、更新状态,然后迅速返回睡眠。避免在ISR中进行复杂的计算或逻辑判断。
一个简化的低功耗测量流程伪代码如下:
主循环:
进入EM2深度睡眠(RTC定时唤醒或外部事件唤醒)
唤醒后:
1. 检查是否需要测量电压(根据定时器或事件标志)
2. 如果需要:
a. 配置ADC(若已预热则跳过部分初始化)
b. 启动单次转换
c. 进入EM1或等待中断
3. ADC转换完成中断触发
a. 读取原始ADC值
b. 计算AVDD电压
c. 根据电压更新电池电量百分比和状态标志
d. 若电压过低,触发紧急处理(如锁死电机、发送低电警报)
4. 关闭ADC电源(或保持预热)
5. 根据其他任务决定下一步:执行其他操作或返回EM2睡眠
3.2 电量估算与预警策略
将准确的电压值转化为用户可理解的电量百分比和预警,需要结合电池放电曲线。
- 电池特性:智能门锁常用CR123A或多节AA电池。它们的放电曲线并非线性。例如,一颗锂亚硫酰氯电池(ER14505)在大部分放电时间内电压平台非常平坦(约3.6V),接近寿命终点时电压会迅速下降。
- 查表法:最实用的方法是通过实验,测量电池从满电到没电整个过程的电压-电量关系,创建一个查找表。软件中根据测量到的电压,在表中进行插值,得到当前剩余电量百分比。
- 温度补偿:电池电压受温度影响。如果产品工作环境温度范围宽(如-20°C到60°C),需要考虑温度补偿。EFR32BG22内部有温度传感器,可以同步测量芯片温度,对电压读数进行修正,或使用不同的电压-电量查找表。
一个简单的预警等级设计:
| 电压范围 (假设2节AA电池) | 电量状态 | 用户提示 | 系统动作 |
|---|---|---|---|
| >2.8V | 充足 | LED绿灯/App显示100%-60% | 正常操作 |
| 2.4V - 2.8V | 中等 | LED蓝灯/App显示60%-20% | 正常操作,可考虑降低蓝牙广播功率 |
| 2.1V - 2.4V | 低 | LED黄灯闪烁/App推送低电通知 | 限制非核心功能(如频繁的状态上报) |
| <2.1V | 严重不足 | LED红灯常亮/App强提醒 | 禁止电机操作(防止开锁一半没电),仅维持蓝牙警报功能 |
| <1.8V (接近截止) | 即将关机 | - | 强制进入最低功耗状态,保存关键数据 |
3.3 常见问题排查与精度提升技巧
即使按照正确步骤配置,在实际项目中仍可能遇到测量波动或偏差。以下是一些排查思路和高级技巧:
- 读数不稳定(波动大):
- 电源噪声:在AVDD引脚附近增加一个10µF的电解电容并联一个100nF的陶瓷电容,可以有效滤除低频和高频噪声。这是提升ADC读数稳定性的最有效硬件措施。
- 采样平均:在软件中连续采样多次(如16次或32次),然后取平均值或中位数,能显著抑制随机噪声。注意这会增加测量时间和功耗。
- 参考电压稳定:确保在启动ADC转换前,内部带隙基准有足够的稳定时间。SDK的
IADC_init函数通常会处理,但如果你在测量前快速开关ADC,可能需要手动插入延迟。
- 读数有固定偏差:
- 校准:这是消除系统误差的关键。在生产线上,用一个高精度的电压源(如4.096V)输入到板子的电池接口,测量并记录ADC原始值。根据理论值和实际值计算出一个校准系数(增益和偏移),存储在Flash中。后续所有测量都应用这个系数进行修正。
- 代码校准示例:
// 假设在3.000V校准电压下,读出的原始码值为raw_cal // 理论码值应为: (3.0/4)/1.21 * 4095 ≈ 2538 // 计算增益误差: scale_factor = 理论码值 / raw_cal (浮点数) // 存储scale_factor到非易失性存储器 // 实际测量时: uint16_t raw_measured = read_adc_once(); float voltage_compensated = (raw_measured * scale_factor / 4095.0f) * 1.21f * 4.0f; - 温度影响:内部1.21V参考源有温漂。如果应用对精度要求极高,需要根据芯片内部温度传感器的读数进行二次补偿。数据手册会提供参考电压的温度系数。
- 测量导致系统功耗增加:
- 检查测量结束后,是否彻底关闭了ADC时钟 (
CMU_ClockEnable(cmuClock_IADC0, false))。 - 确认没有因为配置错误导致ADC或相关模拟电路一直处于活动状态。
- 使用SDK提供的功耗分析工具(如Energy Profiler)来定位异常耗电的时段。
- 检查测量结束后,是否彻底关闭了ADC时钟 (
4. 超越基础:高级场景与替代方案探讨
对于绝大多数智能门锁应用,上述基于内部1.21V参考和分压的方法已经足够。但在某些极端或特殊要求下,你可能需要考虑其他方案。
4.1 使用外部精密参考电压
如果你对测量精度有极致要求(例如优于±1%),或者AVDD电压范围很宽(低至1.8V,高至3.8V),使用内部1.21V参考可能不是最优解。因为输入电压经过1/4分压后,在AVDD很低时(如1.8V),ADC输入电压仅为0.45V,只利用了ADC量程的不到40%,导致分辨率下降。
方案:使用一个外部精密、低功耗的电压基准芯片(如TI的REF3020,输出0.2V至5.5V可调,功耗仅3.5µA),将其连接到EFR32BG22的ADC外部参考输入引脚(VREF 引脚,如果芯片型号支持)。然后配置ADC使用外部参考。
优势:
- 更高分辨率:可以选择一个接近AVDD最大分压后电压的参考值(例如1.0V),充分利用ADC的满量程。
- 更好精度和温漂:外部基准芯片的精度和温度稳定性通常远优于内部带隙基准。
- 灵活性:参考电压值可根据需要调整。
代价:
- 增加BOM成本和PCB面积:多一颗芯片和几个外围被动元件。
- 功耗略微增加:外部基准芯片的静态电流。
- 配置更复杂:需要正确连接硬件,并在软件中切换参考源配置。
4.2 利用PRS(外设反射系统)实现后台监控
EFR32BG22强大的PRS功能允许外设之间直接通信,无需CPU干预。我们可以设计一个方案,让ADC在低功耗定时器(LETIMER)的触发下,自动周期性地测量AVDD,并将结果通过DMA存入内存。CPU只在需要读取结果时才被唤醒。
大致流程:
- 配置LETIMER以固定间隔(如每分钟)产生一个脉冲。
- 配置PRS,将LETIMER的输出连接到ADC的触发输入。
- 配置ADC为单次扫描模式,由PRS信号触发。
- 配置DMA,在ADC每次转换完成后,自动将结果传输到预设的RAM缓冲区。
- 使能ADC和DMA,然后CPU进入EM2深度睡眠。
- DMA传输完成或缓冲区半满/全满时,可以产生中断唤醒CPU,CPU再批量处理这些电压数据。
这种方案将CPU从周期性的ADC管理任务中彻底解放出来,实现了真正的“后台”电压监控,系统整体功耗可以做到更低。
4.3 与其他系列芯片(xG24, BG27)的横向对比
Silicon Labs后续推出的EFR32xG24和EFR32BG27系列,在模拟子系统上又有演进。了解它们之间的区别,有助于为不同项目选型。
- EFR32xG24系列:主打高性能和多功能集成。其ADC性能通常更强(可能支持更高采样率、更多输入通道),参考电压系统也可能更灵活。在测量电源电压方面,设计逻辑可能更接近BG22,但具体是否需要禁用特定LDO,必须查阅xG24的专用数据手册和参考手册,切勿直接套用BG22的经验。
- EFR32BG27系列:作为新一代产品,可能在能效和集成度上更进一步。其数据手册和SDK会明确指导如何进行电源电压测量。Always-ON Domain等新特性的加入,可能会让在深度睡眠下进行超低功耗电压监测变得更加容易。
核心原则:每当切换到一款新的Silicon Labs无线SoC时,关于模拟测量(尤其是电源电压测量)的部分,应该被视为一个需要重新验证的新领域。花一小时仔细阅读数据手册的“Analog to Digital Converter (ADC)”和“Power Management”章节,以及SDK中相关的示例代码,远比盲目移植旧代码然后花费数天调试要高效得多。
最后,调试ADC这类模拟功能,一台好的示波器是你的最佳伙伴。用它观察AVDD引脚在ADC采样瞬间的波形,可以直观地看到电源是否稳定,分压后的信号是否干净。很多时候,硬件上的一个电容,就能解决软件上无数次的逻辑纠错。



被折叠的 条评论
为什么被折叠?



