Обмен данными по SPI между Raspberry Pi и Arduino

9f52ace849bbb05ffcfb6a64e248e684.png

Для задач робототехники, когда хочется применять вычисления на Python, использовать компьютерное зрение, ROS возникает необходимость быстрого и надежного обмена данными с микроконтроллером, который уже рулит всевозможными моторами, сервоприводами и датчиками.

Первое, о чем пришлось позаботиться — это согласование логических уровней двух устройств. Arduino работает на 5V, Raspberry на 3.3V. Для этого используется устройство LogicLevelConverter на 4 канала.

Logic level converter.Logic level converter.

Порты для подключения на устройствах строго определены
Arduino Uno (Nano):

  • 13 — SCK — тактовые импульсы для работы протокола SPI

  • 12 — MISO (Master Input Slave Output) — передача данных от ведомого устройства (Arduino) к ведущему (Raspberry)

  • 11 — MOSI (Master Output Slave Input) — передача данных от ведущего устройства (Raspberry) к ведомому (Arduino)

  • 10 — CS или SS (Chip Select или Slave Select) — выбор устройства для работы. Raspberry может работать с 2 устройствами SPI сразу, и этот порт используется для указания, с каким идет обмен данными

  • fccc3db3b066438851da06eb1bbfc71d.jpeg

Arduino Mega:

  • 52 — SCK — тактовые импульсы для работы протокола SPI

  • 50 — MISO (Master Input Slave Output) — передача данных от ведомого устройства (Arduino) к ведущему (Raspberry)

  • 51 — MOSI (Master Output Slave Input) — передача данных от ведущего устройства (Raspberry) к ведомому (Arduino)

  • 53 — CS или SS (Chip Select или Slave Select) — выбор устройства для работы. Raspberry может работать с 2 устройствами SPI сразу, и этот порт используется для указания, с каким идет обмен данными

Raspberry PI:

  • 23 — SCK — тактовые импульсы для работы протокола SPI

  • 21 — MISO (Master Input Slave Output) — передача данных от ведомого устройства (Arduino) к ведущему (Raspberry)

  • 19 — MOSI (Master Output Slave Input) — передача данных от ведущего устройства (Raspberry) к ведомому (Arduino)

  • 24 — CS или SS (Chip Select или Slave Select) — выбор устройства для работы. Raspberry может работать с 2 устройствами SPI сразу, и этот порт используется для указания, с каким идет обмен данными

  • ab4c9a79b1017c2951c521dfa3c001d2.png

Также к Logic level converter подключается рабочее напряжение каждого устройства и земля

Теперь к коду:

На Raspberry Pi необходимо включить SPI:

  1. sudo raspi-config

  2. Interfacing options — SPI

    29e7f01c89fe0afbdd91b226446771d6.webp
  3. Включаем SPI

    6c3423682651a711b895e846d5e5dad3.webp

Далее устанавливаем библиотеку spidev

pip3 install spidev

И используем заготовку кода для передачи данных

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])

Функция spi_send принимает на вход список до 20 значений от -32768 до 32767, которые разбиваются в 40 байт и передаются в Arduino. В ответ функция возвращает 40 байт, полученных из Arduino

Код для Arduino:

#include 

#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();//выводим данные в монитор порта. Только для тестов!
    //ПОТОМ ОТКЛЮЧИТЬ, Т.К. ЗАМЕДЛЯЕТ РАБОТУ ПРОГРАММЫ
  }
}

Такие дела! Успехов!

© Habrahabr.ru