Для задач робототехники, когда хочется применять вычисления на Python, использовать компьютерное зрение, ROS возникает необходимость быстрого и надежного обмена данными с микроконтроллером, который уже рулит всевозможными моторами, сервоприводами и датчиками.
Первое, о чем пришлось позаботиться - это согласование логических уровней двух устройств. Arduino работает на 5V, Raspberry на 3.3V. Для этого используется устройство LogicLevelConverter на 4 канала.
Порты для подключения на устройствах строго определены
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 сразу, и этот порт используется для указания, с каким идет обмен данными
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 сразу, и этот порт используется для указания, с каким идет обмен данными
Также к Logic level converter подключается рабочее напряжение каждого устройства и земля
Теперь к коду:
На Raspberry Pi необходимо включить SPI:
-
sudo raspi-config
-
Interfacing options - SPI
-
Включаем SPI
Далее устанавливаем библиотеку 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 <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();//выводим данные в монитор порта. Только для тестов!
//ПОТОМ ОТКЛЮЧИТЬ, Т.К. ЗАМЕДЛЯЕТ РАБОТУ ПРОГРАММЫ
}
}
Такие дела! Успехов!
Автор:
Stepan_Burmistrov