diff --git a/src/runtime/runtime_stm32_timers.go b/src/runtime/runtime_stm32_timers.go index 589995d7..dd64a36e 100644 --- a/src/runtime/runtime_stm32_timers.go +++ b/src/runtime/runtime_stm32_timers.go @@ -2,26 +2,19 @@ package runtime -// This file implements a common implementation of implementing 'ticks' and 'sleep' for STM32 devices. The -// implementation uses two 'basic' timers, so should be compatible with a broad range of STM32 MCUs. +// This file implements a common implementation of implementing 'ticks' and +// 'sleep' for STM32 devices. The implementation uses a single timer. The +// timer's PWM frequency (controlled by PSC and ARR) are configured for +// periodic interrupts at 100Hz (TICK_INTR_PERIOD_NS). The PWM counter +// register is used for fine-grained resolution (down to ~150ns) with an +// Output Comparator used for fine-grained sleeps. // -// This implementation is of 'sleep' is for running in a normal power mode. Use of the RTC to enter and -// resume from low-power states is out of scope. -// -// Interface -// --------- -// For each MCU, the following constants should be defined: -// TICK_RATE The desired frequency of ticks, e.g. 1000 for 1KHz ticks -// TICK_TIMER_IRQ Which timer to use for counting ticks (e.g. stm32.IRQ_TIM7) -// TICK_TIMER_FREQ The frequency the clock feeding the sleep timer is set to (e.g. 84MHz) -// SLEEP_TIMER_IRQ Which timer to use for sleeping (e.g. stm32.IRQ_TIM3) -// SLEEP_TIMER_FREQ The frequency the clock feeding the sleep timer is set to (e.g. 84MHz) -// -// The type alias `arrtype` should be defined to either uint32 or uint16 depending on the -// size of that register in the MCU's TIM_Type structure +// The type alias `arrtype` should be defined to either uint32 or uint16 +// depending on the size of that register in the MCU's TIM_Type structure. import ( "device/stm32" + "machine" "runtime/interrupt" "runtime/volatile" ) @@ -33,7 +26,22 @@ type timerInfo struct { } const ( - TICKS_PER_NS = 1000000000 / TICK_RATE + // All STM32 do a constant 16ns per tick. This keeps time + // conversion between ticks and ns fast (shift operation) + // at the expense of more complex logic when getting current + // time (which is already slow due to interfacing with hardware) + NS_PER_TICK = 16 + + // For very short sleeps a busy loop is used to avoid race + // conditions where a trigger would take longer to setup than + // the sleep duration. + MAX_BUSY_LOOP_NS = 10e3 // 10us + + // The period of tick interrupts in nanoseconds + TICK_INTR_PERIOD_NS = 10e6 // 10ms = 100Hz + + // The number of ticks that happen per overflow interrupt + TICK_PER_INTR = TICK_INTR_PERIOD_NS / NS_PER_TICK ) var ( @@ -41,79 +49,65 @@ var ( tickCount volatile.Register64 // The timer used for counting ticks - tickTimer *timerInfo + tickTimer *machine.TIM - // The timer used for sleeping - sleepTimer *timerInfo + // The max counter value (fractional part) + countMax uint32 ) func ticksToNanoseconds(ticks timeUnit) int64 { - return int64(ticks) * TICKS_PER_NS + return int64(ticks) * NS_PER_TICK } func nanosecondsToTicks(ns int64) timeUnit { - return timeUnit(ns / TICKS_PER_NS) + return timeUnit(ns / NS_PER_TICK) } -// number of ticks (microseconds) since start. +// number of ticks since start. //go:linkname ticks runtime.ticks func ticks() timeUnit { - return timeUnit(tickCount.Get()) + // 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 := tickTimer.Count() + overflows := uint64(tickCount.Get()) + hasOverflow := tickTimer.Device.SR.HasBits(stm32.TIM_SR_UIF) + interrupt.Restore(mask) + + if hasOverflow { + continue + } + + return timeUnit(overflows*TICK_PER_INTR + countToTicks(counter)) + } } // // -- Ticks --- // -// Enable the timer used to count ticks -func initTickTimer(ti *timerInfo) { - tickTimer = ti - ti.EnableRegister.SetBits(ti.EnableFlag) +// Enable the timer used to count ticks. +// +// For precise sleeps use a timer with at least one OutputCompare +// channel otherwise minimum reliable sleep resolution is bounded +// by TICK_INTR_PERIOD_NS. +// +// Typically avoid TIM6 / TIM7 as they often do not include an +// output comparator. +func initTickTimer(tim *machine.TIM) { + tickTimer = tim + tickTimer.Configure(machine.PWMConfig{Period: TICK_INTR_PERIOD_NS}) - psc := uint32(TICK_TIMER_FREQ / TICK_RATE) - period := uint32(1) - - // Get the pre-scale into range, with interrupt firing - // once per tick. - for psc > 0x10000 || period == 1 { - psc >>= 1 - period <<= 1 - } - - // Clamp overflow - if period > 0x10000 { - period = 0x10000 - } - - ti.Device.PSC.Set(psc - 1) - ti.Device.ARR.Set(arrtype(period - 1)) - - // Auto-repeat - ti.Device.EGR.SetBits(stm32.TIM_EGR_UG) - - // Register the interrupt handler - intr := interrupt.New(TICK_TIMER_IRQ, handleTick) - intr.SetPriority(0xc1) - intr.Enable() - - // Clear update flag - ti.Device.SR.ClearBits(stm32.TIM_SR_UIF) - - // Enable the hardware interrupt - ti.Device.DIER.SetBits(stm32.TIM_DIER_UIE) - - // Enable the timer - ti.Device.CR1.SetBits(stm32.TIM_CR1_CEN) + countMax = tickTimer.Top() + tickTimer.SetWraparoundInterrupt(handleTick) } -func handleTick(interrupt.Interrupt) { - if tickTimer.Device.SR.HasBits(stm32.TIM_SR_UIF) { - // clear the update flag - tickTimer.Device.SR.ClearBits(stm32.TIM_SR_UIF) - - // increment tick count - tickCount.Set(tickCount.Get() + 1) - } +func handleTick() { + // increment tick count + tickCount.Set(tickCount.Get() + 1) } // @@ -127,76 +121,76 @@ func sleepTicks(d timeUnit) { // The scheduler will call again if there is nothing to do and a further // sleep is required. if hasScheduler { - timerSleep(ticksToNanoseconds(d)) + timerSleep(uint64(d)) return } // There's no scheduler, so we sleep until at least the requested number - // of ticks has passed. + // of ticks has passed. For short sleeps, this forms a busy loop since + // timerSleep will return immediately. end := ticks() + d for ticks() < end { - timerSleep(ticksToNanoseconds(d)) + timerSleep(uint64(d)) } } -// Enable the Sleep clock -func initSleepTimer(ti *timerInfo) { - sleepTimer = ti - - ti.EnableRegister.SetBits(ti.EnableFlag) - - // No auto-repeat - ti.Device.EGR.SetBits(stm32.TIM_EGR_UG) - - // Enable the hardware interrupt. - ti.Device.DIER.SetBits(stm32.TIM_DIER_UIE) - - intr := interrupt.New(SLEEP_TIMER_IRQ, handleSleep) - intr.SetPriority(0xc3) - intr.Enable() -} - -// timerSleep sleeps for 'at most' ns nanoseconds, but possibly less. -func timerSleep(ns int64) { - // Calculate initial pre-scale value. - // delay (in ns) and clock freq are both large values, so do the nanosecs - // conversion (divide by 1G) by pre-dividing each by 1000 to avoid overflow - // in any meaningful time period. - psc := ((ns / 1000) * (SLEEP_TIMER_FREQ / 1000)) / 1000 - period := int64(1) - - // Get the pre-scale into range, with interrupt firing - // once per tick. - for psc > 0x10000 || period == 1 { - psc >>= 1 - period <<= 1 +// timerSleep sleeps for 'at most' ticks, but possibly less. +func timerSleep(ticks uint64) { + // If the sleep is super-small (<10us), busy loop by returning + // to the scheduler (if any). This avoids a busy loop here + // that might delay tasks from being scheduled triggered by + // an interrupt (e.g. channels). + if ticksToNanoseconds(timeUnit(ticks)) < MAX_BUSY_LOOP_NS { + return } - // Clamp overflow - if period > 0x10000 { - period = 0x10000 + // If the sleep is long, the tick interrupt will occur before + // the sleep expires, so just use that. This routine will be + // called again if the sleep is incomplete. + if ticks >= TICK_PER_INTR { + waitForEvents() + return } - // Set the desired duration and enable - sleepTimer.Device.PSC.Set(uint32(psc) - 1) - sleepTimer.Device.ARR.Set(arrtype(period) - 1) - sleepTimer.Device.CR1.SetBits(stm32.TIM_CR1_CEN) + // Sleeping for less than one tick interrupt, now see if the + // next tick interrupt will occur before the sleep expires. If + // so, use that interrupt. (this routine will be called + // again if sleep is incomplete) + cnt := tickTimer.Count() + ticksUntilOverflow := countToTicks(countMax - cnt) + if ticksUntilOverflow <= ticks { + waitForEvents() + return + } + + // The sleep is now known to be: + // - More than a few CPU cycles + // - Less than one interrupt period + // - Expiring before the next interrupt + // + // Setup a PWM channel to trigger an interrupt. + // NOTE: ticks is known to be < MAX_UINT32 at this point. + tickTimer.SetMatchInterrupt(0, handleSleep) + tickTimer.Set(0, cnt+ticksToCount(ticks)) // Wait till either the timer or some other event wakes // up the CPU waitForEvents() - // In case it was not the sleep timer that woke the - // CPU, disable the timer now. - disableSleepTimer() + // In case it was not the sleep interrupt that woke the + // CPU, disable the sleep interrupt now. + tickTimer.Unset(0) } -func handleSleep(interrupt.Interrupt) { - disableSleepTimer() +func handleSleep(ch uint8) { + // Disable the sleep interrupt + tickTimer.Unset(0) } -func disableSleepTimer() { - // Disable and clear the update flag. - sleepTimer.Device.CR1.ClearBits(stm32.TIM_CR1_CEN) - sleepTimer.Device.SR.ClearBits(stm32.TIM_SR_UIF) +func countToTicks(count uint32) uint64 { + return (uint64(count) * TICK_PER_INTR) / uint64(countMax) +} + +func ticksToCount(ticks uint64) uint32 { + return uint32((ticks * uint64(countMax)) / TICK_PER_INTR) } diff --git a/src/runtime/runtime_stm32f103.go b/src/runtime/runtime_stm32f103.go index 9af5cea1..7775f5a1 100644 --- a/src/runtime/runtime_stm32f103.go +++ b/src/runtime/runtime_stm32f103.go @@ -7,39 +7,14 @@ import ( "machine" ) -/* - timer settings used for tick and sleep. - - note: TICK_TIMER_FREQ and SLEEP_TIMER_FREQ are controlled by PLL / clock - settings configured in initCLK, so must be kept in sync if the clock settings - are changed. -*/ -const ( - TICK_RATE = 1000 // 1 KHz - SLEEP_TIMER_IRQ = stm32.IRQ_TIM3 - SLEEP_TIMER_FREQ = 72000000 // 72 MHz - TICK_TIMER_IRQ = stm32.IRQ_TIM4 - TICK_TIMER_FREQ = 72000000 // 72 MHz -) - type arrtype = uint32 func init() { initCLK() - initSleepTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM3EN, - Device: stm32.TIM3, - }) - machine.Serial.Configure(machine.UARTConfig{}) - initTickTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM4EN, - Device: stm32.TIM4, - }) + initTickTimer(&machine.TIM4) } func putchar(c byte) { diff --git a/src/runtime/runtime_stm32f405.go b/src/runtime/runtime_stm32f405.go index 7944fe12..a809cd44 100644 --- a/src/runtime/runtime_stm32f405.go +++ b/src/runtime/runtime_stm32f405.go @@ -64,39 +64,15 @@ const ( FLASH_OPTIONS = stm32.FLASH_ACR_ICEN | stm32.FLASH_ACR_DCEN | stm32.FLASH_ACR_PRFTEN ) -/* - timer settings used for tick and sleep. - - note: TICK_TIMER_FREQ and SLEEP_TIMER_FREQ are controlled by PLL / clock - settings above, so must be kept in sync if the clock settings are changed. -*/ -const ( - TICK_RATE = 1000 // 1 KHz - SLEEP_TIMER_IRQ = stm32.IRQ_TIM3 - SLEEP_TIMER_FREQ = PCLK1_FREQ_HZ * 2 - TICK_TIMER_IRQ = stm32.IRQ_TIM7 - TICK_TIMER_FREQ = PCLK1_FREQ_HZ * 2 -) - type arrtype = uint32 func init() { initOSC() // configure oscillators initCLK() - initSleepTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM3EN, - Device: stm32.TIM3, - }) - initCOM() - initTickTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM7EN, - Device: stm32.TIM7, - }) + initTickTimer(&machine.TIM3) } func initOSC() { diff --git a/src/runtime/runtime_stm32f407.go b/src/runtime/runtime_stm32f407.go index 17a79eb7..67593978 100644 --- a/src/runtime/runtime_stm32f407.go +++ b/src/runtime/runtime_stm32f407.go @@ -26,38 +26,14 @@ const ( PLL_Q = 7 // USB OTS FS, SDIO and RNG Clock = PLL_VCO / PLL_Q ) -/* - timer settings used for tick and sleep. - - note: TICK_TIMER_FREQ and SLEEP_TIMER_FREQ are controlled by PLL / clock - settings above, so must be kept in sync if the clock settings are changed. -*/ -const ( - TICK_RATE = 1000 // 1 KHz - TICK_TIMER_IRQ = stm32.IRQ_TIM7 - TICK_TIMER_FREQ = 84000000 // 84 MHz - SLEEP_TIMER_IRQ = stm32.IRQ_TIM3 - SLEEP_TIMER_FREQ = 84000000 // 84 MHz -) - type arrtype = uint32 func init() { initCLK() - initSleepTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM3EN, - Device: stm32.TIM3, - }) - machine.Serial.Configure(machine.UARTConfig{}) - initTickTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM7EN, - Device: stm32.TIM7, - }) + initTickTimer(&machine.TIM2) } func putchar(c byte) { diff --git a/src/runtime/runtime_stm32f7x2.go b/src/runtime/runtime_stm32f7x2.go index 9e76694f..401962f6 100644 --- a/src/runtime/runtime_stm32f7x2.go +++ b/src/runtime/runtime_stm32f7x2.go @@ -25,38 +25,14 @@ const ( PLL_Q = 2 ) -/* - timer settings used for tick and sleep. - - note: TICK_TIMER_FREQ and SLEEP_TIMER_FREQ are controlled by PLL / clock - settings above, so must be kept in sync if the clock settings are changed. -*/ -const ( - TICK_RATE = 1000 // 1 KHz - SLEEP_TIMER_IRQ = stm32.IRQ_TIM3 - SLEEP_TIMER_FREQ = 54000000 // 54 MHz (2x APB1) - TICK_TIMER_IRQ = stm32.IRQ_TIM7 - TICK_TIMER_FREQ = 54000000 // 54 MHz (2x APB1) -) - type arrtype = uint32 func init() { initCLK() - initSleepTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM3EN, - Device: stm32.TIM3, - }) - machine.Serial.Configure(machine.UARTConfig{}) - initTickTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM7EN, - Device: stm32.TIM7, - }) + initTickTimer(&machine.TIM3) } func putchar(c byte) { diff --git a/src/runtime/runtime_stm32l0x1.go b/src/runtime/runtime_stm32l0x1.go index 264efaf0..3fd5288e 100644 --- a/src/runtime/runtime_stm32l0x1.go +++ b/src/runtime/runtime_stm32l0x1.go @@ -7,20 +7,6 @@ import ( "machine" ) -/* - timer settings used for tick and sleep. - - note: TICK_TIMER_FREQ and SLEEP_TIMER_FREQ are controlled by PLL / clock - settings, so must be kept in sync if the clock settings are changed. -*/ -const ( - TICK_RATE = 1000 // 1 KHz - TICK_TIMER_IRQ = stm32.IRQ_TIM21 - TICK_TIMER_FREQ = 32000000 // 32 MHz - SLEEP_TIMER_IRQ = stm32.IRQ_TIM22 - SLEEP_TIMER_FREQ = 32000000 // 32 MHz -) - const ( FlashLatency = stm32.Flash_ACR_LATENCY_WS1 ) @@ -28,17 +14,7 @@ const ( func init() { initCLK() - initSleepTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB2ENR, - EnableFlag: stm32.RCC_APB2ENR_TIM22EN, - Device: stm32.TIM22, - }) - machine.Serial.Configure(machine.UARTConfig{}) - initTickTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB2ENR, - EnableFlag: stm32.RCC_APB2ENR_TIM21EN, - Device: stm32.TIM21, - }) + initTickTimer(&machine.TIM21) } diff --git a/src/runtime/runtime_stm32l0x2.go b/src/runtime/runtime_stm32l0x2.go index 2412d7e5..89398ff5 100644 --- a/src/runtime/runtime_stm32l0x2.go +++ b/src/runtime/runtime_stm32l0x2.go @@ -7,20 +7,6 @@ import ( "machine" ) -/* - timer settings used for tick and sleep. - - note: TICK_TIMER_FREQ and SLEEP_TIMER_FREQ are controlled by PLL / clock - settings, so must be kept in sync if the clock settings are changed. -*/ -const ( - TICK_RATE = 1000 // 1 KHz - TICK_TIMER_IRQ = stm32.IRQ_TIM7 - TICK_TIMER_FREQ = 32000000 // 32 MHz - SLEEP_TIMER_IRQ = stm32.IRQ_TIM3 - SLEEP_TIMER_FREQ = 32000000 // 32 MHz -) - const ( FlashLatency = stm32.Flash_ACR_LATENCY_WS1 ) @@ -28,17 +14,7 @@ const ( func init() { initCLK() - initSleepTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM3EN, - Device: stm32.TIM3, - }) - machine.Serial.Configure(machine.UARTConfig{}) - initTickTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB1ENR, - EnableFlag: stm32.RCC_APB1ENR_TIM7EN, - Device: stm32.TIM7, - }) + initTickTimer(&machine.TIM3) } diff --git a/src/runtime/runtime_stm32l4x2.go b/src/runtime/runtime_stm32l4x2.go index 7bdf39bf..e1e69c3e 100644 --- a/src/runtime/runtime_stm32l4x2.go +++ b/src/runtime/runtime_stm32l4x2.go @@ -50,38 +50,14 @@ const ( RCC_PLL_SYSCLK = stm32.RCC_PLLCFGR_PLLREN ) -/* - timer settings used for tick and sleep. - - note: TICK_TIMER_FREQ and SLEEP_TIMER_FREQ are controlled by PLL / clock - settings above, so must be kept in sync if the clock settings are changed. -*/ -const ( - TICK_RATE = 1000 // 1 KHz - TICK_TIMER_IRQ = stm32.IRQ_TIM1_UP_TIM16 - TICK_TIMER_FREQ = 80000000 // 80 MHz - SLEEP_TIMER_IRQ = stm32.IRQ_TIM1_BRK_TIM15 - SLEEP_TIMER_FREQ = 80000000 // 84 MHz -) - type arrtype = uint32 func init() { initCLK() - initSleepTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB2ENR, - EnableFlag: stm32.RCC_APB2ENR_TIM15EN, - Device: stm32.TIM15, - }) - machine.Serial.Configure(machine.UARTConfig{}) - initTickTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB2ENR, - EnableFlag: stm32.RCC_APB2ENR_TIM16EN, - Device: stm32.TIM16, - }) + initTickTimer(&machine.TIM15) } func putchar(c byte) { diff --git a/src/runtime/runtime_stm32l5x2.go b/src/runtime/runtime_stm32l5x2.go index c8c84200..0919d2b8 100644 --- a/src/runtime/runtime_stm32l5x2.go +++ b/src/runtime/runtime_stm32l5x2.go @@ -31,19 +31,9 @@ type arrtype = uint32 func init() { initCLK() - initSleepTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB2ENR, - EnableFlag: stm32.RCC_APB2ENR_TIM15EN, - Device: stm32.TIM15, - }) - machine.Serial.Configure(machine.UARTConfig{}) - initTickTimer(&timerInfo{ - EnableRegister: &stm32.RCC.APB2ENR, - EnableFlag: stm32.RCC_APB2ENR_TIM16EN, - Device: stm32.TIM16, - }) + initTickTimer(&machine.TIM16) } func putchar(c byte) {