You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devices/bitbang/spi.go

300 lines
7.2 KiB
Go

// Copyright 2016 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
// Specification
//
// Motorola never published a proper specification.
// http://electronics.stackexchange.com/questions/30096/spi-specifications
// http://www.nxp.com/files/microcontrollers/doc/data_sheet/M68HC11E.pdf page 120
// http://www.st.com/content/ccc/resource/technical/document/technical_note/58/17/ad/50/fa/c9/48/07/DM00054618.pdf/files/DM00054618.pdf/jcr:content/translations/en.DM00054618.pdf
package bitbang
import (
"errors"
"fmt"
"sync"
"time"
"periph.io/x/conn"
"periph.io/x/conn/gpio"
"periph.io/x/conn/physic"
"periph.io/x/conn/spi"
"periph.io/x/host/cpu"
)
// NewSPI returns an spi.PortCloser that communicates SPI over 3 or 4 pins.
//
// BUG(maruel): Completely untested.
//
// cs can be nil.
func NewSPI(clk, mosi gpio.PinOut, miso gpio.PinIn, cs gpio.PinOut) (*SPI, error) {
return &SPI{
spiConn: spiConn{
sck: clk,
sdi: miso,
sdo: mosi,
csn: cs,
},
}, nil
}
// SPI represents a SPI master port implemented as bit-banging on 3 or 4 GPIO
// pins.
type SPI struct {
spiConn spiConn
}
func (s *SPI) String() string {
return fmt.Sprintf("bitbang/spi(%s, %s, %s, %s)", s.spiConn.sck, s.spiConn.sdi, s.spiConn.sdo, s.spiConn.csn)
}
// Close implements spi.PortCloser.
func (s *SPI) Close() error {
return nil
}
// Connect implements spi.PortCloser.
func (s *SPI) Connect(f physic.Frequency, mode spi.Mode, bits int) (spi.Conn, error) {
if f < 0 {
return nil, errors.New("bitbang-spi: invalid frequency")
}
if mode&spi.HalfDuplex == spi.HalfDuplex {
return nil, errors.New("bitbang-spi: half-duplex mode not supported")
}
if mode&spi.LSBFirst == spi.LSBFirst {
return nil, errors.New("bitbang-spi: LSBFirst mode not supported")
}
if mode >= 0x20 {
return nil, fmt.Errorf("bitbang-spi: unhandled mode %d(%s)", mode, mode.String())
}
s.spiConn.mu.Lock()
defer s.spiConn.mu.Unlock()
s.spiConn.freqDev = f
if s.spiConn.freqDev != 0 && (s.spiConn.freqPort == 0 || s.spiConn.freqDev < s.spiConn.freqPort) {
s.spiConn.halfCycle = f.Period() / 2
}
s.spiConn.mode = mode
s.spiConn.bits = bits
s.spiConn.readAfterClockPulse = mode&spi.Mode1 == spi.Mode1
// Set clock idle polarity, ensuring an idle clock to start
s.spiConn.clockIdle = gpio.Level(mode&spi.Mode2 == spi.Mode2)
if err := s.spiConn.sck.Out(s.spiConn.clockIdle); err != nil {
return nil, fmt.Errorf("bitbang-spi: failed to idle clock: %v", err)
}
if err := s.spiConn.initializePins(); err != nil {
return nil, fmt.Errorf("bitbang-spi: failed to initialize pins: %v", err)
}
return &s.spiConn, nil
}
// LimitSpeed implements spi.PortCloser.
func (s *SPI) LimitSpeed(f physic.Frequency) error {
if f <= 0 {
return errors.New("bitbang-spi: invalid frequency")
}
s.spiConn.mu.Lock()
defer s.spiConn.mu.Unlock()
s.spiConn.freqPort = f
if s.spiConn.freqDev == 0 || s.spiConn.freqPort < s.spiConn.freqDev {
s.spiConn.halfCycle = f.Period() / 2
}
return nil
}
// CLK implements spi.Pins.
func (s *SPI) CLK() gpio.PinOut {
return s.spiConn.sck
}
// MOSI implements spi.Pins.
func (s *SPI) MOSI() gpio.PinOut {
return s.spiConn.sdo
}
// MISO implements spi.Pins.
func (s *SPI) MISO() gpio.PinIn {
return s.spiConn.sdi
}
// CS implements spi.Pins.
func (s *SPI) CS() gpio.PinOut {
return s.spiConn.csn
}
//
// spiConn implements spi.Conn.
type spiConn struct {
// Immutable.
sck gpio.PinOut // Clock
sdi gpio.PinIn // MISO
sdo gpio.PinOut // MOSI
csn gpio.PinOut // CS
// Mutable.
mu sync.Mutex
freqPort physic.Frequency
freqDev physic.Frequency
clockIdle gpio.Level
readAfterClockPulse bool
mode spi.Mode
bits int
halfCycle time.Duration
}
func (s *spiConn) String() string {
return fmt.Sprintf("bitbang/spi(%s, %s, %s, %s)", s.sck, s.sdi, s.sdo, s.csn)
}
// Duplex implements spi.Conn.
func (s *spiConn) Duplex() conn.Duplex {
// Maybe implement bitbanging SPI only in half mode?
return conn.Full
}
// Tx implements spi.Conn.
//
// BUG(maruel): Implement mode (HalfDuplex and LSBFirst remain to be done).
// BUG(maruel): Implement bits.
// BUG(maruel): Test if read works.
func (s *spiConn) Tx(w, r []byte) (err error) {
if len(r) != 0 && len(w) != len(r) {
return errors.New("bitbang-spi: write and read buffers must be the same length")
}
s.mu.Lock()
defer s.mu.Unlock()
if err = s.assertCS(); err != nil {
return fmt.Errorf("bitbang-spi: failed to assert chip-select: %v", err)
}
for i := uint(0); i < uint(len(w)*8); i++ {
if err = s.sdo.Out(w[i/8]&(1<<(i%8)) != 0); err != nil {
return fmt.Errorf("bitbang-spi: failed to send bit %d of word %d: %v", i%8, i/8, err)
}
s.sleepHalfCycle()
if err = s.sck.Out(!s.clockIdle); err != nil {
return fmt.Errorf("bitbang-spi: failed to assert clock: %v", err)
}
s.sleepHalfCycle()
if s.readAfterClockPulse {
if err = s.sck.Out(s.clockIdle); err != nil {
return fmt.Errorf("bitbang-spi: failed to idle clock: %v", err)
}
s.sleepHalfCycle()
}
if len(r) != 0 {
if s.sdi.Read() == gpio.High {
r[i/8] |= 1 << (i % 8)
}
}
if !s.readAfterClockPulse {
if err = s.sck.Out(s.clockIdle); err != nil {
return fmt.Errorf("bitbang-spi: failed to idle clock: %v", err)
}
}
}
if err = s.unassertCS(); err != nil {
return fmt.Errorf("bitbang-spi: failed to unassert chip-select: %v", err)
}
return nil
}
// TxPackets implements spi.Conn.
func (s *spiConn) TxPackets(p []spi.Packet) error {
return errors.New("bitbang-spi: not implemented")
}
// Write implements io.Writer.
func (s *spiConn) Write(d []byte) (int, error) {
if err := s.Tx(d, nil); err != nil {
return 0, err
}
return len(d), nil
}
// CLK implements spi.Pins.
func (s *spiConn) CLK() gpio.PinOut {
return s.sck
}
// MOSI implements spi.Pins.
func (s *spiConn) MOSI() gpio.PinOut {
return s.sdo
}
// MISO implements spi.Pins.
func (s *spiConn) MISO() gpio.PinIn {
return s.sdi
}
// CS implements spi.Pins.
func (s *spiConn) CS() gpio.PinOut {
return s.csn
}
//
// sleep does a busy loop to act as fast as possible.
func (s *spiConn) sleepHalfCycle() {
cpu.Nanospin(s.halfCycle)
}
func (s *spiConn) assertCS() error {
if s.csn == nil || s.mode&spi.NoCS == spi.NoCS {
return nil
}
if err := s.csn.Out(gpio.Low); err != nil {
return err
}
s.sleepHalfCycle()
return nil
}
func (s *spiConn) unassertCS() error {
if s.csn == nil || s.mode&spi.NoCS == spi.NoCS {
return nil
}
if err := s.csn.Out(gpio.High); err != nil {
return err
}
s.sleepHalfCycle()
return nil
}
func (s *spiConn) initializePins() error {
if err := s.unassertCS(); err != nil {
return fmt.Errorf("failed to unassert CS: %v", err)
}
if s.sck != nil {
if err := s.sck.Out(s.clockIdle); err != nil {
return fmt.Errorf("failed to idle clock: %v", err)
}
}
if s.sdo != nil {
if err := s.sdo.Out(gpio.Low); err != nil {
return fmt.Errorf("failed to zero MOSI: %v", err)
}
}
if s.sdi != nil {
if err := s.sdi.In(gpio.PullUp, gpio.NoEdge); err != nil {
return fmt.Errorf("failed to initialize MISO: %v", err)
}
}
return nil
}
var _ spi.Conn = &spiConn{}
var _ spi.PortCloser = &SPI{}