From 3fa926c5123651194c99fb3944d16bc79bcd373f Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 26 Sep 2019 15:58:37 +0200 Subject: [PATCH] machine/atsamd21: refactor SPI pin handling to only look at pin numbers Pin mode and pad numbers are automatically calculated from the pin numbers, returning an error if no pinout could be found. --- src/machine/board_arduino_nano33.go | 11 +- src/machine/board_circuitplay_express.go | 11 +- src/machine/board_feather-m0.go | 11 +- src/machine/board_itsybitsy-m0.go | 22 +-- src/machine/board_trinket.go | 11 +- src/machine/machine.go | 7 + src/machine/machine_atsamd21.go | 200 +++++++++++++++++++---- 7 files changed, 202 insertions(+), 71 deletions(-) diff --git a/src/machine/board_arduino_nano33.go b/src/machine/board_arduino_nano33.go index 2802fe79..00cc387c 100644 --- a/src/machine/board_arduino_nano33.go +++ b/src/machine/board_arduino_nano33.go @@ -125,13 +125,10 @@ const ( // SPI on the Arduino Nano 33. var ( - SPI0 = SPI{Bus: sam.SERCOM0_SPI, - SCK: SPI0_SCK_PIN, - MOSI: SPI0_MOSI_PIN, - MISO: SPI0_MISO_PIN, - DOpad: spiTXPad2SCK3, - DIpad: sercomRXPad0, - PinMode: PinSERCOM} + SPI0 = SPI{ + Bus: sam.SERCOM0_SPI, + SERCOM: 0, + } ) // I2S pins diff --git a/src/machine/board_circuitplay_express.go b/src/machine/board_circuitplay_express.go index e0691442..ae1bca9d 100644 --- a/src/machine/board_circuitplay_express.go +++ b/src/machine/board_circuitplay_express.go @@ -114,13 +114,10 @@ const ( // SPI on the Circuit Playground Express. var ( - SPI0 = SPI{Bus: sam.SERCOM3_SPI, - SCK: SPI0_SCK_PIN, - MOSI: SPI0_MOSI_PIN, - MISO: SPI0_MISO_PIN, - DOpad: spiTXPad2SCK3, - DIpad: sercomRXPad0, - PinMode: PinSERCOMAlt} + SPI0 = SPI{ + Bus: sam.SERCOM3_SPI, + SERCOM: 3, + } ) // I2S pins diff --git a/src/machine/board_feather-m0.go b/src/machine/board_feather-m0.go index 7aafa29f..8c4dc759 100644 --- a/src/machine/board_feather-m0.go +++ b/src/machine/board_feather-m0.go @@ -88,13 +88,10 @@ const ( // SPI on the Feather M0. var ( - SPI0 = SPI{Bus: sam.SERCOM4_SPI, - SCK: SPI0_SCK_PIN, - MOSI: SPI0_MOSI_PIN, - MISO: SPI0_MISO_PIN, - DOpad: spiTXPad2SCK3, - DIpad: sercomRXPad0, - PinMode: PinSERCOMAlt} + SPI0 = SPI{ + Bus: sam.SERCOM4_SPI, + SERCOM: 4, + } ) // I2S pins diff --git a/src/machine/board_itsybitsy-m0.go b/src/machine/board_itsybitsy-m0.go index 89c42749..48a7a095 100644 --- a/src/machine/board_itsybitsy-m0.go +++ b/src/machine/board_itsybitsy-m0.go @@ -90,13 +90,10 @@ const ( // SPI on the ItsyBitsy M0. var ( - SPI0 = SPI{Bus: sam.SERCOM4_SPI, - SCK: SPI0_SCK_PIN, - MOSI: SPI0_MOSI_PIN, - MISO: SPI0_MISO_PIN, - DOpad: spiTXPad2SCK3, - DIpad: sercomRXPad0, - PinMode: PinSERCOMAlt} + SPI0 = SPI{ + Bus: sam.SERCOM4_SPI, + SERCOM: 4, + } ) // "Internal" SPI pins; SPI flash is attached to these on ItsyBitsy M0 @@ -109,13 +106,10 @@ const ( // "Internal" SPI on Sercom 5 var ( - SPI1 = SPI{Bus: sam.SERCOM5_SPI, - SCK: SPI1_SCK_PIN, - MOSI: SPI1_MOSI_PIN, - MISO: SPI1_MISO_PIN, - DOpad: spiTXPad2SCK3, - DIpad: sercomRXPad1, - PinMode: PinSERCOMAlt} + SPI1 = SPI{ + Bus: sam.SERCOM5_SPI, + SERCOM: 5, + } ) // I2S pins diff --git a/src/machine/board_trinket.go b/src/machine/board_trinket.go index 9e05e359..7eec1f6f 100644 --- a/src/machine/board_trinket.go +++ b/src/machine/board_trinket.go @@ -65,13 +65,10 @@ const ( // SPI on the Trinket M0. var ( - SPI0 = SPI{Bus: sam.SERCOM0_SPI, - SCK: SPI0_SCK_PIN, - MOSI: SPI0_MOSI_PIN, - MISO: SPI0_MISO_PIN, - DOpad: spiTXPad2SCK3, - DIpad: sercomRXPad0, - PinMode: PinSERCOMAlt} + SPI0 = SPI{ + Bus: sam.SERCOM0_SPI, + SERCOM: 0, + } ) // I2C pins diff --git a/src/machine/machine.go b/src/machine/machine.go index 6b992bef..e55b522d 100644 --- a/src/machine/machine.go +++ b/src/machine/machine.go @@ -1,5 +1,12 @@ package machine +import "errors" + +var ( + ErrInvalidInputPin = errors.New("machine: invalid input pin") + ErrInvalidOutputPin = errors.New("machine: invalid output pin") +) + type PinConfig struct { Mode PinMode } diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index 1375bdeb..5c409d1b 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -103,6 +103,117 @@ const ( PB31 Pin = 63 ) +const ( + pinPadMapSERCOM0Pad0 byte = (0x10 << 1) | 0x00 + pinPadMapSERCOM1Pad0 byte = (0x20 << 1) | 0x00 + pinPadMapSERCOM2Pad0 byte = (0x30 << 1) | 0x00 + pinPadMapSERCOM3Pad0 byte = (0x40 << 1) | 0x00 + pinPadMapSERCOM4Pad0 byte = (0x50 << 1) | 0x00 + pinPadMapSERCOM5Pad0 byte = (0x60 << 1) | 0x00 + pinPadMapSERCOM0Pad2 byte = (0x10 << 1) | 0x10 + pinPadMapSERCOM1Pad2 byte = (0x20 << 1) | 0x10 + pinPadMapSERCOM2Pad2 byte = (0x30 << 1) | 0x10 + pinPadMapSERCOM3Pad2 byte = (0x40 << 1) | 0x10 + pinPadMapSERCOM4Pad2 byte = (0x50 << 1) | 0x10 + pinPadMapSERCOM5Pad2 byte = (0x60 << 1) | 0x10 + + pinPadMapSERCOM0AltPad0 byte = (0x01 << 1) | 0x00 + pinPadMapSERCOM1AltPad0 byte = (0x02 << 1) | 0x00 + pinPadMapSERCOM2AltPad0 byte = (0x03 << 1) | 0x00 + pinPadMapSERCOM3AltPad0 byte = (0x04 << 1) | 0x00 + pinPadMapSERCOM4AltPad0 byte = (0x05 << 1) | 0x00 + pinPadMapSERCOM5AltPad0 byte = (0x06 << 1) | 0x00 + pinPadMapSERCOM0AltPad2 byte = (0x01 << 1) | 0x01 + pinPadMapSERCOM1AltPad2 byte = (0x02 << 1) | 0x01 + pinPadMapSERCOM2AltPad2 byte = (0x03 << 1) | 0x01 + pinPadMapSERCOM3AltPad2 byte = (0x04 << 1) | 0x01 + pinPadMapSERCOM4AltPad2 byte = (0x05 << 1) | 0x01 + pinPadMapSERCOM5AltPad2 byte = (0x06 << 1) | 0x01 +) + +// pinPadMapping lists which pins have which SERCOMs attached to them. +// The encoding is rather dense, with each byte encoding two pins and both +// SERCOM and SERCOM-ALT. +// +// Observations: +// * There are six SERCOMs. Those SERCOM numbers can be encoded in 3 bits. +// * Even pad numbers are always on even pins, and odd pad numbers are always on +// odd pins. +// * Pin pads come in pairs. If PA00 has pad 0, then PA01 has pad 1. +// With this information, we can encode SERCOM pin/pad numbers much more +// efficiently. First of all, due to pads coming in pairs, we can ignore half +// the pins: the information for an odd pin can be calculated easily from the +// preceding even pin. And second, if odd pads are always on odd pins and even +// pads on even pins, we can drop a single bit from the pad number. +// +// Each byte below is split in two nibbles. The 4 high bits are for SERCOM and +// the 4 low bits are for SERCOM-ALT. Of each nibble, the 3 high bits encode the +// SERCOM + 1 while the low bit encodes whether this is PAD0 or PAD2 (0 means +// PAD0, 1 means PAD2). It encodes SERCOM + 1 instead of just the SERCOM number, +// to make it easy to check whether a nibble is set at all. +var pinPadMapping = [32]byte{ + // page 21 + PA00 / 2: 0 | pinPadMapSERCOM1AltPad0, + PB08 / 2: 0 | pinPadMapSERCOM4AltPad0, + PA04 / 2: 0 | pinPadMapSERCOM0AltPad0, + PA06 / 2: 0 | pinPadMapSERCOM0AltPad2, + PA08 / 2: pinPadMapSERCOM0Pad0 | pinPadMapSERCOM2AltPad0, + PA10 / 2: pinPadMapSERCOM0Pad2 | pinPadMapSERCOM2AltPad2, + + // page 22 + PB10 / 2: 0 | pinPadMapSERCOM4AltPad2, + PB12 / 2: pinPadMapSERCOM4Pad0 | 0, + PB14 / 2: pinPadMapSERCOM4Pad2 | 0, + PA12 / 2: pinPadMapSERCOM2Pad0 | pinPadMapSERCOM4AltPad0, + PA14 / 2: pinPadMapSERCOM2Pad2 | pinPadMapSERCOM4AltPad2, + PA16 / 2: pinPadMapSERCOM1Pad0 | pinPadMapSERCOM3AltPad0, + PA18 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM3AltPad2, + PB16 / 2: pinPadMapSERCOM5Pad0 | 0, + PA20 / 2: pinPadMapSERCOM5Pad2 | pinPadMapSERCOM3AltPad2, + PA22 / 2: pinPadMapSERCOM3Pad0 | pinPadMapSERCOM5AltPad0, + PA24 / 2: pinPadMapSERCOM3Pad2 | pinPadMapSERCOM5AltPad2, + + // page 23 + PB22 / 2: 0 | pinPadMapSERCOM5AltPad2, + PA30 / 2: 0 | pinPadMapSERCOM1AltPad2, + PB30 / 2: 0 | pinPadMapSERCOM5AltPad0, + PB00 / 2: 0 | pinPadMapSERCOM5AltPad2, + PB02 / 2: 0 | pinPadMapSERCOM5AltPad0, +} + +// findPinPadMapping looks up the pad number and the pinmode for a given pin, +// given a SERCOM number. The result can either be SERCOM, SERCOM-ALT, or "not +// found" (indicated by returning ok=false). The pad number is returned to +// calculate the DOPO/DIPO bitfields of the various serial peripherals. +func findPinPadMapping(sercom uint8, pin Pin) (pinMode PinMode, pad uint32, ok bool) { + nibbles := pinPadMapping[pin/2] + upper := nibbles >> 4 + lower := nibbles & 0xf + + if upper != 0 { + // SERCOM + if (upper>>1)-1 == sercom { + pinMode = PinSERCOM + pad |= uint32((upper & 1) << 1) + ok = true + } + } + if lower != 0 { + // SERCOM-ALT + if (lower>>1)-1 == sercom { + pinMode = PinSERCOMAlt + pad |= uint32((lower & 1) << 1) + ok = true + } + } + + if ok { + // The lower bit of the pad is the same as the lower bit of the pin number. + pad |= uint32(pin & 1) + } + return +} + // InitADC initializes the ADC. func InitADC() { // ADC Bias Calibration @@ -272,11 +383,6 @@ const ( sercomTXPad0 = 0 // Only for UART sercomTXPad2 = 1 // Only for UART sercomTXPad023 = 2 // Only for UART with TX on PAD0, RTS on PAD2 and CTS on PAD3 - - spiTXPad0SCK1 = 0 - spiTXPad2SCK3 = 1 - spiTXPad3SCK1 = 2 - spiTXPad0SCK3 = 3 ) // Configure the UART. @@ -874,13 +980,8 @@ func waitForSync() { // SPI type SPI struct { - Bus *sam.SERCOM_SPI_Type - SCK Pin - MOSI Pin - MISO Pin - DOpad int - DIpad int - PinMode PinMode + Bus *sam.SERCOM_SPI_Type + SERCOM uint8 } // SPIConfig is used to store config info for SPI. @@ -894,30 +995,69 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) { - config.SCK = spi.SCK - config.MOSI = spi.MOSI - config.MISO = spi.MISO - - doPad := spi.DOpad - diPad := spi.DIpad - - pinMode := spi.PinMode +func (spi SPI) Configure(config SPIConfig) error { + // Use default pins if not set. + if config.SCK == 0 && config.MOSI == 0 && config.MISO == 0 { + config.SCK = SPI0_SCK_PIN + config.MOSI = SPI0_MOSI_PIN + config.MISO = SPI0_MISO_PIN + } // set default frequency if config.Frequency == 0 { config.Frequency = 4000000 } + // Determine the input pinout (for MISO). + misoPinMode, misoPad, ok := findPinPadMapping(spi.SERCOM, config.MISO) + if !ok { + return ErrInvalidInputPin + } + dataInPinout := misoPad // mapped directly + + // Determine the output pinout (for MOSI/SCK). + // See table 26-7 on page 494 of the datasheet. + var dataOutPinout uint32 + sckPinMode, sckPad, ok := findPinPadMapping(spi.SERCOM, config.SCK) + if !ok { + return ErrInvalidOutputPin + } + mosiPinMode, mosiPad, ok := findPinPadMapping(spi.SERCOM, config.MOSI) + if !ok { + return ErrInvalidOutputPin + } + switch sckPad { + case 1: + switch mosiPad { + case 0: + dataOutPinout = 0x0 + case 3: + dataOutPinout = 0x2 + default: + return ErrInvalidOutputPin + } + case 3: + switch mosiPad { + case 2: + dataOutPinout = 0x1 + case 0: + dataOutPinout = 0x3 + default: + return ErrInvalidOutputPin + } + default: + return ErrInvalidOutputPin + } + // Disable SPI port. spi.Bus.CTRLA.ClearBits(sam.SERCOM_SPI_CTRLA_ENABLE) for spi.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPI_SYNCBUSY_ENABLE) { } // enable pins - config.SCK.Configure(PinConfig{Mode: pinMode}) - config.MOSI.Configure(PinConfig{Mode: pinMode}) - config.MISO.Configure(PinConfig{Mode: pinMode}) + config.SCK.Configure(PinConfig{Mode: sckPinMode}) + config.MOSI.Configure(PinConfig{Mode: mosiPinMode}) + config.MISO.Configure(PinConfig{Mode: misoPinMode}) // reset SERCOM spi.Bus.CTRLA.SetBits(sam.SERCOM_SPI_CTRLA_SWRST) @@ -926,16 +1066,16 @@ func (spi SPI) Configure(config SPIConfig) { } // set bit transfer order - dataOrder := 0 + dataOrder := uint32(0) if config.LSBFirst { dataOrder = 1 } // Set SPI master - spi.Bus.CTRLA.Set(uint32((sam.SERCOM_SPI_CTRLA_MODE_SPI_MASTER << sam.SERCOM_SPI_CTRLA_MODE_Pos) | - (doPad << sam.SERCOM_SPI_CTRLA_DOPO_Pos) | - (diPad << sam.SERCOM_SPI_CTRLA_DIPO_Pos) | - (dataOrder << sam.SERCOM_SPI_CTRLA_DORD_Pos))) + spi.Bus.CTRLA.Set((sam.SERCOM_SPI_CTRLA_MODE_SPI_MASTER << sam.SERCOM_SPI_CTRLA_MODE_Pos) | + (dataOutPinout << sam.SERCOM_SPI_CTRLA_DOPO_Pos) | + (dataInPinout << sam.SERCOM_SPI_CTRLA_DIPO_Pos) | + (dataOrder << sam.SERCOM_SPI_CTRLA_DORD_Pos)) spi.Bus.CTRLB.SetBits((0 << sam.SERCOM_SPI_CTRLB_CHSIZE_Pos) | // 8bit char size sam.SERCOM_SPI_CTRLB_RXEN) // receive enable @@ -969,6 +1109,8 @@ func (spi SPI) Configure(config SPIConfig) { spi.Bus.CTRLA.SetBits(sam.SERCOM_SPI_CTRLA_ENABLE) for spi.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPI_SYNCBUSY_ENABLE) { } + + return nil } // Transfer writes/reads a single byte using the SPI interface.