Forum: Mikrocontroller und Digitale Elektronik SDCard, Widerstands-Level-Converter, Speed?


von Oliver L. (ollil)


Lesenswert?

Hi,

nach einigen Optimierungen an meinem Code, komme ich auf folgende 
Übertragungsgeschwindigkeit via SPI Richtung SD-Card mit meinem
ATMega1284P mit 18.4320MHz.

lesen: 486,4 KB/Sek
schreiben: 216,1 KB/Sek.

Kommunikation läuft über SPI. Gemessen wurde eine Übertragung von 5000 * 
512 Byte (also 5000 Blöcke).

Ich verwende Widerstände als Level-Converter von 5V -> 3.3V da mein 
ATMega mit 5V läuft (Plan: http://pofo.de/tmp/P8000_WDC_SDCard.pdf)

Kann die m.M.n. schlechte Übertragungsgeschwindigkeit mit den 
Widerständen zusammenhängen, oder haben die keinen Einfluss auf die 
Geschwindigkeit. Nach dem Motto "Entweder es läuft egal wie 5V->3.3V 
konvertiert wird, oder es läuft gar nicht"?

von Alex S. (thor368)


Lesenswert?

Tach Oliver,

das kommt drauf an. Musst du Blöcke wiederholen, weil nicht korrekt 
übertragen? Sonst nein.

Thor

von Oliver L. (ollil)


Lesenswert?

Ich bekomme keinen Fehler, aber ich hatte vielleicht vermutet, das er 
das intern irgendwo in der Abarbeitung schon handlet. Ich mache ja zum 
senden quasi nur

SPDR = data
while ( ! ( SPSR & ( 1 << SPIF ) ) );

Und es kann ja sein, das er da in dem while-loop ne ganze Weile hängt da 
die eigentliche Übertragung die im Hintergrund abläuft aufgrund von 
Wiederholungen "etwas dauert". Ich kenne SPI leider nicht, und weiss 
nicht was da im ATMega genau abläuft.

von Lukas K. (carrotindustries)


Lesenswert?

SD Karten haben manchmal die doofe Angewohnheit, sich zum Schreiben 
eines Blockes übermäßig viel Zeit zu lassen, wenn gerade Wear-Levelling 
ansteht. Einen Großteil der Zeit dürfte die SD-Karten Routine also beim 
Warten auf die Karte verplempern. Toggel' mal zu geeigneten Zeitpunkten 
einen Pin, um festzustellen, welcher Programmabschnitt wie lange 
braucht.

Prinzipiell könnte es helfen, den "Write Multiple Block"-Befehl (CMD25) 
zu verwenden, da beim Schreiben eines 512-Byte Blockes erst eine ganze 
Flash-Seite gelöscht- und neu beschrieben wird. Diese Zeit ist 
unabhängig davon, ob du nur ein Bit in dieser Seite oder die ganze Seite 
ändern willst. Schreibe also in möglichst großen Blöcken.

von Oliver L. (ollil)


Lesenswert?

Muss ich mal schauen - grundsaetzlich bin ich bei der eigentlichen 
Verwendung der SD-Card von der Byteanlieferung des Host-Systems 
abhaengig. Es liefert aber in der Regel zw. 512 und 4K Blöcke an. Ich 
könnte mal messen, wie oft welche Blockgrößen ankommen. Wenn es 
hauptsaechlich >512B Blöcke sind, kann ich mir das mal anschauen.

von Sascha W. (sascha-w)


Lesenswert?

Oliver Lehmann schrieb:
> Ich bekomme keinen Fehler, aber ich hatte vielleicht vermutet, das er
> das intern irgendwo in der Abarbeitung schon handlet. Ich mache ja zum
> senden quasi nur
>
> SPDR = data
> while ( ! ( SPSR & ( 1 << SPIF ) ) );
>
> Und es kann ja sein, das er da in dem while-loop ne ganze Weile hängt da
> die eigentliche Übertragung die im Hintergrund abläuft aufgrund von
> Wiederholungen "etwas dauert". Ich kenne SPI leider nicht, und weiss
> nicht was da im ATMega genau abläuft.

du kannst nur die Reihenfolger der Befehle optimieren, dazu musst du dir 
den ASM-Code mal anschauen, die maximale Geschwindigkeit erreichst du 
wenn die Befehle so angeordnet sind, das alle Operationen (gelesenes 
Byte speichern, Adresse weiterzählen, ..., Rücksprung der Loop) während 
der SPI-Übertragung ablaufen und vor dem OUT SPDR,.. nur das warten auf 
SPIF steht.
Aber wie schon gesagt, machen die Karten beim schreiben alle paar Blöcke 
erst mal ne 'Gedenkpause'.
Die R-Levelshifter hab ich bei mir auch in mehreren Schaltungen mit knap 
10MHz SPI-Clock laufen.

Sascha

von Lukas K. (carrotindustries)


Lesenswert?

Wenn man die Kapazität der SD-Karten-Eingänge am Spannungsteiler durch 
einen Kondensator 'am oberen Widerstand' kompensiert, sollten auch noch 
mehr als 10MHz drin sein.

von holger (Gast)


Lesenswert?

Bei deinem Prozessor kannst du einen der USARTs für SPI nehmen.
Die USARTS haben einen zwei Byte FIFO und sind beim 1284p das
bessere SPI.

von Oliver L. (ollil)


Lesenswert?

Hi holger,

kannst du mir da ein paar mehr Hinweise finden? Wie mache ich das, oder 
wo finde ich mehr Infos drüber?

Bzgl. ASM-Code Optimierung. Habe schon recht viel erreicht..
komme von 372 KB/sec. lesen und mit Code-Optimierung, inlining komme ich 
auf 486 lesen.

schreiben 166 -> 216 KB/sec.

http://www.matuschek.net/atmega-spi/ war da ganz hilfreich...

von Lukas K. (carrotindustries)


Lesenswert?

Ist SPI wirklich der Flaschenhals?

von Oliver L. (ollil)


Lesenswert?

sicherlich nicht beim schreiben. Habe hier eine kleine 128MB Karte, die 
schreibt so schnell wie sie liest. Aber so kleine Karten bekommt man 
heute ja nicht mehr wirklich, und die größeren haben intern sicherlich 
4K Blöcke oder größer und sind deswegen - vermute ich mal - langsamer.
Aber über 370KB/Sek. komme ich beim besten Willen nicht. Mit 
Checksumming bricht das ganze auch nochmal um die Hälfte ein, aber  mein 
CRC-Code ist nicht optimiert (Performance-Messungen mache ich ohne 
Checksumming)

von Oliver L. (ollil)


Lesenswert?

Hat schonmal wer SPI über nen USART gemacht? Ist das wirklich fixer als 
über die SPI-Schnittstelle? Kommt man beim USART überhaupt in den Genuss 
des 2Byte-Buffers?

von Oliver L. (ollil)


Lesenswert?

Ich habe jetzt gestern nochmal den erzeugten ASM-Code studiert, und u.a. 
auch Multiblock-Read+Write implementiert. Ich komme nun auf folgende 
Übertragungsraten:

Multiblock lesen: 570KB/Sec
Multiblock schreiben: 696KB/Sec
Singleblock lesen: 496KB/Sec
Singleblock schreiben: 496KB/Sec.

Geschrieben wurden 2500 KB. Bei Multiblock in 4K Blöcke unterteilt, bei 
Singleblock - logisch - 512B Blöcke.

Nun denke ich, mehr ist bei meinem ATMega1284P, 18,4320MHz und der 
Hardware-SPI-Schnittstelle nicht drinn.... oder doch? ;)

Vor allem Singleblock-Schreiben scheint auch enorm von der SD-Card 
abzuhängen. Ich habe hier auch Karten, die schreiben nur 1/6 so fix.

von Sascha W. (sascha-w)


Lesenswert?

Oliver Lehmann schrieb:
> Ich habe jetzt gestern nochmal den erzeugten ASM-Code studiert, und u.a.
> auch Multiblock-Read+Write implementiert. Ich komme nun auf folgende
> Übertragungsraten:
>
> Multiblock lesen: 570KB/Sec
> Multiblock schreiben: 696KB/Sec
> Singleblock lesen: 496KB/Sec
> Singleblock schreiben: 496KB/Sec.
>
> Geschrieben wurden 2500 KB. Bei Multiblock in 4K Blöcke unterteilt, bei
> Singleblock - logisch - 512B Blöcke.
>
> Nun denke ich, mehr ist bei meinem ATMega1284P, 18,4320MHz und der
> Hardware-SPI-Schnittstelle nicht drinn.... oder doch? ;)
wohl kaum, denn bei 9MHz SPI-Clock hast du ja pro übertragenem Byte nur 
16 Takte Zeit - und da wird nicht mehr viel.

Aber was zerbrichst du Dir hier überhaupt den Kopf?
Das ganze ist ja nur für ein SDCard-Clean-Device zu gebrauchen - wo 
sollen den in deinem ATmega sonst sinvolle Daten in der Menge herkommen?

Sascha

von holger (Gast)


Angehängte Dateien:

Lesenswert?

>Multiblock lesen: 570KB/Sec
>Multiblock schreiben: 696KB/Sec
>Singleblock lesen: 496KB/Sec
>Singleblock schreiben: 496KB/Sec.

Das ist aber schon ganz anständig.
Würdest du mal die Loops zum lesen und schreiben
der 512Byte Blöcke zeigen?

Im Anhang mal mein Testprogramm für SPI und SPI per USART.
Der SPI Takt ist 8MHz.

>Nun denke ich, mehr ist bei meinem ATMega1284P, 18,4320MHz und der
>Hardware-SPI-Schnittstelle nicht drinn.... oder doch? ;)

Beim schreiben könnte noch was gehen wenn du SPI per USART machst.
770us mit SPI und 520us mit SPI over USART.
Beim lesen war der Unterschied nicht so groß.
Vieleicht noch nicht genug optimiert;)

von holger (Gast)


Angehängte Dateien:

Lesenswert?

Und hier noch ne dreckige Lösung in Assembler für GCC.
Dort wird SPIF nicht abgefragt:)

von Oliver L. (ollil)


Lesenswert?

Ich weiss leider nicht mehr wie ich 570 KB/Sec. lesen gemessen habe... 
ist für mich aktuell nicht mehr nachvollziehbar - ich komme nur noch so 
auf 440 KB/Sec.
Der Code ist hier:

http://cvs.laladev.org/index.html/~checkout~/P8000/P8000_WDC_Emulator/P8000_WDC_Emulator/mmc.c?rev=1.10&content-type=text/plain

Könnte etwas schwer zu lesen sein, da er verschiedene Konfigurationen 
enthällt. Checksumming, Multiblockoperationen mit vorheriger Meldung 
wieviel Blöcke denn zu übertragen sind (ist aber minimal langsamer) und 
"emuliertes" Multiblock-IO. Einfach alle ifdefs wegdenken ;)

Wenn ich das richtig sehe, hast du aber den gleichen loop optimiert ;)

Achso - und bzgl. Zeit. Ich ersetze hier einen alten WDC-Controller 
welcher sich mit einer PIO des Hauptrechners unterhalten hat. Problem 
ist, das der, nachdem der die Daten über die PIO geschickt hat, gerne 
eine Antwort hätte. Ich schreibe in dieser Zeit zwischen "Daten sind 
angekommen" und "Antwort muss raus", die Daten bereits auf die SD-Card. 
Es kommen zwischn 512 und 4096 Byte Daten an, oder werden abgeholt. 
(Bidirektionale Schnittstelle)
Da hab ich nicht Unmengen an Zeit.... klar, ist auch etwas akademisch - 
aber wenns fixer geht - warum nicht fixer machen?

von Oliver L. (ollil)


Lesenswert?

Ok,

mein USART ist so initialisiert:

UBRR1 = 0;
USARTDDR |= ( 1 << XCKPIN );
UCSR1C = ( 1 << UMSEL01 ) | ( 1 << UMSEL00 );
UCSR1B = ( 1 << RXEN1 ) | ( 1 << TXEN1 );
UBRR1 = ne niedrige Baudrate

[sd card init]

UBRR1 = 0x0000;

[sd card read]


Damit komme ich jetzt auf 244,62 KB/Sek.

Ist ja fast die Hälfte vom Hardware-SPI - man könnte vermuten, das der 
"2fach-SPI-Clock-Modus" hier irgendwie noch "fehlt" ;)

von Oliver L. (ollil)


Lesenswert?

Mit deinem SPI Fast-Read komme ich auf 582,75 KB/Sec... irgendwie traue 
ich dem aber nicht.... jedoch auch mit aktivem Checksumming (und dann ca 
321,34 KB/Sec) findet er keine Übertragungsfehler....

von holger (Gast)


Lesenswert?

>Mit deinem SPI Fast-Read komme ich auf 582,75 KB/Sec... irgendwie traue
>ich dem aber nicht..

Ich schon;) Das kommt aus meinen eigenen optimierten SD Karten Routinen.
Die Reihenfolge im Code ist extrem wichtig.

Dein Code:
1
   /* read first byte */
2
    send_dummy_byte();
3
    *buffer = recv_byte();
4
5
    /* read the remaining 511 bytes */
6
    do {
7
        xmit_byte ( 0xff ); /* send dummy byte */
8
        buffer++;
9
        wait_till_send_done();
10
11
// Ab hier verbrennst du Zeit
12
        *buffer = recv_byte();
13
        i++;
14
    } while ( i<bytes );

Mein Code:
1
  SPDR = 0xFF;
2
  while(!(SPSR & (1<<SPIF)));
3
  by = SPDR;   // get first byte, but store later !
4
5
  SPDR = 0xFF;
6
7
  i = 511;
8
  while(i)
9
    {
10
       *p++ = by;
11
       i--;
12
    while(!(SPSR & (1<<SPIF)));
13
// Ich verbrenne nur in dieser Zeile Zeit
14
       by = SPDR;   // get next byte, but store later !
15
    SPDR = 0xFF;
16
    }

von Oliver L. (ollil)


Angehängte Dateien:

Lesenswert?

Jo Schon klar,

ich hatte das i++ auch mal davor, aber das wurde etwas langsamer. Sah so 
aus:
1
    /* read first byte */
2
    send_dummy_byte();
3
    *buffer = recv_byte();
4
5
    /* read the remaining 511 bytes */
6
    do {
7
        xmit_byte ( 0xff ); /* send dummy byte */
8
        buffer++;
9
        i--;
10
        wait_till_send_done();
11
        *buffer = recv_byte();
12
    } while ( i );
13
14
    buffer = buffer - ( bytes - 1 );

Und dann habe ich hier noch eine ziemlich dumme Karte SD-Card, der geht 
das teilweise zu schnell - die will am Anfang der Write-Routinen immer 
eine
1
do {
2
  SPDR = 0xFF;
3
  while ( ! ( SPSR & ( 1 << SPIF ) ) );
4
} while( !SPDR );

Schleife haben... das geht natuerlich auch etwas auf die Performance bei 
den anderen Karten...


Aber - warum ist mein USART nur halb so fix? Hab nochmal das C-File 
angehangen.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Hier noch was zum SPI Tuning:
http://www.matuschek.net/atmega-spi/

Vorm Tuning konnte ich mitm AVR grade so ne mp3 von SD Karte auf nen MP3 
Dekoder schubsen (auf SPI Maximaltakt).
Mitm Tuning ist noch ne menge Zeit über.

von holger (Gast)


Lesenswert?

>Aber - warum ist mein USART nur halb so fix? Hab nochmal das C-File
>angehangen.

Weil

unsigned char spi_transfer ( unsigned char value )

eine Anfängerroutine ist. Die wartet auf jeden Transfer.
Hast du dir meinen Code überhaupt mal angesehen?

von Oliver L. (ollil)


Lesenswert?

Jo, hatte mir deine Quelle schon angeschaut ;)
Du hast mehr oder weniger den UDRE-loop rausgenommen.

Komme so auf 750 KB/Sec. lesen.
Aber ich bau mir jetzt mal noch eben checksumming ein, ob das was ich da 
lese auch korrekt ist ;)

von Oliver L. (ollil)


Lesenswert?

OK, es fehlte das Abfragen des Startbytes 0xFE. Mit der Abfrage bringe 
ich es doch nur auf 511,25 KB/Sek. Und das ist immer noch lahmer als via 
SPI.
Das CMD wird noch mit der "Anfänger Routine" gesendet.

Dort habe ich es irgendwie mit deinem USART_WRITE + FAST_READ nicht dazu 
gebracht, das es sich irgendwann nicht aufhängt.

Das war mein Versuch - aber im mmc_cmd ist irgendwo noch n Fehler, er 
findet dann beim Start der 1. Übertragung als Response von der SD-Card 
0x44, geht raus aus mmc_cmd, und dann gehts in mmc_read_block aber nicht 
weiter, weil der als Antwort aufs Lese-CMD 0 erwartet.
1
uint8_t mmc_cmd ( uint8_t *cmd )
2
{
3
    uint8_t tmp = 0x80;
4
    uint8_t i = 10;
5
    uint8_t a=6;
6
7
    do
8
    {
9
        while(!(UCSR1A & (1<<UDRE1)));
10
        UDR1 = *cmd++;
11
        while(!(UCSR1A & (1<<UDRE1)));
12
        UDR1 = *cmd++;
13
        a -= 2;
14
        //spi_transfer ( *cmd++ );
15
        //spi_transfer ( *cmd++ );
16
    } while(a);
17
18
    while(!(UCSR1A & (1<<TXC1)));
19
20
    do {
21
        while ( ! ( UCSR1A & ( 1 << UDRE1 ) ) );
22
        UDR1 = 0xFF;
23
        while ( ! ( UCSR1A & ( 1 << RXC1 ) ) );
24
        tmp = UDR1;
25
        uart_putc('>');
26
        uart_putc_hex(tmp);
27
//        tmp = spi_transfer ( 0xff );
28
    } while ( ( tmp & 0x80 ) && --i );
29
    uart_putc('.');
30
    return ( tmp );
31
}
32
33
uint8_t mmc_read_block ( uint8_t *cmd, uint8_t *buffer, uint16_t bytes )
34
{
35
    uint16_t i = 1;
36
    uint8_t by;
37
    uint16_t crc;
38
39
    CLRSSLINE;
40
41
    /* send command */
42
    uart_putc('-');
43
    if ( mmc_cmd ( cmd ) != 0 ) {
44
        SETSSLINE;
45
        return ( 1 );
46
    }
47
    uart_putc('#');
48
49
    while ( 1 ) {
50
        UDR1 = 0xFF;
51
        while ( ! ( UCSR1A & ( 1 << RXC1 ) ) );
52
53
        if ( UDR1 == 0xFE )
54
            break;
55
    }
56
57
    while ( ! ( UCSR1A & ( 1 << UDRE1 ) ) );
58
    UDR1 = 0xFF;
59
    while ( ! ( UCSR1A & ( 1 << RXC1 ) ) );
60
    by = UDR1;
61
62
    while ( ! ( UCSR1A & ( 1 << UDRE1 ) ) );
63
    UDR1 = 0xFF;
64
65
    do {
66
        *buffer++ = by;
67
        i++;
68
        while ( ! ( UCSR1A & ( 1 << RXC1 ) ) );
69
        by = UDR1;
70
        //      while(!(UCSR1A & (1<<UDRE1)));
71
        UDR1 = 0xFF;
72
    } while ( i < bytes );
73
74
    *buffer = by;               // store last byte in buffer while SPI module shifts in crc part1
75
76
    while ( ! ( UCSR1A & ( 1 << UDRE1 ) ) );
77
78
    UCSR1A |= ( 1 << TXC1 );
79
    UDR1 = 0xFF;
80
    while ( ! ( UCSR1A & ( 1 << TXC1 ) ) ); // wait for end of transmission
81
82
    while ( ! ( UCSR1A & ( 1 << RXC1 ) ) ) by = UDR1; // flush receive FIFO
83
    SETSSLINE
84
85
    return ( 0 );
86
}

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.