This post describes how the SPI interface works, with particular reference to the Arduino Uno which is based on the ATmega328P microprocessor chip. A lot of the details however will be of more general interest.
SPI is used to send serial data from a microprocessor to another one, or a peripheral, for example an LCD display, a temperature sensor, a memory (SD) chip, and so on.
More information about SPI at:
http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
More information about the Arduino SPI interface at:
http://arduino.cc/en/Reference/SPI
Other protocols
Sending data
Let's start with an image - this is a screenshot taken with a logic analyser. It shows the 3-character sequence "Fab" being sent from the Arduino.
I put a trigger on the SS (Slave Select) pin so that the logic analyser would start analysing from when the sequence started.
From the above graphic note the following points of interest:
- A - no data (SS is high, clock is low)
- B - SS taken low to enable the slave (peripheral). At this point the slave should prepare to transfer data by setting the MOSI (master out, slave in) line, and the SCK (serial clock) as inputs, and the MISO (master in, slave out) as an output. The slave can now prepare to notice clock pulses on the SCK line.
- C - First character arrives (the letter "F" or 0x46 or 0b01000110). For each of the 8 bits the SCK (clock) line is briefly brought high, and then low again. This tells the slave to read the data on the MOSI line. Also the slave can place data on the MISO line for the master to simultaneously read in.
- D - The letter "a" arrives
- E - The letter "b" arrives
- F - "No data" after "Fab" - however the SS is still enabled.
- G - SS taken high to indicate end of the sequence of data. At this stage the slave should release the MISO line (configure it as an input, or "high impedance"). Also the slave should ignore any clock pulses now (they may be for a different peripheral).
The code to produce this (in Arduino's C++ language) was:
// Written by Nick Gammon
// January 2011
#include <SPI.h>
void setup (void)
{
}
void loop (void)
{
digitalWrite(SS, HIGH); // ensure SS stays high
// Put SCK, MOSI, SS pins into output mode
// also put SCK, MOSI into LOW state, and SS into HIGH state.
// Then put SPI hardware into Master mode and turn SPI on
SPI.begin ();
delay (5000); // 5 seconds delay to start logic analyser.
char c;
// enable Slave Select
digitalWrite(SS, LOW); // SS is pin 10
// send test string
for (const char * p = "Fab" ; c = *p; p++)
SPI.transfer (c);
// disable Slave Select
digitalWrite(SS, HIGH);
// turn SPI hardware off
SPI.end ();
while (1); //loop
}
More detail for the first character (the letter "F" or 0x46 or 0b01000110) can be seen here:
Notice how for each bit (starting with the most significant bit) the MOSI line is first changed to the correct state (0 or 1) and then the SCK (clock) line is pulsed to indicate that the data should be read.
Sending and receiving data
The next graphic, taken while sending data to an Ethernet card, shows how both MOSI and MISO lines can be exchanging data simultaneously:
Basically, while the master hardware is clocking out bits on the MOSI line (master out, slave in) it is also clocking in bits on the MISO (master in, slave out). Effectively, during one character time, it both sends and receives one byte. Hence the name of the function SPI.transfer.
The code that produced the above might have looked like:
char a, b;
a = SPI.transfer (4);
// a is now 1
b = SPI.transfer (3);
// b is now 2
Timing
As you can see from the Logic analyser timing, each byte seems to take about 3 microseconds to be sent, so that means you could send 333,333 bytes in one second, effectively being 325.5 Kbytes/s (333333/1024).
The clock pulses are 0.25 microseconds apart (0.125 microseconds on and 0.125 microseconds off). Effectively this means it is clocking at 4 MHz.
[EDIT] However see below ("SPI speeds") for a more detailed analysis. In particular, rates of 888,888 bytes per second are theoretically achievable (868 Kbytes/s). Also you can slow SPI down if the receiving end operates more slowly or needs time to process the data.
Slave Select
There is a bit of confusion about the Slave Select pin. This is because the SPI hardware can be used to communicate with a number of slaves at once. The way this is done is to tie the SCK, MISO and MISO lines together (for each slave) and have a separate SS (slave select) line for each slave. This way you only need 3 wires (plus ground) in total, plus one slave select for each slave.
Thus, slaves should let each of the lines float (as inputs) unless their slave select is taken low. Then, and only then, should they configure their MISO line (master in, slave out) to be an output, so they can send data back to the master.
Pin 10 of the Arduino is the SS line - so does this mean you have to use it for the peripheral SS line? Or should it be kept high? The answer (from the Atmega documentation) is that the SS line (pin 10) must be configured as an output (regardless of what value is on it). If configured as an output the Arduino ignores the value on that line, and thus the SPI hardware will not be put into slave mode.
Since the value on pin 10 is ignored, providing it is set as an output, then it can also be used as the SS line for your first, or only, slave. You could then use other pins (eg. 5, 6, 7, 8, 9) to control other slaves.
Protocols
The SPI spec does not specify protocols as such, so it is up to individual master/slave pairings to agree on what the data means. Whilst you can send and receive bytes simultaneously, the received byte cannot be a direct response to the sent byte (as they are being assembled simultaneously). For example, in the graphic above, the slave is not replying with a 1 to the transfer of 4, because it doesn't know it has received 4 before it has already sent 1.
So it would be more logical for one end to send a request (eg. 4 might mean "list the disk directory") and then do transfers (perhaps just sending zeros outwards) until it receives a complete response. The response might terminate with a newline, or 0x00 character.
You also don't necessarily know if you even have a peripheral attached. One way might be to send out a "query", like this:
SPI.transfer (0xCD); // are you there?
byte x = SPI.transfer (0); // get response
if (x == 0xEF)
{
// peripheral is alive
}
This example asks the slave to reply with 0xEF if it receives 0xCD - this presumably wouldn't happen if the wires were not connected to anything. This could be used to verify that the slave actually was there and "alive".
Modes
For maximum flexibility with various slaves, the SPI protocol allows for variations on the polarity of the clock pulses. CPOL is clock polarity, and CPHA is clock phase.
- Mode 0 (the default) - clock is normally low (CPOL = 0), and the data is sampled on the transition from low to high (leading edge) (CPHA = 0)
- Mode 1 - clock is normally low (CPOL = 0), and the data is sampled on the transition from high to low (trailing edge) (CPHA = 1)
- Mode 2 - clock is normally high (CPOL = 1), and the data is sampled on the transition from high to low (leading edge) (CPHA = 0)
- Mode 3 - clock is normally high (CPOL = 1), and the data is sampled on the transition from low to high (trailing edge) (CPHA = 1)
Mode 0
Mode 1
Mode 2
Mode 3
Example code to change mode:
SPI.setDataMode (SPI_MODE3);
There is also a DORD (data order) flag. The normal order is most significant bit first (as above) but the alternate order is least significant bit first.
Arduino Library
The Arduino development kit comes with an SPI library. To use it you just need to include it, like this:
This gives you an SPIClass, and an instance of that class called SPI in SPI.cpp.
To condition the hardware you call SPI.begin () which configures the SPI pins (SCK, MOSI, SS) as outputs. It sets SCK and MOSI low, and SS high. It then enables SPI mode with the hardware in "master" mode. This has the side-effect of setting MISO as an input.
If you need to change the mode or bit order you have the functions SPI.setDataMode and SPI.setBitOrder. Normally you wouldn't have to.
The function SPI.transfer does the actual transferring of bytes. It is up to you to set SS low at an appropriate time (this may or may not be pin 10 as described above). When finished call SPI.end () to turn the hardware SPI off.
Pinouts on the Arduino Uno
Pinouts on the Arduino Mega2560
On the Arduino Mega, the pins are 50 (MISO), 51 (MOSI), 52 (SCK), and 53 (SS).
Pinouts using the ICSP header
You can also connect to the SPI pins (except SS) by using the ICSP header (on both the Uno and the Mega2560):
[EDIT] Modified 8th July 2012 to add screenshots of logic analyzer output of the various clock modes. |