diff --git a/src/device/avr/avr_tiny85.go b/src/device/avr/avr_tiny85.go new file mode 100644 index 00000000..645914e4 --- /dev/null +++ b/src/device/avr/avr_tiny85.go @@ -0,0 +1,16 @@ +//go:build avr && attiny85 +// +build avr,attiny85 + +package avr + +// The attiny85 have only TIMSK when other AVR boards have TIMSK0 and TIMSK1, etc. +// Create an alias of TIMSK, TIMSK_OCIE0A, TIMSK_OCIE0B and TIMSK_TOIE0 so we can use +// common code to mask interrupts. + +var TIMSK0 = TIMSK + +const ( + TIMSK0_OCIE0A = TIMSK_OCIE0A + TIMSK0_OCIE0B = TIMSK_OCIE0B + TIMSK0_TOIE0 = TIMSK_TOIE0 +) diff --git a/src/machine/machine_atmega1280.go b/src/machine/machine_atmega1280.go index cc63d1e0..62d58659 100644 --- a/src/machine/machine_atmega1280.go +++ b/src/machine/machine_atmega1280.go @@ -179,6 +179,9 @@ func (pwm PWM) Configure(config PWMConfig) error { avr.TCCR0B.Set(prescaler) // Set the PWM mode to fast PWM (mode = 3). avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) + // monotonic timer is using the same time as PWM:0 + // we must adust internal settings of monotonic timer when PWM:0 settings changed + AdjustMonotonicTimer() } else { avr.TCCR2B.Set(prescaler) // Set the PWM mode to fast PWM (mode = 3). @@ -714,6 +717,9 @@ func (pwm PWM) Set(channel uint8, value uint32) { avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) } } + // monotonic timer is using the same time as PWM:0 + // we must adust internal settings of monotonic timer when PWM:0 settings changed + AdjustMonotonicTimer() case 1: mask := interrupt.Disable() switch channel { diff --git a/src/machine/machine_atmega328p.go b/src/machine/machine_atmega328p.go index fe5ace95..390b69f4 100644 --- a/src/machine/machine_atmega328p.go +++ b/src/machine/machine_atmega328p.go @@ -69,6 +69,9 @@ func (pwm PWM) Configure(config PWMConfig) error { avr.TCCR0B.Set(prescaler) // Set the PWM mode to fast PWM (mode = 3). avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) + // monotonic timer is using the same time as PWM:0 + // we must adust internal settings of monotonic timer when PWM:0 settings changed + AdjustMonotonicTimer() } else { avr.TCCR2B.Set(prescaler) // Set the PWM mode to fast PWM (mode = 3). @@ -395,6 +398,9 @@ func (pwm PWM) Set(channel uint8, value uint32) { avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) } } + // monotonic timer is using the same time as PWM:0 + // we must adust internal settings of monotonic timer when PWM:0 settings changed + AdjustMonotonicTimer() case 1: mask := interrupt.Disable() switch channel { diff --git a/src/machine/machine_avr.go b/src/machine/machine_avr.go index 22863846..b791594f 100644 --- a/src/machine/machine_avr.go +++ b/src/machine/machine_avr.go @@ -4,6 +4,7 @@ package machine import ( "device/avr" + "runtime/interrupt" "runtime/volatile" "unsafe" ) @@ -142,3 +143,76 @@ func (a ADC) Get() uint16 { return uint16(avr.ADCL.Get()) | uint16(avr.ADCH.Get())<<8 } + +var Ticks int64 // nanoseconds since start +var tickNanos int64 // nanoseconds per each tick + +func InitMonotonicTimer() { + tickNanos = 0 + Ticks = 0 + + interrupt.New(avr.IRQ_TIMER0_OVF, func(i interrupt.Interrupt) { + Ticks += tickNanos + }) + + // 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) + + AdjustMonotonicTimer() + + // - Unmask interrupt + avr.TIMSK0.SetBits(avr.TIMSK0_TOIE0) +} + +func AdjustMonotonicTimer() { + // adjust the tickNanos + tickNanos = currentTickNanos() +} + +func currentTickNanos() 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(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 +} diff --git a/src/runtime/runtime_avr.go b/src/runtime/runtime_avr.go index 492390c4..2937efc8 100644 --- a/src/runtime/runtime_avr.go +++ b/src/runtime/runtime_avr.go @@ -4,14 +4,15 @@ package runtime import ( "device/avr" + "machine" + "runtime/interrupt" "unsafe" ) const BOARD = "arduino" -type timeUnit uint32 - -var currentTime timeUnit +// 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 @@ -27,6 +28,10 @@ const ( WDT_PERIOD_2S ) +const timerRecalibrateInterval = 6e7 // 1 minute + +var nextTimerRecalibrate timeUnit + //go:extern _sbss var _sbss [0]byte @@ -56,32 +61,49 @@ func postinit() { func init() { initUART() + machine.InitMonotonicTimer() + nextTimerRecalibrate = ticks() + timerRecalibrateInterval } -const tickNanos = 1024 * 16384 // roughly 16ms in nanoseconds - func ticksToNanoseconds(ticks timeUnit) int64 { - return int64(ticks) * tickNanos + return int64(ticks) } func nanosecondsToTicks(ns int64) timeUnit { - return timeUnit(ns / tickNanos) + return timeUnit(ns) } -// Sleep this number of ticks of 16ms. -// -// TODO: not very accurate. Improve accuracy by calibrating on startup and every -// once in a while. +// Sleep this number of ticks of nanoseconds. func sleepTicks(d timeUnit) { - currentTime += d - for d != 0 { - sleepWDT(WDT_PERIOD_16MS) - d -= 1 + waitTill := ticks() + d + // recalibrate if we have some time (>100ms) and it was a while when we did it last time. + if d > 100000 { + now := waitTill - d + if nextTimerRecalibrate < now { + nextTimerRecalibrate = now + timerRecalibrateInterval + machine.AdjustMonotonicTimer() + } + } + 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() timeUnit { - return currentTime +// ticks return time since start in nanoseconds +func ticks() (ticks timeUnit) { + state := interrupt.Disable() + ticks = timeUnit(machine.Ticks) + interrupt.Restore(state) + return } func exit(code int) {