Halloo
Angenommen, ich schreiben Bytes über SPI zu einem Slave.
Der Master (hier der µC) schreibt byteweise in ein SPI-Datenregister und
setzt danach ein Tx-Request. Dann sollten doch mit dem SCK-Takt über die
MISO und MOSI Leitungen die Bits weitergeschoben werden.
Frage ist nun: Zu dem Zeitpunkt, nachdem das Byte in das SPI-Register
geschrieben wurde, wer kümmert sich um die Übertragung via SCK? Wird das
über einen Bypass erledigt oder muss die MCU direkt ran?
Ich will, dass die MCU so wenig wie nötig mit der SPI Sache belastet
wird, denn dann bleiben andere Anwendungen in der Zeit der Übertragung
hinterher.
> das Byte in das SPI-Register>geschrieben wurde, wer kümmert sich um die Übertragung via SCK?
Hä? Was?
Du solltest dir zuerst mal in aller Ruhe eine SPI Verbindung in einem
Datenblatt ansehen. Wie ist das verschaltet, was passiert dort, was muss
die Software machen..
ich kenn SPI.
Nur wollte ich mal weiter ins Detail.
Aber es scheint als wenn nach Setzen des TxReq-Bit sich die CPU von der
Sache abwenden kann und anderes machen kann. Bis eben ein INT auftritt.
Oder mann wartet nach Start der Übertragung im Poll-Verfahren auf
SPIF-Bit.
...das unterscheidet einen Microcontroller von einem schnöden
Microprozessor.
Der uController hat auch Funktionsgruppen in Hardware drin, die
bestimmte Aufgaben autonom erledigen...
SPI kennen, heisst das Detail kennen. Interrupts lohnen sich bei den
maximalen transferraten uebigens kaum. Bei Clk/2 vergehen 16 clocks,
wenn man da in der interruptrouting etwas pusht und popt ist die Zeit
auch schon um.
Nullpainter wrote:
> Bei Clk/2 vergehen 16 clocks,
Ich hab mal irgendwo gelesen, dass es genaugenommen 18 Clocks sind, bis
das nächste Byte wieder ins Register geladen werden darf.
Trotz allem geb ich dir Recht ;) Ich hab auch bei meinen
SD-Karten-Fullspeed-sources (Clk/2) keine Interruptgesteuerte SPI
Schnittstelle. Das lohnt sich einfach nicht. Die paar takte kann man
eben auch warten.
Absolut gesehen sind das bei 20MHz weniger als eine µS.
Bei mir hat die Zeitbetrachtung folgenden Grund:
Ich sende dem ENC28J60-Baustein über SPI @ 8MHz genau 122 Byte
Stack-Daten. (42+80 UDP)
Bei dieser SPI-Geschwindigkeit wird also 1 MegaByte/s übertragen. Oder
anders: 1 Byte/µs
Auf meine 122 Byte wären das dann 122µs an Zeit die vergehe würde, bis
der Datenstrom im ENC ist.
Am Oszi mess ich aber: 380µs.
Ist der Rest alles Warte-Zeit?
Ich hab schon überlegt via DMA die Daten in das SPI-Register zu
schieben. Ich weis nicht ob das Abhilfe schafft.
Zeig' doch mal den Code, wie du die Daten reinschaufelst. 1 MiB/s
erreichst du natürlich nur, wenn das SPI im Dauerbetrieb taktet und das
ist ja nicht möglich. Das Register zu laden benötigt ja auch etwas Zeit.
Mehr als das Dreifache der Zeit ist allerdings schon etwas extrem.
Das beste ist, du guckst dir während der Übertragung die SCK Leitung auf
nem Oszi an. Am besten als Triggerung den Beginn (oder kurz vorher) der
Übertragung des ersten Bytes. zB mit CS=Low, oder per extra Pin.
Dort siehst du sofort, wie "effektiv" deine Übertragung ist. Also wie
groß die Pausen zwischen den Bytes sind... Somit siehst du, obs am
Programm zwischen den ausgaben (zB Daten aus RAM.. holen) liegt, oder
obs am Programmeinsprung.. liegt...
Wenn man's nicht in ASM macht kann schon was an Zeit verplemepert
werden. Wenn mn zB Wartet bis das SPI fertig ist, und dann erst das byte
holengeht, mit Arrayindexrechnung und so, ist einiges moeglich. Ich
wuerd diesen Ladeprozess mal im simulator laufen lassen, oder falls das
nicht geht, am Objekt selbst mit einem puls an einem Debugpin jeweils.
@ Horst (Gast)
>Aber es scheint als wenn nach Setzen des TxReq-Bit sich die CPU von der>Sache abwenden kann und anderes machen kann. Bis eben ein INT auftritt.
Ja. Das Generieren von SCK und rausschieben der Bits macht das SPI-Modul
allein.
>Oder mann wartet nach Start der Übertragung im Poll-Verfahren auf>SPIF-Bit.
Genau. Sinnvollereise wird man erst dan nächste Datenbyte laden und dann
pollen.
MFG
Falk
Hallo, so jetzt gehts weiter..
ich habe also mal das SCK Signal über den gesamten Vorgang aufgenommen.
Es ist also deutlich zu sehen: Lücken zwischen den Bytes.
90% der Daten sind mein Stack der übertragen wird. Die Funktion zum
Schreiben des Stack ist die folgende:
1
voidenc28j60WriteBuffer(uint16_tlen,uint8_t*data)// Länge (Byte), Startadresse Data
2
{
3
// assert CS
4
ENC28J60_CONTROL_PORT&=~(1<<ENC28J60_CONTROL_CS);
5
// issue write command
6
SPDR=ENC28J60_WRITE_BUF_MEM;
7
while(!(SPSR&(1<<SPIF)));
8
while(len--)
9
{
10
// write data
11
SPDR=*data++;
12
while(!(SPSR&(1<<SPIF)));
13
}
14
// release CS
15
ENC28J60_CONTROL_PORT|=(1<<ENC28J60_CONTROL_CS);
16
}
Der Code ist vielermals im Netz zu finden, ich habe den eben auch
genommen. Die Daten werden per Zeiger zugewiesen, was doch schon sehr
zügig sein sollte.
Hier konnte ich dann mit einem Debug-Pin die Wartezeiten feststellen.
Für das erste Bild gilt:
1
while(len--)
2
{
3
// write data
4
SPDR=*data++;
5
PORTC=1;
6
while(!(SPSR&(1<<SPIF)));
7
PORTC=0;
8
}
Hier ist also die Poll-Zeit des SPIF-Bit aufgezeichnet.
Für das zweite Bild:
1
while(len--)
2
{
3
// write data
4
PORTC=1;
5
SPDR=*data++;
6
PORTC=0;
7
while(!(SPSR&(1<<SPIF)));
8
}
Hier also das Schreiben eines Bytes in das SPI Register. Aber wieso
inklusive SCK-Taktung?
Ich denke mal die Warte-Zeit auf das SPIF kann ich nicht verringern.
Beim Transferieren der Daten in das SPI-Datenregister jedoch müsste doch
was machbar sein. Evtl. DMA?
Horst wrote:
> Ich denke mal die Warte-Zeit auf das SPIF kann ich nicht verringern.> Beim Transferieren der Daten in das SPI-Datenregister jedoch müsste doch> was machbar sein. Evtl. DMA?
Hm, auf welcher Optimierungsstufe ist das kompiliert? Hast du eventuell
mal Assembler-code von der Write-Funktion? Komisch, dass das laden und
inkrementieren so "lange" braucht. Wobei das so lange ja garnicht ist ;)
Wenn ich mich nicht irre handelt es sich doch um einen 8-Bit AVR, die
haben doch garkeine DMA Unterstützung.
richtig, dort gibts kein DMA.
Der AVR dient nur zum Testen. EIgentlich läuft der Code auf einem
Fujitsu 32-Bit.
Dort sieht das aber alles sehr ähnlich aus.
@ Horst (Gast)
>ich habe also mal das SCK Signal über den gesamten Vorgang aufgenommen.>Es ist also deutlich zu sehen: Lücken zwischen den Bytes.
Yep.
>void enc28j60WriteBuffer(uint16_t len, uint8_t* data) // Länge (Byte),
Startadresse Data
>{> // assert CS> ENC28J60_CONTROL_PORT &= ~(1<<ENC28J60_CONTROL_CS);> // issue write command> SPDR = ENC28J60_WRITE_BUF_MEM;> while(!(SPSR & (1<<SPIF)));> while(len--)> {> // write data> SPDR = *data++;> while(!(SPSR & (1<<SPIF)));> }> // release CS> ENC28J60_CONTROL_PORT |= (1<<ENC28J60_CONTROL_CS);>}>Der Code ist vielermals im Netz zu finden, ich habe den eben auch>genommen. Die Daten werden per Zeiger zugewiesen, was doch schon sehr>zügig sein sollte.
Naja, hier wird man wohl um Assembler nicht rumkommen. Der Trick ist,
während die alte Übertragung läuft schon die neuen Daten zu holen. In C
ist es nicht sehr einfach zu garantieren, dass der Compiler das nicht in
die flasche Richtung optimiert.
Versuch mal das.
void enc28j60WriteBuffer(uint16_t len, uint8_t* data) // Länge (Byte),
Startadresse Data
{
uint8_t tmp;
// assert CS
ENC28J60_CONTROL_PORT &= ~(1<<ENC28J60_CONTROL_CS);
// issue write command
SPDR = ENC28J60_WRITE_BUF_MEM;
while(!(SPSR & (1<<SPIF)));
while(len--)
{
// write data
tmp = *data++;
while(!(SPSR & (1<<SPIF)));
SPDR = tmp;
}
// release CS
ENC28J60_CONTROL_PORT |= (1<<ENC28J60_CONTROL_CS);
}
>Ich denke mal die Warte-Zeit auf das SPIF kann ich nicht verringern.
Kaum, das SPI-Interface muss ja die Daten erstmal übertragen, bevor neue
reingeschrieben werden können.
>Beim Transferieren der Daten in das SPI-Datenregister jedoch müsste doch>was machbar sein. Evtl. DMA?
Assembler + Knoff Hoff!
MFG
Falk
interessante Links! Danke
mit einer Optimierung O1 konnte ich eben 40% Zeitersparnis erreichen :-)
DIe anderen Optimierungen brachten dann nichts mehr.
@Falk, guter Tipp. Aber erscheint dann am while zu hängen da die
Bedingung bein ersten Mal wohl nicht erfpllt ist.
Mir ist es mal gelungen, 24Bytes innerhalb einer ext. Int. ISR
herauszutakten. Das ganze hat für den kompletten Aufruf(einsprung+push),
Bearbung und Beenden(pop) etwa 35Mikrosekunden gedauert. Das Ganze auf
nem mega128@16MHz.
hab mal diesen Code
Beitrag "Re: SPI Codetuning"
benutzt, und Optimierung Stufe 1 eingestellt.
Da wurden die 122 Byte in 152µs übertragen. (1,2µs pro Byte)
Das ist doch schon eine Steigerung.
Die Lücken sind auch deutlich kleiner.
Die Frage ist, ob man überhaupt Pollen sollte...
while(!(SPSR & (1<<SPIF)));
17c: 77 9b sbis 0x0e, 7
17e: fe cf rjmp .-4
Oben drüber ist das "out". Wenn ich richtig gezählt habe, dann fällt der
16. Takt danach direkt auf das sbis.
Da erst mit dem 17. Takt die Transmission beendet ist, wird nicht
übersprungen und der rjmp wird ausgeführt. Dann dauert es nochmal 4
Takte, bis man aus der Schleife draußen ist, da rjmp=2 + aktives_sbis=2.
Wenn man jedoch einfach 16 Takte durch nops verliert (oder
Platzsparender rjmp +0), dann wäre man eigentlich genau richtig im
Timing.
Die zweite Frage ist, ob das auch so hinhaut. Hat das jemand mal
ausprobiert oder willst du das mal ausprobieren Horst?
>Die zweite Frage ist, ob das auch so hinhaut. Hat das jemand mal>ausprobiert oder willst du das mal ausprobieren Horst?
Ja, hab ich. das geht.
Hab aber statt der nops immer einen temp.ausgabepuffer geladen und
andere dinge gemacht. und das ganze komplett ohne schleife..
@ Matthias L. (lippy)
>Hab aber statt der nops immer einen temp.ausgabepuffer geladen und>andere dinge gemacht. und das ganze komplett ohne schleife..
Ohne Schleife? Wie willst du da variable Datenblöcke übertragen?
MfG
Falk