The SPI interface has become as ubiquitous in embedded design as the UART or ADC. This is for good reasons as the SPI protocol has a number of great advantages. It is these advantages that we will talk about today and how to best utilize them in your design.
Advanced uses of the SPI interface
Below I have gathered a list of advanced uses of the SPI interface hardware that could either improve SPI communications between your devices or use the hardware in an unusual way – often to provide means to a different protocol not supported by the hardware.
Single direction allows for isolation or level shifting
In one of my recent designs, I’ve been using a micro that is limited to 2.8v to 3.3v power source but found an extremely low-power, and low-cost sensor that could only run at 1.8v.
Because of the single direction of each of the SPI interface lines, the voltage levels could have been solved (as compared to using I2C). Something as simple as a MOSFET with the correct power rail for each line could be used to drive each side.
In another project, a 3v system needed to interact with 24v controls (industrial equipment). A single MOSFET wouldn’t have been able to handle this voltage gap and instead, optoisolators were used. These nifty devices combine an LED and a phototransistor to completely isolate the electronic interface.
Both designs took advantage of the single direction of each SPI pin to interface between power domains.
Connecting multiple slave devices to the SPI bus
Like I2C, the SPI interface allows for connecting multiple slave devices to a single host port. Sounds nice and easy in practice, but the nuances of actually implementing this can create additional complications.
Electrical considerations for attaching multiple slave devices
Paul on DorkbotPDX put together a good list of a Better SPI Bus Designs. His focus was on the Arduino hobby market but contains some great insights for all embedded design.
To allow sharing the MISO pin, all non-active SPI devices are required to put this pin in a high-Z state, aka tri-state output. Otherwise, the device that is currently active would create a short against the non-compliant device.
This is an example of the importance of checking the device’s data sheet, even for something as common as SPI.
If one of the devices does not implement the tri-state mode, you have two options: move it to its own SPI port or add a tri-state buffer on its MISO pin (as Paul recommended below).
Pull up nCS
Paul’s design recommendations included adding a pull-up to each chip select.
The pull-up is supposed to prevent devices from being left in an unknown state if the processor wasn’t configured correctly. This may be important for something like an Arduino shield where the user may not realize there is a device there.
For standalone systems, the pull-up shouldn’t be needed as you have complete control over the design. An exception to this could be an embedded Linux environment to ensure the UBoot or other non-typical scenarios don’t break something.
Firmware considerations for attaching multiple slave devices
When attaching multiple SPI devices to a single bus, there are firmware considerations to think of as well.
The most critical component is the latency tolerance each device can tolerate. Simple sensors such as temperature and humidity would cause little loss of information if they didn’t get read right away.
Radio transceivers aren’t so tolerant of delays. If your system is busy communicating with another device the radio may have to drop a packet that wasn’t acknowledged in time.
This could again be solved by putting critical or time intensive (like flash memory) devices on their own dedicated port. Alternatively, if spare pins are a premium, the firmware must be architected in a way that gives priority to the time critical devices – and handles dropping the noncritical ones in a way that they can continue later.
Speaking of the firmware architecture, the software design must also take into account that only one device may access the SPI interface at a time.
If using something like FreeRTOS, a mutex should be used for each task that wants to communicate with its attached device. This prevents another task from attempting to talk to its own device and writing random data in the active device.
No addressing or ACKs allow for blind communication and non-standard protocols
I’ve often seen the lack of ACKs in the SPI interface as a con to the protocol. While this is true for many applications that require reliable communication to the end device (such as our flash memory again), the lack of an ACK signal can also provide a benefit to non-standard uses.
One that I have used on a previous design was to use the SPI MOSI pin to drive an FM modulator. This was a simple broadcast device but required precise bit timing for the receiver to work correctly. By using the SPI hardware within the chip, it was able to achieve this precision and utilize the bit buffer to enable other tasks to run in parallel.
I’ve also seen the SPI interface used to drive LEDs, primarily the neoPixels that AdaFruit is so fond of. These RGB LEDs have a non-standard serial interface that allows multiple LEDs to be chained together on a single port.
The interface requires a pulse width modulated signal where a 1 has one duty cycle, a 0 another with very specific timing requirements as shown below:
For small embedded projects it could bit-bang this in a timer interrupt, but a better alternative is to use the SPI port. You can set up a function that sends 0xE0 for logic 0 and 0xF8 for a logic 1. Adafruit has a tutorial for a USB-to-SPI chip that does exactly that.
Because the port can blindly transmit the bits, it doesn’t care that there isn’t an SPI device on the other end.
Adding IRQ Lines
Another seeming downfall of SPI is the lack of request for attention built into the protocol like I2C’s clock stretching and SMBus. This has led many chip vendors to add an IRQ pin to provide an out of band signal to the host.
I actually believe this is a huge advantage to the design.
Since each device can trigger its own IRQ pin, the host immediately knows which device requires attention rather than poll each one.
It also doesn’t interrupt the communication bus. If you are busy writing to one chip you don’t want a different one to barge in and start talking.
The IRQ pin allows your firmware to decide on when, and if, it wants to talk to the device.
Yes, it takes additional pins. But a clever design and a few additional components could provide a workaround. Perhaps adding an OR gate with all the IRQ pins into a single input of the processor. A second input could be gated by AND gates with the chip select acting as the selector, in this way the firmware could cycle through each chip select until it found which was active.
Go beyond the standard
The best embedded systems use every resource the chip has to offer to create the solution.
The SPI interface is just one of the useful peripherals on a micro, and I hope this article has inspired you to take your design a bit further and think of creative uses.
Have a nifty use of an SPI port you would like to share? Hit up the comments below to share with the community.