SPI'ye Giriş
Seri Periferal Ara-Bus (SPI) arayüzü kısa mesafelerde birden fazla cihaz arasındaki iletişim için kullanılan ve yüksek bir hızda olan.
Genellikle, iletişimi başlatan ve veri aktarım hızını kontrol eden saati sağlayan tek bir "ana" cihaz vardır. Bir veya daha fazla köle olabilir. Birden fazla köle için, her biri daha sonra tarif edilen kendi "köle seçme" sinyaline sahiptir.
SPI sinyalleri
Tam gelişmiş bir SPI sisteminde, dört sinyal hattına sahip olacaksınız:
- Master Out, Slave In ( MOSI ) - Master'dan slave'e giden veriler
- Master In, Slave Out ( MISO ) - bu, slave'den master'a giden verilerdir
- Seri Saat ( SCK ) - bu hem master hem de slave örneğini bir sonraki bit için değiştirdiğinde
- Slave Select ( SS ) - bu, belirli bir slave'e "aktif" olmalarını söyler
MISO sinyaline birden fazla slave bağlandığında, Slave Select tarafından seçilinceye kadar MISO hattının üçlü hale getirilmesi (yüksek empedansta kalması) beklenir. Normalde Slave Select (SS) iddia etmek için aşağı iner. Yani aktif düşük. Belirli bir slave seçildikten sonra, MISO hattını bir çıkış olarak yapılandırmalıdır, böylece ana veriyi gönderebilir.
Bu görüntü, verilerin bir bayt gönderilirken değiştirilme şeklini gösterir:
Master'den (MOSI, SCK, SS) üç sinyal çıktı olduğunu ve birinin bir giriş olduğunu (MISO) unutmayın.
Zamanlama
Olayların sırası:
SS
iddia etmek ve köleyi harekete geçirmek için aşağı iner
SCK
Line veri hatları örneklenebilir gerektiğini belirtmek için geçiş yapar
- Veriler, ön kenardaki hem ana hem de köle tarafından örneklenir
SCK
(varsayılan saat fazını kullanarak)
- Her iki ana ve bağımlı bir sonraki bit hazırlanmak arka kenarına
SCK
değiştirerek, (varsayılan saat faz kullanılarak) MISO
/ MOSI
gerektiğinde
- İletim bittikten sonra (muhtemelen çoklu baytlar gönderildikten sonra) daha sonra yeniden
SS
etkinleştirmek için yüksek olur
Bunu not et:
- En önemli bit ilk önce gönderilir (varsayılan olarak)
- Veriler aynı anda gönderilir ve alınır (tam çift yönlü)
Veriler aynı saat darbesinde gönderilip alındığından, kölenin derhal efendiye yanıt vermesi mümkün değildir. SPI protokolleri genellikle yöneticiden bir aktarım hakkında veri talep etmesini ve ardından bir başkasına yanıt vermesini bekler.
Arduino'daki SPI kütüphanesini kullanarak, tek bir transfer yapmak kodda şöyle görünür:
byte outgoing = 0xAB;
byte incoming = SPI.transfer (outgoing);
Basit kod
Yalnızca gönderme örneği (gelen verileri yok sayarak)
#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
Sadece çıkış SPI için kablolama
Yukarıdaki seri (sadece gönderen) bir çıkış seri kaydırma yazmacını sürmek için kullanılabilir. Bunlar yalnızca çıktı aygıtlarıdır, bu nedenle gelen veriler hakkında endişelenmemize gerek yoktur. Bu durumda SS pimi "mağaza" veya "mandal" pimi olarak adlandırılabilir.
Buna örnek olarak 74HC595 seri kaydırma yazmacı ve birkaç LED'den bahseden çeşitli LED şeritleri var. Örneğin, bu 64 piksel LED ekran, bir MAX7219 yongası ile çalıştırılır:
Bu durumda pano üreticisinin biraz farklı sinyal adları kullandığını görebilirsiniz:
- DIN (Veri Girişi) MOSI (Master Out, Slave In)
- CS (Chip Select) SS'dir (Slave Select)
- CLK (Saat), SCK (Seri Saat)
Çoğu tahta benzer bir desen izleyecektir. Bazen DIN sadece DI'dir (Veri Girişi).
İşte bir başka örnek, bu sefer 7 segmentli bir LED ekran kartı (ayrıca MAX7219 yongasına dayanıyor):
Bu, diğer kart ile tamamen aynı sinyal isimlerini kullanır. Her iki durumda da, kartın sadece 5 tel, SPI için üç kablo, artı güç ve toprak gerektirdiğini görebilirsiniz.
Saat fazı ve polarite
SPI saatini örneklemenin dört yolu vardır.
SPI protokolü, saat darbelerinin kutupları üzerindeki değişikliklere izin verir. CPOL saat polaritesi ve CPHA saat fazıdır.
- Mod 0 (varsayılan) - saat normalde düşüktür (CPOL = 0) ve düşükten yükseğe (baştaki) geçiş sırasında veriler örneklenir (CPHA = 0)
- Mod 1 - saat normalde düşüktür (CPOL = 0) ve yüksekden düşüke geçiş sırasında veriler örneklenir (arka kenar) (CPHA = 1)
- Mod 2 - saat normalde yüksektir (CPOL = 1), ve yüksekten düşüğe geçişin (ön kenar) geçişinde veriler örneklenir (CPHA = 0)
- Mod 3 - saat normalde yüksektir (CPOL = 1) ve alçaktan yükseğe (geçiş kenarı) geçişte veriler örneklenir (CPHA = 1)
Bunlar bu grafikte gösterilmektedir:
Faz ve polariteyi düzeltmek için cihazınızın veri sayfasına başvurmalısınız. Genellikle saati nasıl örnekleyeceğinizi gösteren bir şema olacaktır. Örneğin, 74HC595 yongasının veri sayfasından:
Gördüğünüz gibi saat normal olarak düşüktür (CPOL = 0) ve ön kenardan örneklenir (CPHA = 0) bu nedenle bu SPI mod 0'dır.
Saat polaritesini ve fazını bu kodda değiştirebilirsiniz (yalnızca birini seçin, elbette):
SPI.setDataMode (SPI_MODE0);
SPI.setDataMode (SPI_MODE1);
SPI.setDataMode (SPI_MODE2);
SPI.setDataMode (SPI_MODE3);
Bu yöntem, Arduino IDE'nin 1.6.0 sürümlerinde kullanımdan kaldırılmıştır. Son sürümlerde, çağrıdaki saat modunu değiştirirsiniz, SPI.beginTransaction
şöyle:
SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0)); // 2 MHz clock, MSB first, mode 0
Veri sırası
Varsayılan değer ilk önce en anlamlı bittir, ancak donanıma ilk önce en az anlamlı biti işleme koymasını söyleyebilirsiniz:
SPI.setBitOrder (LSBFIRST); // least significant bit first
SPI.setBitOrder (MSBFIRST); // most significant bit first
Yine, bu Arduino IDE'nin 1.6.0 sürümlerinde kullanımdan kaldırılmıştır. Son sürümlerde, çağrıdaki bit sırasını değiştirirsiniz, SPI.beginTransaction
şöyle:
SPI.beginTransaction (SPISettings (1000000, LSBFIRST, SPI_MODE2)); // 1 MHz clock, LSB first, mode 2
hız
SPI için varsayılan ayar, 16 MHz CPU saati olduğu varsayılarak, sistem saati hızını dörde bölünmüş, yani her 250 one'de bir SPI saat darbesiyle kullanmaktır. Saat bölücüyü şu şekilde kullanarak değiştirebilirsiniz setClockDivider
:
SPI.setClockDivider (divider);
"Ayırıcı" şunlardan biridir:
- SPI_CLOCK_DIV2
- SPI_CLOCK_DIV4
- SPI_CLOCK_DIV8
- SPI_CLOCK_DIV16
- SPI_CLOCK_DIV32
- SPI_CLOCK_DIV64
- SPI_CLOCK_DIV128
16 MHz CPU saati varsayarsak, en yüksek hız her 125 ns'de "2'ye bölün" veya bir SPI saat darbesidir. Bu nedenle bir bayt iletmek için 8 x 125 ns veya 1 µs gerekir.
Bu yöntem, Arduino IDE'nin 1.6.0 sürümlerinde kullanımdan kaldırılmıştır. Son sürümlerde, SPI.beginTransaction
aramadaki aktarım hızını değiştirirsiniz, şöyle:
SPI.beginTransaction (SPISettings (4000000, MSBFIRST, SPI_MODE0)); // 4 MHz clock, MSB first, mode 0
Bununla birlikte ampirik testler, baytlar arasında iki saat atımının gerekli olduğunu göstermektedir, bu nedenle baytların zamanlanabileceği maksimum hız, her biri 1.125 µs'dir (saat bölü 2).
Özetlemek gerekirse, her bir bayt, 1 / 1.125 µs teorik maksimum aktarım hızı veren saniyede (16 MHz saat ile) maksimum hızda bir saniyede gönderilebilir (saniyede 8,888 bayt (SS SS düşük ve benzeri ayarların eklenmesi hariç) hariç ) üzerinde.
Arduino'ya bağlanma
Arduino Uno
10 - 13 arasındaki dijital pimlerle bağlanma:
ICSP başlığı ile bağlanma:
Arduino Atmega2560
50 - 52 arasındaki dijital pimlerle bağlanma:
Yukarıdaki Uno'ya benzer şekilde ICSP başlığını da kullanabilirsiniz.
Arduino Leonardo
Leonardo ve Micro, Uno ve Mega'dan farklı olarak, dijital pinlerdeki SPI pinlerini göstermiyor. Tek seçeneğiniz, yukarıda Uno için gösterildiği gibi ICSP başlık pimlerini kullanmaktır.
Çoklu köleler
Bir ana, birden fazla köle ile iletişim kurabilir (ancak bir seferde yalnızca bir tane). Bunu, bir köle için SS iddia ederek ve diğerleri için de iddia ederek yapar. SS'nin öne sürdüğü köle (genellikle DÜŞÜK anlamına gelir), MISO pimini bir çıktı olarak yapılandırır, böylece köle ve tek başına köle, anaya cevap verebilir. SS iddia edilmediğinde, diğer slave'ler gelen saat darbelerini dikkate almazlar. Böylece, her köle için bir ek sinyale ihtiyacınız vardır, bunun gibi:
Bu grafikte MISO, MOSI, SCK'nin her iki slave arasında paylaşıldığını görebilirsiniz, ancak her bir slave kendi SS (slave select) sinyaline sahiptir.
Protokoller
SPI belirtimi böyle protokoller belirtmez, bu nedenle verinin ne anlama geldiğine karar vermek tek tek master / slave eşleştirmelerine bağlıdır. Aynı anda bayt gönderip alabilmenize rağmen, alınan bayt gönderilen bayta doğrudan bir yanıt olamaz (eşzamanlı olarak toplandıkları gibi).
Bu nedenle, bir ucun bir istek göndermesi daha mantıklı olacaktır (örneğin 4, "disk dizinini listelemek" anlamına gelebilir) ve sonra tam bir cevap alana kadar transferler (belki de sadece dışarıya sıfır göndererek) yapılabilir. Yanıt, yeni bir satırla veya 0x00 karakterle sonlanabilir.
Hangi protokol dizilerinin beklediğini görmek için köle cihazınızın veri sayfasını okuyun.
SPI kölesi nasıl yapılır
Önceki örnek, Arduino'yu ana olarak gösterir ve bir ikincil cihaza veri gönderir. Bu örnek, Arduino'nun nasıl bir köle olabileceğini gösteriyor.
Donanım Kurulumu
İki Arduino Unos'u birbirine bağlanan aşağıdaki pimlerle birlikte bağlayın:
- 10 (SS)
- 11 (MOSI)
- 12 (MISO)
13 (SCK)
+ 5V (gerekirse)
- GND (sinyal dönüşü için)
Arduino Mega'da pinler 50 (MISO), 51 (MOSI), 52 (SCK) ve 53 (SS) 'dir.
Her durumda, bir ucunda MOSI diğer uçta MOSI bağlanır, sen değil değiş tokuş ediyorlar (olduğunu sen yok Mosi <-> MISO). Yazılım, MOSI'nin bir ucunu (ana uç) bir çıkış olarak ve diğer ucunu (bağımlı uç) bir giriş olarak yapılandırır.
Ana örnek
#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
Köle örneği
#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
Köle tamamen kesilerek tahrik edilir, bu nedenle başka şeyler de yapabilir. Gelen SPI verileri bir tamponda toplanır ve bir "önemli bayt" (bu durumda yeni bir satır) geldiğinde bir bayrak ayarlanır. Bu, slave 'ye girmesini ve verileri işlemeye başlamasını söyler.
SPI kullanarak master'ı slave'e bağlama örneği
Bir köleden nasıl yanıt alınır
Bir SPI master'ından bir slave'e veri gönderen yukarıdaki koddan sonra, aşağıdaki örnek, bir slave'e veri göndermeyi, onunla bir şeyler yapmasını ve bir yanıt vermeyi göstermektedir.
Master, yukarıdaki örneğe benzer. Ancak önemli bir nokta, hafif bir gecikme (20 mikrosaniye gibi bir şey) eklememiz gerektiğidir. Aksi halde, kölenin gelen verilere tepki gösterme ve onunla bir şeyler yapma şansı olmaz.
Örnekte "komut" gönderiliyor. Bu durumda "a" (bir şey ekle) veya "s" (bir şey çıkar). Bu, kölenin aslında verilerle bir şeyler yaptığını göstermek içindir.
İşlemi başlatmak için slave-select (SS) komutunu verdikten sonra, master komutu gönderir, ardından herhangi bir sayıda bayt gelir ve ardından işlemi sonlandırmak için SS'yi yükseltir.
Çok önemli bir nokta, kölenin gelen bir bayta aynı anda yanıt verememesidir. Yanıt bir sonraki baytta olmalı. Bunun nedeni, gönderilen bitlerin ve alınan bitlerin aynı anda gönderilmeleridir. Böylece dört sayıya bir şey eklemek için beş aktarmaya ihtiyacımız var, bunun gibi:
transferAndWait ('a'); // add command
transferAndWait (10);
a = transferAndWait (17);
b = transferAndWait (33);
c = transferAndWait (42);
d = transferAndWait (0);
İlk önce 10 numara için harekete geçmeyi talep ediyoruz. Ancak "a", 10'a verilen cevaba ayarlanacaktır. Sonunda 42 cevabını almak için "sahte" bir numara 0 göndereceğiz.
Master (örnek)
#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
Slave kodu, temel olarak kesme rutindeki hemen hemen her şeyi yapar (gelen SPI verileri geldiğinde çağrılır). Gelen baytı alır ve hatırlanan "komut baytına" göre ekler veya çıkarır. Yanıtın, döngü boyunca bir dahaki sefere "toplanacağına" dikkat edin. İşte bu yüzden, efendinin son cevabı alabilmek için son bir "sahte" transfer göndermesi gerekiyor.
Örneğimde, SS'nin ne zaman yükseldiğini algılamak ve kaydedilmiş komutu silmek için ana döngüyü kullanıyorum. Bu şekilde, bir sonraki işlem için SS tekrar düşük çekildiğinde, ilk bayt komut baytı olarak kabul edilir.
Daha güvenilir bir şekilde, bu bir kesinti ile yapılabilir. Yani, fiziksel olarak SS'yi kesme girişlerinden birine (örneğin Uno'da, pim 10'u (SS) pim 2'ye (kesme girişi) bağlayın veya pim 10'da pim değiştirme kesmesi kullanın.
Ardından, kesme SS'nin alçak veya yükseğe çekildiğini fark etmek için kullanılabilir.
Köle (örnek)
// 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
Örnek çıktı
Adding results:
25
32
48
57
Subtracting results:
2
9
25
34
Adding results:
25
32
48
57
Subtracting results:
2
9
25
34
Mantık analizörü çıkışı
Bu, yukarıdaki kodda gönderme ve alma arasındaki zamanlamayı gösterir:
IDE 1.6.0'da yeni işlevler
IDE'nin 1.6.0 sürümü SPI'nin çalışma şeklini bir ölçüde değiştirdi. Sen hala yapmanız gereken SPI.begin()
SSA'sını kullanmadan önce. Bu SPI donanımını kurar. Ancak şimdi, bir köle ile iletişim kurmaya başlamak üzereyken, SPI'yi (bu köle için) doğru şekilde ayarlamak için de yapıyorsunuz SPI.beginTransaction()
:
- Saat hızı
- Bit sipariş
- Saat fazı ve polarite
Köle ile iletişim kurmanız bittiğinde, onu ararsınız SPI.endTransaction()
. Örneğin:
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
Neden SPI kullanıyorsunuz?
Bir ön soru daha ekleyeceğim: SPI'yi ne zaman / neden kullandınız? Çoklu ana konfigürasyona veya çok fazla sayıda köle ihtiyacı, ölçeği I2C'ye doğru eğebilir.
Bu mükemmel bir soru. Cevaplarım:
- Bazı cihazlar (oldukça az sayıda) yalnızca SPI aktarma yöntemini destekler. Örneğin 74HC595 çıkış kaydırma yazmacı, 74HC165 giriş kaydırma yazmacı, MAX7219 LED sürücüsü ve gördüğüm oldukça az sayıda LED şeridi. Dolayısıyla, hedef aygıt yalnızca desteklediği için kullanabilirsiniz.
- SPI, Atmega328 (ve benzeri) yongalarında gerçekten en hızlı yöntemdir. Yukarıda belirtilen en yüksek oran, saniyede 888.888 bayttır. I 2 C kullanarak saniyede yalnızca 40.000 bayt elde edebilirsiniz. I 2 C'nin ek yükü oldukça önemlidir ve gerçekten hızlı bir şekilde arayüz oluşturmaya çalışıyorsanız, SPI tercih edilen seçimdir. Oldukça az sayıda yonga ailesi (örneğin, MCP23017 ve MCP23S17) aslında hem I 2 C hem de SPI'yi desteklemektedir, böylece genellikle hız ve tek bir veriyolunda birden fazla cihaza sahip olma arasında seçim yapabilirsiniz.
- SPI ve I 2 C cihazlarının her ikisi de Atmega328'in donanımında desteklenir, böylece size hız artışı sağlayacak olan I 2 C ile aynı anda SPI üzerinden transfer yapabilirsiniz .
Her iki yöntemin de yeri var. I 2 C, birçok cihazı tek bir veri yoluna (iki tel, artı toprak) bağlamanıza izin verir; bu nedenle, belki de oldukça nadiren, çok sayıda cihazı sorgulamanız gerekirse, tercih edilen seçenek olacaktır. Ancak SPI'nin hızı, hızlı bir şekilde çıktı almanız (örneğin bir LED şeridi) veya hızlı bir şekilde girmeniz (örneğin bir ADC dönüştürücüsü) gerektiren durumlar için daha uygun olabilir.
Referanslar