aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2021-05-08 15:52:12 +0200
committerRon Evans <[email protected]>2021-05-08 20:59:40 +0200
commit25b045d0a72d33b934db7279fe516ef16060a771 (patch)
tree7fa471f45ad562ed38978e466de7d7bae7b3e2fa
parent78acbdf0d99f40dd7abe20f822739ca6aadf7304 (diff)
downloadtinygo-25b045d0a72d33b934db7279fe516ef16060a771.tar.gz
tinygo-25b045d0a72d33b934db7279fe516ef16060a771.zip
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
-rw-r--r--src/runtime/runtime_atsamd21.go58
-rw-r--r--src/runtime/runtime_atsamd51.go59
-rw-r--r--src/runtime/runtime_nrf.go51
3 files changed, 116 insertions, 52 deletions
diff --git a/src/runtime/runtime_atsamd21.go b/src/runtime/runtime_atsamd21.go
index a5f99293f..452532b2d 100644
--- a/src/runtime/runtime_atsamd21.go
+++ b/src/runtime/runtime_atsamd21.go
@@ -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.
diff --git a/src/runtime/runtime_atsamd51.go b/src/runtime/runtime_atsamd51.go
index 51a09413d..9babc686b 100644
--- a/src/runtime/runtime_atsamd51.go
+++ b/src/runtime/runtime_atsamd51.go
@@ -206,11 +206,19 @@ func initRTC() {
}
irq := interrupt.New(sam.IRQ_RTC, func(interrupt.Interrupt) {
- // disable IRQ for CMP0 compare
- sam.RTC_MODE0.INTFLAG.SetBits(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)
irq.SetPriority(0xc0)
irq.Enable()
}
@@ -220,10 +228,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
@@ -248,7 +253,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) {
return
@@ -257,15 +261,34 @@ func sleepTicks(d timeUnit) {
}
}
-// ticks returns number of microseconds since start.
+// ticks returns the elapsed time since reset.
func ticks() timeUnit {
- waitForSync()
+ // 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
+ }
- rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get())
- offset := (rtcCounter - timerLastCounter) // change since last measurement
- timerLastCounter = rtcCounter
- timestamp += timeUnit(offset)
- return timestamp
+ // 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 {
+ waitForSync()
+ return sam.RTC_MODE0.COUNT.Get()
}
// ticks are in microseconds
@@ -290,7 +313,7 @@ func timerSleep(ticks uint32) bool {
sam.RTC_MODE0.COMP[0].Set(uint32(cnt) + ticks)
// 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()
@@ -300,7 +323,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.
diff --git a/src/runtime/runtime_nrf.go b/src/runtime/runtime_nrf.go
index fcab3646b..b55ca18e9 100644
--- a/src/runtime/runtime_nrf.go
+++ b/src/runtime/runtime_nrf.go
@@ -47,10 +47,18 @@ func initLFCLK() {
func initRTC() {
nrf.RTC1.TASKS_START.Set(1)
intr := interrupt.New(nrf.IRQ_RTC1, func(intr interrupt.Interrupt) {
- nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0)
- nrf.RTC1.EVENTS_COMPARE[0].Set(0)
- rtc_wakeup.Set(1)
+ if nrf.RTC1.EVENTS_COMPARE[0].Get() != 0 {
+ nrf.RTC1.EVENTS_COMPARE[0].Set(0)
+ nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0)
+ nrf.RTC1.EVENTS_COMPARE[0].Set(0)
+ rtc_wakeup.Set(1)
+ }
+ if nrf.RTC1.EVENTS_OVRFLW.Get() != 0 {
+ nrf.RTC1.EVENTS_OVRFLW.Set(0)
+ rtcOverflows.Set(rtcOverflows.Get() + 1)
+ }
})
+ nrf.RTC1.INTENSET.Set(nrf.RTC_INTENSET_OVRFLW)
intr.SetPriority(0xc0) // low priority
intr.Enable()
}
@@ -63,17 +71,13 @@ const asyncScheduler = false
func sleepTicks(d timeUnit) {
for d != 0 {
- ticks() // update timestamp
ticks := uint32(d) & 0x7fffff // 23 bits (to be on the safe side)
rtc_sleep(ticks)
d -= timeUnit(ticks)
}
}
-var (
- timestamp timeUnit // nanoseconds since boottime
- rtcLastCounter uint32 // 24 bits ticks
-)
+var rtcOverflows volatile.Register32 // number of times the RTC wrapped around
// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds.
func ticksToNanoseconds(ticks timeUnit) int64 {
@@ -92,16 +96,29 @@ func nanosecondsToTicks(ns int64) timeUnit {
}
// Monotonically increasing numer of ticks since start.
-//
-// Note: very long pauses between measurements (more than 8 minutes) may
-// overflow the counter, leading to incorrect results. This might be fixed by
-// handling the overflow event.
func ticks() timeUnit {
- rtcCounter := uint32(nrf.RTC1.COUNTER.Get())
- offset := (rtcCounter - rtcLastCounter) & 0xffffff // change since last measurement
- rtcLastCounter = rtcCounter
- timestamp += timeUnit(offset)
- return timestamp
+ // 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 (slightly) smaller
+ // code and is perhaps easier to prove correct.
+ for {
+ mask := interrupt.Disable()
+ counter := uint32(nrf.RTC1.COUNTER.Get())
+ overflows := rtcOverflows.Get()
+ hasOverflow := nrf.RTC1.EVENTS_OVRFLW.Get() != 0
+ interrupt.Restore(mask)
+
+ if hasOverflow {
+ // There was an overflow. Try again.
+ continue
+ }
+
+ // The counter is 24 bits in size, so the number of overflows form the
+ // upper 32 bits (together 56 bits, which covers 71493 years at
+ // 32768kHz: I'd argue good enough for most purposes).
+ return timeUnit(overflows)<<24 + timeUnit(counter)
+ }
}
var rtc_wakeup volatile.Register8