Forum: Mikrocontroller und Digitale Elektronik STM32 SDIO Blöcke lesen


von Tobias P. (hubertus)


Lesenswert?

Hallo zusammen

ich möchte mit meinem STM32F4 (Discovery Board) eine SD Karte lesen 
(genauer gesagt: eine MicroSD Karte).

Dazu möchte ich DMA benutzen. Um einen oder mehrere Blöcke zu lesen, 
verwende ich folgenden Code:
1
SDIO_DCTRL = 0;
2
3
/* clear dma stream 3 interrupt flags */
4
DMA2_LIFCR = (0xfu << 24);
5
6
/* disable dma stream 3 */
7
DMA2_S3CR = 0;
8
DMA2_LISR = (0xfu << 24);
9
10
DMA2_S3PAR = 0x40012C80; // SDIO_FIFO_BASE
11
DMA2_S3M0AR = (uint32_t)BufferDST;
12
DMA2_S3CR = (4u << 25) | (1u << 23) | (1u << 21) | (3u << 16) | (2u << 13) | (2u << 11) | (1 << 10) | (1 << 5) | (1 << 4);
13
DMA2_S3FCR = BIT_02 | (3u << 0);
14
DMA2_S3NDTR = 512;
15
DMA2_S3CR |= 1;
16
17
/* enable dma in the data control register */
18
SDIO_DCTRL |= 1(<< 3);
19
20
/* initialise a data transfer with maximal timeout */
21
SDIO_DTIMER = SD_DATATIMEOUT;
22
23
/* set the data length */
24
SDIO_DLEN = NumberOfBlocks * BlockSize;
25
26
/* block size is 512 bytes, read from the card and enable dpsm */
27
SDIO_DCTRL = ((9u << 4) | (1 << 1) | (1 <<0));
28
29
send_command(ReadAddr, SD_CMD_READ_MULT_BLOCK);

Was mich aber sehr verwirrt, ist, dass der DMA gleich loslegt, nachdem 
ich das Enable-Bit gesetzt habe (DMA2_S3CR |= 1) und der S3NDTR sofort 
auf 65535 springt. Was mache ich da falsch?

Die Initialisierung der Karte geht soweit problemlos und zuverlässig 
auch mit verschiedenen Karten, die Kartenkapazität etc. wird zuverlässig 
erkannt. Wie würde ich einen Block lesen, ohne DMA zu benutzen?


Gruss
Tobias

von Jim M. (turboj)


Lesenswert?

Tobias Plüss schrieb:
> Was mich aber sehr verwirrt, ist, dass der DMA gleich loslegt, nachdem
> ich das Enable-Bit gesetzt habe

Was soll er auch sonst machen?
Du musst sicher zuerst SDIO konfigurieren und dann DMA anmachen.

Ach ja: Vieeel zuviele magic numbers. Von ST gibt es Header Dateien, wo 
die ganzen Bits auch mit Namen definiert sind.

von Dr. Sommer (Gast)


Lesenswert?

Um SDIO+DMA zu verwenden musst du im DMA den Peripheral Flow Controller 
aktivieren, und das NDTR Register überhaupt nicht verwenden. Der SDIO 
bestimmt dann wie viele Items übertragen werden. Allerdings kannst du 
auf einen DMA-Transfer nur max 2^16-1 Items übertragen.

von Tobias P. (hubertus)


Lesenswert?

Hallo

ich habe es jetzt versucht umzustellen, also der Read-Command wird 
gesendet, bevor der DMA initialisiert wird. Das geht natürlich auch 
nicht, die Karte wird einen Transfer_Error.

Kann man die Daten vorerst auch mal lesen, ohne DMA zu benutzen? ich 
verstehe da einfach irgendwie nicht, wie der SDIO_FIFO funktioniert.

von Tobias P. (hubertus)


Lesenswert?

Hallo Kollegen

ich hänge euch mal meinen Code an.
Ich habe diesen im Netz gefunden, leider basierte er auf der absolut 
grässlichen Library von ST, die ich nicht einsetzen will. Also habe ich 
damit begonnen, das ganze ein wenig umzustrukturieren, dass der Code 
auch ohne die Library (möglichst standalone) compilierbar ist.

Ich möchte dann den Code zusammen mit FatFS von CHAN benutzen.

Vielleicht kann mir ja einer über meine Leseroutine drüber schauen. Ich 
habe es jetzt geschafft, dass mit diesem Code ein einzelner Sektor von 
der Karte gelesen wird. Allerdings geht der SDIO in einen Overrun Error.

Auch würde mich sehr interessieren, wie man das hässliche Polling der 
Flags weg bekommen könnte (z.B. unter Zuhilfenahme von Interrupts).

: Bearbeitet durch User
von holger (Gast)


Lesenswert?

>leider basierte er auf der absolut
>grässlichen Library von ST, die ich nicht einsetzen will.

Wer nicht will der hat schon und macht sich seine Probleme selber.

>Vielleicht kann mir ja einer über meine Leseroutine drüber schauen.

Wozu? Das ist viel zu komplex für "mal eben" drüberschauen.

>Ich möchte dann den Code zusammen mit FatFS von CHAN benutzen.

Hab ich mit dem Code von ST schon gemacht und es funktioniert.
Du musst das Rad nicht neu erfinden.

von Tobias P. (hubertus)


Lesenswert?

Ja holger, danke dir.
Das ändert allerdings nichts daran, dass die Lib von ST voll ist mit 
Polling und damit eigentlich schlecht geeignet ist, um in einer Umgebung 
mit RTOS und laufendem Watchdogtimer betrieben zu werden.

Aber gut. Ich werds schon auch selbst zum Laufen kriegen ;-)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Ich verwende folgenden Code zur Initialisierung vom DMA und 
SDIO-Transfer:
1
// Schreibe in diesen Puffer
2
uint8_t buffer [512];
3
4
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
5
DMA_Stream_TypeDef* dmaStream = DMA2_Stream6;
6
dmaStream->CR = 0; while ((dmaStream->CR & DMA_SxCR_EN) != 0);
7
DMA2->HIFCR = 0x7D0000;
8
dmaStream->PAR = reinterpret_cast<uint32_t> (&SDIO->FIFO);
9
dmaStream->M0AR = reinterpret_cast<uint32_t> (buffer);
10
dmaStream->FCR = DMA_SxFCR_DMDIS;
11
12
SDIO->DTIMER = timeout;
13
// Anzahl zu lesender Bytes setzen
14
SDIO->DLEN = 512;
15
16
// Verwende blockSize = 9 zum Lesen von 512Byte-Blocks von der SD-Karte. Für CMD6 und CMD13 verwende blockSize=6, für ACMD13 verwende blockSize=3
17
SDIO->DCTRL = SDIO_DCTRL_DMAEN | SDIO_DCTRL_DTDIR | SDIO_DCTRL_DTEN | (blockSize << 4);  
18
dmaStream->CR = DMA_SxCR_CHSEL_2 /* channel 4 */ | DMA_SxCR_PBURST_0 /* 4 beats */ | DMA_SxCR_MSIZE_1 /* word */ | DMA_SxCR_PSIZE_1 | DMA_SxCR_MINC | DMA_SxCR_PFCTRL | DMA_SxCR_EN;
19
20
// Sende CMD17 - Einzelnen Block lesen
21
SDIO->ARG = blockIndex;  // Zu lesender Block
22
SDIO->CMD = SDIO_CMD_CPSMEN | 17 | (1 << 6);

Du musst dann noch den SDIO-Interrupt aktivieren (DMA-Interrupt wird 
nicht gebraucht) und dort auf die Beendigung des Transfers warten.

von Tobias P. (hubertus)


Lesenswert?

Hallo Niklas
danke für deine Hilfe. Ich versuch das gleich hier nachzuvollziehen.
Hast du deinen Code auch schon mit SDHC Karten getestet? in der SD 
Karten Spezifikation heisst es, dass die Blockgrösse auf 512 Bytes 
festgesetzt ist bei diesen Karten und nicht verändert werden kann, 
insofern würde es meiner Meinung nach dann zu einem Error führen, wenn 
man andere Blocksizes als 9 verwendet.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Tobias Plüss schrieb:
> Hast du deinen Code auch schon mit SDHC Karten getestet?
Ja, nur.

Tobias Plüss schrieb:
> insofern würde es meiner Meinung nach dann zu einem Error führen, wenn
> man andere Blocksizes als 9 verwendet.
Nein, denn blockSize=9 bedeutet dass 2^9=512 Bytes übertragen werden. 
Siehe dazu im Reference Manual des STM32:
Bits 7:4 DBLOCKSIZE: Data block size
Define the data block length when the block data transfer mode is 
selected:
0000: (0 decimal) lock length = 2 0 = 1 byte
0001: (1 decimal) lock length = 2 1 = 2 bytes
0010: (2 decimal) lock length = 2 2 = 4 bytes
0011: (3 decimal) lock length = 2 3 = 8 bytes
0100: (4 decimal) lock length = 2 4 = 16 bytes
0101: (5 decimal) lock length = 2 5 = 32 bytes
0110: (6 decimal) lock length = 2 6 = 64 bytes
0111: (7 decimal) lock length = 2 7 = 128 bytes
1000: (8 decimal) lock length = 2 8 = 256 bytes
1001: (9 decimal) lock length = 2 9 = 512 bytes
1010: (10 decimal) lock length = 2 10 = 1024 bytes
1011: (11 decimal) lock length = 2 11 = 2048 bytes
1100: (12 decimal) lock length = 2 12 = 4096 bytes
1101: (13 decimal) lock length = 2 13 = 8192 bytes
1110: (14 decimal) lock length = 2 14 = 16384 bytes

von Tobias P. (hubertus)


Lesenswert?

Ja, genau, aber eben, du schreibst in deinem Kommentar, dass ...

>> Für CMD6 und CMD13 verwende blockSize=6, für ACMD13 verwende blockSize=3

was dann != 9 ist und somit einen Error ergibt, oder nicht?

Übrigens habe ich den Code jetzt so implementiert. Normale SD Karte geht 
zuverlässig, meine Sektoren werden mit dem Commando "Read Multiple" 
erfolgreich gelesen. SDHC Karte liest aber immer nur einen Sektor aufs 
mal, wieso kann ich auch nicht verstehen :-(


Gruss

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Tobias Plüss schrieb:
>>> Für CMD6 und CMD13 verwende blockSize=6, für ACMD13 verwende blockSize=3
>
> was dann != 9 ist und somit einen Error ergibt, oder nicht?
Nein, denn CMD6, CMD13 und ACMD13 lesen keinen Block von der Karte, 
sondern übertragen andere Daten/Register mit anderen Blockgrößen (siehe 
SD-Spezifikation). Zum Lesen von Datenblöcken der Karte brauchst du 
CMD17, und da braucht es blockSize=9 (=512 Bytes).

Tobias Plüss schrieb:
> SDHC Karte liest aber immer nur einen Sektor aufs
> mal, wieso kann ich auch nicht verstehen :-(
Weil mein CMD17 nur einen Block liest. Zum Lesen mehrerer Blocks 
brauchst du CMD18. Siehe SD Spezifikation.

: Bearbeitet durch User
von Tobias P. (hubertus)


Lesenswert?

Hi,

ja klar, deinen Code habe ich natürlich auf CMD18 angepasst.

Ich versuche wie folgt zu lesen:
1
__attribute__((aligned)) char buffer[2048];
2
sd_read(buffer, sector << 9, 512, count);

dann die sd_read:
1
static mcierror_t sd_read(void *readbuff, uint64_t ReadAddr, uint16_t BlockSize, uint32_t NumberOfBlocks)
2
{
3
mcierror_t errorstatus = SD_OK;
4
5
/* clearing the data control register to ensure sdio controller is idle */
6
SDIO_DCTRL = 0;
7
8
/* configure dma */
9
/* disable the dma stream and wait until it is acutally disabled */
10
DMA2_S3CR = 0;
11
while(DMA2_S3CR & BIT_00);
12
13
/* clear dma interrupt flags for stream 6 */
14
DMA2_LIFCR = (0xfu << 24);
15
16
/* peripheral address: sdio fifo base address */
17
DMA2_S3PAR = 0x40012C80; //SDIO_FIFO
18
19
/* disable direct mode */
20
DMA2_S3FCR = BIT_02 | 3u;
21
22
/* memory address: address of the buffer */
23
DMA2_S3M0AR = (uint32_t)readbuff;
24
25
/* configure the dma stream: select ch 4, use memory bursts of size 4,
26
peripheral bursts of size 4, highest priority, 32 bit transfers, memory
27
increment mode, transfer from peripheral to memory and use the peripheral
28
as the flow controller. */
29
DMA2_S3CR = ((4u << 25) | (1u << 23) | (1u << 21) | (3u << 16) |
30
(2u << 13) | (2u << 11) | BIT_10 | BIT_05 | BIT_04);
31
32
/* enable the dma stream */
33
DMA2_S3CR |= BIT_00;
34
35
/* initialise a data transfer with maximal timeout */
36
SDIO_DTIMER = 0xffffffff;
37
38
/* set the data length */
39
SDIO_DLEN = NumberOfBlocks * BlockSize;
40
41
/* block size is 8 bytes, read from the card and enable dpsm */
42
SDIO_DCTRL = (BIT_11 | (9u << 4) | BIT_01 | BIT_03);
43
44
/* enable dma in the data control register */
45
SDIO_DCTRL |= BIT_00;
46
47
/* set the block size for the card */
48
send_command(BlockSize, SD_CMD_SET_BLOCKLEN, resp_short, wait_no);
49
errorstatus = check_resp1_error(SD_CMD_SET_BLOCKLEN);
50
if(errorstatus != SD_OK)
51
{
52
  return errorstatus;
53
}
54
55
/* read mult block: cmd18 */
56
send_command(ReadAddr, SD_CMD_READ_MULT_BLOCK, resp_short, wait_no);
57
return = check_resp1_error(SD_CMD_READ_MULT_BLOCK);
58
}

Es funkioniert so mit einer normalen SD Karte. Bei einer SDHC, die ich 
hier auch noch auf dem Tisch liegen habe, wird immer maximal ein 
einzelner Sektor gelesen.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Initialisierst du die SDHC Karte denn auch entsprechend? Bei den 
Lese-Kommandos bei SDHC musst du die Start-Adresse als Blocknummer 
angeben, und nicht als Byte-Adresse, d.h. das "sector << 9" ist 
verkehrt.
CMD16 ist unnötig bei SDHC.

von Tobias P. (hubertus)


Lesenswert?

Hi Niklas,

was heisst ob ich sie entsprechend initialisiere?
Ich bin nach dem Flowchart auf S. 28 der Spezifikation vorgegangen. Also 
zuerst CMD0, dann CMD8, dann ACMD41 mit gesetztem HCS bit. Die Karte 
antwortet immer wie erwartet und meine Initialisierung klappt.

Im Falle von SDHC mache ich das "sector << 9" nicht, sondern übergebe 
nur "sector" (da ist also noch ne Fallunterscheidung mit drin, ja).

von Tobias P. (hubertus)


Lesenswert?

Hi Niklas

so ich habe meinen Code mal gründlich überarbeitet und das ganze 
Polling-Zeug raus geschmissen und verwende nun alles mit Interrupts und 
Semaphoren vom OS. Jetzt funktioniert das Lesen und Schreiben!

Was ich allerdings feststelle: wenn man sehr oft hintereinander eine 
Lese- oder Schreibfunktion aufruft, dann passiert es sporadisch, dass 
der Vorgang fehlschlägt. Ich tippe mal darauf dass da das Wear Leveling 
zuschlägt.

Im Moment mache ich es so, dass ich vor dem Schreib/Lesevorgang die 
Interrupts aktiviere (SDIO_MASK). Im Interrupthandler setze ich dann ein 
Semaphor. In der Schreibroutine warte ich auf das Semaphor, aber mit 
einem Timeout. Das funktioniert gut, aber sporadisch werden einzelne 
Sektoren nicht richtig gelesen (CRC_FAILED). Es könnte allerdings auch 
an meinem Aufbau liegen (der SD Sockel ist mit Wrapdrähten am 
Discoveryboard angelötet).

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Tobias Plüss schrieb:
> Ich tippe mal darauf dass da das Wear Leveling
> zuschlägt.
Das manifestiert sich aber lediglich in langen Schreib/Erase-Zeiten, 
nicht durch Fehlschlagen einer Operation.

Tobias Plüss schrieb:
> Das funktioniert gut, aber sporadisch werden einzelne
> Sektoren nicht richtig gelesen (CRC_FAILED). Es könnte allerdings auch
> an meinem Aufbau liegen (der SD Sockel ist mit Wrapdrähten am
> Discoveryboard angelötet).
Ja das klingt nach Übertragungsfehler. In dem Fall die Operation einfach 
noch einmal durchführen?

von Tobias P. (hubertus)


Lesenswert?

Hallo Niklas

O.K. bin jetzt so weit dass ich auf normale SD Karten zuverlässig 
schreiben und davon lesen kann. Initialisierung und so weiter klappt 
auch sehr zuverlässig. Ich habe mittels FATFS soeben auf meinem 
Discoveryboard ein paar Hello World Textdateien anlegen und am PC 
auslesen können, das funktioniert jetzt also!

SDHC Karte habe ich leider nur eine hier. Und die meldet mir beim 
Zugriff einen CRC Fehler. Auch wenn ich die Taktfrequenz auf 400 kHz 
heruntersetze. Kann dies mit einer falschen Initialisierung 
zusammenhängen, oder könnte auch einfach meine Karte defekt sein?

Den CMD23 (SET_BLOCK_COUNT) frisst meine SDHC Karte auch nicht. Da wird 
ein 'illegal command' zurückgemeldet. Gemäss Spezifikation müsste die 
Karte aber doch den Befehl akzeptieren?

von Tobias P. (hubertus)


Lesenswert?

Hallo Niklas
ich habe nun nach längerer unterbrechung das Projekt SD Karte wieder 
angefangen.
Ich habe ein paar neue Karten gekauft (SDHC) und das Lesen einzelner 
Sektoren funktioniert jetzt. D.h. die Sektoren werden komplett gelesen. 
Das Problem war, dass ich den Stop Transfer command gesendet hatte, 
bevor der DMA den Datentransfer beendet hatte.

Das einzige Problem was ich jetzt noch habe ist folgendes: wenn ich 
innerhalb kurzer Zeit mehrmals Sektoren von der Karte lesen will, dann 
schlägt dies nach nicht genau reproduzierbarer Zeit fehl, und die Karte 
meldet beim Lesebefehl 'illegal command'. Wenn ich den Lesebefehl 
wiederhole, dann bleibt es dabei - die Karte scheint sich 'aufzuhängen'. 
Woran könnte dies liegen?

Gruss
Tobias

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Vielleicht versehentlich CMD55 gesendet?

von Tobias P. (hubertus)


Lesenswert?

Hallo Niklas

nein, das war es nicht. Mittlerweile scheint es so, dass ich den Fehler 
gefunden habe - der Abblockkondensator, den ich an meine Kabel zur 
SD-Karte angelötet hatte, hatte anscheinend eine kalte Lötstelle, wo was 
unterbrochen wurde. Und da die Karte beim Lesen doch ein wenig Strom 
braucht, vermute ich, dass das Problem daran lag, denn jetzt, wo ich den 
Kondensator wieder angelötet habe, funktioniert es :-)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.