Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime: improve timers on nrf, and samd chips #1870

Merged
merged 1 commit into from
May 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 41 additions & 17 deletions src/runtime/runtime_atsamd21.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand All @@ -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

Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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.
Expand Down
59 changes: 41 additions & 18 deletions src/runtime/runtime_atsamd51.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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.
Expand Down
51 changes: 34 additions & 17 deletions src/runtime/runtime_nrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand All @@ -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 {
Expand All @@ -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
Expand Down