SPI Transactions in Arduino - Wed, 2014-07-30 11:05

For the last several weeks, I've been working on SPI transactions for Arduino's SPI library, to solve conflicts that sometimes occur between multiple SPI devices when using SPI from interrupts and/or different SPI settings.

To explain, a picture is worth 1000 works.  In this screenshot, loop() repetitively sends 2 bytes, where green is its chip select and red is the SPI clock.  Blue is the interrupt signal (rising edge) from a wireless module.  In this test, the interrupt happens at just the worst moment, during the first byte while loop() is using the SPI bus!

Click "Read mode" for lots more detail.....

Without transactions, the wireless lib interrupt would immediately assert (active low) the yellow chip select while the green is still active low, then begin sending its data with both devices listening!

With transactions (shown above), this interrupt is masked until the green access completes.  Not shown is the fact other unrelated interrupts remain enabled, so timing sensitive libs like Servo & SoftwareSerial aren't delayed, only interrupts using the SPI library.  SPI transactions also manage SPI settings, so each device is always accessed with its own settings, as shown here with the fast clock during green and slower clock during yellow.

Here's a more involved test case with Adafruit CC3000 and the SD library.

Hopefully soon this new SPI code will become part of Arduino's officially published SPI library and as SPI-based libs update to use the new functions, strange conflicts between SPI devices will become a thing of the past.

This new transaction support is being done in collaboration with the Arduino developers.  In fact, Matthijs Kooijman really deserves credit for the SPISettings portion of this work, as that part and the AVR implemention of it was his work.  Mikael Patel contributed many valuable insights, based on his COSA project, and Cristian Maglie (Arduino's technical lead) created benchmarks to study the performance impact these new functions might have.  Nantonos also contributed, by writing detailed documentation for these new SPI functions.

This work adds 3 new functions to the SPI library.

SPI.beginTransaction(SPISettings); SPI.endTransaction(); SPI.usingInterrupt(number);

In a netshell, SPI.beginTransaction() protects your SPI access from other interrupt-based libraries, and guarantees correct setting while you use the SPI bus.  SPI.endTransaction() tells the library when you're done using the SPI bus, and SPI.usingInterrupt() informs the SPI library if you will be using SPI from a function through attachInterrupt.

The new SPISettings is a special data type, just for describing SPI clock, data order and format.  For fixed settings, you can use beginTransaction(SPISettings(clock, order, format)), and the compiler will automatically inline your fixed settings with the most optimal code.  For user controlled settings, you can create a variable of SPISetting type and assign it based on user choice, which you don't know in advance.  This allows a very efficient beginTransaction(), because the non-const settings are converted to an efficient form ahead of time.

For example, you might use these new functions this way, for fixed settings:

int readStuff(void) { SPI.beginTransaction(SPISettings(12000000, MSBFIRST, SPI_MODE0)); // gain control of SPI bus digitalWrite(10, LOW); // assert chip select SPI.transfer(0x74); // send 16 bit command SPI.transfer(0xA2); byte b1 = SPI.transfer(0); // read 16 bits of data byte b2 = SPI.transfer(0); digitalWrite(10, HIGH); // deassert chip select SPI.endTransaction(); // release the SPI bus return (int16_t)((b1 << 8) | b2); }

This approach generates the most efficient code, because the SPI settings are fixed.  SPISettings compiles to optimal code, thanks to Matthijs Kooijman's nice work!

The other awesome feature of SPISetting is hardware independence.  Any clock speed can be specified as a normal 32 bit integer.  There's no need for "dividers" that require knowing your board's clock rate.  Matthijs's code inside SPISettings efficiently converts those integers to the dividers used by the hardware.  The clock speed you give to SPISettings is the maximum speed your SPI device can use, not the actual speed your Arduino compatible board can create.  The SPISettings code automatically converts the max clock to the fastest clock your board can produce, which doesn't exceed the SPI device's capability.  As Arduino grows as a platform, onto more capable hardware, this approach is meant to allow SPI-based libraries to automatically use new faster SPI speeds.

For non-const settings, on libraries that allow the user to set the clock speed or other settings, you can create a SPISettings variable to hold the settings.  For example:

SPISettings mySettings; void useClockSpeed(unsigned long clock) { mySettings = SPISettings(clock, MSBFIRST, SPI_MODE3); }; void writeTwoBytes(byte a, byte b) { SPI.beginTransaction(mySettings); // gain control of SPI bus digitalWrite(10, LOW); // assert chip select SPI.transfer(a); // send 16 bit command SPI.transfer(b); digitalWrite(10, HIGH); // deassert chip select SPI.endTransaction(); // release the SPI bus }

The simplest way to use SPI transactions involves SPI.beginTransaction() right before asserting chip select, and SPI.endTransaction() right after releasing it.  But other approaches are possible.  For example, my SPI transaction patch for the Ethernet library implements transactions at the socket level.  In the SD library initialization, 80 clocks are sent with chip select high to prep the SD card, as another example where transactions to not necessarily correspond to chip selects.  This design is meant to be flexible, and easy to add to the dozens of existing SPI-based libraries.

The new SPI.h header defines a symbol SPI_HAS_TRANSACTION, to allow library authors to easily add SPI.beginTransaction() and SPI.endTransaction() inside #ifdef checks, for libraries supporting a wide range of Arduino versions.

The one other new function is SPI.usingInterrupt(), which informs the SPI library you will be using SPI functions from an interrupt.  It takes an integer input, which is the same number you use with attachInterrupt().

Newer versions of Arduino have a digitalPinToInterrupt() function, which is useful for converting Arduino pin numbers into their interrupt numbers, and it can tell you whether a pin has interrupt capability.  Here's how it would be used:

void configureInterruptPin(byte pin) { int intNum = digitalPinToInterrupt(pin); if (intNum != NOT_AN_INTERRUPT) { SPI.usingInterrupt(intNum); attachInterrupt(intNum, myInterruptFunction, RISING); } }

The main idea is a call from your SPI-based library using interrupts causes OTHER libraries to temporarily mask the interrupt your library uses, so your interrupt won't conflict with their SPI activity and the SPI bus will be free when your interrupt function runs.

Developing and testing this function SPI sharing functionality has been a long and difficult path.  Since April, the API was discussed in great detail before Cristian Maglie (Arduino's technical lead and the man who ultimately decides what contributions Arduino will accept) ultimately decided on this approach, using a hybrid of my original proposal and Matthijs's SPISettings.  All contributions to a very widely used system like Arduino involve some controversy... there's *always* someone who doesn't like change or wants things done differently.  Luckily, after about 6 weeks of discussion, a clear path was chosen (by Cristian).

I should also mention how rare and insidious these SPI conflicts are.  Even with my intentional test case, rapidly re-reading a file from the SD card while the Adafruit CC3000 library is likely to interrupt, the data would often be read without error.  The SPI port is very fast and the AVR processor is quite slow, so even in a worst case, the window of opportunity for conflicts is small.  Many "normal" programs might see errors very rarely.  Sketches written in very simple ways, where you wait for all activity on one device to finish before using another, are unlikely to ever have conflicts.  The rare nature of these problems has led many people to question the need for this work, but I can assure you (or you could run the test code yourself) these conflicts are real and eventually cause data error or even complete lockup if the interrupt strikes are just the wrong moment.

Hopefully soon, Arduino will publish a new version with this updated SPI library, and over time, the many SPI-based Arduino libraries will gradually update to use these functions.  I want Arduino to be a very solid platform, not just for people using my own Teensy boards, but for all Arduino compatible products... which is why I've put so much work into this SPI library update over the last several weeks.




Categories: DorkbotPDX,

RFM69 Wireless & SPI Bus Sharing (feedback wanted...) - Sun, 2014-07-27 11:19

Recently I've been working to improve the Arduino SPI library, to better support multiple SPI devices with different settings, and SPI devices requiring interrupts.

Today I discovered a new problem while testing the HopeRF RFM69 wireless module.

Click "Read more" for details and the workaround I found....

First, this article is meant to publically document what I've found and invite conversation with anyone else who might have experienced similar issues.  It's very difficult to know if this problem is caused by buggy software, faulty hardware, or radio interference.  Please comment below, or on this forum thread, if you have any insight or opinions.

In a nutshell, I've been working on new Arduino SPI library functions to allow SPI devices with different settings and usage of attachInterrupt, where the interrupt routine uses SPI.transfer(), to hopefully work together.  With the SPI library from Arduino 1.0.5 & 1.5.7, usage from within an interrupt routine can occur while the main program is using SPI.transfer with another device's chip select active, and of course each device might need very different SPI settings.

As part of this testing, I've been working with the Ethernet library using a WIZ820io module and the RadioHead library with a RFM69W wireless module.  The Ethernet library used with Teensy has a patch to allow either W5100 or W5200 chips, so the normally unsupported WIZ820io works fine.  In my testing, I've patched both libraries to use the new SPI.beginTransaction() and SPI.endTransaction() functions, and RadioHead uses SPI.usingInterrupt(), to let those 2 functions know which interrupt needs to be masked while the Ethernet library is using SPI.

So a caveat is that I've working with 3 patched libraries.  There's a lot of uncertainty here.

My test program uses a Teensy 3.1 to send a request to another board running RadioHead's "rf69_server" example, and also a HTTP request to fetch a web page.  Then it tries to receive both of them.  I'll attach the code below.

The RFM69 is unable to receive while the Ethernet library polls the WIZ820io.  I spent hours carefully checking the waveforms on my oscilloscope.  At first I thought this must be a bug in my code.  But it's easy to see the 2 chip selects never assert simultaeously, as they might if SPI.beginTransaction didn't mask the interrupt while Ethetnet uses SPI.  It's easy to see the other device is sending the reply, because its LED blinks when it transmits on its RFM69.  They're sitting right next to each other on my desk (the first photo) and with a line uncommented in the code to cause the Ethernet polling to wait 20 ms, the radio reply is always received correctly.

Finally, in an act of desparation, I build this little hack to force the SCK signal low at the RFM69 when its NSS chip select is idle (high).

Amazingly, this works!  It allows the RFM69 to receive, while the Ethenet module is polling.

I'm still not ready to conclude the RFM69 has a hardware bug, where activity at the SCK input, which ought to be ignored while NSS is high, might be causing it to miss incoming radio data.

It's entirely possible the communication could be creating radio interference.  My little adaptor board has a ground plane between the RFM69 and the digital signals (I'm a little paranoid of messing up RF stuff).

It's also possible my software has a bug, even though the waveforms appear to be fine on a scope.  I might have missed something.

Anyway, here's a quick schematic for that little circuit.

I also created a new adaptor board with this circuit.

Here are my patched copies of these libraries:

At this point, the big question is whether I've made a mistake somewhere (entirely likely), or if the RFM69 module really has some sort of issue where SPI bus usage by other devices, with the RFM69's NSS signal high, causes trouble with radio reception?

If you've used the RFM69 or similar HopeRF modules on a SPI bus with a lot of other activity, please reply or comment on the forum thread.  I'd really like to hear about your experiences.

AttachmentSize rf69_client_and_ethernet.zip1.66 KB
Categories: DorkbotPDX,

Standalone SPI 7-Segment Display - Sat, 2014-07-26 10:03

I had some spare 4-digit 7-segment LED displays and some AT90USB82s, and I'd always intended to do something with them.  This was probably the easiest thing!  It's just the AT90 driving the display, with a(t least) 4 wires controlling it: Vcc, GND, MOSI and SCK.  (I haven't written the code yet, but my plan is to make the display accept characters via SPI and then spend the rest of the time displaying them).

The board has footprints for a 16MHz crystal and USB connector, so you could make it a USB-enabled 7-segment display as well.  I stuffed those parts on my test board, but I'm not sure whether the USB actually works.  You can power the display from USB, at least, although the video shows it being powered over SPI (which is the same connection I use to flash code).

(Some time passes...)

Ha!  It turns out Paul's code for Teensy USB Serial (AT90USB1286) runs on the AT90USB82 also.  So I just plugged it in, and now the 7-Seg display shows up as a serial port.  When you type digits, it scrolls them in from the right.  Next up: escape codes to control brightness, dots, colon and apostrophe.  Plus I have to add characters (I only have hex digits now).  Hee hee, this was almost too easy!

Categories: DorkbotPDX,

[dorkbotpdx-announce] Fwd: Flux move out tonight & tomorrow: score some stuff

dorkbotpdx-announce - Wed, 2014-07-23 11:49
This is a bummer, but grab some furniture and trinkets! ---------- Forwarded message ---------- From: Molly Danielsson Attention Friends (& sweet Trolls) of Flux! We are closing due to insufficient funds. If you thought Flux was awesome and fun, please come help us move ou
Categories: DorkbotPDX, Mailing Lists

Power Playground - Sun, 2014-07-13 23:05

Here's the beginnings of a circuit for playing with what I think of as power electronics -- particularly inductors and batteries.  Design goal is modest voltage (6-25V), modest current (1-5A), modest power (5-10W).  I'd like to be able to characterize inductors and transformers, learn about magnetic saturation, and charge & discharge batteries.

AttachmentSize PowerPlayground.pdf27.22 KB
Categories: DorkbotPDX,

Capacitive Sensing Workshop

DorkbotPDX Flickr Group - Mon, 2014-06-30 10:02

bzztbomb has added a photo to the pool:

Capacitive Sensing Workshop

Capacitive Sensing Workshop

DorkbotPDX Flickr Group - Mon, 2014-06-30 10:02

bzztbomb has added a photo to the pool:

Capacitive Sensing Workshop

Capacitive Sensing Workshop

DorkbotPDX Flickr Group - Mon, 2014-06-30 10:02

bzztbomb has added a photo to the pool:

Capacitive Sensing Workshop

Analog Synth Project - Sun, 2014-06-22 16:26

Here is a  pix of my synth project. The top panel has artwork that will get applied to the panel. Lots of holes drilled...whew! Below is the bottom panel with applied artwork and holes ready for mounting controls! I spent many hours getting the artwork right. That would dictate where the holes would go and allow for spacing of the pots and switches and to get it all to fit on the panel size I had.

The electronics is a design from Elby Designs Australia. ( I have the ASM2...a second generation from the ASM1 by Gene Stopp. Still a 'buttload' of work to do to get it all wired. I've added an 8 Chan. Sequencer that I will