-
Notifications
You must be signed in to change notification settings - Fork 151
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
update SPI proposal #105
update SPI proposal #105
Conversation
|
||
A `SPI_END` command disables the SPI bus for the specified channel. | ||
|
||
|
||
### SPI_BEGIN |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SPI_BEGIN may not be necessary for Arduino, Linux. It could simply be part of the initialization sequence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SPI_BEGIN is not necessary on Linux if character special device files as described here are being used to access SPI devices. Having it as part of the initialization sequence sounds like a good idea as it may make sense on some systems.
@@ -267,13 +252,13 @@ N: END_SYSEX | |||
|
|||
### SPI_END |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While SPI_BEGIN may not be necessary, I'm not sure yet if SPI_END is useful. I could see a need to provide this in order to clean up when closing the user application, but that may also depend on the particular platform this proposal is implemented for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On Linux SPI_END could be used to free resources, for example, to close files. The being said, if the application is about to terminate lots of resources will be freed automatically by Linux.
proposals/spi-proposal.md
Outdated
1 = Active HIGH) | ||
bit 1: CS_TOGGLE (0 = toggle at end of transfer (default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know of any specific SPI devices that needed the CS_TOGGLE option, it was just a suggestion based on an old SPI thread. I'm removing it for now and reserving the additional bits for potential future options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds like a good idea to me.
651a3d9
to
6436ed5
Compare
Assuming that character special device files as described here are being used to access SPI devices on Linux, here is how the commands from the SPI proposal could be mapped to Linux. SPI_BEGIN
SPI_DEVICE_CONFIG
SPI_BEGIN_TRANSACTION
SPI_END_TRANSACTION
SPI_TRANSFER
SPI_WRITE
SPI_READ
SPI_REPLY
SPI_END
|
The default bitOrder is LSBFIRST. Give that the Raspberry Pi only supports MSBFIRST I would assume that most devices are MSBFIRST. This makes me wonder whether the default of LSBFIRST is correct. Would a default of MSBFIRST not be better? |
The proposal for SPI_DEVICE_CONFIG contains the following paragraph:
On Linux the csPin is normally configured automatically at boot time and controlled automatically by the SPI driver. In this case userspace code doesn't have anything to do with the csPin and I wouldn't expect that it be necessary to specify what the csPin is in an SPI_DEVICE_CONFIG command on Linux. However, it's still necessary to specify what the csPinOptions options are on Linux. On the other hand, if the csPin is not specified, the semantics for SPI_DEVICE_CONFIG is that usespace code is responsible for controlling the csPin. In addition, Linux does have a noChipSelect option. If noChipSelect is set, Linux has nothing to do with the csPin. Either there is no csPin or the csPin is controlled by userspace code. At least I think this is how it works but I'm not 100% sure as I have never actually used the Linux noChipSelect option. Something isn't quire right here yet. It's not clear to me how an SPI_DEVICE_CONFIG command can be used to to tell Linux that the csPin either doesn't exist or is controlled by userspace code. If userspace code was forced to specify what the csPin is on Linux then it would work. I'll have to think about this a bit and experiment with the Linux noChipSelect to see if it does what I think it does. One last point related to csPin. csPin is optional so some encoding csPin will be used to specify that there is no csPin. No matter what encoding is used, there may be a platform where that encoding is a valid pin number. |
I could flip the order of csPin and csPinOptions and then make one option "no cs pin control":
I'm not sure if "enabled/disabled" is the right wording yet. It can be clarified in the description. That way specifying a pin number can still be optional even if the active state may be required for some platforms. And if bit 0 is 0 that's a signal for platforms like Arduino that the user will manually control the CS pin state and a csPin does not need to be specified and if one is, it is ignored. |
This was mapping to Arduino. EDIT: I should change it to MSBFIRST though since even the Arduino SPI library documentation says most devices use MSBFIRST. |
I wonder how reliable automatic control is. For example, how does the auto pin handling know what the users intention is in the following typical scenario where multiple commands are sent between the cs pin toggle states:
I assume then for Linux the CS pin is probably automatically toggled once per SPI.transfer so the read_address would be byte 0 in the array and there is only a single call to SPI.transfer and any call to SPI.transfer is framed by a toggle of the CS pin. |
One thing that may be missing from the SPI proposal is an optional delay between when the CS pin is set to the Active state and when SPI.transfer is called. I have seen various delays specified in different device datasheets. Usually they are no more than a couple hundred microseconds. If needed that could be an additional parameter (at least 2 bytes) in SPI_CONFIG_DEVICE. If the value is zero, ignore it (no delay) if > 0 use that delay. Tricky thing then is microseconds vs milliseconds since the microsecond value could be high if a device ever requires a long delay (thus requiring multiple bytes for the delay parameter). |
Good idea. |
Initial experiments with the Linux noChipSelect flag (aka SPI_NO_CS in C code) with the stock Raspbian driver on a Raspberry Pi indicate that the driver isn't particularly interested in this flag and continues to control the chip select pin even if this flag is set to true. |
Looking more into how to perform the following in Linux using ioctl:
I see there is a The other way to handle it (if we are to keep |
Yes, that would resolve the issue. |
If I keep EDIT: |
It's reliable in my opinion. On Linux the pseudo code would look a little different.
On Linux a single ioctl() request can perform multiple transfers as single transaction. Each transfer contains information specifying whether the CS pin should deselect the device before the next transfer (cs_change). It also contains a microsecond delay (delay_usecs). Before the first transfer the device is selected. After the last transfer the device is deselected. If I remember correctly the default max. size of a single transfer is 64 bytes and single ioctl() request can perform multiple transfers as long as the total length of all transfers doesn't exceed 4095 bytes. Up until now my assumption was that you are not really interested in these Linux transactions because they can result in what would be large amounts of RAM being needed on Arduino UNO. My assumption here may be incorrect. Perhaps the readme for this repo describes what's possible on Linux a littel better. |
The big difference between the Arduino transactions and the Linux transaction is that the Arduino transactions allow user code to execute between each transfer. On Linux this is not the case. |
My main concern with Arduino is CS pin handling and finding a way here that it can also well with Linux as well. That's the main hangup I've had with this SPI proposal and why it's taking me years to finalize it (well that time duration is mainly based on lack of feedback in the past). |
So when SPI support is included in an io-plugin for johnny-five I assume a SPI.transfer(params...) interface would be consistent regardless if the user has an Arduino or a Linux-compatible board right? Or is there flexibility in what is passed to the transfer method, such as passing a Message object for Linux and an array for Arduino? This all plays into how I eventually end up controlling the CS pin for Arduino applications since I'm trying to avoid having the user control the pin manually via separate |
You're getting there, I can see light at the end of the tunnel 😄
This is the way it is with I2C. I would imagine that it's a goal for SPI too. That being said, I haven't seen any discussions related to how SPI will be integrated into Johnny-Five.
To the best of my knowledge Johnny-Five doesn't know whether an Arduino or a Linux board is being used. Maybe Johnny-Five can pass an object containing everything that needed for both? |
Looking again at the following pseudo code:
Is it possible to introduce a restrictions like this:
The goal of these restrictions is to prevent the data read by the first transfer from being used to determine what's sent in the second transfer. If these restrictions are possible, Linux transactions could also be used. In other words, all the transfers could be constructed up front before the first one is even performed. For an Arduino the transfers would be performed one by one. On Linux they would be performed by one ioctl() request. |
Arduino has a buffer option for SPI transfers so they don't need to be sent one-by-one, they just can't exceed about 30 bytes when using Firmata with an Arduino over Serial. Here's a prototype I made a while back based on the prior SPI Firmata proposal: https://github.com/firmata/firmata.js/blob/spi-alpha/examples/spiAdxl345.js That demonstrates the level of simplicity I'm hoping to enable on the client side. The readRegister and writeRegister methods. mask the weirdness of the pin control options. That (pin control options) approach also avoids having to send separate |
And this is the Arduino implementation of that prototype: https://github.com/firmata/arduino/blob/spi-alpha/utility/SPIFirmata.cpp |
Would it help if there was a Linux prototype? I've used this io-plugin for prototyping in the past, it could be extended to support SPI. For SPI on Linux this module could be used. If I'm not mistaken implementing a prototype would basically mean implementing the EDIT: The word "equivalent" above should be taken with a grain of salt as we already know that some of the SPI commands from the proposal can't really be mapped to Linux. |
Either a prototype or play through some method signatures. Here is an example with 2 options for what I think could work well, at least when interfacing with an Arduino board. Option A has an advantage of sending fewer Firmata messages over the wire, but Option B has a slightly cleaner API IMHO, but my preference is still for Option A. // updated method signatures (with option param examples) since last commit:
//
// board.spiBegin(channel); // optional
// board.spiEnd(channel);
//
// Board.prototype.spiDeviceConfig({
// channel: 0,
// deviceId: 0,
// bitOrder: board.SPI_BIT_ORDER.MSBFIRST, // default
// dataMode: board.SPI_DATA_MODES.MODE0,
// maxClockSpeed: 2500000,
// wordSize: 8, // default
// csActiveState: SPI_CS_ACTIVE_STATE.LOW, // default (or disabled = default?)
// csPin: 4, // optional
// });
//
// board.spiBeginTransaction(channel, deviceId); // only for Option B below, no-op for Linux
// board.spiEndTransaction(channel, deviceId); // only for Option B below, no-op for Linux
//
// board.spiWrite(inBytes, {
// channel: 0,
// deviceId: 0,
// csPinEnabled: false, // default (only for Option A below)
// });
//
// board.spiRead(numBytesToRead, {
// channel: 0,
// deviceId: 0,
// csPinEnabled: false, // default (only for Option A below)
// }, callback);
//
// board.spiTransfer(inBytes, {
// channel: 0,
// deviceId: 0,
// csPinEnabled: false, // default (only for Option A below)
// }, callback);
/**********************************************************************/
// Read register Option A, implicit transactions (begin/end transaction would be
// handled in the Arduino code based on csPinEnabled state).
// 2 Firmata messages (no begin/end transaction needed with this option)
this.spiWrite(register, {
channel: 0,
deviceId: 0,
csPinEnabled: false, // default (don't toggle CS pin)
});
this.spiRead(numBytesToRead, {
channel: 0,
deviceId: 0,
csPinEnabled: true, // toggle CS pin
}, callback);
// Option A as a spi transfer instead of separate write/read
// write to a register (8 bit) and read 6 bytes
// 1 Firmata message (if total message < 64 bytes), but user needs to parse
// data out of return (throw away first returned byte)
// set csPinEnabled: false to link multiple spiTransfer messages as a single transaction
this.spiTransfer(
[register, 0, 0, 0, 0, 0, 0],
{
channel: 0,
deviceId: 0,
csPinEnabled: true, // toggle CS pin at end of transfer
},
callback
);
/**********************************************************************/
// Read register Option B, explicit transactions (Arduino)
// 4 Firmata messages (CS pin set to active state in begin, toggled in end)
// write and read are simpler, but at the expense of transmitting
// 2 additional Firmata messages
this.spiBeginTransaction(channel, deviceId); // no-op for Linux
this.spiWrite(register, {channel: 0, deviceId: 0});
this.spiRead(numBytesToRead, {channel: 0, deviceId: 0}, callback);
this.spiEndTransaction(channel, deviceId); // no-op for Linux
// Option B as a spi transfer
// 3 Firmata messages (CS pin set to active state in begin, toggled in end)
this.spiBeginTransaction(channel, deviceId); // no-op for Linux
this.spiTransfer(
[register, 0, 0, 0, 0, 0, 0],
{
channel: 0,
deviceId: 0
},
callback
);
this.spiEndTransaction(channel, deviceId); // no-op for Linux |
A initial commit for a Linux SPI prototype can be seen here. It's minimalistic and still needs work but it functions correctly. It should be relatively easy to add the missing pieces later.
Observations/Questions (1) In the API proposed here sometimes channel and deviceId appear to be optional but sometimes they appear to be required. For example, it looks like they are optional for spiTransfer but required for spiBeginTransaction. Should the same options not always be optional? (2) Why is continuous read needed for I2C but not needed for SPI? This isn't actually an issue for Linux, I'm just curious as to why it's not needed for SPI. (3) Controlling the CS pin manually from user code on Linux will be quite difficult. If we assume that methods like spiRead, spiWrite and spiTransfer are async then it's going to be difficult to know when to change the state of the CS pin from user code. For example, if an async spiWrite method is called when exactly should the state of the CS pin be changed by user code? There may already be several previous calls to async methods that have not completed yet that are queued waiting to be run (the Linux SPI driver queues them.) Consider the following code: board.spiWrite(inBytes1);
board.spiWrite(inBytes2); Lets say that user code should set the CS pin low before spiWrite and high after spiWrite. The code could be modified to this: board.digitalWrite(CS_PIN, 0);
board.spiWrite(inBytes1);
board.digitalWrite(CS_PIN, 1);
board.digitalWrite(CS_PIN, 0);
board.spiWrite(inBytes2);
board.digitalWrite(CS_PIN, 1); However, this will not work. digitalWrite is typically a synchronous method on Linux. In Pi-IO digitalWrite is non-blocking and each call takes about 500 nanoseconds to execute. The four calls to digitalWrite above may have completed before the first byte of data from the first async spiWrite has been written. In addition, the async spiWrite method doesn't have a completion callback that could be used to help synchronize things. To be honest, I don't know if it will be necessary to control the CS pin from user code on Linux so I don't know if this will be an issue. (4) The events emitted when spiTransfer and spiRead have completed are named with the pattern "spi-data-deviceid-channel". For device 0 on channel 0 the event name is always "spi-data-0-0". Lets say the user runs the following code: board.io.on('spi-data-0-0', function (reply) {
// reply is an array of bytes but to which spiTransfer does it correspond?
});
// The WHO_AM_I register contains the device id which should be 0x33
var readAddress = register.WHO_AM_I | READ_BIT;
board.io.spiTransfer([readAddress, 0], transferOpts, function(data) {
console.log('Device ID: 0x' + data[1].toString(16));
});
// Data rate 50Hz, normal mode, X, Y and Z axis enabled
board.io.spiTransfer([register.CTRL_REG1, 0x47], transferOpts);
// Block data update enabled, +/- 2G, high resolution enabled
board.io.spiTransfer([register.CTRL_REG4, 0x88], transferOpts); If the event name is always "spi-data-0-0" and the reply is simply an array of bytes how can the user know which spiTransfer or spiRead the reply corresponds to? Note that the event names for I2C in similar scenarios include the register number.
However, with SPI including a register number will not be enough. Some SPI devices don't even have registers as such, for example the MCP3008 analog to digital converter. (5) I think there's a bug in the following firmata code from spiTransfer:
If spiTransfer is called with a callback twice in quick succession then two handlers will be registered. When the first spiTransfer completes, both handlers will be run. The handler for the second call to spiTransfer will be run when the first call completes rather than when the second call completes. (6) I attempted to implement an example using an ADXL345 similar to this one but I couldn't manage to get the ADXL345 to work on a Raspberry Pi 2. |
Sloppy work on my part. They should always be optional.
Read continuous was added as a convenience for I2C many years ago. I just hadn't considered also adding it for SPI yet, but I could if there is a good case for it.
One of the main things I'm getting at, but maybe I'm not making myself clear is that I want to eliminate manual control of the CS pin since it requires sending too many Firmata messages in quick succession. That's why I had proposed the adding the
I could add a callback, but
I guess I had never run into that issue in the Arduino implementation. I could see it becoming a problem if there was an error though even in an Arduino implementation. One way to handle this is adding one more param which is an ID for each call to spiTransfer that can be used match the reply correctly. Maybe the ID is 7 bits and rolls over at 128 and it could be incremented automatically during each call spiTransfer so the user would never need to deal with it directly.
Possibly. I wasn't sure if
I don't even have an ADXL345 to test with. I just wrote that code as an example since it's a popular sensor, but I only had an old LIS3LV02DQ accelerometer which I'm pretty sure not many people have. |
There is one thing about the Linux implementation that make me nervous. From what you are saying, it sounds like there is no way to guaranteed the order in which bytes are written to the spi device when sending two or more spiTransfer messages in quick succession. Seems this could be a big issue if a particular spi device expects commands in a specific order. It it possible to use a synchronous write for spiTransfer, spiRead and spiWrite to guarantee data is written to the spi device in the proper order? I guess this is potentially a JavaScript issue and not just a Linux issue, but I haven't run into it specifically when using firmata.js with an Arduino. |
Here's another SPI use case we should make sure the proposal covers. There are camera modules where you clock out an image which can be hundreds or thousands of bytes. Here's what that looks like in an Arduino sketch: myCAM.CS_LOW();
myCAM.set_fifo_burst();
response = "--frame\r\n";
response += "Content-Type: image/jpeg\r\n\r\n";
server.sendContent(response);
static const size_t bufferSize = 1024; // 4096
static uint8_t buffer[bufferSize] = {0xFF};
while (len) {
size_t will_copy = (len < bufferSize) ? len : bufferSize;
SPI.transferBytes(&buffer[0], &buffer[0], will_copy);
if (!client.connected()) break;
client.write(&buffer[0], will_copy);
len -= will_copy;
}
myCAM.CS_HIGH(); Notice how CS is pulled LOW at the beginning, then there are a bunch of SPI transfers in a loop and once the loop exits CS is pulled HIGH. What is important here is the entire series of SPI transfers needs to be bound by a single LOW to HIGH toggle of the CS pin. If trying something like this using Firmata, the buffer would be much smaller than 1024 bytes. |
Some of the firmata.js I2C methods return this and some don't. For example, i2cRead and i2cReadOnce do but sendI2CReadRequest and sendI2CWriteRequest don't. That being said, sendI2CReadRequest and sendI2CWriteRequest are deprecated.
@dtex Here you say that the
@soundanalogous I agree with what you say here. I, personally, would also use the completion callback and not the events. On the other hand, what you say here appears to contradict what @dtex says about the importance of these events for firmata.js dependents other than Johnny-Five. To cut a long story short, if the Also, please don't forget that the IO-Plugins specification has made events like |
I was simply musing on why the i2c-reply events might exist. Feature parity with firmata.js is the important thing, not these events. I should have separated those two thoughts more clearly. It sounds like we agree that the transferId's do not need to be public for SPI because there is another way. To maintain the pattern established by i2c we could keep I'm sorry if it sounds like I'm politicking for |
The patterns established for I2C are Why do you think that keeping
I'm having difficulties coming up with a concrete example of how the purpose of the data can be derived from the data itself. Do you have a concrete example for some SPI device?
|
For arguments sake lets say the images from the camera module are 10000 bytes. I'm not familiar with the camera module but think that there are two ways this could be handled well on Linux. Alternative OneThe default max SPI message size on Linux is typically 4096 bytes. On a Raspberry Pi the max SPI message size can be seen in
A 4096 byte message is too small for a 10000 byte image so the max SPI message size can be increased from 4096 to 10000 by adding Alternative TwoOn Linux an SPI message is actually one or more SPI transfers. This means that it's possible to send a message with multiple SPI transfers from a user space application to the SPI driver with one call and the SPI driver will take care of all the details and handle everything as one transaction. Here's an example of how this can be achieved with the spi-device module. The example sends a single message with three transfers to the SPI driver. 4096 + 4096 + 1808 = 10000. const spi = require('spi-device');
const camera = spi.open(0, 1, {mode: spi.MODE0, maxSpeedHz: 500000}, function (err) {
if (err) throw err;
const message = [
{receiveBuffer: Buffer.alloc(4096), byteLength: 4096, chipSelectChange: false},
{receiveBuffer: Buffer.alloc(4096), byteLength: 4096, chipSelectChange: false},
{receiveBuffer: Buffer.alloc(1808), byteLength: 1808, chipSelectChange: false}
];
camera.transfer(message, function (err, message) {
if (err) throw err;
// The image data is now available in
// message[0].receiveBuffer - the first 4096 bytes of the image
// message[1].receiveBuffer - the next 4096 bytes of the image
// message[2].receiveBuffer - the last 1808 bytes of the image
});
}); Note how the I actually think it would be good if firmata.js and Johnny-Five IO-Plugins supported messages with multiple transfers. The following line of C code from the Arduino example was ignored as I'm not sure what the implementation of myCAM.set_fifo_burst(); It will also be necessary for an IO-Plugin to tell Johnny-Five the maximum size of an SPI message. |
Thinking more realistically about the camera module use case, for firmata.js to support messages with multiple transfers may actually be impractical (if not impossible) for the following reason: The (Serial) buffer size is limited to 64 bytes which is only 29 bytes of actual data (64 byte serial buffer - 6 non data bytes per message) / 2. So a 10k image would require 345 SPI_TRANSFER Firmata messages (where "Firmata message" is different from a SPI message) and the user would have to reassemble all 345 SPI_REPLY Firmata messages back together. Probably not a good use case for Firmata over Serial after all. So a Linux-based board where the application is running entirely on the actual board HW has a huge advantage over an Arduino Firmata application there. Obviously no problem to pull an image purely from an Arduino sketch, but the RPC nature of Firmata over Serial has serious limitations for sure. |
@soundanalogous @dtex we started this conversation about SPI on Linux boards like the Raspberry Pi here. I think I've provided all the feedback I can for the moment or do you see the need for additional feedback? |
I think you have described the challenge and the reasoning for the transferId return value and the "fully qualified" event names really well. I went quiet because I finally "got it" and don't have a good alternative. The only argument I have is "It feels wrong to introduce this new return value pattern", which is not a very helpful argument. |
I don't think we need the return value. I agree that doesn't seem quite right. What we could do is make the transferId an optional parameter of the read, write and transfer methods. That way if the developer/user wants to know the value, they are responsible for creating it and passing it to the method. If a developer/user doesn't care then it's handled automatically internally. The developer/user just has to ensure that the value does not exceed 127, but we can also guard against that within the method. |
I think we've made great progress here. Thanks a ton for your input so far! I'll update the proposal per the comments in these 2 threads and then ping you two when I've made the changes so we can ensure we're all on the same page. I may not get to it for a few days. |
In firmata.js we could let the user pass in any string or number as an id, push that onto a |
That string would need to be unique per each message for a small sequence. I'd prefer a number but for non Arduino platforms we don't need to limit it to the 0-127 range. |
spi-proposal-no-transaction.md
Outdated
@@ -0,0 +1,256 @@ | |||
SPI (Proposal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This proposal does not include SPI_BEGIN_TRANSACTION
and SPI_END_TRANSACTION
. Instead it adds a csPinChange
paramter to the TRANSFER, READ and WRITE commands. If SPI transactions are supported for a given platform such as Arduino the appropriate functions would be used in the implementation behind the scenes. The value of the csPinChange
parameter would be an indication of when endTransaction
would be called in the firmware. beginTransaction
would be called when a TRANSFER, READ or WRITE command is received AND endTransaction had been previously called for the same deviceId
OR it was the first TRANSFER, READ or WRITE command received for that device. While this masks a lot of complexity from the user, there is a risk of begin/end transaction getting out of sync.
spi-proposal-no-transaction.md
Outdated
2: SPI_TRANSFER (0x02) | ||
3: deviceId | channel (bits 3-6: deviceId, bits 0-2: channel) | ||
4: requestId (0-127) // increment for each call | ||
5: csPinChange (0 = don't deselect csPin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is borrowed from the Linux SPI api and has the same behavior, but applied to Arduino and other platforms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be tempted to pack this bit into the deviceId/channel byte, but that would lose 4 channels or 8 device Ids.
proposals/spi-proposal.md
Outdated
|
||
|
||
### SPI_BEGIN_TRANSACTION |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Arduino-compatible implementations the CS pin would be automatically handled in the begin/end transaction commands. However the user would still need to send the begin transaction message, then the transfer, read or write message(s) then the end transaction message. Some of this added complexity can be masked with writeRegister, readRegister helper functions on the client side but there are times when the user would need to understand how to properly use the transaction framing messages.
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) | ||
11: csPinOptions bit 0: CS_PIN_CONTROL (0 = disable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these 2 states (disable | enable) sufficient or do we need additional states? For example in the Linux case would disable
here mean ignore the csPin parameter and would enable
always require the user to specify a valid csPin number. That later case is where I think there may be a need for an additional state such as:
- 0: disable // user will control pin manually
- 1: enable-specify-pin // auto control of pin and pin number required
- 2: enable-no-pin // auto control of pin but pin number not required
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If character special device files as described here are used to access SPI devices on Linux the csPin parameter is of no relevance. The SPI driver know automatically which pin to used based on the file being accessed (/dev/spidev<channel>.<deviceId>
). This implies that the CS_PIN_CONTROL option is also of no relevance on Linux.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sound like a binary enable/disable value for csPinOptions would be sufficient then and Linux implementations would simply ignore csPin
and csPinOptions
.
I've pushed an update based on the comments in this and the other related thread. Changes I've made:
I also added an alternate proposal named spi-proplsal-no-transaction.md since I'm still a bit torn on whether or not the explicit |
On Linux the cs_change option is only used when an SPI message contains multiple transfers. Lets say a message contains N transfers. Before the 1st transfer the csPin will always select the device and after the Nth transfer the csPin will always deselect the device. The csPin will deselect the device after the Nth transfer irrespective of whether the cs_change option for the Nth transfer is true or false. The cs_change option will only be taken into account for transfers 1 through N-1. This means that the cs_change options as described above will not be of usage on Linux. |
Ideally a johnny-five (or similar library that supports both Linux and Arduino-based boards) user could write a single application that would run on either a Pi (or other Linux board) or an Arduino without modification. I'm not sure how (or if) this can be accomplished when Linux requires a "message" but Arduino does not. Perhaps it's not possible to frame a message in a way that works interchangeable at the API level. |
Revisiting this statement, firmata.js and johnny-five (including io-plugins) could expose separate const message = [
{receiveBuffer: Buffer.alloc(4096), byteLength: 4096, chipSelectChange: false},
{receiveBuffer: Buffer.alloc(4096), byteLength: 4096, chipSelectChange: false},
{receiveBuffer: Buffer.alloc(1808), byteLength: 1808, chipSelectChange: false}
];
// add a new spiMessage method to handle multiple transfers
board.spiMessage(message, function (err, message) {
if (err) throw err;
// The image data is now available in
// message[0].receiveBuffer - the first 4096 bytes of the image
// message[1].receiveBuffer - the next 4096 bytes of the image
// message[2].receiveBuffer - the last 1808 bytes of the image
});
}); For Linux applications it would work as above, for Arduino applications, |
Pushed another rev of the proposal: https://github.com/firmata/protocol/blob/6c448fa8377894ae7ecab3d1e18111cad9a67703/proposals/spi-proposal.md Removed |
proposals/spi-proposal.md
Outdated
|
||
The CS pin behavior can be further controlled by using the `csPinControl` byte of the | ||
`SPI_TRANSFER`, `SPI_WRITE` or `SPI_READ` messages. | ||
Use `csPinOptions` to set the active state (typically LOW) for the CS pin. If |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fivdi Please let me know if this description makes more sense now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the description make sense.
Maybe it would be a little clearer if the first sentence was changed from:
Use csPinOptions
to set the active state (typically LOW) for the CS pin.
to:
Use CS_ACTIVE_STATE
to set the active state (typically LOW) for the CS pin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good.
proposals/spi-proposal.md
Outdated
2: SPI_TRANSFER (0x02) | ||
3: deviceId | channel (bits 3-6: deviceId, bits 0-2: channel) | ||
4: requestId (0-127) // increment for each call | ||
5: csPinChange (0 = don't deselect csPin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
csPinChange
could be ignored for Linux-based boards, but it's necessary for Arduino-compatible and perhaps other non-Linux-based platforms
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also love to stuff this bit somewhere rather than allocating an entire byte for it. I could stuff it in requestId
, but that would limit the requestId
range to 0-63. I could stuff it in byte 3 but that would require limiting the deviceId
range to 8 values or the channel
range to 4 values (keeping device Id as 16).
However, by keeping it as byte 5 (as above) the other bits in byte 5 can be reserved in case additional functionality is needed in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To confirm confirm that we're on the same page here, csPinChange
will be ignored for Linux-based boards if character special device files as described here are used to access SPI devices. However, if firmata.js and johnny-five (including io-plugins) exposed separate spiTransfer and spiMessage methods as described here where spiTransfer is a single transfer and spiMessage is for multi-transfer then csPinChange
could be used as intended for multi-transfers. Is this correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is tricky. There are 2 different concepts of csPinChange, one that's part of Firmata and has to be passed over the wire, and another than's specific to Linux. They do the same thing, but one has to go over a wire and one doesn't.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think at the firmata.js / johnny-five layer the difference doesn't matter. The underlying implementations would be very different though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On Linux, I guess csPinChange would be a no-op for spiTransfer. That may be difficult to communicate as it may not align with a user's expectations. I'm not sure of a better way to handle it since I think having that functionality in spiTransfer simplifies things for Arduino or other transport-based Firmata implementations.
|
||
Signals the end of a transaction. Must be sent before starting communication | ||
with another SPI device. | ||
Since transport (Serial, Ethernet, Wi-Fi, etc) buffers tend to be small on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may end up needing a SPI_BEGIN_MESSAGE
, SPI_END_MESSAGE
pair of commands to ensure proper framing of SPI messages for Arduino-based implementation. However I'm going to see if I can get away without it at first. Depends on the outcome of the Arduino implementation of this proposal.
proposals/spi-proposal.md
Outdated
|
||
Provided as a convenience. The same can be accomplished using `SPI_TRANSFRER` and ignoring | ||
the `SPI_REPLY` message. | ||
A `SPI_WRITE` command should return a `SPI_REPLY` with a value of `1` if the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes me wonder if we need a way to signal an error for SPI_READ
and SPI_TRANSFER
commands as well. One approach is to return a single value 0
if there is an error. However that does not work if the read or transfer request only a single byte/word.
The latest version of the proposal looks good to me. |
15441ed
to
505e532
Compare
Going to merge this and update the prototype Arduino and firmata.js implementations. |
Opening for comment. Updates to the SPI proposal per discussion in #26.