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

外设也要睡:控制传感器和屏幕电源时要检查什么?

用两路指定负载开关分时供电,逐一核对I2C释放、放电、重初始化,并建立常供电与断电的整周期能量对照。

读完能做什么
用两路指定负载开关分时供电,逐一核对I2C释放、放电、重初始化,并建立常供电与断电的整周期能量对照。
开始之前
完成所列前置,能按具体板图断电接线、保存原始记录,并使用相应框架构建独立工程。
所需器材
官方DevKitC-1 v1.1 N8R8;桥接UART115200,GPIO43/44。、两块TI TPS22919EVM PSIL050/TPS22919DCKR,SLVUBH0版本,100kΩ外部ON下拉各一。、Adafruit PID4885 SHT40与PID326 128x64 SSD1306(2020后自动复位、默认I2C/0x3D),已可靠焊接排针。、PPK2 Source Meter5V/600mA,万用表、合适探头示波器;全部待读者实测。
版本组合
框架:ESP-IDF5.5.5 / b774170ff46c393eeb5e495ea37936038d3f4f4f;原生GPIO/I2C/Sleep,无外部库外设:TPS22919EVM PSIL050 / SLVUBH0;器件SLVSEN5B;PID4885/0x44,PID326自动RST/0x3D;两路100kHz
适用范围

ESP-IDF5.5.5;官方ESP32-S3-DevKitC-1 v1.1/WROOM-1-N8R8,DIO80MHz/8MB、PSRAM关闭;TI TPS22919EVM PSIL050×2、PID4885与2020后PID326。仅此组合;无本站物理测量。

这篇验证到了哪一步

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

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

01 / 实验方法与完整例程

主控睡下了,电源路径却未必断开

L02量到的是整板和外设的总和;L03让主控深睡,仍不能说明屏幕稳压器、上拉、传感器和电容没有耗电。本篇把问题缩到一个能复验的动作:先给SHT40上电读一次并关闭,再给OLED上电显示同一份值3秒,最后两路都请求关闭、主控请求深睡60秒。屏幕不是持续显示的看板,3秒后熄灭是明确功能取舍。

完成物是独立原生IDF工程、两路电源/两组信号接线、软件关断轨迹、空电压与电量表,以及常供电对照方法。没有实板时能检查代码与构建,不能把REQUEST OFF打印当测到0V,也不能先写一个节能百分比。

做到这里,你应该看到
你能沿着VIN、VOUT、信号与地分别追踪两组外设,而非只找供电正线。
02 / 实验方法与完整例程

开关型号、QOD和默认态分别确认

本例指定两块TI TPS22919EVM/PSIL050,器件TPS22919DCKR;3.3V在其1.6–5.5V输入范围内,ON的高低阈值按SLVSEN5B电气表核。最大1.5A是开关额定,不能据此要求DevKit的3V3输出1.5A;本例负载与浪涌仍须检查供电压降。TPS22919未提供本例可依赖的反向电流阻断保障,不能把“有负载开关”写成杜绝任何反供。

两块JP1跳帽均移除,用TP2接受GPIO控制;每块另接100kΩ ON到GND,保证ROM/复位/深睡前后的默认关断路径。控制高时该下拉约消耗33µA,这是3.3V/100k的计算,须算进总能量。内部Smart Pull Down不替代外部默认态核验。只装JP2使QOD直连VOUT,JP3/JP4留空;内部24Ω是典型值,放电时间还取决于负载电容,100ms等待不是测量结果。

SLVUBH0中JP3段落的300Ω与图/BOM的2kΩ不一致,本例不使用JP3,也不靠该数值外推下降时间。不要另从USB/STEMMA给VOUT一侧供电,否则既可能绕过开关,又可能经过器件内部路径返回VIN。

参考:[1] TI TPS22919 SLVSEN5B:ON/QOD/电气表与功能[2] TPS22919EVM SLVUBH0:PSIL050图1及§4.1端子/跳帽

03 / 实验方法与完整例程

让每组上拉跟着自己的电源走

SHT40的VIN接EVM S的VOUT,GPIO8/9分别接SDA/SCL;OLED的VIN接EVM O的VOUT,GPIO17/18分别接SDA/SCL。两组分别用I2C0/I2C1,绝不相连。所有GND保持连通;SHT的3V输出、OLED的RST及其它SPI焊盘留空。PID326限定2020后自动复位的128x64默认I2C/0x3D修订,旧模块需要重新核对复位路线。

排针必须已可靠焊接。模块已有上拉随其VIN及板载电源关断,不再加到常供3V3的上拉;检查任何电平转换板、调试器、其它I2C器件和接线仪表是否另有供电路径。仅把主控GPIO设输入还不够:内部上拉仍可能把关断外设拉高,程序明确禁止输入输出驱动并设GPIO_FLOATING关闭内部上下拉。浮空只描述驱动配置,并不承诺信号电压一定为0。

信号SHT40域OLED域
3V3/GNDEVM S J1 VIN/GNDEVM O J1 VIN/GND
控制GPIO5→TP2,另100k到GNDGPIO6→TP2,另100k到GND
下游电源EVM S J2→PID4885 VIN/GNDEVM O J2→PID326 VIN/GND
I2CGPIO8 SDA / 9 SCL,0x44GPIO17 SDA / 18 SCL,0x3D

参考:[2] TPS22919EVM SLVUBH0:PSIL050图1及§4.1端子/跳帽[6] PID326修订与I2C接线[7] DevKitC-1 v1.1引脚表

04 / 实验方法与完整例程

先完成事务,再删除句柄,最后拉低ON

源码用同一个PowerSequence策略组织正常与失败路径:SHT上电后等20ms,创建总线/设备,FD转换等10ms再收6字节验双CRC;不复用关电前的I2C句柄。20ms考虑裸芯片达到最低电压后最大1ms启动及板级开关/稳压余量,实际VOUT上升仍要测。

所有事务同步完成后先移设备,再删总线;随后禁止GPIO输入输出并关闭内部上下拉,才把EN5拉低、等100ms。这里故意不只调用gpio_reset_pin:固定IDF实现会为有效输出脚打开上拉。然后EN6高、等100ms、重建I2C1,重新送完整SSD1306命令与1024字节帧。OLED行顶整数单位0.1°C,底整数单位0.1%RH,一条/两条左标记区分;例如231表示23.1°C,是取整显示。

3秒观察窗后先发AE和8D10关显示/电荷泵,再同样移设备、删总线、释放17/18、关EN6。任何启动/CRC/清理错误都请求相应关断并停留FAULT awake;它不是有效省电周期。主机8条策略路径能证明调用顺序,不证明GPIO、电源或画面。

参考:[3] IDF5.5.5 GPIO:DISABLE/FLOATING实现[4] IDF5.5.5 I2C:设备和总线生命周期[5] SHT4x固定手册:启动、FD转换与CRC

05 / 实验方法与完整例程

编译与首次操作先把外设电源断开

下载是完整CMake/main/头文件/版本锁/许可,固定IDF5.5.5,S3 DIO80MHz 8MB、PSRAM关闭、桥接UART115200。激活这个版本,在解压目录执行idf.py set-target esp32s3、idf.py build,再用自己的PORT运行idf.py -p PORT flash monitor。上传时先断开两EVM VIN;已确认新程序后整机断电,再按表恢复接线。

首次不接负载先测两EVM的TP2/TP5/TP6;核默认OFF、控制变化与QOD下降,再分别接SHT、OLED。预期日志是请求上电→INIT→SAMPLE valid→HIZ/OFF→OLED传输→HIZ/OFF→SLEEP,实际图像要肉眼核对。深睡计时请求60秒,完整周期还含ROM、启动、显示和串口清空,不称精确每分钟。

做到这里,你应该看到
软件请求轨迹、TP6实际电压和肉眼画面三类证据各自有记录;没有其中一项就留待测。
06 / 实验方法与完整例程

原样主程序与同源顺序策略

下面文件与下载逐字一致。PowerSequence负责控制顺序,Hardware负责实际GPIO/I2C及清理。ShtSample.h、完整构建配置与主机fixture在下载目录,温湿度转换和CRC沿用L03固定实现。原生SSD1306缓冲仅1024字节,不需要图形库或PSRAM。

代码 / 配置
// ---- main/PowerSequence.h ----
// SPDX-License-Identifier: MIT
#pragma once
// This exact policy is used by the firmware and host fixture. IO cleanup must
// release bus/device and float both signal pads before requesting rail OFF.
template<class IO> bool runPowerCycle(IO& io){
 bool ok=io.startSensor();if(ok)ok=io.readSensor();
 bool closed=io.stopSensor();ok=ok&&closed;
 if(!ok)return false;
 ok=io.startDisplay();if(ok)ok=io.drawDisplay();
 if(ok)io.viewWindow();
 bool slept=io.displayOff();bool released=io.stopDisplay();
 return ok&&slept&&released;
}

// ---- main/main.cpp ----
// SPDX-License-Identifier: MIT
#include <cstdio>
#include <cstring>
#include <initializer_list>
#include <inttypes.h>
#include "esp_sleep.h"
#include "esp_timer.h"
#include "driver/gpio.h"
#include "driver/i2c_master.h"
#include "driver/uart.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ShtSample.h"
#include "PowerSequence.h"
struct Channel {gpio_num_t en,sda,scl;int port;uint8_t address;int waitMs;i2c_master_bus_handle_t bus=nullptr;i2c_master_dev_handle_t dev=nullptr;};
static void waitMs(int ms){vTaskDelay(pdMS_TO_TICKS(ms));}
static void floatPad(gpio_num_t pin){ESP_ERROR_CHECK(gpio_set_direction(pin,GPIO_MODE_DISABLE));ESP_ERROR_CHECK(gpio_set_pull_mode(pin,GPIO_FLOATING));}
static void offPads(Channel& c){floatPad(c.sda);floatPad(c.scl);}
static bool start(Channel& c){
 ESP_ERROR_CHECK(gpio_set_level(c.en,1));printf("REQUEST ON EN%d; wait_ms=%d (measure TP6; no rail feedback)\n",int(c.en),c.waitMs);waitMs(c.waitMs);
 i2c_master_bus_config_t b{};b.i2c_port=c.port;b.sda_io_num=c.sda;b.scl_io_num=c.scl;b.clk_source=I2C_CLK_SRC_DEFAULT;b.glitch_ignore_cnt=7;b.flags.enable_internal_pullup=false;
 esp_err_t e=i2c_new_master_bus(&b,&c.bus);i2c_device_config_t d{};d.device_address=c.address;d.dev_addr_length=I2C_ADDR_BIT_LEN_7;d.scl_speed_hz=100000;
 if(e==ESP_OK){e=i2c_master_bus_add_device(c.bus,&d,&c.dev);}
 printf("INIT EN%d api=%s\n",int(c.en),esp_err_to_name(e));return e==ESP_OK;
}
static bool stop(Channel& c){
 bool ok=true;if(c.dev){auto e=i2c_master_bus_rm_device(c.dev);ok=e==ESP_OK;if(ok)c.dev=nullptr;}
 if(c.bus){auto e=i2c_del_master_bus(c.bus);ok=(e==ESP_OK)&&ok;if(e==ESP_OK)c.bus=nullptr;}
 // All requests above are synchronous, no outstanding transactions/callbacks.
 // Even driver cleanup failure must not leave pads driving a powered-off module.
 offPads(c);ESP_ERROR_CHECK(gpio_set_level(c.en,0));printf("REQUEST HIZ SDA%d/SCL%d -> OFF EN%d; discharge_wait_ms=100\n",int(c.sda),int(c.scl),int(c.en));waitMs(100);return ok;
}
static esp_err_t tx(Channel& c,const uint8_t* p,size_t n){return c.dev?i2c_master_transmit(c.dev,p,n,200):ESP_ERR_INVALID_STATE;}
// Native SSD1306, 128x64, I2C=0x3D, horizontal addressing, 1024-byte framebuffer.
static uint8_t frame[1024];
static void pixel(int x,int y){if(x>=0&&x<128&&y>=0&&y<64)frame[x+(y/8)*128]|=uint8_t(1u<<(y&7));}
static void number(int value,int x,int y){
 static const uint8_t digits[10][5]={{0x3e,0x51,0x49,0x45,0x3e},{0,0x42,0x7f,0x40,0},{0x42,0x61,0x51,0x49,0x46},{0x21,0x41,0x45,0x4b,0x31},{0x18,0x14,0x12,0x7f,0x10},{0x27,0x45,0x45,0x45,0x39},{0x3c,0x4a,0x49,0x49,0x30},{1,0x71,9,5,3},{0x36,0x49,0x49,0x49,0x36},{6,0x49,0x49,0x29,0x1e}};
 char text[16];snprintf(text,sizeof text,"%d",value);for(const char* p=text;*p;++p,x+=12)for(int col=0;col<5;col++)for(int row=0;row<7;row++)if((*p=='-'?row==3:digits[*p-'0'][col]&(1u<<row)))for(int dx=0;dx<2;dx++)for(int dy=0;dy<2;dy++)pixel(x+col*2+dx,y+row*2+dy);
}
struct Hardware {
 Channel sensor{GPIO_NUM_5,GPIO_NUM_8,GPIO_NUM_9,0,0x44,20},display{GPIO_NUM_6,GPIO_NUM_17,GPIO_NUM_18,1,0x3d,100};ShtSample sample{};
 bool startSensor(){return start(sensor);}bool stopSensor(){return stop(sensor);}bool startDisplay(){return start(display);}bool stopDisplay(){return stop(display);}
 bool readSensor(){uint8_t cmd=0xfd,b[6];esp_err_t e=tx(sensor,&cmd,1);if(e==ESP_OK){waitMs(10);e=i2c_master_receive(sensor.dev,b,6,200);}if(e==ESP_OK)sample=decodeSht(b);printf("SAMPLE api=%s valid=%u t_mC=%d rh_mPct=%d\n",esp_err_to_name(e),sample.valid,sample.t_mC,sample.rh_mPct);return e==ESP_OK&&sample.valid;}
 bool drawDisplay(){
  const uint8_t init[]={0,0xae,0xd5,0x80,0xa8,63,0xd3,0,0x40,0x8d,0x14,0x20,0,0xa1,0xc8,0xda,0x12,0x81,0x7f,0xd9,0xf1,0xdb,0x20,0xa4,0xa6,0x2e,0x21,0,127,0x22,0,7};
  esp_err_t e=tx(display,init,sizeof init);memset(frame,0,sizeof frame);number(sample.t_mC/100,12,8);number(sample.rh_mPct/100,12,36);
  // Top integer is T in 0.1 C, bottom is RH in 0.1%; row markers distinguish them.
  for(int x=0;x<5;x++){pixel(x,8);pixel(x,36);pixel(x,40);}
  for(int p=0;p<8&&e==ESP_OK;p++){uint8_t b[129];b[0]=0x40;memcpy(b+1,frame+128*p,128);e=tx(display,b,sizeof b);}if(e==ESP_OK){const uint8_t on[]={0,0xaf};e=tx(display,on,sizeof on);}printf("OLED transfer=%s; visual result pending\n",esp_err_to_name(e));return e==ESP_OK;
 }
 void viewWindow(){waitMs(3000);}bool displayOff(){if(!display.dev)return false;const uint8_t off[]={0,0xae,0x8d,0x10};return tx(display,off,sizeof off)==ESP_OK;}
};
extern "C" void app_main(){
 int64_t began=esp_timer_get_time();Hardware io;
 // 100k external EN-to-GND per EVM is required during ROM/boot/deep sleep.
 for(auto pin:{GPIO_NUM_5,GPIO_NUM_6}){ESP_ERROR_CHECK(gpio_set_level(pin,0));ESP_ERROR_CHECK(gpio_set_direction(pin,GPIO_MODE_OUTPUT));ESP_ERROR_CHECK(gpio_set_pull_mode(pin,GPIO_FLOATING));}
 offPads(io.sensor);offPads(io.display);waitMs(2000);bool ok=runPowerCycle(io);
 if(!ok){puts("FAULT: rails requested OFF; stay awake, verify VOUT/signals and reset after repair");return;}
 ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(60000000ULL));printf("SLEEP active_app_us=%" PRId64 " request_us=60000000; current/rail voltage unmeasured\n",esp_timer_get_time()-began);fflush(stdout);ESP_ERROR_CHECK(uart_wait_tx_idle_polling(UART_NUM_0));esp_deep_sleep_start();
}
07 / 实验方法与完整例程

测残压与整周期电量,再决定是否值得关电

示波器记录ON→VOUT有效电压、VOUT下降、SDA/SCL与各自VOUT关系,写入observations.csv。20/100ms等待都需用真实模块验证;若关断后残压不降,先查常供上拉、第二供电、电平转换器和探头影响。SHT至少要实际穿过掉电条件才能保证下一次为冷启动,不能只看到ON低。不要故意把GPIO驱动高来演示反供。

常供电对照须整机断电,先拆掉GPIO5/6到TP2的线,再把每块JP1接ON到VIN(图中1–2)。不能同时保留GPIO线和高跳帽。用相同固件、同3秒画面和60秒睡眠请求;常供电组仍发软关屏命令,比较的是软休眠留电与实际切电。

按L02整板边界:PPK2 Source Meter5.0V供DevKit5V/GND,DUT总电流上限600mA,DUT两USB和其它供电旁路断开;仪表自己的USB仍接电脑。至少各30个有效完成周期,从同一边界积分到最后一个完整睡眠结束,包含浪涌、初始化、控制电阻、失败重试和睡眠。导出后显式映射时间/电流单位,用下载的normalize_csv.py和integrate.py计算Q与E;缺口上限按实际采样设定,不能跨丢失数据积分。两组结果都还空着,因此本篇没有本站电量收益数字。

参考:[1] TI TPS22919 SLVSEN5B:ON/QOD/电气表与功能[2] TPS22919EVM SLVUBH0:PSIL050图1及§4.1端子/跳帽

08 / 实验方法与完整例程

失败先查供电域,修好后再重新开始一组

INIT NACK先看TP6是否有效、地址/模块修订是否匹配,再看总线分组;CRC失败先看电源上升与等待、SDA/SCL完整性,不填假温湿度。OLED transfer=OK但空屏时查0x3D、自动RST修订、完整重初始化和实际VCC,而不是把API成功当图像证明。FAULT后两路请求OFF、主控故意不睡,修复后RESET重开记录。

若无法下载,先拔外设VIN,按住BOOT再按RESET进ROM恢复;不关欠压检测,不先擦全部Flash。保存原日志与接线配置,坏周期不混进节能平均。下一篇L06固定外设供电,比较每次联网与攒批上传,把不同机制的收益分别测。

BACK TO THE SOURCE

资料来源与版本说明

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

  1. TI TPS22919 SLVSEN5B:ON/QOD/电气表与功能
  2. TPS22919EVM SLVUBH0:PSIL050图1及§4.1端子/跳帽
  3. IDF5.5.5 GPIO:DISABLE/FLOATING实现
  4. IDF5.5.5 I2C:设备和总线生命周期
  5. SHT4x固定手册:启动、FD转换与CRC
  6. PID326修订与I2C接线
  7. DevKitC-1 v1.1引脚表