diff options
author | Ayke van Laethem <[email protected]> | 2021-05-08 15:52:12 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2021-05-08 20:59:40 +0200 |
commit | 25b045d0a72d33b934db7279fe516ef16060a771 (patch) | |
tree | 7fa471f45ad562ed38978e466de7d7bae7b3e2fa | |
parent | 78acbdf0d99f40dd7abe20f822739ca6aadf7304 (diff) | |
download | tinygo-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.go | 58 | ||||
-rw-r--r-- | src/runtime/runtime_atsamd51.go | 59 | ||||
-rw-r--r-- | src/runtime/runtime_nrf.go | 51 |
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 |