Search

Sucking Power in Sleep Mode

How I solved a bizarre ESP32 power leak that was draining the battery while sleeping.

Admin3 min read
Sucking Power in Sleep Mode

I am a mechanical product designer by trade. My days are usually spent managing CAD assembly tolerances, researching plastic injection molding rules, and tuning tactile button feel. But with Click, our open source physical companion, I decided to step completely out of my comfort zone. I took on designing my first prototype PCB in KiCad and committed to a unique workflow: using prompt-based AI for 100% of the firmware generation.

Click is an evolving workspace where the community can join, test, and adapt the concept alongside me. And during our early prototype verification phase, I ran straight into my first major hardware debugging wall: the sleeping power leak.

The Standby Battery Anomaly

Our initial power budget is centered around a single-cell 1200mAh LiPo pouch battery running an ESP32-WROOM microcontroller and a standard monochrome I2C OLED screen. Awake, the system performed beautifully, drawing a stable average of about 48mA while cycling through screen animations.

But when I triggered the light sleep mode to shut down the display and rest the core, my multimeter showed a massive spike: the current jumped to 58mA. The device was burning more power when it was supposed to be sleeping than when it was awake. If I left it like this, our 1200mAh battery would drain in less than 24 hours just sitting idle on a desk, completely ruining the goal of a low-profile pocket companion designed to last weeks on a single charge.

Finding the Leak with AI

Since I do not write firmware from scratch, I treated the bug like a system architecture puzzle. I recorded the power rails, mapped out our hardware pins, and fed the entire system state back to my virtual architect.

The diagnostic was fast. The issue was not the microcontroller sleeping, but the I2C lines: GPIO21 and GPIO22. When the ESP32 entered sleep, the I2C lines were left floating in an undefined state. This created a low-resistance path that back-powered the display’s internal controller, causing current to bleed directly through the display and pull power from our battery.

The Non-Blocking Code Solution

To fix it, we patched the standby routine to explicitly handle the physical bus states during transition:

  1. Pull-Up State Enforcement: We explicitly set both I2C lines to a high-impedance input pull-up state right before sleep. This holds the lines at 3.3V, matching the system logic level and stopping any leakage.
  2. Display Wake Recovery: To prevent the display from freezing when the system wakes up, we programmed a quick, non-blocking hardware reset pulse that toggles the display’s reset line low for 100 microseconds.

Because Click’s entire loop is designed to be completely non-blocking, we avoided using any delay functions in our state machines. Instead, the timing is managed entirely through millis, keeping the buttons responsive and the transitions smooth.

The Result

The moment we compiled and flashed the updated firmware, the multimeter numbers dropped immediately. Sleep current plummeted from 58mA to exactly 1.5mA.

On our 1200mAh battery, a 1.5mA standby draw pushes our battery life up to 26 days of standby time. It was an incredibly rewarding moment, seeing real-world physics respond directly to targeted code.