
We have an optimization for this specific pattern, but it's really just a hack. With the addition of unsafe.Add in Go 1.17 we can directly specify the intent instead and eventually remove this special case. The code is also easier to read.
188 строки
4,6 КиБ
Go
188 строки
4,6 КиБ
Go
//go: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.Add(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
|
|
}
|