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"?
Tach Oliver, das kommt drauf an. Musst du Blöcke wiederholen, weil nicht korrekt übertragen? Sonst nein. Thor
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.
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.
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.
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
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.
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.
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...
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)
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?
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.
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
>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;)
Und hier noch ne dreckige Lösung in Assembler für GCC. Dort wird SPIF nicht abgefragt:)
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?
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" ;)
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....
>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 | }
|
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.
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.
>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?
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 ;)
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.