From 06ab3a836f1e1cea7c40e0d316af95db7cbf6589 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Fri, 30 Nov 2018 21:34:28 +0100 Subject: [PATCH] machine/nrf: SPI master implementation Signed-off-by: Ron Evans --- src/examples/mcp3008/mcp3008.go | 55 ++++++++++++++++++ src/machine/board_microbit.go | 7 +++ src/machine/board_nrf52840-mdk.go | 7 +++ src/machine/board_pca10031.go | 7 +++ src/machine/board_pca10040.go | 7 +++ src/machine/board_pca10056.go | 11 +++- src/machine/board_reelboard.go | 7 +++ src/machine/machine_nrf.go | 95 +++++++++++++++++++++++++++++++ src/machine/machine_nrf51.go | 16 ++++++ src/machine/machine_nrf52.go | 16 ++++++ src/machine/machine_nrf52840.go | 16 ++++++ src/machine/spi.go | 70 +++++++++++++++++++++++ 12 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 src/examples/mcp3008/mcp3008.go create mode 100644 src/machine/spi.go diff --git a/src/examples/mcp3008/mcp3008.go b/src/examples/mcp3008/mcp3008.go new file mode 100644 index 00000000..eeab2f1a --- /dev/null +++ b/src/examples/mcp3008/mcp3008.go @@ -0,0 +1,55 @@ +// Connects to an MCP3008 ADC via SPI. +// Datasheet: https://www.microchip.com/wwwproducts/en/en010530 +package main + +import ( + "errors" + "machine" + "time" +) + +// CS_PIN is the pin used for Chip Select (CS). Change to whatever is in use on your board. +const CS_PIN = 3 + +var ( + tx []byte + rx []byte + val, result uint16 + cs machine.GPIO +) + +func main() { + cs = machine.GPIO{CS_PIN} + cs.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT}) + + machine.SPI0.Configure(machine.SPIConfig{ + Frequency: 4000000, + Mode: 3}) + + tx = make([]byte, 3) + rx = make([]byte, 3) + + for { + val, _ = Read(0) + println(val) + time.Sleep(50 * time.Millisecond) + } +} + +// Read analog data from channel +func Read(channel int) (uint16, error) { + if channel < 0 || channel > 7 { + return 0, errors.New("Invalid channel for read") + } + + tx[0] = 0x01 + tx[1] = byte(8+channel) << 4 + tx[2] = 0x00 + + cs.Low() + machine.SPI0.Tx(tx, rx) + result = uint16((rx[1]&0x3))<<8 + uint16(rx[2]) + cs.High() + + return result, nil +} diff --git a/src/machine/board_microbit.go b/src/machine/board_microbit.go index f5c6029f..41222a97 100644 --- a/src/machine/board_microbit.go +++ b/src/machine/board_microbit.go @@ -36,6 +36,13 @@ const ( SCL_PIN = 0 // P19 on the board ) +// SPI pins +const ( + SPI0_SCK_PIN = 0 + SPI0_MOSI_PIN = 0 + SPI0_MISO_PIN = 0 +) + // LED matrix pins const ( LED_COL_1 = 4 diff --git a/src/machine/board_nrf52840-mdk.go b/src/machine/board_nrf52840-mdk.go index 6e875fdd..68426154 100644 --- a/src/machine/board_nrf52840-mdk.go +++ b/src/machine/board_nrf52840-mdk.go @@ -23,3 +23,10 @@ const ( SDA_PIN = 0xff SCL_PIN = 0xff ) + +// SPI pins (unused) +const ( + SPI0_SCK_PIN = 0 + SPI0_MOSI_PIN = 0 + SPI0_MISO_PIN = 0 +) diff --git a/src/machine/board_pca10031.go b/src/machine/board_pca10031.go index afd0045d..ace07158 100644 --- a/src/machine/board_pca10031.go +++ b/src/machine/board_pca10031.go @@ -30,3 +30,10 @@ const ( SDA_PIN = 0xff SCL_PIN = 0xff ) + +// SPI pins (unused) +const ( + SPI0_SCK_PIN = 0 + SPI0_MOSI_PIN = 0 + SPI0_MISO_PIN = 0 +) diff --git a/src/machine/board_pca10040.go b/src/machine/board_pca10040.go index 065fda19..70ddf159 100644 --- a/src/machine/board_pca10040.go +++ b/src/machine/board_pca10040.go @@ -44,3 +44,10 @@ const ( SDA_PIN = 26 SCL_PIN = 27 ) + +// SPI pins +const ( + SPI0_SCK_PIN = 25 + SPI0_MOSI_PIN = 23 + SPI0_MISO_PIN = 24 +) diff --git a/src/machine/board_pca10056.go b/src/machine/board_pca10056.go index 5ea72513..94fa6672 100644 --- a/src/machine/board_pca10056.go +++ b/src/machine/board_pca10056.go @@ -30,6 +30,13 @@ const ( // I2C pins const ( - SDA_PIN = 26 - SCL_PIN = 27 + SDA_PIN = 26 // P0.26 + SCL_PIN = 27 // P0.27 +) + +// SPI pins +const ( + SPI0_SCK_PIN = 47 // P1.15 + SPI0_MOSI_PIN = 45 // P1.13 + SPI0_MISO_PIN = 46 // P1.14 ) diff --git a/src/machine/board_reelboard.go b/src/machine/board_reelboard.go index db9abaa1..61de59af 100644 --- a/src/machine/board_reelboard.go +++ b/src/machine/board_reelboard.go @@ -33,3 +33,10 @@ const ( SDA_PIN = 26 SCL_PIN = 27 ) + +// SPI pins +const ( + SPI0_SCK_PIN = 47 + SPI0_MOSI_PIN = 45 + SPI0_MISO_PIN = 46 +) diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index eaa7ab6f..d0a1a913 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -232,3 +232,98 @@ func (i2c I2C) readLastByte() byte { i2c.signalStop() // signal 'stop' now, so it is sent when reading RXD return byte(i2c.Bus.RXD) } + +// 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 uint8 + MOSI uint8 + MISO uint8 + 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 = nrf.SPI_ENABLE_ENABLE_Disabled + + // set frequency + var freq uint32 + + switch config.Frequency { + case 125000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_K125 + case 250000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_K250 + case 500000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_K500 + case 1000000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_M1 + case 2000000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_M2 + case 4000000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_M4 + case 8000000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_M8 + default: + freq = nrf.SPI_FREQUENCY_FREQUENCY_K500 + } + spi.Bus.FREQUENCY = nrf.RegValue(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 = nrf.RegValue(conf) + + // set pins + spi.setPins(config.SCK, config.MOSI, config.MISO) + + // Re-enable bus now that it is configured. + spi.Bus.ENABLE = 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 = nrf.RegValue(w) + for spi.Bus.EVENTS_READY == 0 { + } + r := spi.Bus.RXD + spi.Bus.EVENTS_READY = 0 + + // TODO: handle SPI errors + return byte(r), nil +} diff --git a/src/machine/machine_nrf51.go b/src/machine/machine_nrf51.go index a24a3710..701a915c 100644 --- a/src/machine/machine_nrf51.go +++ b/src/machine/machine_nrf51.go @@ -25,3 +25,19 @@ func (i2c I2C) setPins(scl, sda uint8) { i2c.Bus.PSELSCL = nrf.RegValue(scl) i2c.Bus.PSELSDA = nrf.RegValue(sda) } + +// SPI +func (spi SPI) setPins(sck, mosi, miso uint8) { + if sck == 0 { + sck = SPI0_SCK_PIN + } + if mosi == 0 { + mosi = SPI0_MOSI_PIN + } + if miso == 0 { + miso = SPI0_MISO_PIN + } + spi.Bus.PSELSCK = nrf.RegValue(sck) + spi.Bus.PSELMOSI = nrf.RegValue(mosi) + spi.Bus.PSELMISO = nrf.RegValue(miso) +} diff --git a/src/machine/machine_nrf52.go b/src/machine/machine_nrf52.go index 810bc678..70d12f55 100644 --- a/src/machine/machine_nrf52.go +++ b/src/machine/machine_nrf52.go @@ -27,6 +27,22 @@ func (i2c I2C) setPins(scl, sda uint8) { i2c.Bus.PSELSDA = nrf.RegValue(sda) } +// SPI +func (spi SPI) setPins(sck, mosi, miso uint8) { + if sck == 0 { + sck = SPI0_SCK_PIN + } + if mosi == 0 { + mosi = SPI0_MOSI_PIN + } + if miso == 0 { + miso = SPI0_MISO_PIN + } + spi.Bus.PSEL.SCK = nrf.RegValue(sck) + spi.Bus.PSEL.MOSI = nrf.RegValue(mosi) + spi.Bus.PSEL.MISO = nrf.RegValue(miso) +} + // InitADC initializes the registers needed for ADC. func InitADC() { return // no specific setup on nrf52 machine. diff --git a/src/machine/machine_nrf52840.go b/src/machine/machine_nrf52840.go index 2f0e5b48..f8322f3f 100644 --- a/src/machine/machine_nrf52840.go +++ b/src/machine/machine_nrf52840.go @@ -29,3 +29,19 @@ func (i2c I2C) setPins(scl, sda uint8) { i2c.Bus.PSEL.SCL = nrf.RegValue(scl) i2c.Bus.PSEL.SDA = nrf.RegValue(sda) } + +// SPI +func (spi SPI) setPins(sck, mosi, miso uint8) { + if sck == 0 { + sck = SPI0_SCK_PIN + } + if mosi == 0 { + mosi = SPI0_MOSI_PIN + } + if miso == 0 { + miso = SPI0_MISO_PIN + } + spi.Bus.PSEL.SCK = nrf.RegValue(sck) + spi.Bus.PSEL.MOSI = nrf.RegValue(mosi) + spi.Bus.PSEL.MISO = nrf.RegValue(miso) +} diff --git a/src/machine/spi.go b/src/machine/spi.go new file mode 100644 index 00000000..79454765 --- /dev/null +++ b/src/machine/spi.go @@ -0,0 +1,70 @@ +// +build nrf + +package machine + +import "errors" + +var ( + ErrTxSlicesRequired = errors.New("SPI Tx requires a write or read slice, or both") + ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size") +) + +// 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 { + if w == nil && r == nil { + return ErrTxSlicesRequired + } + + var err error + + switch { + case w == nil: + // 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 r == nil: + // write only + for _, b := range w { + _, err = spi.Transfer(b) + if err != nil { + return err + } + } + + 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 +}