Add SPI support for Atmega based chips.
This is based on @Nerzal's #1398 PR, but is a bit of a refactor and expansion to support all the Atmega based chips present in tinygo.
Этот коммит содержится в:
родитель
9f5bd2c460
коммит
f4b4dd8d62
6 изменённых файлов: 249 добавлений и 2 удалений
|
@ -5,6 +5,8 @@ package machine
|
||||||
import (
|
import (
|
||||||
"device/avr"
|
"device/avr"
|
||||||
"runtime/interrupt"
|
"runtime/interrupt"
|
||||||
|
"runtime/volatile"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2CConfig is used to store config info for I2C.
|
// I2CConfig is used to store config info for I2C.
|
||||||
|
@ -157,3 +159,108 @@ func (uart UART) WriteByte(c byte) error {
|
||||||
avr.UDR0.Set(c) // send char
|
avr.UDR0.Set(c) // send char
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SPIConfig is used to store config info for SPI.
|
||||||
|
type SPIConfig struct {
|
||||||
|
Frequency uint32
|
||||||
|
LSBFirst bool
|
||||||
|
Mode uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPI is for the Serial Peripheral Interface
|
||||||
|
// Data is taken from http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf page 169 and following
|
||||||
|
type SPI struct {
|
||||||
|
// The registers for the SPIx port set by the chip
|
||||||
|
spcr *volatile.Register8
|
||||||
|
spdr *volatile.Register8
|
||||||
|
spsr *volatile.Register8
|
||||||
|
|
||||||
|
// The io pins for the SPIx port set by the chip
|
||||||
|
sck Pin
|
||||||
|
sdi Pin
|
||||||
|
sdo Pin
|
||||||
|
cs Pin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure is intended to setup the SPI interface.
|
||||||
|
func (s SPI) Configure(config SPIConfig) error {
|
||||||
|
|
||||||
|
// This is only here to help catch a bug with the configuration
|
||||||
|
// where a machine missed a value.
|
||||||
|
if s.spcr == (*volatile.Register8)(unsafe.Pointer(uintptr(0))) ||
|
||||||
|
s.spsr == (*volatile.Register8)(unsafe.Pointer(uintptr(0))) ||
|
||||||
|
s.spdr == (*volatile.Register8)(unsafe.Pointer(uintptr(0))) ||
|
||||||
|
s.sck == 0 || s.sdi == 0 || s.sdo == 0 || s.cs == 0 {
|
||||||
|
return errSPIInvalidMachineConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the defaults meaningful
|
||||||
|
if config.Frequency == 0 {
|
||||||
|
config.Frequency = 4000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default all port configuration bits to 0 for simplicity
|
||||||
|
s.spcr.Set(0)
|
||||||
|
s.spsr.Set(0)
|
||||||
|
|
||||||
|
// Setup pins output configuration
|
||||||
|
s.sck.Configure(PinConfig{Mode: PinOutput})
|
||||||
|
s.sdi.Configure(PinConfig{Mode: PinInput})
|
||||||
|
s.sdo.Configure(PinConfig{Mode: PinOutput})
|
||||||
|
|
||||||
|
// Prevent CS glitches if the pin is enabled Low (0, default)
|
||||||
|
s.cs.High()
|
||||||
|
// If the CS pin is not configured as output the SPI port operates in
|
||||||
|
// slave mode.
|
||||||
|
s.cs.Configure(PinConfig{Mode: PinOutput})
|
||||||
|
|
||||||
|
frequencyDivider := CPUFrequency() / config.Frequency
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case frequencyDivider >= 128:
|
||||||
|
s.spcr.SetBits(avr.SPCR_SPR0 | avr.SPCR_SPR1)
|
||||||
|
case frequencyDivider >= 64:
|
||||||
|
s.spcr.SetBits(avr.SPCR_SPR1)
|
||||||
|
case frequencyDivider >= 32:
|
||||||
|
s.spcr.SetBits(avr.SPCR_SPR1)
|
||||||
|
s.spsr.SetBits(avr.SPSR_SPI2X)
|
||||||
|
case frequencyDivider >= 16:
|
||||||
|
s.spcr.SetBits(avr.SPCR_SPR0)
|
||||||
|
case frequencyDivider >= 8:
|
||||||
|
s.spcr.SetBits(avr.SPCR_SPR0)
|
||||||
|
s.spsr.SetBits(avr.SPSR_SPI2X)
|
||||||
|
case frequencyDivider >= 4:
|
||||||
|
// The clock is already set to all 0's.
|
||||||
|
default: // defaults to fastest which is /2
|
||||||
|
s.spsr.SetBits(avr.SPSR_SPI2X)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch config.Mode {
|
||||||
|
case Mode1:
|
||||||
|
s.spcr.SetBits(avr.SPCR_CPHA)
|
||||||
|
case Mode2:
|
||||||
|
s.spcr.SetBits(avr.SPCR_CPOL)
|
||||||
|
case Mode3:
|
||||||
|
s.spcr.SetBits(avr.SPCR_CPHA | avr.SPCR_CPOL)
|
||||||
|
default: // default is mode 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.LSBFirst {
|
||||||
|
s.spcr.SetBits(avr.SPCR_DORD)
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable SPI, set controller, set clock rate
|
||||||
|
s.spcr.SetBits(avr.SPCR_SPE | avr.SPCR_MSTR)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer writes the byte into the register and returns the read content
|
||||||
|
func (s SPI) Transfer(b byte) (byte, error) {
|
||||||
|
s.spdr.Set(uint8(b))
|
||||||
|
|
||||||
|
for !s.spsr.HasBits(avr.SPSR_SPIF) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return byte(s.spdr.Get()), nil
|
||||||
|
}
|
||||||
|
|
|
@ -69,3 +69,13 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) {
|
||||||
return avr.PORTD, 1 << uint8(p-portD)
|
return avr.PORTD, 1 << uint8(p-portD)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SPI configuration
|
||||||
|
var SPI0 = SPI{
|
||||||
|
spcr: avr.SPCR,
|
||||||
|
spsr: avr.SPSR,
|
||||||
|
spdr: avr.SPDR,
|
||||||
|
sck: PB7,
|
||||||
|
sdo: PB5,
|
||||||
|
sdi: PB6,
|
||||||
|
cs: PB4}
|
||||||
|
|
|
@ -126,3 +126,13 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) {
|
||||||
return avr.PORTA, 255
|
return avr.PORTA, 255
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SPI configuration
|
||||||
|
var SPI0 = SPI{
|
||||||
|
spcr: avr.SPCR,
|
||||||
|
spdr: avr.SPDR,
|
||||||
|
spsr: avr.SPSR,
|
||||||
|
sck: PB1,
|
||||||
|
sdo: PB2,
|
||||||
|
sdi: PB3,
|
||||||
|
cs: PB0}
|
||||||
|
|
|
@ -88,3 +88,13 @@ func (pwm PWM) Set(value uint16) {
|
||||||
panic("Invalid PWM pin")
|
panic("Invalid PWM pin")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SPI configuration
|
||||||
|
var SPI0 = SPI{
|
||||||
|
spcr: avr.SPCR,
|
||||||
|
spdr: avr.SPDR,
|
||||||
|
spsr: avr.SPSR,
|
||||||
|
sck: PB5,
|
||||||
|
sdo: PB3,
|
||||||
|
sdi: PB4,
|
||||||
|
cs: PB2}
|
||||||
|
|
109
src/machine/machine_atmega328pb.go
Обычный файл
109
src/machine/machine_atmega328pb.go
Обычный файл
|
@ -0,0 +1,109 @@
|
||||||
|
// +build avr,atmega328pb
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device/avr"
|
||||||
|
"runtime/volatile"
|
||||||
|
)
|
||||||
|
|
||||||
|
const irq_USART0_RX = avr.IRQ_USART0_RX
|
||||||
|
|
||||||
|
// getPortMask returns the PORTx register and mask for the pin.
|
||||||
|
func (p Pin) getPortMask() (*volatile.Register8, uint8) {
|
||||||
|
switch {
|
||||||
|
case p >= PB0 && p <= PB7: // port B
|
||||||
|
return avr.PORTB, 1 << uint8(p-portB)
|
||||||
|
case p >= PC0 && p <= PC7: // port C
|
||||||
|
return avr.PORTC, 1 << uint8(p-portC)
|
||||||
|
default: // port D
|
||||||
|
return avr.PORTD, 1 << uint8(p-portD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitPWM initializes the registers needed for PWM.
|
||||||
|
func InitPWM() {
|
||||||
|
// use waveform generation
|
||||||
|
avr.TCCR0A.SetBits(avr.TCCR0A_WGM00)
|
||||||
|
|
||||||
|
// set timer 0 prescale factor to 64
|
||||||
|
avr.TCCR0B.SetBits(avr.TCCR0B_CS01 | avr.TCCR0B_CS00)
|
||||||
|
|
||||||
|
// set timer 1 prescale factor to 64
|
||||||
|
avr.TCCR1B.SetBits(avr.TCCR1B_CS11)
|
||||||
|
|
||||||
|
// put timer 1 in 8-bit phase correct pwm mode
|
||||||
|
avr.TCCR1A.SetBits(avr.TCCR1A_WGM10)
|
||||||
|
|
||||||
|
// set timer 2 prescale factor to 64
|
||||||
|
avr.TCCR2B.SetBits(avr.TCCR2B_CS22)
|
||||||
|
|
||||||
|
// configure timer 2 for phase correct pwm (8-bit)
|
||||||
|
avr.TCCR2A.SetBits(avr.TCCR2A_WGM20)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure configures a PWM pin for output.
|
||||||
|
func (pwm PWM) Configure() error {
|
||||||
|
switch pwm.Pin / 8 {
|
||||||
|
case 0: // port B
|
||||||
|
avr.DDRB.SetBits(1 << uint8(pwm.Pin))
|
||||||
|
case 2: // port D
|
||||||
|
avr.DDRD.SetBits(1 << uint8(pwm.Pin-16))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a
|
||||||
|
// 8-bit value ranging from 0 to 255.
|
||||||
|
func (pwm PWM) Set(value uint16) {
|
||||||
|
value8 := uint8(value >> 8)
|
||||||
|
switch pwm.Pin {
|
||||||
|
case PD3:
|
||||||
|
// connect pwm to pin on timer 2, channel B
|
||||||
|
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1)
|
||||||
|
avr.OCR2B.Set(value8) // set pwm duty
|
||||||
|
case PD5:
|
||||||
|
// connect pwm to pin on timer 0, channel B
|
||||||
|
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1)
|
||||||
|
avr.OCR0B.Set(value8) // set pwm duty
|
||||||
|
case PD6:
|
||||||
|
// connect pwm to pin on timer 0, channel A
|
||||||
|
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1)
|
||||||
|
avr.OCR0A.Set(value8) // set pwm duty
|
||||||
|
case PB1:
|
||||||
|
// connect pwm to pin on timer 1, channel A
|
||||||
|
avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1)
|
||||||
|
// this is a 16-bit value, but we only currently allow the low order bits to be set
|
||||||
|
avr.OCR1AL.Set(value8) // set pwm duty
|
||||||
|
case PB2:
|
||||||
|
// connect pwm to pin on timer 1, channel B
|
||||||
|
avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1)
|
||||||
|
// this is a 16-bit value, but we only currently allow the low order bits to be set
|
||||||
|
avr.OCR1BL.Set(value8) // set pwm duty
|
||||||
|
case PB3:
|
||||||
|
// connect pwm to pin on timer 2, channel A
|
||||||
|
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1)
|
||||||
|
avr.OCR2A.Set(value8) // set pwm duty
|
||||||
|
default:
|
||||||
|
panic("Invalid PWM pin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPI configuration
|
||||||
|
var SPI0 = SPI{
|
||||||
|
spcr: avr.SPCR0,
|
||||||
|
spdr: avr.SPDR0,
|
||||||
|
spsr: avr.SPSR0,
|
||||||
|
sck: PB5,
|
||||||
|
sdo: PB3,
|
||||||
|
sdi: PB4,
|
||||||
|
cs: PB2}
|
||||||
|
|
||||||
|
var SPI1 = SPI{
|
||||||
|
spcr: avr.SPCR1,
|
||||||
|
spdr: avr.SPDR1,
|
||||||
|
spsr: avr.SPSR1,
|
||||||
|
sck: PC1,
|
||||||
|
sdo: PE3,
|
||||||
|
sdi: PC0,
|
||||||
|
cs: PE2}
|
|
@ -1,4 +1,4 @@
|
||||||
// +build !baremetal sam stm32,!stm32f7x2 fe310 k210
|
// +build !baremetal sam stm32,!stm32f7x2 fe310 k210 atmega
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ const (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size")
|
ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size")
|
||||||
|
errSPIInvalidMachineConfig = errors.New("SPI port was not configured properly by the machine")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read
|
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче