Arduino Uno, Mega2560, लियोनार्डो और इसी तरह के बोर्डों के संदर्भ में:
- एसपीआई कैसे काम करता है?
- SPI कितनी तेज है?
- मैं एक स्वामी और दास के बीच कैसे जुड़ूं?
- मैं एक SPI गुलाम कैसे बनाऊं?
कृपया ध्यान दें: यह एक संदर्भ प्रश्न के रूप में है।
Arduino Uno, Mega2560, लियोनार्डो और इसी तरह के बोर्डों के संदर्भ में:
कृपया ध्यान दें: यह एक संदर्भ प्रश्न के रूप में है।
जवाबों:
सीरियल परिधीय इंटरफेस बस (एसपीआई) इंटरफ़ेस उच्च गति से कम दूरी पर कई उपकरणों के बीच संचार के लिए प्रयोग किया जाता है, और।
आमतौर पर एक एकल "मास्टर" डिवाइस है, जो संचार शुरू करता है और उस घड़ी की आपूर्ति करता है जो डेटा ट्रांसफर दर को नियंत्रित करता है। एक या अधिक दास हो सकते हैं। एक से अधिक दासों के लिए, प्रत्येक का अपना "दास चयन" संकेत होता है, जिसे बाद में वर्णित किया गया है।
एक पूर्ण विकसित SPI प्रणाली में आपके पास चार सिग्नल लाइनें होंगी:
जब कई दास MISO सिग्नल से जुड़े होते हैं तो उनसे त्रिकोणीय स्थिति की उम्मीद की जाती है (उच्च प्रतिबाधा रखें) कि MISO लाइन तब तक चुनी जाती है जब तक कि वे स्लेव सेलेक्ट नहीं हो जाते। आम तौर पर स्लेव सेलेक्ट (SS) का जोर कम होता है। यही है, यह सक्रिय कम है। एक बार जब एक विशेष दास का चयन किया जाता है, तो उसे MISO लाइन को आउटपुट के रूप में कॉन्फ़िगर करना चाहिए ताकि वह मास्टर को डेटा भेज सके।
इस छवि से पता चलता है कि डेटा का आदान-प्रदान एक बाइट के रूप में किया जाता है:
ध्यान दें कि तीन सिग्नल मास्टर (MOSI, SCK, SS) से आउटपुट हैं और एक इनपुट (MISO) है।
घटनाओं का क्रम है:
SS
यह जोर देने और दास को सक्रिय करने के लिए कम हो जाता हैSCK
लाइन से संकेत मिलता है जब डेटा लाइनों नमूना किया जाना चाहिए टॉगलSCK
(डिफ़ॉल्ट घड़ी चरण का उपयोग करके)SCK
MISO
MOSI
SS
इसे डी-एसेर करने के लिए उच्च जाता हैध्यान दें कि:
क्योंकि डेटा को उसी घड़ी पल्स पर भेजा और प्राप्त किया जाता है, इसलिए दास के लिए तुरंत मास्टर को जवाब देना संभव नहीं है। एसपीआई प्रोटोकॉल आमतौर पर मास्टर से एक ट्रांसमिशन पर डेटा का अनुरोध करने की उम्मीद करते हैं, और बाद में एक प्रतिक्रिया प्राप्त करते हैं।
Arduino पर SPI लाइब्रेरी का उपयोग करते हुए, एक ही हस्तांतरण कोड में इस तरह दिखता है:
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
उपरोक्त कोड (जो केवल भेजता है) का उपयोग आउटपुट सीरियल शिफ्ट रजिस्टर को चलाने के लिए किया जा सकता है। ये आउटपुट-ओनली डिवाइस हैं, इसलिए हमें किसी भी आने वाले डेटा के बारे में चिंता करने की आवश्यकता नहीं है। उनके मामले में एसएस पिन को "स्टोर" या "लैच" पिन कहा जा सकता है।
इसका उदाहरण 74HC595 सीरियल शिफ्ट रजिस्टर और विभिन्न एलईडी स्ट्रिप्स हैं, बस एक जोड़े का उल्लेख करना है। उदाहरण के लिए, MAX7219 चिप द्वारा संचालित यह 64 पिक्सेल एलईडी डिस्प्ले:
इस मामले में आप देख सकते हैं कि बोर्ड निर्माता ने थोड़े अलग संकेत नामों का उपयोग किया है:
अधिकांश बोर्ड एक समान पैटर्न का पालन करेंगे। कभी-कभी DIN सिर्फ DI (डेटा इन) होता है।
यहाँ एक और उदाहरण है, इस बार एक 7-खंड एलईडी डिस्प्ले बोर्ड (MAX7219 चिप पर आधारित):
यह अन्य बोर्ड की तरह बिल्कुल उसी सिग्नल नामों का उपयोग करता है। इन दोनों मामलों में आप देख सकते हैं कि बोर्ड को केवल 5 तारों की आवश्यकता है, तीन SPI, प्लस पावर और ग्राउंड के लिए।
एसपीआई घड़ी का नमूना लेने के चार तरीके हैं।
एसपीआई प्रोटोकॉल घड़ी की दालों की ध्रुवता पर बदलाव के लिए अनुमति देता है। CPOL घड़ी ध्रुवीयता है, और CPHA घड़ी चरण है।
इनका वर्णन इस ग्राफिक में किया गया है:
आपको चरण और ध्रुवता को सही करने के लिए अपने डिवाइस के लिए डेटाशीट का संदर्भ लेना चाहिए। आमतौर पर एक आरेख होगा जो दिखाता है कि घड़ी का नमूना कैसे लिया जाए। उदाहरण के लिए, 74HC595 चिप के लिए डेटशीट से:
जैसा कि आप देख सकते हैं कि घड़ी सामान्य रूप से कम (CPOL = 0) है और इसे प्रमुख किनारे (CPHA = 0) पर नमूना लिया गया है इसलिए यह SPI मोड 0 है।
आप इस तरह कोड में घड़ी की ध्रुवता और चरण को बदल सकते हैं (केवल एक को चुनें, निश्चित रूप से):
SPI.setDataMode (SPI_MODE0);
SPI.setDataMode (SPI_MODE1);
SPI.setDataMode (SPI_MODE2);
SPI.setDataMode (SPI_MODE3);
यह विधि Arduino IDE के बाद के संस्करणों में 1.6.0 में निकाली गई है। हाल के संस्करणों के लिए आप 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
फिर से, यह Arduino IDE के 1.6.0 संस्करणों में अपग्रेड किया गया है। हाल के संस्करणों के लिए आप SPI.beginTransaction
कॉल में बिट ऑर्डर बदलते हैं , जैसे:
SPI.beginTransaction (SPISettings (1000000, LSBFIRST, SPI_MODE2)); // 1 MHz clock, LSB first, mode 2
एसपीआई के लिए डिफ़ॉल्ट सेटिंग सिस्टम घड़ी की गति को चार से विभाजित करने के लिए उपयोग करना है, अर्थात, प्रत्येक 250 एनएस में एक एसपीआई क्लॉक पल्स, एक 16 मेगाहर्ट्ज सीपीयू घड़ी मानकर। आप setClockDivider
इस तरह का उपयोग करके घड़ी विभक्त को बदल सकते हैं :
SPI.setClockDivider (divider);
जहां "विभक्त" में से एक है:
सबसे तेज़ दर है "विभाजित करके 2" या एक एसपीआई घड़ी पल्स हर 125 एनएस, एक 16 मेगाहर्ट्ज सीपीयू घड़ी मान। इसलिए यह एक बाइट संचारित करने के लिए 8 * 125 ns या 1 *s लगेगा।
यह विधि Arduino IDE के बाद के संस्करणों में 1.6.0 में निकाली गई है। हाल के संस्करणों के लिए आप SPI.beginTransaction
कॉल में स्थानांतरण गति को बदलते हैं , जैसे:
SPI.beginTransaction (SPISettings (4000000, MSBFIRST, SPI_MODE0)); // 4 MHz clock, MSB first, mode 0
हालाँकि अनुभवजन्य परीक्षण से पता चलता है कि बाइट्स के बीच दो घड़ी की दालों का होना आवश्यक है, इसलिए बाइट्स को जिस अधिकतम दर पर देखा जा सकता है, वह प्रत्येक का 1.125 (है (2 के क्लॉक डिवाइडर के साथ)।
संक्षेप में, प्रत्येक बाइट को 1.125 125 प्रति (16 मेगाहर्ट्ज घड़ी के साथ) प्रति 1 / 1.125, की सैद्धांतिक अधिकतम अंतरण दर, या 888,888 बाइट प्रति सेकंड (ओवरहेड को छोड़कर एसएस कम और इतने पर छोड़कर) की अधिकतम दर पर भेजा जा सकता है। पर)।
डिजिटल पिंस 10 से 13 के माध्यम से कनेक्ट करना:
ICSP हेडर के माध्यम से कनेक्ट करना:
50 से 52 तक डिजिटल पिन के माध्यम से जुड़ना:
आप ऊपर Uno के समान ICSP हैडर का भी उपयोग कर सकते हैं।
लियोनार्डो और माइक्रो, यूआई और मेगा के विपरीत, डिजिटल पिंस पर एसपीआई पिन को उजागर नहीं करते हैं। आपका एकमात्र विकल्प आईसीएसपी हेडर पिन का उपयोग करना है, जैसा कि ऊनो के लिए ऊपर वर्णित है।
एक मास्टर कई दासों के साथ संवाद कर सकता है (हालांकि एक समय में केवल एक ही)। यह एसएस को एक गुलाम के रूप में बताकर और बाकी सभी के लिए यह दावा करता है। जिस दास ने एसएस का दावा किया है (आमतौर पर इसका मतलब है कि कम है) अपने MISO पिन को आउटपुट के रूप में कॉन्फ़िगर करता है ताकि गुलाम, और वह गुलाम अकेले ही मास्टर को जवाब दे सके। अन्य दास किसी भी आने वाली घड़ी दालों की उपेक्षा करते हैं यदि एसएस मुखर नहीं है। इस प्रकार आपको प्रत्येक दास के लिए एक अतिरिक्त संकेत की आवश्यकता है, जैसे:
इस ग्राफिक में आप देख सकते हैं कि MISO, MOSI, SCK दोनों दासों के बीच साझा किए जाते हैं, हालांकि प्रत्येक दास का अपना SS (दास चयन) संकेत होता है।
SPI कल्पना प्रोटोकॉल को इस तरह निर्दिष्ट नहीं करती है, इसलिए यह व्यक्तिगत मास्टर / दास की जोड़ी पर निर्भर है कि डेटा का क्या अर्थ है। जब तक आप एक साथ बाइट भेज और प्राप्त कर सकते हैं, प्राप्त बाइट भेजे गए बाइट का सीधा जवाब नहीं हो सकता है (जैसा कि उन्हें एक साथ इकट्ठा किया जा रहा है)।
इसलिए एक अनुरोध भेजने के लिए एक छोर के लिए यह अधिक तर्कसंगत होगा (उदाहरण। 4 का मतलब "डिस्क निर्देशिका को सूचीबद्ध कर सकता है") और फिर पूर्ण प्रतिक्रिया प्राप्त होने तक स्थानांतरण (शायद सिर्फ शून्य को बाहर की ओर भेजना) करें। प्रतिक्रिया एक नई रेखा या 0x00 वर्ण के साथ समाप्त हो सकती है।
अपने गुलाम डिवाइस के लिए डेटाशीट पढ़ें कि यह क्या प्रोटोकॉल अनुक्रम की उम्मीद करता है।
पहले का उदाहरण अरुडिनो को स्वामी के रूप में दर्शाता है, एक दास डिवाइस को डेटा भेज रहा है। यह उदाहरण दिखाता है कि कैसे Arduino गुलाम हो सकता है।
एक दूसरे से जुड़े निम्नलिखित पिन के साथ दो Arduino Unos कनेक्ट करें:
13 (SCK)
+ 5 v (यदि आवश्यक हो)
Arduino Mega पर, पिंस 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 मास्टर से डेटा भेजता है, नीचे उदाहरण एक गुलाम को डेटा भेजने से पता चलता है, यह उसके साथ कुछ कर रहा है, और एक प्रतिक्रिया लौटाता है।
गुरु ऊपर के उदाहरण के समान है। हालांकि एक महत्वपूर्ण बिंदु यह है कि हमें थोड़ी देरी (20 माइक्रोसेकंड की तरह कुछ) जोड़ने की आवश्यकता है। अन्यथा दास के पास आने वाले डेटा पर प्रतिक्रिया करने और उसके साथ कुछ करने का मौका नहीं होता है।
उदाहरण "कमांड" भेजना दिखाता है। इस स्थिति में "a" (कुछ जोड़ें) या "s" (कुछ घटाएँ)। यह यह दिखाना है कि दास वास्तव में डेटा के साथ कुछ कर रहा है।
लेन-देन शुरू करने के लिए दास-चयन (एसएस) का दावा करने के बाद, मास्टर आदेश भेजता है, उसके बाद किसी भी संख्या में बाइट्स, और फिर लेनदेन को समाप्त करने के लिए एसएस को उठाता है।
एक बहुत महत्वपूर्ण बिंदु यह है कि दास एक ही समय में आने वाली बाइट का जवाब नहीं दे सकता है। प्रतिक्रिया अगले बाइट में होना है। ऐसा इसलिए है क्योंकि जो बिट्स भेजे जा रहे हैं, और जो बिट्स प्राप्त हो रहे हैं, उन्हें एक साथ भेजा जा रहा है। इस प्रकार कुछ संख्याओं को जोड़ने के लिए हमें पाँच हस्तांतरणों की आवश्यकता है, जैसे:
transferAndWait ('a'); // add command
transferAndWait (10);
a = transferAndWait (17);
b = transferAndWait (33);
c = transferAndWait (42);
d = transferAndWait (0);
पहले हम 10 नंबर पर कार्रवाई का अनुरोध करते हैं। लेकिन हमें अगले ट्रांसफर (17 के लिए एक) तक कोई प्रतिक्रिया नहीं मिलती है। हालाँकि "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
दास के लिए कोड मूल रूप से रुकावट दिनचर्या में लगभग सब कुछ करता है (जब आने वाले एसपीआई डेटा आता है)। यह आने वाली बाइट लेता है, और याद किए गए "कमांड बाइट" के अनुसार जोड़ या घटाता है। ध्यान दें कि अगली बार लूप के माध्यम से प्रतिक्रिया "एकत्र" की जाएगी। यही कारण है कि मास्टर को अंतिम उत्तर पाने के लिए एक अंतिम "डमी" हस्तांतरण भेजना पड़ता है।
मेरे उदाहरण में मैं मुख्य लूप का उपयोग कर रहा हूं ताकि एसएस उच्च हो, और सहेजे गए कमांड को साफ कर सके। इस तरह, जब एसएस को अगले लेनदेन के लिए फिर से कम खींचा जाता है, तो पहले बाइट को कमांड बाइट माना जाता है।
अधिक मज़बूती से, यह एक रुकावट के साथ किया जाएगा। यही है, आप SS को बाधित इनपुट्स में से एक से जोड़ेंगे (जैसे, Uno पर, pin 10 (SS) को pin 2 से कनेक्ट करें (एक इंटरप्ट इनपुट)), या pin 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
यह उपरोक्त कोड में भेजने और प्राप्त करने के बीच के समय को दर्शाता है:
आईडीई के संस्करण 1.6.0 ने एसपीआई के काम करने के तरीके को एक हद तक बदल दिया है। आपको अभी भी SPI.begin()
SPI का उपयोग करने से पहले करने की आवश्यकता है । यह SPI हार्डवेयर सेट करता है। हालाँकि अब, जब आप एक दास के साथ संवाद शुरू करने वाले होते हैं, तो आप सही के साथ SPI (इस दास के लिए) स्थापित करने के लिए भी करते हैं SPI.beginTransaction()
:
जब आप दास के साथ संवाद कर रहे होते हैं, तो आप कॉल करते हैं 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 का उपयोग करेंगे? मल्टी-मास्टर कॉन्फ़िगरेशन या दासों की एक बहुत बड़ी संख्या की आवश्यकता I2C की ओर पैमाने को झुकाएगी।
यह एक अच्छा सवाल है। मेरे उत्तर हैं:
दोनों विधियों का अपना स्थान है। I 2 C आपको कई उपकरणों को एक बस (दो तारों, प्लस ग्राउंड) से जोड़ने की सुविधा देता है, इसलिए यह एक पसंदीदा विकल्प होगा यदि आपको पर्याप्त संख्या में उपकरणों की पूछताछ करने की आवश्यकता होती है, शायद काफी अनैतिक रूप से। हालांकि एसपीआई की गति उन स्थितियों के लिए अधिक प्रासंगिक हो सकती है जहां आपको तेजी से आउटपुट करने की आवश्यकता होती है (जैसे। एक एलईडी पट्टी) या तेजी से इनपुट (जैसे। एक एडीसी कनवर्टर)।
SPI के बारे में मेरा पृष्ठ - इसमें बिट-धमाकेदार SPI के बारे में विवरण है, और Atmega328 चिप पर एक दूसरे हार्डवेयर SPI हासिल करने के लिए USART का उपयोग किया गया है।
Are you going to cover the weirdness that is the Due's SPI?
- मुझे ड्यू के एसपीआई के बारे में कुछ भी पता नहीं है (इसके अलावा समग्र प्रोटोकॉल समान है)। उस पहलू को कवर करने वाले उत्तर को जोड़ने के लिए आपका स्वागत है।