Kampis Elektroecke

AVR mit einer SD-Karte erweitern – Teil 1

SD-Karten eignen sich hervoragend dazu, große Datenmengen zu speichern und am Computer wieder abrufen zu können. Dies macht den Einsatz von SD-Karten gerade für Mikrocontrollerprojekte durchaus interessant, da ein Mikrocontroller in der Regel nur ein, vergleichsweise kleines, EEPROM als Datenspeicher bereitstellt.

Beginnend mit diesem Artikel möchte ich zeigen, wie ein einfaches Interface für SD-Karten mit FAT Unterstützung auf einem AVR Mikrocontroller (hier ein XMega256A3BU) implementiert werden kann.

Aufbau und Funktionsweise einer SD-Karte:

Eine SD-Karte besteht aus einem Interface, einem Controller, ein paar Registern und dem eigentlichen Speicherelement.

Pin SD SPI
1 DAT3 CS
2 CMD DI
3 Vss Vss
4 Vdd Vdd
5 CLK SCLK
6 Vss Vss
7 DAT0 DO
8 DAT1 NC
9 DAT2 NC

Als Kommunikationsschnittstelle soll das SPI-Interface des Mikrocontrollers zum Einsatz kommen. In diesem Beispiel verwende ich den USART-SPI des XMegas. Ein Steuerbefehl für die SD-Karte ist dabei folgendermaßen aufgebaut:

Quelle: http://elm-chan.org/docs/mmc/mmc_e.html

Im SPI-Modus besteht jedes Kommando aus einem 8 Bit Kommando, bei dem das letzte Bit auf 0 und das vorletzte Bit 1 gesetzt ist. Die restlichen Bits ergeben sich aus dem Index des Befehls. Danach folgt ein 4 Byte langes Befehlsargument und eine 1 Byte lange CRC (im SPI Modus optional).

Nachdem der Befehl gesendet worden ist, benötigt die SD-Karte eine gewisse Zeit um diesen Befehl zu verarbeiten und darauf zu antworten. Diese Zeit NCR beträgt zwischen 0 und 8 für SD-Karten, bzw. 1 bis 8 Bytes für MM-Karten. Nach Ablauf der Zeit sendet die Karte die entsprechende Antwort auf den Befehl. Während der NCR-Phase muss permanent ein 0xFF an die Karte gesendet werden, womit DO auf High liegt und die SD-Karte getaktet über SCLK wird.

Für die Kommunikation mit der SD-Karte sind folgende Befehle definiert:

Index Argument Antwort Daten Beschreibung
CMD0 0 R1 Nein Softwareseitiger Reset der Karte
CMD1 0 R1 Nein Karte initialisieren
ACMD41* 0x40000000 R1 Nein Karte Initialisieren (nur SDC)
CMD8 0x1AA R3 Nein Spannung überprüfen (nur SDC V2)
CMD9 0 R1 Ja CSD-Register auslesen
CMD10 0 R1 Ja CID-Register auslesen
CMD12 0 R1b Nein Daten lesen abbrechen
CMD16 Blocklänge R1 Nein Blockgröße ändern
CMD17 Adresse R1 Ja Einzelnen Block lesen
CMD18 Adresse R1 Ja Mehrere Blöcke lesen
CMD23 Anzahl Blöcke R1 Nein Anzahl zu sendender Blöcke setzen (nur MMC)
ACMD23* Anzahl Blöcke R1 Nein Anzahl der zu löschenden Blöcke beim nächsten Multi-Block Schreiben setzen (nur SDC)
CMD24 Adresse R1 Ja Block schreiben
CMD25 Adresse R1 Ja Mehrere Blöcke schreiben
CMD55 0 R1 Nein ACMD-Kommando einleiten
CMD58 0 R3 Nein OCR-Register lesen
*ACMD<n> : Diese Kommandos sind eine Aneinanderkettung von CMD55 und CMD<n>

Die SD-Karte antwortet auf jeden Befehl mit einem R1 oder R3. Eine R1-Antwort besteht aus 8 und eine R3-Antwort aus 40 Bit.

R1-Antwort
7 6 5 4 3 2 1 0
0 Parameter Error Address Error Erase Sequence Error Command CRC Error Illegal Command Erase Reset In Idle State

Die R1-Antwort enthält immer den aktuellen Zustand des Status-Registers der SD-Karte. Ein Wert von 0x00 bedeutet, dass der Befehl erfolgreich verarbeitet worden ist. Der Befehl CMD12 gibt eine R1b Antwort zurück, welche sich nur in einer längeren Zeit NCR von der Antwort R1 unterscheidet.

Eine R3-Antwort beinhaltet immer den aktuellen Wert des Status-Registers (also eine R1-Antwort), sowie den Wert des OCR-Registers.

R3-Antwort
R1 (8 Bit) OCR-Register (32 Bit)

Nach einem Reset (z. B. dem Einstecken in den Kartenleser) muss eine SD-Karte immer erst initialisiert werden. Während der Initialisierung stellt der Host (in diesem Beispiel der Mikrocontroller) außerdem fest, um was für einen Typ von Speicherkarte es sich handelt, sprich ob es eine SD-Karte bis 2 GB Speicherkapazität, eine Karte mit einer Kapazität >2 GB oder um eine Multimediacard (MMC) handelt. Je nach Kartentyp wird eine andere Initialisierung verwendet.

Aus “Physical Layer Simplified Specification Version 4.10 – Figure 7-2”

Implementierung des SPI-Treibers:

Wie bereits erwähnt soll die die Karteim SPI-Modus betrieben und das USART-SPI-Interface des XMegas genutzt werden. Natürlich kann auch jede andere SPI-Schnittstelle genutzt werden, indem der Code entsprechend angepasst wird.

Zuerst wird die Schnittstelle initialisiert werden. Laut Spezifikation müssen SD-Karten im SPI-Modus 0 (CPOL = 0, CPHA = 0) betrieben werden. Für die Anbindung der Karte sind folgende I/Os genutzt worden:

Pin Signal
PD1 SCLK
PD2 MISO
PD3 MOSI
PE0 CS

Die I/Os für die Signale SCK, MOSI und CS müssen während der Initialisierung als Ausgang definiert und auf High gesetzt werden. Im Idle-Zustand müssen die Datenleitungen zudem auf High liegen (siehe Bild).

Bei der Schnittstellenkonfiguration müssen folgende Einstellungen durchgeführt werden:

  • Einstellen des Modus (USART-SPI)
  • Konfigurieren der Clockphase und der Clockpolarität (CPHA = 0, CPOL = 0)
  • Reihenfolge der Daten einstellen (LSB-First)
  • Receiver und Transmitter aktivieren

SD-Karten sollten während der ersten Initialisierung mit einer Taktfrequenz von etwa 100 kHz bis 400 kHz betrieben werden, da diese Taktfrequenz sowohl von alten, als auch neuen Karten unterstützt wird. Neuere Karten können auch 1 MHz oder mehr, weswegen nach der Initialisierung der Karte die Taktfrequenz erhöht werden kann.

Nachdem die Schnittstelle und alle GPIOs konfiguriert wurden, kann mit der Karte kommuniziert und die Initialisierung (siehe Bild) abgearbeitet werden.

Nach einem Power ON Reset wird die Karte ausgewählt und anschließend werden mind. 74 Taktpulse erzeugt (das entspricht 10x 0xFF senden). Dadurch wird der native Betriebsmodus der Karte aktiviert, wodurch die Karte die oben genannten Befehle versteht. Danach wird das CMD0 gesendet, womit die Karte in den Idle-Modus versetzt werden soll. Die Karte anwortet mit einem R1-Befehl (0x01 – siehe Übersicht), sobald sie in den Idle-Modus gewechselt hat.

Wenn die SD-Karte mehrmals nicht auf den Befehl reagiert, wird ein Fehler ausgegeben und die Initialisierung abgebrochen. Nach der Initialisierung wird bei den einzelnen Kommandos zudem keine Checksumme mehr benötigt.

Die Routine zum Senden eines beliebigen Kommandos mit Argumenten und Checksumme, sowie das Senden und Empfangen beliebiger Daten, sieht folgendermaßen aus.

In der Funktion wird die Karte zuerst angewählt um dann den Befehl mit seinen Befehlsargumenten und der Checksumme zu senden. Anschließend werden 8 Datenbytes gesendet um die Zeit NCR zu überbrücken. Gleichzeitig wird überprüft ob die Karte bereits eine Antwort gesendet hat. Eine empfangene Antwort wird gespeichert und anschließend werden alle optinalen Datenbytes eingelesen und gespeichert. Zum Schluss wird die Karte abgewählt und das Antwortbyte zurückgegeben. Es ist zudem empfehlenswert vor und nach dem Senden des eigentlichen Kommandos 8 Taktzyklen zu erzeugen (also ein Leerbyte zu senden), damit der Controller der SD-Karte etwas zusätzliche Zeit bekommt für die Abarbeitung der interne Abläufe bekommt.

Bei einem erfolgreichen Wechsel in den Idle-Mode werden die nächsten Schritte für die Initialisierung durchgeführt:

In dieser Initalisierungsroutine wird zudem auch noch der Kartentyp bestimmt, da eine MMC einen anderen Befehlssatz verwendet als eine SD-Karte. Zudem wird eine Version 1 SD-Karte (< 2 GB) anders initialisiert, als eine Version 2 SD-Karte.

Wenn die Karte erfolgreich initialisiert wurde, wird die Geschwindigkeit des SPI auf den ursprünglichen Wert zurückgeändert und damit ist die SD-Karte einsatzbereit und kann gelesen und beschrieben werden. Wie das geht erkläre ich im nächsten Teil.

Das komplette XMega-Projekt, inkl. meiner verwendeten AVR Library, ist in meinem GitLab-Repository zu finden.

Viele Grüße
Daniel

Ein Kommentar

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.