avr: add attiny1616 support

This is just support for the chip, no boards are currently supported.
However, you can use this target on a custom board.

Notes:

  - This required a new runtime and machine implementation, because the
    hardware is actually very different (and much nicer than older
    AVRs!).
  - I had to update gen-device-avr to support this chip. This also
    affects the generated output of other AVRs, but I checked all chips
    we support and there shouldn't be any backwards incompatible
    changes.
  - I did not implement peripherals like UART, I2C, SPI, etc because I
    don't need them. That is left to do in the future.

You can flash these chips with only a UART and a 1kOhm resistor, which
is really nice (no special hardware needed). Here is the program I've
used for this purpose: https://pypi.org/project/pymcuprog/
This commit is contained in:
Ayke van Laethem
2023-05-18 00:13:43 +02:00
committed by Ron Evans
parent 4d11d552db
commit 2fb866ca86
12 changed files with 472 additions and 2 deletions
+11
View File
@@ -0,0 +1,11 @@
package main
import "time"
// This is used for smoke tests for chips without an associated board.
func main() {
for {
time.Sleep(time.Second)
}
}
+52
View File
@@ -0,0 +1,52 @@
//go:build attiny1616
package machine
import (
"device/avr"
)
const (
portA Pin = iota * 8
portB
portC
)
const (
PA0 = portA + 0
PA1 = portA + 1
PA2 = portA + 2
PA3 = portA + 3
PA4 = portA + 4
PA5 = portA + 5
PA6 = portA + 6
PA7 = portA + 7
PB0 = portB + 0
PB1 = portB + 1
PB2 = portB + 2
PB3 = portB + 3
PB4 = portB + 4
PB5 = portB + 5
PB6 = portB + 6
PB7 = portB + 7
PC0 = portC + 0
PC1 = portC + 1
PC2 = portC + 2
PC3 = portC + 3
PC4 = portC + 4
PC5 = portC + 5
PC6 = portC + 6
PC7 = portC + 7
)
// getPortMask returns the PORT peripheral and mask for the pin.
func (p Pin) getPortMask() (*avr.PORT_Type, uint8) {
switch {
case p >= PA0 && p <= PA7: // port A
return avr.PORTA, 1 << uint8(p-portA)
case p >= PB0 && p <= PB7: // port B
return avr.PORTB, 1 << uint8(p-portB)
default: // port C
return avr.PORTC, 1 << uint8(p-portC)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build avr
//go:build avr && !avrtiny
package machine
+60
View File
@@ -0,0 +1,60 @@
//go:build avrtiny
package machine
import "device/avr"
const deviceName = avr.DEVICE
const (
PinInput PinMode = iota
PinInputPullup
PinOutput
)
// Configure sets the pin to input or output.
func (p Pin) Configure(config PinConfig) {
port, mask := p.getPortMask()
if config.Mode == PinOutput {
// set output bit
port.DIRSET.Set(mask)
// Note: if the pin was PinInputPullup before, it'll now be high.
// Otherwise it will be low.
} else {
// configure input: clear output bit
port.DIRCLR.Set(mask)
if config.Mode == PinInput {
// No pullup (floating).
// The transition may be one of the following:
// output high -> input pullup -> input (safe: output high and input pullup are similar)
// output low -> input -> input (safe: no extra transition)
port.OUTCLR.Set(mask)
} else {
// Pullup.
// The transition may be one of the following:
// output high -> input pullup -> input pullup (safe: no extra transition)
// output low -> input -> input pullup (possibly problematic)
// For the last transition (output low -> input -> input pullup),
// the transition may be problematic in some cases because there is
// an intermediate floating state (which may cause irratic
// interrupts, for example). If this is a problem, the application
// should set the pin high before configuring it as PinInputPullup.
// We can't do that here because setting it to high as an
// intermediate state may have other problems.
port.OUTSET.Set(mask)
}
}
}
// Set changes the value of the GPIO pin. The pin must be configured as output.
func (p Pin) Set(high bool) {
port, mask := p.getPortMask()
if high {
port.OUTSET.Set(mask)
} else {
port.OUTCLR.Set(mask)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build avr
//go:build avr && !avrtiny
package runtime
+191
View File
@@ -0,0 +1,191 @@
//go:build avrtiny
// Runtime for the newer AVRs introduced since around 2016 that work quite
// different from older AVRs like the atmega328p or even the attiny85.
// Because of these large differences, a new runtime and machine implementation
// is needed.
// Some key differences:
// * Peripherals are now logically separated, instead of all mixed together as
// one big bag of registers. No PORTA/DDRA etc registers anymore, instead a
// real PORT peripheral type with multiple instances.
// * There is a real RTC now! No need for using one of the timers as a time
// source, which then conflicts with using it as a PWM.
// * Flash and RAM are now in the same address space! This avoids the need for
// PROGMEM which couldn't (easily) be supported in Go anyway. Constant
// globals just get stored in flash, like on Cortex-M chips.
package runtime
import (
"device/avr"
"runtime/interrupt"
"runtime/volatile"
)
type timeUnit int64
//export main
func main() {
// Initialize RTC.
for avr.RTC.STATUS.Get() != 0 {
}
avr.RTC.CTRLA.Set(avr.RTC_CTRLA_RTCEN | avr.RTC_CTRLA_RUNSTDBY)
avr.RTC.INTCTRL.Set(avr.RTC_INTCTRL_OVF) // enable overflow interrupt
interrupt.New(avr.IRQ_RTC_CNT, rtcInterrupt)
// Configure sleep:
// - enable sleep mode
// - set sleep mode to STANDBY (mode 0x1)
avr.SLPCTRL.CTRLA.Set(avr.SLPCTRL_CTRLA_SEN | 0x1<<1)
// Enable interrupts after initialization.
avr.Asm("sei")
run()
exit(0)
}
func initUART() {
// no UART configured
}
func putchar(b byte) {
// no-op
}
// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds.
func ticksToNanoseconds(ticks timeUnit) int64 {
// The following calculation is actually the following, but with both sides
// reduced to reduce the risk of overflow:
// ticks * 1e9 / 32768
return int64(ticks) * 1953125 / 64
}
// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz).
func nanosecondsToTicks(ns int64) timeUnit {
// The following calculation is actually the following, but with both sides
// reduced to reduce the risk of overflow:
// ns * 32768 / 1e9
return timeUnit(ns * 64 / 1953125)
}
// Sleep for the given number of timer ticks.
func sleepTicks(d timeUnit) {
ticksStart := ticks()
sleepUntil := ticksStart + d
// Sleep until we're in the right 2-second interval.
for {
avr.Asm("cli")
overflows := rtcOverflows.Get()
if overflows >= uint32(sleepUntil>>16) {
// We're in the right 2-second interval.
// At this point we know that the difference between ticks() and
// sleepUntil is ≤0xffff.
avr.Asm("sei")
break
}
// Sleep some more, because we're not there yet.
avr.Asm("sei\nsleep")
}
// Now we know the sleep duration is small enough to fit in rtc.CNT.
// Update rtc.CMP (atomically).
cnt := uint16(sleepUntil)
low := uint8(cnt)
high := uint8(cnt >> 8)
avr.RTC.CMPH.Set(high)
avr.RTC.CMPL.Set(low)
// Disable interrupts, so we can change interrupt settings without racing.
avr.Asm("cli")
// Enable the CMP interrupt.
avr.RTC.INTCTRL.Set(avr.RTC_INTCTRL_OVF | avr.RTC_INTCTRL_CMP)
// Check whether we already reached CNT, in which case the interrupt may
// have triggered already (but maybe not, it's a race condition).
low2 := avr.RTC.CNTL.Get()
high2 := avr.RTC.CNTH.Get()
cnt2 := uint16(high2)<<8 | uint16(low2)
if cnt2 < cnt {
// We have not, so wait until the interrupt happens.
for {
// Sleep until the next interrupt happens.
avr.Asm("sei\nsleep\ncli")
if cmpMatch.Get() != 0 {
// The CMP interrupt occured, so we have slept long enough.
cmpMatch.Set(0)
break
}
}
}
// Disable the CMP interrupt, and restore things like they were before.
avr.RTC.INTCTRL.Set(avr.RTC_INTCTRL_OVF)
avr.Asm("sei")
}
// Number of RTC overflows, updated in the RTC interrupt handler.
// The RTC is running at 32768Hz so an overflow happens every 2 seconds. A
// 32-bit integer is large enough to run for about 279 years.
var rtcOverflows volatile.Register32
// Set to one in the RTC CMP interrupt, to signal the expected number of ticks
// have passed.
var cmpMatch volatile.Register8
// Return the number of RTC ticks that happened since reset.
func ticks() timeUnit {
var ovf uint32
var count uint16
for {
// Get the tick count and overflow value, in a 4-step process to avoid a
// race with the overflow interrupt.
mask := interrupt.Disable()
// 1. Get the overflow value.
ovf = rtcOverflows.Get()
// 2. Read the RTC counter.
// This way of reading is atomic (due to the TEMP register).
low := avr.RTC.CNTL.Get()
high := avr.RTC.CNTH.Get()
// 3. Get the interrupt flags.
intflags := avr.RTC.INTFLAGS.Get()
interrupt.Restore(mask)
// 4. Check whether an overflow happened somewhere in the last three
// steps. If so, just repeat the loop.
if intflags&avr.RTC_INTFLAGS_OVF == 0 {
count = uint16(high)<<8 | uint16(low)
break
}
}
// Create the 64-bit tick count, combining the two.
return timeUnit(ovf)<<16 | timeUnit(count)
}
// Interrupt handler for the RTC.
// It happens every two seconds, and while sleeping using the CMP interrupt.
func rtcInterrupt(interrupt.Interrupt) {
flags := avr.RTC.INTFLAGS.Get()
if flags&avr.RTC_INTFLAGS_OVF != 0 {
rtcOverflows.Set(rtcOverflows.Get() + 1)
}
if flags&avr.RTC_INTFLAGS_CMP != 0 {
cmpMatch.Set(1)
}
avr.RTC.INTFLAGS.Set(flags) // clear interrupts
}
func exit(code int) {
abort()
}
//export __vector_default
func abort()