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
Этот коммит содержится в:
родитель
78acbdf0d9
коммит
25b045d0a7
3 изменённых файлов: 116 добавлений и 52 удалений
|
@ -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)
|
||||
|
||||
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.
|
||||
|
|
|
@ -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)
|
||||
|
||||
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)
|
||||
|
||||
rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get())
|
||||
offset := (rtcCounter - timerLastCounter) // change since last measurement
|
||||
timerLastCounter = rtcCounter
|
||||
timestamp += timeUnit(offset)
|
||||
return timestamp
|
||||
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 {
|
||||
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.
|
||||
|
|
|
@ -47,10 +47,18 @@ func initLFCLK() {
|
|||
func initRTC() {
|
||||
nrf.RTC1.TASKS_START.Set(1)
|
||||
intr := interrupt.New(nrf.IRQ_RTC1, func(intr interrupt.Interrupt) {
|
||||
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
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче