Skip to content

Commit ba7ece6

Browse files
ysoldakdeadprogram
authored andcommitted
rp2040: rtc delayed interrupt
1 parent a1714dc commit ba7ece6

File tree

3 files changed

+277
-0
lines changed

3 files changed

+277
-0
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,8 @@ smoketest:
474474
@$(MD5SUM) test.hex
475475
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
476476
@$(MD5SUM) test.hex
477+
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/rtcinterrupt
478+
@$(MD5SUM) test.hex
477479
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
478480
@$(MD5SUM) test.hex
479481
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//go:build rp2040
2+
3+
package main
4+
5+
// This example demonstrates scheduling a delayed interrupt by real time clock.
6+
//
7+
// An interrupt may execute user callback function or used for its side effects
8+
// like waking up from sleep or dormant states.
9+
//
10+
// The interrupt can be configured to repeat.
11+
//
12+
// There is no separate method to disable interrupt, use 0 delay for that.
13+
//
14+
// Unfortunately, it is not possible to use time.Duration to work with RTC directly,
15+
// that would introduce a circular dependency between "machine" and "time" packages.
16+
17+
import (
18+
"fmt"
19+
"machine"
20+
"time"
21+
)
22+
23+
func main() {
24+
25+
// Schedule and enable recurring interrupt.
26+
// The callback function is executed in the context of an interrupt handler,
27+
// so regular restructions for this sort of code apply: no blocking, no memory allocation, etc.
28+
delay := time.Minute + 12*time.Second
29+
machine.RTC.SetInterrupt(uint32(delay.Seconds()), true, func() { println("Peekaboo!") })
30+
31+
for {
32+
fmt.Printf("%v\r\n", time.Now().Format(time.RFC3339))
33+
time.Sleep(1 * time.Second)
34+
}
35+
}

src/machine/machine_rp2040_rtc.go

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
//go:build rp2040
2+
3+
// Implementation based on code located here:
4+
// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_rtc/rtc.c
5+
6+
package machine
7+
8+
import (
9+
"device/rp"
10+
"errors"
11+
"runtime/interrupt"
12+
"unsafe"
13+
)
14+
15+
type rtcType rp.RTC_Type
16+
17+
type rtcTime struct {
18+
Year int16
19+
Month int8
20+
Day int8
21+
Dotw int8
22+
Hour int8
23+
Min int8
24+
Sec int8
25+
}
26+
27+
var RTC = (*rtcType)(unsafe.Pointer(rp.RTC))
28+
29+
const (
30+
second = 1
31+
minute = 60 * second
32+
hour = 60 * minute
33+
day = 24 * hour
34+
)
35+
36+
var (
37+
rtcAlarmRepeats bool
38+
rtcCallback func()
39+
rtcEpoch = rtcTime{
40+
Year: 1970, Month: 1, Day: 1, Dotw: 4, Hour: 0, Min: 0, Sec: 0,
41+
}
42+
)
43+
44+
var (
45+
ErrRtcDelayTooSmall = errors.New("RTC interrupt deplay is too small, shall be at least 1 second")
46+
ErrRtcDelayTooLarge = errors.New("RTC interrupt deplay is too large, shall be no more than 1 day")
47+
)
48+
49+
// SetInterrupt configures delayed and optionally recurring interrupt by real time clock.
50+
//
51+
// Delay is specified in whole seconds, allowed range depends on platform.
52+
// Zero delay disables previously configured interrupt, if any.
53+
//
54+
// RP2040 implementation allows delay to be up to 1 day, otherwise a respective error is emitted.
55+
func (rtc *rtcType) SetInterrupt(delay uint32, repeat bool, callback func()) error {
56+
57+
// Verify delay range
58+
if delay > day {
59+
return ErrRtcDelayTooLarge
60+
}
61+
62+
// De-configure delayed interrupt if delay is zero
63+
if delay == 0 {
64+
rtc.disableInterruptMatch()
65+
return nil
66+
}
67+
68+
// Configure delayed interrupt
69+
rtc.setDivider()
70+
71+
rtcAlarmRepeats = repeat
72+
rtcCallback = callback
73+
74+
err := rtc.setTime(rtcEpoch)
75+
if err != nil {
76+
return err
77+
}
78+
rtc.setAlarm(toAlarmTime(delay), callback)
79+
80+
return nil
81+
}
82+
83+
func toAlarmTime(delay uint32) rtcTime {
84+
result := rtcEpoch
85+
remainder := delay + 1 // needed "+1", otherwise alarm fires one second too early
86+
if remainder >= hour {
87+
result.Hour = int8(remainder / hour)
88+
remainder %= hour
89+
}
90+
if remainder >= minute {
91+
result.Min = int8(remainder / minute)
92+
remainder %= minute
93+
}
94+
result.Sec = int8(remainder)
95+
return result
96+
}
97+
98+
func (rtc *rtcType) setDivider() {
99+
// Get clk_rtc freq and make sure it is running
100+
rtcFreq := configuredFreq[clkRTC]
101+
if rtcFreq == 0 {
102+
panic("can not set RTC divider, clock is not running")
103+
}
104+
105+
// Take rtc out of reset now that we know clk_rtc is running
106+
resetBlock(rp.RESETS_RESET_RTC)
107+
unresetBlockWait(rp.RESETS_RESET_RTC)
108+
109+
// Set up the 1 second divider.
110+
// If rtc_freq is 400 then clkdiv_m1 should be 399
111+
rtcFreq -= 1
112+
113+
// Check the freq is not too big to divide
114+
if rtcFreq > rp.RTC_CLKDIV_M1_CLKDIV_M1_Msk {
115+
panic("can not set RTC divider, clock frequency is too big to divide")
116+
}
117+
118+
// Write divide value
119+
rtc.CLKDIV_M1.Set(rtcFreq)
120+
}
121+
122+
// setTime configures RTC with supplied time, initialises and activates it.
123+
func (rtc *rtcType) setTime(t rtcTime) error {
124+
125+
// Disable RTC and wait while it is still running
126+
rtc.CTRL.Set(0)
127+
for rtc.isActive() {
128+
}
129+
130+
rtc.SETUP_0.Set((uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos) |
131+
(uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos) |
132+
(uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos))
133+
134+
rtc.SETUP_1.Set((uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos) |
135+
(uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos) |
136+
(uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos) |
137+
(uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos))
138+
139+
// Load setup values into RTC clock domain
140+
rtc.CTRL.SetBits(rp.RTC_CTRL_LOAD)
141+
142+
// Enable RTC and wait for it to be running
143+
rtc.CTRL.SetBits(rp.RTC_CTRL_RTC_ENABLE)
144+
for !rtc.isActive() {
145+
}
146+
147+
return nil
148+
}
149+
150+
func (rtc *rtcType) isActive() bool {
151+
return rtc.CTRL.HasBits(rp.RTC_CTRL_RTC_ACTIVE)
152+
}
153+
154+
// setAlarm configures alarm in RTC and arms it.
155+
// The callback is executed in the context of an interrupt handler,
156+
// so regular restructions for this sort of code apply: no blocking, no memory allocation, etc.
157+
func (rtc *rtcType) setAlarm(t rtcTime, callback func()) {
158+
159+
rtc.disableInterruptMatch()
160+
161+
// Clear all match enable bits
162+
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)
163+
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)
164+
165+
// Only add to setup if it isn't -1 and set the match enable bits for things we care about
166+
if t.Year >= 0 {
167+
rtc.IRQ_SETUP_0.SetBits(uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos)
168+
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA)
169+
}
170+
171+
if t.Month >= 0 {
172+
rtc.IRQ_SETUP_0.SetBits(uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos)
173+
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MONTH_ENA)
174+
}
175+
176+
if t.Day >= 0 {
177+
rtc.IRQ_SETUP_0.SetBits(uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos)
178+
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_DAY_ENA)
179+
}
180+
181+
if t.Dotw >= 0 {
182+
rtc.IRQ_SETUP_1.SetBits(uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos)
183+
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA)
184+
}
185+
186+
if t.Hour >= 0 {
187+
rtc.IRQ_SETUP_1.SetBits(uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos)
188+
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_HOUR_ENA)
189+
}
190+
191+
if t.Min >= 0 {
192+
rtc.IRQ_SETUP_1.SetBits(uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos)
193+
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_MIN_ENA)
194+
}
195+
196+
if t.Sec >= 0 {
197+
rtc.IRQ_SETUP_1.SetBits(uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos)
198+
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_SEC_ENA)
199+
}
200+
201+
// Enable the IRQ at the proc
202+
interrupt.New(rp.IRQ_RTC_IRQ, rtcHandleInterrupt).Enable()
203+
204+
// Enable the IRQ at the peri
205+
rtc.INTE.Set(rp.RTC_INTE_RTC)
206+
207+
rtc.enableInterruptMatch()
208+
}
209+
210+
func (rtc *rtcType) enableInterruptMatch() {
211+
// Set matching and wait for it to be enabled
212+
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA)
213+
for !rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) {
214+
}
215+
}
216+
217+
func (rtc *rtcType) disableInterruptMatch() {
218+
// Disable matching and wait for it to stop being active
219+
rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA)
220+
for rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) {
221+
}
222+
}
223+
224+
func rtcHandleInterrupt(itr interrupt.Interrupt) {
225+
// Always disable the alarm to clear the current IRQ.
226+
// Even if it is a repeatable alarm, we don't want it to keep firing.
227+
// If it matches on a second it can keep firing for that second.
228+
RTC.disableInterruptMatch()
229+
230+
// Call user callback function
231+
if rtcCallback != nil {
232+
rtcCallback()
233+
}
234+
235+
if rtcAlarmRepeats {
236+
// If it is a repeatable alarm, reset time and re-enable the alarm.
237+
RTC.setTime(rtcEpoch)
238+
RTC.enableInterruptMatch()
239+
}
240+
}

0 commit comments

Comments
 (0)