Arduino SPI Library Gains Transaction Support

Transaction SPI Timing
Transaction SPI Timing

To prevent data corruption when using multiple SPI devices on the same bus, care must be taken to ensure that they are only accessed from within the main loop, or from the interrupt routine, never both. Data corruption can happen when one device is chip selected in the main loop, and then during that transfer an interrupt occurs, chip selecting another device. The original device now gets incorrect data.

For the last several weeks, [Paul] has been working on a new Arduino SPI library, to solve these types of conflicts. In the above scenario, the new library will generate a blocking SPI transaction, thus allowing the first main loop SPI transfer to complete, before attempting the second transfer. This is illustrated in the picture above, the blue trace rising edge is when the interrupt occurred, during the green trace chip select. The best part, it only affects SPI, your other interrupts will still happen on time. No servo jitter!

This is just one of the new library features, check out the link above for the rest. [Paul] sums it up best: “protects your SPI access from other interrupt-based libraries, and guarantees correct setting while you use the SPI bus”.

32 thoughts on “Arduino SPI Library Gains Transaction Support

        1. Many reasons, but like all open source, mainly because nobody contributed a fix until now.

          Historically, very few SPI-based libraries have used interrupts. Recently, wireless communication libraries like CC3000 (wifi), nRF8001 (bluetooth low energy), RFM12/22/69 have changed that situation.

          Something to keep in mind is the narrow window of opportunity for these interrupts to strike at just the wrong moment. The SPI bus is fast and AVR chips execute code quite slowly. Arduino Due is faster, but most of its code has much more overhead. Many projects use only a single SPI device, or if they have 2 or more, often the code is written in a simplistic do-one-thing-at-a-time approach (or simplified to such a way when users hit mysterious unsolvable problems). The net result is rare and elusive problems. Even the test cases I and others wrote take many tries before they’ll reproduce data corruption and lockups.

          As you can see in some of the comments here, and in the lengthy discussion about this issue on the Arduino Developers Mailing List in April-May, many people disagreed about this problem or didn’t even understand it back them. Now, with a well tested solution, oscilloscope screenshots and 2+ months of experience working on the problem, it’s much clearer. If you look back to that conversation in April and older threads on my own forum and even some buried on Arduino’s forum, there was little understanding of the problem. What little knowledge existed before January was drown in the noise of other issues, like users connecting wires incorrectly, lack of pullup resistors on chip select pins to keep unconfigured devices inactive (still a big problem), some chips that don’t properly tri-state their MISO signal, “simple” bugs in libraries, and lots of other issues.

          I’ve personally spent a LOT of hours on the SPI library over the last 2 months. In fact, it’s set back my own development schedule for new Teensy products. The “Arduino market” is becoming far more competitive, which is great for users because ever more capable hardware is coming at ever more attractive pricing, but it does make investing engineering time into the larger platform ever more challenging.

    1. Make a SPI device that queues up the requests in an OS. Synch/async I/O call to those device. etc. This let you write much cleaner and reusable code. People that do that already not on Arduino frameworks.

      It is kinda like the x86 world (at least before x64 etc). Ugly architecture, but tons of software.

      1. Do you seriously believe a fully preemptive scheduler, necessitating per-thread stacks, managing arbitrary length request queues is a viable solution for compatibility issues among many existing SPI-based libraries and programs designed to run on microcontrollers with as little as 2K or even 1K of RAM?

      2. 1kB or 2kB is a luxuary in an embedded environment. You were there in the 8051 days, so I don’t have to tell you that.

        You also make teensy with 64kB oif SRAM, so there is no problem there.

        Note that I said “People that do that already not on Arduino frameworks”…

  1. Haven’t browsed through the code, but when I don’t want stuff interrupted by interrupts I just enclose it in cli(), sei() pairs. This clears / sets the global interrupt vector, turning all interrupts off for the duration, so the first transmission goes through un-interrupted even if it’s in the main loop.

    In the mean time, the per-interrupt flags are still set, and they’ll be serviced as soon as the code works down to the sei() command. So even though the first SPI transmission blocks the second, the interrupt won’t get overlooked. It’ll get queued up to be handled as soon as the first send is done.

    cli() / sei() pairs are all sorts of useful. The strangest glitches can occur when (for instance) dealing with 16- or 32-bit numbers that are made up of volatile bytes that can be changed underneath you from within an ISR. Your code operates on the first byte, interrupt hits, number changes, and then your code goes back to the second byte as if nothing’s happened. Bad mojo.

    You (and I) should probably be worrying about this kind of atomicity every time we use any global/register variable — though I often live on the cowboy side myself.

    1. Global interrupt disable plays havoc with Servo, SoftwareSerial and other interrupt latency sensitive libraries. This SPI transaction layer only masks the specific external interrupts registered as using SPI, so it doesn’t introduce terrible latency for all other interrupts.

      Hack-a-Day’s summary even mentions this feature: “your other interrupts will still happen on time. No servo jitter!”

      If an unknown interrupt is registered, SPI.beginTransaction() falls back to global interrupt disable, pretty much as you’ve proposed. But the SPI-based libraries using interrupts (CC3000, RFM69, RadioHead, nRF8000) all use attachInterrupt.

    2. You should really only use global interrupt enable/disable for operations that take only a few cycles, such as locking operations. In the case you mentioned, you’d have a lock for the SPI bus and yield if it was taken.

      The problem with randomly performing SPI accesses with both interrupt handlers and without interrupt handlers is that there usually isn’t a good way to yield in an interrupt context. One solution might be to disable whatever interrupts cause this to happen in your locking step, but there are many reasons that you might initiate a SPI access from interrupt context and it might not make sense to block all of them.

      A queued transaction system gives you the best of both worlds. You can asynchronously queue accesses with the driver and receive async notification when complete. If you are working with a good threading model, you can yield until you receive the async notification to make it into a blocking request.

  2. I’ve got the agree with the others. Solving this problem is a matter of restructuring/redesigning your code not using another library to hack around it.

    If it is really about keeping Arduino script kiddies from shooting themselves in the foot then that is another thing… I’m not against Arduino or simple libraries for new programmers etc.

    Who is the target audience with this library? I sure hope this isn’t slated to become the replacement SPI library for Arduino as a whole.

    1. What would you suggest as an alternative structure? To not use interrupts? To only do SPI communications in the main loop? If I am updating a DAC in an interrupt 44K time a second, but I also want to read data from the SD card in my main loop, I’ve got a choice of keeping a global “busy” variable for the SPI bus, or disabling interrupts while I access the card.

      1. Other operating systems handle this by having the SPI driver manage the request queue. Wait queue primitives or callbacks are used to inform the initiator that the transaction is complete.

    2. These new SPI functions don’t somehow “hack around” an existing problem. They are in fact the means to update all the existing SPI-based Arduino libraries to properly manage SPI bus access.

      In essence, this work really is about restructuring/redesigning all of Arduino’s SPI-based libraries, just as you and others have said needs to be done.

      1. Its an improvement. It handles one type of situation of initiating transfers in interrupt context. It does this with a somewhat smaller hammer than disabling interrupts globally.

  3. I have a project that uses the Ethernet library and another library that communicates with a RFM69 radio transceiver. Both use SPI and my program would lock up after a few minutes. I think the radio would do something while the their was an Ethernet connection and the program would lock up. I fixed it by using cli() and sei() in w5100.h as described here: http://bit.ly/1rHk0o8
    I wasn’t keen about changing the w5100.h file, but it seems to work fine and I haven’t seen any problems with other sketches. I wonder if this library would be a better solution.

  4. This is great if all devices use the same spi configuration (phase, polarity, bit count etc). What happens when you have different device formats. What happens when you have high-speed devices competing with slower block oriented devices. A specific case is when one object sends data to an LCD display and a second object communicates over the ethernet. Blocking ethernet to send LCD blocks seems problematic unless the ethernet buffers are big enough. Interrupt the LCD transfer to handle ethernet traffic with dissimilar where device spi configurations are different is also problematic.
    Anyone have any thoughts on this?

    1. @Jim – SPI.beginTransaction() takes a SPISettings() object, so every SPI transaction runs at the proper settings for its chip, even if you’re using other chips with very different settings.

      If you read the linked article (I’m the author), or if you just look at the picture above, you can pretty clearly see the test case involves dramatically different SPI clock speeds when 2 different chip select signals are asserted.

      SPI transactions became officially part of Arduino SPI library some time ago. This happened after many months (most of 2014) of lengthy discussion and testing for these and MANY other details. Indeed, we did think of this, and a lot of other things too.

      The blocking issues you mention, specifically Ethernet and SPI-based displays, occur regardless of SPI transactions. The Arduino Ethernet library doesn’t use interrupts, and neither do any of the SPI-based LCD libraries.

      However, SPI transactions have allowed projects with Ethernet and SD cards to work well with interrupt-based radios, like RFM69 (RadioHead) and nRF8001 (from Adafruit). You may believe it seems problematic, but indeed this code has been in widespread use for many months now and it works great.

      All these issues and a lot more about APIs and performance trade-offs were all discussed at incredible detail and length over several months in 2014, on the Arduino Developers Mail List. Decisions were made. Tremendouls amounts of testing was done. Finalized code was published.

      You’re coming very late to the party and bringing up stuff that was settled long ago. But if you *still* want to dig into this, just get any of the six versions of software Arduino.cc has released in 2015. They all have SPI transactions build into the official SPI library. If you think can find any problems, open an issue on Arduino’s github issue tracker. Please, if you do open an issue on Github, post a sketch that demonstrates the problem with a published copy of Arduino, rather than trying to argue hypothetical situations.

  5. Sorry to come so late. Story of my life.
    How does the SPI library support multiple devices with different configurations? Do I simply create multiple instances each with it’s own configuration? Sometimes it’s hard with so much information, to find what you wan’t or even ask the right question.
    Regards,
    Jim

  6. HI Paul,

    The cat came back… with flame suit on.

    My new question is around devices/libraries that handle SPI configuration internally.
    While one could dig into the library code to determine configuration, it would seem, (to me anyway) that having a configuration read-back function would help. In other words, instantiate the device and let i configure SPI, Then use a SPI library function to read-back the current configuration or even better, integrate this into the transaction function the ability to read/save the current configuration. So the final process would be instantiate device one, Use the transaction function to read/save it’s SPI configuration, then instantiate device two and repeat.

    Have a great week,
    Jim

    1. The Arduino Developers Mail List or Arduino’s issue tracker on GitHub are the proper place to suggest such features and extensions and usage cases. Nobody’s ever going to see it and act upon it here, in comments on an old HaD blog.

      I could write a lengthy reply about these ideas, and I very well may…. but only if you post in the proper place. It’s pointless to continue discussing possible future features on long-past blog comments.

  7. Paul,
    Thanks for all the hard work you’ve put into this library (and others). I have several projects that use two SPI devices (RFM69 radio + Ethernet). Keep it up!!

Leave a Reply to JimCancel reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.