Arduino - Serial Peripheral Interface
Arduino - Serial Peripheral Interface (SPI)
Section titled “Arduino - Serial Peripheral Interface (SPI)”The Serial Peripheral Interface (SPI) is a synchronous serial communication protocol used for short-distance communication, primarily in embedded systems. It’s known for being faster than I2C and supporting full-duplex (simultaneous send and receive) communication.
SPI communication involves a Master device that controls the communication and one or more Slave devices that respond to the master. It operates using a set of four signal lines.
The Four SPI Wires
Section titled “The Four SPI Wires”| Signal | Alternative Name | Description |
|---|---|---|
| SCK | SCLK | Serial Clock - The clock signal sent by the Master to synchronize data transfer. |
| MOSI | COPI | Master Out, Slave In - The data line for sending data from the Master to the Slave. |
| MISO | CIPO | Master In, Slave Out - The data line for sending data from the Slave to the Master. |
| SS | CS | Slave Select - A line controlled by the Master to select which Slave it wants to communicate with. Each slave needs a dedicated SS line. |
Arduino SPI Pins and Wiring
Section titled “Arduino SPI Pins and Wiring”The SPI pins are located on specific pins for different Arduino boards. On an Arduino UNO, they are on the ICSP header and also mirrored on digital pins 10-13. Here is the standard wiring for connecting two Arduino UNOs:
- Common Ground: Connect a GND pin on the Master to a GND pin on the Slave. This is essential!
- SCK: Master Pin 13 → Slave Pin 13
- MOSI: Master Pin 11 → Slave Pin 11
- MISO: Master Pin 12 → Slave Pin 12
- SS: Master Pin 10 → Slave Pin 10
Note: For other boards like the Mega or MKR series, consult their official pinout diagrams to locate the SPI pins.
Modern SPI Programming: Transactions
Section titled “Modern SPI Programming: Transactions”The modern approach to SPI communication uses a ‘transaction’. This ensures that the SPI settings (like clock speed and data mode) are correctly configured for a specific device before communication begins and are not interfered with by other devices on the same bus. This is the best practice.
SPI.beginTransaction(SPISettings(speed, dataOrder, dataMode)): Begins an SPI transaction.SPISettingsis where you configure the communication parameters.speed: The clock speed in Hz (e.g.,4000000for 4MHz).dataOrder:MSBFIRST(Most Significant Bit first) orLSBFIRST.dataMode:SPI_MODE0,SPI_MODE1,SPI_MODE2, orSPI_MODE3. This defines the clock polarity and phase. The device’s datasheet will specify which mode to use.SPI.transfer(data): Sends a byte of data and simultaneously receives a byte back, which is the return value of the function.SPI.endTransaction(): Releases the SPI bus for other devices.
Example: Master and Slave Communication
Section titled “Example: Master and Slave Communication”In this example, the Master will send a command character to the Slave. The Slave will process the command and prepare a response. The Master will then read this response.
Master Code
Section titled “Master Code”/* SPI Master Example - Modernized
This sketch sends a command to an SPI slave and reads the response.*/
#include <SPI.h>
const int slaveSelectPin = 10;
// Define the settings for our SPI communicationSPISettings slaveSettings(4000000, MSBFIRST, SPI_MODE0);
void setup() { Serial.begin(115200); while (!Serial);
// Set the Slave Select pin as an output pinMode(slaveSelectPin, OUTPUT); digitalWrite(slaveSelectPin, HIGH); // Deselect slave initially
// Initialize SPI SPI.begin(); Serial.println("Master Initialized");}
void loop() { // Send command 'a' and get a response char response = sendCommand('a'); Serial.print("Received from slave: "); Serial.println(response); delay(1000);
// Send command 'b' and get a response response = sendCommand('b'); Serial.print("Received from slave: "); Serial.println(response); delay(1000);}
char sendCommand(char command) { // Start the transaction with the slave's settings SPI.beginTransaction(slaveSettings);
// Select the slave digitalWrite(slaveSelectPin, LOW);
// Send the command and receive a byte at the same time char result = SPI.transfer(command);
// Deselect the slave digitalWrite(slaveSelectPin, HIGH);
// End the transaction SPI.endTransaction();
return result;}Slave Code
Section titled “Slave Code”/* SPI Slave Example - Modernized
This sketch listens for commands from an SPI master and replies. It uses an interrupt to handle incoming data efficiently.*/
#include <SPI.h>
volatile byte receivedData;volatile byte dataToSend;volatile bool newData = false;
void setup() { Serial.begin(115200); while (!Serial);
// Set MISO as an output pinMode(MISO, OUTPUT);
// Configure SPI for Slave mode SPCR |= _BV(SPE); // Enable SPI SPCR |= _BV(SPIE); // Enable SPI Interrupt
receivedData = 0; dataToSend = 'x'; // Default initial response Serial.println("Slave Initialized");}
// SPI Interrupt Service RoutineISR(SPI_STC_vect) { receivedData = SPDR; // Read received data SPDR = dataToSend; // Pre-load the data to be sent next time newData = true; // Set flag for the main loop}
void loop() { if (newData) { newData = false; // Clear the flag Serial.print("Received command: "); Serial.println((char)receivedData);
// Process the command and prepare the next response if (receivedData == 'a') { dataToSend = 'A'; // Prepare 'A' as the response for the next transfer } else if (receivedData == 'b') { dataToSend = 'B'; // Prepare 'B' } else { dataToSend = '?'; // Unknown command } }}Debugging SPI
Section titled “Debugging SPI”- No Communication: The most common issue is wiring. Double-check all connections: MOSI-to-MOSI, MISO-to-MISO, SCK-to-SCK, and especially the common ground.
- Garbage Data: If you receive incorrect data, there’s likely a mismatch in SPI settings. Ensure both Master and Slave are using the same
dataModeanddataOrder. - Multi-Slave Issues: When using multiple slaves, ensure only one slave is selected (its SS pin is LOW) at any given time. All others must be deselected (SS pin HIGH).
- The Best Tool: For complex SPI issues, a logic analyzer is invaluable. It allows you to see the actual signals on the wires and quickly diagnose timing, mode, or data problems.