rwaldron / io-plugins

Documentation and discussion point for IO Plugins
117 stars 21 forks source link

SPI #3

Open rwaldron opened 9 years ago

rwaldron commented 9 years ago

Define a generic API for SPI that can be implemented across all platforms

nebrius commented 9 years ago

FWIW, here's the documentation for SPI in the C++ library I use for raspi-io: http://wiringpi.com/reference/spi-library/. Specifically, you specify a channel and a clock rate in initialization, and reading/writing is batched together (since SPI slaves can only write while the master is also sending data)

natevw commented 9 years ago

xref: https://github.com/rwaldron/johnny-five/issues/624#issuecomment-74809873

Also, you could check out my https://github.com/natevw/pi-spi as an example simple wrapper around the Linux spidev interface if that's relevant for such a backend.

soundanalogous commented 9 years ago

I'm also looking into this spi module for Linux: https://www.npmjs.com/package/spi. I'll try both once I get a compatible board.

soundanalogous commented 9 years ago

One of the bigger issues I'm facing in defining a protocol is how to handle the CS pin and any secondary CS-type pins. On the Firmata side there is an advantage to specifying the CS pin and any CS-like pins in the config message, then managing them on the firmware side to avoid having to send too many messages over Serial (especially for SPI devices that require toggling CS between every byte read - not yet sure if that's even a case worth supporting). However for Linux implementations (Pi, BeagleBone, Galileo, etc) it may be better to handle the CS pin separately since there is far less latency involved (I'm assuming).

natevw commented 9 years ago

Yes, another thing to consider along with related pins is transactions that need to hold the SPI bus. I forget the exact situation but there was an avoidable need with https://github.com/tessel/sdcard to "lock out" SPI communications with other chips during some operations (i.e. a CSN pin had to remain down even between individual SPI transfers) — there are likely other types of devices that are are SPI "compatible" but need similar coddling.

This is how Tessel ended up exposing such a feature at the API level: https://github.com/tessel/docs/blob/master/tutorials/raw-spi.md (via https://tessel.io/docs/hardwareAPI#api-spi-lock-callback)

nebrius commented 9 years ago

I remember in college doing some SPI work where I had something like 4 SS pins (ne CS, ne CSN, ne why the hell are there so many names for this pin) on the master that would essentially "toggle" between slaves to avoid needing more than one SPI port.

If we want to support these types of use cases, would it make sense to start by having the SS just be "automagic" for now and handled on the firmata side, and provide a flag for saying "no, I'm going to handle SS myself"?

natevw commented 9 years ago

Right, sharing a bus is the usual SPI situation (see http://www.dorkbotpdx.org/blog/paul/better_spi_bus_design_in_3_steps for "protips" on the hardware side) but there's some devices (SD Card for one I'm familiar with) that are mostly SPI-compatible but don't really follow that "standard" and use the select pin for signaling more than just "I am talking with you via the other three pins". In the SD case, after you issue a read/write request you need to hold its CSN equivalent "active" (i.e. low) and during that time you want to make sure no other SPI usage happens.

That's what the "lock" is for, so that you can reserve the SPI bus against other traffic. A more mundane use case is when you run out of hardware pins and start using random other GPIO pins for CSN…that pin state needs to be coordinated with the SPI transfer the same sort of mutex/fifo can facilitate that.

nebrius commented 9 years ago

TIL

soundanalogous commented 9 years ago

My current thoughts (incorporating feedback from Nate and Bryan):

configuration options

// I'm working this out for the Firmata protocol at the same time so that is why you
// see the number of bits here... for J5 each config value would be separate, for node-firmata
// they will need to be packed (arrangement TBD) prior to TX to the Arduino
channel          for Linux: "/dev/spidev0.0", etc; TBD for other multi SPI port HW
deviceId         (enum from 0) used to identify the specific SPI device (could use CS pin instead)
bitOrder         (1 bits)  MSB (default?) | LSB
dataMode         (2 bits)  (0 | 1 | 2 | 3)
clockSpeed       (32 bits)

// by default handle the CS pin automatically by pulling it to the active state at the
// beginning of a transfer and releasing it at the end of the transfer
// the user can set this bit to 0 to handle CS (and any CS-like pins) manually
handleCSPin      (1 bit)  1 if CS pin should be handled automatically
  csActive       (1 bit)  default = 0 (LO)
  csToggle       (1 bit)  1 if toggle CS pin should be toggled 
                          between transferred word (this is a rare case)
  csPin          (7 bits) default = 0

// the following may be useful. We'd default to 8 bit words, but with an option for
// the user to set 1 to 16 bit word lengths
// I'm not sure what the smallest word size is but have seen documentation for devices
// as small as 3 bit words
wordSize         (4 bits) (1 to 16 bit) value + 1 (default = 8)

methods

// the idea here is that each device would have its own SPI instance
// someone please  correct me if I'm wrong but it seems this is already the case for
// many Linux SPI interfaces. Otherwise if there is only a single SPI instance (per SPI
// port on a device if multiple) then options will need to be set on the spi object
// instance before each transaction with a different device

// constructor (see options above)
// a setter could also be provided for each option
var spi = new SPI(options)

// when you need to write and read in a single transaction (most common use case)
// The implementation must ensure that the number of words written equals the
// number of words received. If a written word does not return data, 0 should be returned
spi.transfer(txBuffer, cb(err, rxBuffer))

// writes 0 for each byte requested
// length is number of words (see words option above)
spi.read(length, cb(err, rxBuffer))

// for write only transactions
spi.write(txBuffer, cb(err))
soundanalogous commented 9 years ago

For node-firmata applications we'll also need spi.end()

nebrius commented 9 years ago

Just a thought: what do you think about making txBuffer optional in spi.transfer? This way, read and write are just direct aliases to transfer, and can be literally implemented as:

SPI.prototype.transfer = SPI.prototype.read = SPI.prototype.write = function() ...
soundanalogous commented 9 years ago

The separation is necessary for the following reasons:

spi.write and spi.transfer could be consolidated but it would take an additional parameter to specify whether or not to return data. I think having separate write and transfer methods is cleaner.

nebrius commented 9 years ago

I see, so spi.write/spi.read are convenience methods, essentially, right? SPI works by reading/writing simultaneously, and write/read essentially handles the case where you don't care about one or the other but still have to provide something, correct? In that case, you're right, it does make sense to have them separate.

soundanalogous commented 9 years ago

That is correct.

nodebotanist commented 9 years ago

@soundanalogous Hello! I have some bandwidth this week to begin working on this as well-- I'm going to start with the edison wrapper, maybe add Galileo if there's time this week.

Let me know if there's a branch I can look at to see where you're at, or have any further ideas :).

soundanalogous commented 9 years ago

@nodebotanist that's great news! I haven't done any on an implementation yet for Edison or Galileo yet. My effort so far has been on trying to nail down the SPI protocol for Firmata and coming up with a high level API for johnny-five (this thread) that will work for both Linux and Arduino/Firmata based SPI implementations. If you want to take on the Linux side (at least for Edison and maybe Galileo for now) that would be great actually since I don't have much experience with those platforms. I plan to work on the Arduino implementation. Let's sync up on the high level API for johnny-five.

soundanalogous commented 7 years ago

I'm finally making some progress here on the Arduino and firmata.js front:

Feedback is welcome.

rwaldron commented 7 years ago

Wow, rad! I will try to have a look as soon as I can :)

fivdi commented 7 years ago

Another potential candidate for Linux IO Plugins would be https://github.com/fivdi/spi-device

natevw commented 7 years ago

@soundanalogous @rwaldron Very cool! It looks like the SPI proposal was merged upstream, does that mean we're go for SPI in johnny-five?

soundanalogous commented 7 years ago

The Firmata SPI proposal is not yet final. It will still be a while before it is finalized due to the limited amount of feedback on the proposal.