From 094a866c1075fa847308c27e93300aaa2e8eb9ca Mon Sep 17 00:00:00 2001 From: Drahoslav Date: Thu, 4 Oct 2018 01:03:17 +0200 Subject: [PATCH 1/4] Implement basic SPI --- rpio.go | 80 ++++++++++++++++++++++++--------- spi.go | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 20 deletions(-) create mode 100644 spi.go diff --git a/rpio.go b/rpio.go index 155766c..50017c6 100644 --- a/rpio.go +++ b/rpio.go @@ -85,6 +85,7 @@ const ( gpioOffset = 0x200000 clkOffset = 0x101000 pwmOffset = 0x20C000 + spiOffset = 0x204000 memLength = 4096 ) @@ -93,6 +94,7 @@ var ( gpioBase int64 clkBase int64 pwmBase int64 + spiBase int64 ) func init() { @@ -100,6 +102,7 @@ func init() { gpioBase = base + gpioOffset clkBase = base + clkOffset pwmBase = base + pwmOffset + spiBase = base + spiOffset } // Pin mode, a pin can be set in Input or Output, Clock or Pwm mode @@ -108,6 +111,7 @@ const ( Output Clock Pwm + Spi ) // State of pin, High / Low @@ -137,9 +141,11 @@ var ( gpioMem []uint32 clkMem []uint32 pwmMem []uint32 + spiMem []uint32 gpioMem8 []uint8 clkMem8 []uint8 pwmMem8 []uint8 + spiMem8 []uint8 ) // Set pin as Input @@ -243,14 +249,17 @@ func PinMode(pin Pin, mode Mode) { shift := (uint8(pin) % 10) * 3 f := uint32(0) + const in = 0 // 000 + const out = 1 // 001 const alt0 = 4 // 100 + const alt4 = 3 // 011 const alt5 = 2 // 010 switch mode { case Input: - f = 0 // 000 + f = in case Output: - f = 1 // 001 + f = out case Clock: switch pin { case 4, 5, 6, 32, 34, 42, 43, 44: @@ -269,6 +278,19 @@ func PinMode(pin Pin, mode Mode) { default: return } + case Spi: + switch pin { + case 7, 8, 9, 10, 11: // SPI0 + f = alt0 + case 35, 36, 37, 38, 39: // SPI0 + f = alt0 + case 16, 17, 18, 19, 20, 21: // SPI1 + f = alt4 + case 40, 41, 42, 43, 44, 45: // SPI2 + f = alt4 + default: + return + } } memlock.Lock() @@ -284,19 +306,19 @@ func PinMode(pin Pin, mode Mode) { func WritePin(pin Pin, state State) { p := uint8(pin) - // Clear register, 10 / 11 depending on bank // Set register, 7 / 8 depending on bank - clearReg := p/32 + 10 + // Clear register, 10 / 11 depending on bank setReg := p/32 + 7 + clearReg := p/32 + 10 memlock.Lock() - defer memlock.Unlock() if state == Low { gpioMem[clearReg] = 1 << (p & 31) } else { gpioMem[setReg] = 1 << (p & 31) } + memlock.Unlock() // not deferring saves ~600ns } // Read the state of a pin @@ -312,14 +334,23 @@ func ReadPin(pin Pin) State { } // Toggle a pin state (high -> low -> high) -// TODO: probably possible to do this much faster without read func TogglePin(pin Pin) { - switch ReadPin(pin) { - case Low: - pin.High() - case High: - pin.Low() + p := uint8(pin) + + setReg := p/32 + 7 + clearReg := p/32 + 10 + levelReg := p/32 + 13 + + bit := uint32(1 << (p & 31)) + + memlock.Lock() + + if (gpioMem[levelReg] & bit) != 0 { + gpioMem[clearReg] = bit + } else { + gpioMem[setReg] = bit } + memlock.Unlock() } // Enable edge event detection on pin. @@ -346,14 +377,14 @@ func DetectEdge(pin Pin, edge Edge) { bit := uint32(1 << (p & 31)) if edge&RiseEdge > 0 { // set bit - gpioMem[renReg] = gpioMem[renReg] | bit + gpioMem[renReg] |= bit } else { // clear bit - gpioMem[renReg] = gpioMem[renReg] &^ bit + gpioMem[renReg] &^= bit } if edge&FallEdge > 0 { // set bit - gpioMem[fenReg] = gpioMem[fenReg] | bit + gpioMem[fenReg] |= bit } else { // clear bit - gpioMem[fenReg] = gpioMem[fenReg] &^ bit + gpioMem[fenReg] &^= bit } gpioMem[edsReg] = bit // to clear outdated detection @@ -387,9 +418,9 @@ func PullMode(pin Pin, pull Pull) { switch pull { case PullDown, PullUp: - gpioMem[pullReg] = gpioMem[pullReg]&^3 | uint32(pull) + gpioMem[pullReg] |= uint32(pull) case PullOff: - gpioMem[pullReg] = gpioMem[pullReg] &^ 3 + gpioMem[pullReg] &^= 3 } // Wait for value to clock in, this is ugly, sorry :( @@ -400,7 +431,7 @@ func PullMode(pin Pin, pull Pull) { // Wait for value to clock in time.Sleep(time.Microsecond) - gpioMem[pullReg] = gpioMem[pullReg] &^ 3 + gpioMem[pullReg] &^= 3 gpioMem[pullClkReg] = 0 } @@ -537,14 +568,14 @@ func SetDutyCycle(pin Pin, dutyLen, cycleLen uint32) { func StopPwm() { const pwmCtlReg = 0 const pwen = 1 - pwmMem[pwmCtlReg] = pwmMem[pwmCtlReg] &^ (pwen<<8 | pwen) + pwmMem[pwmCtlReg] &^= pwen<<8 | pwen } // Start pwm for both channels func StartPwm() { const pwmCtlReg = 0 const pwen = 1 - pwmMem[pwmCtlReg] = pwmMem[pwmCtlReg] | pwen<<8 | pwen + pwmMem[pwmCtlReg] |= pwen<<8 | pwen } // Open and memory map GPIO memory range from /dev/mem . @@ -584,6 +615,12 @@ func Open() (err error) { return } + // Memory map spi registers to slice + spiMem, spiMem8, err = memMap(file.Fd(), spiBase) + if err != nil { + return + } + return nil } @@ -619,6 +656,9 @@ func Close() error { if err := syscall.Munmap(pwmMem8); err != nil { return err } + if err := syscall.Munmap(spiMem8); err != nil { + return err + } return nil } diff --git a/spi.go b/spi.go new file mode 100644 index 0000000..41015cc --- /dev/null +++ b/spi.go @@ -0,0 +1,134 @@ +package rpio + +import ( + "errors" +) + +var ( + SpiMapError = errors.New("SPI registers not mapped correctly - are you root?") +) + +const ( + SPI0 = iota // only spi0 supported for now + SPI1 // aux + SPI2 // aux +) + +const ( + csReg = 0 + fifoReg = 1 // TX/RX FIFO + clkDivReg = 2 +) + +// Sets SPI pins of given device to SIP mode +// (CE0, CE1, [CE2], SCLK, MOSI, MISO) +// also reset SPI control register +func SpiBegin(dev int) error { + spiMem[csReg] = 0 // reset spi settings to default + if spiMem[csReg] == 0 { + // this should not read only zeroes after reset -> mem map failed + return SpiMapError + } + + for _, pin := range getSpiPins(dev) { + pin.Mode(Spi) + } + + clearSpiTxRxFifo() + setSpiDiv(128) + return nil +} + +// Sets SPI pins of given device to default (Input) mode +func SpiEnd(dev int) { + var pins = getSpiPins(dev) + for _, pin := range pins { + pin.Mode(Input) + } +} + +// Set (maximal) speed [Hz] of SPI clock +// Param speed may be as big as 125MHz in theory, but +// only values up to 31.25MHz are considered relayable. +func SpiSpeed(speed int) { + const baseFreq = 250 * 1000000 + cdiv := uint32(baseFreq / speed) + setSpiDiv(cdiv) +} + +// Select chip, one of 0, 1, 2 +// for selecting slave on CE0, CE1, or CE2 +func SpiChipSelect(chip int) { // control & status + const csMask = 3 // chip select has 2 bits + + cs := uint32(chip & csMask) + + spiMem[csReg] = spiMem[csReg]&^csMask | cs +} + +// Transmit all bytes in data to slave +// and simultaneously receives bytes from slave to data +func SpiTransfer(data []byte) { // control & status + const ta = 1 << 7 // transfer active + const txd = 1 << 18 // tx fifo can accept data + const rxd = 1 << 17 // rx fifo contains data + const done = 1 << 16 + + length := len(data) + i := 0 // data index + + clearSpiTxRxFifo() + + // set TA = 1 + spiMem[csReg] |= ta + + for i < length { + // Poll TXD writing bytes to SPI_FIFO + for spiMem[csReg]&txd == 0 { + } + spiMem[fifoReg] = uint32(data[i]) + // Poll RXD reading bytes from SPI_FIFO + for spiMem[csReg]&rxd == 0 { + } + data[i] = byte(spiMem[fifoReg]) + i++ + } + + // wait for DONE + for spiMem[csReg]&done == 0 { + } + + // Set TA = 0 + spiMem[csReg] &^= ta +} + +func setSpiDiv(div uint32) { + const divMask = 1<<16 - 1 - 1 // cdiv have 16 bits and must be odd (for some reason) + spiMem[clkDivReg] = div & divMask +} + +func clearSpiTxRxFifo() { + const clearTxRx = 1<<5 | 1<<4 + + spiMem[csReg] |= clearTxRx +} + +func getSpiPins(dev int) []Pin { + switch dev { + case SPI0: + return []Pin{ + 7, 8, 9, 10, 11, + // 35, 36, 37, 38, 39, // only one set of SPI0 can be set in Spi mode at a time + } + case SPI1: + return []Pin{ + 16, 17, 18, 19, 20, 21, + } + case SPI2: + return []Pin{ + 40, 41, 42, 43, 44, 45, + } + default: + return []Pin{} + } +} From 7d2387e2cc457416ffeba4901b189a904d03f384 Mon Sep 17 00:00:00 2001 From: Drahoslav Date: Fri, 5 Oct 2018 15:23:51 +0200 Subject: [PATCH 2/4] Define 3 SPI transfering functions, and other minor changes --- spi.go | 82 +++++++++++++++++++++++++++++++---------------------- spi_test.go | 16 +++++++++++ 2 files changed, 64 insertions(+), 34 deletions(-) create mode 100644 spi_test.go diff --git a/spi.go b/spi.go index 41015cc..1e40d01 100644 --- a/spi.go +++ b/spi.go @@ -1,15 +1,12 @@ +// SPI functionality is implemented here package rpio import ( "errors" ) -var ( - SpiMapError = errors.New("SPI registers not mapped correctly - are you root?") -) - const ( - SPI0 = iota // only spi0 supported for now + SPI0 = iota // only SPI0 supported for now SPI1 // aux SPI2 // aux ) @@ -20,9 +17,13 @@ const ( clkDivReg = 2 ) -// Sets SPI pins of given device to SIP mode -// (CE0, CE1, [CE2], SCLK, MOSI, MISO) -// also reset SPI control register +var ( + SpiMapError = errors.New("SPI registers not mapped correctly - are you root?") +) + +// Sets SPI pins of given device to SPI mode +// (CE0, CE1, [CE2], SCLK, MOSI, MISO). +// It also resets SPI control register. func SpiBegin(dev int) error { spiMem[csReg] = 0 // reset spi settings to default if spiMem[csReg] == 0 { @@ -39,7 +40,7 @@ func SpiBegin(dev int) error { return nil } -// Sets SPI pins of given device to default (Input) mode +// Sets SPI pins of given device to default (Input) mode. func SpiEnd(dev int) { var pins = getSpiPins(dev) for _, pin := range pins { @@ -47,7 +48,7 @@ func SpiEnd(dev int) { } } -// Set (maximal) speed [Hz] of SPI clock +// Set (maximal) speed [Hz] of SPI clock. // Param speed may be as big as 125MHz in theory, but // only values up to 31.25MHz are considered relayable. func SpiSpeed(speed int) { @@ -57,8 +58,8 @@ func SpiSpeed(speed int) { } // Select chip, one of 0, 1, 2 -// for selecting slave on CE0, CE1, or CE2 -func SpiChipSelect(chip int) { // control & status +// for selecting slave on CE0, CE1, or CE2 pin +func SpiChipSelect(chip int) { const csMask = 3 // chip select has 2 bits cs := uint32(chip & csMask) @@ -66,32 +67,50 @@ func SpiChipSelect(chip int) { // control & status spiMem[csReg] = spiMem[csReg]&^csMask | cs } +// SpiTransmit takes one or more bytes and send them to slave. +// +// Data received from slave are ignored. +// Use spread operator to send slice of bytes. +func SpiTransmit(data ...byte) { + SpiExchange(append(data[:0:0], data...)) // clone data because it will be rewriten by received bytes +} + +// SpiReceive receives n bytes from slave. +// +// Note that n zeroed bytes are send to slave as side effect. +func SpiReceive(n int) []byte { + data := make([]byte, n, n) + SpiExchange(data) + return data +} + // Transmit all bytes in data to slave -// and simultaneously receives bytes from slave to data -func SpiTransfer(data []byte) { // control & status +// and simultaneously receives bytes from slave to data. +// +// If you want to only send or only receive, use SpiTransmit/SpiReceive +func SpiExchange(data []byte) { const ta = 1 << 7 // transfer active const txd = 1 << 18 // tx fifo can accept data const rxd = 1 << 17 // rx fifo contains data const done = 1 << 16 - length := len(data) - i := 0 // data index - clearSpiTxRxFifo() // set TA = 1 spiMem[csReg] |= ta - for i < length { - // Poll TXD writing bytes to SPI_FIFO + for i := range data { + // wait for TXD for spiMem[csReg]&txd == 0 { } + // write bytes to SPI_FIFO spiMem[fifoReg] = uint32(data[i]) - // Poll RXD reading bytes from SPI_FIFO + + // wait for RXD for spiMem[csReg]&rxd == 0 { } + // read bytes from SPI_FIFO data[i] = byte(spiMem[fifoReg]) - i++ } // wait for DONE @@ -102,32 +121,27 @@ func SpiTransfer(data []byte) { // control & status spiMem[csReg] &^= ta } -func setSpiDiv(div uint32) { - const divMask = 1<<16 - 1 - 1 // cdiv have 16 bits and must be odd (for some reason) +// set spi clock divider value +func setSpiDiv(cdiv uint32) { + const cdivMask = 1<<16 - 1 - 1 // cdiv have 16 bits and must be odd (for some reason) spiMem[clkDivReg] = div & divMask } +// clear both FIFOs func clearSpiTxRxFifo() { const clearTxRx = 1<<5 | 1<<4 - spiMem[csReg] |= clearTxRx } func getSpiPins(dev int) []Pin { switch dev { case SPI0: - return []Pin{ - 7, 8, 9, 10, 11, - // 35, 36, 37, 38, 39, // only one set of SPI0 can be set in Spi mode at a time - } + return []Pin{7, 8, 9, 10, 11} + // ommit 35, 36, 37, 38, 39 - only one set of SPI0 can be set in Spi mode at a time case SPI1: - return []Pin{ - 16, 17, 18, 19, 20, 21, - } + return []Pin{16, 17, 18, 19, 20, 21} case SPI2: - return []Pin{ - 40, 41, 42, 43, 44, 45, - } + return []Pin{40, 41, 42, 43, 44, 45} default: return []Pin{} } diff --git a/spi_test.go b/spi_test.go new file mode 100644 index 0000000..8348011 --- /dev/null +++ b/spi_test.go @@ -0,0 +1,16 @@ +package rpio + +import () + +func Example_SPI() { + SpiBegin(SPI0) // BCM pins 7 to 11 + + SpiSpeed(144000) // 144kHz + SpiChipSelect(1) // CE1 + + SpiTransmit(0xFF) + SpiTransmit(0xDE, 0xAD) + SpiTransmit(data...) + + SpiEnd(SPI0) +} From 794e6ddd7e06fbdb541ecd02289a3476b3810352 Mon Sep 17 00:00:00 2001 From: Drahoslav Date: Fri, 5 Oct 2018 17:04:53 +0200 Subject: [PATCH 3/4] Improve doc --- rpio.go | 12 +++++++++--- spi.go | 39 ++++++++++++++++++++++++--------------- spi_test.go | 27 +++++++++++++++++---------- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/rpio.go b/rpio.go index 50017c6..f4aeef3 100644 --- a/rpio.go +++ b/rpio.go @@ -8,9 +8,13 @@ Supports simple operations such as: - Pin read (high/low) - Pin edge detection (no/rise/fall/any) - Pull up/down/off -And clock/pwm related oparations: +Also clock/pwm related oparations: - Set Clock frequency - Set Duty cycle +And SPI oparations: + - SPI transmit/recieve/exchange bytes + - Chip select + - Set speed Example of use: @@ -238,10 +242,12 @@ func (pin Pin) EdgeDetected() bool { return EdgeDetected(pin) } -// PinMode sets the mode (direction) of a given pin (Input, Output, Clock or Pwm) +// PinMode sets the mode of a given pin (Input, Output, Clock, Pwm or Spi) // // Clock is possible only for pins 4, 5, 6, 20, 21. // Pwm is possible only for pins 12, 13, 18, 19. +// +// Spi mode should not be set by this directly, use SpiBegin instead. func PinMode(pin Pin, mode Mode) { // Pin fsel register, 0 or 1 depending on bank @@ -296,7 +302,7 @@ func PinMode(pin Pin, mode Mode) { memlock.Lock() defer memlock.Unlock() - const pinMask = 7 // 0b111 - pinmode is 3 bits + const pinMask = 7 // 111 - pinmode is 3 bits gpioMem[fselReg] = (gpioMem[fselReg] &^ (pinMask << shift)) | (f << shift) } diff --git a/spi.go b/spi.go index 1e40d01..03368cd 100644 --- a/spi.go +++ b/spi.go @@ -1,14 +1,17 @@ -// SPI functionality is implemented here package rpio import ( "errors" ) +type SpiDev int + +// SPI devices. +// Only SPI0 supported for now. const ( - SPI0 = iota // only SPI0 supported for now - SPI1 // aux - SPI2 // aux + Spi0 SpiDev = iota + Spi1 // aux + Spi2 // aux ) const ( @@ -21,10 +24,16 @@ var ( SpiMapError = errors.New("SPI registers not mapped correctly - are you root?") ) -// Sets SPI pins of given device to SPI mode -// (CE0, CE1, [CE2], SCLK, MOSI, MISO). +// Sets all pins of given SPI device to SPI mode +// dev\pin | CE0 | CE1 | CE2 | SCLK | MOSI | MISO | +// Spi0 | 7 | 8 | - | 9 | 10 | 11 | +// Spi1 | 16 | 17 | 18 | 19 | 20 | 21 | +// Spi2 | 40 | 41 | 42 | 43 | 44 | 45 | +// // It also resets SPI control register. -func SpiBegin(dev int) error { +// +// Note that you should disable SPI interface in raspi-config first! +func SpiBegin(dev SpiDev) error { spiMem[csReg] = 0 // reset spi settings to default if spiMem[csReg] == 0 { // this should not read only zeroes after reset -> mem map failed @@ -40,8 +49,8 @@ func SpiBegin(dev int) error { return nil } -// Sets SPI pins of given device to default (Input) mode. -func SpiEnd(dev int) { +// Sets SPI pins of given device to default (Input) mode. See SpiBegin. +func SpiEnd(dev SpiDev) { var pins = getSpiPins(dev) for _, pin := range pins { pin.Mode(Input) @@ -122,8 +131,8 @@ func SpiExchange(data []byte) { } // set spi clock divider value -func setSpiDiv(cdiv uint32) { - const cdivMask = 1<<16 - 1 - 1 // cdiv have 16 bits and must be odd (for some reason) +func setSpiDiv(div uint32) { + const divMask = 1<<16 - 1 - 1 // cdiv have 16 bits and must be odd (for some reason) spiMem[clkDivReg] = div & divMask } @@ -133,14 +142,14 @@ func clearSpiTxRxFifo() { spiMem[csReg] |= clearTxRx } -func getSpiPins(dev int) []Pin { +func getSpiPins(dev SpiDev) []Pin { switch dev { - case SPI0: + case Spi0: return []Pin{7, 8, 9, 10, 11} // ommit 35, 36, 37, 38, 39 - only one set of SPI0 can be set in Spi mode at a time - case SPI1: + case Spi1: return []Pin{16, 17, 18, 19, 20, 21} - case SPI2: + case Spi2: return []Pin{40, 41, 42, 43, 44, 45} default: return []Pin{} diff --git a/spi_test.go b/spi_test.go index 8348011..36dae12 100644 --- a/spi_test.go +++ b/spi_test.go @@ -2,15 +2,22 @@ package rpio import () -func Example_SPI() { - SpiBegin(SPI0) // BCM pins 7 to 11 +func ExampleSpiTransmit() { + SpiTransmit(0xFF) // send single byte + SpiTransmit(0xDE, 0xAD, 0xBE) // send several bytes - SpiSpeed(144000) // 144kHz - SpiChipSelect(1) // CE1 - - SpiTransmit(0xFF) - SpiTransmit(0xDE, 0xAD) - SpiTransmit(data...) - - SpiEnd(SPI0) + data := []byte{'H', 'e', 'l', 'l', 'o', 0} + SpiTransmit(data...) // send slice of bytes +} + +func ExampleSpiBegin() { + err := SpiBegin(Spi0) // pins 7 to 11 + if err != nil { + panic(err) + } + + // any Spi functions must go there... + SpiTransmit(42) + + SpiEnd(Spi0) } From 473106cc71b04ced9a24bbfd7624521dc0656f7c Mon Sep 17 00:00:00 2001 From: Drahoslav Date: Fri, 5 Oct 2018 21:48:50 +0200 Subject: [PATCH 4/4] Add clockmode and cspolarity SPI settings --- spi.go | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/spi.go b/spi.go index 03368cd..bb2c4ab 100644 --- a/spi.go +++ b/spi.go @@ -68,7 +68,7 @@ func SpiSpeed(speed int) { // Select chip, one of 0, 1, 2 // for selecting slave on CE0, CE1, or CE2 pin -func SpiChipSelect(chip int) { +func SpiChipSelect(chip uint8) { const csMask = 3 // chip select has 2 bits cs := uint32(chip & csMask) @@ -76,6 +76,40 @@ func SpiChipSelect(chip int) { spiMem[csReg] = spiMem[csReg]&^csMask | cs } +// Sets polarity (0/1) of active chip select +// default active=0 +func SpiChipSelectPolarity(chip uint8, polarity uint8) { + if chip > 2 { + return + } + cspol := uint32(1 << (21 + chip)) // bit 21, 22 or 23 depending on chip + + if polarity == 0 { // chip select is active low + spiMem[csReg] &^= cspol + } else { // chip select is active hight + spiMem[csReg] |= cspol + } +} + +// Set polarity (0/1) and phase (0/1) of spi clock +// default polarity=0; phase=0 +func SpiMode(polarity uint8, phase uint8) { + const cpol = 1 << 3 + const cpha = 1 << 2 + + if polarity == 0 { // Rest state of clock = low + spiMem[csReg] &^= cpol + } else { // Rest state of clock = high + spiMem[csReg] |= cpol + } + + if phase == 0 { // First SCLK transition at middle of data bit + spiMem[csReg] &^= cpha + } else { // First SCLK transition at beginning of data bit + spiMem[csReg] |= cpha + } +} + // SpiTransmit takes one or more bytes and send them to slave. // // Data received from slave are ignored.