SPI - Serial Peripheral Interface
This content is not available in your language yet.
Theorie
Das SPI ist ein serielles Kommunikationsprotokoll (Bus-System), welches verwendet werden kann, um den Datenaustausch zwischen verschiedenen Komponenten effizient zu ermöglichen. Dieses bidirektionale, synchronisierte Interface bietet eine schnelle und zuverlässige Methode für die Übertragung von Daten zwischen einem Controller
-Gerät und mehreren Peripheral
-Geräten, wie zum Beispiel SD-Karten, Sensoren oder Schieberegister.
Dabei muss die Kommunikation immer vom Controller aus gestartet werden. Ein Peripheral kann nur Daten senden, wenn er vom Controller dazu aufgefordert wird.
Das SPI verwendet separate Takt- und Datenleitungen sowie eine Auswahlleitung, um das Gerät auszuwählen, mit dem Sie sprechen möchten. Dies ist auch einer der Unterschiede zum USART, welches keine synchrone Schnittstelle ist, da es keine Garantie gibt, dass beide Seiten mit der gleichen Taktrate laufen. Beim USART müssen sich die beiden Seiten im Vorhinein einigen, mit welcher Übertragungsgeschwindigkeit (Baudrate) sie kommunizieren und es müssen zusätzliche Start- und Stoppbits übertragen werden.
Einige Dokumentationen verwenden noch die veralteten Begriffe, wie Master
oder Slave
. Aufgrund ihrer historischen Verbindung mit Rassismus werden diese Begriffe heutzutage durch neutralere Alternativen ersetzt. Folgende Tabelle zeigt die Änderungen diesbezüglich:
Veralteter Begriff | Neuer Name |
---|---|
Master | Controller |
Slave | Peripheral |
MISO (Master In Slave Out) | POCI (Peripheral Out Controller In) |
MOSI (Master Out Slave In) | PICO (Peripheral In Controller Out) |
SS (Slave Select) | CS (Chip Select) |
Für weitere Informationen siehe Wikipedia.
Clock
Genau wie der Timer benötigt das SPI einen Prescaler, um den Systemtakt anzupassen. Mithilfe dieser Tabelle (Datenblatt) und diesen Registern kann man den Prescaler einstellen:
SPI2X | SPR1 | SPR0 | SCK Frequency |
---|---|---|---|
0 | 0 | 0 | fOSC / 4 |
0 | 0 | 1 | fOSC / 16 |
0 | 1 | 0 | fOSC / 64 |
0 | 1 | 1 | fOSC / 128 |
1 | 0 | 0 | fOSC / 2 |
1 | 0 | 1 | fOSC / 8 |
1 | 1 | 0 | fOSC / 32 |
1 | 1 | 1 | fOSC / 64 |
Diese Bits können in den Registern SPSR
(SPI2X
) und SPCR
(SPR1
, SPR0
) gesetzt werden.
SPCR |= (1<<SPR0) | (1<<SPR1);SPSR |= (1<<SPI2X);
Chip Select
Vor jedem Senden und Empfangen der Daten wird empfohlen das Peripheral zu selektieren, indem das jeweilige PIN auf 0V gesetzt wird und anschließend die interne Spannung wieder auf 5V gesetzt wird. Anders gesagt, ist der Chip Select active low
.
Beispielsweise kann man mittels dieser Konfiguration den RFID
(ein Kartenlesegerät) selektieren:
#define RFID PORTB2PORTB &= ~(1<<RFID);
Und anschließend wieder deselektieren:
PORTB |= (1<<RFID);
Für jedes Peripheral muss man einen eigenen Chip Select PIN verwenden. Diese Architektur kann in diesem Bild (Quelle) noch einmal besser veranschaulicht werden:
Read und Write
Das Prinzip bei SPI ist immer: Der Controller sendet Daten an das Peripheral und der Peripheral sendet Daten zurück. Allerdings werden immer alle Bits gleichzeitig verschoben (also eigentlich ein Schieberegister). Während der Controller Bits von rechts nach links (Controller beginnt mit dem MSB) sendet, sendet der Peripheral die Bits, die aus seinem Byte hinausgeschoben werden zurück an den Controller.
Das bedeutet, dass in den ersten acht Zyklen die Daten von Controller an den Peripheral gesendet werden. Gleichzeit erhält der Controller nutzlose Daten vom Peripheral, welche noch im Byte vom Peripheral enthalten waren. Und anschließend kann der Controller mithilfe von Dummy Daten
die eigentlichen Daten vom Peripheral auslesen.
In diesem Beispiel werden die Daten zuerst vom Controller zum Peripheral gesendet:
uint8_t data = ((1<<7) | (0x37<<1));
SPDR = data;while(!(SPSR & (1<<SPIF)));
uint8_t temp = SPDR;
Der Prozess dieses Ablaufs ist in diesem Diagramm nochmal dargestellt:


Und anschließend werden die berechneten Daten vom Peripheral zum Controller gesendet:
uint8_t dummyData = 0x00;
SPDR = dummyData;while(!(SPSR & (1<<SPIF)));
uint8_t versionNumber = SPDR;


Code
Config
#define CS_PIN PORTB2
void SPI_init(void){ DDRB |= (1<<DDB2) | (1<<DDB3) | (1<<DDB5); PORTB |= (1<<CS_PIN); SPCR |= (1<<MSTR) | (1<<SPE) | (1<<SPR0);}
Read und Write Methods
uint8_t ReadCommand(uint8_t command) { return ((1<<7) | (command<<1));}
uint8_t WriteCommand(uint8_t command) { return (command<<1);}