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”.

Comments

  1. FlippyBits says:

    I guess this would work, but if you’re getting collisions on your SPI bus, you should probably restructure your code.

    • This work could in a sense be considered restructuring the code, since all SPI-based sketches and libraries need to be updated with SPI.beginTransaction and SPI.endTransaction.

      • Waterjet says:

        Why is this (SPI collision detection/avoidance) not already implemented?

        • 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.

  2. Chamb Onz says:

    Well done!

  3. asdg says:

    Fix code that is defective by design by making another layer of indirection. Arduino commune at it’s best.

  4. NotArduino says:

    Yes, a great way to patch design flaws in the structure of your code is to add yet another layer of code…why not just use an OS at this point?

    • tekkieneet says:

      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.

      • 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?

      • tekkieneet says:

        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”…

  5. 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.

    • jiago says:

      Or you can just switch to 32-bit world and forget this 8-bit nonsense.

    • 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.

    • russdill says:

      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.

  6. kennedybushnell says:

    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.

    • Shawn Swift says:

      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.

      • russdill says:

        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.

    • 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.

  7. Scott216 says:

    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.

  8. Drone says:

    Use a Propeller (multi-core) instead.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 97,790 other followers