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
This commit is contained in:
Ayke van Laethem
2021-05-08 15:52:12 +02:00
committed by Ron Evans
parent 78acbdf0d9
commit 25b045d0a7
3 changed files with 116 additions and 52 deletions
+41 -17
View File
@@ -217,11 +217,19 @@ func initRTC() {
waitForSync()
rtcInterrupt := interrupt.New(sam.IRQ_RTC, func(intr interrupt.Interrupt) {
// disable IRQ for CMP0 compare
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0)
timerWakeup.Set(1)
flags := sam.RTC_MODE0.INTFLAG.Get()
if flags&sam.RTC_MODE0_INTENSET_CMP0 != 0 {
// The timer (for a sleep) has expired.
timerWakeup.Set(1)
}
if flags&sam.RTC_MODE0_INTENSET_OVF != 0 {
// The 32-bit RTC timer has overflowed.
rtcOverflows.Set(rtcOverflows.Get() + 1)
}
// Mark this interrupt has handled for CMP0 and OVF.
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0 | sam.RTC_MODE0_INTENSET_OVF)
})
sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_OVF)
rtcInterrupt.SetPriority(0xc0)
rtcInterrupt.Enable()
}
@@ -231,10 +239,7 @@ func waitForSync() {
}
}
var (
timestamp timeUnit // ticks since boottime
timerLastCounter uint64
)
var rtcOverflows volatile.Register32 // number of times the RTC wrapped around
var timerWakeup volatile.Register8
@@ -259,7 +264,6 @@ func nanosecondsToTicks(ns int64) timeUnit {
// sleepTicks should sleep for d number of microseconds.
func sleepTicks(d timeUnit) {
for d != 0 {
ticks() // update timestamp
ticks := uint32(d)
if !timerSleep(ticks) {
// Bail out early to handle a non-time interrupt.
@@ -269,17 +273,37 @@ func sleepTicks(d timeUnit) {
}
}
// ticks returns number of microseconds since start.
// ticks returns the elapsed time since reset.
func ticks() timeUnit {
// For some ways of capturing the time atomically, see this thread:
// https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617
// Here, instead of re-reading the counter register if an overflow has been
// detected, we simply try again because that results in smaller code.
for {
mask := interrupt.Disable()
counter := readRTC()
overflows := rtcOverflows.Get()
hasOverflow := sam.RTC_MODE0.INTFLAG.Get()&sam.RTC_MODE0_INTENSET_OVF != 0
interrupt.Restore(mask)
if hasOverflow {
// There was an overflow while trying to capture the timer.
// Try again.
continue
}
// This is a 32-bit timer, so the number of timer overflows forms the
// upper 32 bits of this timer.
return timeUnit(overflows)<<32 + timeUnit(counter)
}
}
func readRTC() uint32 {
// request read of count
sam.RTC_MODE0.READREQ.Set(sam.RTC_MODE0_READREQ_RREQ)
waitForSync()
rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get()) // each counter tick == 30.5us
offset := (rtcCounter - timerLastCounter) // change since last measurement
timerLastCounter = rtcCounter
timestamp += timeUnit(offset)
return timestamp
return sam.RTC_MODE0.COUNT.Get()
}
// ticks are in microseconds
@@ -305,7 +329,7 @@ func timerSleep(ticks uint32) bool {
waitForSync()
// enable IRQ for CMP0 compare
sam.RTC_MODE0.INTENSET.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
sam.RTC_MODE0.INTENSET.Set(sam.RTC_MODE0_INTENSET_CMP0)
wait:
waitForEvents()
@@ -315,7 +339,7 @@ wait:
if hasScheduler {
// The interurpt may have awoken a goroutine, so bail out early.
// Disable IRQ for CMP0 compare.
sam.RTC_MODE0.INTENCLR.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
sam.RTC_MODE0.INTENCLR.Set(sam.RTC_MODE0_INTENSET_CMP0)
return false
} else {
// This is running without a scheduler.