gobot.io/x/gobot/v2@v2.1.0/system/spi_gpio.go (about)

     1  package system
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"gobot.io/x/gobot/v2"
     8  )
     9  
    10  type spiGpioConfig struct {
    11  	pinProvider gobot.DigitalPinnerProvider
    12  	sclkPinID   string
    13  	nssPinID    string
    14  	mosiPinID   string
    15  	misoPinID   string
    16  }
    17  
    18  // spiGpio is the implementation of the SPI interface using GPIO's.
    19  type spiGpio struct {
    20  	cfg spiGpioConfig
    21  	// time between clock edges (i.e. half the cycle time)
    22  	tclk    time.Duration
    23  	sclkPin gobot.DigitalPinner
    24  	nssPin  gobot.DigitalPinner
    25  	mosiPin gobot.DigitalPinner
    26  	misoPin gobot.DigitalPinner
    27  }
    28  
    29  // newSpiGpio creates and returns a new SPI connection based on given GPIO's.
    30  func newSpiGpio(cfg spiGpioConfig, maxSpeed int64) (*spiGpio, error) {
    31  	spi := &spiGpio{cfg: cfg}
    32  	spi.initializeTime(maxSpeed)
    33  	return spi, spi.initializeGpios()
    34  }
    35  
    36  func (s *spiGpio) initializeTime(maxSpeed int64) {
    37  	// maxSpeed is given in Hz, tclk is half the cycle time, tclk=1/(2*f), tclk[ns]=1 000 000 000/(2*maxSpeed)
    38  	// but with gpio's a speed of more than ~15kHz is most likely not possible, so we limit to 10kHz
    39  	if maxSpeed > 10000 {
    40  		if systemDebug {
    41  			fmt.Printf("reduce SPI speed for GPIO usage to 10Khz")
    42  		}
    43  		maxSpeed = 10000
    44  	}
    45  	tclk := time.Duration(1000000000/2/maxSpeed) * time.Nanosecond
    46  	if systemDebug {
    47  		fmt.Println("clk", tclk)
    48  	}
    49  }
    50  
    51  // TxRx uses the SPI device to send/receive data. Implements gobot.SpiSystemDevicer.
    52  func (s *spiGpio) TxRx(tx []byte, rx []byte) error {
    53  	var doRx bool
    54  	if rx != nil {
    55  		doRx = true
    56  		if len(tx) != len(rx) {
    57  			return fmt.Errorf("length of tx (%d) must be the same as length of rx (%d)", len(tx), len(rx))
    58  		}
    59  	}
    60  
    61  	if err := s.nssPin.Write(0); err != nil {
    62  		return err
    63  	}
    64  
    65  	for idx, b := range tx {
    66  		val, err := s.transferByte(b)
    67  		if err != nil {
    68  			return err
    69  		}
    70  		if doRx {
    71  			rx[idx] = val
    72  		}
    73  	}
    74  
    75  	return s.nssPin.Write(1)
    76  }
    77  
    78  // Close the SPI connection. Implements gobot.SpiSystemDevicer.
    79  func (s *spiGpio) Close() error {
    80  	if s.sclkPin != nil {
    81  		s.sclkPin.Unexport()
    82  	}
    83  	if s.mosiPin != nil {
    84  		s.mosiPin.Unexport()
    85  	}
    86  	if s.misoPin != nil {
    87  		s.misoPin.Unexport()
    88  	}
    89  	if s.nssPin != nil {
    90  		s.nssPin.Unexport()
    91  	}
    92  	return nil
    93  }
    94  
    95  func (cfg *spiGpioConfig) String() string {
    96  	return fmt.Sprintf("sclk: %s, nss: %s, mosi: %s, miso: %s", cfg.sclkPinID, cfg.nssPinID, cfg.mosiPinID, cfg.misoPinID)
    97  }
    98  
    99  // transferByte simultaneously transmit and receive a byte
   100  // polarity and phase are assumed to be both 0 (CPOL=0, CPHA=0), so:
   101  // * input data is captured on rising edge of SCLK
   102  // * output data is propagated on falling edge of SCLK
   103  func (s *spiGpio) transferByte(txByte uint8) (uint8, error) {
   104  	rxByte := uint8(0)
   105  	bitMask := uint8(0x80) // start at MSBit
   106  
   107  	for i := 0; i < 8; i++ {
   108  		if err := s.mosiPin.Write(int(txByte & bitMask)); err != nil {
   109  			return 0, err
   110  		}
   111  
   112  		time.Sleep(s.tclk)
   113  		if err := s.sclkPin.Write(1); err != nil {
   114  			return 0, err
   115  		}
   116  
   117  		v, err := s.misoPin.Read()
   118  		if err != nil {
   119  			return 0, err
   120  		}
   121  		if v != 0 {
   122  			rxByte |= bitMask
   123  		}
   124  
   125  		time.Sleep(s.tclk)
   126  		if err := s.sclkPin.Write(0); err != nil {
   127  			return 0, err
   128  		}
   129  
   130  		bitMask = bitMask >> 1 // next lower bit
   131  	}
   132  
   133  	return rxByte, nil
   134  }
   135  
   136  func (s *spiGpio) initializeGpios() error {
   137  	var err error
   138  	// nss is an output, negated (currently not implemented at pin level)
   139  	s.nssPin, err = s.cfg.pinProvider.DigitalPin(s.cfg.nssPinID)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	if err := s.nssPin.ApplyOptions(WithPinDirectionOutput(1)); err != nil {
   144  		return err
   145  	}
   146  	// sclk is an output, CPOL = 0
   147  	s.sclkPin, err = s.cfg.pinProvider.DigitalPin(s.cfg.sclkPinID)
   148  	if err != nil {
   149  		return err
   150  	}
   151  	if err := s.sclkPin.ApplyOptions(WithPinDirectionOutput(0)); err != nil {
   152  		return err
   153  	}
   154  	// miso is an input
   155  	s.misoPin, err = s.cfg.pinProvider.DigitalPin(s.cfg.misoPinID)
   156  	if err != nil {
   157  		return err
   158  	}
   159  	// mosi is an output
   160  	s.mosiPin, err = s.cfg.pinProvider.DigitalPin(s.cfg.mosiPinID)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	return s.mosiPin.ApplyOptions(WithPinDirectionOutput(0))
   165  }