From cda5fffd98acb6dc1d0fc0acc086750d1d7abfc7 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 2 Dec 2020 00:50:47 +0100 Subject: [PATCH] nrf: use SPIM peripheral instead of the legacy SPI peripheral This newer peripheral supports DMA (through EasyDMA) and should generally be faster. Importantly for some operations: interrupts (within 255 byte buffers) will not interfere with the SPI transfer. --- src/machine/machine_nrf.go | 160 ---------------------------- src/machine/machine_nrf51.go | 179 +++++++++++++++++++++++++++++--- src/machine/machine_nrf528xx.go | 151 +++++++++++++++++++++++++-- 3 files changed, 305 insertions(+), 185 deletions(-) diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index ac01fd0b..f2e6777e 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -328,163 +328,3 @@ func (i2c I2C) readByte() (byte, error) { i2c.Bus.EVENTS_RXDREADY.Set(0) return byte(i2c.Bus.RXD.Get()), nil } - -// SPI on the NRF. -type SPI struct { - Bus *nrf.SPI_Type -} - -// There are 2 SPI interfaces on the NRF5x. -var ( - SPI0 = SPI{Bus: nrf.SPI0} - SPI1 = SPI{Bus: nrf.SPI1} -) - -// SPIConfig is used to store config info for SPI. -type SPIConfig struct { - Frequency uint32 - SCK Pin - SDO Pin - SDI Pin - LSBFirst bool - Mode uint8 -} - -// Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) { - // Disable bus to configure it - spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Disabled) - - // set frequency - var freq uint32 - - if config.Frequency == 0 { - config.Frequency = 4000000 // 4MHz - } - - switch { - case config.Frequency >= 8000000: - freq = nrf.SPI_FREQUENCY_FREQUENCY_M8 - case config.Frequency >= 4000000: - freq = nrf.SPI_FREQUENCY_FREQUENCY_M4 - case config.Frequency >= 2000000: - freq = nrf.SPI_FREQUENCY_FREQUENCY_M2 - case config.Frequency >= 1000000: - freq = nrf.SPI_FREQUENCY_FREQUENCY_M1 - case config.Frequency >= 500000: - freq = nrf.SPI_FREQUENCY_FREQUENCY_K500 - case config.Frequency >= 250000: - freq = nrf.SPI_FREQUENCY_FREQUENCY_K250 - default: // below 250kHz, default to the lowest speed available - freq = nrf.SPI_FREQUENCY_FREQUENCY_K125 - } - spi.Bus.FREQUENCY.Set(freq) - - var conf uint32 - - // set bit transfer order - if config.LSBFirst { - conf = (nrf.SPI_CONFIG_ORDER_LsbFirst << nrf.SPI_CONFIG_ORDER_Pos) - } - - // set mode - switch config.Mode { - case 0: - conf &^= (nrf.SPI_CONFIG_CPOL_ActiveHigh << nrf.SPI_CONFIG_CPOL_Pos) - conf &^= (nrf.SPI_CONFIG_CPHA_Leading << nrf.SPI_CONFIG_CPHA_Pos) - case 1: - conf &^= (nrf.SPI_CONFIG_CPOL_ActiveHigh << nrf.SPI_CONFIG_CPOL_Pos) - conf |= (nrf.SPI_CONFIG_CPHA_Trailing << nrf.SPI_CONFIG_CPHA_Pos) - case 2: - conf |= (nrf.SPI_CONFIG_CPOL_ActiveLow << nrf.SPI_CONFIG_CPOL_Pos) - conf &^= (nrf.SPI_CONFIG_CPHA_Leading << nrf.SPI_CONFIG_CPHA_Pos) - case 3: - conf |= (nrf.SPI_CONFIG_CPOL_ActiveLow << nrf.SPI_CONFIG_CPOL_Pos) - conf |= (nrf.SPI_CONFIG_CPHA_Trailing << nrf.SPI_CONFIG_CPHA_Pos) - default: // to mode - conf &^= (nrf.SPI_CONFIG_CPOL_ActiveHigh << nrf.SPI_CONFIG_CPOL_Pos) - conf &^= (nrf.SPI_CONFIG_CPHA_Leading << nrf.SPI_CONFIG_CPHA_Pos) - } - spi.Bus.CONFIG.Set(conf) - - // set pins - spi.setPins(config.SCK, config.SDO, config.SDI) - - // Re-enable bus now that it is configured. - spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Enabled) -} - -// Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { - spi.Bus.TXD.Set(uint32(w)) - for spi.Bus.EVENTS_READY.Get() == 0 { - } - r := spi.Bus.RXD.Get() - spi.Bus.EVENTS_READY.Set(0) - - // TODO: handle SPI errors - return byte(r), nil -} - -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read -// interface, there must always be the same number of bytes written as bytes read. -// The Tx method knows about this, and offers a few different ways of calling it. -// -// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. -// Note that the tx and rx buffers must be the same size: -// -// spi.Tx(tx, rx) -// -// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros -// until all the bytes in the command packet have been received: -// -// spi.Tx(tx, nil) -// -// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": -// -// spi.Tx(nil, rx) -// -func (spi SPI) Tx(w, r []byte) error { - var err error - - switch { - case len(w) == 0: - // read only, so write zero and read a result. - for i := range r { - r[i], err = spi.Transfer(0) - if err != nil { - return err - } - } - case len(r) == 0: - // write only - spi.Bus.TXD.Set(uint32(w[0])) - w = w[1:] - for _, b := range w { - spi.Bus.TXD.Set(uint32(b)) - for spi.Bus.EVENTS_READY.Get() == 0 { - } - spi.Bus.EVENTS_READY.Set(0) - _ = spi.Bus.RXD.Get() - } - for spi.Bus.EVENTS_READY.Get() == 0 { - } - spi.Bus.EVENTS_READY.Set(0) - _ = spi.Bus.RXD.Get() - - default: - // write/read - if len(w) != len(r) { - return ErrTxInvalidSliceSize - } - - for i, b := range w { - r[i], err = spi.Transfer(b) - if err != nil { - return err - } - } - } - - return nil -} diff --git a/src/machine/machine_nrf51.go b/src/machine/machine_nrf51.go index 464a1a2c..bbb4f763 100644 --- a/src/machine/machine_nrf51.go +++ b/src/machine/machine_nrf51.go @@ -29,18 +29,169 @@ func (i2c I2C) setPins(scl, sda Pin) { i2c.Bus.PSELSDA.Set(uint32(sda)) } -// SPI -func (spi SPI) setPins(sck, sdo, sdi Pin) { - if sck == 0 { - sck = SPI0_SCK_PIN - } - if sdo == 0 { - sdo = SPI0_SDO_PIN - } - if sdi == 0 { - sdi = SPI0_SDI_PIN - } - spi.Bus.PSELSCK.Set(uint32(sck)) - spi.Bus.PSELMOSI.Set(uint32(sdo)) - spi.Bus.PSELMISO.Set(uint32(sdi)) +// SPI on the NRF. +type SPI struct { + Bus *nrf.SPI_Type +} + +// There are 2 SPI interfaces on the NRF51. +var ( + SPI0 = SPI{Bus: nrf.SPI0} + SPI1 = SPI{Bus: nrf.SPI1} +) + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + SCK Pin + SDO Pin + SDI Pin + LSBFirst bool + Mode uint8 +} + +// Configure is intended to setup the SPI interface. +func (spi SPI) Configure(config SPIConfig) { + // Disable bus to configure it + spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Disabled) + + // set frequency + var freq uint32 + + if config.Frequency == 0 { + config.Frequency = 4000000 // 4MHz + } + + switch { + case config.Frequency >= 8000000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_M8 + case config.Frequency >= 4000000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_M4 + case config.Frequency >= 2000000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_M2 + case config.Frequency >= 1000000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_M1 + case config.Frequency >= 500000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_K500 + case config.Frequency >= 250000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_K250 + default: // below 250kHz, default to the lowest speed available + freq = nrf.SPI_FREQUENCY_FREQUENCY_K125 + } + spi.Bus.FREQUENCY.Set(freq) + + var conf uint32 + + // set bit transfer order + if config.LSBFirst { + conf = (nrf.SPI_CONFIG_ORDER_LsbFirst << nrf.SPI_CONFIG_ORDER_Pos) + } + + // set mode + switch config.Mode { + case 0: + conf &^= (nrf.SPI_CONFIG_CPOL_ActiveHigh << nrf.SPI_CONFIG_CPOL_Pos) + conf &^= (nrf.SPI_CONFIG_CPHA_Leading << nrf.SPI_CONFIG_CPHA_Pos) + case 1: + conf &^= (nrf.SPI_CONFIG_CPOL_ActiveHigh << nrf.SPI_CONFIG_CPOL_Pos) + conf |= (nrf.SPI_CONFIG_CPHA_Trailing << nrf.SPI_CONFIG_CPHA_Pos) + case 2: + conf |= (nrf.SPI_CONFIG_CPOL_ActiveLow << nrf.SPI_CONFIG_CPOL_Pos) + conf &^= (nrf.SPI_CONFIG_CPHA_Leading << nrf.SPI_CONFIG_CPHA_Pos) + case 3: + conf |= (nrf.SPI_CONFIG_CPOL_ActiveLow << nrf.SPI_CONFIG_CPOL_Pos) + conf |= (nrf.SPI_CONFIG_CPHA_Trailing << nrf.SPI_CONFIG_CPHA_Pos) + default: // to mode + conf &^= (nrf.SPI_CONFIG_CPOL_ActiveHigh << nrf.SPI_CONFIG_CPOL_Pos) + conf &^= (nrf.SPI_CONFIG_CPHA_Leading << nrf.SPI_CONFIG_CPHA_Pos) + } + spi.Bus.CONFIG.Set(conf) + + // set pins + if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { + config.SCK = SPI0_SCK_PIN + config.SDO = SPI0_SDO_PIN + config.SDI = SPI0_SDI_PIN + } + spi.Bus.PSELSCK.Set(uint32(config.SCK)) + spi.Bus.PSELMOSI.Set(uint32(config.SDO)) + spi.Bus.PSELMISO.Set(uint32(config.SDI)) + + // Re-enable bus now that it is configured. + spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Enabled) +} + +// Transfer writes/reads a single byte using the SPI interface. +func (spi SPI) Transfer(w byte) (byte, error) { + spi.Bus.TXD.Set(uint32(w)) + for spi.Bus.EVENTS_READY.Get() == 0 { + } + r := spi.Bus.RXD.Get() + spi.Bus.EVENTS_READY.Set(0) + + // TODO: handle SPI errors + return byte(r), nil +} + +// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// interface, there must always be the same number of bytes written as bytes read. +// The Tx method knows about this, and offers a few different ways of calling it. +// +// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. +// Note that the tx and rx buffers must be the same size: +// +// spi.Tx(tx, rx) +// +// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros +// until all the bytes in the command packet have been received: +// +// spi.Tx(tx, nil) +// +// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": +// +// spi.Tx(nil, rx) +// +func (spi SPI) Tx(w, r []byte) error { + var err error + + switch { + case len(w) == 0: + // read only, so write zero and read a result. + for i := range r { + r[i], err = spi.Transfer(0) + if err != nil { + return err + } + } + case len(r) == 0: + // write only + spi.Bus.TXD.Set(uint32(w[0])) + w = w[1:] + for _, b := range w { + spi.Bus.TXD.Set(uint32(b)) + for spi.Bus.EVENTS_READY.Get() == 0 { + } + spi.Bus.EVENTS_READY.Set(0) + _ = spi.Bus.RXD.Get() + } + for spi.Bus.EVENTS_READY.Get() == 0 { + } + spi.Bus.EVENTS_READY.Set(0) + _ = spi.Bus.RXD.Get() + + default: + // write/read + if len(w) != len(r) { + return ErrTxInvalidSliceSize + } + + for i, b := range w { + r[i], err = spi.Transfer(b) + if err != nil { + return err + } + } + } + + return nil } diff --git a/src/machine/machine_nrf528xx.go b/src/machine/machine_nrf528xx.go index 2a207580..76409522 100644 --- a/src/machine/machine_nrf528xx.go +++ b/src/machine/machine_nrf528xx.go @@ -111,20 +111,149 @@ func (a ADC) Get() uint16 { return uint16(value << 4) } -// SPI -func (spi SPI) setPins(sck, sdo, sdi Pin) { - if sck == 0 { - sck = SPI0_SCK_PIN +// SPI on the NRF. +type SPI struct { + Bus *nrf.SPIM_Type +} + +// There are 3 SPI interfaces on the NRF528xx. +var ( + SPI0 = SPI{Bus: nrf.SPIM0} + SPI1 = SPI{Bus: nrf.SPIM1} + SPI2 = SPI{Bus: nrf.SPIM2} +) + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + SCK Pin + SDO Pin + SDI Pin + LSBFirst bool + Mode uint8 +} + +// Configure is intended to setup the SPI interface. +func (spi SPI) Configure(config SPIConfig) { + // Disable bus to configure it + spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Disabled) + + // Pick a default frequency. + if config.Frequency == 0 { + config.Frequency = 4000000 // 4MHz } - if sdo == 0 { - sdo = SPI0_SDO_PIN + + // set frequency + var freq uint32 + switch { + case config.Frequency >= 8000000: + freq = nrf.SPIM_FREQUENCY_FREQUENCY_M8 + case config.Frequency >= 4000000: + freq = nrf.SPIM_FREQUENCY_FREQUENCY_M4 + case config.Frequency >= 2000000: + freq = nrf.SPIM_FREQUENCY_FREQUENCY_M2 + case config.Frequency >= 1000000: + freq = nrf.SPIM_FREQUENCY_FREQUENCY_M1 + case config.Frequency >= 500000: + freq = nrf.SPIM_FREQUENCY_FREQUENCY_K500 + case config.Frequency >= 250000: + freq = nrf.SPIM_FREQUENCY_FREQUENCY_K250 + default: // below 250kHz, default to the lowest speed available + freq = nrf.SPIM_FREQUENCY_FREQUENCY_K125 } - if sdi == 0 { - sdi = SPI0_SDI_PIN + spi.Bus.FREQUENCY.Set(freq) + + var conf uint32 + + // set bit transfer order + if config.LSBFirst { + conf = (nrf.SPIM_CONFIG_ORDER_LsbFirst << nrf.SPIM_CONFIG_ORDER_Pos) } - spi.Bus.PSEL.SCK.Set(uint32(sck)) - spi.Bus.PSEL.MOSI.Set(uint32(sdo)) - spi.Bus.PSEL.MISO.Set(uint32(sdi)) + + // set mode + switch config.Mode { + case 0: + conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos) + conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos) + case 1: + conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos) + conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos) + case 2: + conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos) + conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos) + case 3: + conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos) + conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos) + default: // to mode + conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos) + conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos) + } + spi.Bus.CONFIG.Set(conf) + + // set pins + if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { + config.SCK = SPI0_SCK_PIN + config.SDO = SPI0_SDO_PIN + config.SDI = SPI0_SDI_PIN + } + spi.Bus.PSEL.SCK.Set(uint32(config.SCK)) + spi.Bus.PSEL.MOSI.Set(uint32(config.SDO)) + spi.Bus.PSEL.MISO.Set(uint32(config.SDI)) + + // Re-enable bus now that it is configured. + spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Enabled) +} + +// Transfer writes/reads a single byte using the SPI interface. +func (spi SPI) Transfer(w byte) (byte, error) { + var wbuf, rbuf [1]byte + wbuf[0] = w + err := spi.Tx(wbuf[:], rbuf[:]) + return rbuf[0], err +} + +// Tx handles read/write operation for SPI interface. Since SPI is a syncronous +// write/read interface, there must always be the same number of bytes written +// as bytes read. Therefore, if the number of bytes don't match it will be +// padded until they fit: if len(w) > len(r) the extra bytes received will be +// dropped and if len(w) < len(r) extra 0 bytes will be sent. +func (spi SPI) Tx(w, r []byte) error { + // Unfortunately the hardware (on the nrf52832) only supports up to 255 + // bytes in the buffers, so if either w or r is longer than that the + // transfer needs to be broken up in pieces. + // The nrf52840 supports far larger buffers however, which isn't yet + // supported. + for len(r) != 0 || len(w) != 0 { + // Prepare the SPI transfer: set the DMA pointers and lengths. + if len(r) != 0 { + spi.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0])))) + n := uint32(len(r)) + if n > 255 { + n = 255 + } + spi.Bus.RXD.MAXCNT.Set(n) + r = r[n:] + } + if len(w) != 0 { + spi.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0])))) + n := uint32(len(w)) + if n > 255 { + n = 255 + } + spi.Bus.TXD.MAXCNT.Set(n) + w = w[n:] + } + + // Do the transfer. + // Note: this can be improved by not waiting until the transfer is + // finished if the transfer is send-only (a common case). + spi.Bus.TASKS_START.Set(1) + for spi.Bus.EVENTS_END.Get() == 0 { + } + spi.Bus.EVENTS_END.Set(0) + } + + return nil } // InitPWM initializes the registers needed for PWM.