Arduino SPI – Serial Peripheral Interface

Az SPI buszról

A SPI (Serial Peripheral Interface) egy szinkron soros adatátviteli protokoll, amelyet mikrovezérlők használnak egymás közti vagy perifériaeszközökkel való gyors kommunikációra, rövid távolságon.

Az SPI busz egy nagysebességű egyirányú kommunikációs vonal, ezáltal a kétirányú kommunikáció egyidőben történhet, tehát az eszközök egyszerre küldhetnek és fogadhatnak adatokat.

Arduino Mega 2560 mikrokontroller kártya

Arduino Mega 2560
Arduino Mega 2560 – hirdetés

SPI adatátvitel esetén mindig van egy kontroller eszköz, általában egy mikrokontroller, amely felügyeli a kommunikációt és vezérli a periféria eszközöket. Korábban a vezérlő eszközt „Master”, míg a periféria eszközöket „Slave” néven ismerhettük, az Arduino már nem támogatja ennek a terminológiának a használatát. Újabban ezek elnevezése a”Controller” és a „Peripheral”.

Vegyük sorra az SPI busz vezetékeit

Arduino UNO - Spi busz csatlakozó csapjai

„COPI” (Controller Out Peripheral In), a Vezérlő felől az adatok perifériákra való küldésére használt vonal, korábban ennek a neve „MOSI” (Master Out Slave In) volt.

„CIPO” (Controller In, Peripheral Out), a Periféria irányából az adatok vezérlőhöz juttatására való vezeték, ez régebben a „MISO” (Master In Slave Out) volt.

„SCK” Serial Clock (SCK), a Vezérlő által generált óraimpulzusok, amelyek szinkronizálják az adatátvitelt, a soros órajel neve nem változott.

„CS” (Chip Select pin), minden egyes perifériához tartozik egy vonal, amelyet a Vezérlő használ a periféria eszközök engedélyezésére és letiltására. Alapértelmezésben ez a pin magas állapotban van. Ha egy Peripheral eszköz „CS” pinje alacsony állapotba kerül, kommunikál a vezérlővel. Korábban „SS” (Slave Select) néven volt ismert.

Szimpla SPI busz, egy Controller eszköz és egy Pheripheral eszköz között

Általában három közös vonal van az SPI buszon a Controller eszköz és a Pheriperal eszközök között, valamint a Controller felől minden egyes Pheriperal eszközhöz kapcsolódik egy vonal, amely segítségével kiválasztja a vezérlő, hogy melyik perifériával szeretne kommunikálni.

A kommunikáció ideje alatt a Controller a CS vonalat alacsony jelszinten tartja. A Controller adatokat küld a COPI (MOSI) vonalon keresztül, az SCK vonalon küldi a kommunikációhoz használt órajelet. Ha a Controller (Master) eszköz választ vár a Peripheral (Slave) eszköztől, akkor továbbra is órajeleket küld, amíg az adat meg nem érkezik a CIPO (MISO) lábon. Ez a független Slave/Peripheral konfiguráció.

SPI busz - független Slave/Peripheral konfiguráció.

A Daisy Chain konfigurációban a Controller eszköznek csak egy CS lábra van szüksége az összes Peripheral eszközzel való kommunikációhoz. A Controller a CS vonalat alacsony jelszintre húzza a kommunikáció kezdeményezéséhez, ezzel jelzi az összes Peripheral eszköznek, hogy készüljenek fel az adatok fogadására a COPI érintkezőkön. A Controller CS vonalat a kommunikáció időtartama alatt alacsony szinten tartja.

A Controller eszköz ezután adatokat küld a COPI lábon a lánc első Peripheral eszközére. A Controller az SCK vonalon küldi a kommunikációhoz használt órajelet. A Controller-től küldött adatok ezután Peripheral eszközről Peripheral eszközre áramlanak. A Controller eszközneknek elegendő órajelet kell generálni, hogy az adatok elérjék a lánc utolsó Peripheral eszközét.

Ha egy Peripheral eszköztől választ várunk, a Controller eszköz továbbra is órajelet küld, amíg az összes adat meg nem érkezik a CIPO lábon.

SPI busz - Daisy Chain konfiguráció

Az SPI könyvtárat nem kell külön telepíteni, része az arduino IDE-nek. Csak be kell illeszteni a vázlat elején.

#include <SPI.h>

SPISettings

Az SPISettings az SPI-eszköz SPI-portjának konfigurálására szolgál. Az SPISettings objektumnak 3 paramétere van, ezek a kommunikáció sebessége; a bitsorrend; valamint az óra fázisa és polaritása.

Az első paraméter az SPI átviteli sebessége, Hertzben kell megadni. Lehetővé teszi a SPI átviteli sebességének beállítását, amelyet az SPI osztály használ. Az értéke a legtöbb Arduino esetében 4, 8, 16, 32, 64, 128 vagy 256 MHz lehet.

A második paraméter a bitsorrend határozza meg, hogy az átvitel melyik bittel kezdődik (LSBFIRST vagy MSBFIRST).

A harmadik paraméter a négy átviteli mód egyike: SPI_MODE0, SPI_MODE1, SPI_MODE2, SPI_MODE3

Ezek az átviteli módok határozzák meg az adatok léptetését az órajel felfutó vagy lefutó élén (órafázis), és hogy az órajel mikor tétlen, ha magas vagy alacsony (ez az óra polaritása). 

A négy átviteli mód szerinti órajel polaritást és a fázisát láthatjuk a táblázatban:

Átviteli módÓrajel polaritása (CPOL)Órajel fázisa (CPHA)Kimenet adat eltolás
Bemenet adat rögzítés
SPI_MODE000Lefutó élFelfutó él
SPI_MODE101Felfutó élLefutó él
SPI_MODE210Felfutó élLefutó él
SPI_MODE311Lefutó élFelfutó él

A CPOL határozza meg az órajel polaritását.

A CPOL=0 az órajel, amely alaphelyzetben 0, és minden impulzus 1. Tehát a bevezető él egy felfutó él. Ha a CPOL=1, akkor az óra 1-nél tétlen, és minden ciklus 0 impulzusból áll. Ez azt jelenti, hogy a bevezető él egy lefutó él.

A CPHA határozza meg az adatbitek időzítését (azaz fázisát) az órajel impulzusokhoz képest.

CPHA=0 esetén a kimeneti oldal az előző óraciklus lefutó élén váltja az adatokat, míg a fogadó oldal az óraciklus felfutó élén (vagy röviddel utána) rögzíti az adatokat. A kimeneti oldalon az adatok érvényesek az aktuális óraciklus lefutó éléig. Az első ciklusban az első bitnek a COPI vonalon kell lennie a bevezető órajel előtt.

CPHA=1 esetén a kimeneti oldal az aktuális óraciklus felfutó élén lépteti az adatokat, míg a fogadó oldal az órajel ciklus lefutó élén (vagy röviddel azután) rögzíti az adatokat. A kimeneti oldalon az adatok érvényesek a következő óraciklus felfutó éléig. Az utolsó ciklusban a Peripheral eszköz megtartja a CIPO-sort mindaddig, amíg a Peripheral kiválasztást meg nem szüntetik.

SPI busz órajel fázisa és polaritása

Az SPISettings objektumot a SPI.beginTransaction() paramétereként kell használni .

A tartalomhoz

begin()

A begin() inicializálja az SPI buszt, ezt minden más SPI-függvény használata előtt meg kell tenni a setup() függvényben. A begin() függvénynek nincs visszatérési értéke.

void setup()
{
  SPI.begin();

  .....
}

A tartalomhoz

beginTransaction()

A beginTransaction()függvény inicializálja az SPI buszt az SPISettings objektum segítségével .

A beginTransaction() függvényt általában az SPI kommunikáció előtt hívjuk meg, hogy beállítsuk a kommunikációs paramétereket. Ezek a paraméterek magukban foglalják az SPI busz sebességet, a bitsorrendet és a kommunikációs módokat.

Ha paraméterként konstans értéket használunk, az kisebb és gyorsabb kódot eredményez.

SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));

Ha bármelyik paraméter változó, létrehozhatunk egy példányt az SPISettings objektumból a 3 beállítás változóinak tárolására.

Ezután megadhatjuk az objektum példányát az SPI.beginTransaction() függvénynek. Ez a módszer hatékonyabb lehet, ha a beállítások nem állandók.

SPISettings mySetting(speedMaximum, dataOrder, dataMode);

SPI.beginTransaction(mySettings);

A beginTransaction() függvénynek nincs visszatérési értéke.

A tartalomhoz

endTransaction()

Az SPI kommunikáció lezárásához az SPI.endTransaction() függvényt kell használni az Arduino kódjában. Ez a függvény lezárja az aktuális SPI tranzakciót, és felszabadítja az SPI hardvert az új tranzakciókhoz.

Az SPI.endTransaction() függvény használata opcionális, mivel az SPI kommunikáció automatikusan lezáródik, ha az Arduino kódja végrehajtódik. Azonban, ha az SPI kommunikáció hosszabb ideig tart, vagy ha az SPI kommunikáció közben hiba történik, akkor az SPI.endTransaction() függvény használata ajánlott a kommunikáció lezárásához és a hardver felszabadításához.

Nincs visszatérési értéke.

SPI.endTransaction();

A tartalomhoz

end()

Az SPI.end() függvény letiltja az SPI buszt (változatlanul hagyja a pin módokat). Nincs visszatérési értéke. A Controller nem tud tovább kommunikálni az adott eszközzel, amíg újra inicializálja az SPI kommunikációt.

SPI.end();

A tartalomhoz

setBitOrder()

Ez a funkció elavult! Új projektekben használjuk helyette a SPI.beginTransaction() függvényt a SPISettings objektummal a paraméterek konfigurálásához.

Az Arduino SPI könyvtárában ezt a setBitOrder() függvény határozza meg, hogy az átvitel melyik bittel kezdődik. A paramátere lehet LSBFIRST vagy MSBFIRST. A setBitOrder() függvénynek nincs visszatérési értéke.

SPI.setBitOrder(order);

A tartalomhoz

setClockDivider()

Ez a funkció elavult! Új projektekben használjuk helyette a SPI.beginTransaction() függvényt a SPISettings objektummal a paraméterek konfigurálásához.

Az SPI.setClockDivider() függvény segítségével beállíthatjuk az SPI kommunikáció sebességét.

Az SPI.setClockDivider() függvénynek egyetlen paramétere van, amely beállítja az SPI órajel osztót a rendszerórához képest. Az AVR alapú kártyákon a rendelkezésre álló osztók 2, 4, 8, 16, 32, 64 vagy 128.

Az alapértelmezett beállítás a SPI_CLOCK_DIV4, amely az SPI órajelét a rendszeróra frekvenciájának egynegyedére állítja be. Tehát egy 16MHz órajelű alaplapnál az SPI busz órajele 4 Mhz lesz.

SPI.setClockDivider(divider);

A függvény paraméterei lehetnek:

SPI_CLOCK_DIV2
SPI_CLOCK_DIV4
SPI_CLOCK_DIV8
SPI_CLOCK_DIV16
SPI_CLOCK_DIV32
SPI_CLOCK_DIV64
SPI_CLOCK_DIV128

A setClockDivider() függvénynek nincs visszatérési értéke.

A tartalomhoz

setDataMode()

Ez a funkció elavult! Új projektekben használjuk helyette a SPI.beginTransaction() függvényt a SPISettings objektummal a paraméterek konfigurálásához.

A setDateMode() függvény beállítja az SPI adatmódot, az órajel polaritását és fázisát. Paramétere a négy átviteli mód egyike: SPI_MODE0, SPI_MODE1, SPI_MODE2, SPI_MODE3

SPI.setDataMode(mode);

A tartalomhoz

transfer()

Az SPI.transfer() függvény használatával a Controller (master) küldhet adatokat a Peripheral (slave) eszköznek, és fogadhat adatokat a Peripheral eszköztől.

Az SPI.transfer() függvény használata nagyon egyszerű. A függvény egyetlen bájtot küld vagy fogad az SPI buszon. A függvény szintaxisa a következő:

byte SPI.transfer(byte data);

A függvény egyetlen bemeneti paramétere a küldendő adat, és egy bájtot ad vissza, amely a fogadott adatot tartalmazza.

// Adat küldése a Peripheral eszköznek
SPI.transfer(byte data);

// Adat fogadása a Peripheral eszköztől
byte response = SPI.transfer(byte data);

Az SPI.transfer(buffer, size) átvitel esetén a fogadott adatok a helyben tárolódnak a bufferben. Az SPI tranzakció során a bufferben lévő adatok felülíródnak a fogadott adatokkal, tehát a küldött adatok helyébe a fogadott adatok kerülnek.

A függvény két paramétert vár, a buffer-t, amelyben az adatok tárolódnak, és a size-ot, amely megadja az adatok méretét.

// Adatok elküldése az SPI buszon
byte buffer[] = {0x01, 0x02, 0x03, 0x04};
int size = sizeof(buffer);
SPI.transfer(buffer, size);
  
// Adatok fogadása az SPI buszon
byte receivedData[size];
SPI.transfer(receivedData, size);

A tartalomhoz

usingInterrupt()

Az SPI.usingInterrupt() függvény használata előnyös lehet, ha az Arduino-nak más feladatokat is el kell végeznie az SPI kommunikáció során. Az SPI kommunikáció megszakításainak kezelése lehetővé teszi az Arduino számára, hogy hatékonyabban kezelje az adatátvitelt és a hibákat.

Az SPI.usingInterrupt() függvény lehetővé teszi, hogy az Arduino megszakításokat használjon az SPI kommunikáció során. Az interruptNumber paraméter megadja a megszakítás sorszámát, általában 0 vagy 1, attól függően, hogy melyik megszakítási vonalat használjuk.

Az usingInterrupt() függvény paraméterében megadott megszakítás le lesz tiltva a beginTransaction() meghívásakor, és újra engedélyezve lesz az endTransaction() függvény meghívása után.

SPI.usingInterrupt(interruptNumber)

Az usingInterrupt() függvénynek nincs visszatérési értéke.

A tartalomhoz

SPI Példakódok

Elöször lássunk egy példát két Arduino UNO közti SPI kommunikáció megvalósításáról. Az SPI port pinjei a 13. SCK, 12. CIPO, és a 11. a COPI. A példában a 10-es pin lesz a CS vonal, ezt szabadon választhatjuk. Fontos, hogy a GND pineket is összekapcsoljuk!

Az alábbi kép alapján kössük össze a két eszközt.

SPI kommunikáció két arduino uno között

A Controller eszköz irányítja a kommunikációt, és küldi az adatokat a Perpheral eszköznek. A Perpheral eszköz csak akkor küldhet adatokat, ha a Controller eszköz kérte őket. Az SPI kommunikáció során mindkét eszköznek ugyanazokat a beállításokat kell használnia, például az adatátviteli sebességet valamint a kommunikációs módokat.

A következő egyszerű példában a Controller küld egy bájtot a Perpheral-nak, majd vár 1 másodpercet. A Perpheral fogadja az értéket, majd kiírja a Soros monitorra.

A Controller eszköz példakódja, töltsük fel az egyik Arduino UNO-ra.

#include <SPI.h>

const char chipSelectPin = 10;

void setup()
{
  pinMode(chipSelectPin, OUTPUT);
  digitalWrite(chipSelectPin, HIGH);
  SPI.begin();
  Serial.begin(9600);
}

void loop()
{
  digitalWrite(chipSelectPin, LOW);
  SPI.transfer(0x01);
  digitalWrite(chipSelectPin, HIGH);
  delay(1000);
}

Ez pedig a Peripheral eszköz kódja, ez kerül a második Arduino UNO-ra.

#include <SPI.h>

const char chipSelectPin = 10;
int data = 0;

void setup()
{
  pinMode(chipSelectPin, INPUT);
  SPI.begin();
  Serial.begin(9600);
}

void loop()
{
  if (digitalRead(chipSelectPin) == LOW)
  {
    data = SPI.transfer(0x00);
    Serial.println(data);
  }
}

A következő példa az Arduino Uno és egy 74HC595 Shift Register IC közötti SPI kommunikációt mutatja be. A 74HC595 IC az egyik legegyszerübb, legolcsóbb portbővítő megoldás.

A setup() függvényben beállítjuk az SPI kommunikációt valamint beállitjuk kimenetként az Arduino 10-es pinjét, Ez tölti be az SPI kommunikációhoz használt CS (Chip Select) vonalat, Ez a 74HC595 IC latch pinjéhez kapcsolódik.

A loop() függvényben létrehozunk egy byte változót, majd a for() ciklusban elküldjük a byte-ot a 74HC595 IC-nek, majd 1 bittel eltoljuk balra a byte bitjeit, és a következő ciklusban újraküldjük . A for() ciklus 8-szor fut le majd kezdődik elölről.

A kommunikáció megkezdéséhez a 74HC595 IC a latch pinjét alacsonyra állítjuk,hogy az IC adatokat fogadhasson, majd az SPI.transfer() függvény segítségével átvisszük az adatot. Végül a latch pin-t magasra állítjuk, hogy az átküldött adatot a 74HC595 IC kimenetén megjeleníthessük.

Arduino Uno és egy 74HC595 Shift Register IC közötti SPI kommunikáció

Lássuk a példakódot:

#include <SPI.h>

const char latchPin = 10;            // 74HC595 IC latch pin

void setup()
{
  SPI.begin();                       // SPI inicializálása
  pinMode(latchPin, OUTPUT);         // latch pin beállítása kimenetként
}

void loop()
{
  byte data = 0b00000001;            // kimeneti byte inicializálása
  for (int i = 0; i < 8; i++)
  {
    digitalWrite(latchPin, LOW);     // latch pin alacsonyra állítása
    SPI.transfer(data);              // adat átvitele SPI-n keresztül
    digitalWrite(latchPin, HIGH);    // latch pin magasra állítása
    data = data << 1;                // 1 bittel eltolás balra
    delay(300);                      // 300ms várakozás
  }
}

A 74HC595 IC hez csatlakoztatott ledek helyére kapcsolhatunk reléket is, így egy megfelelő arduino vázlattal számos eszközt kapcsolhatunk pár arduino pin segítségével.

A harmadik példában egy közös katódos 7 szegmens kijelzőt kapcsolunk a 74HC595 IC-n keresztül az SPI buszra.

A 74HC595 IC-t és az Arduino UNO-t kössük össze az előző példa alapján. A LED-ek helyére most egy 7 szegmens kijelző kerül.

74HC595 IC Q0-Q6 lábait 330ohmos ellenálláson keresztül kössük a 7 szegmens kijelző a-g lábaira és a Q7 lábat szintén egy 330 ohmos ellenálláson keresztül a 7 szegmens kijelző dp lábára.

7 szegmens kijelzőt kapcsolunk a 74HC595 IC-n keresztül az SPI buszon egy arduino unohoz

A következő vázlat 0-tól 9-ig kiírja a számjegyeket a 7 szegmens kijelzőre. A láthatóság érdekében egy másodperces késleltetést helyeztünk el a for() ciklusban a számjegyek megjelenítése között.

#include <SPI.h>


const char latchPin = 10;


byte digits[] = {
  B11111100,      // 0
  B01100000,      // 1
  B11011010,      // 2
  B11110010,      // 3
  B01100110,      // 4
  B10110110,      // 5
  B10111110,      // 6
  B11100000,      // 7
  B11111110,      // 8
  B11110110       // 9
};

void setup()
{
  pinMode(latchPin, OUTPUT);
  SPI.begin();
}

void loop()
{
  for (int i = 0; i < 10; i++)
  {
    digitalWrite(latchPin, LOW);

    SPI.transfer(digits[i]);
    digitalWrite(latchPin, HIGH);
    delay(1000);
  }
}

Ebből a pár példából láthatjuk, hogy milyen egyszerűen használhatjuk az Arduino SPI busz lehetőségeit.

A tartalomhoz

UNO R3 Mikrocontroller USB-Kábellel
UNO R3 Mikrocontroller USB-Kábellel hirdetés