Awaking the Raspberry Pico from deep sleep
The Raspberry Pico was released in Jan 2021, and retails for around $4. It has a Dual-core Arm Cortex-M0+ processor (RP2040), flexible clock running up to 133 MHz and 264KB internal RAM.
This is a short overview of how you might reduce the power consumption of your Pico to a very low level, around 1.3mA at 25c according to the official datasheet.
This is useful for periodically performing some work, consuming minimal power whilst we are sleeping.
Sleeping is fairly simple, but there were a few extra steps required to bring the Pico back to full functionality once it’s awake.
Choosing a sleep mode
There appear to be two different sleep modes, a dormant
mode and a sleep
mode. Read more about them from a programming perspective here in the 2.11.5. Programmer’s Model
section.
The dormant mode uses even less power 0.8mA at 25c, however it appears to require the use of an external trigger to bring the Pico back out of sleep.
To use dormant mode, they use the following function in their example:
// Go to sleep until we see a high edge on GPIO 10
sleep_goto_dormant_until_edge_high(10);
If you want to purely rely on the internal clock of the Pico to trigger it to awaken, we’re going to have to use the sleep
mode, using slightly more power.
Example sleep code
Lets take a look then at the sleep example from the pico-playground:
int main() {
stdio_init_all();
printf("Hello Sleep!\n");
printf("Switching to XOSC\n");
// Wait for the fifo to be drained so we get reliable output
uart_default_tx_wait_blocking();
// UART will be reconfigured by sleep_run_from_xosc
sleep_run_from_xosc();
printf("Switched to XOSC\n");
awake = false;
//At this point the pico will sleep
rtc_sleep();
//We won't hit this part of the code until the
//time set in rtc_sleep() is reached.
// Make sure we don't wake
while (!awake) {
printf("Should be sleeping\n");
}
return 0;
}
The above makes a call to rtc_sleep()
which sets everything up and then sleeps for 10 seconds:
static void rtc_sleep(void) {
// Start on Friday 5th of June 2020 15:45:00
datetime_t t = {
.year = 2020,
.month = 06,
.day = 05,
.dotw = 5, // 0 is Sunday, so 5 is Friday
.hour = 15,
.min = 45,
.sec = 00
};
// Alarm 10 seconds later
datetime_t t_alarm = {
.year = 2020,
.month = 06,
.day = 05,
.dotw = 5, // 0 is Sunday, so 5 is Friday
.hour = 15,
.min = 45,
.sec = 10
};
// Start the RTC
rtc_init();
rtc_set_datetime(&t);
printf("Sleeping for 10 seconds\n");
uart_default_tx_wait_blocking();
sleep_goto_sleep_until(&t_alarm, &sleep_callback);
}
All fairly straight forward, and if you run this it works as expected. This line:
printf("Should be sleeping\n");
Is never printed as by the time we reach this point in the code, the sleep callback is triggered on awaking, setting awake
to true:
static void sleep_callback(void) {
printf("RTC woke us up\n");
awake = true;
}
Fantastic! All sorted?
Unfortunately this is not the whole story, we don’t wake up in a great state. Though at first everything seems fine.
Lets say we expand on the code in the example above, and we try to sleep again using the regular sleep_ms
method the Pico SDK provides:
//We won't hit this part of the code until the
//time set in rtc_sleep() is reached.
// Make sure we don't wake
while (!awake) {
printf("Should be sleeping\n");
}
printf("switch on the onboard LED\n");
uart_default_tx_wait_blocking();
gpio_put(LED_PIN, 1);
printf("Sleep from sleep_ms\n");
uart_default_tx_wait_blocking();
//We hang at this point forever
sleep_ms(2000);
printf("Switch off LED\n");
uart_default_tx_wait_blocking();
gpio_put(LED_PIN, 0);
return 0;
What happens is we hang at the sleep_ms
command.
What’s going on here?
Lets follow through the function calls rtc_sleep
is making and see what’s being changed to allow us to enter this deep sleep state.
One of the functions we call is sleep_run_from_xosc()
, this sets up the Pico to run from the internal Crystal Oscillator.
Following that through we end up in this area of the code:
// CLK SYS = CLK_REF
clock_configure(clk_sys,
CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF,
0, // Using glitchless mux
src_hz,
src_hz);
// CLK USB = 0MHz
clock_stop(clk_usb);
// CLK ADC = 0MHz
clock_stop(clk_adc);
So it seems we’re reconfiguring the system clock, and stopping a few of the other clocks. Could that be why we’re not able to sleep correctly later? Thankfully an example in the SDK docs gives us a away to check what the internal clocks are set to.
We can print these values out before and after sleeping, and see how it looks.
Disabled clocks
Before sleeping:
pll_sys = 125001kHz
pll_usb = 48000kHz
rosc = 4689kHz
clk_sys = 125000kHz
clk_peri = 125000kHz
clk_usb = 48000kHz
clk_adc = 48000kHz
clk_rtc = 47kHz
After sleeping:
pll_sys = 0kHz
pll_usb = 0kHz
rosc = 0kHz
clk_sys = 12000kHz
clk_peri = 12000kHz
clk_usb = 0kHz
clk_adc = 0kHz
clk_rtc = 47kHz
That’s probably not helping. There is actually a clocks_init
function provided by the SDK, however when trying to call that after sleeping, it will just hang.
By creating a new function, and picking out some of the commands from clocks_init
, some of the clocks could be reset.
pll_sys = 125000kHz
pll_usb = 48000kHz
rosc = 0kHz
clk_sys = 12000kHz
clk_peri = 12000kHz
clk_usb = 48000kHz
clk_adc = 48000kHz
clk_rtc = 47kHz
However, trying to reset the system clock would seem to hang:
clock_configure(clk_sys,
CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
125 * MHZ,
125 * MHZ);
As the ring oscillator is still disabled, lets see if we can bring that back to a good state. It’s being disabled in this part of the code. Which does the following:
void rosc_disable(void) {
uint32_t tmp = rosc_hw->ctrl;
tmp &= (~ROSC_CTRL_ENABLE_BITS);
tmp |= (ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB);
rosc_write(&rosc_hw->ctrl, tmp);
// Wait for stable to go away
while(rosc_hw->status & ROSC_STATUS_STABLE_BITS);
}
As a side note, the SDK has an interesting note around disabling the ring oscillator before switching the system clock to run from another source.
The system clock must be switched to another source before
setting this field to DISABLE otherwise the chip will lock up
Lets try and re-enable the ROSC:
rosc_write(&rosc_hw->ctrl, ROSC_CTRL_ENABLE_BITS);
At this point we can then run clocks_init
and it no longer hangs, the clocks are then back to a similar state before sleeping, great!
pll_sys = 125000kHz
pll_usb = 48000kHz
rosc = 4675kHz
clk_sys = 125000kHz
clk_peri = 125000kHz
clk_usb = 48000kHz
clk_adc = 48000kHz
clk_rtc = 47kHz
We’re still hanging
Unfortunately though, we’re still hanging if we try and call sleep_ms after waking. Let’s go deeper into the code to see what happens when we actually sleep.
sleep_goto_sleep_until(&t_alarm, &sleep_callback);
In this function, all the clocks are being disabled apart from the Real Time Clock (we need this to wake us up):
// Turn off all clocks when in sleep mode except for RTC
clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS;
clocks_hw->sleep_en1 = 0x0;
We’re then enabling deep sleeping on the processor:
uint save = scb_hw->scr;
// Enable deep sleep at the proc
scb_hw->scr = save | M0PLUS_SCR_SLEEPDEEP_BITS;
So it’s likely we’re going to need to reverse these settings as well to bring everything back to the pre-sleep state. Lets create a function to bring everything back to the good state for us:
void recover_from_sleep(uint scb_orig, uint clock0_orig, uint clock1_orig){
//Re-enable ring Oscillator control
rosc_write(&rosc_hw->ctrl, ROSC_CTRL_ENABLE_BITS);
//reset procs back to default
scb_hw->scr = scb_orig;
clocks_hw->sleep_en0 = clock0_orig;
clocks_hw->sleep_en1 = clock1_orig;
//reset clocks
clocks_init();
stdio_init_all();
return;
}
Bringing it all together then we end up with the following:
int main() {
const uint LED_PIN = 25;
stdio_init_all();
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
//save values for later
uint scb_orig = scb_hw->scr;
uint clock0_orig = clocks_hw->sleep_en0;
uint clock1_orig = clocks_hw->sleep_en1;
printf("Hello Sleep!\n");
printf("Switching to XOSC\n");
// Wait for the fifo to be drained so we get reliable output
uart_default_tx_wait_blocking();
// UART will be reconfigured by sleep_run_from_xosc
sleep_run_from_xosc();
printf("Switched to XOSC\n");
awake = false;
//At this point the pico will sleep
rtc_sleep();
//We won't hit this part of the code until the
//time set in rtc_sleep() is reached.
// Make sure we don't wake
while (!awake) {
printf("Should be sleeping\n");
}
//reset processor and clocks back to defaults
recover_from_sleep(scb_orig, clock0_orig, clock1_orig);
printf("switch on the onboard LED\n");
uart_default_tx_wait_blocking();
gpio_put(LED_PIN, 1);
//We no longer hang here!
sleep_ms(2000);
printf("Switch off LED\n");
uart_default_tx_wait_blocking();
gpio_put(LED_PIN, 0);
return 0;
}
Finally we no longer hang when calling sleep_ms
after waking.
Full example with build instructions can be seen here.
Hopefully this has been somewhat interesting and perhaps will help someone else to wake their Pico after deep sleeping.