Для задач робототехники, когда хочется применять вычисления на 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 сразу, и этот порт используется для указания, с каким идет обмен данными

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:

  1. sudo raspi-config

  2. Interfacing options - SPI

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

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

Комментарии (19)


  1. Aquahawk
    01.01.2023 22:19
    +4

    Вопрос не по софту, а по аппаратаной части, как вы крепите такие платы на основание?


    1. Stepan_Burmistrov Автор
      02.01.2023 06:47
      +1

      Ардуино и logicLevelConverter можно распаять на зеленой макетке, если "по-быстрому". Расберри имеет штатные крепления. Платы между собой коннекторами DuPont


      1. Aquahawk
        02.01.2023 10:24

        Штатные крепления отверстия это хорошо. И допустим ардуину распаяли, а дальше, воо есть платы с отверстиями, как их крепить в корпус? Дело в том что крепление по типу материнских плат на стойки очень непросто выполнить, ибо точность сверления под стойки должна быть ну очень высокой, ручным инструментом нормально не делается.


        1. ToSHiC
          02.01.2023 11:33
          +2

          Да ну не придумывайте, нету ничего такого сложного. Тёмным маркером рисуете, потом поверх процарапываете разметку, где сверлить (можно циркулем, можно штангенциркулем), керном керните центры отверстий и дальше 3мм сверлом сверлите. Керном пользоваться обязательно, в этом секрет успеха.


          1. Aquahawk
            02.01.2023 12:27

            Мне нужно закрепить несколько десятков плат, это пару сотен отверстий, слишком много усилий на такой метод, уже пробовал.


            1. ToSHiC
              02.01.2023 13:59
              +1

              Платы одинаковые? Если да - то сделайте кондуктор хоть из фанеры и сверлите в удовольствие, повторяемость неплохая будет.


              1. Aquahawk
                02.01.2023 14:09

                Нескольких разных типов. Да кондукторы вариант, скорее всего так и буду делать, но ещё рассматриваю вариант крепления в мягкое основание саморезами через дистанционные втулки. Вопрос технологичности производства. Странно что очень много информации как собрать схемы на соплях, как развести платы, а как доводить это до конечного устройства информации в разы меньше.


        1. Stepan_Burmistrov Автор
          02.01.2023 18:07

          У меня есть лазерный резак по фанере 3д-принтер. Варианты:

          1. Корпус из фанеры и крепление винтами(возможно пластик) через проставки

          2. Корпус из пластика на принтере. Крепление саморезами с плоской шляпкой

          3. Корпус из пластика и вплавление резьбовых вставок из латуни

          4. Корпус из металла, тогда шаблон из фанеры и по нему сверлить, как уже выше предлагали


          1. Aquahawk
            02.01.2023 18:12
            +1

            Да всё это включено в перечень рассматриваемых вариантов. Мне не нравится применение горючих материалов, и найти достаточно большие листы толстого негорючего пластика тоже сложно. Но спасибо, как минимум я понимаю что не только мне в голову пришли такие варианты и, похоже, каких-то очевидных варианов я не пропустил.


  1. PKav
    01.01.2023 22:30

    SPI так себе выбор. А что если слейву срочно захочется что-то сказать? Например, сообщить об ошибке, чтобы вся система максимально оперативно остановилась? Тут по всем параметрам лучше подойдет CAN, а если без дополнительного оборудования - UART.


    1. AquariusStar
      01.01.2023 22:39
      +2

      Обычно вешают уже дополнительную линию в качестве прерывания с общей подтяжкой. Просто надо смотреть, какие скорости передачи нужны.


    1. Stepan_Burmistrov Автор
      02.01.2023 06:53
      +4

      Слейв в данной конфигурации может опрашиваться постоянно и сообщать все, что угодно. Если в spi_send передать пустой список, то мы не передавая команд видим все, что хочет сказать ардуино.

      Uart мне показался менее стабильным.

      CAN-идея хорошая но штатно не поддерживается не распберри, не ардуино. Я-преподаватель и мне хочется делать достаточно простые в реализации решения.


      1. PKav
        02.01.2023 14:33
        +1

        Тогда уж I2C. Те же 2 провода, но не придется тянуть CS до каждого слейва. Плюс детекция отключения слейва от шины, когда он ACK в ответ на адрес не пришлет.


        1. Stepan_Burmistrov Автор
          02.01.2023 18:10

          I2C, как вариант.

          Я пока не разбирался, как arduino в режиме слейв работает. Но идея существует, да.


  1. Nahrenako
    02.01.2023 02:17
    +1

    А как насчет трехпроводного SPI с двунаправленной шиной данных?

    Есть идеи как это реализовать на Rasperry PI? Гугл говорит что задача не тривиальная.


    1. Stepan_Burmistrov Автор
      02.01.2023 06:58
      +2

      Если обмен должен инициировать именно слейв, как сказал AquariusStar, в комментарии выше, можно линию с прерыванием сделать. Других идей пока не приходило


  1. firehacker
    02.01.2023 18:58

    Ну как же написать статью на тему электроники, транзисторов, пинов, уровней напряжения, и не вставить сюда упоминание Python-а... Вселенная же схлопнется...


  1. sami777
    03.01.2023 12:43

    Вот эта платка для согласования уровней идея так себе. Может с импульсами в сотни килогерц она еще и будет работать, но выше уже не годится. Нужны пуш-пульные схемы.


    1. Stepan_Burmistrov Автор
      03.01.2023 17:50

      Интересно, у меня тоже были сомнения, но на скорости 1МГц прекрасно работает несколько таких сборок. Пока все стабильно