Forum: Compiler & IDEs Assembler Frage


von Sven (Gast)


Lesenswert?

Ich hoffe, es gehört hier hin.
Ich habe folgenden Code, den AVRGCC erzeugt hat:
1
    while(delay--) asm volatile("nop");
2
     1d4:   00 00        nop
3
     1d6:   01 97        sbiw    r24, 0x01   ; 1
4
     1d8:   00 97        sbiw    r24, 0x00   ; 0
5
     1da:   e1 f7        brne    .-8         ; 0x1d4

Im Grunde verstehe ich den Code: Er zählt Register r24 runter. Aber 
warum ist da das zweite sbiw, welches 0 subtrahiert?

: Verschoben durch Moderator
von Fred S. (kogitsune)


Lesenswert?

Optimierung eingeschaltet? Mit -O1 bekomme ich
1
uint16_t delay=100;  
2
while(delay--) asm volatile("nop");  
3
 32a:  00 00         nop
4
 32c:  01 97         sbiw  r24, 0x01  ; 1
5
 32e:  e9 f7         brne  .-6        ; 0x32a <main+0x18>

von c-hater (Gast)


Lesenswert?

Sven schrieb:

> Ich habe folgenden Code, den AVRGCC erzeugt hat:

Also schonmal keine Assemblerfrage. Vieles von dem Assemblercode, den 
der GCC erzeugt, ist nicht sinnvoll. Eben deswegen lohnt ja 
Assembleroptimierung auch immer noch...

>
1
>     while(delay--) asm volatile("nop");
2
>      1d4:   00 00        nop
3
>      1d6:   01 97        sbiw    r24, 0x01   ; 1
4
>      1d8:   00 97        sbiw    r24, 0x00   ; 0
5
>      1da:   e1 f7        brne    .-8         ; 0x1d4
6
>
>
> Im Grunde verstehe ich den Code: Er zählt Register r24 runter. Aber
> warum ist da das zweite sbiw, welches 0 subtrahiert?

Kein Ahnung. Eigentlich kann es nur drei Gründe dafür geben.

1) "sbiw R24,0" muß sinngemäß eigentlich als "NOP2" gelesen werden
2) es ist aus irgendeinem Grunde für den verschissenen C-Compiler 
wichtig, die MCU-Flags beim Austritt aus der Warteschleife in einem 
bestimmten Zustand zu haben, eben in dem, der sich nach einer 
Subtraktion 0-0 einstellt.
3) eine Kombination aus 1) und 2)

Ich würde auf 3) tippen...

von Klaus W. (mfgkw)


Lesenswert?

Das wurde vielleicht nicht wegoptimiert: das Abziehen von 1 könnte vom 
-- herrühren, das Abziehen von 0 zum Test für das while.

(Wobei ja eigentlich das Postfix-Dekrement nach dem Test kommen sollte, 
aber zum Ausgleich findet es ja nach dem Schleifenrumpf statt - dann 
müsste die Anzahl wieder hinkommen.)

Probiere doch mal mit mehr Optimierung zu kompilieren, vielleicht 
verschwindet die eine Subtraktion dann ja?

: Bearbeitet durch User
von Bastler (Gast)


Lesenswert?

Das zweite sbiw ist schlicht das noch nicht optimierte Testen auf 0.
Aber da der GCC so blöd ist, die Anweisung "nicht so stark optimieren" 
ernst zu nehmen, sollt man besser die ganzen 32k Code in ASM von Hand 
schreiben. Da lernt man was für's Leben!
(gefundene Ironie darf behalten werden)

von Nase (Gast)


Lesenswert?

c-hater schrieb:
> Ich würde auf 3) tippen...
Und ich auf ein krasses Unverständnis von der Arbeitsweise eines solch 
komplexen Werkzeuges wie GCC.

Es ist beim GCC (und bei allen anderen ernsthaften Compilern) nicht mehr 
sinnvoll, willkürlich nach dem Sinn irgendeines Schnippels zu fragen. 
Ganz einfach deshalb, weil zwischen C-Quelltext und ASM-Emission viel zu 
viele Schritte liegen.

Wenn du also in deinem Beispiel fragst, warum deine Schleife nicht 
effizienter (lies: ohne die scheinbar überflüssige Subtraktion) 
compiliert wurde, dann lautet die einzige richtige Antwort: Weil du es 
offenbar nicht vom Compiler verlangt hast (-O0)...

Im Übrigen erzeugt der GCC schon ziemlich effizienten Code. Betrachtet 
man neben der Effizienz des Codes auch die Effizienz des Programmierens 
('Time-to-market'), dann ist der Einsatz eines guten Compilers heute 
fast immer effizienter, als Hand-Optimiererei in Assembler.

von Sven (Gast)


Lesenswert?

Nett, was man so lernt...
Es ist ein mega16. Ich hatte mit -Os compiliert. Bei -O1 kommt dann 
raus:
1
 while(delay--) asm volatile("nop");
2
     1d2:  00 97         sbiw  r24, 0x00  ; 0
3
     1d4:  19 f0         breq  .+6        ; 0x1dc <ow_delay+0xa>
4
     1d6:  00 00         nop
5
     1d8:  01 97         sbiw  r24, 0x01  ; 1
6
     1da:  e9 f7         brne  .-6        ; 0x1d6 <ow_delay+0x4>
7
     1dc:  08 95         ret
Ob das nun besser ist?

Bei 2/3/s dann das von oben wieder.

Aber ich wäre ja schon froh, wenn mir ein Assembler-Guru das -0 erklären 
könnte. Es geht mir dabei mehr ums Verständnis, als um Effizienzfragen 
etc. Man will ja wissen, was da so unter der Motorhaube passiert...

von Sven (Gast)


Lesenswert?

Ach ja: Ich nutze ein uint16_t als Übergabewert. Scheint auch daran zu 
liegen. Wenn ich ein uint8_t nehmen, dann wird's:
1
  while(delay--) asm volatile("nop");
2
     1d4:  00 00         nop
3
     1d6:  81 50         subi  r24, 0x01  ; 1
4
     1d8:  88 23         and  r24, r24
5
     1da:  e1 f7         brne  .-8        ; 0x1d4 <ow_delay+0x2>

GCC wird schon wissen, was er tut ;-) Aber ich?

von Lattice User (Gast)


Lesenswert?

Sven schrieb:

>
> Aber ich wäre ja schon froh, wenn mir ein Assembler-Guru das -0 erklären
> könnte. Es geht mir dabei mehr ums Verständnis, als um Effizienzfragen
> etc. Man will ja wissen, was da so unter der Motorhaube passiert...

Dazu muss man kein Assembler-Guru sein, sondern nur verstehen was
1
while(delay--) asm volatile("nop");
für beliebige Anfangswerte einschlisselich 0 von delay machen soll. In 
deiner erster Variante steht vermutlich noch irgendwo ein Spung auf 
0x1d8.

Versuch mal statt dessen ein
1
while(--delay) asm volatile("nop");
dann dürfte der angeblich überflüssige Vergleich auf 0 weg sein.

von Sven (Gast)


Lesenswert?

DANKE!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

c-hater schrieb:
> Kein Ahnung. Eigentlich kann es nur drei Gründe dafür geben.
>
> 1) "sbiw R24,0" muß sinngemäß eigentlich als "NOP2" gelesen werden
> 2) es ist aus irgendeinem Grunde für den verschissenen C-Compiler
> wichtig, die MCU-Flags beim Austritt aus der Warteschleife in einem
> bestimmten Zustand zu haben, eben in dem, der sich nach einer
> Subtraktion 0-0 einstellt.
> 3) eine Kombination aus 1) und 2)
>
> Ich würde auf 3) tippen...

Weder 1) 2) noch 3) treffen zu.  Was hingegen zutrifft sind 4) und 5)

4) Mit einem halbwegs aktuellen Compiler (4.7, 4.8, ...) und aktivierter 
Optimierung (etwa -Os) ist der erzeugte Code mit 8-Bit bzw. 16-Bit 
unsigend delay:
1
.L2:
2
  sbiw r24,1
3
  brcs .L5
4
/* #APP */
5
  nop
6
/* #NOAPP */
7
  rjmp .L2
8
.L5:
bzw.
1
  subi r24,1
2
  brcs .L9
3
/* #APP */
4
  nop
5
/* #NOAPP */
6
  rjmp .L7


5) Das einzige, was hier verschissen ist, sind deine Umgangsformen.

von Karl H. (kbuchegg)


Lesenswert?

Sven schrieb:
> Bei 2/3/s dann das von oben wieder.
>
> Aber ich wäre ja schon froh, wenn mir ein Assembler-Guru das -0 erklären
> könnte.

es ist ganz einfach die kürzeste Version, um eine 16 Bit Zahl auf 0 zu 
testen. Zieh 0 ab und sieh dir nachher die Flags an.

In deiner Urversion von ganz oben steht die 2.te Subtraktion deswegen 
drinnen, weil
1
  while(delay--)
ja nichts anderes als die Kurzform von
1
  while(delay-- != 0)
ist. Sprich: der Compiler muss zunächst den Ausdruck berechnen und dann 
entscheiden, ob das Ergebnis einem FALSE (0) entspricht oder nicht. Je 
nachdem erfolgt dann ein weiterer Durchgang durch die Schleife.

Offenbar ist die 2.te Subtraktion aber noch nicht rausoptimiert worden.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Karl Heinz schrieb:
> Offenbar ist die 2.te Subtraktion aber noch nicht rausoptimiert worden.

Ich tippe eher auf die Erklärung von "Lattice User".

Wenn man
   while (delay--)...
zeitoptimiert umsetzt, dann landet man bei
   if (delay) do ... while (--delay)
und genau das macht er bei -O1.

Bei -Os hingegen verkürzt er den Code zu Lasten der Laufzeit zu
   goto test;
loop:
   --delay;
test:
   if delay != 0 goto loop;
und da lässt sich der Test nicht rausoperieren. Den "goto test" 
allerdings hatte er zu erwähnen vergessen.

von Lattice User (Gast)


Lesenswert?

Karl Heinz schrieb:
>
> In deiner Urversion von ganz oben steht die 2.te Subtraktion deswegen
> drinnen, weil
>
1
>   while(delay--)
2
>
> ja nichts anderes als die Kurzform von
>
1
>   while(delay-- != 0)
2
>
> ist. Sprich: der Compiler muss zunächst den Ausdruck berechnen und dann
> entscheiden, ob das Ergebnis einem FALSE (0) entspricht oder nicht. Je
> nachdem erfolgt dann ein weiterer Durchgang durch die Schleife.

Tut mir leid, das ist nicht richtig
1
  delay--
ist post decrement, also zuerst vergleichen und dann decrementieren.

>
> Offenbar ist die 2.te Subtraktion aber noch nicht rausoptimiert worden.

Die Variante war laut Sven mit Os d.h. Platzbedarf. Der Vergleich auf 0 
ist für den Vergleich beim allerersten Durchlauf enthalten.


Die Variante, die sich am besten optimieren lässt dürfte
1
   do {
2
      asm volatile("nop"); 
3
   } while( --delay );
sein, da sie ohne Sonderbehandlung beim Schleifeneintritt auskommt. Ein 
Startwert von 0 macht dann aber nicht mehr das gleiche wie beim Orginal.

von Karl H. (kbuchegg)


Lesenswert?

Lattice User schrieb:
> Karl Heinz schrieb:
>>
>> In deiner Urversion von ganz oben steht die 2.te Subtraktion deswegen
>> drinnen, weil
>>
1
>>   while(delay--)
2
>>
>> ja nichts anderes als die Kurzform von
>>
1
>>   while(delay-- != 0)
2
>>
>> ist. Sprich: der Compiler muss zunächst den Ausdruck berechnen und dann
>> entscheiden, ob das Ergebnis einem FALSE (0) entspricht oder nicht. Je
>> nachdem erfolgt dann ein weiterer Durchgang durch die Schleife.
>
> Tut mir leid, das ist nicht richtig
>
1
>   delay--
2
>
> ist post decrement, also zuerst vergleichen und dann decrementieren.

Das hat mich auch zunächst verblüfft.
Aber wenn du dir den genauen Code ansiehst und wir davon ausgehen, dass 
da am Anfang noch ein Sprung auf den 2.ten SBIW fehlt, dann hat der 
Compiler das gar nicht so unclever gelöst (Jetzt mal davon abgesehen, 
dass der 2.te SBIW nicht notwendig wäre). Er hat den NOP vorgezogen 
wodurch die Anzahl der ausgeführten NOP wieder dieselbe ist. Interessant 
wäre es jetzt, wie es im C Code weiter geht, denn delay hat nach der 
Schleife einen falschen Wert (0 anstelle von -1), was aber unter 
Umständen nichts ausmacht, wenn die Funktion an dieser Stelle sowieso 
schon zu Ende ist bzw. delay nicht mehr benutzt wird.

>    do {
>       asm volatile("nop");
>    } while( --delay );

in genau so eine ähnliche Form dürfte sich der Compiler die Schleife 
zunächst gebracht haben

PseudoCode, kein echtes C
1
  goto check;
2
  do {
3
    asm volatile("nop");
4
    --delay; 
5
  } check: while( delay != 0 );

und das hat er dann 1:1 übersetzt.

Aber ich stimme zu: das alles ist Spekulation.

: Bearbeitet durch User
von LUGS (Gast)


Lesenswert?

Johann L. schrieb:
> 5) Das einzige, was hier verschissen ist, sind deine Umgangsformen.

Was hat denn dich gebissen? Was soll diese Ansage? Mir unverständlich.
Oder beziehst Du das auf Dich?

von (prx) A. K. (prx)


Lesenswert?

LUGS schrieb:
> Was hat denn dich gebissen? Was soll diese Ansage? Mir unverständlich.

c-hater (nomen est omen) hatte vom "verschissenen C-Compiler" 
geschrieben, und Johann reagierte darauf verständlicherweise etwas 
empfindlich.

von (prx) A. K. (prx)


Lesenswert?

Karl Heinz schrieb:
> Schleife einen falschen Wert (0 anstelle von -1), was aber unter
> Umständen nichts ausmacht, wenn die Funktion an dieser Stelle sowieso
> schon zu Ende ist bzw. delay nicht mehr benutzt wird.

Kann gut sein, dass der Compiler im Rahmen der schematischen Umstellung 
der Schleife im Zwischencode die fällige Angleichoperation hinter der 
Schleife zunächst erzeugt, diese aber im weiteren Verlauf der 
Optimierung rausfliegt, weil erkennbar ungenutzt.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:
> Karl Heinz schrieb:
>> Schleife einen falschen Wert (0 anstelle von -1), was aber unter
>> Umständen nichts ausmacht, wenn die Funktion an dieser Stelle sowieso
>> schon zu Ende ist bzw. delay nicht mehr benutzt wird.
>
> Kann gut sein, dass der Compiler im Rahmen der schematischen Umstellung
> der Schleife im Zwischencode die fällige Angleichoperation hinter der
> Schleife zunächst erzeugt, diese aber im weiteren Verlauf der
> Optimierung rausfliegt, weil erkennbar ungenutzt.

Kopfklatsch.
Richtig, natürlich.
Ich hab mir schon die ganze Zeit den Kopf darüber zerbrochen, wie er 
dieses Problem hätte lösen können.


Wie man sieht, kann ein Post-Dekrement ganz schön unangenehm sein. 
Pre-Decrement ist einfacher zu handhaben, wenn man nicht nur auf den 
Nebeneffekt des Dekrementierens aus ist, sondern mit dem Wert selbst 
auch noch was machen muss.

Aber seit 4.7 ist ja (laut Johann) da sowieso mit einem komplett anderen 
Ergebnis zu rechnen. Und an dem Ergebnis kann  noch nicht mal c-hater 
rummäkeln. Nicht das es irgendjemand interessieren würde, was c-hater so 
meint.

von Lattice User (Gast)


Lesenswert?

A. K. schrieb:
> Karl Heinz schrieb:
>> Schleife einen falschen Wert (0 anstelle von -1), was aber unter
>> Umständen nichts ausmacht, wenn die Funktion an dieser Stelle sowieso
>> schon zu Ende ist bzw. delay nicht mehr benutzt wird.
>
> Kann gut sein, dass der Compiler im Rahmen der schematischen Umstellung
> der Schleife im Zwischencode die fällige Angleichoperation hinter der
> Schleife zunächst erzeugt, diese aber im weiteren Verlauf der
> Optimierung rausfliegt, weil erkennbar ungenutzt.

Durchaus möglich, da ja im Orginalbeispiel (zumindesten im geposteten 
Code) direkt davor delay auf einen konstanten Wert gesetzt wird. Aber 
warum hat er dass dann in der -O1 Variante nicht gemacht?

von Rolf Magnus (Gast)


Lesenswert?

Übrigens finde ich es verwunderlich, einerseits von Hand ein NOP 
einzufügen, sich andererseits über eine zusätzliche vom Compiler 
eingebaute Instruktion aufzuregen.

von (prx) A. K. (prx)


Lesenswert?

Lattice User schrieb:
> Durchaus möglich, da ja im Orginalbeispiel (zumindesten im geposteten
> Code) direkt davor delay auf einen konstanten Wert gesetzt wird. Aber
> warum hat er dass dann in der -O1 Variante nicht gemacht?

Vorsicht. Das mit der Konstante war nicht von Sven sondern von Fred.

von Fred S. (kogitsune)


Lesenswert?

A. K. schrieb:
> Vorsicht. Das mit der Konstante war nicht von Sven sondern von Fred.

Ist aber mit einer Variablen nicht anders (-O1, gcc 3.4.2/Atmel Studio 
6.1):
Source:
1
  for (uint16_t n=1000; n>0; n--){
2
    uint16_t delay=n;
3
    while(delay--) asm volatile("nop");
4
  }

LSS:
1
 326:  28 ee         ldi  r18, 0xE8  ; 232
2
 328:  33 e0         ldi  r19, 0x03  ; 3
3
 32a:  48 ee         ldi  r20, 0xE8  ; 232
4
 32c:  53 e0         ldi  r21, 0x03  ; 3
5
 32e:  07 c0         rjmp  .+14       ; 0x33e <main+0x2c>
6
    uint16_t delay=n;
7
    while(delay--) asm volatile("nop");
8
 330:  00 00         nop
9
 332:  01 97         sbiw  r24, 0x01  ; 1
10
 334:  e8 f7         brcc  .-6        ; 0x330 <main+0x1e>
11
12
int main(void) {
13
    for (uint16_t n=1000; n>0; n--){
14
 336:  21 50         subi  r18, 0x01  ; 1
15
 338:  31 09         sbc  r19, r1
16
 33a:  a9 01         movw  r20, r18
17
 33c:  29 f0         breq  .+10       ; 0x348 <main+0x36>
18
    uint16_t delay=n;
19
    while(delay--) asm volatile("nop");
20
 33e:  ca 01         movw  r24, r20
21
 340:  01 97         sbiw  r24, 0x01  ; 1
22
 342:  45 2b         or  r20, r21
23
 344:  a9 f7         brne  .-22       ; 0x330 <main+0x1e>
24
 346:  f7 cf         rjmp  .-18       ; 0x336 <main+0x24>

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Um den Compiler für dumm zu verkaufen musst du schon schlauer vorgehen 
:-). Dass die Var hier nie 0 sein kann weiss er.

: Bearbeitet durch User
von Sven (Gast)


Lesenswert?

Hätte ich geahnt, was meine Frage für Wellen schlägt...

Lattice User schrieb:
> A. K. schrieb:
> Durchaus möglich, da ja im Orginalbeispiel (zumindesten im geposteten
> Code) direkt davor delay auf einen konstanten Wert gesetzt wird. Aber
> warum hat er dass dann in der -O1 Variante nicht gemacht?

Der C Code war trivial:
1
inline __attribute__((gnu_inline)) void ow_delay (uint16_t delay)
2
{
3
  while(delay--) asm volatile("nop");
4
}

Also egal, was delay danach für'n Wert hat. Übergeben wird eine 
Konstante.
Sorry, dass ich nicht so weit ausgeholt hatte, aber konnte ja keiner 
Ahnen, dass das wichtig wird. Und für die Vergleichbarkeit: Compiliert 
mit avr-gcc (WinAVR 20100110) 4.3.3

Rolf Magnus schrieb:
> Übrigens finde ich es verwunderlich, einerseits von Hand ein NOP
> einzufügen, sich andererseits über eine zusätzliche vom Compiler
> eingebaute Instruktion aufzuregen.

Mooooment: Ich wollte nur wissen, was da vor sich geht. ist ist völlig 
Schnuppe, wie Optimal das hinsichtlich von Laufzeit oder Größe ist. 
Eigentlich wollte ich nur die Ticks ermitteln und bin dabei über -0 
gestolpert und da ich von Assembler nur Grundkenntnisse habe aus einer 
Zeit, als man noch mit 8 Schaltern programmierte und das auch nur ein 
paar Stunden lang gemacht habe, fragte ich die Community, damit ich mein 
Verständnis auffrischen kann.

von Stephan (Gast)


Lesenswert?

Hi,
mal eine Frage:
Wenn der TO wirklich Ticks zählen will, wäre es dann nicht besser die 
Func. immer mit der gleichen Optimierung zu kompilieren! Unabhängig vom 
Rest des Programms!

nur eine Idee: (nicht getestet)
1
inline __attribute__((gnu_inline, optimize("O1")) void ow_delay (uint16_t delay)
2
{
3
  while(delay--) asm volatile("nop");
4
}

von Karl H. (kbuchegg)


Lesenswert?

Sven schrieb:

> Mooooment: Ich wollte nur wissen, was da vor sich geht. ist ist völlig
> Schnuppe, wie Optimal das hinsichtlich von Laufzeit oder Größe ist.
> Eigentlich wollte ich nur die Ticks ermitteln

Compiler sind komplexe Gebilde. Speziell dann, wenn es um Optimierungen 
geht. Es ist schwierig im Detail nachzuvollziehen, wie der Compiler ganz 
konkret in einer ganz bestimmten Situation zu einer spezifischen Lösung 
kommt, weil meistens viele Faktoren zusammenspielen und dabei eine 
Unmenge an Code im Compiler in der einen oder anderen Form mitspielt. 
Auch in deinem Code ist es sehr wahrscheinlich nicht eine einzige 
Optimierung, die für das Ergebnis verantwortlich ist, sondern da sind 
viele Optimierungen am Werk, die sukzessive und in Schritten den 
Ausgangscode auf das letztendlich generierte Ergebnis transformieren.
Manchmal optimieren Compiler Dinge nicht, die eigentlich recht 
offensichtlich sind, dafür gibt es wieder andere Fälle, in denen sie mit 
wirklich cleveren Lösungen hochkommen.
Über alles gesehen ist aber das Ergebnis bei einem durchschnittlichen 
Compiler meistens so, dass er auf dem Level eines guten 
Assembler-Programmierers operiert. D.h. um den Compiler zu schlagen, 
muss man schon recht gut in Assembler sein und viel Erfahrung haben. Bei 
kleinen Programmen geht das noch recht problemlos aber wenn die Dinge 
größer werden (und ich rede da jetzt nicht von 50 Zeilen Code, 50 Zeilen 
ist Micky Mouse) hast du als Assembler Programmierer kaum mehr eine 
Chance, weil du das Programm in seiner Gesammtheit in allen Details 
einfach nicht mehr überblicken kannst. Die einzige "Chance" die du hast 
ist die, Fehler einzubauen, weil du wieder mal irgendwo ein Register 
versemmelt hast oder dir die Flags mit einer Instruktion zerschossen 
hast, die dann den Conditional Branch 3 Instruktionen weiter falsch 
springen lassen. Derartige Fehler passieren einem Compiler nun mal 
nicht. Daher nimmt man die paar Prozentpunkte, die das compilierte 
Produkt gegenüber einem handoptimierten Assemblercode langsamer ist, 
gerne in Kauf. Man kann seine Zeit auch besser verbringen, als 
stundenlang in 30 Bildschirmseiten Code den kritischen Pfad durch alle 
Conditional Branches zu suchen, der zu einem Pop zu wenig führt, der 
dann letzten Endes den Stack zerschiesst. Den in den wenigsten 
Programmen kommt es tatsächlich auf jede µs an. Ob ein Relais 2 µs 
früher oder später nach einem Tastendruck schaltet, interessiert keinen. 
Was aber interessiert ist, dass es das zuverlässig und korrekt 
entsprechend der vorgegebenen Logik und den Nebenbedingungen tut.

: Bearbeitet durch User
von Sven (Gast)


Lesenswert?

Stephan schrieb:
> Hi,
> mal eine Frage:
> Wenn der TO wirklich Ticks zählen will, wäre es dann nicht besser die
> Func. immer mit der gleichen Optimierung zu kompilieren! Unabhängig vom
> Rest des Programms!
>
> nur eine Idee: (nicht getestet)
> inline __attribute__((gnu_inline, optimize("O1")) void ow_delay
> (uint16_t delay)
> {
>   while(delay--) asm volatile("nop");
> }

Das ist eine gute Idee, brauche ich aber nicht.

Ich danke wirklich für die vielen Kommentare. Aber wie gesagt: es geht 
mir nicht um die Optimierung. Ich sah, verstand nicht und fragte. Ich 
dachte einfach, dass ich sbiw vielleicht nicht richtg verstanden habe 
und brne danach nur dann funktioniert, wenn -0 vorher gemacht wird. Oder 
irgendwas anderes, was sich meinem Assembler-Verstehen entzogen hat.
Mir ist im grunde schnuppe, was da steht und wie der Compiler sich das 
zusammengefrickelt hat. Jetzt weiß ich, daß liegt am delay-- und das ist 
ausreichend. und ich hatte sbiw und brne schon richtig verstanden und 
die machen das so wie ich dachte und die Feinheiten warum das so optimal 
ist überlasse ich den Kapazitäten, die dankenswerterweise am AVR GCC 
entwickeln. Und ich werde den Code weder ändern noch in Assembler coden. 
Das habe ich schon vor Jahrzehnten als unpraktikabel abgelehnt (wie 
gesagt: 8x Klick und 1x Tipp war grausig genug) Ich bleibe schön bei C 
(oder anderen Hochsprachen) und bin zufrieden mit dem was raus kommt. 
Nur manchmal juckt es einen halt zu sehen, was da rauskommt. Ich bastle 
ja auch am Auto und frage mich oft genug, was "der Scheiß" da soll. 
Deshalb baue ich ja nicht gleich das Auto selber oder kaufe mir eine 
neues und baue es erst mal um.

von Peter D. (peda)


Lesenswert?

Sven schrieb:
> Und ich werde den Code weder ändern

Das solltest Du aber doch in Betracht ziehen.

Der AVR-GCC kennt schon sehr lange _delay_us() und _delay_ms().
Damit kann man zuverlässig Delays erzeugen, ohne sich später die Haare 
zu raufen, wenn man mal einen anderen Quarz anschließen will.

Handoptimierte Delayschleifen sind eine mehr Dirty als Quicklösung. Ein 
anderer Quarz, andere Compilerversion schmeißen Dir alles wieder 
komplett über den Haufen.

von Sven (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Der AVR-GCC kennt schon sehr lange _delay_us() und _delay_ms().
> Damit kann man zuverlässig Delays erzeugen, ohne sich später die Haare
> zu raufen, wenn man mal einen anderen Quarz anschließen will.

Und schon mal gemessen, wie genau die sind? 480 µs? Keine Chance. Für 
1-Wire muß man sich schon etwas verbiegen. Und es ging hier nicht ums 
Optimum.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sven schrieb:
> Peter Dannegger schrieb:
>> Der AVR-GCC kennt schon sehr lange _delay_us() und _delay_ms().
>> Damit kann man zuverlässig Delays erzeugen, ohne sich später die Haare
>> zu raufen, wenn man mal einen anderen Quarz anschließen will.
>
> Und schon mal gemessen, wie genau die sind? 480 µs? Keine Chance. Für
> 1-Wire muß man sich schon etwas verbiegen. Und es ging hier nicht ums
> Optimum.

Die zugrundelegenden Primitiven sind Zykel-genau.  Die erreichbare 
Genauigkeit hängt also nur noch von der Taktfrequenz ab: Je größer, 
desto genauer.  Ebenfalls hängt die erreichbare Genauigkeit von deiner 
IRQ-Last ab.

Nehmen wir mal folgendes kleines Beispiel bei 20 MHz:
1
#define F_CPU 20000000
2
3
#include <util/delay.h>
4
5
void wait (void)
6
{
7
    _delay_us (10.3);
8
}


10.3µs entsprechen 10.3 / 1e6 * 20e6 = 206 Ticks.

Lassen wir das Beispiel mit avr-gcc übersetzen — hier mit 4.8 — und 
setzen zusätzlich zu der für delay.h erforderlichen Optimierungsoption 
noch -fdump-tree-optimized weil wir zu faul sind, die Ticks im erzeugten 
Programm zu zählen.  Das entsprechende .optimized enhält für wait:
1
wait ()
2
{
3
  <bb 2>:
4
  __builtin_avr_delay_cycles (206); [tail call]
5
  return;
6
}

Das ist genau die oben errechnete Anzahl an Ticks.

Und wennst nicht glaubst daß avr-gcc in der Lage ist, das in eine 
Sequenz (modulo IRQ-Last) umzusetzen, die genau 206 Ticks dauert, dann 
studier gerne die GCC-Quellen oder zähl es im erzeugten Assembler-Code 
nach:
1
wait:
2
  ldi r24,lo8(68)   ;  5  delay_cycles_1  [length = 3]
3
  1: dec r24
4
  brne 1b
5
  rjmp .   ;  6  *nopv/2  [length = 1]
6
  ret   ;  13  return  [length = 1]

Wobei das ret am Ende nicht mehr zum delay gehört. Und selbst wenn es zu 
Rundungsfehlern kommt weil die Anzahl der errechneten Ticks nicht 
ganzzahlig ist: Genauer als hausbackener C-Code ist's allemal...

von Peter D. (peda)


Lesenswert?

Sven schrieb:
> Und schon mal gemessen, wie genau die sind? 480 µs? Keine Chance. Für
> 1-Wire muß man sich schon etwas verbiegen.

Also bei mir funktioniert 1-Wire einwandfrei mit _delay_us() und vor 
allem auf Anhieb und bei beliebiger Quarzfrequenz.

Beim 60µs Bitzugriff muß man aber in der Regel Interrupts verbieten.
Beim 480..960µs Resetpuls brauche ich das nicht, da meine Interrupts 
kurz genug sind.

von (prx) A. K. (prx)


Lesenswert?

Sven schrieb:
> Und schon mal gemessen, wie genau die sind? 480 µs? Keine Chance.

Ich weiss nicht wie du es schaffst, die ziemlich gut entwickelten 
Delay-Routinen der AVR-Lib derart ungenau werden zu lassen, dass 1-Wire 
Code in die Binsen geht. Das geht eigentlich nur mit falscher 
Taktvorgabe, störenden Interrupts oder unoptimierter Übersetzung.

: Bearbeitet durch User
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.