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.
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:
sudo raspi-config
Interfacing options – SPI
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!