Third party cookies may be stored when visiting this site. Please see the cookie information.

PenguinTutor YouTube Channel

Using Raspberry Pi and Arduino together through SPI serial communications

In the previous page I showed how you can control an Arduino from a Raspberry Pi using USB serial communications (UART). In this I show an alternative way to communicate between a Raspberry Pi and an Arduino by using the SPI (Serial Peripheral Interface) protocol.

What is SPI?

SPI is a serial protocol, which means that data is sent one bit at a time down a wire. Unlike the serial protocol used for USB which is asynchronous, SPI is synchronous. It uses a clock signal so that all devices can stay in sync and it's full-duplex allowing data to be transferred in both directions at the same time. It's also a bus technology which means you can add multiple devices on the same serial ports.

A word about naming

SPI is sometimes referred to as a Master, Slave relationship. This is now considered by many to be inappropriate as it is trivialising the suffering of slaves both historically and through modern day slavery affecting vulnerable people around the world. In addition the terminology isn't always correct.

In this example the Raspberry Pi will be controlling the communication bus, but by using an Arduino it's possible that it could be the Arduino telling the Raspberry Pi what to do. The control only applies to the bus and not to the devices.

I will therefore be using the terms main device (or main controller) to indicate the computer that is controlling the SPI bus, and secondary device to indicate the device that the main device communicates with. This will mean that the usual acronyms can still be used.

SPI Bus

There can be several secondary devices, but only a single main device controlling the bus. Communications are full-duplex and bidirectional. There is not fixed meaning for how the data is formatted (only the way it is transmitted). If using an Arduino you can define your own protocol, or if communicating with an integrated circuit (IC) then the protocol is provided by the datasheet for that particular integrated circuit.

The image below shows how one main device can control the SPI communications with three secondary devices.

SPI Bus representation

There is a single SPI Clock signal (SCLK) which goes from the main device to all the secondary devices. There is a bus connection labelled MOSI (main out, secondary in) which handles communication from the main device to all the devices, and MISO (main in, secondary out) which is used for the data transferred from the secondary device to the main device. All these 3 lines are shared between all devices, but there is an additional secondary select which enables just one secondary device at a time. This needs one additional line per device. The secondary select can sometimes be called chip select (CS) or chip enable (CE).

Advantage of SPI communications between Raspberry Pi and Arduino

In the earlier example I showed how serial communications can be handled over the USB or serial ports on the Raspberry Pi and Arduino. That is the easiest way to connect one pair of devices.

The advantage of SPI is that you can control multiple devices through one SPI bus. It can also be faster. It is however a little more complicated to understand and code and I've not properly used the full-duplex feature in my example code.

SPI on the Raspberry Pi and the Arduino

Warning! The Raspberry Pi and Arduino work may work at different voltages.

In this example we will be using the Raspberry Pi as the main device passing instructions to a Raspberry Pi

The SPI ports on the Raspberry Pi are 3.3V only. They can be damaged by if a slave device raises a bus to 5V. If connecting to a 5V device then a level shifter should be used. Based on all models of the Raspberry Pi with 40pins, the pins for SPI0 are (physical pins):
MOSI - pin 19
MISO - pin 21
SCLK - pin 23
CE0 - pin 24
CE1 - pin 26
Ground - pin 25

SPI on the Arduino

Different models of the Arduino have different capabilities and ports for SPI. The newer MKR series run at 3.3V, but the more common UNO runs at 5V. I tested this using a Freeduino UNO which has the advantage of being able to switch between 3.3V and 5V, but otherwise similar to the official Arduino. It did have the advantage of allowing me to test the connection was working using 3.3V prior to adding the level shifter.

The ports on the Arduino UNO that are used are:
MOSI - pin 11
MISO - pin 12
SCLK - pin 13
SS / CE - pin 10 (optional)

For other versions of the Arduino then you may need to check the appropriate datasheet. You may also need to update the code if using a different SPI port on the Arduino.

Logic Level Convertor

Due to the voltage difference I suggest that you use a logic level convertor (sometimes called a level-shifter or voltage shifter) between the Raspberry Pi and Arduino. The model I used is the "4-channel I2C-safe Bi-directional Logic Level Converter - BSS138". This is designed for I2C, but also works well with SPI up to 2Mhz.

The diagram below shows the Raspberry Pi and Arduino connected together through an Adafruit 4-bit level shifter".

Unlike using traditional PC serial communications there is no need to cross the transmit and receive as the input and receive are handled appropriately.

Test setup

For test purposes I have connected digital outputs 3 to 9 to LEDs and resistors to ground. I used a bar graph LED and resistor array for convenience, but did not use all the LEDs on the bar graph. I used digital pin 2 to test an input (moving between +5V and Gnd) and I also added a variable resistor so that I could measure an analog voltage on analog input A0. This is shown in the diagram below (for convenience this is turned 90 degrees compared with the earlier diagram).

Test setup Raspberry Pi and Arduino SPI serial communications

This allows the code to demonstrate two way communication, with the Raspberry Pi asking the Arduino to set digital outputs on or off and request the Arduino provide information about digital and analog inputs. The Arduino sends the relevant information back to the Raspberry Pi. This provides something that can be used as a standalone system, although it's really just a small demonstration of what you can do.

Devising a communications protocol

The SPI communication sends a byte at a time between the two devices. There is no fixed structure to the data so I created my own protocol to determine how to interpret instructions and values. The request send byte is shown in the diagram below.

The response from the Arduino is either a value (Analog read), or confirmation response that the request has been actioned, or 0xff if an error occured.

Client software on the Arduino

The client is setup as a secondary device receiving instructions from the Raspberry Pi. The Arduino is normally programmed in C++ which is what I used here. The standard SPI library (SPI.h) is used. The library can be used either for main device or secondary device, but in this case is set as secondary.

The following code is used to setup SPI on the Arduino.



void setup() 

{

  // Set the Main in Secondary Out as an output

  pinMode(MISO, OUTPUT);

  // turn on SPI as a secondary

  // Set appropriate bit in SPI Control Register

  SPCR |= _BV(SPE);

}

The following code is then used to receive a byte and then set the data register to return a byte to the master controller device.



void loop () 

{

  byte in_byte;

  // SPIF indicates transmission complete (byte received)

  if ((SPSR & (1 << SPIF)) != 0)

  {

    in_byte = SPDR;

    

    // Handle the input code here

    // Set return_val to the value you want to return

    

    SPDR = return_val;

  }

In the example code then the comments inside the if statement are replaced with code to itnerpret the incoming byte and to update the return value accordingly.

For example the following line looks to see if the command has the most signficant bit set to 1 (no operation) and just echos the byte back to the main device.
if (in_byte & 0x80) SPDR = in_byte;

Python SPI code on the Raspberry Pi

The code that runs on the Raspberry Pi is written in Python using the spidev module. The setup code for SPI is handled using this code.



import spidev



spi_bus = 0

spi_device = 0



spi = spidev.SpiDev()

spi.open(spi_bus, spi_device)

spi.max_speed_hz = 1000000

This is based on using SPI Bus 0 (the pin numbers mentioned previously) and device 0 (CE0). The following code shows the transfer of two bytes from the Raspberry Pi to the Arduino.



# Send a null byte to check for value

send_byte = 0x80

rcv_byte = spi.xfer2([send_byte])

# repeat to check for a response

rcv_byte = spi.xfer2([send_byte])

data_recv = rcv_byte[0]

if (data_recv != 0x80):

    print ("Unable to communicate with Arduino "+str(data_recv))

    quit()

The binary representation of 0x80 has the first bit set to 1, so instructs the Arduino just to echo it back. The spi.xfer2 method is used to send that byte to the Arduino. The transfer simultaneously sends a byte and receives one from the Arduino. The value received at this point will be meaningless as it has not yet been set by the Arduino. A second byte is sent so that we can get the response from the Arduino. Due to this being full-duplex this response is from the first instruction which was sent.

I have used spi.xfer2 method. If you look at the documentation for they Python Spidev module then you will see that there are 3 different xfer functions called: xfer, xfer2 and xfer3. These can be used for different types of chip-select and transferring larger blocks of data. The xfer2 method works well with the Raspberry Pi and Arduino and I've deliberately avoided sending large amounts of data to keep the example as simple as possible.

Software download

The code above is just a small sample of the code that I've created for this demonstration. It should however provide the information that is needed to understand how to send communication between the Raspberry Pi and the Arduino. The code can be used to setup the Arduino as a secondary device and have any of the digital pins turned on and off (I've used pins 3 to 9 only to avoid conflicts with serial and SPI data) and to read from the same digital pins or from the Analog inputs.

Both the Raspberry Pi Python code and the Arduino CPP code is packaged into a single file tar file which you can download below.

Previous RPi Pico with SSD1306 OLED display SPI
RPi Pico with SSD1306 OLED display SPI
Next RPi Pico and Arduino I2C
RPi Pico and Arduino I2C