Skip to main content

USB Power Loss Handling

The ESP32 runs from USB 5 V only — there is no battery-backed power supply. When USB power is removed, the ESP32 shuts down. A separate discrete backup circuit (ATtiny85 + AA batteries + piezo buzzer) handles the alarm independently without the ESP32 involved.

The ESP32's firmware role on power loss is limited to:

  1. Detecting that USB power has disappeared
  2. Disabling WiFi gracefully
  3. Updating the display with a power-loss indicator
  4. Ensuring the DS3231 alarm registers are current so the ATtiny fires at the right time

See the battery backup wiring guide for the hardware setup, and the battery backup concepts page for the design rationale.


Detecting Power Loss: GPIO41 (USB_PRESENT)

GPIO41 is connected via a 100 kΩ / 100 kΩ voltage divider to the USB-C 5 V rail. It reads HIGH (~2.5 V) when USB is connected and LOW (0 V) when USB is absent.

#define USB_PRESENT_GPIO GPIO_NUM_41

static void usb_present_gpio_init(void) {
gpio_config_t cfg = {
.pin_bit_mask = (1ULL << USB_PRESENT_GPIO),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&cfg);
}

bool power_is_usb_present(void) {
return gpio_get_level(USB_PRESENT_GPIO) == 1;
}

Poll power_is_usb_present() in the power_monitor task every few seconds. Transition detection does not require a hardware interrupt.


State Machine Events

Add to the event_type_t enum:

EVT_POWER_LOSS // USB-present pin went LOW
EVT_POWER_RESTORED // USB-present pin went HIGH

Handle in state_machine_step():

  • EVT_POWER_LOSS from STATE_IDLE: call battery_mode_enter()
  • EVT_POWER_RESTORED from STATE_IDLE: call battery_mode_exit()
  • During STATE_ALARM_FIRING: the alarm was already fired by the ESP32 (USB was still present when it triggered). Complete the alarm normally.

Entering Power-Loss Mode

static void battery_mode_enter(void) {
ESP_LOGI(TAG, "USB power lost — disabling WiFi, updating display");

// 1. Disable WiFi — saves 200–300 mA and avoids spurious NTP updates
esp_wifi_stop();
esp_wifi_deinit();

// 2. Show power-loss indicator on clock face
display_set_flag(DISPLAY_FLAG_POWER_LOSS);
display_request_refresh();

// The ATtiny backup circuit will handle the alarm at the DS3231 alarm time.
// No further action needed from the ESP32 — it will go dark when USB is removed.
}

The ESP32 does not enter light sleep, does not set up DS3231 GPIO interrupts, and does not attempt to play audio. The ATtiny handles all of that independently.


Keeping DS3231 Alarm Registers Current

The ATtiny firmware reads the DS3231 SQW output but never communicates with the DS3231 over I2C — it simply wakes when SQW goes LOW. This means the ESP32 must write the correct alarm time into the DS3231 Alarm 1 registers eagerly, not lazily.

Write the DS3231 alarm registers every time an alarm is:

  • Created
  • Modified
  • Dismissed (write the next occurrence time immediately)
  • On every boot, to re-sync after any gap
// Call this whenever an alarm is set or modified:
void alarm_sync_to_rtc(const alarm_t *alarm) {
// Write next fire time into DS3231 Alarm 1 registers
rtc_set_alarm1(alarm->hour, alarm->minute);
ESP_LOGD(TAG, "DS3231 Alarm 1 set to %02d:%02d", alarm->hour, alarm->minute);
}

If the DS3231 alarm registers are stale when USB power is lost, the ATtiny will fire at the wrong time (or not at all). Keeping the registers current is the single most important firmware responsibility for the backup circuit to work correctly.


Exiting Power-Loss Mode (USB Restored)

static void battery_mode_exit(void) {
ESP_LOGI(TAG, "USB power restored — re-enabling WiFi");

// 1. Reinit WiFi — ntp_task reconnects and syncs automatically
wifi_init_sta_from_nvs();

// 2. Remove power-loss display flag
display_clear_flag(DISPLAY_FLAG_POWER_LOSS);
display_request_refresh();
}

Startup Without USB

If USB power is absent at boot (e.g. the ESP32 briefly brownout-reset during an outage), enter power-loss mode immediately after driver initialisation:

// In app_main(), after all drivers are initialised:
usb_present_gpio_init();
if (!power_is_usb_present()) {
ESP_LOGI(TAG, "Booted without USB — entering power-loss mode");
battery_mode_enter();
}

The ATtiny handles the alarm regardless. The ESP32 showing a power-loss indicator on the display is a best-effort — if USB drops immediately the display may not refresh before the ESP32 goes dark.


Testing Power Loss Handling

  1. With USB connected, confirm serial shows USB present: 1 from the power_monitor task.
  2. Set an alarm 2 minutes in the future (to ensure DS3231 alarm registers are written).
  3. Unplug USB — serial should show USB power lost, WiFi disconnect logs, and display refresh with power-loss indicator.
  4. Wait for the alarm time — the ATtiny backup circuit fires the buzzer. The ESP32 serial monitor is silent (it is off).
  5. Press SNOOZE — buzzer stops, refires after ~9 minutes (ATtiny internal timer).
  6. Reconnect USB — serial should show USB power restored and NTP sync. Clear the alarm.

See Also