MicroPython Deep Sleep on ESP32: Timer, Button and Touch Wake-Up
Deep sleep lets an ESP32 shut down much of its active circuitry between tasks. A sensor node can boot, take a measurement, send a short report, and sleep until the next timer or supported external event. It is useful for periodic battery-powered projects, but it changes the program model: waking from deep sleep generally starts execution again from the beginning rather than resuming the line after the sleep call.
ESP32 variants differ. A wake source supported by the original ESP32 may not be available in the same way on an ESP32-C3, S2, S3, or another family member. Check the exact chip and MicroPython build before wiring a button or touch electrode. This guide covers timer sleep, wake-source checks, persistent state, sensor-node design, current measurement, and troubleshooting.
1. Understand the power state
Deep sleep disables much of the processor's active circuitry. Depending on chip family and configuration, selected RTC-domain resources may remain available. Actual current depends on the module and the whole board: voltage regulator, USB-to-serial bridge, LEDs, sensors, and attached circuitry can consume more than the sleeping chip.
Use deep sleep when a project can tolerate restarting after each wake. It suits periodic environmental measurements and occasional telemetry, but not a system that needs uninterrupted real-time control or a continuously active network connection. Define the required wake interval, response time, retained data, and battery target before selecting a wake source.
A development board powered by USB is not a reliable way to estimate final battery current. Some board components remain powered during sleep. Measure the complete device using the same regulator and peripherals planned for deployment.
2. Verify the chip and firmware
Identify the ESP32 family from the module marking or board documentation, and install a MicroPython firmware build for that target. APIs and supported pins vary across chips and releases. You can inspect some exposed names at runtime:
import machine
print([name for name in dir(machine) if "wake" in name.lower() or "sleep" in name.lower()])
This is only a diagnostic clue; an API may be exposed on a pin object or have target-specific limitations. Read the official MicroPython documentation for your exact firmware. Keep a local copy of scripts and know how to reset or reflash the board before testing long sleep intervals.
3. Timer-based deep sleep
Start with a short timer interval so the board does not appear dead while you debug:
import machine
import time
print("Starting a short sleep test")
time.sleep(1)
machine.deepsleep(10_000) # commonly milliseconds on MicroPython ESP32
Confirm the time unit for your installed build. After timer wake, the program normally boots from the beginning, so the print message should appear again. The call is not a normal pause that continues to the next statement.
Once a short test works, choose the interval based on how often the application needs data. Deep sleep is not a precision clock; drift and wake latency depend on the chip and clock source. Use a suitable real-time clock or network time synchronization if accurate timestamps matter.
4. Inspect the wake reason
Some firmware builds expose a wake-reason API; availability and constants differ. Check before calling it:
import machine
if hasattr(machine, "wake_reason"):
try:
print("Wake reason:", machine.wake_reason())
except Exception as exc:
print("Wake reason unavailable:", exc)
else:
print("Wake-reason API is not exposed by this firmware")
Use documented constants to interpret the returned value. Do not assume a numeric result has the same meaning on every ESP32 variant. A reset-cause API and a wake-reason API may describe different events, including power-on, external reset, watchdog reset, and deep-sleep wake.
5. Preserve small pieces of state
Ordinary Python variables do not survive a deep-sleep restart. Depending on the chip and firmware, you may be able to use RTC memory or a file in flash. File storage is easy to understand, but frequent writes add flash activity and an interrupted write can corrupt the file.
import json
STATE_FILE = "state.json"
def load_count():
try:
with open(STATE_FILE, "r") as f:
return int(json.load(f).get("count", 0))
except (OSError, ValueError, TypeError):
return 0
def save_count(count):
with open(STATE_FILE, "w") as f:
json.dump({"count": count}, f)
count = load_count() + 1
save_count(count)
print("Wake cycle:", count)
Treat this as a demonstration, not transactional storage. Back up important data and consider write frequency, recovery from malformed files, and whether state can be reconstructed remotely. RTC memory is not the same as permanent storage and may be lost when power is removed.
6. Button wake-up: verify hardware support
External wake-up support depends on the ESP32 family, supported pins, wake mode, and firmware API. Some targets require RTC-capable GPIOs or a specific low-level wake configuration. Do not copy a GPIO number from an unrelated tutorial and assume it works.
Consult the official MicroPython documentation and chip technical reference manual for supported pins, polarity, pull configuration, and power-domain requirements. A button circuit commonly uses a defined pull-up or pull-down rather than leaving the input floating, but the correct circuit depends on the documented wake method. First test the button as a normal input while awake and confirm the idle and pressed logic levels. Then configure deep-sleep wake according to the target's documented API.
If the device wakes immediately, check whether the button is held, the pin is already at its active level, or the polarity is wrong. Remove external wiring and verify timer wake separately. Do not apply unknown voltages to wake pins.
7. Touch wake-up and variant differences
Capacitive touch hardware is not available on every ESP32 family, and touch-capable pins and APIs can differ. The original ESP32 has touch hardware on specific pins; newer chips may use different controllers or firmware support. Confirm that the exact target supports touch wake before designing the circuit.
If supported, test the documented touch API while the processor is awake. Measure a baseline without contact and observe how it changes when the electrode is touched. Thresholds depend on electrode size, board layout, enclosure, humidity, grounding, and wire length, so calibrate on the actual assembly instead of copying a threshold blindly.
Use only documented touch-capable pads and follow the chip manufacturer's layout advice. Keep the electrode away from noisy switching circuits and never connect it to mains wiring or an unknown voltage. If the target lacks a suitable API, use a supported GPIO wake method or timer wake rather than experimenting with random pins.
8. Design a periodic sensor node
A practical sensor node should boot, initialize only required peripherals, read and validate its sensor, connect to Wi-Fi if a report is needed, send a compact message, save essential state, and sleep. Use timeouts and bounded retries. A device in a weak-signal area can drain its battery if it retries forever, so it should have a failure path that still reaches sleep.
Do not initialize a display or sensor if that device is not needed for the current measurement. Peripherals can continue drawing current while the ESP32 sleeps, and signal pins can sometimes back-power an unpowered module. Power-switch peripherals only when the design permits it and ensure that voltage levels remain safe.
For telemetry, include a device identifier and timestamp where appropriate. The receiver should tolerate duplicate or delayed messages. Measure the entire cycle: sensor warm-up, Wi-Fi association, transmission bursts, failed retries, and regulator quiescent current can dominate average power even when the sleep interval is long.
9. Measure real power consumption
A multimeter can help measure steady current, but use the correct current jack and range and place the meter in series with the supply. Never connect a current meter directly across a battery or supply, because that creates a short circuit. Brief radio current peaks may be too fast for an ordinary meter; a current profiler or suitable sense instrument may be needed.
Measure active operation, radio transmission if used, and sleep with the same board, regulator, USB bridge, sensors, and wiring intended for deployment. A rough average-current estimate for a repeating cycle is:
I_avg ≈ (I_active × t_active + I_sleep × t_sleep) / (t_active + t_sleep)
Use consistent units. Include startup time, reconnection delays, failed attempts, regulator losses, and peripheral current. Battery capacity also changes with load, temperature, age, and cutoff voltage, so capacity divided by a chip datasheet's sleep-current figure is not a reliable full-system lifetime prediction.
10. Troubleshooting
The board seems dead: wait for the configured timer or trigger the intended wake source, then reset if necessary. A short interval is easier to debug. Immediate wake: check the button's idle level, polarity, pull configuration, and whether the input is already active. Missing API: verify firmware and target support in the official documentation. High current: measure the complete board, disconnect USB, and inspect LEDs, regulators, sensors, and back-power paths. Lost variables: ordinary Python variables are recreated at boot; use a supported retention feature or deliberate storage. Wi-Fi failure: use bounded retries and a timeout, and ensure the board and firmware support wireless operation.
If the program enters an inconvenient sleep cycle, use the board's documented reset/recovery method and shorten the interval for the next test. Keep scripts backed up locally. Disconnect battery power before changing wiring, and never assume deep sleep makes an unsafe circuit safe.
Final checklist and next steps
- Confirmed the exact ESP32 variant and matching MicroPython build.
- Tested timer sleep with a short interval.
- Accounted for the restart-from-beginning behaviour after wake.
- Checked documented wake-reason constants rather than guessing numeric values.
- Used only a supported GPIO or touch source.
- Added timeouts and bounded retries to network tasks.
- Chosen state storage deliberately and kept local backups.
- Measured active, transmit, and sleep current on the complete board.
Consult the official MicroPython ESP32 documentation for machine.deepsleep, wake and reset APIs, and variant-specific limitations. Use the Espressif technical reference manual for the exact chip to confirm pin and wake-source restrictions. These sources take precedence over examples written for another board or firmware release.
SolderHub takeaway: begin with timer wake-up, keep each wake cycle short and bounded, measure the complete board, and add GPIO or touch wake only after verifying that the exact chip supports it. That makes battery-saving behaviour more predictable and easier to debug.
Deployment validation and maintenance
Before deploying a battery-powered sensor node, keep a test log with the exact board model, firmware version, supply method, peripherals, wake interval, and measured current. Change one variable at a time and repeat multiple cycles. A single successful wake does not prove that a button input is noise-resistant or that a network retry path is bounded.
Test failure conditions deliberately. A sensor may return invalid data, an access point may be unavailable, or a button may remain pressed. Give sensor and network operations deadlines, record a compact status when useful, and proceed to sleep when the design permits it. Avoid writing to flash on every rapid retry, because repeated writes add power cost and contribute to flash wear.
Measure the complete assembly rather than the ESP32 chip alone. Regulators, power LEDs, USB bridges, sensor boards, and level shifters can continue drawing current during sleep. Measure active and sleep periods separately, then estimate average current over a full cycle. Include radio bursts, reconnect attempts, sensor warm-up, battery temperature, ageing, and margin for board variation. A datasheet sleep figure is not a guarantee of whole-product battery life.
For button or touch wake, test with the final cable length and enclosure. Long wires, moisture, noise, and electrode placement can change input behaviour. Keep a local source-code backup and document how to reset and reflash the board before placing it somewhere difficult to reach. Start with short sleep intervals during development and increase them only after the wake source and recovery path are reliable.
Choosing the wake interval
Set the interval from the real sampling requirement, not simply the longest interval that seems to save power. Fast-changing measurements need more frequent samples, while slowly changing environmental readings may tolerate longer gaps. Consider what happens if a wake is missed: does the device skip one sample, retry once, or remain awake until a deadline? Make that behaviour explicit and test it.
If the ESP32 uses Wi-Fi, account for the energy and time needed to associate with the access point and transmit. In some projects a longer sleep interval reduces average current, but only if the active work remains short and retries are bounded. If a sensor needs a warm-up period, include that in the active-time measurement. Recalculate after changing the board, regulator, sensor, or network environment.