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: rtc deep sleep #3405

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/rtcinterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/rtcsleep
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
Expand Down
35 changes: 35 additions & 0 deletions src/examples/rtcinterrupt/rtcinterrupt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//go:build rp2040

package main

// This example demonstrates scheduling a delayed interrupt by real time clock.
//
// An interrupt may execute user callback function or used for its side effects
// like waking up from sleep or dormant states.
//
// The interrupt can be configured to repeat.
//
// There is no separate method to disable interrupt, use 0 delay for that.
//
// Unfortunately, it is not possible to use time.Duration to work with RTC directly,
// that would introduce a circular dependency between "machine" and "time" packages.

import (
"fmt"
"machine"
"time"
)

func main() {

// Schedule and enable recurring interrupt.
// The callback function is executed in the context of an interrupt handler,
// so regular restructions for this sort of code apply: no blocking, no memory allocation, etc.
delay := time.Minute + 12*time.Second
machine.RTC.SetInterrupt(uint32(delay.Seconds()), true, func() { println("Peekaboo!") })

for {
fmt.Printf("%v\r\n", time.Now().Format(time.RFC3339))
time.Sleep(1 * time.Second)
}
}
75 changes: 75 additions & 0 deletions src/examples/rtcsleep/rtcsleep.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//go:build rp2040

package main

// This example shows how to send a board to a low power mode (deep sleep) and wake it up on timer.
//
// There are 3 states the board can be in this example:
// - Base power: on-board LED is off, does not consume power (5 seconds);
// - High power: on-board LED shines, consumes power (5 seconds);
// - Low power: almost everything is off but crystal oscilator, rtc, sys and ref clocks (5 seconds).
//
// Measured power consumption for Arduino Nano RP2040 Connect board powered from 2S battery (~8.2v)
// - Base power: 13.55 mA (0.11W) bare minimum on fully powered up RP2040 chip
// - High power: 15.55 mA (0.13W) on-board LED consumes 2 mA (same for power LED, see below)
// - Deep sleep: 4.00 mA (0.03W) power LED consumes 2 mA
// 2.00 mA (0.015W) power LED removed => ~month life time, see below
//
// Life expectancy on 2S 2200 Li-Ion battery.
// 4.2v->3.2v, avg 3.7v per cell (7.4v total).
// Remaining charge ~1000mAh not to ruin the battery => 1.2Ah consumable.
//
// Expected number of hours = 3.7*2*1.2 / w, there "w" is power consumption, in Watts.
//
// High power: 68h (~3 days)
// Base power: 81h (~3.5 days)
// Deep sleep: 296h (~12 days)
// 592h (~24 days)

import (
"machine"
"time"
)

const led = machine.LED

func main() {

led.Configure(machine.PinConfig{Mode: machine.PinOutput})

// Pause and let user connect to serial console
time.Sleep(5 * time.Second)

// Fire RTC interrupt every 15 seconds: ~10 seconds we spend in high+base power and sleep the rest
machine.RTC.SetInterrupt(10+5, true, nil)

for {

// Base power
// Nano RP2040 Connect: 0.11W
log("Base power, 5 sec, 0.11W")
led.Low()
time.Sleep(5 * time.Second) // light sleep

// High power
// Nano RP2040 Connect: 0.13W
log("High power, 5 sec, 0.13W")
led.High()
time.Sleep(5 * time.Second) // light sleep

// Low power
// Nano RP2040 Connect: 0.03W
log(" Low power, 5 sec, 0.03W")
led.Low()
// machine.RTC.SetInterrupt(10, false, nil) // alternatively non-recurring RTC interrupt can be scheduled every time
machine.Sleep() // deep sleep

}

}

func log(message string) {
time.Sleep(10 * time.Millisecond) // ensure output subsystem fully initialised
println(time.Now().Format(time.RFC3339) + " " + message)
time.Sleep(10 * time.Millisecond) // ensure output subsystem doe not go sleep before message printed out
}
49 changes: 46 additions & 3 deletions src/machine/machine_rp2040_clocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) {
// init initializes the clock hardware.
//
// Must be called before any other clock function.
// Must be called on start and wake up from deep sleep
func (clks *clocksType) init() {
// Start the watchdog tick
watchdog.startTick(xoscFreq)
Expand Down Expand Up @@ -235,11 +236,11 @@ func (clks *clocksType) init() {
48*MHz,
48*MHz)

// clkRTC = pllUSB (48MHz) / 1024 = 46875Hz
// clkRTC = xosc (12MHz) / 256 = 46875Hz
clkrtc := clks.clock(clkRTC)
clkrtc.configure(0, // No GLMUX
rp.CLOCKS_CLK_RTC_CTRL_AUXSRC_CLKSRC_PLL_USB,
48*MHz,
rp.CLOCKS_CLK_RTC_CTRL_AUXSRC_XOSC_CLKSRC,
12*MHz,
46875)

// clkPeri = clkSys. Used as reference clock for Peripherals.
Expand All @@ -251,3 +252,45 @@ func (clks *clocksType) init() {
125*MHz,
125*MHz)
}

// stop the clock
func (clk *clock) stop() {
// Disable clock. On clkRef and clkSys this does nothing,
// all other clocks have the ENABLE bit in the same position.
clk.ctrl.ClearBits(rp.CLOCKS_CLK_GPOUT0_CTRL_ENABLE)
configuredFreq[clk.cix] = 0
}

// Stop most clocks & PLLs in preparation for sleep.
// Ref, Sys and Rtc clocks run from XOSC.
// After awakening, need to call configure() to re-initialize.
func (clks *clocksType) sleep() {

// Configure clocks
// clkRef = xosc (12MHz) / 1 = 12MHz

// clkSys = xosc (12MHz) / 1 = 12MHz
clksys := clks.clock(clkSys)
clksys.configure(rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX,
rp.CLOCKS_CLK_SYS_CTRL_AUXSRC_XOSC_CLKSRC,
12*MHz,
12*MHz)

// clkUSB = 0MHz
clkusb := clks.clock(clkUSB)
clkusb.stop()

// clkADC = 0MHz
clkadc := clks.clock(clkADC)
clkadc.stop()

// clkRTC = xosc (12MHz) / 256 = 46875Hz

// clkPeri = 0MHz
clkperi := clks.clock(clkPeri)
clkperi.stop()

// Stop the PLLs
pllSys.stop()
pllUSB.stop()
}
6 changes: 6 additions & 0 deletions src/machine/machine_rp2040_pll.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,9 @@ func (pll *pll) init(refdiv, vcoFreq, postDiv1, postDiv2 uint32) {
pll.pwr.ClearBits(rp.PLL_SYS_PWR_POSTDIVPD)

}

func (pll *pll) stop() {
// Turn off PLL
pwr := uint32(rp.PLL_SYS_PWR_PD | rp.PLL_SYS_PWR_VCOPD | rp.PLL_SYS_PWR_POSTDIVPD)
pll.pwr.SetBits(pwr)
}
Loading