aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/runtime/runtime_avr.go
blob: 10ce1dada65f95933bad2474d75e9501b6c168b3 (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
//go:build avr
// +build avr

package runtime

import (
	"device/avr"
	"machine"
	"runtime/interrupt"
	"runtime/volatile"
	"unsafe"
)

const BOARD = "arduino"

// timeUnit in nanoseconds
type timeUnit int64

// Watchdog timer periods. These can be off by a large margin (hence the jump
// between 64ms and 125ms which is not an exact double), so don't rely on this
// for accurate time keeping.
const (
	WDT_PERIOD_16MS = iota
	WDT_PERIOD_32MS
	WDT_PERIOD_64MS
	WDT_PERIOD_125MS
	WDT_PERIOD_250MS
	WDT_PERIOD_500MS
	WDT_PERIOD_1S
	WDT_PERIOD_2S
)

const timerRecalibrateInterval = 6e7 // 1 minute

var nextTimerRecalibrate timeUnit

//go:extern _sbss
var _sbss [0]byte

//go:extern _ebss
var _ebss [0]byte

//export main
func main() {
	preinit()
	initHardware()
	run()
	exit(0)
}

func preinit() {
	// Initialize .bss: zero-initialized global variables.
	ptr := unsafe.Pointer(&_sbss)
	for ptr != unsafe.Pointer(&_ebss) {
		*(*uint8)(ptr) = 0
		ptr = unsafe.Pointer(uintptr(ptr) + 1)
	}
}

func initHardware() {
	initUART()
	initMonotonicTimer()
	nextTimerRecalibrate = ticks() + timerRecalibrateInterval

	// Enable interrupts after initialization.
	avr.Asm("sei")
}

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

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

// Sleep this number of ticks of nanoseconds.
func sleepTicks(d timeUnit) {
	waitTill := ticks() + d
	for {
		// wait for interrupt
		avr.Asm("sleep")
		if waitTill <= ticks() {
			// done waiting
			return
		}
		if hasScheduler {
			// The interrupt may have awoken a goroutine, so bail out early.
			return
		}
	}
}

func ticks() (ticksReturn timeUnit) {
	state := interrupt.Disable()
	// use volatile since ticksCount can be changed when running on multi-core boards.
	ticksReturn = timeUnit(volatile.LoadUint64((*uint64)(unsafe.Pointer(&ticksCount))))
	interrupt.Restore(state)
	return
}

func exit(code int) {
	abort()
}

func abort() {
	// Disable interrupts and go to sleep.
	// This can never be awoken except for reset, and is recogized as termination by simavr.
	avr.Asm("cli")
	for {
		avr.Asm("sleep")
	}
}

var ticksCount int64                // nanoseconds since start
var nanosecondsInTick int64 = 16000 // nanoseconds per each tick

func initMonotonicTimer() {
	ticksCount = 0

	interrupt.New(avr.IRQ_TIMER0_OVF, func(i interrupt.Interrupt) {
		// use volatile
		ticks := volatile.LoadUint64((*uint64)(unsafe.Pointer(&ticksCount)))
		ticks += uint64(nanosecondsInTick)
		volatile.StoreUint64((*uint64)(unsafe.Pointer(&ticksCount)), ticks)
	})

	// initial initialization of the Timer0
	// - Mask interrupt
	avr.TIMSK0.ClearBits(avr.TIMSK0_TOIE0 | avr.TIMSK0_OCIE0A | avr.TIMSK0_OCIE0B)

	// - Write new values to TCNT2, OCR2x, and TCCR2x.
	avr.TCNT0.Set(0)
	avr.OCR0A.Set(0xff)
	// - Set mode 3
	avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01)
	// - Set prescaler 1
	avr.TCCR0B.Set(avr.TCCR0B_CS00)

	// - Unmask interrupt
	avr.TIMSK0.SetBits(avr.TIMSK0_TOIE0)
}

//go:linkname adjustMonotonicTimer machine.adjustMonotonicTimer
func adjustMonotonicTimer() {
	// adjust the nanosecondsInTick using volatile
	mask := interrupt.Disable()
	volatile.StoreUint64((*uint64)(unsafe.Pointer(&nanosecondsInTick)), uint64(currentNanosecondsInTick()))
	interrupt.Restore(mask)
}

func currentNanosecondsInTick() int64 {
	// this time depends on clk_IO, prescale, mode and OCR0A
	// assuming the clock source is CPU clock
	prescaler := int64(avr.TCCR0B.Get() & 0x7)
	clock := (int64(1e12) / prescaler) / int64(machine.CPUFrequency())
	mode := avr.TCCR0A.Get() & 0x7

	/*
	 Mode WGM02 WGM01 WGM00 Timer/Counter       TOP  Update of  TOV Flag
	                        Mode of Operation        OCRx at    Set on
	 0    0     0     0     Normal              0xFF Immediate  MAX
	 1    0     0     1     PWM, Phase Correct  0xFF TOP        BOTTOM
	 2    0     1     0     CTC                 OCRA Immediate  MAX
	 3    0     1     1     Fast PWM            0xFF BOTTOM     MAX
	 5    1     0     1     PWM, Phase Correct  OCRA TOP        BOTTOM
	 7    1     1     1     Fast PWM            OCRA BOTTOM     TOP
	*/
	switch mode {
	case 0, 3:
		// normal & fast PWM
		// TOV0 Interrupt when moving from MAX (0xff) to 0x00
		return clock * 256 / 1000
	case 1:
		// Phase Correct PWM
		// TOV0 Interrupt when moving from MAX (0xff) to 0x00
		return clock * 256 * 2 / 1000
	case 2, 7:
		// CTC & fast PWM
		// TOV0 Interrupt when moving from MAX (OCRA) to 0x00
		return clock * int64(avr.OCR0A.Get()) / 1000
	case 5:
		// Phase Correct PWM
		// TOV0 Interrupt when moving from MAX (OCRA) to 0x00
		return clock * int64(avr.OCR0A.Get()) * 2 / 1000
	}

	return clock / 1000 // for unknown
}