bajo-consumo-deep-sleep-nanoframework

Deep Sleep and Low Power in nanoFramework

  • 5 min

Low power in nanoFramework is the set of techniques to reduce the energy consumed by the device when it doesn’t need to be active.

Our device already connects to Wi-Fi, reads sensors, and saves data, but all these functions have a cost: power consumption.

But it has a serious problem: Power consumption. An ESP32 with Wi-Fi enabled consumes between 150mA and 240mA. If you connect it to a standard 2000mAh battery, it will drain in less than 10 hours.

To create devices that last months or years, we need the chip to spend 99% of its life “dead” and only wake up for a few seconds to work. This is called Deep Sleep.

In this post, we’ll learn how to use nanoFramework’s power APIs to put our little friend to sleep and wake it up only when necessary.

For this tutorial, you need to install the NuGet package specific to your hardware. For ESP32 it’s: nanoFramework.Hardware.Esp32

Difference between Thread.Sleep and Deep Sleep

It’s important not to confuse these two concepts:

  1. Thread.Sleep(1000): suspends the thread, but the system remains active and can execute other threads.
  2. Deep Sleep: turns off the CPU and radio, loses normal RAM, and only maintains the domains necessary for waking up.

The difference can be enormous, although a complete development board usually consumes much more than the isolated chip due to the regulator, USB converter, and LEDs.

Deep Sleep Lifecycle

When waking from deep sleep, the program starts again from Main().

When the ESP32 wakes up:

  1. It does NOT continue from the code line after where it slept.
  2. It DOES start from the beginning of Main().
  3. All RAM variables have been lost.

Therefore, the structure of a low-power program is different from an infinite loop.

public static void Main()
{
    // 1. Initialize hardware
    // 2. Read sensors
    // 3. Connect Wi-Fi and send data
    // 4. Configure the wake-up
    // 5. Go to sleep! (The program dies here)
}
Copied!

Waking Up with a Timer

This is the most typical case: “Measure temperature every 10 minutes.”

We’ll use the Sleep class from the nanoFramework.Hardware.Esp32 namespace.

using System;
using System.Diagnostics;
using System.Threading;
using nanoFramework.Hardware.Esp32; // <--- Required

public class Program
{
    public static void Main()
    {
        Debug.WriteLine($"Good morning! Time to work: {DateTime.UtcNow}");

        // --- DO YOUR WORK HERE ---
        // Read sensor...
        // Send MQTT...
        // Save to SD...
        // Simulate work with a pause
        Thread.Sleep(2000); 
        
        Debug.WriteLine("Work done. Setting alarm...");

        // Configure wake-up: In 10 seconds
        // TimeSpan defines how long we want to sleep
        Sleep.EnableWakeupByTimer(TimeSpan.FromSeconds(10));

        Debug.WriteLine("Entering Deep Sleep. See you later!");

        // Start deep sleep
        Sleep.StartDeepSleep();

        // --- THIS LINE WILL NEVER EXECUTE ---
        Debug.WriteLine("This is impossible to read.");
    }
}
Copied!

If you run this, you’ll see the device starts, works for 2 seconds, and shuts down. 10 seconds later, it prints “Good morning!” again. It’s a constant “Groundhog Day.”

Waking Up with a GPIO

Imagine a smart doorbell or an emergency button. The device must sleep indefinitely until someone presses the button.

// Configure wake-up by PIN
// Pin: GPIO 33 (For example)
// Level: SleepWakeupGpioPin.WakeupGpioPinLow (Wakes up when connected to GND)
Sleep.EnableWakeupByPin(Sleep.WakeupGpioPin.Pin33, 0);

Debug.WriteLine("Sleeping until someone presses the button on GPIO 33...");
Sleep.StartDeepSleep();
Copied!

Be careful with RTC pins: In Deep Sleep, only a few special pins (the RTC pins) can wake the chip. On the ESP32, these are usually GPIO 0, 2, 4, 12-15, 25-27, 32-39. If you try to use a regular pin, it won’t work. Check your board’s pinout.

Querying the Wake-up Cause

Sometimes your device can wake up for multiple reasons (by timer OR by button). We need to know what happened to act accordingly.

Sleep.WakeupCause cause = Sleep.GetWakeupCause();

switch (cause)
{
    case Sleep.WakeupCause.Timer:
        Debug.WriteLine("I woke up by the Timer. Time to measure temperature.");
        break;
        
    case Sleep.WakeupCause.Ext0: // GPIO
        Debug.WriteLine("Someone pressed the button! Sending alert.");
        break;
        
    case Sleep.WakeupCause.Undefined:
        Debug.WriteLine("This is the first startup (Power On / Reset).");
        break;
}
Copied!

Preserving Data Between Wake-ups

As mentioned, RAM is cleared when sleeping. If you want to count how many times you’ve woken up, you can’t use a variable int counter = 0; at the start of the program, because it will always reset to 0.

You have two options:

  1. Flash/SD: Use what we learned in the previous chapter (slow, memory wear).
  2. RTC Memory: The ESP32 has a small memory (8KB) that is NOT erased during Deep Sleep.

A simple option is to save the necessary state in a small file on the internal flash (I:\state.json). Do this sparingly to avoid wearing it out.

// Conceptual example
var state = ConfigurationManager.LoadState();
state.WakeUpCount++;
ConfigurationManager.SaveState(state);

Sleep.StartDeepSleep();
Copied!

Power Saving Strategies

For the battery to truly last, follow these rules:

  1. Work fast: The less time the chip is awake, the better.
  2. Connect Wi-Fi at the end: Don’t turn on the radio right when it starts. First, read the sensors. If the value hasn’t changed, maybe it’s not necessary to send it. Just go back to sleep and save the Wi-Fi connection!
  3. Use Static IP: DHCP takes several seconds to negotiate. Using a static IP saves radio-on time.