// Copyright 2017 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. // This functionality requires MPSSE. // // Interfacing SPI: // http://www.ftdichip.com/Support/Documents/AppNotes/AN_114_FTDI_Hi_Speed_USB_To_SPI_Example.pdf // // Implementation based on // http://www.ftdichip.com/Support/Documents/AppNotes/AN_180_FT232H%20MPSSE%20Example%20-%20USB%20Current%20Meter%20using%20the%20SPI%20interface.pdf package ftdi import ( "context" "errors" "fmt" "periph.io/x/conn/v3" "periph.io/x/conn/v3/gpio" "periph.io/x/conn/v3/physic" "periph.io/x/conn/v3/spi" ) // spiMPSEEPort is an SPI port over a FTDI device in MPSSE mode using the data // command on the AD bus. type spiMPSEEPort struct { c spiMPSEEConn // Mutable. maxFreq physic.Frequency } func (s *spiMPSEEPort) Close() error { s.c.f.mu.Lock() s.c.f.usingSPI = false s.maxFreq = 0 s.c.edgeInvert = false s.c.clkActiveLow = false s.c.noCS = false s.c.lsbFirst = false s.c.halfDuplex = false s.c.f.mu.Unlock() return nil } func (s *spiMPSEEPort) String() string { return s.c.f.String() } // Connect implements spi.Port. func (s *spiMPSEEPort) Connect(f physic.Frequency, m spi.Mode, bits int) (spi.Conn, error) { if f > physic.GigaHertz { return nil, fmt.Errorf("d2xx: invalid speed %s; maximum supported clock is 30MHz", f) } if f > 30*physic.MegaHertz { // TODO(maruel): Figure out a way to communicate that the speed was lowered. // https://github.com/google/periph/issues/255 f = 30 * physic.MegaHertz } if f < 100*physic.Hertz { return nil, fmt.Errorf("d2xx: invalid speed %s; minimum supported clock is 100Hz; did you forget to multiply by physic.MegaHertz?", f) } if bits&7 != 0 { return nil, errors.New("d2xx: bits must be multiple of 8") } if bits != 8 { return nil, errors.New("d2xx: implement bits per word above 8") } s.c.f.mu.Lock() defer s.c.f.mu.Unlock() s.c.noCS = m&spi.NoCS != 0 s.c.halfDuplex = m&spi.HalfDuplex != 0 s.c.lsbFirst = m&spi.LSBFirst != 0 m &^= spi.NoCS | spi.HalfDuplex | spi.LSBFirst if s.c.halfDuplex { return nil, errors.New("d2xx: spi.HalfDuplex is not yet supported (implementing wouldn't be too hard, please submit a PR") } if m < 0 || m > 3 { return nil, errors.New("d2xx: unknown spi mode") } s.c.edgeInvert = m&1 != 0 s.c.clkActiveLow = m&2 != 0 if s.maxFreq == 0 || f < s.maxFreq { // TODO(maruel): We could set these only *during* the SPI operation, which // would make more sense. if _, err := s.c.f.h.MPSSEClock(f); err != nil { return nil, err } s.maxFreq = f } s.c.resetIdle() if err := s.c.f.h.MPSSEDBus(s.c.f.dbus.direction, s.c.f.dbus.value); err != nil { return nil, err } s.c.f.usingSPI = true return &s.c, nil } // LimitSpeed implements spi.Port. func (s *spiMPSEEPort) LimitSpeed(f physic.Frequency) error { if f > physic.GigaHertz { return fmt.Errorf("d2xx: invalid speed %s; maximum supported clock is 30MHz", f) } if f > 30*physic.MegaHertz { f = 30 * physic.MegaHertz } if f < 100*physic.Hertz { return errors.New("d2xx: minimum supported clock is 100Hz; did you forget to multiply by physic.MegaHertz?") } s.c.f.mu.Lock() defer s.c.f.mu.Unlock() if s.maxFreq != 0 && s.maxFreq <= f { return nil } s.maxFreq = f // TODO(maruel): We could set these only *during* the SPI operation, which // would make more sense. _, err := s.c.f.h.MPSSEClock(s.maxFreq) return err } // CLK returns the SCK (clock) pin. func (s *spiMPSEEPort) CLK() gpio.PinOut { return s.c.CLK() } // MOSI returns the SDO (master out, slave in) pin. func (s *spiMPSEEPort) MOSI() gpio.PinOut { return s.c.MOSI() } // MISO returns the SDI (master in, slave out) pin. func (s *spiMPSEEPort) MISO() gpio.PinIn { return s.c.MISO() } // CS returns the CSN (chip select) pin. func (s *spiMPSEEPort) CS() gpio.PinOut { return s.c.CS() } type spiMPSEEConn struct { // Immutable. f *FT232H // Initialized at Connect(). edgeInvert bool // CPHA=1 clkActiveLow bool // CPOL=1 noCS bool // CS line is not changed lsbFirst bool // Default is MSB first halfDuplex bool // 3 wire mode } func (s *spiMPSEEConn) String() string { return s.f.String() } func (s *spiMPSEEConn) Tx(w, r []byte) error { var p = [1]spi.Packet{{W: w, R: r}} return s.TxPackets(p[:]) } func (s *spiMPSEEConn) Duplex() conn.Duplex { // TODO(maruel): Support half if there's a need. return conn.Full } func (s *spiMPSEEConn) TxPackets(pkts []spi.Packet) error { // Verification. for _, p := range pkts { if p.KeepCS { return errors.New("d2xx: implement spi.Packet.KeepCS") } if p.BitsPerWord&7 != 0 { return errors.New("d2xx: bits must be a multiple of 8") } if p.BitsPerWord != 0 && p.BitsPerWord != 8 { return errors.New("d2xx: implement spi.Packet.BitsPerWord") } if err := verifyBuffers(p.W, p.R); err != nil { return err } } s.f.mu.Lock() defer s.f.mu.Unlock() const clk = byte(1) << 0 const mosi = byte(1) << 1 const miso = byte(1) << 2 const cs = byte(1) << 3 s.resetIdle() idle := s.f.dbus.value start1 := idle if !s.noCS { start1 &^= cs } // In mode 0 and 2, start2 is not needed. start2 := start1 stop := idle if s.edgeInvert { // This is needed to 'prime' the clock. start2 ^= clk // With mode 1 and 3, keep the clock steady while CS is being deasserted to // not create a spurious clock. stop ^= clk } ew := gpio.FallingEdge er := gpio.RisingEdge if s.edgeInvert { ew, er = er, ew } if s.clkActiveLow { // TODO(maruel): Not sure. ew, er = er, ew } // FT232H claims 512 USB packet support, so to reduce the chatter over USB, // try to make all I/O be aligned on this amount. This also removes the need // for heap usage. The idea is to always trail reads by one buffer. This is // fine as the device has 1024 byte read buffer. Operations look like this: // W, W, R, W, R, W, R, R // This enables reducing the I/O gaps between USB packets as the device is // always busy with operations. var buf [512]byte cmd := buf[:0] keptCS := false // Loop, without increasing the index. for _, p := range pkts { if len(p.W) == 0 && len(p.R) == 0 { continue } // TODO(maruel): s.halfDuplex. if !keptCS { for i := 0; i < 5; i++ { cmd = append(cmd, gpioSetD, idle, s.f.dbus.direction) } for i := 0; i < 5; i++ { cmd = append(cmd, gpioSetD, start1, s.f.dbus.direction) } } if s.edgeInvert { // This is needed to 'prime' the clock. for i := 0; i < 5; i++ { cmd = append(cmd, gpioSetD, start2, s.f.dbus.direction) } } op := mpsseTxOp(len(p.W) != 0, len(p.R) != 0, ew, er, s.lsbFirst) // Do an I/O loop. We can mutate p here because it is a copy. // TODO(maruel): Have the pipeline cross the packet boundary. if len(p.W) == 0 { // Have the write buffer point to the read one. This saves from // allocating memory. The side effect is that it will write whatever // happened to be in the read buffer. p.W = p.R[:] } pendingRead := 0 for len(p.W) != 0 { // op, sizelo, sizehi. chunk := len(buf) - 3 - len(cmd) if l := len(p.W); chunk > l { chunk = l } cmd = append(cmd, op, byte(chunk-1), byte((chunk-1)>>8)) cmd = append(cmd, p.W[:chunk]...) p.W = p.W[chunk:] if _, err := s.f.h.WriteFast(cmd); err != nil { return err } cmd = buf[:0] // TODO(maruel): Read 62 bytes at a time? // Delay reading by 512 bytes. if pendingRead >= 512 { if len(p.R) != 0 { // Align reads on 512 bytes exactly, aligned on USB packet size. if _, err := s.f.h.ReadAll(context.Background(), p.R[:512]); err != nil { return err } p.R = p.R[512:] pendingRead -= 512 } } pendingRead += chunk } // Do not forget to read whatever is pending. // TODO(maruel): Investigate if a flush helps. if len(p.R) != 0 { // Send a flush to not wait for data. cmd = append(cmd, flush) if _, err := s.f.h.WriteFast(cmd); err != nil { return err } cmd = buf[:0] if _, err := s.f.h.ReadAll(context.Background(), p.R); err != nil { return err } } // TODO(maruel): Inject this in the write if it fits (it will generally // do). That will save one USB I/O, which is not insignificant. keptCS = p.KeepCS if !keptCS { cmd = append(cmd, flush) for i := 0; i < 5; i++ { cmd = append(cmd, gpioSetD, stop, s.f.dbus.direction) } for i := 0; i < 5; i++ { cmd = append(cmd, gpioSetD, idle, s.f.dbus.direction) } if _, err := s.f.h.WriteFast(cmd); err != nil { return err } cmd = buf[:0] } } return nil } // CLK returns the SCK (clock) pin. func (s *spiMPSEEConn) CLK() gpio.PinOut { return s.f.D0 } // MOSI returns the SDO (master out, slave in) pin. func (s *spiMPSEEConn) MOSI() gpio.PinOut { return s.f.D1 } // MISO returns the SDI (master in, slave out) pin. func (s *spiMPSEEConn) MISO() gpio.PinIn { return s.f.D2 } // CS returns the CSN (chip select) pin. func (s *spiMPSEEConn) CS() gpio.PinOut { return s.f.D3 } // resetIdle sets D0~D3. D0, D1 and D3 are output but only touch D3 is CS is // used. func (s *spiMPSEEConn) resetIdle() { const clk = byte(1) << 0 const mosi = byte(1) << 1 const miso = byte(1) << 2 const cs = byte(1) << 3 if !s.noCS { s.f.dbus.direction &= 0xF0 s.f.dbus.direction |= cs s.f.dbus.value &= 0xF0 s.f.dbus.value |= cs } else { s.f.dbus.value &= 0xF8 s.f.dbus.direction &= 0xF8 } s.f.dbus.direction |= mosi | clk if s.clkActiveLow { // Clock idles high. s.f.dbus.value |= clk } } // // spiSyncPort is an SPI port over a FTDI device in synchronous bit-bang mode. type spiSyncPort struct { c spiSyncConn // Mutable. maxFreq physic.Frequency } func (s *spiSyncPort) Close() error { s.c.f.mu.Lock() s.c.f.usingSPI = false s.maxFreq = 0 s.c.edgeInvert = false s.c.clkActiveLow = false s.c.noCS = false s.c.lsbFirst = false s.c.halfDuplex = false s.c.f.mu.Unlock() return nil } func (s *spiSyncPort) String() string { return s.c.f.String() } const ft232rMaxSpeed = 3 * physic.MegaHertz // Connect implements spi.Port. func (s *spiSyncPort) Connect(f physic.Frequency, m spi.Mode, bits int) (spi.Conn, error) { if f > physic.GigaHertz { return nil, fmt.Errorf("d2xx: invalid speed %s; maximum supported clock is 1.5MHz", f) } if f > ft232rMaxSpeed/2 { // TODO(maruel): Figure out a way to communicate that the speed was lowered. // https://github.com/google/periph/issues/255 f = ft232rMaxSpeed / 2 } if f < 100*physic.Hertz { return nil, fmt.Errorf("d2xx: invalid speed %s; minimum supported clock is 100Hz; did you forget to multiply by physic.MegaHertz?", f) } if bits&7 != 0 { return nil, errors.New("d2xx: bits must be multiple of 8") } if bits != 8 { return nil, errors.New("d2xx: implement bits per word above 8") } s.c.f.mu.Lock() defer s.c.f.mu.Unlock() s.c.noCS = m&spi.NoCS != 0 s.c.halfDuplex = m&spi.HalfDuplex != 0 s.c.lsbFirst = m&spi.LSBFirst != 0 m &^= spi.NoCS | spi.HalfDuplex | spi.LSBFirst if s.c.halfDuplex { return nil, errors.New("d2xx: spi.HalfDuplex is not yet supported (implementing wouldn't be too hard, please submit a PR") } if m < 0 || m > 3 { return nil, errors.New("d2xx: unknown spi mode") } s.c.edgeInvert = m&1 != 0 s.c.clkActiveLow = m&2 != 0 if s.maxFreq == 0 || f < s.maxFreq { if err := s.c.f.SetSpeed(f * 2); err != nil { return nil, err } s.maxFreq = f } // D0, D2 and D3 are output. D4~D7 are kept as-is. const mosi = byte(1) << 0 // TX const miso = byte(1) << 1 // RX const clk = byte(1) << 2 // RTS const cs = byte(1) << 3 // CTS mask := mosi | clk | cs | (s.c.f.dmask & 0xF0) if err := s.c.f.setDBusMaskLocked(mask); err != nil { return nil, err } // TODO(maruel): Combine both following calls if possible. We'd shave off a // few ms. if !s.c.noCS { // CTS/SPI_CS is active low. if err := s.c.f.dbusSyncGPIOOutLocked(3, gpio.High); err != nil { return nil, err } } if s.c.clkActiveLow { // RTS/SPI_CLK is active low. if err := s.c.f.dbusSyncGPIOOutLocked(2, gpio.High); err != nil { return nil, err } } s.c.f.usingSPI = true return &s.c, nil } // LimitSpeed implements spi.Port. func (s *spiSyncPort) LimitSpeed(f physic.Frequency) error { if f > physic.GigaHertz { return fmt.Errorf("d2xx: invalid speed %s; maximum supported clock is 1.5MHz", f) } if f < 100*physic.Hertz { return fmt.Errorf("d2xx: invalid speed %s; minimum supported clock is 100Hz; did you forget to multiply by physic.MegaHertz?", f) } s.c.f.mu.Lock() defer s.c.f.mu.Unlock() if s.maxFreq != 0 && s.maxFreq <= f { return nil } if err := s.c.f.SetSpeed(f * 2); err == nil { s.maxFreq = f } return nil } // CLK returns the SCK (clock) pin. func (s *spiSyncPort) CLK() gpio.PinOut { return s.c.CLK() } // MOSI returns the SDO (master out, slave in) pin. func (s *spiSyncPort) MOSI() gpio.PinOut { return s.c.MOSI() } // MISO returns the SDI (master in, slave out) pin. func (s *spiSyncPort) MISO() gpio.PinIn { return s.c.MISO() } // CS returns the CSN (chip select) pin. func (s *spiSyncPort) CS() gpio.PinOut { return s.c.CS() } type spiSyncConn struct { // Immutable. f *FT232R // Initialized at Connect(). edgeInvert bool // CPHA=1 clkActiveLow bool // CPOL=1 noCS bool // CS line is not changed lsbFirst bool // Default is MSB first halfDuplex bool // 3 wire mode } func (s *spiSyncConn) String() string { return s.f.String() } func (s *spiSyncConn) Tx(w, r []byte) error { var p = [1]spi.Packet{{W: w, R: r}} return s.TxPackets(p[:]) } func (s *spiSyncConn) Duplex() conn.Duplex { // TODO(maruel): Support half if there's a need. return conn.Full } func (s *spiSyncConn) TxPackets(pkts []spi.Packet) error { // We need to 'expand' each bit 2 times * 8 bits, which leads // to a 16x memory usage increase. Adds 5 samples before and after. totalW := 0 totalR := 0 for _, p := range pkts { if p.KeepCS { return errors.New("d2xx: implement spi.Packet.KeepCS") } if p.BitsPerWord&7 != 0 { return errors.New("d2xx: bits must be a multiple of 8") } if p.BitsPerWord != 0 && p.BitsPerWord != 8 { return errors.New("d2xx: implement spi.Packet.BitsPerWord") } if err := verifyBuffers(p.W, p.R); err != nil { return err } // TODO(maruel): Correctly calculate offsets. if len(p.W) != 0 { totalW += 2 * 8 * len(p.W) } if len(p.R) != 0 { totalR += 2 * 8 * len(p.R) } } // Create a large, single chunk. var we, re []byte if totalW != 0 { totalW += 10 we = make([]byte, 0, totalW) } if totalR != 0 { totalR += 10 re = make([]byte, totalR) } const mosi = byte(1) << 0 // TX const miso = byte(1) << 1 // RX const clk = byte(1) << 2 // RTS const cs = byte(1) << 3 // CTS s.f.mu.Lock() defer s.f.mu.Unlock() // https://en.wikipedia.org/wiki/Serial_Peripheral_Interface#Data_transmission csActive := s.f.dvalue & s.f.dmask & 0xF0 csIdle := csActive if !s.noCS { csIdle = csActive | cs } clkIdle := csActive clkActive := clkIdle | clk if s.clkActiveLow { clkActive, clkIdle = clkIdle, clkActive csIdle |= clk } // Start of tx; assert CS if needed. we = append(we, csIdle, clkIdle, clkIdle, clkIdle, clkIdle) for _, p := range pkts { if len(p.W) == 0 && len(p.R) == 0 { continue } // TODO(maruel): s.halfDuplex. for _, b := range p.W { for j := uint(0); j < 8; j++ { // For each bit, handle clock phase and data phase. bit := byte(0) if !s.lsbFirst { // MSBF if b&(0x80>>j) != 0 { bit = mosi } } else { // LSBF if b&(1<> uint(j) } else { // LSBF b |= 1 << uint(j) } } } p.R[i] = b } } return nil } // CLK returns the SCK (clock) pin. func (s *spiSyncConn) CLK() gpio.PinOut { return s.f.D2 // RTS } // MOSI returns the SDO (master out, slave in) pin. func (s *spiSyncConn) MOSI() gpio.PinOut { return s.f.D0 // TX } // MISO returns the SDI (master in, slave out) pin. func (s *spiSyncConn) MISO() gpio.PinIn { return s.f.D1 // RX } // CS returns the CSN (chip select) pin. func (s *spiSyncConn) CS() gpio.PinOut { return s.f.D3 // CTS } // func verifyBuffers(w, r []byte) error { if len(w) != 0 { if len(r) != 0 { if len(w) != len(r) { return errors.New("d2xx: both buffers must have the same size") } } // TODO(maruel): When the buffer is >64Kb, cut it in parts and do not // request a flush. Still try to read though. if len(w) > 65536 { return errors.New("d2xx: maximum buffer size is 64Kb") } } else if len(r) != 0 { // TODO(maruel): Remove, this is not a problem. if len(r) > 65536 { return errors.New("d2xx: maximum buffer size is 64Kb") } } return nil } var _ spi.PortCloser = &spiMPSEEPort{} var _ spi.Conn = &spiMPSEEConn{} var _ spi.PortCloser = &spiSyncPort{} var _ spi.Conn = &spiSyncConn{}