Пример связи двух плат arduino по протоколу spi

Тема в разделе "Проводная и беспроводная связь", создана пользователем issaom, 13 янв 2020.

Метки:
  1. issaom

    issaom Гуру

    Запилил пару примеров организации связи 2-х плат Ардуино по шине SPI
    SPI_NANO.jpg

    Резисторы R1 и R2 - управляют яркостью светодиодов HL3 и HL4
    Резисторы R3 и R4- управляют яркостью светодиодов HL1 и HL2
    Код мастера:

    Код (C++):
    // Данный код будет работать только на платах с чипом ATmega328P например
    // Arduino UNO, NANO
    // Он использует интрефейс SPI на пинах
    // 13 - SCK, 12 - MISO, 11 - MOSI, 10 - SS

    // Массивы могут иметь произвольную длинну
    // Но должны совпадать по количеству байт
    // Не использованные байты можно оставлять пустыми

    // Массив который отправляем ведомому
    uint8_t master_arr [2];
    // Массив корторый получаем от ведомого
    uint8_t slave_arr  [2];

    void setup (void)
    {
      // объявляем 2-й и 3-й ПИНЫ как выходы
      pinMode (5, OUTPUT);
      pinMode (3, OUTPUT);

      // Настройка SPI как мастера
      DDRB |= (1 << PB2) | (1 << PB3) | (1 << PB5);   // Настроить выводы MOSI,SS,SCK на выход          
      PORTB |= (1 << PB2);                            // Установить "1" на линии SS
      /* Enable SPI, Master, set clock rate fck/16 */
      SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
    }


    void refreshSPI ()                        // Процедура обмена массивами между платами
    {
      PORTB &= ~(1 << PB2);                   // Установить "0" на линии SS1
      SPDR = 0xFF;                            // Отправляем "пустышку" для загрузки байта slave_arr[0]
      while (!(SPSR & (1 << SPIF))) ;         // Дождаться окончания передачи
      delayMicroseconds (20);                 // Пауза чтобы успел подгрузиться байт [0] на ведомом
      // цикл по master_arr[]
      for (uint8_t i = 0; i < sizeof(master_arr); i++) {
        SPDR = master_arr[i];                 // Отправить байт
        while (!(SPSR & (1 << SPIF))) ;       // Дождаться окончания передачи
        slave_arr[i] = SPDR;                  // Принять байт
        delayMicroseconds (20);               // Пауза чтобы успел подгрузиться байт на ведомом
      }
      PORTB |= (1 << PB2);                    // Установить "1" на линии SS
    }                                         // Конец процедуры обмена


    void loop (void)
    {
      // Записываем в первый байт массива значение потенциометра N1 мастера
      master_arr [0] = analogRead(A2)/4;
      // Записываем во второй байт массива значение потенциометра N2 мастера
      master_arr [1] = analogRead(A3)/4;

      // Яркость красного светодиода N1 на мастере получаем от потенциометра N1 на SLAVE
      analogWrite(5,slave_arr[0]);
      // Яркость красного светодиода N2 на мастере получаем от потенциометра N2 на SLAVE
      analogWrite(3,slave_arr[1]);

      // производим обмен данными между мастером и слайвом
      refreshSPI ();

      delay (100);
    }
    Код ведомого:
    Код (C++):
    // Данный код будет работать только на платах с чипом ATmega328P например
    // Arduino UNO, NANO
    // Он использует интрефейс SPI на пинах
    // 13 - SCK, 12 - MISO, 11 - MOSI, 10 - SS

    // Массивы могут иметь произвольную длинну
    // Но должны совпадать по количеству байт
    // Не использованные байты можно оставлять пустыми

    // Массив корторый приходит от мастера
    volatile uint8_t master_arr [2];
    // Массив который отдаем от мастеру
    volatile uint8_t slave_arr  [2];

    // счетчик входящих байт
    volatile int16_t countSPIb = -1;

    void setup (void)
    {
      // объявляем 2-й и 3-й ПИНЫ как выходы
      pinMode (5, OUTPUT);
      pinMode (3, OUTPUT);

      Serial.begin(9600);

      // Настройка SPI как SLAVE
      DDRB |= (1 << PB4);                    // Настроить вывод MISO на выход
      SPCR |= (1 << SPIE) | (1 << SPE);      // Прерывание включено и сам SPI как SLAVE
    }

    ISR (SPI_STC_vect)                       // Прерывание SPI - пришел байт
    {
      if (countSPIb < 0) {                   // пришла "пустышка"
        countSPIb++;                         // увеличивам счетчик
        SPDR = slave_arr [countSPIb];        // подгружаем нулевой байт массива ведомого
        return;                              // выходим из процедуры
      }

      master_arr [countSPIb] = SPDR;         // получаем байт от мастера
      countSPIb++;                           // увеличиваем счетчик
      SPDR = slave_arr [countSPIb];          // отдаем байт ведомого (+1 индекс)

      if (countSPIb >= sizeof(master_arr)) { // если кончился массив
        countSPIb = -1;                      // обнуляем счетчик и ждем следующий обмен
      }
    }                                        // Конец SPI - пришел байт

    void loop (void)
    {
      if (digitalRead (SS) == HIGH) countSPIb = -1;  //сброс в случае глюка связи

      // Яркость зеленого светодиода N1 на ведомом получаем от потенциометра N1 на мастере
      analogWrite(5, master_arr[0]);
      // Яркость красного светодиода N2 на ведомом получаем от потенциометра N2 на мастере
      analogWrite(3, master_arr[1]);

      // Записываем в первый байт массива мастера значение потенциометра N1 на ведомом
      slave_arr [0] = analogRead(A2)/4;
      // Записываем во второй байт массива мастера значение потенциометра N2 на ведомом
      slave_arr [1] = analogRead(A3)/4;

      Serial.println(master_arr[1]);
      delay(10);
    }
    Пример для второго Аппаратного SPI платы Iskra Nano Pro
    Схема подключения:

    SPI_ISKRA.jpg
    Код мастера:

    Код (C++):
    // Данный код будет работать только на платах с чипом ATmega328PB например
    // https://amperka.ru/product/iskra-nano-pro-headless
    // Он использует 2-й аппаратный SPI на пинах
    // A0-MISO1, A1-SCK1, A6-SS1, A7-MOSI1
    #define PE2 2
    #define PE3 3

    // Массивы могут иметь произвольную длинну
    // Но должны совпадать по количеству байт
    // Не использованные байты можно оставлять пустыми

    // Массив который отправляем ведомому
    uint8_t master_arr [3];
    // Массив корторый получаем от ведомого
    uint8_t slave_arr  [3];

    void setup (void)
    {
      // отладка
      Serial.begin (115200);
      Serial.println ();
      master_arr [0] = 10;
      master_arr [1] = 20;
      master_arr [2] = 25;

      // Настройка SPI как мастера
      DDRE |= (1 << PE2) | (1 << PE3);        // Настроить выводы SS1,MOSI1
      DDRC |= (1 << PC1);                     // ,SCK1 на выход
      PORTE |= (1 << PE2);                    // Установить "1" на линии SS
      /* Enable SPI, Master, set clock rate fck/16 */
      SPCR1 = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
    }


    void refreshSPI ()                        // Процедура обмена массивами между платами
    {
      PORTE &= ~(1 << PE2);                   // Установить "0" на линии SS1
      SPDR1 = 0xFF;                           // Отправляем "пустышку" для загрузки 1 байта
      while (!(SPSR1 & (1 << SPIF1))) ;       // Дождаться окончания передачи
      delayMicroseconds (20);                 // Пауза чтобы успел подгрузиться байт [0] на ведомом
      // цикл по master_arr[]
      for (uint8_t i = 0; i < sizeof(master_arr); i++) {
        SPDR1 = master_arr[i];                // Отправить байт
        while (!(SPSR1 & (1 << SPIF1))) ;     // Дождаться окончания передачи
        slave_arr[i] = SPDR1;                 // Принять байт
        delayMicroseconds (20);               // Пауза чтобы успел подгрузиться байт на ведомом
      }
      PORTE |= (1 << PE2);                    // Установить "1" на линии SS
    }                                         // Конец процедуры обмена


    void loop (void)
    {
      delay (1000);
      refreshSPI ();
      // цикл по slave_arr[] - выводим в порт значения массива ведомого устройства
      for (int i = 0; i < sizeof(slave_arr); i++) {
        Serial.println(slave_arr[i], DEC);
      }
      Serial.println('-');
    }
    Код ведомого устройства:

    Код (C++):
    // Данный код будет работать только на платах с чипом ATmega328PB например
    // https://amperka.ru/product/iskra-nano-pro-headless
    // Он использует 2-й аппаратный SPI на пинах
    // A0-MISO1, A1-SCK1, A6-SS1, A7-MOSI1

    // Массивы могут иметь произвольную длинну
    // Но должны совпадать по количеству байт
    // Не использованные байты можно оставлять пустыми

    // Массив корторый приходит от мастера
    volatile uint8_t master_arr [3];
    // Массив который отдаем от мастеру
    volatile uint8_t slave_arr  [3];

    // счетчик входящих байт
    volatile int16_t countSPIb = -1;

    void setup (void)
    {

      // Настройка SPI как SLAVE
      DDRC |= (1 << PC0);                     // Настроить вывод MISO1 на выход
      SPCR1 |= (1 << SPIE1)|(1 << SPE1)  ;    // Прерывание включено и сам SPI как SLAVE

      // отладка
      slave_arr [0] = 100;
      slave_arr [1] = 200;
      slave_arr [2] = 250;
      Serial.begin (115200);
      Serial.println ();

    }

    ISR (SPI1_STC_vect)                      // Прерывание SPI1 - пришел байт
    {
      if (countSPIb < 0) {                   // пришла "пустышка"
        countSPIb++;                         // увеличивам счетчик
        SPDR1 = slave_arr [countSPIb];       // подгружаем нулевой байт массива ведомого
        return;                              // выходим из процедуры
      }

      master_arr [countSPIb] = SPDR1;        // получаем байт от мастера
      countSPIb++;                           // увеличиваем счетчик
      SPDR1 = slave_arr [countSPIb];         // отдаем байт ведомого (+1 индекс)

      if (countSPIb >= sizeof(master_arr)) { // если кончился массив
        countSPIb = -1;                      // обнуляем счетчик и ждем следующий обмен
      }
    }                                        // Конец SPI - пришел байт

    void loop (void)
    {
      if (digitalRead (SS1) == HIGH) countSPIb = -1;  //сброс в случае глюка связи

      delay(250);

      for (int i = 0; i < sizeof(master_arr); i++) {
        Serial.println(master_arr[i], DEC); //выводим в порт массив Master
      }
      Serial.println('-');

    }
    У какого какие будут замечания по коду ?
     
    alp69, SergeiL, Airbus и ещё 1-му нравится это.
  2. parovoZZ

    parovoZZ Гуру

    А скорость SPI где задается? Режим?
    Было б интересно менять мастера и слейва на лету.
     
  3. BAR__MEN

    BAR__MEN Вселенский Няш Администратор

    [​IMG]