Funktioniert auch prima. Allerdings stellt mich das Verhalten der ersten
for-Schleife vor ein Rätsel. Ich hatte mir überlegt, dass diese Schleife
ja eigentlich niemals verlassen werden muss, so dass ich mir die
while(true)-Schleife sparen kann. Also habe ich die Ausführbedingung
"true" gesetzt. Da "i" unsignet ist sollte das ja eigentlich
funktionieren. Aber Pustekuchen, der durchläuft die Schleife einmal,
schaltet die LED ein und dann ist Feierabend. Und das obwohl die
while(1)-Schleife noch da war...
OK, anderer Versuch: Ausführbedingung auf >=0 gesetzt -> gleiches
Verhalten. Selbst >0 führt zum Absturz, und das obwohl es bei den
eingestellten Werten für T_START und DELTA_T eine Punktlandung auf 0
gibt, eine Abbruchbedingung also klar definiert ist!
Setzt man aber z.B. T_STOP auf 1 und DELTA_T auf 0x00FE (wegen der
Punktlandung) stellt sich das gewünschte Verhalten ein.
Folgende Version führt übrigens zu dem gleichen Fehler:
1
...
2
uint16_ti,n;
3
i=T_START;
4
while(1){
5
i=i-DELTA_T;// Blinkfrequenz steigt stetig
6
for(n=i;n>0;n--);// Rechenzeit verbraten
7
PORTA=PORTA^0x01;// LED an Port A0 togglen
8
}
9
...
Ich benutze übrigens die aktuelle Version von WinAVR.
Leicht verwirrte Grüße,
Thomas
...oder einfach in deine Schleife z.B. ein Port Hin und Her schalten
lassen - z.B. einen der Portbits, die nicht nach außen geführt sind -
je nach Ausführung ...
Wie bereits gesagt ist das mein erstes uC-Programm und man kann
schließlich nur die Funktionen anwenden, die man auch kennt. Und durch
Spielen lernt man schließlich immer noch am besten, hier z.B. die Tücken
des Compilers.
Abgesehen davon machen die delay-Funktionen, die man über "util/delay.h"
erreicht auch nichts anderes als Rechenzeit zu verbraten...
Elegant macht man sowas natürlich über Timerinterrupt, das ist mir auch
klar.
Thomas wrote:
> Abgesehen davon machen die delay-Funktionen, die man über "util/delay.h"> erreicht auch nichts anderes als Rechenzeit zu verbraten...
Dafür tun sie das aber im Gegensatz zur
"for(i = 0; i < 30000; i++) /* wait */;"-Schleife auch wirklich. ;-)
Das Problem ist wohl, dass ich einfach noch nicht so denke wie ein
Compiler. Ich habe das gleiche Programm kurz vorher auch in Assembler
implementiert, von dieser hardwarenahen Betrachtung muss man sich wohl
in C ein Stück weit lösen. Aber deswegen experimentier ich ja auch und
finde es gut, wenn ich über solche Probleme stolper. Als nächstes währe
es vielleicht mal interessant das C-HEX zu disassemblieren und mit
meinem Ergebnis zu vergleichen...
Ach ja, und die AVR-libc Lösung wird doch wohl im Kern auch nicht ganz
anders implementiert sein, als "for(i = 0; i < 30000; i++) asm
volatile("nop");", oder?
Thomas wrote:
> Das Problem ist wohl, dass ich einfach noch nicht so denke wie ein> Compiler. Ich habe das gleiche Programm kurz vorher auch in Assembler> implementiert, von dieser hardwarenahen Betrachtung muss man sich wohl> in C ein Stück weit lösen.
Ja, man muß völlig umdenken, wenn man in C programmiert. C hat keinerlei
Zeitbezug.
Es kümmert sich nicht, wann etwas gemacht wird, wie lange etwas dauert
oder in welcher Reihenfolge etwas gemacht wird.
Für C ist eine Rechnung immer dann richtig, wenn am Ende das Ergebnis
stimmt.
Spezielle Compiler für Microcontroller sind in der Regel näher an der
Hardware, d.h. sie sortieren Operationen nicht um, wenn sich
Ausführungszeit oder Codebedarf dadurch nicht verringern.
Der GCC gehört leider nicht dazu. Der GCC hat den Hang, Sachen möglichst
spät auszuführen (ist quasi faul), auch wenn dadurch der Code- und
Zeitbedarf oftmals steigt.
Für den GCC sind nur volatile Zugriffe Fixpunkte, d.h. wenn man z.B.
einen (volatile definierten) Portpin setzt, müssen alle dafür
erforderlichen Berechnungen ausgeführt worden sein.
Hört sich vielleicht schlimmer an, als es ist, aber man muß es im
Hinterkopf behalten, daß für den GCC Zeit und Abläufe keine Rolle
spielen.
Benötigt man das, muß man es durch volatile Zugriffe erzwingen.
Wenn Du Assembler kannst, solltest Du ruhig mal ins Listing schauen, Du
wirst staunen, was der Compiler da alles umsortiert.
Z.B. kurze Funktionen stehen zwar im Listing, werden aber nie
aufgerufen, weil sie im Aufrufer nochmal dupliziert (geinlined) sind.
Peter
> so geht`s immer:> for(n=i; n>0; n--) asm volatile("nop"); // Rechenzeit verballern>> Bleibt die Frage, warum es in manchen Situationen auch ohne "nop" geht.
Ohne das nop geht's durchaus auch. Es reicht ein:
Mir ist klar, daß du das nicht so gemeint hast. Ich wollte es aber nur
der Vollständigkeit halber erwähnen.
> Das Problem ist wohl, dass ich einfach noch nicht so denke wie ein> Compiler.
Nun, der Compiler hat einen Optimizer, und dessen Zweck ist es, das, was
du in deinem Code formulierst, so umzubauen, daß es in möglichst kurzer
Zeit und/oder mit möglichst wenig Code ausgeführt wird. Und wenn er
merkt, daß ein Teil deines Codes absolut gar nichts tut, wird er diesen
durch absolut gar nichts ersetzen, da das immer noch am wenigsten
Rechenzeit und Codespeicher benötigt. Damit ist eine leere Schleife
natürlich ein gefundenes Fressen für den Optimizer. Er weiß ja nicht,
daß du in diesem speziellen Fall eben nicht möglichst effizienten Code
willst.
> Ich habe das gleiche Programm kurz vorher auch in Assembler> implementiert, von dieser hardwarenahen Betrachtung muss man sich wohl> in C ein Stück weit lösen.
Ja. C wird zwar oft als portabler Assembler bezeichnet, aber das stimmt
eben so nicht.
> Als nächstes währe es vielleicht mal interessant das C-HEX zu> disassemblieren und mit meinem Ergebnis zu vergleichen...
Das ist eine gute Idee.
Peter Dannegger wrote:
> Der GCC gehört leider nicht dazu. Der GCC hat den Hang, Sachen> möglichst spät auszuführen (ist quasi faul), auch wenn dadurch der> Code- und Zeitbedarf oftmals steigt.
Peter, auch wenn du diese Behauptung gebetsmühlenartig wiederholst,
bleibt sie nichts anderes als FUD.
Selbstverständlich ist es das erklärte Ziel des Optimierers, den
Codegrößen- bzw. Ausführungszeitbedarf des erzeugten Codes zu
reduzieren. Dass er dieses Ziel beim AVR-GCC nicht immer erreicht,
liegt vornehmlich daran, dass den GCC-Entwicklern kein automatisiertes
Werkzeug zur Verfügung steht, mit dem sie eine ggf. entstehende
Verschlechterung für das Target "avr" bereits während ihrer
Entwicklungsarbeit feststellen könnten. Daher kommt es also zu einem
neuen Compiler-Release, und die AVR-Nutzer können dann erst nachher
feststellen, ob irgendwas besser oder schlechter geworden ist.
Wenn du statt der Verbreitung von FUD zu fröhnen lieber aktiv
mithelfen willst, dass ungünstige Optimierungen repariert werden, dann
schreib bitte für alles, was du findest, ordentliche Bugreports (in
GCCs Bugzilla, nicht einfach irgendwo in ein Forum) und diskutiere das
dann mit den GCC-Entwicklern durch. Wenn du zeigen kannst dass
zwischen zwei Compilerversionen etwas schlechter geworden ist, dann
kannst du das im Subject des Bugreports mit [regression] markieren,
damit bekommt es in der Bearbeitung eine gewisse Priorität.
Es hilft hier niemandem was, wenn du stets aufs neue lamentierst, dass
die Compiler der vorvorherigen Generation, die in der Tat noch
,,bessere Assembler'' waren, ja ach so viel besser waren. (Das waren
sie nämlich gar nicht, bei denen musstest du einfach als Programmierer
noch die Hälfte der Optimierung übernehmen. Ja, dein Code macht das
auch, aber sorry, dafür hat er leider nicht den Ruf, dass man ihn
sofort beim Angucken versteht.)
> Für den GCC sind nur volatile Zugriffe Fixpunkte, d.h. wenn man z.B.> einen (volatile definierten) Portpin setzt, müssen alle dafür> erforderlichen Berechnungen ausgeführt worden sein.
Naja, und wie wir neulich festgestellt haben, fehlt es eigentlich im
C-Standard teilweise auch an Konzepten, die dem besser gewordenen
Optimierungspotenzial der Compiler dahingehend Rechnung tragen, dass
man als Programmierer ggf. Prioritäten setzen kann. Stichwort
`volatile code block' oder sowas. Das kannst du aber nicht dem
Compiler anlasten, sondern wenn du das geändert haben willst, musst du
das irgendwie ins Standard-Kommittee reinbringen. Ein möglicher Weg,
wie das passieren kann ist, dass du mit den GCC-Entwicklern dafür
einen Weg diskutierst und versuchst, dass es zu einer
Beispielimplementierung kommt. Eine funktionierende
Beispielimplementierung stellt für das Standardkommittee eine gute
Basis dar, bestimmte Dinge ggf. mal offiziell zu übernehmen. Das war
auch der Grund, warum ich beispielsweise die 0b-Binärkonstanten im GCC
versucht habe durchzuziehen (was mir mittlerweile ja auch gelungen
ist).
Thomas wrote:
> Ach ja, und die AVR-libc Lösung wird doch wohl im Kern auch nicht ganz> anders implementiert sein, als "for(i = 0; i < 30000; i++) asm> volatile("nop");", oder?
Sie macht es als inline-Assembler-Schleife und ist damit (im Inneren,
also _delay_loop_1() und _delay_loop_2()) unabhängig von den
Optimierungseinstellungen des Compilers.
> Ohooo.....unabhängig von den Optimierungseinstellungen?
Ja.
> Da hab ich aber was ganz anderes gehört:
Das ist _delay_ms, nicht _delay_loop_1 und _delay_loop_2. Bei _delay_ms
kommt eine Fließkomma-Berechnung dazu, bei der es praktisch
lebensnotwendig ist, daß sie zur Compilezeit ausgeführt wird.
@ Peter Dannegger:
> Ja, man muß völlig umdenken, wenn man in C programmiert. C hat> keinerlei Zeitbezug.> Es kümmert sich nicht, wann etwas gemacht wird, wie lange etwas> dauert oder in welcher Reihenfolge etwas gemacht wird.> Für C ist eine Rechnung immer dann richtig, wenn am Ende das> Ergebnis stimmt.
Das sehe ich nun garnicht so: In meinen Augen ist C ein
Highlevel-Assembler. Eine maschinennähere höhere Programmiersprache, als
C, ist mir bisher nicht begegnet. Wer jemals in einer Sprache wie C++
(unter Ausnutzung der abstrakten Sprachfeatures und z.B. STL), Pascal,
Ada progammiert hat, weiß was ich meine.
Im übrigen ist einem Assembler das Zeitverhalten des Codes genauso
wurscht, wie jedem Hochsprachen-Compiler, incl. C: Dafür ist
ausschließlich der Programmierer verantwortlich.
Wenn der nicht weiß, was er tut, dann ist er ein Stümper...
> Das sehe ich nun garnicht so: In meinen Augen ist C ein> Highlevel-Assembler.
Die C-Norm definiert das aber bewußt anders. Dort gibt es die "as-if
rule". Die besagt, daß nicht genau das getan werden muß, was in der Norm
steht, sondern nur etwas, dessen Effekt genau so ist, als ob das getan
wurde, was dort steht, und die Ausführungszeit gehört hier nicht dazu.
> Im übrigen ist einem Assembler das Zeitverhalten des Codes genauso> wurscht, wie jedem Hochsprachen-Compiler, incl. C: Dafür ist> ausschließlich der Programmierer verantwortlich.
In Assembler ja. In C ist der Optimizer auch mit dafür verantwortlich.
In einer Hochsprache werden viele Elemente der Zielplattform
wegabstrahiert, und das gilt auch für C. Es sind zwar keine
Objektorientierung oder Exceptions in die Sprache eingebaut, aber es
gibt viele andere Abstraktionen, z.B. existieren in C die Register
nicht, und genausowenig gibt es die Flags oder Interrupts. Sobald du von
der Hardware abstrahierst, mußt du die Sachen anders schreiben, als man
es direkt für die Hardware müßte. Auf einmal ist eben der Compiler dafür
verantwortlich, die Register effizient zu nutzen. Der Programmierer
definiert nur noch Variablen. Schon hier muß vom Compiler Optimierung
betrieben werden, die der Programmierer in C einfach nicht mehr machen
kann, weil er nicht mehr an die Register kommt, und er soll sich da auch
gar nicht drum kümmern müssen.
Die Idee ist dabei nicht nur, von der Hardware unabhängiger zu werden,
sondern auch, daß man Dinge lesbarer und einfacher formulieren kann.
Abstraktion ist aber fast immer mit einem gewissen Overhead verbunden,
und um den möglichst gering zu halten, dazu besitzt der Compiler den
Optimizer. Das erleichtert es, Programme so zu schreiben, daß sie
leichter verständlich sind. Die Implementation wird dadurch oft weniger
effizient, aber das soll dann der Optimizer automatisch ausgleichen.
In C kann man trotzdem noch sehr hardwarenah programmieren, aber wenn
man irgendwo ein ganz bestimmtes Timingverhalten braucht, ist C sowieso
schon zu abstrakt.