Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPI #3

Open
rwaldron opened this issue Feb 18, 2015 · 21 comments
Open

SPI #3

rwaldron opened this issue Feb 18, 2015 · 21 comments
Assignees

Comments

@rwaldron
Copy link
Owner

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

@nebrius
Copy link
Contributor

nebrius commented Feb 18, 2015

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
Copy link

natevw commented Feb 18, 2015

xref: rwaldron/johnny-five#624 (comment)

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
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link

natevw commented Feb 19, 2015

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
Copy link
Contributor

nebrius commented Feb 19, 2015

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
Copy link

natevw commented Feb 19, 2015

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
Copy link
Contributor

nebrius commented Feb 19, 2015

TIL

@soundanalogous
Copy link
Collaborator

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
Copy link
Collaborator

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

@nebrius
Copy link
Contributor

nebrius commented Mar 8, 2015

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
Copy link
Collaborator

The separation is necessary for the following reasons:

  • For spi.read a length needs to be supplied to indicate how many bytes to clock in
  • spi.write indicates to the underlying implementation that no data is expected in return
  • spi.transfer indicates that for each word sent a word should be returned

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
Copy link
Contributor

nebrius commented Mar 8, 2015

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
Copy link
Collaborator

That is correct.

@nodebotanist
Copy link

@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
Copy link
Collaborator

@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
Copy link
Collaborator

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

Feedback is welcome.

@rwaldron
Copy link
Owner Author

rwaldron commented Jan 9, 2017

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

@fivdi
Copy link
Contributor

fivdi commented Apr 8, 2017

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

@natevw
Copy link

natevw commented Jun 5, 2017

@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
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants