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 }