diff --git a/src/machine/machine_atmega.go b/src/machine/machine_atmega.go index e2854560..feba3b76 100644 --- a/src/machine/machine_atmega.go +++ b/src/machine/machine_atmega.go @@ -5,6 +5,8 @@ package machine import ( "device/avr" "runtime/interrupt" + "runtime/volatile" + "unsafe" ) // 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 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 +} diff --git a/src/machine/machine_atmega1284p.go b/src/machine/machine_atmega1284p.go index c80b0e3a..83a88906 100644 --- a/src/machine/machine_atmega1284p.go +++ b/src/machine/machine_atmega1284p.go @@ -69,3 +69,13 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) { 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} diff --git a/src/machine/machine_atmega2560.go b/src/machine/machine_atmega2560.go index f14731aa..d50f9081 100644 --- a/src/machine/machine_atmega2560.go +++ b/src/machine/machine_atmega2560.go @@ -126,3 +126,13 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) { 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} diff --git a/src/machine/machine_atmega328p.go b/src/machine/machine_atmega328p.go index ae1356e8..7eefc914 100644 --- a/src/machine/machine_atmega328p.go +++ b/src/machine/machine_atmega328p.go @@ -88,3 +88,13 @@ func (pwm PWM) Set(value uint16) { 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} diff --git a/src/machine/machine_atmega328pb.go b/src/machine/machine_atmega328pb.go new file mode 100644 index 00000000..38c26dfb --- /dev/null +++ b/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} diff --git a/src/machine/spi.go b/src/machine/spi.go index a10df267..e9e3c42d 100644 --- a/src/machine/spi.go +++ b/src/machine/spi.go @@ -1,4 +1,4 @@ -// +build !baremetal sam stm32,!stm32f7x2 fe310 k210 +// +build !baremetal sam stm32,!stm32f7x2 fe310 k210 atmega package machine @@ -13,7 +13,8 @@ const ( ) 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