Suggested slight change to SPI library.

220 views
Skip to first unread message

Zachary Carey

unread,
May 27, 2018, 7:16:03 AM5/27/18
to Developers
Add an additional overload when writing to SPI with a buffer, SPI.transfer(buffer, len, recv), where recv is a boolean, when true will replace the buffer with the incoming, however when false is leaves the buffer alone. I ran into a issue with my current project where I needed the buffer to remain unchanged, and it took me awhile to realize that is was the SPI library changing the data.

Söù Må Yã

unread,
May 27, 2018, 7:58:55 AM5/27/18
to devel...@arduino.cc
Thank you so much but i dont  indrestand can you give me exemple of code source

Le dim. 27 mai 2018 12:16, Zachary Carey <zca...@ltu.edu> a écrit :
Add an additional overload when writing to SPI with a buffer, SPI.transfer(buffer, len, recv), where recv is a boolean, when true will replace the buffer with the incoming, however when false is leaves the buffer alone. I ran into a issue with my current project where I needed the buffer to remain unchanged, and it took me awhile to realize that is was the SPI library changing the data.

--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

Kurt Eckhardt

unread,
May 27, 2018, 4:16:21 PM5/27/18
to devel...@arduino.cc

I know that with at least a few different platforms,  there is a version of the SPI.transfer, that allows you to specify different buffers for the send and receive buffers:  Example Teensy boards:

                void setTransferWriteFill(uint8_t ch ) {_transferWriteFill = ch;}

                void transfer(const void * buf, void * retbuf, size_t count);

Note: Transfer Fill is used if NULL ptr for buf.

 

ESP8266 has:

  void transferBytes(uint8_t * out, uint8_t * in, uint32_t size);

 

Likewise for ESP32:

    void transferBytes(uint8_t * data, uint8_t * out, uint32_t size);

    void transferBits(uint32_t data, uint32_t * out, uint8_t bits);

 

It would be great if we could somehow standardize on some of these extended functions.

--

Owen Lyke

unread,
Jul 10, 2018, 5:25:55 PM7/10/18
to Developers
Hello all,

I just joined the group because of this problem. Several times now when writing control code that uses hardware SPI peripherals I was bitten by the fact that SPI.transfer() overwrites my output buffer. 

I agree that we should standardize something to address this. Personally I am looking for two features:

- transmit from a buffer while preserving its contents (discarding received SPI data)
- read into a buffer and be confident that the MOSI line is in a known state (of course this CAN be achieved with the normal transfer() method but it would require pre-definition of a full array)

For the second method I like to specify a byte that will be sent each time, this way you can choose all low (0x00), all high (0xFF), or something else in case of special requirements.


I suggest:

-transfer(void * buf, size_t size) retains the same usage
-transferOut(void * bufOut, size_t size) is added to send data and preserve the buffer contents
-transferIn(void * bufIn, size_t size, uint8_t outByte) is used to receive data and send 'outByte' repeatedly on the MOSI lines


I'd really like to see this become a standardized capability in Arduino, and happy to help in discussion or implementation.

Thanks,
Owen

Andrew Kroll

unread,
Jul 10, 2018, 10:26:19 PM7/10/18
to Arduino Developers
Excellent idea. I constantly have to implement my own method call for the overwrite issue too.
Perhaps you can just extend the class too, and give it a new library name, since not all platforms are going to jump on this immediately. Just need a name for it, perhaps "SuperSPI"?

Owen Lyke

unread,
Jul 11, 2018, 3:09:06 AM7/11/18
to Developers
I like the idea to extend the class, it seems like the right short-term solution. I was thinking about how you might go about doing this and ultimately found these questions:

1 - To extend or to replace entirely?
2 - How to organize the extended class for different devices?
3 - How to be compatible with devices that have multiple hardware SPI peripherals?
4 - Can we start an organized project and build it up as needed until it covers enough devices to become standard?

I'm glad there's some interest in this topic! Who wants to collaborate on a solution? I could really use some advice for how to make this ultimately fit into the standard Arduino experience. Let's keep up the discussion!

And for people with spare time here are my more detailed thoughts on the above topics. Be warned, I'm thinking out loud here

1 - This question mostly comes from my lack of understanding of how Arduino gets developed. If ultimately we wanted these features to go to everyone is it easier to merge an inherited class or a copy/pasted original class with the needed functions added? I think that even in the inherited option the functions would need to be written from scratch and be specific to a given architecture, so what's the advantage of using inheritance? Earlier today I copy/pasted the avr SPI library and added my own functions (sounds like what you've done, more or less) and it worked well - just couldn't use it in a library that I want to have work for everyone.

2 - As far as planning ahead goes how does it sound to make a whole library (header + cpp) for each HW peripheral on a given architecture. Seems to me that this would make it easier to move it into the existing cores later. Then you're left with something that can't easily be included in other libraries, so how about a 'master organizer' that includes the right specific libraries based on your architecture at compile time? The master could be the sole include needed in other libraries and that still seems fairly accessible to others. (Drawback: having a lot of different libraries on your computer) Hmm... One problem: it would require work to make any libraries that were built on that system compatible with the cores once they are replaced. This is mostly because we will have a different name than 'SPIClass' but all the same functions (and then some)

3 - This is mostly covered in (2)

4 - Where's the right place to start this effort (GitHub seems right...)? Who wants to be responsible? (I volunteer, if I'm not stepping on toes) What are the guidelines?
+ Preliminary guidelines:
+ + The goal is to make the discussed features available easily so that they can be used in development of other libraries
+ + The body of work can be developed as-needed for a given architecture
+ + Existing Arduino compatibility shall be maintained
+ + Uniform naming across device specific implementations
+ + There should be an easy way to use this support in libraries right now


What am I overlooking? Is there another way to extend the existing SPI libraries that would work better? What would be the best way to make it so that libraries that use this extension won't be totally derailed if/when out-of-the-box support becomes a reality?

Thanks for your time and input!

Owen Lyke

unread,
Jul 11, 2018, 3:10:26 AM7/11/18
to Developers
Did I mention that 'SuperSPI' is a great clever name? Thinking 007 over here

Kurt Eckhardt

unread,
Jul 11, 2018, 9:10:36 AM7/11/18
to devel...@arduino.cc

As my earlier message/email is mentioned I thought I would  reiterate that I added some of this to the Teensy versions of the SPI library and now recently to the Robotis OpenCR hardware as well.

 

The new functions were added to these libraries (SPI) using the same methodology as was earlier used to add transactions to the SPI library.

That is in SPI.h there is:

// SPI_HAS_TRANSACTION means SPI has beginTransaction(), endTransaction(),

// usingInterrupt(), and SPISetting(clock, bitOrder, dataMode)

#define SPI_HAS_TRANSACTION 1

And the transaction stuff is in the library.   Those programs/libraries that wish to use transactions (hopefully most all of them by now) can/should have code like:

                #if defined(SPI_HAS_TRANSACTION)

                                < do stuff for transactions>

                #else …

 

So in the Teensy SPI library code, we have:

// SPI_HAS_TRANSFER_BUF - is defined to signify that this library supports

// a version of transfer which allows you to pass in both TX and RX buffer

// pointers, either of which could be NULL

#define SPI_HAS_TRANSFER_BUF 1

That libraries/programs can check against before they compile in code that requires (or prefer to use) the new version of the API

 

Again a new API was defined:

 

void transfer(const void * buf, void * retbuf, size_t count);

 

Yes you could define new ones for Read only operation or write only operations if you like, but can do the same with above

Simply passing in NULL for either or both (both would simply send your fill character)

 

If you want  versions that are specifically defined as only write or only read, than you can easily also include those.  For example some other platforms

Had a writeBuf function defined, which would be something like:

                Inline void writeBuf(const void *buf, size_t count) {transfer(buf, NULL, count);}

 

Secondary addition on Teensy (and limited on openCR) an asynchronous (DMA) version was added  which code could look for:

#define SPI_HAS_TRANSFER_ASYNC 1

 

But this uses some of the Teensy  EventResponder code, which may be beyond this thread…   But there is a form of the transfer method:

                bool transfer(const void *txBuffer, void *rxBuffer, size_t count,  EventResponderRef  event_responder);

where the transfer will start up using DMA and return and when it completes an event is set using the event_responder, which can call of to

a user function.  When the call happens depends on how you have the event_responder setup (now, some timer, idle, …)

Mikael Patel

unread,
Jul 11, 2018, 9:38:52 AM7/11/18
to Developers, zca...@ltu.edu
I did some work last year in trying to abstract the Arduino core functions. One area was the SPI interface. Below is a link to the main header file. It contains much of what is suggested but also support for multi-tasking; https://github.com/mikaelpatel/Arduino-SPI/blob/master/src/SPI.h

Cheers!

Owen Lyke

unread,
Jul 11, 2018, 11:00:56 AM7/11/18
to Developers, zca...@ltu.edu
Kurt and Mikael, thank you for your help. I will take a look at your work and see what I can learn from it.

Andrew Kroll

unread,
Jul 11, 2018, 12:31:39 PM7/11/18
to Arduino Developers
The transaction parts are all that should be required for multitasking... That way you don't have to poll for busy, just yield() in the transaction start until the previous transaction completes. Just my 2¢.

--

Thomas Roell

unread,
Jul 11, 2018, 12:47:56 PM7/11/18
to Arduino Developers
We have been playing for a while now with async SPI (and I2C).

bool transfer(const void *txBuffer, void *rxBuffer, size_t count, void(*callback)(void));

One of the interesting things in real life was that you'd want a ::busy() operation and allow a NULL callback. A simple case as to why was a TFT display. You kick off one transfer buffer, while you fill up the next. Periodically you check whether the previous had been done. If not you keep filling up, otherwise you submit the partial new buffer. In any case a simple volatile bool flag and a callback would have done the same, but having this SPI.busy() made the code way easier to read.


While being at it, we got 2 interesting requests from customers. One was to allow a SPI.cancel() operation, i.e. aborting a async transfer midway. The other way that SPI.busy() should return the number of bytes still left to transfer, hence allowing to know how many had been processed, and thus being able to reuse the transfer buffer, or start early on paring the receive buffer.

- Thomas


To unsubscribe from this group and stop receiving emails from it, send an email to developers+unsubscribe@arduino.cc.

--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+unsubscribe@arduino.cc.

Kurt Eckhardt

unread,
Jul 11, 2018, 4:37:23 PM7/11/18
to devel...@arduino.cc

I can see/use all of the above.  My first version was the same and had a null callback function, but then with teensy we started experimenting with the ability for different types of callbacks.  That is do you want your code to be called on the DMA completion interrupt, or do you want that to then call it off of a timer (in our case millisecond timer), or do you only want it called during idle and maybe you don’t want it not to call anything but you can ask the object if it completed or not…

 

 

And I do have display code (ili9341 as well and SSD1306) that does this.  Note the ILI9341 code is not using the SPI code but is doing the DMA directly.

 

I can see uses for the functions busy function as you mentioned. Both the simple version as well as some indication of how much is done or left to do. 

 

I do something similar in some of display code (Teensy 3.5/6 ili9341_t3n library), where I can ask for the screen to update using DMA, and I can ask if the update has completed or not.  I then have some other user code, that will decide if it is safe to update the image in the frame buffer or not…

 

But for me, It would be great if we could at least take some baby steps and standardize on a (or set of) update functions that don’t overwrite the buffer.   Then maybe we would have some luck with then getting library owners to take in Pull requests that often significantly speeds them up and those speed ups work for multiple platforms.  At the same time would be great if we could also have more libraries where you can somehow specify which SPI object is to be used and not always use SPI.  As there are now more and more of these platforms who support multiple SPI (and Wire) objects.

To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

--
You received this message because you are subscribed to the Google Groups "Developers" group.

To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

 

--
You received this message because you are subscribed to the Google Groups "Developers" group.

To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

Reply all
Reply to author
Forward
0 new messages