低功耗与电池 / 实验方法与完整例程发布 内容更新

ESP32 读电池电量:为什么一条直线换算百分比不可靠?

在经典ESP32 Feather V2 Rev F上核200k分压、C5与ADC校准,记录USB、负载和静置条件下的电压,用自己的万用表数据核验而不生成SOC百分比。

读完能做什么
在经典ESP32 Feather V2 Rev F上核200k分压、C5与ADC校准,记录USB、负载和静置条件下的电压,用自己的万用表数据核验而不生成SOC百分比。
开始之前
完成所列前置,能按具体板图断电接线、保存原始记录,并使用相应框架构建独立工程。
所需器材
Adafruit Feather ESP32 V2 PID5400,原厂RevF设计cbde2c6658acb3c788616ceb13423b5d36305ef0;先核实际修订与200k/200k/C5,不混用S3 Feather。、Adafruit PID258保护1S3.7V/4.2V1200mAh LiPo,核JST-PH极性;本例不接高压/多节电池。、具备已知误差/输入阻抗的万用表、合适探头及独立无反供RX采集条件;真实读数全部待测。
版本组合
指定电池板:Adafruit Feather ESP32 V2 PID5400 PCB Rev F,经典ESP32-PICO-MINI-02 / 8MB Flash / 2MB物理PSRAM,本例禁PSRAM框架与板型:esp32:esp32:adafruit_feather_esp32_v2:FlashSize=8M,FlashFreq=80,PartitionScheme=default_8MB,PSRAM=disabled,CPUFreq=240,UploadSpeed=115200;Arduino3.3.12,CLI1.5.1分压/电池:原厂R1=200k/R3=200k/C5=1µF,GPIO35输入专用;PID258保护1S LiPo,不换算SOC
适用范围

经典ESP32/ESP32-PICO-MINI-02;Adafruit Feather ESP32 V2 PID5400 PCB Rev F固定原厂设计,不是ESP32-S3。Arduino3.3.12/CLI1.5.1,独立adafruit_feather_esp32_v2 FQBN。没有本站电池/ADC/误差/充电或续航实测。

这篇验证到了哪一步

资料核对已通过
docs/esp32-evidence/low-power-systems.md#l07验证日期:
构建 / 配置已通过
docs/esp32-evidence/low-power-systems-builds.json对象:完整实际构建Exit0,输入/配置/最终产物与原日志SHA归档;不代表实板运行版本:Arduino3.3.12/CLI1.5.1验证日期:
仿真已通过
docs/esp32-evidence/low-power-systems-hosts.json对象:同源纯逻辑/合成工具或实际本机链路,范围在各篇列明;非电气/ESP32硬件仿真验证日期:
本站实板待验证
没有本组合实板/仪表/电池,观察CSV只有表头;没有本站物理量或无线结果。

构建通过只说明指定环境中的代码或配置通过检查;实物功能、功耗和稳定性以对应的实板记录为准。

01 / 实验方法与完整例程

先回答测到哪里,再谈它能代表什么

本篇标题中的“电量”是读者的问题,完成物只是一份带条件的电压记录。锂电池端电压受电流、温度、老化、化学体系和静置恢复影响,不能把某两个电压点直线映射为0–100%,更不能由一次读数生成剩余小时数。

这次换用有公开原厂分压设计的经典ESP32 Feather V2 PID5400 Rev F,不沿用本系列S3板设置。固定Eagle commit cbde2c6…可追踪R1/R3/C5和ADC点;实际板必须先核修订。固定设计含CP2102N,后来实物可能改用CH9102F;原厂不一致时先查对应图与桥驱动,不把板名相似当全部布局相同。S3 Feather的I2C电量计路线不适用。

参考:[1] Adafruit固定RevF原厂Eagle/Pinout[2] 原厂Feather V2引脚/电源/电池分压

02 / 实验方法与完整例程

200k不是唯一参数,C5与测表也在电路里

原厂Rev F:R1=200kΩ从VBAT到A13/I35,R3=200kΩ从该点到GND,C5=1µF并联到GND。GPIO35是经典ESP32 ADC1_CH7、输入专用,不是S3上可被存储器占用的同号脚。本板已有分压,不另把电池正端接GPIO,也不启用内部上拉或给它输出。

名义4.2V经2:1分压为2.1V;电阻支路4.2/400k=10.5µA,均为电路算值,未包含板子/ADC/电容实际漏电。Thevenin约100kΩ与C5形成约100ms时间常数;高源阻抗与ADC采样电容、实际C5状态可能改变误差,不能只加平均次数就称准确。程序等1.5秒、丢4次,再做32组raw与mV转换且每组等2ms,是公开采样方法而非固定精度保证。

DMM接BAT/GND可比较整板输出;若直接测分压点,10MΩ表会与R3并联,使名义分压点约下降1%,还改变动态过程。记录表输入阻抗/探头电容与测点,优先用BAT端参考,不为了测量反而改变被测点又把偏差归咎软件。

参考:[1] Adafruit固定RevF原厂Eagle/Pinout[2] 原厂Feather V2引脚/电源/电池分压

03 / 实验方法与完整例程

USB、实际电池和负载条件写入原始记录

例用PID258带保护1S 3.7V/4.2V1200mAh LiPo,JST-PH极性匹配。固定Rev F的MCP73831T-2ACI/OT与5.1kΩ R4对应约196mA名义充电设定;这是原手册公式的计算,实际还受容差/温度/热限制影响。电池厂要求充电不超过500mA,连接前核实际电池/极性/充电器修订,断USB再插拔电池,不用2S或4.35V电芯。

USB接入可能给电池充电并改变系统供电;无电池时充电器也可能使VBAT出现电压/灯闪,不能由一个合法电压推断“有电池”。所以程序只报VBAT_NODE_ONLY,不自动识别电池或USB。空观察表要求操作者确认battery_present、usb、REST/LOAD/CHARGE、静置分钟数和温度。

USB0记录需要独立、不反供的高阻RX/记录设备,不能插回USB看串口却仍填USB0。板自身仍有工作负载,关闭无线不叫完全开路静置。低端观察遵守电池原规格,本例到3.3V附近停止,不用保护截止来“标定0%”;本程序也不承担安全切断功能。

组别必须记录能解释的范围
REST / USB0实际有电池、静置时间/温度、板负载该条件下的端电压对照
LOAD / USB0固定负载及电流/持续时间负载压降与恢复,不等于容量
CHARGE / USB1USB、电池、充电状态与温度充电时端电压,不混入REST拟合
无电池 / USB1明确battery_present=0只观察VBAT节点,不能称电池读数

参考:[1] Adafruit固定RevF原厂Eagle/Pinout[2] 原厂Feather V2引脚/电源/电池分压[5] MCP73831 DS20001984H充电设定[6] PID258电池原规格与极性

04 / 实验方法与完整例程

芯片mV校准和整板万用表校正分开

Arduino core3.3.12的analogReadMilliVolts在经典ESP32上建立line-fitting校准,失败会返回0。源码配置没有给缺eFuse情况填一个猜测default_vref;本例先查eFuse,缺出厂校准则只报raw/NO_EFUSE_CAL,不能假定所有芯片参考电压都是1100mV。设置12bit与ADC_11db,raw和mV是不同转换,不按4095与3.3V直接比例换算。

本例ADC1200–2200mV策略窗覆盖正常1S分压用途,原始近端点或任一次mV=0使该帧无效;这不是全ADC推荐量程声明。mV×2只用了名义电阻比,默认DMM_CALIBRATED=false,整板校正输出NA。core启动会开GPIO2的NeoPixel/QT电源,本例setup明确关它及GPIO13 LED;不因此声称量得低功耗。

参考:[3] Arduino3.3.12 ADC原样实现[4] Arduino3.3.12 Feather V2 variant引脚与启动电源

05 / 实验方法与完整例程

用此板FQBN构建,先查无电池条件

固定CLI1.5.1与Arduino-ESP32 3.3.12,无第三方库。完整FQBN在versions.lock/README/sketch.yaml:adafruit_feather_esp32_v2、FlashSize8M、FlashFreq80、default_8MB、PSRAM disabled、CPU240、Upload115200;不能照抄S3的USBMode/CDC/OPI参数。sketch.yaml不设default_profile,常规用完整--fqbn构建,显式profile仍可独立复建。

在解压目录运行README完整compile/upload命令,PORT取此板实际USB串口,再115200 monitor。先仅USB、明确无电池,确认板型/eFuse/状态,不以此电压称电池剩余量;断USB后接指定电池,按已经核过的采集方式记录。程序不写电池/Flash参数,没有自动百分比或自动充电控制。

做到这里,你应该看到
你能分清出厂ADC校准、未启用的DMM校正,以及人工确认的电池/USB条件。
06 / 实验方法与完整例程

原样草图、同源计算与默认校正配置

下面三文件与下载一致。每帧记录raw均值、校准ADC mV、名义VBAT节点mV、可选自己的DMM校正与状态;没有电池存在检测。BatteryVoltage只负责数值/范围,host检查证明算术及拒绝输入,不能证明ADC/eFuse/分压/电池行为。

代码 / 配置
// ---- BatteryVoltage.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <cmath>
struct Voltage {bool valid=false,dmmValid=false;double nominal=0,corrected=0;};
inline Voltage voltageEstimate(double raw,double adcMv,bool efuse,bool dmm,double gain,double offset){
 Voltage v;if(!efuse||!std::isfinite(raw)||!std::isfinite(adcMv)||raw<40||raw>4080||adcMv<1200||adcMv>2200)return v;
 v.nominal=adcMv*2.0;v.valid=true;
 if(dmm&&std::isfinite(gain)&&std::isfinite(offset)&&gain>=0.9&&gain<=1.1&&std::abs(offset)<=300){double x=gain*v.nominal+offset;if(x>=2400&&x<=4400){v.dmmValid=true;v.corrected=x;}}return v;
}

// ---- calibration.h ----
// SPDX-License-Identifier: MIT
#pragma once
// No site measurements exist. Keep disabled until YOUR board/meter/context has
// a reference dataset and independent check rows; tool output is not automatic proof.
constexpr bool DMM_CALIBRATED=false;
constexpr double DMM_GAIN=1.0;
constexpr double DMM_OFFSET_MV=0.0;

// ---- esp32-battery-voltage-monitor.ino ----
// SPDX-License-Identifier: MIT
#include <Arduino.h>
#include "esp_adc/adc_cali_scheme.h"
#include "BatteryVoltage.h"
#include "calibration.h"
static_assert(BATT_MONITOR==35,"Only Adafruit Feather ESP32 V2 GPIO35 divider is supported");
static bool factoryCalibration=false;
void setup(){
 Serial.begin(115200);
 pinMode(NEOPIXEL_I2C_POWER,OUTPUT);digitalWrite(NEOPIXEL_I2C_POWER,LOW); // core initVariant initially enabled this rail
 pinMode(LED_BUILTIN,OUTPUT);digitalWrite(LED_BUILTIN,LOW);
 analogReadResolution(12);analogSetPinAttenuation(BATT_MONITOR,ADC_11db);
 adc_cali_line_fitting_efuse_val_t efuse;esp_err_t e=adc_cali_scheme_line_fitting_check_efuse(&efuse);
 factoryCalibration=e==ESP_OK&&efuse!=ADC_CALI_LINE_FITTING_EFUSE_VAL_DEFAULT_VREF;
 delay(1500); // Rth~100k and C5=1uF => nominal tau~100ms; physical settling still must be checked
 Serial.printf("L07 Feather ESP32 V2 PID5400 RevF; ADC1 GPIO35; efuse_api=%s factory_cal=%u; no automatic battery/USB detection\n",esp_err_to_name(e),factoryCalibration);
 Serial.println("elapsed_ms,raw_mean,adc_mv,vbat_node_mv,dmm_corrected_mv,factory_cal,dmm_cal,status");
}
void loop(){
 // Raw and calibrated calls are separate conversions, not the same ADC sample.
 for(int i=0;i<4;i++){analogRead(BATT_MONITOR);delay(2);}
 uint32_t raw=0,mv=0;bool allConversions=true;for(int i=0;i<32;i++){uint16_t x=analogRead(BATT_MONITOR);raw+=x;if(x<40||x>4080)allConversions=false;if(factoryCalibration){uint32_t m=analogReadMilliVolts(BATT_MONITOR);mv+=m;if(!m)allConversions=false;}delay(2);}
 double a=raw/32.0,b=mv/32.0;Voltage v=voltageEstimate(allConversions?a:-1,b,factoryCalibration,DMM_CALIBRATED,DMM_GAIN,DMM_OFFSET_MV);
 Serial.printf("%lu,%.2f,",static_cast<unsigned long>(millis()),a);
 if(!factoryCalibration){Serial.println("NA,NA,NA,0,0,NO_EFUSE_CAL");}
 else if(!v.valid){Serial.printf("%.2f,NA,NA,1,0,RANGE_OR_ADC_ERROR\n",b);}
 else{Serial.printf("%.2f,%.2f,",b,v.nominal);if(v.dmmValid)Serial.printf("%.2f",v.corrected);else Serial.print("NA");Serial.printf(",1,%u,VBAT_NODE_ONLY\n",v.dmmValid);}
 delay(1000);
}
07 / 实验方法与完整例程

自己的5个拟合点,再加独立核验点

至少5个REST/USB0且确认有电池的点,正常使用区间跨度≥200mV,每点等待条件稳定,同步记录程序名义VBAT和DMM BAT/GND。表型号/校准状态/误差、温度、负载、休息时间和输入阻抗不可缺。另留至少2个未参与拟合的核验点;参与拟合的残差小并不能证明新数据的准确度。

把自己的参考行填入reference.csv,运行python3 tools/fit_reference.py reference.csv。空表、NaN、跨度窄、混充电组或离谱校正会拒绝;gain/offset只描述本数据,不能抄合成检查中的数。先排电路/探头/纹波,再决定是否校正。仅在独立核验达到自己的容差后,把真实系数写入calibration.h、启用标志并重建锁源码。

核验误差用估计值−DMM值,分别列REST/LOAD/CHARGE的分布、最大绝对误差和仪表不确定度,不把不同条件混成一个误差数字。reference.csv与observations.csv现在只有表头,本站没有电池读数、精度、漏电实测或续航结果。

参考:[1] Adafruit固定RevF原厂Eagle/Pinout[3] Arduino3.3.12 ADC原样实现[5] MCP73831 DS20001984H充电设定

08 / 实验方法与完整例程

超窗先排实物和条件,别夹成“满电”

全0/NO_EFUSE_CAL先查实际板型、eFuse/API与串口日志;合法节点电压却无电池时是供电条件问题,不代表电池突然满电。超窗先查BAT/GND、电池保护是否断开、USB/充电、分压修订与探头;不要把异常值夹到百分比0或100。噪声先看供电/负载和采样时刻,平均不能修增益或非线性。

上传失败先查该板串口桥驱动与PORT,按原厂boot恢复步骤加载已知固件,不先erase全部Flash。保留原始CSV与修改系数的构建信息。完成本实验的判据是条件可追踪、误差有自己的核验,不能因为计算出“3.8V”就声称知道剩余50%。回看P05把ADC输入范围与校准链路再核一遍,续航需另做完整周期能量与长时间记录。

BACK TO THE SOURCE

资料来源与版本说明

逐项核固定源码、原厂电路与实际构建;电压、能量、无线、ADC和电池结果仍待物理实验。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。

  1. Adafruit固定RevF原厂Eagle/Pinout
  2. 原厂Feather V2引脚/电源/电池分压
  3. Arduino3.3.12 ADC原样实现
  4. Arduino3.3.12 Feather V2 variant引脚与启动电源
  5. MCP73831 DS20001984H充电设定
  6. PID258电池原规格与极性