aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/runtime/runtime_stm32_timers.go
blob: 81b728f779639e2fa8ca4c87d33b581013bdb8f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
//go:build stm32
// +build stm32

package runtime

// 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.

import (
	"device/stm32"
	"machine"
	"runtime/interrupt"
	"runtime/volatile"
)

type timerInfo struct {
	EnableRegister *volatile.Register32
	EnableFlag     uint32
	Device         *stm32.TIM_Type
}

const (
	// 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 (
	// Tick count since boot
	tickCount volatile.Register64

	// The timer used for counting ticks
	tickTimer *machine.TIM

	// The max counter value (fractional part)
	countMax uint32
)

func ticksToNanoseconds(ticks timeUnit) int64 {
	return int64(ticks) * NS_PER_TICK
}

func nanosecondsToTicks(ns int64) timeUnit {
	return timeUnit(ns / NS_PER_TICK)
}

// number of ticks since start.
//go:linkname ticks runtime.ticks
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 := 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.
//
// 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})

	countMax = tickTimer.Top()
	tickTimer.SetWraparoundInterrupt(handleTick)
}

func handleTick() {
	// increment tick count
	tickCount.Set(tickCount.Get() + 1)
}

//
//  ---  Sleep  ---
//

func sleepTicks(d timeUnit) {
	// If there is a scheduler, we sleep until any kind of CPU event up to
	// a maximum of the requested sleep duration.
	//
	// The scheduler will call again if there is nothing to do and a further
	// sleep is required.
	if hasScheduler {
		timerSleep(uint64(d))
		return
	}

	// There's no scheduler, so we sleep until at least the requested number
	// of ticks has passed.  For short sleeps, this forms a busy loop since
	// timerSleep will return immediately.
	end := ticks() + d
	for ticks() < end {
		timerSleep(uint64(d))
	}
}

// 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
	}

	// 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
	}

	// 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 interrupt that woke the
	// CPU, disable the sleep interrupt now.
	tickTimer.Unset(0)
}

func handleSleep(ch uint8) {
	// Disable the sleep interrupt
	tickTimer.Unset(0)
}

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)
}