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

Add proposal for SPI #26

Open
soundanalogous opened this issue Feb 15, 2015 · 48 comments
Open

Add proposal for SPI #26

soundanalogous opened this issue Feb 15, 2015 · 48 comments

Comments

@soundanalogous
Copy link
Member

via conversation with @ajfisher on gitter:

There was a pull-request for SPI opened a while back: firmata/arduino#135. However, it's going to be more complex than just wrapping the Arduino SPI api (since that would be inefficient). Lets start by drafting a protocol here: https://github.com/firmata/protocol.

My initial thoughts are that it should have a SPI_CONFIG message (like I2C and many other features in Firmata). The config message should let the user specify the following:

CS/SS pin (this is also how you'll keep track of multiple SPI devices)
bit order (LSB or MSB first)
data mode (see the 4 modes defined here)
clock divider? (I'm not sure how this would work in Firmata or if it even applies... I think it depends on if a hardware enabled SS pin is selected on the board)
We'll need a way to call SPI.begin and SPI.end. I'm not sure if it's better to call begin when the config message is received or if there is any advantage in having it be a separate command. SPI_END can be a simple message to call SPI.end, perhaps also specifying the CS pin to be compliant with Arduino Due's extended SPI protocol (for other boards this parameter would simply be ignored in the implementation).

We'll need a SPI_REQUEST message. It should contain the CS/SS pin number and the data to be sent via SPI.transfer(val) on the board. It will be more efficient to send multiple bytes rather than a single byte (this likely also depends on the particular device) - so in the implementation you'd call SPI.transfer(val) multiple times for a single SPI_REQUEST rather than sending a separate Request for each byte). We'll need a strategy around buffering as well (more for the implementation than the protocol). It will be helpful to look across a variety of SPI devices to see what what type of data is sent... what would constitute a "packet". Be aware however of the 64 byte serial data buffer on Arduino since this will likely come into play. It would also be helpful to support a READ_CONTINUOUSLY mode like I2C Firmata (this is especially useful for SPI-based sensors).

And we need a SPI_REPLY message to send data back the the Firmata client. This should include the CS pin, maybe the number of bytes in the reply and whether or not this is the full packet (or if it's broken up into multiple packets). Perhaps this is as simple as indicating if this is the LAST set of data.

Also try to support Arduino Due's extended SPI feature as an option. You'd add this to the protocol for SPI_END and SPI_REQUEST (although I'm not sure how beneficial this extended set would be in a Firmata client library... may add more complexity than it's worth). This is lower priority.

@soundanalogous
Copy link
Member Author

The trick with SPI is the configuration parameters for each SPI device may differ. Therefore a single SPI_CONFIG message would not be sufficient if parameters are passed via CONFIG. Some options:

  1. SPI_BEGIN for initialization of SPI hardware (only needs to be called once regardless of number of SPI devices).
  2. SPI_CONFIG to be called for each device, passing along any required parameters: csPin, bitOrder, dataMode, clockDivider. The idea is to call CONFIG from the constructor in the client implementation. Then in SPI_REQUEST, pass the csPin and in the firmware implementation look to see if it has changed and if so, update any parameters with those passed with SPI_CONFIG.
  3. Instead of SPI_CONFIG for each device, SPI_SELECT with the following parameters: csPin, bitOrder, dataMode, clockDivider. Would need to be called each time you want to transfer data to a different device. In this case SPI_REQUEST would not need to update configuration parameters if a different device was selected for data transfer.

@PaulStoffregen
Copy link
Member

I haven't been active in Firmata development for a while, but I certainly have with SPI. :)

Arduino 1.6.0 was (finally) released 1 week ago, featuring a new SPI library with transaction support (contributed by me), and improved hardware-independent configuration (contributed by Matthijs Kooijman).

I know Firmata has historically supported older versions of Arduino. But for supporting SPI, I'd highly recommend using the new SPISettings() feature with SPI.beginTransaction(). If the settings will be delivered over the Firmata protocol, you can use them once to store a SPISettings object, so the overhead of converting the settings to SPISettings's efficient internal storage only needs to be done once.

This new SPISettings approach is much more efficient, and it's hardware neutral. There's no need to ever write code dealing with hardware dividers or clock speeds. You just give it the actual SPI settings, with clock speed as a plain 32 bit integer, and the SPISettings feature in the library automatically turns those into the hardware's closest possible configuration.

Arduino hasn't documented this stuff yet on their website. Hopefully they will soon. Today, the only docs are the code+comments, and this page on my site:

http://www.pjrc.com/teensy/td_libs_SPI.html

@soundanalogous
Copy link
Member Author

It's great to see you chiming in here Paul! Your expertise is appreciated.

Thanks for the tip, that actually makes thing much easier for the Arduino implementation. I'm trying to keep the Firmata protocol documentation (this particular repo) from being Arduino-specific in case some day platforms other than Arduino adopt the Firmata protocol. However I think the Arduino SPI api is still generic enough to work on other platforms, especially now that the SPISettings allows specifying a general clockSpeed rather than a AVR-specific clockDivider parameter.

@soundanalogous
Copy link
Member Author

Here's an initial pass at a generic Firmata SPI protocol.

For Arduino, SPI support would be for Arduino 1.6 and higher.

// SPI_BEGIN
// called once to initialize SPI hardware
// can optionally be set internally on first SPI_CONFIG_DEVICE message
0:  START_SYSEX
1:  SPI_DATA
2:  SPI_BEGIN
3:  END_SYSEX
// SPI_CONFIG_DEVICE
// sent once for each SPI device
0:  START_SYSEX 
1:  SPI_DATA
2:  SPI_CONFIG_DEVICE
3:  csPin (0-127)  // use csPin to identify individual devices
4:  bitOrder (bit 0)  | dataMode (bits 1-6) // also need to track csPin HIGH | LOW ?
5:  clockSpeed (bits 0 - 6)
6:  clockSpeed (bits 7 - 14)
7:  clockSpeed (bits 15 - 21)
8:  clockSpeed (bits 22 - 28)
9:  clockSpeed (bits 29 - 32)
10:  END_SYSEX

// implementation details:
// Upon receiving SPI_CONFIG_DEVICE, If SPI_BEGIN has not yet been called
// call it once internally
// for Arduino: store a SPISettings for each device (determined by csPin number)
// SPI_TRANSFER_REQUEST
// send a byte array of data to transfer to the SPI slave device
0:  START_SYSEX
1:  SPI_DATA
2:  SPI_TRANSFER_REQUEST
3:  csPin (0 - 127)
4:  numBytes (limit TBD) // needed because we'll reuse a single array for all transfers
5:  data 0 (LSB)
6:  data 1 (MSB)
... up to numBytes * 2
N: END_SYSEX

// implementation details:
// Track csPin and if the csPin on an incoming request differs from the last request,
// set the stored configuration parameters (if they differ from the last device) for the 
// device associated with the request.
// for Arduino: pass the stored SPISettings for this csPin to SPI.BeginTransaction, 
// then call digitalWrite on the last and current device to toggle the CS pin for each
// SPI_TRANSFER_REPLY
// a byte array of data received from the SPI slave device in response to the transfer
0:  START_SYSEX
1:  SPI_DATA
2:  SPI_TRANSFER_REPLY
3:  csPin (0 - 127)
4:  numBytes (limit TBD)
5:  data 0 (LSB)
6:  data 1 (MSB)
... up to numBytes * 2
N: END_SYSEX

// implementation details:
// Ensure that numBytes in reply is equal to numBytes in request.
// Zero pad array before sending reply.
// SPI_END
// called once to release SPI hardware
// send before quitting a Firmata client application
0:  START_SYSEX
1:  SPI_DATA
2:  SPI_END
3:  END_SYSEX

// implementation details
// for Arduino: call SPI.endTransaction()

@PaulStoffregen
Copy link
Member

One thing I learned last year, while working on the Arduino SPI library, is the tremendous number of different ways SPI actually gets used. I really do not believe it's possible to design a Firmata protocol that will accommodate absolutely all SPI uses. The question is probably how large a fraction of uses should it cover?

For example, many of the SPI interface displays take 2 control signals, a chip select and an other CS-like signal which functions as an address bit, for which register within the chip will be accessed.

Some devices require an extra dummy byte after chip select is de-asserted. Some require chip select only on the first or last byte. There are lots of special cases.

A complex protocol that could cover a lot of cases might involve sending several extra bits with each data byte. Four or 5 bits could indicate which digital pins to assert for chip select, during each byte. Perhaps 1 bit could indicate if the received byte during that transfer needs to be returned, or simply discarded.

Or, you could implement the paradigm where a single chip select goes low before the first transfer, and returns high after the last byte. That certainly is enough for a lot of SPI chips. But there are also a good number of special cases, so at least know that other stuff exists.

@soundanalogous
Copy link
Member Author

Paul do you remember any specific SPI devices you came across that exhibit some of the edge cases you encountered? I'd like to look over their data sheets.

I don't plan to support all possible cases. SPI devices requiring continuous high speed data transfer (like a mp3 decoder) would not be possible with Firmata and I'm fine with that. I think most users will want to use SPI for various sensors and perhaps some actuators. Those are the majority of requests for SPI support I've had so far are for sensors that use SPI and I usually point out the fact that the same sensors typically have an I2C interface which is already supported by Firmata.

The option of using the extra 6 bits from every other byte is interesting... I had not thought of that approach before. It would require a few extra cycles on the microcontroller to process but it would add a few options to expand the range of supported devices for sure so it may be worth it if that increases overall support by a significant factor. I'll look into this further.

My current thought is to return a byte for every byte transferred. If the transfer doesn't return data, I'd just send a value of 0 for that byte. Then it would be the responsibility of the Firmata client to properly parse the response data according to the particular SPI device in use. However, I may vary well be overlooking something here. It's been a while since I've done any extensive work with SPI.

I could also keep the CS pin out of the definition and require the user to manage that separately (along with any additional CS-like signal as you mentioned) but there is something convenient of supplying those pins in the initial CONFIG message rather than sending separate messages to toggle CS and transfer data.

@soundanalogous
Copy link
Member Author

If a SPI device does not need to return data (such as an LCD screen or other write-only device) then my assumption about always returning data would be a waste of bandwidth. In that case, using one of the extra 6 bits to indicate whether or not to return a byte could work, or I could alternatively use a bit in the command byte of the SPI_TRANSFER_REQUEST message:

0: START_SYSEX
1: SPI_DATA
2: SPI_TRANSFER_REQUEST
    bits 0-2 = command
    if bit 6 is set, don't send a response because transfer does not return data for this device
    since this is sent by the Firmata client, it expects not to receive a response for this message
...

This would work if no bytes in the message (assuming a multi-byte message) return data. If at least 1 transferred byte in the message returns data, then an padded byte for each message would be returned since a response would be needed anyway.

@madeintheusb
Copy link

#26

For my USB device Nusbio, I implemented part of i2c and spi protocol.

Here some interresting chips, I tested

Classic SPI

MCP3008 - 8 ADC 10 bit (a good case to test regular SPI)
    - PINS Clock, Mosi, Miso, CS (SELECT)

MCP4231 - 2 digital potentiomenters
    - PINS Clock, Mosi, Miso, CS (SELECT)

8x8 LED Matrix, Seven Segment Display based MAX7219.
    - PINS Clock, Mosi, CS (SELECT)
    - No Miso, but support chaining up to 8 MAX7219. Because MAX7219 is a SPI shift register,
    the behavior of SELECT/UNSELECT act as a commit transacton. Good test case for supporting
    different SPI in chained shift register mode.

Not Classic SPI

Oled 128x64 Chip SH1106, SSD1306 (with this one there is  no Miso, but we need a DC and RESET pin)
    - PINS Clock, Mosi, CS (SELECT), DC (Data/Command), RESET
    - RESET pin is part of SPI, but not always used

APA 102 LED Strip - Not stricly speaking SPI, but fun. APA 102 should support a higher refresh rate than WS2812 and should work with SPI.
    - PINS Clock, Mosi

MCP4131 - Digital potetiometer - With this one MISO and MOSI are combined on the same pin. I could not get it to work with the regular arduino SPI library
- PINS Clock, Mosi/Miso, CS (SELECT)

@soundanalogous
Copy link
Member Author

The latest iteration of the SPI proposal is here: #27

@raybuay
Copy link

raybuay commented Jul 12, 2016

but is there a firmata vertion with at least basic master to slave SPI support?
what is the fastest way to be able to use digipots with Pure Data or Max/Msp?
both digipots and digital to analog converters need the SPI protocol to operate
and it is still not implemented in firmata!!!

i tried using RC low pass filters for DAC conversion but the quality is terrible.
are there digipots that could operate with PWM instead of the SPI protocol?
or a DAC chip that will convert PWM to an analog voltage without the SPI protocol?
i found myself trapped with this problem.

when will firmata have SPI support?

regards
r.y

@soundanalogous
Copy link
Member Author

I need someone much more experienced with SPI than myself to contribute to lead this effort as I am not as familiar with all of the edge cases that Paul pointed out. So far no one has taken this on and any existing SPI implementations for Firmata are very basic and only cover one or two common cases. I don't want to lock into an implementation that will not scale well.

@raybuay
Copy link

raybuay commented Jul 12, 2016

but is there implementation for digipots like the AD5206 or the MCP41100?

@soundanalogous
Copy link
Member Author

Not for Firmata

@soundanalogous
Copy link
Member Author

Well it may work with some of the Firmata forks that add SPI. I haven't tried any of them. If you find one that works, encourage the author to submit a PR to add SPI support.

@madeintheusb
Copy link

Ray,
Our device Nusbio may help you right away

@raybuay
Copy link

raybuay commented Jul 13, 2016

hello

the problem i have is that without at least some basic implementation of spi in firmata
i cannot set up and control the digipots or DACs from WITHIN PD. firmata needs to receive
the messages to enable the devices from the host program and then
receive the 8 bit controller data to the available registers.

r.y

@soundanalogous
Copy link
Member Author

Try ExtendedFirmata, it includes SPI support.

@raybuay
Copy link

raybuay commented Jul 13, 2016

tried to load extended firmata but i gives me errors
i upgraded the arduino ide to 1.6.9 but it doesn't fix it

errors like serial1, 2, 3 are not declare on this scope
and some conflict of pin modes redefinitions

how can i solve this
thanks
r.y

@soundanalogous
Copy link
Member Author

I see now that ExtendedFirmata is only compatible with an Arduino Mega or Due.

@raybuay
Copy link

raybuay commented Jul 13, 2016

ok, i tried in a due and now i have a: fatal error SoftwareSerial

@raybuay
Copy link

raybuay commented Jul 13, 2016

i took the: #include SoftwareSerial.h
out of the code and it did go through. i don't
know if this will affect functionality in any way?

and where is the information need it to operate
the spi functionality from the host program?

thanks so much for the help

regards
r.y

@soundanalogous
Copy link
Member Author

That's right... forgot Due doesn't support SoftwareSerial. It will only work with a Mega then.

@soundanalogous
Copy link
Member Author

You will have to add SPI functionality to the host (sounds like PD in your case) unless you are using golang.

@raybuay
Copy link

raybuay commented Jul 14, 2016

in pd, you send a pinout definition message to enable the boards pins
example, input,output,pwm,servo, spi??? . . .
and then sending or receiving data based on this initial configuration

i don't know where are the instruction for driving a digipot from pd
because the help files all have all the old implementation
what is golang?

r.y

@soundanalogous
Copy link
Member Author

You will likely need to create a new object for PD to implement the Firmata SPI interface as defined in ExtendedFirmata. I'm not a PD user so I can't help you there.

@raybuay
Copy link

raybuay commented Jul 16, 2016

thanks 4 all d help soundanalogous. the thing is that i cannot find anywhere the commands i need to enable the spi functionality. i tried finding some clues from the extendedfirmata file itself but all i have are errors. where can i find this messages or commands?

it could be nice to have this new implementation in the firmata test program so you
get the idea of the structure order for enabling operation.

@soundanalogous
Copy link
Member Author

You need to look at this code specifically: https://github.com/kraman/go-firmata/blob/master/contrib/ExtendedFirmata/ExtendedFirmata.ino#L626-L663.

However it is probably best to wait until SPI support is officially added to the Firmata protocol. At that point you can engage with the PD community for help on updating the PD Firmata client to add SPI support. It's not simply going to work just because Firmata gets SPI. Each firmata client also needs to implement that part of the protocol in order to get the new functionality.

@dprophet
Copy link

This is almost 2 years old. Whats the status of SPI + Firmata? I am in need of this feature as well because customized sensor libraries steal the 32k of space.

@soundanalogous
Copy link
Member Author

I don't have a ton of experience with SPI so I don't feel the best suited person to take on this task, however no one else is volunteering which is why it has seen no progress in 2 years.

@soundanalogous
Copy link
Member Author

@madeintheusb this isn't a place to advertise your services.

@madeintheusb
Copy link

I guess no SPI at all if we cannot advertise product that can do SPI.

@soundanalogous
Copy link
Member Author

What I need here is consensus around a proposal so that a SPI implementation for Firmata can move forward. This is the most recent version: #27.

@soundanalogous
Copy link
Member Author

Did a substantial revision the SPI proposal today: #27

Feedback is welcome.

@soundanalogous
Copy link
Member Author

soundanalogous commented May 13, 2018

Picking this up again and I think the following changes to the proposal are necessary (and an improvement over past iterations):

  1. Remove the csPinControl from the read, write and transfer messages. Instead make CS pin control an option of the begin/end transaction (or part of the new SPI_DEVICE_CONFIG option). This was a mess anyway.
  2. Each transaction should generally be framed by begin/end transaction. Previously I was approaching SPI_BEGIN_TRANSACTION as something you'd only need to send once if you only had 1 SPI device attached. However since Firmata also supports WiFi and Ethernet HW that uses the SPI bus, that would have caused potential conflicts. Enforcing this framing also simplifies automated CS pin management, but requires more messages to be sent than in the current proposal.
  3. To simplify more frequent use of the SPI_BEGIN_TRANSACTION message, provide a way to either set the SPI settings once per device, or at most once each time the user addresses a new device (See options A & B below for a comparison).

There are a couple of different approaches to SPI_BEGIN_TRANSACTION here:

Option A

Make passing the config params to SPI_BEGIN_TRANSACTION optional. The first time you address a device, you'd pass all of the required parameters. Subsequently you would not unless addressing a separate device. This results in sending a lot more data in the case where you are frequently addressing 2 more different attached devices, but saves memory over the alternate approach (Option B).

// First transfer to an attached device, or when addressing a separate device
// on the same channel. The settings would be stored for this channel.
0:  START_SYSEX
1:  SPI_DATA              (0x68)
2:  SPI_BEGIN_TRANSACTION (0x01)
3:  channel               (bits 2-6: reserved, bits 0-1: channel)
4:  dataMode | bitOrder   (bits 1-2: dataMode (0-3), bit 0: bitOrder)
5:  maxSpeed              (bits 0 - 6)
6:  maxSpeed              (bits 7 - 14)
7:  maxSpeed              (bits 15 - 21)
8:  maxSpeed              (bits 22 - 28)
9:  maxSpeed              (bits 29 - 32)
10: wordSize              (0 = DEFAULT = 8-bits, 1 = 1-bit, 2 = 2 bits, etc)
11: csPin                 [optional] (0-127) The chip select pin number
12: csPinOptions          (required if csPin specified)
                          bit 0: CS_ACTIVE_STATE (0 = Active LOW (default)
                                                  1 = Active HIGH)
                          bit 1: CS_TOGGLE (0 = toggle at end of transfer (default)
                                            1 = toggle between words)
11|13: END_SYSEX
// Subsequent transfers to the same device, no need to pass the parameters
// since they are stored on the device. The stored settings would be re-applied.
0:  START_SYSEX
1:  SPI_DATA              (0x68)
2:  SPI_BEGIN_TRANSACTION (0x01)
3:  channel               (bits 2-6: reserved, bits 0-1: channel)
4:  END_SYSEX

Option B

Introduce a new SPI_DEVICE_CONFIG message. The advantage here is the user only needs to pass the SPI config parameters only once per attached device. The disadvantage is increased memory usage. The SPISettings need to be stored for each devices and static allocation should be used, which requires enforcing a limit on the number of attached SPI devices (although I'm doubtful there are many cases where a user would have more than 2 devices attached in a Firmata application).

We keep the deviceId parameter in this case to use as a lookup. There have been past discussions about using the CS pin as a lookup, but I think there are a few advantages of using a separate ID:

  1. Not all SPI devices use a CS pin (although this is a rare case)
  2. The user may want to manage the CS pin manually (and omitting it is a way to express that intent)
  3. The deviceID parameter can enumerate starting at 0 for simplicity in storing the device parameters.
  4. By using separate param for the device ID, if a user passes the optional CS pin param, the firmware can manage the pin setup, where this would be ambiguous otherwise.

This SPI_DEVICE_CONFIG approach may also work better for non-Arduino implementations that may not have a transaction method in the API. It is also possible to skip the use of begin/end transaction if the user would rather manage the CS pin manually and is not concerned with potential SPI interrupt conflicts. (In that case, when using an Arduino-compatible board, the user would need to call SPI_END_TRANSACTION after sending the SPI_DEVICE_CONFIG message to free up the interrupt but preserve the SPI settings).

// Only ever sent once per attached SPI device
0:  START_SYSEX
1:  SPI_DATA              (0x68)
2:  SPI_DEVICE_CONFIG     (0x00)
3:  deviceId | channel    (bits 2-6: deviceId, bits 0-1: channel)
4:  dataMode | bitOrder   (bits 1-2: dataMode (0-3), bit 0: bitOrder)
5:  maxSpeed              (bits 0 - 6)
6:  maxSpeed              (bits 7 - 14)
7:  maxSpeed              (bits 15 - 21)
8:  maxSpeed              (bits 22 - 28)
9:  maxSpeed              (bits 29 - 32)
10: wordSize              (0 = DEFAULT = 8-bits, 1 = 1-bit, 2 = 2 bits, etc)
11: csPin                 [optional] (0-127) The chip select pin number
12: csPinOptions          (required if csPin specified)
                          bit 0: CS_ACTIVE_STATE (0 = Active LOW (default)
                                                  1 = Active HIGH)
                          bit 1: CS_TOGGLE (0 = toggle at end of transfer (default)
                                            1 = toggle between words)
11|13: END_SYSEX

Note: In an non-Arduino implementation maxSpeed could be swapped with clockDivider (although a long is probably a bit much for storing that value).

In this approach we pass the deviceId to the SPI_BEGIN_TRANSACTION message to be used as a lookup in the firmware.

// Sent once for each SPI transaction (unless the user chooses to manage the
// CS pin manually in which case it can be skipped).
0:  START_SYSEX
1:  SPI_DATA              (0x68)
2:  SPI_BEGIN_TRANSACTION (0x01)
3:  deviceId | channel    (bits 2-6: deviceId, bits 0-1: channel)
4:  END_SYSEX

Conclusion

I'm torn between Option A and B. Option A is simple and requires less memory, but Option B is more flexible and may map better to non-Arduino implementations of this protocol.

@soundanalogous
Copy link
Member Author

soundanalogous commented May 13, 2018

Another approach (Option C) that would save memory while also potentially being more friendly with non-Arduino implementations (Raspberry Pi, etc) would be to add the new SPI_DEVICE_CONFIG message but require the user to call it any time they switch to address a separate device. In that case we would not need the deviceId since there is nothing to lookup. It's the same as Option A, but with a different message for CONFIG vs BEGIN_TRANSACTION which could make the API cleaner and more adaptable across architectures (especially when the notion of begin/end transition may not be universal).

@dtex
Copy link
Contributor

dtex commented May 14, 2018

Is this just for configurableFirmata or are you considering it for standardFirmata as well?

@soundanalogous
Copy link
Member Author

Both because it adds board-level functionality.

@dtex
Copy link
Contributor

dtex commented May 14, 2018

Well, you could set a low number of SPI devices allocated to static memory (2-4) for standardFirmata. If someone needs more devices, it's a small tweak that could probably be made by changing a single constant. Plus they could use configurableFirmata to get something with just the parts they need to save space so I'm in the Option B camp.

I took a look at Tessel 2 JS API and the Electric Imp and Particle firmware API's. All the concepts seem to fit well with your proposed protocol so adding support in those that mirror the support in firmata.js shouldn't be a problem. Curiously Tessel doesn't seem to support changing bit order.

@soundanalogous
Copy link
Member Author

Using a user tweakable MAX_SPI_DEVICESconstant is a good idea 👍

@fivdi
Copy link

fivdi commented May 14, 2018

@soundanalogous I'd like to provide some feedback related to how the proposal would map to Linux boards like the Raspberry Pi but I have to ask a question first.

The Raspberry Pi has three SPI controllers. Two of the controllers (SPI0 and SPI1) are broken out onto the GPIO header. Is what's referred to as an SPI controller on the Raspberry Pi referred to a channel in this SPI proposal?

@soundanalogous
Copy link
Member Author

@fivdi That's correct SPI0 = channel 0, SPI1 = channel 1, etc. Some Arduino boards have multiple channels as well but only channel 0 is exposed via the Arduino SPI api.

@fivdi
Copy link

fivdi commented May 15, 2018

Although there's more than one way to access SPI devices on Linux boards like the Raspberry Pi the assumption here is that character special device as described here are used for access.

Comments about the SPI Proposal

SPI_BEGIN

SPI_BEGIN is used to initialize the SPI bus.

On Linux systems userspace programs are typically not involved in the initialization of an SPI bus. An SPI bus is typically initialized during the boot process and userspace programs simply use the SPI bus.

On Linux systems userspace programs access SPI devices using character special device files.

SPI_BEGIN_TRANSACTION

bitOrder
  • Available on Linux
  • The Pi only supports MSBFIRST
dataMode
  • Available on Linux
  • The Pi supports all four modes
maxSpeed
  • Available on Linux
  • Supported on the Pi, default 500kHz
wordSize
  • Available on Linux
  • The Pi only supports 8 bit words
csPin
  • Not know to userspace programs on Linux, handled automatically
  • Linux uses character special device files. /dev/spidevB.D for device number D on bus number B
csPinOptions
  • CS_ACTIVE_STATE is available on Linux
    • The Pi supports Active LOW, I'm not sure about Active High
  • CS_TOGGLE is not available on the Linux

SPI_TRANSFER

Full-duplex write/read transfers are supported using ioctl() requests on Linux.

csPinControl
  • DEFAULT
    • Available on Linux
    • Supported on the Pi
  • CS_DISABLE
    • Not available on Linux
  • CS_START_ONLY
    • The same or a similar effect can be achieved with chipSelectChange
  • CS_END_ONLY
    • The same or a similar effect can be achieved with chipSelectChange

SPI_END

Call once to release SPI hardware send before quitting a Firmata client application.

On Linux systems this can be achieved by closing the character special device files that are used to access SPI devices.

Transactions

This comment above discusses transaction that should generally be framed by begin/end transaction. On an Arduino UNO this will work well as the Firmata sketch running on the UNO has total control over everything and doesn't have to worry about another process/sketch attempting to access SPI devices while it is accessing those devices. On Linux where there are multiple processes this isn't necessarily the case. Multiple processes that are running at the same time may access the same SPI device at the same time. Linux makes such parallel accesses possible by allowing userspace processes to create a buffer containing multiple transfers. This buffer containing multiple transfers can be passed to the SPI driver with an ioctl() request and the driver will ensure that the transfers are performed as a transaction.

These Linux transactions are fundamentally different to the transactions that are being proposed in the SPI proposal.

Whether or not this is actually an issue is a different question. It would be possible to say that the SPI proposal will only function correctly in scenarios where a single process accesses SPI devices.

I haven't said everything that I'd like to say here yet but I'm afraid it's getting late and I should go to bed. I'll return to say the rest asap.

@soundanalogous
Copy link
Member Author

soundanalogous commented May 16, 2018

I'm dropping csPinControl from SPI_TRANSFER (and also from SPI_READ and SPI_WRITE). It's not necessary for Linux or Pi since I assume the user would handle the CS pin manually . SPI_READ and SPI_WRITE are also not required. They are simply added for convenience. In an Arduino implementation they simply use spiTransfer behind the scenes.

I think I can also make beginTransaction and endTransaction optional, even for Arduino-based implementations as long as I use SPI_DEVICE_CONFIG as described in this comment.

@fivdi
Copy link

fivdi commented May 17, 2018

This comment assumes that character special device files as described here are being used to access SPI devices on Linux.

I'm dropping csPinControl from SPI_TRANSFER (and also from SPI_READ and SPI_WRITE). It's not necessary for Linux or Pi since I assume the user would handle the CS pin manually .

Dropping csPinControl would be ok. Linux has a noChipSelect option that can be used to prevent the SPI driver from controlling the CS pin. The user is expected to control the CS pin is such cases.

SPI_READ and SPI_WRITE are also not required. They are simply added for convenience.

Linux also supports SPI_READ and SPI_WRITE for convenience.

I think I can also make beginTransaction and endTransaction optional, even for Arduino-based implementations as long as I use SPI_DEVICE_CONFIG as described in this comment.

I'm not sure I fully understand here. Do you mean (1) optional as in they are not needed and will be removed from the proposal or do you mean (2) a mapping of the SPI proposal to Linux need not implement beginTransaction and endTransaction?

Option A

I don't think the SPI_BEGIN_TRANSACTION described in Option A can be mapped to anything on Linux. If I'm not mistaken SPI_BEGIN_TRANSACTION in Option A expresses the intent to communicate with a currently unknown device on a specific channel. The channel should be configured for communicating with this as of yet unknown device. On Linux it's only possible to set the configuration for a specific device on a specific channel. It's not possible to set the configuration for an unknown device on a specific channel.

With Option A it wouldn't be possible to have two transactions running at the same time each of which is for a different device on the same channel. On Linux this is possible and the SPI driver takes care of everything.

Option B

The SPI_DEVICE_CONFIG described in Option B would map well to Linux. It would mean opening a file and setting the configuration options for communication on that file. For example, to communicate with device 2 on bus 1 it would mean opening the file /dev/spidev1.2. The configuration options can then be set with an ioctl() request.

On the Raspberry Pi there are still the limitations mentioned for SPI_BEGIN_TRANSACTION in this comment. On other boards there will be similar limitations. The limitations will depend on the SPI driver.

There is one remaining issue. The proposal says:

The deviceId is any value between 0 and 31 and is chosen by the user. The value should be unique for each device used in an application.

On Linux there can be device 0 on channel 0 and also device 0 on channel 1. These Linux device numbers are currently not available anywhere. Should they be encoded in deviceId, for example, something like this:

deviceId = 0 -> bus 0 device 0
deviceId = 1 -> bus 0 device 1
deviceId = 2 -> bus 0 device 2
deviceId = 3 -> bus 0 device 3
deviceId = 4 -> bus 1 device 0
deviceId = 5 -> bus 1 device 1
deviceId = 6 -> bus 1 device 2
deviceId = 7 -> bus 1 device 3
...
deviceId = 28 -> bus 7 device 0
deviceId = 29 -> bus 7 device 1
deviceId = 30 -> bus 7 device 2
deviceId = 31 -> bus 7 device 3

To be honest I don't think this encoding is very robust and something better would be needed.

Option C

Option C from this comment is similar to Option A in that it expresses the intent to communicate with a currently unknown device on a specific channel which isn't possible on Linux. It's not necessary to switch to a device to communicate with it on Linux. It's possible to communicate with multiple devices at the same time. For example, a Raspberry Pi has 2 channels, channel 0 and channel 1. There can be 2 devices on channel 0 and 3 devices on channel 1. The corresponding files are:

/dev/spidev0.0
/dev/spidev0.1
/dev/spidev1.0
/dev/spidev1.1
/dev/spidev1.2

It's possible to open all 5 five files and communicate with all 5 devices at the same time in 5 different threads (one device per thread). The SPI driver will keep everything synchronized ensuring that everything functions correctly.

@soundanalogous
Copy link
Member Author

I'm not sure I fully understand here. Do you mean (1) optional as in they are not needed and will be removed from the proposal or do you mean (2) a mapping of the SPI proposal to Linux need not implement beginTransaction and endTransaction?

It would be in the proposal, but not a requirement for implementation. If I go with Option B, it sounds like that is not an issue on Linux.

I'd implement begin/end transition for Arduino-compatible boards (where they are compiled with the Arduino IDE), but even then if I use Option B, an Arduino user would not be required to use begin/end transaction, but they would have to manually toggle the CS pin so I can't see a good reason to skip it, but the flexibility is there. Essentially it maps more to the old pre transaction way of doing things with SPI on Arduino.

Regarding deviceId, I'm still not 100% set on using it. It helps map the active device in Arduino applications, but I could achieve the same effect by incrementing an id value in the firmware and then the user never needs to worry about it. Just not sure yet if that is fool proof.

@soundanalogous
Copy link
Member Author

Actually I think we'll need the deviceId for linux. The deviceID value is 0-32 per channel so you can map up to 32 IDs to each bus. Let me know if you'd need more than 4 busses though, I could increase to 8 which would decrease the deviceID range to 16 but I don't see that being a limitation at least not for Firmata applications.

@soundanalogous
Copy link
Member Author

soundanalogous commented May 18, 2018

Here's an update to the proposal based on recent comments in the above thread: #105. I figured it will be easier to comment on details in context.

@fivdi
Copy link

fivdi commented May 20, 2018

Actually I think we'll need the deviceId for linux.

Yes, this is correct.

Let me know if you'd need more than 4 busses though, I could increase to 8 which would decrease the deviceID range to 16 but I don't see that being a limitation at least not for Firmata applications.

I don't know of an application processor (for example, a Cortex-A processor capable of running Linux) with more than 4 SPI busses. This doesn't mean that none exist. I have seen microcontrollers (for example, Cortex-M microcontrollers not capable of running Linux) with more than 4 busses. The STM32F746ZG which is a Cortex-M7 microcontroller has up to 6 SPI busses.

In addition, Linux also has an SPI driver that uses generic bitbanged GPIO. This means that in addition to hardware controlled SPI busses there can be software controlled SPI busses. Like hardware controlled SPI busses these software controlled SPI busses can be accessed using character special device files /dev/spidev<channel>.<deviceId>.

I'd say it would be better to allow up to 8 busses with up to 16 devices per bus.

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

No branches or pull requests

7 participants