github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/runtime/runtime_stm32_timers.go (about) 1 //go:build stm32 2 3 package runtime 4 5 // This file implements a common implementation of implementing 'ticks' and 6 // 'sleep' for STM32 devices. The implementation uses a single timer. The 7 // timer's PWM frequency (controlled by PSC and ARR) are configured for 8 // periodic interrupts at 100Hz (TICK_INTR_PERIOD_NS). The PWM counter 9 // register is used for fine-grained resolution (down to ~150ns) with an 10 // Output Comparator used for fine-grained sleeps. 11 12 import ( 13 "device/stm32" 14 "machine" 15 "runtime/interrupt" 16 "runtime/volatile" 17 ) 18 19 type timerInfo struct { 20 EnableRegister *volatile.Register32 21 EnableFlag uint32 22 Device *stm32.TIM_Type 23 } 24 25 const ( 26 // All STM32 do a constant 16ns per tick. This keeps time 27 // conversion between ticks and ns fast (shift operation) 28 // at the expense of more complex logic when getting current 29 // time (which is already slow due to interfacing with hardware) 30 NS_PER_TICK = 16 31 32 // For very short sleeps a busy loop is used to avoid race 33 // conditions where a trigger would take longer to setup than 34 // the sleep duration. 35 MAX_BUSY_LOOP_NS = 10e3 // 10us 36 37 // The period of tick interrupts in nanoseconds 38 TICK_INTR_PERIOD_NS = 10e6 // 10ms = 100Hz 39 40 // The number of ticks that happen per overflow interrupt 41 TICK_PER_INTR = TICK_INTR_PERIOD_NS / NS_PER_TICK 42 ) 43 44 var ( 45 // Tick count since boot 46 tickCount volatile.Register64 47 48 // The timer used for counting ticks 49 tickTimer *machine.TIM 50 51 // The max counter value (fractional part) 52 countMax uint32 53 ) 54 55 func ticksToNanoseconds(ticks timeUnit) int64 { 56 return int64(ticks) * NS_PER_TICK 57 } 58 59 func nanosecondsToTicks(ns int64) timeUnit { 60 return timeUnit(ns / NS_PER_TICK) 61 } 62 63 // number of ticks since start. 64 // 65 //go:linkname ticks runtime.ticks 66 func ticks() timeUnit { 67 // For some ways of capturing the time atomically, see this thread: 68 // https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617 69 // Here, instead of re-reading the counter register if an overflow has been 70 // detected, we simply try again because that results in smaller code. 71 for { 72 mask := interrupt.Disable() 73 counter := tickTimer.Count() 74 overflows := uint64(tickCount.Get()) 75 hasOverflow := tickTimer.Device.SR.HasBits(stm32.TIM_SR_UIF) 76 interrupt.Restore(mask) 77 78 if hasOverflow { 79 continue 80 } 81 82 return timeUnit(overflows*TICK_PER_INTR + countToTicks(counter)) 83 } 84 } 85 86 // 87 // -- Ticks --- 88 // 89 90 // Enable the timer used to count ticks. 91 // 92 // For precise sleeps use a timer with at least one OutputCompare 93 // channel otherwise minimum reliable sleep resolution is bounded 94 // by TICK_INTR_PERIOD_NS. 95 // 96 // Typically avoid TIM6 / TIM7 as they often do not include an 97 // output comparator. 98 func initTickTimer(tim *machine.TIM) { 99 tickTimer = tim 100 tickTimer.Configure(machine.PWMConfig{Period: TICK_INTR_PERIOD_NS}) 101 102 countMax = tickTimer.Top() 103 tickTimer.SetWraparoundInterrupt(handleTick) 104 } 105 106 func handleTick() { 107 // increment tick count 108 tickCount.Set(tickCount.Get() + 1) 109 } 110 111 // 112 // --- Sleep --- 113 // 114 115 func sleepTicks(d timeUnit) { 116 // If there is a scheduler, we sleep until any kind of CPU event up to 117 // a maximum of the requested sleep duration. 118 // 119 // The scheduler will call again if there is nothing to do and a further 120 // sleep is required. 121 if hasScheduler { 122 timerSleep(uint64(d)) 123 return 124 } 125 126 // There's no scheduler, so we sleep until at least the requested number 127 // of ticks has passed. For short sleeps, this forms a busy loop since 128 // timerSleep will return immediately. 129 end := ticks() + d 130 for ticks() < end { 131 timerSleep(uint64(d)) 132 } 133 } 134 135 // timerSleep sleeps for 'at most' ticks, but possibly less. 136 func timerSleep(ticks uint64) { 137 // If the sleep is super-small (<10us), busy loop by returning 138 // to the scheduler (if any). This avoids a busy loop here 139 // that might delay tasks from being scheduled triggered by 140 // an interrupt (e.g. channels). 141 if ticksToNanoseconds(timeUnit(ticks)) < MAX_BUSY_LOOP_NS { 142 return 143 } 144 145 // If the sleep is long, the tick interrupt will occur before 146 // the sleep expires, so just use that. This routine will be 147 // called again if the sleep is incomplete. 148 if ticks >= TICK_PER_INTR { 149 waitForEvents() 150 return 151 } 152 153 // Sleeping for less than one tick interrupt, now see if the 154 // next tick interrupt will occur before the sleep expires. If 155 // so, use that interrupt. (this routine will be called 156 // again if sleep is incomplete) 157 cnt := tickTimer.Count() 158 ticksUntilOverflow := countToTicks(countMax - cnt) 159 if ticksUntilOverflow <= ticks { 160 waitForEvents() 161 return 162 } 163 164 // The sleep is now known to be: 165 // - More than a few CPU cycles 166 // - Less than one interrupt period 167 // - Expiring before the next interrupt 168 // 169 // Setup a PWM channel to trigger an interrupt. 170 // NOTE: ticks is known to be < MAX_UINT32 at this point. 171 tickTimer.SetMatchInterrupt(0, handleSleep) 172 tickTimer.Set(0, cnt+ticksToCount(ticks)) 173 174 // Wait till either the timer or some other event wakes 175 // up the CPU 176 waitForEvents() 177 178 // In case it was not the sleep interrupt that woke the 179 // CPU, disable the sleep interrupt now. 180 tickTimer.Unset(0) 181 } 182 183 func handleSleep(ch uint8) { 184 // Disable the sleep interrupt 185 tickTimer.Unset(0) 186 } 187 188 func countToTicks(count uint32) uint64 { 189 return (uint64(count) * TICK_PER_INTR) / uint64(countMax) 190 } 191 192 func ticksToCount(ticks uint64) uint32 { 193 return uint32((ticks * uint64(countMax)) / TICK_PER_INTR) 194 }