In the previous tutorial, we learned how to interface an ADXL345 accelerometer with Arduino by using the I2C bus. UART, I2C, and SPI are the most common serial communication protocols used in embedded electronics.
UART, a universal asynchronous receiver-transmitter, is used for full-duplex serial communication with a single device.
The I2C, an inter-integrated circuit, is a master-slave, serial-data communication protocol. It allows half-duplex communication with hundreds of devices over just two wires.
SPI, or serial peripheral interface, is an alternative to UART when full-duplex data communication is required with multiple devices. SPI is also a master-slave-type serial protocol. As an SPI bus allows for full-duplex data transfer with multiple devices, it’s often used by electronic sensors that need bi-directional data communication with controllers or computers.
Many sensors that only require unilateral data transfer also use a three-wire SPI instead of the I2C because the SPI is twice fast.
An SPI bus
The SPI bus developed by Motorola is a master-slave synchronous, serial-data communication standard. There can be only one master on the SPI bus, but many slave devices can share the bus.
In practical terms, the number of slave devices that can be connected in independent configuration to an SPI bus is limited. Typically, it’s best to avoid connecting more than three or four slaves to an SPI bus.
The SPI has been designed for uninterrupted, high-speed data communication within a PCB or over short wires. Generally, the sensors or modules that use an SPI interface can plug into the controller or computer boards.
The SPI bus has four wires:
1. Master Out Slave In (MOSI) — for data transfer from a master to a slave
2. Master In Slave Out (MISO) — for data transfer from a slave to a master
3. Serial Clock (SCK/SCLK) — for a clock signal from a master to a slave
4. Slave Select (SS on Master)/Chip Select (CS on Slave) — for selection of a slave by the master
The slave devices can be connected to an SPI bus in either parallel or daisy-chain configuration.
Note:
- If slaves are connected in a daisy chain, the single Slave Select pin is required on the master
- If slaves are connected in a parallel configuration, there needs to be a separate Slave Select pin on the master for each slave
The data communication on the SPI bus is solely controlled by the master. If slaves need to talk back to the master, it must be pre-arranged and dictated by a strict command structure. The data bits are transferred in an uninterrupted continuous manner with each bit exchanged on a clock pulse. There’s no acknowledgment or error when checking.
Certain electronic components require only transmitting or receiving data. Such parts typically only have a MISO or MOSI channel. Usually, these components prefer the SPI over an I2C for unidirectional data communication, simply for the sake of speed. In fact, there is no theoretical maximum speed for an SPI bus.
Two devices can communicate over the SPI bus so long as they can match speeds and perform the sampling of bits without error. The data speed on an SPI bus can be as high as 10 Mbps.
Some devices also use three-wire SPI where the MISO and MOSI are combined to one wire — called Master In Master Out (MIMO). However, the three-wire SPI allows for only half-duplex data communication.
Why an SPI?
The SPI bus is used by several devices over an I2C or a UART. The most important reason behind this is that the SPI bus can be configured to transfer data at twice the speed as an I2C bus. Plus, the clock speed can be arbitrarily decided by the SPI master.
The hardware required to communicate data over the SPI bus can be as simple as a shift register. There’s no complicated in-band slave addressing. The SPI allows full-duplex communication with multiple devices. But, it’s not perfect.
The number of slave devices that can be connected in an independent configuration is limited by practicality. The SPI only works over short wires or within a PCB layout.
There’s no error-checking in the SPI protocol, so the data communicated over it cannot be verified. And even if it allowed for full-duplex communication, any data communicated back to the master by a slave must follow a strict command structure. The master must know what size of data and when it will get from the slave in response to a command. Learn more about the SPI bus here.
The SPI in Arduino
Most Arduino boards have at least one SPI interface. The SPI interface on Arduino UNO is shown here:
The following table summarizes the SPI pins on various Arduino boards:
The SPI library
Arduino can be configured as both an SPI master and slave. When using an SPI bus in Arduino, the SPI library is required. This library is designed to use Arduino as an SPI master. It can be imported in a sketch using this statement:
#include <SPI.h>
The library has these methods…
SPISettings() – used to create an object to configure the SPI port of Arduino. It’s used to set the data speed, data order, and SPI mode altogether. This method can be directly called in the SPIbeginTransaction() if constant settings are to be done. If the variable settings are required, an instance of the SPISettings object must be created. It has this syntax:
SPI.beginTransaction(SPISettings(dataSpeed, dataOrder, SPIMode)) //for constant settings
or
SPISettings mySetting(dataSpeed, dataOrder, SPIMode) //for variable settings
This method takes three parameters. The first parameter is the speed of communication, which is specified in bits-per-second (or Hertz). The maximum SPI speed supported by Arduino is 20 MHz (i.e. 20000000).
The second parameter is the data order. It can be either MSBFIRST or LSBFIRST. Most of the SPI chips use the MSB first. The third parameter is the SPI mode. It can have these values: SPI_MODE0, SPI_MODE1, SPI_MODE2, and SPI_MODE3.
Here’s a valid example of this method:
SPI.beginTransaction(SPISettings(9600, MSBFIRST, SPI_MODE0));
Note that the SPI.setBitOrder(), SPI.setClockDivider(), and SPI.setDataMode() methods have been deprecated.
SPI.beginTransanction() – used to initialize the SPI bus. This method takes the SPISettings as the parameter. Also, the SPISettings() can be called as a parameter directly or an instance of the SPISettings can be passed as a parameter. This method has this syntax:
SPI.beginTransaction(spiSettings)
This is a valid example of this method:
SPI.beginTransaction(SPISettings(9600, MSBFIRST, SPI_MODE0));
SPI.begin() – used to initialize the SPI port of Arduino. It sets the MOSI, SCK, and SS pins to output. It also pulls the MOSI and SCK pins LOW and the drive the SS pin HIGH. It has this syntax:
SPI.begin()
Here’s a valid example of this method:
SPI.begin();
SPI.transfer() – used to send and receive data over the SPI bus. It performs a simultaneous read and write on the SPI bus. This method must be passed the data to be sent as the parameter as a byte or data buffer. It will return the received data that can be retrieved in a variable and has this syntax:
SPI.transfer(val)
spiReceive = = SPI.transfer(val)
SPI.transfer(buffer, size)
spiReceive = = SPI.transfer(buffer, size)
This is a valid example of this method:
SPI.transfer(0x53);
SPI.transfer([0x53, 0x0B], 2);
SPI.transfer16() – the same as the SPI.transfer except that it’s used to send two bytes, one after the other. It has this syntax:
SPI.transfer16(val16)
spiReceive16 = = SPI.transfer16(val16)
Here’s a valid example of this method:
unsigned int testint = 0;
SPI.begin();
SPI.transfer16(testint);
SPI.endTransaction() – used to stop using the SPI bus. Bu the chip select must be de-asserted before calling this method. It has this syntax:
SPI.endTransaction()
This is a valid example:
SPI.endTransaction();
SPI.end() – used to disable the SPI bus. It frees the SPI pins on Arduino, but leaves the pin modes unchanged. It has this syntax:
SPI.end()
Here’s a valid example of this method:
SPI.end();
SPI.usingInterrupt() – used to disable an interrupt when the SPI transactions are performed. This method takes the interrupt number or name as the parameter.
The interrupt is disabled when the SPI.beginTransaction() is called and re-enabled after the SPI.endTransaction() is called. It has this syntax:
SPI.usingInterrupt(interruptNumber/interruptName)
Arduino as the SPI master
To use Arduino as the SPI master, the SPI library must first be imported in the sketch and a call to the SPI.begin().
The SPI settings can be configured by calling the SPISettings() method as a parameter in the SPI.beginTransaction() method or by creating an instance of the SPISettings and using it as the parameter in SPI.beginTransaction() method.
The CS pin must be set as an output to indicate to Arduino that the SPI library is to be used to configure it as the SPI master. The data can be transferred to a slave and read from a slave using the SPI.transfer() and/or SPI.transfer16() methods.
To stop using the SPI bus, the SPI.endTransaction() and SPI.end() methods must be called.
The SPI control register
The AVR controller on Arduino boards has a built-in register SPCR to configure the SPI settings. The register has this bit diagram:
Note:
- If the SPIE bit is 1, it enables the SPI interrupt.
- If the SPE bit is 1, it enables the SPI bus.
- If the DORD is 0, it sends the MSB first. But if it’s 1, it sends the LSB first.
- If the MSTR is 0, it sets Arduino as the slave. If it’s 1, it sets Arduino as the SPI master
- The CPOL controls clock polarity and if the CPOL=0, the data clock is idle when LOW. Or, if the CPOL=1, the data clock is idle when HIGH.
- The CPHA sets the clock phase and if the CPHA=0, the data is sampled on the rising edge. But if the CPHA=1, data is sampled on the falling edge
- The SPR1 and SPR0 set the data speed.
The SPI serial-transfer complete interrupt
The AVR microcontroller on Arduino boards supports multiple interrupts. These interrupts are identified by a vector name or number.
The vector name or number must be passed as a parameter in the ISR() routine. This table lists the interrupts supported by the ATmega328P microcontroller (Arduino UNO):
The SPI serial-transfer complete is one of the AVR interrupts. It’s triggered when an SPI transfer is complete. This interrupt is particularly useful when Arduino is configured as an SPI slave. It’s the only mechanism for an SPI slave Arduino to know that it has received data from the SPI master.
When a slave Arduino receives data (a byte) from a master, it’s stored in the SPI data register (SPDR).
An interrupt service routine can be called for SPI as follows:
ISR (SPI_STC_vect)
{
…..
}
or
ISR (18)
{
…..
}
The interrupt service routine can be written to read data from the SPI data register to a variable and use the value further.
Arduino as a SPI slave
To configure Arduino as an SPI slave, the library must first be imported in the sketch. The SPI port must also be initialized by calling the SPI.begin() method.
Arduino is configured as an SPI slave by default as the MSTR bit of the SPCR register is 0 (by default). The SPI must be enabled by setting the SPE bit of the SPCR register. This can be done using the _BV() function.
The SPI interrupt should then be initiated by calling the SPI.attachInterrupt() method. Next, write an Interrupt Service Routine for SPI_STC_vect. The routine will involve reading data from the SPI data register (SPDR). If the data is written back to the master device from a slave Arduino, the MISO pin must be set as output.
In the next tutorial, we’ll learn about 7-segment multiplexing using the MAX7219 IC. The MAX7219 IC communicates data with a controller over an SPI bus.
Filed Under: Arduino, Microcontroller Projects
Questions related to this article?
👉Ask and discuss on EDAboard.com and Electro-Tech-Online.com forums.
Tell Us What You Think!!
You must be logged in to post a comment.