Arduino Uno, Mega2560, Leonardo ve benzeri kartlara referansla:
- SPI nasıl çalışır?
- SPI ne kadar hızlı?
- Bir ana ve bir köle arasında nasıl bağlantı kurabilirim?
- SPI'yi nasıl köle yapabilirim?
Lütfen dikkat: Bu bir referans soru olarak tasarlanmıştır.
Arduino Uno, Mega2560, Leonardo ve benzeri kartlara referansla:
Lütfen dikkat: Bu bir referans soru olarak tasarlanmıştır.
Yanıtlar:
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.
Tam gelişmiş bir SPI sisteminde, dört sinyal hattına sahip olacaksınız:
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.
Olayların sırası:
SS iddia etmek ve köleyi harekete geçirmek için aşağı inerSCKLine veri hatları örneklenebilir gerektiğini belirtmek için geçiş yaparSCK(varsayılan saat fazını kullanarak)SCKdeğiştirerek, (varsayılan saat faz kullanılarak) MISO/ MOSIgerektiğindeSSetkinleştirmek için yüksek olurBunu not et:
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);
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
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:
Ç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.
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.
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
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
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:
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.beginTransactionaramadaki 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.
10 - 13 arasındaki dijital pimlerle bağlanma:
ICSP başlığı ile bağlanma:
50 - 52 arasındaki dijital pimlerle bağlanma:
Yukarıdaki Uno'ya benzer şekilde ICSP başlığını da kullanabilirsiniz.
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.
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.
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.
Ö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.
İki Arduino Unos'u birbirine bağlanan aşağıdaki pimlerle birlikte bağlayın:
13 (SCK)
+ 5V (gerekirse)
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.
#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
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.
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.
#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.
// 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
Bu, yukarıdaki kodda gönderme ve alma arasındaki zamanlamayı gösterir:
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():
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
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:
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.
SPI hakkındaki sayfamda ayrıca, bit-banged SPI hakkında ayrıntılar var ve USGA'yı Atmega328 çipinde ikinci bir donanım SPI'si elde etmek için kullanıyor.
Are you going to cover the weirdness that is the Due's SPI?- Due'in SPI'si hakkında hiçbir şey bilmiyorum (genel protokolün aynı olduğunu varsaymanın dışında). Bu yönünü kapsayan bir cevap eklemek için bekliyoruz.