diff --git a/src/machine/machine_rp2040.go b/src/machine/machine_rp2040.go index dd8b5c70..4b18368e 100644 --- a/src/machine/machine_rp2040.go +++ b/src/machine/machine_rp2040.go @@ -86,6 +86,11 @@ func ticks() uint64 { return timer.timeElapsed() } +//go:linkname lightSleep runtime.machineLightSleep +func lightSleep(ticks uint64) { + timer.lightSleep(ticks) +} + // UART pins const ( UART_TX_PIN = UART0_TX_PIN diff --git a/src/machine/machine_rp2040_timer.go b/src/machine/machine_rp2040_timer.go index 1ebe2130..9396d68a 100644 --- a/src/machine/machine_rp2040_timer.go +++ b/src/machine/machine_rp2040_timer.go @@ -4,13 +4,23 @@ package machine import ( + "device/arm" "device/rp" + "runtime/interrupt" "runtime/volatile" "unsafe" ) const numTimers = 4 +// Alarm0 is reserved for sleeping by tinygo runtime code for RP2040. +// Alarm0 is also IRQ0 +const sleepAlarm = 0 +const sleepAlarmIRQ = 0 + +// The minimum sleep duration in μs (ticks) +const minSleep = 10 + type timerType struct { timeHW volatile.Register32 timeLW volatile.Register32 @@ -50,3 +60,47 @@ func (tmr *timerType) timeElapsed() (us uint64) { } return uint64(hi)<<32 | uint64(lo) } + +// lightSleep will put the processor into a sleep state a short period +// (up to approx 72mins per RP2040 datasheet, 4.6.3. Alarms). +// +// This function is a 'light' sleep and will return early if another +// interrupt or event triggers. This is intentional since the +// primary use-case is for use by the TinyGo scheduler which will +// re-sleep if needed. +func (tmr *timerType) lightSleep(us uint64) { + // minSleep is a way to avoid race conditions for short + // sleeps by ensuring there is enough time to setup the + // alarm before sleeping. For very short sleeps, this + // effectively becomes a 'busy loop'. + if us < minSleep { + return + } + + // Interrupt handler is essentially a no-op, we're just relying + // on the side-effect of waking the CPU from "wfe" + intr := interrupt.New(sleepAlarmIRQ, func(interrupt.Interrupt) { + // Clear the IRQ + timer.intR.Set(1 << sleepAlarm) + }) + + // Reset interrupt flag + tmr.intR.Set(1 << sleepAlarm) + + // Enable interrupt + tmr.intE.SetBits(1 << sleepAlarm) + intr.Enable() + + // Only the low 32 bits of time can be used for alarms + target := uint64(tmr.timeRawL.Get()) + us + tmr.alarm[sleepAlarm].Set(uint32(target)) + + // Wait for sleep (or any other) interrupt + arm.Asm("wfe") + + // Disarm timer + tmr.armed.Set(1 << sleepAlarm) + + // Disable interrupt + intr.Disable() +} diff --git a/src/runtime/runtime_rp2040.go b/src/runtime/runtime_rp2040.go index 1de9dd08..0d4db365 100644 --- a/src/runtime/runtime_rp2040.go +++ b/src/runtime/runtime_rp2040.go @@ -12,6 +12,9 @@ import ( // machineTicks is provided by package machine. func machineTicks() uint64 +// machineLightSleep is provided by package machine. +func machineLightSleep(uint64) + type timeUnit uint64 // ticks returns the number of ticks (microseconds) elapsed since power up. @@ -32,6 +35,16 @@ func sleepTicks(d timeUnit) { if d == 0 { return } + + if hasScheduler { + // With scheduler, sleepTicks may return early if an interrupt or + // event fires - so scheduler can schedule any go routines now + // eligible to run + machineLightSleep(uint64(d)) + return + } + + // Busy loop sleepUntil := ticks() + d for ticks() < sleepUntil { }