In Bezug auf die Arduino Uno, Mega2560, Leonardo und ähnliche Boards:
- Wie funktioniert SPI?
- Wie schnell ist SPI?
- Wie verbinde ich einen Master mit einem Slave?
- Wie mache ich einen SPI-Sklaven?
Bitte beachten Sie: Dies ist eine Referenzfrage.
In Bezug auf die Arduino Uno, Mega2560, Leonardo und ähnliche Boards:
Bitte beachten Sie: Dies ist eine Referenzfrage.
Antworten:
Die SPI-Schnittstelle ( Serial Peripheral Interface Bus ) wird für die Kommunikation zwischen mehreren Geräten über kurze Entfernungen und mit hoher Geschwindigkeit verwendet.
Typischerweise gibt es ein einzelnes "Master" -Gerät, das die Kommunikation initiiert und die Uhr liefert, die die Datenübertragungsrate steuert. Es kann einen oder mehrere Slaves geben. Für mehr als einen Slave hat jeder sein eigenes "Slave-Auswahl" -Signal, das später beschrieben wird.
In einem vollwertigen SPI-System haben Sie vier Signalleitungen:
Wenn mehrere Slaves an das MISO-Signal angeschlossen sind, wird von ihnen erwartet, dass sie diese MISO-Leitung im Tristate-Zustand (bei hoher Impedanz) halten, bis sie durch Aktivieren von Slave Select ausgewählt werden. Normalerweise geht Slave Select (SS) auf Low, um dies zu bestätigen. Das heißt, es ist aktiv niedrig. Sobald ein bestimmter Slave ausgewählt ist, sollte er die MISO-Leitung als Ausgang konfigurieren, damit Daten an den Master gesendet werden können.
Dieses Bild zeigt, wie Daten ausgetauscht werden, wenn ein Byte gesendet wird:
Beachten Sie, dass drei Signale vom Master (MOSI, SCK, SS) ausgegeben werden und eines ein Eingang (MISO) ist.
Die Reihenfolge der Ereignisse ist:
SS
geht auf low, um es zu aktivieren und den Slave zu aktivierenSCK
Zeile wechselt, um anzuzeigen, wann die Datenzeilen abgetastet werden sollenSCK
(unter Verwendung der Standardtaktphase) abgetastet.SCK
(unter Verwendung der Standardtaktphase) vor, indem sie MISO
/ MOSI
falls erforderlich ändernSS
der High-Pegel aktiviert, um sie zu deaktivierenBeachten Sie, dass:
Da Daten im selben Takt gesendet und empfangen werden, kann der Slave nicht sofort auf den Master reagieren. SPI-Protokolle erwarten normalerweise, dass der Master bei einer Übertragung Daten anfordert und bei einer nachfolgenden eine Antwort erhält.
Wenn Sie die SPI-Bibliothek auf dem Arduino verwenden, sieht eine einzelne Übertragung im Code folgendermaßen aus:
byte outgoing = 0xAB;
byte incoming = SPI.transfer (outgoing);
Beispiel für das Senden nur (alle eingehenden Daten ignorieren):
#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
Der obige Code (der nur sendet) kann verwendet werden, um ein serielles Ausgangsschieberegister anzusteuern. Dies sind reine Ausgabegeräte, sodass wir uns keine Gedanken über eingehende Daten machen müssen. In ihrem Fall könnte der SS-Pin als "Speicher" - oder "Verriegelungs" -Pin bezeichnet werden.
Beispiele hierfür sind das serielle Schieberegister 74HC595 und verschiedene LED-Streifen, um nur einige zu nennen. Diese 64-Pixel-LED-Anzeige wird beispielsweise von einem MAX7219-Chip angesteuert:
In diesem Fall können Sie sehen, dass der Board-Hersteller etwas andere Signalnamen verwendet hat:
Die meisten Boards folgen einem ähnlichen Muster. Manchmal ist DIN nur DI (Data In).
Hier ist ein weiteres Beispiel, diesmal eine 7-Segment-LED-Anzeigetafel (ebenfalls basierend auf dem MAX7219-Chip):
Dies verwendet genau die gleichen Signalnamen wie die andere Karte. In beiden Fällen ist zu sehen, dass die Platine nur fünf Drähte benötigt, die drei für SPI sowie Strom und Masse.
Es gibt vier Möglichkeiten, die SPI-Uhr abzutasten.
Das SPI-Protokoll ermöglicht Änderungen der Polarität der Taktimpulse. CPOL ist die Taktpolarität und CPHA ist die Taktphase.
Diese sind in dieser Grafik dargestellt:
Beziehen Sie sich auf das Datenblatt Ihres Geräts, um die richtige Phase und Polarität zu erhalten. Es wird normalerweise ein Diagramm geben, das zeigt, wie die Uhr abgetastet wird. Zum Beispiel aus dem Datenblatt für den 74HC595-Chip:
Wie Sie sehen, ist der Takt normalerweise niedrig (CPOL = 0) und wird an der Vorderflanke abgetastet (CPHA = 0), daher ist dies der SPI-Modus 0.
Sie können die Taktpolarität und -phase in Code wie folgt ändern (natürlich nur einen auswählen):
SPI.setDataMode (SPI_MODE0);
SPI.setDataMode (SPI_MODE1);
SPI.setDataMode (SPI_MODE2);
SPI.setDataMode (SPI_MODE3);
Diese Methode ist ab Version 1.6.0 der Arduino IDE veraltet. Für neuere Versionen ändern Sie den Uhrzeitmodus im SPI.beginTransaction
Aufruf wie folgt:
SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0)); // 2 MHz clock, MSB first, mode 0
Der Standardwert ist das höchstwertige Bit zuerst. Sie können jedoch die Hardware so einstellen, dass das niedrigstwertige Bit zuerst verarbeitet wird:
SPI.setBitOrder (LSBFIRST); // least significant bit first
SPI.setBitOrder (MSBFIRST); // most significant bit first
Auch dies ist in den Versionen 1.6.0 und höher der Arduino IDE veraltet. Für neuere Versionen ändern Sie die Bitreihenfolge im SPI.beginTransaction
Aufruf wie folgt:
SPI.beginTransaction (SPISettings (1000000, LSBFIRST, SPI_MODE2)); // 1 MHz clock, LSB first, mode 2
Die Standardeinstellung für SPI ist die Verwendung der Systemtaktrate geteilt durch vier, dh eines SPI-Taktimpulses alle 250 ns, unter der Annahme eines 16-MHz-CPU-Takts. Sie können den Taktteiler folgendermaßen ändern setClockDivider
:
SPI.setClockDivider (divider);
Wo "Teiler" ist einer von:
Die schnellste Rate ist "Dividieren durch 2" oder ein SPI-Takt alle 125 ns, unter der Annahme eines 16-MHz-CPU-Takts. Dies würde daher 8 · 125 ns oder 1 us benötigen, um ein Byte zu übertragen.
Diese Methode ist ab Version 1.6.0 der Arduino IDE veraltet. In den letzten Versionen ändern Sie die Übertragungsgeschwindigkeit im SPI.beginTransaction
Anruf wie folgt:
SPI.beginTransaction (SPISettings (4000000, MSBFIRST, SPI_MODE0)); // 4 MHz clock, MSB first, mode 0
Empirische Tests haben jedoch gezeigt, dass zwei Taktimpulse zwischen den Bytes erforderlich sind, sodass die maximale Rate, mit der Bytes ausgelesen werden können, jeweils 1,125 µs beträgt (mit einem Taktteiler von 2).
Zusammenfassend kann jedes Byte mit einer maximalen Rate von eins pro 1,125 us (mit einem 16-MHz-Takt) gesendet werden, was eine theoretische maximale Übertragungsrate von 1 / 1,125 us oder 888.888 Bytes pro Sekunde ergibt (außer Overhead wie das Setzen von SS auf niedrig und so weiter) auf).
Anschluss über digitale Pins 10 bis 13:
Verbindung über den ICSP-Header:
Anschluss über digitale Pins 50 bis 52:
Sie können auch den ICSP-Header verwenden, ähnlich dem obigen Uno.
Im Gegensatz zu Uno und Mega legen Leonardo und Micro die SPI-Pins der digitalen Pins nicht frei. Sie können nur die ICSP-Header-Pins verwenden, wie oben für Uno dargestellt.
Ein Master kann mit mehreren Slaves kommunizieren (jedoch jeweils nur mit einem). Dies geschieht, indem SS für einen Sklaven aktiviert und für alle anderen deaktiviert wird. Der Slave, bei dem SS aktiviert ist (normalerweise bedeutet dies LOW), konfiguriert seinen MISO-Pin als Ausgang, damit der Slave und nur dieser Slave auf den Master reagieren können. Die anderen Slaves ignorieren ankommende Taktimpulse, wenn SS nicht aktiviert ist. Daher benötigen Sie für jeden Slave ein zusätzliches Signal:
In dieser Grafik können Sie sehen, dass MISO, MOSI und SCK von beiden Slaves gemeinsam genutzt werden. Jeder Slave verfügt jedoch über ein eigenes SS-Signal (Slave Select).
Die SPI-Spezifikation spezifiziert keine Protokolle als solche, so dass es Sache der einzelnen Master / Slave-Paarungen ist, zu vereinbaren, was die Daten bedeuten. Während Sie Bytes gleichzeitig senden und empfangen können, kann das empfangene Byte keine direkte Antwort auf das gesendete Byte sein (da sie gleichzeitig zusammengestellt werden).
Daher wäre es logischer, wenn ein Ende eine Anfrage senden würde (z. B. 4 bedeutet "Plattenverzeichnis auflisten") und dann Übertragungen durchführen würde (möglicherweise nur Nullen nach außen senden), bis es eine vollständige Antwort erhält. Die Antwort wird möglicherweise mit einem Zeilenumbruch oder einem 0x00-Zeichen beendet.
Lesen Sie im Datenblatt Ihres Slave-Geräts nach, welche Protokollsequenzen es erwartet.
Das vorherige Beispiel zeigt den Arduino als Master, der Daten an ein Slave-Gerät sendet. Dieses Beispiel zeigt, wie der Arduino ein Sklave sein kann.
Verbinden Sie zwei Arduino Unos mit den folgenden Stiften, die miteinander verbunden sind:
13 (SCK)
+ 5v (falls erforderlich)
Auf dem Arduino Mega sind die Pins 50 (MISO), 51 (MOSI), 52 (SCK) und 53 (SS).
In jedem Fall MOSI an einem Ende mit MOSI an dem anderen verbunden ist, können Sie nicht tauschen sie um (die Sie ist nicht über MOSI <-> MISO). Die Software konfiguriert ein Ende von MOSI (Master-Ende) als Ausgang und das andere Ende (Slave-Ende) als Eingang.
#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
Der Slave ist vollständig interruptgesteuert und kann daher andere Aufgaben ausführen. Die eingehenden SPI-Daten werden in einem Puffer gesammelt und ein Flag gesetzt, wenn ein "signifikantes Byte" (in diesem Fall eine neue Zeile) ankommt. Dies weist den Slave an, einzusteigen und mit der Datenverarbeitung zu beginnen.
In Anlehnung an den obigen Code, der Daten von einem SPI-Master an einen Slave sendet, wird im folgenden Beispiel gezeigt, wie Daten an einen Slave gesendet werden, damit etwas getan wird und eine Antwort zurückgegeben wird.
Der Master ähnelt dem obigen Beispiel. Ein wichtiger Punkt ist jedoch, dass wir eine leichte Verzögerung hinzufügen müssen (etwa 20 Mikrosekunden). Andernfalls hat der Slave keine Möglichkeit, auf die eingehenden Daten zu reagieren und etwas damit zu tun.
Das Beispiel zeigt das Senden eines "Befehls". In diesem Fall "a" (etwas hinzufügen) oder "s" (etwas subtrahieren). Dies soll zeigen, dass der Slave tatsächlich etwas mit den Daten tut.
Nach dem Aktivieren von Slave-Select (SS) zum Initiieren der Transaktion sendet der Master den Befehl, gefolgt von einer beliebigen Anzahl von Bytes, und löst dann SS aus, um die Transaktion zu beenden.
Ein sehr wichtiger Punkt ist, dass der Slave nicht gleichzeitig auf ein eingehendes Byte reagieren kann. Die Antwort muss im nächsten Byte sein. Dies liegt daran, dass die Bits, die gesendet werden, und die Bits, die empfangen werden, gleichzeitig gesendet werden. Um also etwas zu vier Zahlen hinzuzufügen, benötigen wir fünf Überweisungen, wie diese:
transferAndWait ('a'); // add command
transferAndWait (10);
a = transferAndWait (17);
b = transferAndWait (33);
c = transferAndWait (42);
d = transferAndWait (0);
Zuerst fordern wir eine Aktion für Nummer 10 an. Bis zur nächsten Überweisung (die für 17) erhalten wir jedoch keine Antwort. Für die Antwort auf 10 wird jedoch ein "a" gesetzt. Schließlich senden wir eine "Dummy" -Nummer 0, um die Antwort für 42 zu erhalten.
#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
Der Code für den Slave erledigt im Prinzip fast alles in der Interruptroutine (wird aufgerufen, wenn eingehende SPI-Daten ankommen). Es nimmt das eingehende Byte und addiert oder subtrahiert gemäß dem gespeicherten "Befehlsbyte". Beachten Sie, dass die Antwort das nächste Mal über die Schleife "gesammelt" wird. Aus diesem Grund muss der Master eine letzte "Dummy" -Übertragung senden, um die endgültige Antwort zu erhalten.
In meinem Beispiel verwende ich die Hauptschleife, um einfach zu erkennen, wann SS hoch geht, und den gespeicherten Befehl zu löschen. Auf diese Weise wird das erste Byte als Befehlsbyte betrachtet, wenn SS für die nächste Transaktion wieder auf niedrig gesetzt wird.
Zuverlässiger würde dies mit einem Interrupt geschehen. Das heißt, Sie würden SS physisch mit einem der Interrupt-Eingänge verbinden (z. B. auf dem Uno Pin 10 (SS) mit Pin 2 (einem Interrupt-Eingang) verbinden oder einen Pin-Wechsel-Interrupt auf Pin 10 verwenden.
Dann könnte der Interrupt verwendet werden, um zu bemerken, wenn SS niedrig oder hoch gezogen wird.
// 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
Dies zeigt das Timing zwischen Senden und Empfangen im obigen Code:
Die IDE-Version 1.6.0 hat die Funktionsweise von SPI in gewissem Maße geändert. Sie müssen dies noch tun, SPI.begin()
bevor Sie SPI verwenden können. Das richtet die SPI-Hardware ein. Aber jetzt, wenn Sie sind mit einem Slave starten Kommunikation Sie auch tun SPI.beginTransaction()
SPI einzurichten (für diesen Slave) mit dem richtigen:
Wenn Sie mit dem Slave fertig sind, rufen Sie an SPI.endTransaction()
. Zum Beispiel:
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
Ich würde eine vorläufige Frage hinzufügen: Wann / warum würden Sie SPI verwenden? Eine Multi-Master-Konfiguration oder eine sehr große Anzahl von Slaves würde die Waage in Richtung I2C neigen.
Dies ist eine ausgezeichnete Frage. Meine Antworten sind:
Beide Methoden haben ihren Platz. Mit I 2 C können Sie viele Geräte an einen einzigen Bus anschließen (zwei Drähte plus Masse), sodass dies die bevorzugte Wahl ist, wenn Sie eine erhebliche Anzahl von Geräten abfragen müssen, möglicherweise ziemlich selten. Die Geschwindigkeit von SPI könnte jedoch für Situationen relevanter sein, in denen eine schnelle Ausgabe (z. B. ein LED-Streifen) oder eine schnelle Eingabe (z. B. ein ADC-Wandler) erforderlich ist.
Meine Seite über SPI - enthält auch Details zu Bit-Banged-SPI und zur Verwendung von USART, um ein zweites Hardware-SPI auf dem Atmega328-Chip zu erhalten.
Are you going to cover the weirdness that is the Due's SPI?
- Ich weiß nichts über die SPI des Due (abgesehen von der Annahme, dass das Gesamtprotokoll dasselbe ist). Sie können gerne eine Antwort hinzufügen, die diesen Aspekt abdeckt.