Hallo Forum,
habe einen C-Schnipsel in ASM umgewandelt, um die Ausführungszeit zu
frisieren. Der Schnipsel prüft, ob neue Daten im SPI-Puffer sind
(SPIC.IF gesetzt). Falls ja oder falls ein Timeout (R19:R18 von 0xFFFF
nach 0x0000 durchzählen) geschieht, dann gehts weiter. In C sieht das so
aus:
Wenn ich jetzt Register R19 und R18 für den Timeout-Zähler verwende,
überschreibe ich an anderer Stelle im Programm ungewollt Register :/
Wenn ich R21 und R20 nehme, dann geht alles.
Darf ich diese Register wirklich nutzen?
1
2
asm("LDI R20, 0x00");/* R18 auf 0xFF setzen, R18 als Zähler nutzen */ \
3
asm("LDI R21, 0x00");/* R19 auf 0xFF setzen, R19 als Zähler nutzen */ \
4
asm("Anfang:"); \
5
asm("LDS R24,0x08C2");/* SPIC.Status nach R24 laden */ \
6
asm("TST R24");/* Status.IF testen */ \
7
asm("BRLT Ende");/* Zum Ende springen, falls Bit 7 gesetzt */ \
8
asm("INC R20");/* R18 inkrementieren */ \
9
asm("BRNE Anfang");/* falls nicht 0, dann Sprung */ \
10
asm("INC R21");/* R18 dekrementieren */ \
11
asm("BRNE Anfang");/* falls nicht 0, dann Sprung */ \
12
asm("Ende:");
Hintergrund: Nachdem neue Daten im SPI-Puffer sind, soll der Puffer so
schnell wie möglich ausgelesen werden und für neue Daten frei werden, um
den Datendurchsatz des SPI zu maximieren.
Danke für eure Tipps! :)
VG
Christoph
Ich vermute, dass du den GCC nimmst:
http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_reg_usage
Aber: ein derartiges Inline-Assembler-Gehacke (verzeih' mir bitten den
Ausdruck) ist überhaupt nicht sinnvoll. Beim GCC benutzt man
sinnvollerweise dessen Constraint-System, um ihm eine maximale
Integration in die Optimierung zu ermöglichen. Dann legst du die
Register auch gar nicht mehr selbst fest, die du benutzt, sondern lässt
sie vom Compiler zuordnen.
Weiterhin passen deine Kommentare nicht zum Text (Kommentar behauptet,
irgendein Register wird mit 0xFF geladen, dabei wird ein ganz anderes
mit 0 geladen), und du benutzt absolute Zahlen, wo man besser Namen
übergibt.
@ Christoph M. (chrito)
>habe einen C-Schnipsel in ASM umgewandelt, um die Ausführungszeit zu>frisieren.
Und du glaubst, daß du damit großartig schneller bist?
>nach 0x0000 durchzählen) geschieht, dann gehts weiter. In C sieht das so>aus:>Zaehler16Bit=0;>while ((!(SPI2_PORT.STATUS& 0b10000000))&&(--Zaehler16Bit));
Hast du dir den entstandenen ASM-Code angeschaut? War der so schlecht
und langsam?
>Wenn ich R21 und R20 nehme, dann geht alles.>Darf ich diese Register wirklich nutzen?
Nein. Inlineassembler hat seine besonderen Regeln und
Kommunikationsmittel zum Compiler. Wenn du denn WIRKLICH eine Routine in
ASM beschleunigen willst, mach eine reine ASM-Funktion draus. Das ist
deutlich einfacher zu schreiben und man muss nicht so viele Randbedingen
beachten. Es gibt eine gute Application Note von Atmel dazu.
Atmel AT1886: Mixing Assembly and C with AVRGCC - Microchip
http://ww1.microchip.com/downloads/en/appnotes/doc42055.pdf>Hintergrund: Nachdem neue Daten im SPI-Puffer sind, soll der Puffer so>schnell wie möglich ausgelesen werden und für neue Daten frei werden, um>den Datendurchsatz des SPI zu maximieren.
Dazu braucht man kein ASM, dazu reicht C.
Der Trick liegt darin, daß man während der SPI-Übertragung schon die
nächsten Daten holt und in einer Variable zwischenspeichert und dann
erst prüft, ob SPI wieder neue Daten aufnehmen kann. Beim Lesen per SPI
muss man so oder so erstmal die Daten aus dem Register auslesen und den
neuen Zyklus starten. Aber auch hier kann man durch eine
Zwischenvariable Zeit gewinnen.
Prinzipiell darfst du dem Compiler überhaupt niemals dazwischenpfuschen,
ohne ihm das explizit mitzuteilen.
Wie sieht den der vom Compiler generierte Code (mit Optimierung) aus?
Und ansonsten sind die Kommnetare in deinem Code das klassische Beispiel
dafür, wie es nicht sein sollte...
> asm("INC R20"); /* R18 inkrementieren */ \> asm("INC R21"); /* R18 dekrementieren */ \
Oliver
000005A0 LDD R24,Z+2 Load indirect with displacement
6
000005A1 TST R24 Test for Zero or Minus
7
000005A2 BRLT PC+0x0C Branch if less than, signed
8
000005A3 LDS R24,0x2176 Load direct from data space
9
000005A5 LDS R25,0x2177 Load direct from data space
10
000005A7 SBIW R24,0x01 Subtract immediate from word
11
000005A8 STS 0x2176,R24 Store direct to data space
12
000005AA STS 0x2177,R25 Store direct to data space
13
000005AC OR R24,R25 Logical OR
14
000005AD BRNE PC-0x0D Branch if not equal
Und mit den richtigen Kommentaren hier noch einmal:
1
asm("LDI R20, 0x00");/* R20 auf 0x00 setzen, R20 als Zähler nutzen */ \
2
asm("LDI R21, 0x00");/* R21 auf 0x00 setzen, R21 als Zähler nutzen */ \
3
asm("Anfang:"); \
4
asm("LDS R24,0x08C2");/* SPIC.Status nach R24 laden */ \
5
asm("TST R24");/* Status.IF testen */ \
6
asm("BRLT Ende");/* Zum Ende springen, falls Bit 7 gesetzt */ \
7
asm("INC R20");/* R20 inkrementieren */ \
8
asm("BRNE Anfang");/* falls nicht 0, dann Sprung */ \
9
asm("INC R21");/* R21 dekrementieren */ \
10
asm("BRNE Anfang");/* falls nicht 0, dann Sprung */ \
11
asm("Ende:");
Würde schon behaupten, dass mein Code einige Takte schneller ist, da er
den Zähler mit zwei Registern realisiert und nichts in data space
wegspeichern muss.
Wie wäre es mit dem Lösen der Handbremse? Schalt' doch einfach mal
die Optimierung ein. Was dann rauskommt, habe ich oben gepostet, da
kann sich dein Assemblercode noch ein paar Takte dahinter verstecken.
Falk B. schrieb:> Der Trick liegt darin, daß man während der SPI-Übertragung schon die> nächsten Daten holt und in einer Variable zwischenspeichert und dann> erst prüft, ob SPI wieder neue Daten aufnehmen kann.
Die zu versendenden Daten liegen schon in einem Puffer bereit. Wenn ich
SPIC.Data während der Übertragung schreibe, dann erzeuge ich doch
Datenmüll!?
Am schnellsten muss es doch dann sein, direkt nach dem IF-Flag
auszulesen und das neue Byte zu schreiben.
Mit Interrupt hatte ich es probiert, da ist die Reaktion auf IF durch
den Overhead des IR langsamer. Ständig auf IF=1 prüfen und dann
übertragen geht deutlich schneller.
@Christoph M. (chrito)
>> Der Trick liegt darin, daß man während der SPI-Übertragung schon die>> nächsten Daten holt und in einer Variable zwischenspeichert und dann>> erst prüft, ob SPI wieder neue Daten aufnehmen kann.>Die zu versendenden Daten liegen schon in einem Puffer bereit.
In einem Array. Das muss adressiert werden und die Daten aus dem RAM
gelesen werden.
> Wenn ich>SPIC.Data während der Übertragung schreibe, dann erzeuge ich doch>Datenmüll!?
Davon war keine Sekunde die Rede!
>Am schnellsten muss es doch dann sein, direkt nach dem IF-Flag>auszulesen und das neue Byte zu schreiben.
Fast.
>Mit Interrupt hatte ich es probiert, da ist die Reaktion auf IF durch>den Overhead des IR langsamer.
Logisch, das ist auch Unsinn.
> Ständig auf IF=1 prüfen und dann>übertragen geht deutlich schneller.
Die Erkenntnis ist nicht sonderlich neu ;-)
Zeigt mal vollständigen Code für deine Übertragung.
Beitrag "Re: Frage zu IR-Remote+LED-Strips an AVR"
Christoph M. schrieb:> Wenn ich SPIC.Data während der Übertragung schreibe, dann erzeuge ich> doch Datenmüll!?
Ja, leider. Das SPI der Xmegas hat keinen zusätzlichen Puffer
implementiert.
Du könntest allerdings eine USART im SPI-Modus benutzen, die hat sowas.
:)
@ Christoph M. (chrito)
>mit O3 bekomme ich:
Glaub ich nicht. Denn bei Optimierung schreibt der Compiler die Daten
ganz sicher nicht in den RAM zurück sondern rechnet mit einem Register!
>000007B1 LDS R24,0x2176 Load direct from data space>000007B3 LDS R25,0x2177 Load direct from data space>000007B5 SBIW R24,0x01 Subtract immediate from word>000007B6 STS 0x2176,R24 Store direct to data space>000007B8 STS 0x2177,R25 Store direct to data space
Das Rückschreiben hier ist verdächtig.
Ich glaub du hast nicht wirklich mit Optimierung übersetzt.
Christoph M. schrieb:> und er verwendet weiterhin zum zählen data space.
Unsinniges "volatile" irgendwo? Es gibt keinen Grund, warum der
Compiler das in den SRAM legen sollte.
Der Code, den ich oben gepostet habe, stammt vom Compiler, allerdings
mit -Os. -O3 nimmt man nur, wenn man zu viel Flash hat, denn es
sagt dem Compiler: „Platz spielt keine Rolle, Hauptsache, es wird
schnell.“ Da werden dann bspw. massiv Schleifen entrollt.
Ansonsten: poste einfach compilierfähigen Code statt des Zweizeilers
oben. Der Code, den ich für den Test compiliert hatte, war:
Jörg W. schrieb:> Christoph M. schrieb:>> Wenn ich SPIC.Data während der Übertragung schreibe, dann erzeuge ich>> doch Datenmüll!?>> Ja, leider. Das SPI der Xmegas hat keinen zusätzlichen Puffer> implementiert.>> Du könntest allerdings eine USART im SPI-Modus benutzen, die hat sowas.> :)
Ok, dann bin ich doch noch nicht ganz verkalkt :)
Also dachte ich mir, ist die schnellste Lösung, direkt auf das kommende
IF-Flag die Daten abzuholen und neue in SPIC.DATA hineinzuwerfen. Also
ungefähr so:
Ich denke, dass du das neue Byte sogar schon zum Senden anschieben
kannst, bevor du das vorige einliest. Die Empfangsrichtung ist ja
wenigstens gepuffert, anders als die Senderichtung.
Aber Falks Frage bleibt natürlich: wofür soll der Timeout gut sein?
Der schlägt nur dann zu, wenn du keinen SPI-Takt im Master gestartet
hast, aber dann hast du einfach einen Bug im Code. Sofern garantiert
ist, dass der Takt des Masters läuft, dann ist nach 8 SPI-Takten
ein Schiebevorgang zu Ende.
Jörg W. schrieb:> Aber Falks Frage bleibt natürlich: wofür soll der Timeout gut sein?> Der schlägt nur dann zu, wenn du keinen SPI-Takt im Master gestartet> hast, aber dann hast du einfach einen Bug im Code. Sofern garantiert> ist, dass der Takt des Masters läuft, dann ist nach 8 SPI-Takten> ein Schiebevorgang zu Ende.
Der Timeout verhindert, dass der Controller in der while-Schleife hängen
bleibt, falls das SPI-Modul nie das SPIC.IF setzt. Kann z.B. passieren,
falls das SPI nicht angschlossen ist (dann soll das Gerät trotzdem noch
funktionieren) oder ein Übertragungsfehler geschieht.
@Christoph M. (chrito)
>Der Timeout verhindert, dass der Controller in der while-Schleife hängen>bleibt, falls das SPI-Modul nie das SPIC.IF setzt.
Kann nie passieren, es sein denn, dein uC ist massiv defekt.
> Kann z.B. passieren,>falls das SPI nicht angschlossen ist
Kaum, denn das SPI ist fester Siliziumbestandteil.
> (dann soll das Gerät trotzdem noch>funktionieren) oder ein Übertragungsfehler geschieht.
Auch Unsinn. Du kannst mit SPI beliebig Daten hin- und herschaufeln,
ganz egal ob da eine IC dranhängt oder nicht, denn es gibt keine aktive
Rückmeldung wie bei I2C.
>ungefähr so:>while ((!(SPIC.STATUS& 0b10000000))&&(--Zaehler16Bit)); //warte>SPI_Empfangspuffer[Pufferindex] = SPIC.DATA; // hole Byte>SPIC.DATA = SPI_Sendepuffer[Pufferindex]; // sende Byte
Nö, so nicht. Lies mal das hier.
http://www.matuschek.net/atmega-spi/
Gilt auch Für die Xmegas, da gibt es keinen nennenswerten Unterschied.
Christoph M. schrieb:> Der Timeout verhindert, dass der Controller in der while-Schleife hängen> bleibt, falls das SPI-Modul nie das SPIC.IF setzt.
Falls das passieren kann, wäre folgende Lösung möglich wenn's wirklich
schnell sein soll:
Der Timeout wird asynchron per Timer-ISR ausgelöst:
1
#include<setjmp.h>
2
#include<stdbool.h>
3
#include<avr/io.h>
4
#include<avr/interrupt.h>
5
6
staticjmp_bufbuf_timeout;
7
8
boolsend(void)
9
{
10
if(setjmp(buf_timeout))
11
{
12
// Timeout!
13
// Disable Timer and respective IRQ
14
__asm("; Timeout");
15
return0;
16
}
17
18
// Set Timer and enable respective IRQ.
19
// Code that might time-out
20
__asm("; Code");
21
return1;
22
}
23
24
ISR(INT0_vect)
25
{
26
longjmp(buf_timeout,1);
27
}
avr-gcc v8 -Os erzeugt für ATmega8 und send():
1
send:
2
push r28
3
push r29
4
in r28,__SP_L__
5
in r29,__SP_H__
6
ldi r24,lo8(buf_timeout)
7
ldi r25,hi8(buf_timeout)
8
rcall setjmp
9
or r24,r25
10
breq .L2
11
; Timeout
12
ldi r24,0
13
.L1:
14
pop r29
15
pop r28
16
ret
17
.L2:
18
; Code
19
ldi r24,lo8(1)
20
rjmp .L1
Damit ist der heiße Zweig frei von Warterei und Testen auf Timeout. Der
Ansatz entspricht einer setjmp/longjmp Exception, und der Overhead
wandert in den kalten Zweig, nämlich Handling von longjmp und
Stack-Unwind in der ISR:
Falk B. schrieb:> Kann nie passieren, es sein denn, dein uC ist massiv defekt.
Er muss nicht defekt sein, aber zumindest massiv fehlkonfiguriert:
wenn man dem SPI-Modul den Takt klaut, dann passiert natürlich
nichts mehr, es gibt also auch kein Interruptflag.
Bei ARMs ist dieses Phänomen häufiger anzutreffen, da viele
Peripheriemodule standardmäßig nicht vom Takt versorgt werden, damit
sie keine sinnlose Energie schlucken. Beim Xmega kann man die Takte
zwar auch ausschalten, aber sie sind standardmäßig alle an.