SPI communication between Raspberry Pi and Arduino


For robotics tasks, when you want to use Python calculations, use computer vision, ROS, there is a need for fast and reliable data exchange with a microcontroller that already drives all kinds of motors, servos and sensors.

The first thing that had to be taken care of was matching the logical levels of the two devices. Arduino runs on 5V, Raspberry on 3.3V. For this, a 4-channel LogicLevelConverter device is used.

Logic level converter.
Logic level converter.

Ports for connection on devices are strictly defined
Arduino Uno(Nano):

  • 13 – SCK – clock pulses for the operation of the SPI protocol

  • 12 – MISO (Master Input Slave Output) – data transfer from a slave device (Arduino) to a master (Raspberry)

  • 11 – MOSI (Master Output Slave Input) – data transfer from the master (Raspberry) to the slave (Arduino)

  • 10 – CS or SS (Chip Select or Slave Select) – device selection for operation. Raspberry can communicate with 2 SPI devices at once, and this port is used to indicate which one is communicating with

Arduino Mega:

  • 52 – SCK – clock pulses for the operation of the SPI protocol

  • 50 – MISO (Master Input Slave Output) – data transfer from a slave device (Arduino) to a master (Raspberry)

  • 51 – MOSI (Master Output Slave Input) – data transfer from the master device (Raspberry) to the slave device (Arduino)

  • 53 – CS or SS (Chip Select or Slave Select) – device selection for operation. Raspberry can communicate with 2 SPI devices at once, and this port is used to indicate which one is communicating with

Raspberry PI:

  • 23 – SCK – clock pulses for the operation of the SPI protocol

  • 21 – MISO (Master Input Slave Output) – data transfer from a slave device (Arduino) to a master (Raspberry)

  • 19 – MOSI (Master Output Slave Input) – data transfer from the master (Raspberry) to the slave (Arduino)

  • 24 – CS or SS (Chip Select or Slave Select) – device selection for operation. Raspberry can communicate with 2 SPI devices at once, and this port is used to indicate which one is communicating with

Also, the operating voltage of each device and ground are connected to the Logic level converter.

Now to the code:

On the Raspberry Pi, you need to enable SPI:

  1. sudo raspi-config

  2. Interfacing options – SPI

  3. Turn on SPI

Next, install the library spidev

pip3 install spidev

And we use a code blank for data transfer

import spidev
import time
from camer2 import getCherry

def list_int_to_bytes(input_list):
    # Split list int values to list ready for transfer by SPI
    # every value from -32768 to 32767
    # will be replaced two values from -255 to 255
    # Original values must be collected by Arduino after transmission 
    output_list = []
    for int_data in input_list:
        output_list.append(int_data >> 8)
        output_list.append(int_data & 255)
    return output_list


def spi_send(txData):
    # Send and recieve 40 bytes
    N = 40
    spi = spidev.SpiDev()
    spi.open(0, 0)
    spi.max_speed_hz = 1000000
    txData = list_int_to_bytes(txData)
    txData = txData+[0]*(N-len(txData))
    rxData = []
    _ = spi.xfer2([240])  # 240 - b11110000 - start byte
    for i in range(40):
        rxData.append(spi.xfer2([txData[i]])[0])
    spi.close()
    return rxData

recieved_data = spi_send([1,2,3,4,5,6])

The spi_send function takes as input a list of up to 20 values ​​from -32768 to 32767, which are split into 40 bytes and transferred to the Arduino. In response, the function returns 40 bytes received from the Arduino

Code for Arduino:

#include <SPI.h>

#define DATA_SIZE 40
byte data[DATA_SIZE];//массив, в которые получаем исходные данные
int int_data[DATA_SIZE / 2];//массив в котором будут значения, полученные от Raspberry
byte sendData[DATA_SIZE];//массив, значения которого будут переданы на Raspberry
volatile byte counter = 0;
volatile byte in_byte = 0;
volatile byte spiTranferEnd = 0;
volatile byte spiTranferStarted = 0;

void fillSendData() {//заполняем массив числами, чтобы проверить корректность передачи
  for (byte i = 1; i < 40; i++) {
    sendData[i] = i;
  }
}

void setup() {
  Serial.begin(9600);
  pinMode(MISO, OUTPUT);
  SPCR |= _BV(SPE);//переводим SPI в режим Slave
  SPI.attachInterrupt();//включаем прерывания по SPI
  fillSendData();
}

ISR (SPI_STC_vect)//обработка прерывания, получение и передача данных
{
  in_byte = SPDR;
  if (in_byte == 240 and !spiTranferStarted) {
    spiTranferStarted = 1;
    counter = 0;
    SPDR = sendData[counter];
  }
  if (spiTranferStarted and counter > 0) {
    data[counter - 1] = in_byte;
    SPDR = sendData[counter];
  }
  counter++;

  if (counter == DATA_SIZE) {
    SPDR = sendData[counter - 1];
    counter = 0;
    spiTranferStarted = 0;
    spiTranferEnd = 1;
  }
}

void joinRecievedBytes() {//функция, которая собирает 40 байт в 20 значений, которые передавались
  for (int i = 0; i < DATA_SIZE; i += 2) {
    int_data[i / 2] = data[i] << 8 | data[i + 1];
  }
  spiTranferEnd = 0;
}

void printSpiData() {//вывод в монитор порта полученных значений
  for (int i = 0; i < DATA_SIZE / 2; i++) {
    Serial.print(int_data[i]);
    Serial.print(" ");
  }
  Serial.println();
}

void loop () {
  if (spiTranferEnd) {//если эта переменная стала равна true, значит мы получили все 40 байт
    joinRecievedBytes();//собираем из 40 байт 20 значений

    // Тут можно написать действия с массивом int_data
    // if (int_data[0]==1) {
    //    что-то делаем
    //}
    printSpiData();//выводим данные в монитор порта. Только для тестов!
    //ПОТОМ ОТКЛЮЧИТЬ, Т.К. ЗАМЕДЛЯЕТ РАБОТУ ПРОГРАММЫ
  }
}

So it goes! Good luck!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *