Zum Inhalt springen

SPI - Serial Peripheral Interface

starlightViewModes.switchTo

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.

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:

SPI2XSPR1SPR0SCK Frequency
000fOSC / 4
001fOSC / 16
010fOSC / 64
011fOSC / 128
100fOSC / 2
101fOSC / 8
110fOSC / 32
111fOSC / 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 PORTB2
PORTB &= ~(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:

Aufbau SPI

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:

Data Transfer Data Transfer

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;
Data Transfer Data Transfer

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);
}