tinygo
tinygo copied to clipboard
RP2040 deep sleep required
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.
@aykevl @deadprogram Be a good time to define the API for deep sleep. I believe this PR implements the desired functionality https://github.com/tinygo-org/tinygo/pull/2276
@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/
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.
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.
@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.
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.
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: https://github.com/tinygo-org/tinygo/pull/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.
For context, here's a chart of power consumption on the Challenger RP2040 LoRa from the battery connection:
Selected range (in gray) is during wfe
for 1 sec, with average 21.25mA consumption.
Either side is dormant mode, averaging 870uA: