Як ви використовуєте SPI на Arduino?


44

З посиланням на дошки Arduino Uno, Mega2560, Leonardo та подібні дошки:

  • Як працює SPI?
  • Наскільки швидко SPI?
  • Як з’єднати між господарем і рабом?
  • Як зробити раба SPI?

Зверніть увагу: це розроблено як опорне запитання.


Ви можете відповісти на це пов’язане питання arduino.stackexchange.com/questions/60703/…
qwr

Відповіді:


80

Вступ до SPI

Інтерфейс послідовної периферійної шини інтерфейсу (SPI) використовується для зв'язку між декількома пристроями на коротких відстанях та на високій швидкості.

Зазвичай є один "головний" пристрій, який ініціює зв'язок і постачає годинник, який управляє швидкістю передачі даних. Може бути один або кілька рабів. Для більш ніж одного підлеглого кожен має свій власний сигнал "вибір невільників", описаний далі.


SPI-сигнали

У повномасштабній системі SPI у вас буде чотири сигнальні лінії:

  • Master Out, Slave In ( MOSI ) - це дані, що надходять від ведучого до підлеглого
  • Master In, Slave Out ( MISO ) - це дані, що переходять від підлеглого до ведучого
  • Послідовний годинник ( SCK ) - коли це перемикає і головний, і підлеглий вибірки наступного біта
  • Вибір рабовласницького типу ( СС ) - це повідомляє конкретному рабу перейти "активним"

Коли до сигналу MISO підключено декілька підлеглих, очікується, що вони три-стану (триматимуть високий опір) цієї лінії MISO до тих пір, поки вони не будуть обрані при визначенні вибору Slave Select. Зазвичай Slave Select (SS) знижується, щоб стверджувати це. Тобто, він активний низький. Після вибору певного підлеглого він повинен налаштувати рядок MISO як вихід, щоб він міг надсилати дані майстру.

Це зображення показує спосіб обміну даними в одному байті:

SPI-протокол, що показує 4 сигнали

Зверніть увагу, що три сигнали - це вихідні дані від головного (MOSI, SCK, SS), а один - вхідний (MISO).


Хронометраж

Послідовність подій:

  • SS знижується, щоб стверджувати це і активувати раба
  • SCKЛінія перемикається , щоб вказати , коли лінії даних повинні бути обрані
  • Дані вибірки відбираються як головним, так і веденим на передньому краї SCK(використовуючи тактову фазу за замовчуванням)
  • Як ведучі та ведені підготуватися до наступного біту на задній кромці SCK( з використанням фази тактовою по замовчуванню), шляхом зміни MISO/ MOSIпри необхідності
  • Як тільки передача закінчена (можливо, після того, як було надіслано кілька байтів), тоді вона SSпереходить на високу позицію, щоб її затвердити

Зауважте, що:

  • Найзначніший біт надсилається спочатку (за замовчуванням)
  • Дані надсилаються та отримуються в ту ж мить (повний дуплекс)

Оскільки дані надсилаються та приймаються в одному і тому ж тактовому імпульсі, підлеглий не може негайно реагувати на ведучого. Протоколи SPI зазвичай очікують, що ведучий запитає дані на одній передачі та отримає відповідь на наступній.

Використовуючи бібліотеку SPI в Arduino, один передача виглядає так у коді:

 byte outgoing = 0xAB;
 byte incoming = SPI.transfer (outgoing);

Зразок коду

Приклад лише надсилання (ігнорування вхідних даних):

#include <SPI.h>

void setup (void)
  {
  digitalWrite(SS, HIGH);  // ensure SS stays high
  SPI.begin ();
  } // end of setup

void loop (void)
  {
  byte c;

  // enable Slave Select
  digitalWrite(SS, LOW);    // SS is pin 10

  // send test string
  for (const char * p = "Fab" ; c = *p; p++)
    SPI.transfer (c);

  // disable Slave Select
  digitalWrite(SS, HIGH);

  delay (100);
  } // end of loop

Проводка для SPI, що працює лише на виході

Вищевказаний код (який надсилається лише) може використовуватися для ведення вихідного реєстру послідовних змін. Це пристрої, що працюють лише на виході, тому нам не потрібно турбуватися про вхідні дані. У їхньому випадку контактний штифт може бути названий штифтом "магазин" або "засувка".

Протокол SPI, що показує 3 сигнали

Прикладами цього є регістр послідовного зсуву 74HC595 та різні світлодіодні смуги, лише згадуючи про пару. Наприклад, цей світлодіодний дисплей з 64 пікселями, керований мікросхемою MAX7219:

Світлодіодний дисплей з 64 пікселями

У цьому випадку ви бачите, що виробник плати використовував дещо інші назви сигналів:

  • DIN (Data In) - MOSI (Master Out, Slave In)
  • CS (Chip Select) - це SS (Slave Select)
  • CLK (Clock) - це SCK (послідовний годинник)

Більшість дощок дотримуються подібної схеми. Іноді DIN - це просто DI (дані в).

Ось ще один приклад, цього разу 7-сегментна LED-панель дисплея (також заснована на мікросхемі MAX7219):

7-сегментний світлодіодний дисплей

Для цього використовуються точно такі ж назви сигналів, що й інші плати. В обох цих випадках ви бачите, що платі потрібно лише 5 проводів, три для SPI, плюс потужність і заземлення.


Фаза та полярність

Існує чотири способи вибірки годин SPI.

Протокол SPI дозволяє змінювати полярність тактових імпульсів. CPOL є тактовою полярністю, а CPHA - тактовою фазою.

  • Режим 0 (за замовчуванням) - годинник зазвичай низький (CPOL = 0), а дані відбираються на вибірці при переході від низького до високого (передній край) (CPHA = 0)
  • Режим 1 - годинник зазвичай низький (CPOL = 0), і дані відбирають вибірки при переході від високого до низького (кінцевий край) (CPHA = 1)
  • Режим 2 - тактовий годинник зазвичай високий (CPOL = 1), і дані відбирають вибірки при переході від високого до низького (передній край) (CPHA = 0)
  • Режим 3 - тактовий годинник зазвичай високий (CPOL = 1), і дані відбирають вибірки при переході від низького до високого (кінцевий край) (CPHA = 1)

Вони зображені на цій графіці:

Фаза та полярність SPI

Слід звернутися до таблиці даних для свого пристрою, щоб виправити фазу та полярність. Зазвичай буде схема, яка показує, як пробувати годинник. Наприклад, з таблиці даних для мікросхеми 74HC595:

74HC595 годинник

Як ви бачите, годинник зазвичай низький (CPOL = 0), і він відбирається на передньому краю (CPHA = 0), тому це режим SPI 0.

Ви можете змінити полярність та фазу годинника у такому коді (вибирайте лише, звичайно):

SPI.setDataMode (SPI_MODE0);
SPI.setDataMode (SPI_MODE1);
SPI.setDataMode (SPI_MODE2);
SPI.setDataMode (SPI_MODE3);

Цей метод застарілий у версіях 1.6.0 і далі IDE Arduino. Для останніх версій ви змінюєте годинниковий режим у SPI.beginTransactionдзвінку, наприклад:

SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));  // 2 MHz clock, MSB first, mode 0

Порядок передачі даних

Типовий біт - це найзначніший біт спочатку, однак ви можете сказати апарату, щоб спочатку обробити найменш значущий біт:

SPI.setBitOrder (LSBFIRST);   // least significant bit first
SPI.setBitOrder (MSBFIRST);   // most significant bit first

Знову ж таки, це застаріло у версіях 1.6.0 і далі для Arduino IDE. Для останніх версій ви змінюєте порядок бітів у SPI.beginTransactionдзвінку, наприклад:

SPI.beginTransaction (SPISettings (1000000, LSBFIRST, SPI_MODE2));  // 1 MHz clock, LSB first, mode 2

Швидкість

Параметр SPI за замовчуванням полягає у використанні системної тактової частоти, розділеної на чотири, тобто один тактовий імпульс SPI кожні 250 нс, припускаючи тактовий процесор 16 МГц. Ви можете змінити подільник годин, використовуючи setClockDividerтакий:

SPI.setClockDivider (divider);

Де "роздільник" є одним із:

  • SPI_CLOCK_DIV2
  • SPI_CLOCK_DIV4
  • SPI_CLOCK_DIV8
  • SPI_CLOCK_DIV16
  • SPI_CLOCK_DIV32
  • SPI_CLOCK_DIV64
  • SPI_CLOCK_DIV128

Найшвидша швидкість - "ділити на 2" або один тактовий імпульс SPI кожні 125 нс, припускаючи тактовий процесор 16 МГц. Тому для передачі одного байту знадобиться 8 * 125 нс або 1 мкс.

Цей метод застарілий у версіях 1.6.0 і далі IDE Arduino. Для останніх версій ви змінюєте швидкість передачі в SPI.beginTransactionдзвінку, наприклад:

SPI.beginTransaction (SPISettings (4000000, MSBFIRST, SPI_MODE0));  // 4 MHz clock, MSB first, mode 0

Однак емпіричне тестування показує, що необхідно мати два тактові імпульси між байтами, тому максимальна швидкість, з якою байти можуть бути згорнуті, становить 1,125 мкс у кожному (з тактовою частотою 2).

Підводячи підсумок, кожен байт може бути надісланий з максимальною швидкістю один на 1,125 мкс (з тактовою частотою 16 МГц), даючи теоретичну максимальну швидкість передачі 1 / 1,125 мкс, або 888,888 байт в секунду (без урахування накладних витрат, як встановлення SS низький і так на).


Підключення до Ардуїно

Ардуїно Уно

Підключення за допомогою цифрових штифтів 10-13:

Шпильки Arduino Uno SPI

Підключення через заголовок ICSP:

ICSP розпізнавання - Uno

Заголовок ICSP

Ардуїно Атмега2560

Підключення за допомогою цифрових штифтів 50 до 52:

Шпильки Arduino Mega2560 SPI

Ви також можете використовувати заголовок ICSP, подібний до Uno, описаний вище.

Ардуїно Леонардо

Леонардо та Мікро не піддають контактам SPI цифрові штифти, на відміну від Uno та Mega. Ваш єдиний варіант - використовувати штифти заголовків ICSP, як показано вище для Uno.


Кілька рабів

Майстер може спілкуватися з кількома рабами (однак лише один за одним). Це робиться, стверджуючи СС для одного раба і дезатверджуючи його для всіх інших. Раб, який стверджував SS (зазвичай це означає НИСЬ), конфігурує свій штифт MISO як вихід, щоб підлеглий і той самий підлеглий могли відповідати ведучому. Інші раби ігнорують будь-які вхідні тактові імпульси, якщо SS не встановлено. Таким чином, вам потрібен один додатковий сигнал для кожного раба, наприклад, такий:

Кілька рабів SPI

На цій графіці ви бачите, що MISO, MOSI, SCK розділяються між обома рабами, однак у кожного підлеглого є власний SS (вибір підлеглого).


Протоколи

Специфікація SPI не вказує протоколи як такі, тому окремі пари master / slave домовляються про те, що означають дані. Хоча ви можете надсилати та приймати байти одночасно, отриманий байт не може бути прямою відповіддю на відправлений байт (оскільки вони збираються одночасно).

Тож було б логічніше для одного кінця надіслати запит (наприклад, 4 може означати "список дискового каталогу"), а потім робити передачі (можливо, просто надсилання нулів назовні), поки він не отримає повну відповідь. Відповідь може закінчитися новим рядком або символом 0x00.

Прочитайте таблицю даних для свого підлеглого пристрою, щоб побачити, які послідовності протоколів очікують.


Як зробити раба SPI

У попередньому прикладі ардуїно показано як головний майстер, який надсилає дані на підлеглий пристрій. Цей приклад показує, як ардуїно може бути рабом.

Налаштування обладнання

З'єднайте два Arduino Unos разом із наступними штифтами, з'єднаними один з одним:

  • 10 (SS)
  • 11 (MOSI)
  • 12 (MISO)
  • 13 (SCK)

  • + 5 В (якщо потрібно)

  • GND (для повернення сигналу)

На Мега Ардуїно штифти - 50 (MISO), 51 (MOSI), 52 (SCK) і 53 (SS).

У будь-якому випадку, MOSI на одному кінці підключено до MOSI, на іншому, ви не обмінюєтесь ними (тобто у вас немає MOSI <-> MISO). Програмне забезпечення конфігурує один кінець MOSI (головний кінець) як вихід, а інший кінець (ведений кінець) як вхід.

Основний приклад

#include <SPI.h>

void setup (void)
{

  digitalWrite(SS, HIGH);  // ensure SS stays high for now

  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on
  SPI.begin ();

  // Slow down the master a bit
  SPI.setClockDivider(SPI_CLOCK_DIV8);

}  // end of setup


void loop (void)
{

  char c;

  // enable Slave Select
  digitalWrite(SS, LOW);    // SS is pin 10

  // send test string
  for (const char * p = "Hello, world!\n" ; c = *p; p++)
    SPI.transfer (c);

  // disable Slave Select
  digitalWrite(SS, HIGH);

  delay (1000);  // 1 seconds delay
}  // end of loop

Раб-приклад

#include <SPI.h>

char buf [100];
volatile byte pos;
volatile bool process_it;

void setup (void)
{
  Serial.begin (115200);   // debugging

  // turn on SPI in slave mode
  SPCR |= bit (SPE);

  // have to send on master in, *slave out*
  pinMode (MISO, OUTPUT);

  // get ready for an interrupt
  pos = 0;   // buffer empty
  process_it = false;

  // now turn on interrupts
  SPI.attachInterrupt();

}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register

  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;

    // example: newline means time to process buffer
    if (c == '\n')
      process_it = true;

    }  // end of room available
}  // end of interrupt routine SPI_STC_vect

// main loop - wait for flag set in interrupt routine
void loop (void)
{
  if (process_it)
    {
    buf [pos] = 0;
    Serial.println (buf);
    pos = 0;
    process_it = false;
    }  // end of flag set

}  // end of loop

Раб повністю керований перериванням, тому він може робити інші речі. Дані SPI, що надходять, збираються в буфер, а прапор встановлюється, коли надходить "значний байт" (у цьому випадку новий рядок). Це сповіщає підлеглого ввімкнутись і почати обробляти дані.

Приклад підключення ведучого до підлеглого за допомогою SPI

Arduino SPI майстер і раб


Як отримати відповідь від раба

Виходячи з коду вище, який надсилає дані від ведучого SPI до підлеглого, у наведеному нижче прикладі показано надсилання даних на підлеглий, змусивши щось зробити з ним, і повернути відповідь.

Майстер аналогічний прикладу вище. Однак важливим моментом є те, що нам потрібно додати невелику затримку (щось на зразок 20 мікросекунд). Інакше у рабовласництва немає шансу реагувати на вхідні дані та робити щось із цим.

Приклад показує надсилання "команди". У цьому випадку "a" (додати щось) або "s" (відняти щось). Це означає, що раб насправді щось робить із даними.

Після затвердження slave-select (SS) для ініціювання транзакції, ведучий надсилає команду, а потім будь-яку кількість байтів, а потім піднімає SS для припинення транзакції.

Дуже важливим моментом є те, що підлеглий не може відповісти на вхідний байт в той же момент. Відповідь має бути в наступному байті. Це відбувається тому, що біти, що надсилаються, і біти, які отримуються, надсилаються одночасно. Таким чином, щоб додати щось до чотирьох чисел, нам потрібно п’ять переказів, наприклад:

transferAndWait ('a');  // add command
transferAndWait (10);
a = transferAndWait (17);
b = transferAndWait (33);
c = transferAndWait (42);
d = transferAndWait (0);

Спочатку ми вимагаємо дії на номер 10. Але ми не отримаємо відповіді до наступної передачі (такої для 17). Однак відповідь "a" буде встановлена ​​на 10. Зрештою, ми нарешті надсилаємо "фіктивний" номер 0, щоб отримати відповідь на номер 42.

Майстер (приклад)

  #include <SPI.h>

  void setup (void)
    {
    Serial.begin (115200);
    Serial.println ();

    digitalWrite(SS, HIGH);  // ensure SS stays high for now
    SPI.begin ();

    // Slow down the master a bit
    SPI.setClockDivider(SPI_CLOCK_DIV8);
    }  // end of setup

  byte transferAndWait (const byte what)
    {
    byte a = SPI.transfer (what);
    delayMicroseconds (20);
    return a;
    } // end of transferAndWait

  void loop (void)
    {

    byte a, b, c, d;

    // enable Slave Select
    digitalWrite(SS, LOW);

    transferAndWait ('a');  // add command
    transferAndWait (10);
    a = transferAndWait (17);
    b = transferAndWait (33);
    c = transferAndWait (42);
    d = transferAndWait (0);

    // disable Slave Select
    digitalWrite(SS, HIGH);

    Serial.println ("Adding results:");
    Serial.println (a, DEC);
    Serial.println (b, DEC);
    Serial.println (c, DEC);
    Serial.println (d, DEC);

    // enable Slave Select
    digitalWrite(SS, LOW);

    transferAndWait ('s');  // subtract command
    transferAndWait (10);
    a = transferAndWait (17);
    b = transferAndWait (33);
    c = transferAndWait (42);
    d = transferAndWait (0);

    // disable Slave Select
    digitalWrite(SS, HIGH);

    Serial.println ("Subtracting results:");
    Serial.println (a, DEC);
    Serial.println (b, DEC);
    Serial.println (c, DEC);
    Serial.println (d, DEC);

    delay (1000);  // 1 second delay
    }  // end of loop

Код веденого в основному робить майже все в режимі переривання (викликається при надходженні даних SPI). Він бере вхідний байт і додає або віднімає відповідно до запам'ятовуваного "командного байту". Зауважте, що відповідь буде "зібрана" наступного разу через цикл. Ось чому майстер повинен надіслати одну остаточну передачу "манекена", щоб отримати остаточну відповідь.

У своєму прикладі я використовую основний цикл, щоб просто визначити, коли SS піднімається високо, і очистити збережену команду. Таким чином, коли SS знову знижується для наступної транзакції, перший байт вважається командним байтом.

Більш надійно це було б зроблено з перервою. Тобто, ви фізично підключите SS до одного з входів переривання (наприклад, на Uno, підключіть штифт 10 (SS) до контакту 2 (вхід переривання) або використаєте переривання зміни штифта на штирі 10.

Тоді переривання можна використовувати, щоб помітити, коли СС витягується низько або високо.

Раб (приклад)

// what to do with incoming data
volatile byte command = 0;

void setup (void)
  {

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // turn on interrupts
  SPCR |= _BV(SPIE);

  }  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
  {
  byte c = SPDR;

  switch (command)
    {
    // no command? then this is the command
    case 0:
      command = c;
      SPDR = 0;
      break;

    // add to incoming byte, return result
    case 'a':
      SPDR = c + 15;  // add 15
      break;

    // subtract from incoming byte, return result
    case 's':
      SPDR = c - 8;  // subtract 8
      break;

    } // end of switch

  }  // end of interrupt service routine (ISR) SPI_STC_vect

void loop (void)
  {

  // if SPI not active, clear current command
  if (digitalRead (SS) == HIGH)
    command = 0;
  }  // end of loop

Приклад виведення

Adding results:
25
32
48
57
Subtracting results:
2
9
25
34
Adding results:
25
32
48
57
Subtracting results:
2
9
25
34

Вихід логічного аналізатора

Тут відображаються терміни між відправленням та отриманням у наведеному вище коді:

SPI майстер і рабські терміни


Нова функціональність в IDE 1.6.0 і далі

Версія 1.6.0 IDE значною мірою змінила спосіб роботи SPI. Ви все ще потрібно зробити SPI.begin() , перш ніж використовувати SPI. Це налаштовує обладнання SPI. Однак тепер, коли ви збираєтеся почати спілкування з веденим ви також зробити , SPI.beginTransaction()щоб налаштувати SPI (для цього ВУ) з правильним:

  • Тактова швидкість
  • Порядок розрядів
  • Фаза та полярність

Коли ви закінчите спілкування з рабом, ви телефонуєте SPI.endTransaction(). Наприклад:

SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));
digitalWrite (SS, LOW);        // assert Slave Select
byte foo = SPI.transfer (42);  // do a transfer
digitalWrite (SS, HIGH);       // de-assert Slave Select
SPI.endTransaction ();         // transaction over

Навіщо використовувати SPI?

Я додам ще одне попереднє запитання: коли / навіщо використовувати SPI? Необхідність конфігурації мультимайстра або дуже велика кількість рабів нахилить шкалу до I2C.

Це відмінне запитання. Мої відповіді:

  • Деякі пристрої (досить багато) підтримують лише метод передачі SPI. Наприклад, регістр зсуву 74HC595, регістр зсуву входу 74HC165, світлодіодний драйвер MAX7219 та досить багато світлодіодних смужок, які я бачив. Отже, ви можете використовувати його, оскільки цільовий пристрій підтримує його лише.
  • SPI - це дійсно найшвидший метод, доступний на чіпах Atmega328 (і подібних). Найшвидша швидкість, процитована вище, - 888,888 байт в секунду. Використовуючи I 2 C, ви можете отримати близько 40 000 байт в секунду. Накладні витрати на I 2 C досить суттєві, і якщо ви намагаєтеся зробити інтерфейс дуже швидко, SPI є кращим вибором. Досить мало сімей чипів (наприклад, MCP23017 та MCP23S17) насправді підтримують як I 2 C, так і SPI, тому ви часто можете обирати між швидкістю та можливістю наявності декількох пристроїв на одній шині.
  • Пристрої SPI та I 2 C підтримуються апаратно на Atmega328, тому можна було б робити передачу через SPI одночасно з I 2 C, що дасть вам прискорити швидкість.

Обидва методи мають своє місце. I 2 C дозволяє підключити багато пристроїв до однієї шини (два дроти плюс земля), тому було б кращим вибором, якщо вам потрібно було б допитати значну кількість пристроїв, можливо, досить рідко. Однак швидкість SPI може бути більш актуальною для ситуацій, коли потрібно швидко виводити (наприклад, світлодіодна смуга) або швидко вводити (наприклад, перетворювач АЦП).


Список літератури


Ви збираєтесь висвітлити дивацтво, яке є SPI Due? Де конфігурація порту SPI прив’язана до використовуваного штифта SS, і там (IIRC) 4 апаратні SS-штифти призначені порту SPI?
Majenko

Інший пункт щодо вибору: іноді у вас дійсно немає вибору, оскільки датчик, який ви хочете / потрібно використовувати, доступний лише як I2C.
Ігор Стоппа

Are you going to cover the weirdness that is the Due's SPI?- Я не знаю нічого про SPI Due (крім припущення, що загальний протокол однаковий). Ви можете додати відповідь, що висвітлює цей аспект.
Нік Гаммон

Коли вийде аудіокнига цієї відповіді та чи читатимете ви її самі?)
AMADANON Inc.

1
@AMADANONInc. Можливо, музичне відео? Або анімація? Я не впевнений, чи мій австралійський акцент був би зрозумілим. : P
Нік Гаммон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.