aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorYurii Soldak <[email protected]>2023-01-19 02:51:25 +0100
committerRon Evans <[email protected]>2023-02-23 09:23:37 +0100
commit4d0dfbd6fd607eacfd77460e74eadae2ba73215c (patch)
treef6a4ed10c0b7daa24f86d4880847b06b1e78ff29
parent1065f06e5752537bb58e797d9cb9aeb44c59361f (diff)
downloadtinygo-4d0dfbd6fd607eacfd77460e74eadae2ba73215c.tar.gz
tinygo-4d0dfbd6fd607eacfd77460e74eadae2ba73215c.zip
rp2040: rtc delayed interrupt
-rw-r--r--Makefile2
-rw-r--r--src/examples/rtcinterrupt/rtcinterrupt.go35
-rw-r--r--src/machine/machine_rp2040_rtc.go240
3 files changed, 277 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index a019a357b..a0acb881a 100644
--- a/Makefile
+++ b/Makefile
@@ -474,6 +474,8 @@ 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=pca10040 examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
diff --git a/src/examples/rtcinterrupt/rtcinterrupt.go b/src/examples/rtcinterrupt/rtcinterrupt.go
new file mode 100644
index 000000000..7211e4c30
--- /dev/null
+++ b/src/examples/rtcinterrupt/rtcinterrupt.go
@@ -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)
+ }
+}
diff --git a/src/machine/machine_rp2040_rtc.go b/src/machine/machine_rp2040_rtc.go
new file mode 100644
index 000000000..192e187c0
--- /dev/null
+++ b/src/machine/machine_rp2040_rtc.go
@@ -0,0 +1,240 @@
+//go:build rp2040
+
+// Implementation based on code located here:
+// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_rtc/rtc.c
+
+package machine
+
+import (
+ "device/rp"
+ "errors"
+ "runtime/interrupt"
+ "unsafe"
+)
+
+type rtcType rp.RTC_Type
+
+type rtcTime struct {
+ Year int16
+ Month int8
+ Day int8
+ Dotw int8
+ Hour int8
+ Min int8
+ Sec int8
+}
+
+var RTC = (*rtcType)(unsafe.Pointer(rp.RTC))
+
+const (
+ second = 1
+ minute = 60 * second
+ hour = 60 * minute
+ day = 24 * hour
+)
+
+var (
+ rtcAlarmRepeats bool
+ rtcCallback func()
+ rtcEpoch = rtcTime{
+ Year: 1970, Month: 1, Day: 1, Dotw: 4, Hour: 0, Min: 0, Sec: 0,
+ }
+)
+
+var (
+ ErrRtcDelayTooSmall = errors.New("RTC interrupt deplay is too small, shall be at least 1 second")
+ ErrRtcDelayTooLarge = errors.New("RTC interrupt deplay is too large, shall be no more than 1 day")
+)
+
+// SetInterrupt configures delayed and optionally recurring interrupt by real time clock.
+//
+// Delay is specified in whole seconds, allowed range depends on platform.
+// Zero delay disables previously configured interrupt, if any.
+//
+// RP2040 implementation allows delay to be up to 1 day, otherwise a respective error is emitted.
+func (rtc *rtcType) SetInterrupt(delay uint32, repeat bool, callback func()) error {
+
+ // Verify delay range
+ if delay > day {
+ return ErrRtcDelayTooLarge
+ }
+
+ // De-configure delayed interrupt if delay is zero
+ if delay == 0 {
+ rtc.disableInterruptMatch()
+ return nil
+ }
+
+ // Configure delayed interrupt
+ rtc.setDivider()
+
+ rtcAlarmRepeats = repeat
+ rtcCallback = callback
+
+ err := rtc.setTime(rtcEpoch)
+ if err != nil {
+ return err
+ }
+ rtc.setAlarm(toAlarmTime(delay), callback)
+
+ return nil
+}
+
+func toAlarmTime(delay uint32) rtcTime {
+ result := rtcEpoch
+ remainder := delay + 1 // needed "+1", otherwise alarm fires one second too early
+ if remainder >= hour {
+ result.Hour = int8(remainder / hour)
+ remainder %= hour
+ }
+ if remainder >= minute {
+ result.Min = int8(remainder / minute)
+ remainder %= minute
+ }
+ result.Sec = int8(remainder)
+ return result
+}
+
+func (rtc *rtcType) setDivider() {
+ // Get clk_rtc freq and make sure it is running
+ rtcFreq := configuredFreq[clkRTC]
+ if rtcFreq == 0 {
+ panic("can not set RTC divider, clock is not running")
+ }
+
+ // Take rtc out of reset now that we know clk_rtc is running
+ resetBlock(rp.RESETS_RESET_RTC)
+ unresetBlockWait(rp.RESETS_RESET_RTC)
+
+ // Set up the 1 second divider.
+ // If rtc_freq is 400 then clkdiv_m1 should be 399
+ rtcFreq -= 1
+
+ // Check the freq is not too big to divide
+ if rtcFreq > rp.RTC_CLKDIV_M1_CLKDIV_M1_Msk {
+ panic("can not set RTC divider, clock frequency is too big to divide")
+ }
+
+ // Write divide value
+ rtc.CLKDIV_M1.Set(rtcFreq)
+}
+
+// setTime configures RTC with supplied time, initialises and activates it.
+func (rtc *rtcType) setTime(t rtcTime) error {
+
+ // Disable RTC and wait while it is still running
+ rtc.CTRL.Set(0)
+ for rtc.isActive() {
+ }
+
+ rtc.SETUP_0.Set((uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos) |
+ (uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos) |
+ (uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos))
+
+ rtc.SETUP_1.Set((uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos) |
+ (uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos) |
+ (uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos) |
+ (uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos))
+
+ // Load setup values into RTC clock domain
+ rtc.CTRL.SetBits(rp.RTC_CTRL_LOAD)
+
+ // Enable RTC and wait for it to be running
+ rtc.CTRL.SetBits(rp.RTC_CTRL_RTC_ENABLE)
+ for !rtc.isActive() {
+ }
+
+ return nil
+}
+
+func (rtc *rtcType) isActive() bool {
+ return rtc.CTRL.HasBits(rp.RTC_CTRL_RTC_ACTIVE)
+}
+
+// setAlarm configures alarm in RTC and arms it.
+// The callback is executed in the context of an interrupt handler,
+// so regular restructions for this sort of code apply: no blocking, no memory allocation, etc.
+func (rtc *rtcType) setAlarm(t rtcTime, callback func()) {
+
+ rtc.disableInterruptMatch()
+
+ // Clear all match enable bits
+ rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA | rp.RTC_IRQ_SETUP_0_MONTH_ENA | rp.RTC_IRQ_SETUP_0_DAY_ENA)
+ rtc.IRQ_SETUP_1.ClearBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA | rp.RTC_IRQ_SETUP_1_HOUR_ENA | rp.RTC_IRQ_SETUP_1_MIN_ENA | rp.RTC_IRQ_SETUP_1_SEC_ENA)
+
+ // Only add to setup if it isn't -1 and set the match enable bits for things we care about
+ if t.Year >= 0 {
+ rtc.IRQ_SETUP_0.SetBits(uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos)
+ rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA)
+ }
+
+ if t.Month >= 0 {
+ rtc.IRQ_SETUP_0.SetBits(uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos)
+ rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MONTH_ENA)
+ }
+
+ if t.Day >= 0 {
+ rtc.IRQ_SETUP_0.SetBits(uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos)
+ rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_DAY_ENA)
+ }
+
+ if t.Dotw >= 0 {
+ rtc.IRQ_SETUP_1.SetBits(uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos)
+ rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA)
+ }
+
+ if t.Hour >= 0 {
+ rtc.IRQ_SETUP_1.SetBits(uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos)
+ rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_HOUR_ENA)
+ }
+
+ if t.Min >= 0 {
+ rtc.IRQ_SETUP_1.SetBits(uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos)
+ rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_MIN_ENA)
+ }
+
+ if t.Sec >= 0 {
+ rtc.IRQ_SETUP_1.SetBits(uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos)
+ rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_SEC_ENA)
+ }
+
+ // Enable the IRQ at the proc
+ interrupt.New(rp.IRQ_RTC_IRQ, rtcHandleInterrupt).Enable()
+
+ // Enable the IRQ at the peri
+ rtc.INTE.Set(rp.RTC_INTE_RTC)
+
+ rtc.enableInterruptMatch()
+}
+
+func (rtc *rtcType) enableInterruptMatch() {
+ // Set matching and wait for it to be enabled
+ rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA)
+ for !rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) {
+ }
+}
+
+func (rtc *rtcType) disableInterruptMatch() {
+ // Disable matching and wait for it to stop being active
+ rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA)
+ for rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) {
+ }
+}
+
+func rtcHandleInterrupt(itr interrupt.Interrupt) {
+ // Always disable the alarm to clear the current IRQ.
+ // Even if it is a repeatable alarm, we don't want it to keep firing.
+ // If it matches on a second it can keep firing for that second.
+ RTC.disableInterruptMatch()
+
+ // Call user callback function
+ if rtcCallback != nil {
+ rtcCallback()
+ }
+
+ if rtcAlarmRepeats {
+ // If it is a repeatable alarm, reset time and re-enable the alarm.
+ RTC.setTime(rtcEpoch)
+ RTC.enableInterruptMatch()
+ }
+}