Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RP2040 deep sleep required #3014

Open
jadefox10200 opened this issue Jul 25, 2022 · 8 comments
Open

RP2040 deep sleep required #3014

jadefox10200 opened this issue Jul 25, 2022 · 8 comments
Labels
enhancement New feature or request rp2040 RP2040 (Pi Pico, RP2040 Feather, etc)

Comments

@jadefox10200
Copy link

I am building a GPS tracker using the AI Thinker A9G board which uses GSM and GPS. I am using a Pico RP2040 as the controller to run the A9G via the UART with AT commands. Because it must last in the field for at least 2 months, I must be able to put the Pico to sleep as I am using a small rechargable battery. I am open to using other boards that would serve a similar purpose but I must be able to consume as little power as possible and wake up periodically to then execute a code block and go back to sleep.

I am a traditional Go web programmer so I'm not very familiar with how to make a library for a board. From everything I've read so far, deep sleep simply hasn't been implemented. I am willing to do whatever it takes as this project simply won't work unless I am able to put the board to sleep. I have everything else working except this one feature.

Any direction or advice would be awesome and I'd love to get this feature added which I will of course contribute to.

@deadprogram deadprogram added enhancement New feature or request rp2040 RP2040 (Pi Pico, RP2040 Feather, etc) labels Jul 25, 2022
@soypat
Copy link
Contributor

soypat commented Jul 27, 2022

@aykevl @deadprogram Be a good time to define the API for deep sleep. I believe this PR implements the desired functionality #2276

@soypat
Copy link
Contributor

soypat commented Jul 27, 2022

@jadefox10200 If you want to use the RTC for deep sleep control you may need to implement it yourself. The function Sleep as implemented waits for an interrupt. It is based on this function from the pico-extras library.

The function that uses an RTC is this one

Let me know if you need any help! In case you are not already on the slack, I strongly suggest joining- the tinygo community is very active on there and answers most questions instantly :) https://invite.slack.golangbridge.org/

@jadefox10200
Copy link
Author

If you want to use the RTC for deep sleep control you may need to implement it yourself

@soypat Thank you for the answer and yes, I've come the conclusion that I will most likely need to implement this myselt. As stated, I am willing to do that work as it will determine the success of this project. However, I've mostly done backend web development. The only lower level implementation that I've done is working with printers and some cryptography. I am very unfamiliar with where to start when it comes to implementing a lower level library that would control the Pico board. I'll check out the slack link for sure but any further direction you could give me on how these things work would be a huge help.

@soypat
Copy link
Contributor

soypat commented Jul 27, 2022

Note: The RP2040 is the MCU aboard the Raspberry Pi Pico.

So I started my MCU programming career by implementing the pico's I2C, SPI, PWM interfaces last year. The way I went about it was by reading the pico-sdk source code and translating it into Go. Some things are slightly different between the two but you'll see they share more similarities than differences.

Like I said earlier, you'll have to translate this function to Go. My guess is that the function will have the form

func SleepUntil(t time.Time) error

And will use the crystal oscillator as the default Dormant source (XOSC). I strongly suggest reading parts of the datasheet while you do this work so you get a feel for what you are really doing. The datasheet is very well written compared to most MCU datasheets.

FYI the clocks_hw value in the pico-sdk refers to the clock variable in the machine package. All of the baremetal code for the RP2040 is inside the machine package under files with the machine_rp2040 prefix. You may fork this repo and make changes on the dev branch.

@jadefox10200
Copy link
Author

@soypat awesome. Thank you for the direction. I'll get to work and hopefully be able to get this working. I have a bunch of code to still write on the main project to get the Raspberry Pi Pico talking to my A9G and sending all of the data correctly but will fit this in as well as it's pretty critical to the project.

@aykevl
Copy link
Member

aykevl commented Jul 31, 2022

Be a good time to define the API for deep sleep.

There are many kinds of "deep sleep". Making an API that works for all is very difficult. Especially as it depends on what the application wants. Therefore, if we are going to have one, we have to define what "deep sleep" really means.

  • Some very good chips (like the nrf series) can go to a very low power state (just a few µA) with RAM retention and waking up from a low-power RTC.
  • Other chips need to use a special feature to go into deep sleep mode, the default wfe might not be good enough.
  • Some limited chips (like the esp32) can't really "sleep" with low power - they can disable the core and memory and reset after a predefined time.

Especially the distinction between waking up with or without RAM is important, because it greatly affects how you write software.

I don't know where the RP2040 falls in this regard. I hope its default sleep is low-power but I haven't measured it.

But back to the original question: how to design a low-power board?
From what I've read and in my experience, the best way to do this is by turning all features off until you reach your desired current consumption, and then slowly turn things back on while keeping an eye on current consumption. For example, you can go into a kind of deep sleep on all Cortex-M chips using this:

import "runtime/interrupt"
import "device"

func main() {
    interrupt.Disable()
    for {
        device.Asm("wfe")
    }
}

If this doesn't get you low enough, you need to look into where this power is going. Make sure all peripherals are disabled. For example, disable the serial connection by compiling with -serial=none. Also, use a bare bones board without USB-serial chip or voltage regulator, and especially without a power LED (they are very power hungry!). Only once you get below 10µA or so you know that low-power sleep is working and you can slowly turn things back on again.

@kenbell
Copy link
Member

kenbell commented Jul 31, 2022

The "wfe" sleep on RP2040 could be made more efficient - there's registers to disable and reenable peripherals seemlessly (no glitches) on 'wfe / wfi' - but in RP2040 the only way to get real low-power is the 'Dormant mode'.

I've evolved @soypat 's implementation here: #3037

We run of the external XTAL, so the docs are very clear that the PLLs 'must' be disabled, so going dormant involves a whole bunch of clock re-configuration. I can get sub 1mA current draw on a 'feather-like' board.

I think there's two things here:
1 - We really need an abstraction for 'most other' chips than nrf, where wfe isn't enough, and there are 1 (or more) explicit low-power modes.
2 - For RP2040 we've got some code that's useful - can we (should we?) get that improved and into dev on the understanding it's not 'the abstraction', but is a useful capability on RP2040.

On the abstraction front, there's a whole bunch of variability. I'd suggest we model 'wake-up sources', 'sleep depth' and 'powered peripherals' orthogonally. For wake-up sources, the two most common seem to be:

  • Wake on GPIO Interrupt
  • Wake on RTC alarm

For sleep depth, I'd suggest we pick some sensible semantics, like:

  • Super-Light:
    • Preserve execution state (i.e. sleep will return)
    • Keep all peripherals powered (i.e. equivalent to wfe)
    • Scheduler runs
    • All configured interrupts active
    • Channels can be used to communicate from ISRs to other go routines
    • Time continues
  • Light:
    • Preserve execution state (i.e. sleep will return)
    • Keep all peripherals powered (i.e. equivalent to wfe)
    • Stop the scheduler (no go routines will run even if eligible)
    • All Interrupts other than nominated 'wake' interrupts are masked
    • Time may be paused
  • Medium:
    • Deepest sleep state that still preserves execution state (i.e. sleep will return)
    • Scheduler will not run, only wake interrupts will be active
    • Requested powered peripherals will remain powered during sleep (where possible all others powered off)
    • Time may be paused
  • Deep:
    • Execution state is not preserved (i.e. sleep will not return), the app is re-started on wake
    • Scheduler will not run, only wake interrupts will be active
    • Requested powered peripherals will remain powered during sleep
    • Time is reset on wake (equivalent to 'RESET')

For RP2040 implementation could be something like this:

Mode Implementation
Super-Light time.Sleep()
Light wfe+interrupt masking(mask off timer)
Medium Dormant Mode
Deep Dormant Mode + reset on wake

For Powered Peripherals, do basic groups?

  • GPIO
  • RTC
  • ADC
  • USB
  • etc

Something unique to RP2040 like PIO would map to GPIO?

I'm not sure my specific sleep depth definitions make sense - should be based on depths that will make sense in real-world apps.

@kenbell
Copy link
Member

kenbell commented Aug 1, 2022

For context, here's a chart of power consumption on the Challenger RP2040 LoRa from the battery connection:
image

Selected range (in gray) is during wfe for 1 sec, with average 21.25mA consumption.

Either side is dormant mode, averaging 870uA:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request rp2040 RP2040 (Pi Pico, RP2040 Feather, etc)
Projects
None yet
Development

No branches or pull requests

5 participants