Skip to content

Commit 25b045d

Browse files
aykevldeadprogram
authored andcommitted
runtime: improve timers on nrf, and samd chips
This commit improves the timers on various microcontrollers to better deal with counter wraparound. The result is a reduction in RAM size of around 12 bytes and a small effect (sometimes positive, sometimes negative) on flash consumption. But perhaps more importantly: getting the current time is now interrupt-safe (it previously could result in a race condition) and the timer will now be correct when the timer isn't retrieved for a long duration. Before this commit, a call to `time.Now` more than 8 minutes after the previous call could result in an incorrect time. For more details, see: https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617
1 parent 78acbdf commit 25b045d

File tree

3 files changed

+116
-52
lines changed

3 files changed

+116
-52
lines changed

src/runtime/runtime_atsamd21.go

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,19 @@ func initRTC() {
217217
waitForSync()
218218

219219
rtcInterrupt := interrupt.New(sam.IRQ_RTC, func(intr interrupt.Interrupt) {
220-
// disable IRQ for CMP0 compare
221-
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0)
222-
223-
timerWakeup.Set(1)
220+
flags := sam.RTC_MODE0.INTFLAG.Get()
221+
if flags&sam.RTC_MODE0_INTENSET_CMP0 != 0 {
222+
// The timer (for a sleep) has expired.
223+
timerWakeup.Set(1)
224+
}
225+
if flags&sam.RTC_MODE0_INTENSET_OVF != 0 {
226+
// The 32-bit RTC timer has overflowed.
227+
rtcOverflows.Set(rtcOverflows.Get() + 1)
228+
}
229+
// Mark this interrupt has handled for CMP0 and OVF.
230+
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0 | sam.RTC_MODE0_INTENSET_OVF)
224231
})
232+
sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_OVF)
225233
rtcInterrupt.SetPriority(0xc0)
226234
rtcInterrupt.Enable()
227235
}
@@ -231,10 +239,7 @@ func waitForSync() {
231239
}
232240
}
233241

234-
var (
235-
timestamp timeUnit // ticks since boottime
236-
timerLastCounter uint64
237-
)
242+
var rtcOverflows volatile.Register32 // number of times the RTC wrapped around
238243

239244
var timerWakeup volatile.Register8
240245

@@ -259,7 +264,6 @@ func nanosecondsToTicks(ns int64) timeUnit {
259264
// sleepTicks should sleep for d number of microseconds.
260265
func sleepTicks(d timeUnit) {
261266
for d != 0 {
262-
ticks() // update timestamp
263267
ticks := uint32(d)
264268
if !timerSleep(ticks) {
265269
// Bail out early to handle a non-time interrupt.
@@ -269,17 +273,37 @@ func sleepTicks(d timeUnit) {
269273
}
270274
}
271275

272-
// ticks returns number of microseconds since start.
276+
// ticks returns the elapsed time since reset.
273277
func ticks() timeUnit {
278+
// For some ways of capturing the time atomically, see this thread:
279+
// https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617
280+
// Here, instead of re-reading the counter register if an overflow has been
281+
// detected, we simply try again because that results in smaller code.
282+
for {
283+
mask := interrupt.Disable()
284+
counter := readRTC()
285+
overflows := rtcOverflows.Get()
286+
hasOverflow := sam.RTC_MODE0.INTFLAG.Get()&sam.RTC_MODE0_INTENSET_OVF != 0
287+
interrupt.Restore(mask)
288+
289+
if hasOverflow {
290+
// There was an overflow while trying to capture the timer.
291+
// Try again.
292+
continue
293+
}
294+
295+
// This is a 32-bit timer, so the number of timer overflows forms the
296+
// upper 32 bits of this timer.
297+
return timeUnit(overflows)<<32 + timeUnit(counter)
298+
}
299+
}
300+
301+
func readRTC() uint32 {
274302
// request read of count
275303
sam.RTC_MODE0.READREQ.Set(sam.RTC_MODE0_READREQ_RREQ)
276304
waitForSync()
277305

278-
rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get()) // each counter tick == 30.5us
279-
offset := (rtcCounter - timerLastCounter) // change since last measurement
280-
timerLastCounter = rtcCounter
281-
timestamp += timeUnit(offset)
282-
return timestamp
306+
return sam.RTC_MODE0.COUNT.Get()
283307
}
284308

285309
// ticks are in microseconds
@@ -305,7 +329,7 @@ func timerSleep(ticks uint32) bool {
305329
waitForSync()
306330

307331
// enable IRQ for CMP0 compare
308-
sam.RTC_MODE0.INTENSET.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
332+
sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_CMP0)
309333

310334
wait:
311335
waitForEvents()
@@ -315,7 +339,7 @@ wait:
315339
if hasScheduler {
316340
// The interurpt may have awoken a goroutine, so bail out early.
317341
// Disable IRQ for CMP0 compare.
318-
sam.RTC_MODE0.INTENCLR.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
342+
sam.RTC_MODE0.INTENCLR.Set(sam.RTC_MODE0_INTENSET_CMP0)
319343
return false
320344
} else {
321345
// This is running without a scheduler.

src/runtime/runtime_atsamd51.go

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,19 @@ func initRTC() {
206206
}
207207

208208
irq := interrupt.New(sam.IRQ_RTC, func(interrupt.Interrupt) {
209-
// disable IRQ for CMP0 compare
210-
sam.RTC_MODE0.INTFLAG.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
211-
212-
timerWakeup.Set(1)
209+
flags := sam.RTC_MODE0.INTFLAG.Get()
210+
if flags&sam.RTC_MODE0_INTENSET_CMP0 != 0 {
211+
// The timer (for a sleep) has expired.
212+
timerWakeup.Set(1)
213+
}
214+
if flags&sam.RTC_MODE0_INTENSET_OVF != 0 {
215+
// The 32-bit RTC timer has overflowed.
216+
rtcOverflows.Set(rtcOverflows.Get() + 1)
217+
}
218+
// Mark this interrupt has handled for CMP0 and OVF.
219+
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0 | sam.RTC_MODE0_INTENSET_OVF)
213220
})
221+
sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_OVF)
214222
irq.SetPriority(0xc0)
215223
irq.Enable()
216224
}
@@ -220,10 +228,7 @@ func waitForSync() {
220228
}
221229
}
222230

223-
var (
224-
timestamp timeUnit // ticks since boottime
225-
timerLastCounter uint64
226-
)
231+
var rtcOverflows volatile.Register32 // number of times the RTC wrapped around
227232

228233
var timerWakeup volatile.Register8
229234

@@ -248,7 +253,6 @@ func nanosecondsToTicks(ns int64) timeUnit {
248253
// sleepTicks should sleep for d number of microseconds.
249254
func sleepTicks(d timeUnit) {
250255
for d != 0 {
251-
ticks() // update timestamp
252256
ticks := uint32(d)
253257
if !timerSleep(ticks) {
254258
return
@@ -257,15 +261,34 @@ func sleepTicks(d timeUnit) {
257261
}
258262
}
259263

260-
// ticks returns number of microseconds since start.
264+
// ticks returns the elapsed time since reset.
261265
func ticks() timeUnit {
262-
waitForSync()
266+
// For some ways of capturing the time atomically, see this thread:
267+
// https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617
268+
// Here, instead of re-reading the counter register if an overflow has been
269+
// detected, we simply try again because that results in smaller code.
270+
for {
271+
mask := interrupt.Disable()
272+
counter := readRTC()
273+
overflows := rtcOverflows.Get()
274+
hasOverflow := sam.RTC_MODE0.INTFLAG.Get()&sam.RTC_MODE0_INTENSET_OVF != 0
275+
interrupt.Restore(mask)
276+
277+
if hasOverflow {
278+
// There was an overflow while trying to capture the timer.
279+
// Try again.
280+
continue
281+
}
263282

264-
rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get())
265-
offset := (rtcCounter - timerLastCounter) // change since last measurement
266-
timerLastCounter = rtcCounter
267-
timestamp += timeUnit(offset)
268-
return timestamp
283+
// This is a 32-bit timer, so the number of timer overflows forms the
284+
// upper 32 bits of this timer.
285+
return timeUnit(overflows)<<32 + timeUnit(counter)
286+
}
287+
}
288+
289+
func readRTC() uint32 {
290+
waitForSync()
291+
return sam.RTC_MODE0.COUNT.Get()
269292
}
270293

271294
// ticks are in microseconds
@@ -290,7 +313,7 @@ func timerSleep(ticks uint32) bool {
290313
sam.RTC_MODE0.COMP[0].Set(uint32(cnt) + ticks)
291314

292315
// enable IRQ for CMP0 compare
293-
sam.RTC_MODE0.INTENSET.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
316+
sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_CMP0)
294317

295318
wait:
296319
waitForEvents()
@@ -300,7 +323,7 @@ wait:
300323
if hasScheduler {
301324
// The interurpt may have awoken a goroutine, so bail out early.
302325
// Disable IRQ for CMP0 compare.
303-
sam.RTC_MODE0.INTENCLR.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
326+
sam.RTC_MODE0.INTENCLR.Set(sam.RTC_MODE0_INTENSET_CMP0)
304327
return false
305328
} else {
306329
// This is running without a scheduler.

src/runtime/runtime_nrf.go

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,18 @@ func initLFCLK() {
4747
func initRTC() {
4848
nrf.RTC1.TASKS_START.Set(1)
4949
intr := interrupt.New(nrf.IRQ_RTC1, func(intr interrupt.Interrupt) {
50-
nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0)
51-
nrf.RTC1.EVENTS_COMPARE[0].Set(0)
52-
rtc_wakeup.Set(1)
50+
if nrf.RTC1.EVENTS_COMPARE[0].Get() != 0 {
51+
nrf.RTC1.EVENTS_COMPARE[0].Set(0)
52+
nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0)
53+
nrf.RTC1.EVENTS_COMPARE[0].Set(0)
54+
rtc_wakeup.Set(1)
55+
}
56+
if nrf.RTC1.EVENTS_OVRFLW.Get() != 0 {
57+
nrf.RTC1.EVENTS_OVRFLW.Set(0)
58+
rtcOverflows.Set(rtcOverflows.Get() + 1)
59+
}
5360
})
61+
nrf.RTC1.INTENSET.Set(nrf.RTC_INTENSET_OVRFLW)
5462
intr.SetPriority(0xc0) // low priority
5563
intr.Enable()
5664
}
@@ -63,17 +71,13 @@ const asyncScheduler = false
6371

6472
func sleepTicks(d timeUnit) {
6573
for d != 0 {
66-
ticks() // update timestamp
6774
ticks := uint32(d) & 0x7fffff // 23 bits (to be on the safe side)
6875
rtc_sleep(ticks)
6976
d -= timeUnit(ticks)
7077
}
7178
}
7279

73-
var (
74-
timestamp timeUnit // nanoseconds since boottime
75-
rtcLastCounter uint32 // 24 bits ticks
76-
)
80+
var rtcOverflows volatile.Register32 // number of times the RTC wrapped around
7781

7882
// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds.
7983
func ticksToNanoseconds(ticks timeUnit) int64 {
@@ -92,16 +96,29 @@ func nanosecondsToTicks(ns int64) timeUnit {
9296
}
9397

9498
// Monotonically increasing numer of ticks since start.
95-
//
96-
// Note: very long pauses between measurements (more than 8 minutes) may
97-
// overflow the counter, leading to incorrect results. This might be fixed by
98-
// handling the overflow event.
9999
func ticks() timeUnit {
100-
rtcCounter := uint32(nrf.RTC1.COUNTER.Get())
101-
offset := (rtcCounter - rtcLastCounter) & 0xffffff // change since last measurement
102-
rtcLastCounter = rtcCounter
103-
timestamp += timeUnit(offset)
104-
return timestamp
100+
// For some ways of capturing the time atomically, see this thread:
101+
// https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617
102+
// Here, instead of re-reading the counter register if an overflow has been
103+
// detected, we simply try again because that results in (slightly) smaller
104+
// code and is perhaps easier to prove correct.
105+
for {
106+
mask := interrupt.Disable()
107+
counter := uint32(nrf.RTC1.COUNTER.Get())
108+
overflows := rtcOverflows.Get()
109+
hasOverflow := nrf.RTC1.EVENTS_OVRFLW.Get() != 0
110+
interrupt.Restore(mask)
111+
112+
if hasOverflow {
113+
// There was an overflow. Try again.
114+
continue
115+
}
116+
117+
// The counter is 24 bits in size, so the number of overflows form the
118+
// upper 32 bits (together 56 bits, which covers 71493 years at
119+
// 32768kHz: I'd argue good enough for most purposes).
120+
return timeUnit(overflows)<<24 + timeUnit(counter)
121+
}
105122
}
106123

107124
var rtc_wakeup volatile.Register8

0 commit comments

Comments
 (0)