Ich bin grad ein wenig am Rumspielen (Learning by doing) bezüglich C.
Nun habe ich mir ein kleines Programm geschrieben und mir ist
aufgefallen das dieses mit einem festen Wert (define) ca. 12% des
Speichers eines ATmega8 und mit einer Variablen (uint8_t) ca. 80% des
Speichers verbraucht. Warum das?
Ich nutze AVR Studio 4.16
Der Speicherverbrauch ist auf allen Optimierungsstufen nahezu gleich
(ausser Q0).
1
#include"files.h"
2
3
//#define Blinkgeschwindigkeit 100
4
uint8_tBlinkgeschwindigkeit=100;
5
uint8_ttemp=0;
6
7
intmain(){
8
9
Konfiguration();
10
SetColor(Gruen);
11
12
while(1){
13
if(temp>=7){
14
temp=0;
15
}
16
temp++;
17
SetColor(temp);
18
_delay_ms(Blinkgeschwindigkeit);
19
20
21
}
22
23
24
}
Noch eine Frage OT nebenbei: Was ist der unterschied zwischen "uint" und
"uint8_t"?
Das ist der Klassiker, kommt jede Woche >=1 Mal...
_delay_ms + Variable --> float-Library wird eingebunden, Programm wird
riesig und die Zeiten stimmen auch nicht mehr. Frag mal die Suche.
uint = unsigned int (Größe kann alles sein, ich type mal auf "standard
int" = 8 Bit auf nem AVR bzw. 32 auf nem PC)
uint8 = unsigned char (8 Bit)
> 8 Bit auf nem AVR
Nope, sind 16 bit auf nem AVR.
:-)
Das Problem wurde schon genannt, die Funktion delay darf nicht mit
Variablen aufgerufen werden. Dann lieber so:
Floh schrieb:>> 8 Bit auf nem AVR> Nope, sind 16 bit auf nem AVR.> :-)
Argh, stimmt, da war doch neulich erst was von wegen -mint8 oder so.
Naja, ich hab seit Monaten keinen mehr angefasst, gilt das als
Entschuldigung? :-)
> Das Problem wurde schon genannt, die Funktion delay darf nicht mit> Variablen aufgerufen werden. Dann lieber so:>>
Hätte die Suche ja mal bemühen können; das ich nichts gefunden hätte
(habe grad mal bissle gesucht) is nu auch keine Entschuldigung mehr.
Vielen Dank für die Info und den Lösungsvorschlag.
Bezüglich Learning by doing mache ich das so dass ich mich durch ein C
Tutorial für den PC durcharbeite und immer wieder kleine Programme für
den AVR schreibe um mich mit den Funktionen und Realisierungen vertraut
zu machen. (Wie muss was genau geschrieben werden, wie kann ich welches
Problem in C lösen ....)
Mark Brandis schrieb:> blablub schrieb:>> _delay_ms + Variable --> float-Library wird eingebunden>> Hm, was ist eigentlich der Sinn dabei?
Dass du auch 2.8 Millisekunden warten lassen kannst.
Ist der Optimizer eingeschaltet und ist das Argument eine Konstante,
dann macht der Compiler die Berechnung für die Anzahl der
Schleifendurchläufe, so dass der _delay_ms tatsächlich 2.8 Millisekunden
dauert. Und optimiert dabei dann auch gleich den Einsatz der Floating
Point Library wieder weg.
Karl Heinz Buchegger schrieb:> Dass du auch 2.8 Millisekunden warten lassen kannst.>> Ist der Optimizer eingeschaltet und ist das Argument eine Konstante,> dann macht der Compiler die Berechnung für die Anzahl der> Schleifendurchläufe, so dass der _delay_ms tatsächlich 2.8 Millisekunden> dauert. Und optimiert dabei dann auch gleich den Einsatz der Floating> Point Library wieder weg.
Ja gut, nur dass eine Integer-Variable einen Wert wie 2.8 niemals
annehmen kann und das Einbinden der float-Library daher in diesem Fall
sinnlos ist, das sollte der Compiler/Linker an und pfirsich schon
erkennen können. :-)
Mark Brandis schrieb:> Ja gut, nur dass eine Integer-Variable einen Wert wie 2.8 niemals> annehmen kann
Es soll ja auch keine Integer-Variable sein, sondern eine Konstante -
und zwar durchaus auch eine Fließkomma-Konstante. Trotz
Fließkomma-Konstante wird dann die Float-Lib nicht eingebunden.
_delay_ms(2.8) geht also.
Mark Brandis schrieb:> a gut, nur dass eine Integer-Variable einen Wert wie 2.8 niemals> annehmen kann und das
Vielleicht erkennt der das auch und nimmt deswegen float...?
citb
blablub schrieb:> Ach und "_t" ist dann ein typedef, ist einfach ne Konvention.
Nein, uint8_t ist ein Typ, den der Compiler bereitstellen muß. Wie er
das macht (per typedef oder nicht), ist seine Sache.
Michael schrieb:> blablub schrieb:>> Ach und "_t" ist dann ein typedef, ist einfach ne Konvention.>> Keine Konvention, sondern Typen, die in stdint.h deklariert sind, z.B.>
1
typedefunsignedcharuint8_t;
In diesem Falle: ja. Aber auch sonst ist das Anhängen von _t an
Typnamen eine gängigen Konvention, die man gut und gern auch für
selbstdefinierte Typen benutzen kann. Vorteil dabei ist, dass nicht
nur der Mensch sofort erkennen kann, dass es sich um eine
Typdefinition handelt, sondern dass es auch diverse Editoren für das
Syntaxhighlighting erkennen.
Mark Brandis schrieb:> Ja gut, nur dass eine Integer-Variable einen Wert wie 2.8 niemals> annehmen kann und das Einbinden der float-Library daher in diesem Fall> sinnlos ist, das sollte der Compiler/Linker an und pfirsich schon> erkennen können. :-)
Wie soll denn ein Compiler erkennen, daß ein Programm sinnvoll ist oder
unsinnig? Es kann durchaus sinnvoll sein, eine Int-Variable durch eine
float-Berechnung zu erhalten.
Ein Compiler oder Tools wie Lint können zwar fragwürdige Konstrukte
anmeckern, aber es wird niemals Tools geben können, sie "sinnvolle" von
"sinnlosen" Programmen unterscheiden osder die Intention es
Programmierers erahnen. Und selbst wenn es sowas mal geben sollte — bis
dahin gilt "what you get is what you request".
Dass die Funktion _delay_ms() überhaupt anscheinend float oder double
als Argument akzeptiert, ist meiner Meinung nach nicht sinnvoll. Für den
Fall, dass jemand tatsächlich mal 2,8 Millisekunden warten wollte, gibt
es auch Festkommaarithmetik (dann z.B. mit einer Funktion
_delay_microseconds() oder ähnlichem), die wird doch auch und gerade
hier im Forum gerne gepredigt - zu Recht. :-)
So intelligent darf ein Build-System für Mikrocontroller dann schon
sein, dass es auf einer Zielhardware mit knappem Speicher nicht einfach
mal eben so unnötig Bibliotheken einbindet. Wenn Ressourcen knapp sind,
kann oder sollte man nicht verschwenderisch sein.
Mark Brandis schrieb:> Dass die Funktion _delay_ms() überhaupt anscheinend float oder double> als Argument akzeptiert, ist meiner Meinung nach nicht sinnvoll.
Warum nicht?
Was ist so schlimm daran, wenn jamend wirklich 2.8ms warten lassen will.
> Für den> Fall, dass jemand tatsächlich mal 2,8 Millisekunden warten wollte, gibt> es auch Festkommaarithmetik (dann z.B. mit einer Funktion> _delay_microseconds() oder ähnlichem), die wird doch auch und gerade> hier im Forum gerne gepredigt - zu Recht. :-)
Natürlich.
Aber der springende Punkt ist doch: Von der Floating Point Arithmetik
bleibt im Endeffekt nichts übrig. Wenn man es richtig macht. Und wie es
richtig ist, ist ausreichend dokumentiert. Ob der Compiler zur
Berechnung der Wiederholungen Floating Point benutzt oder Festkomma ist
mir sowas von egal. Von mir aus kann der rechnen bis er schwarz wird,
mich interessiert das Compilat das hinten raus kommt.
Wem das nicht gefällt, der kann immer noch eine Version mit
Festkommaarithmetik hochziehen. Das Problem ist in beiden Fällen das
gleiche: Halbwegs genaue kleine Zeiten kriegst du nur, wenn die
Berechnung der Wiederholungen selbst keine Zeit kostet. Also muss da der
Optimizer ran, um die Berechnung wegzuoptimieren. Da kannst du
genausogut Floating Point Arithmetik nehmen.
Jedes Werkzeug ist immer nur so gut, wei derjenige, der es bedient. Die
zu beachtenden Punkte bei _delay_ms sind alle gut dokumentiert und es
ist nicht weiter schwer, diese Punkte einzuhalten und zu beherzigen. Nur
kennen muss man sie. Und da sind wir dann wieder bei dem
Programmier-Hauptthema hier im Forum: Doku gelesen wird nur im absoluten
Notfall.
Ich habe die Gleitkommaarithmetik da reingebracht ;-), und meine
Argumente sind ansonsten natürlich auch die, die Karl Heinz hier schon
gebracht hat: wer die Voraussetzung, dass diese Funktionen nur mit
Optimierung Sinn haben, nicht verstanden hat, dem ist eigentlich nicht
zu helfen.
Der Computer soll für den Menschen da sein und nicht umgekehrt. Wenn
ich 2,8 ms warten will, dann soll sich bitte der Computer den nicht
vorhandenen Kopf zerbrechen, statt dass ich zuvor den Taschenrechner
zücken muss, um das in eine "computergerechte" Form zu bringen.
_delay_us und _delay_ms sind ja "convenience functions", und damit sie
dem gerecht werden, sollen sie bitteschön auch "convenient" zu
benutzen sein. Gleitkommazahlen sind dem, wie der Mensch denkt,
einfach ein Stück näher.
Ansonsten sind wir bei dem, was wir davor schon hatten, und bei dem
die Nutzer immer wieder genervt haben, wie sie denn nun eigentlich die
entsprechende Konstante ausrechnen sollen: _delay_loop_1 und
_delay_loop_2.
Es gibt übrigens einen weiteren Grund für mich, dass man dort
Gleitkommazahlen benutzen kann: wenn ich einen Quarz habe, auf dem
"7,37 MHz" steht, dann möchte ich diese Zahl in meinen Quelltext
übernehmen können, ohne erst drüber nachzudenken, wie viele Nullen ich
jetzt anhängen muss, damit die Freunde der "Gleitkommazahlen sind
übel"-Fraktion befriedigt werden. Daher kann man dann
#define F_CPU 7.37E6
schreiben. (Leider hat der Autor des zweiten Nutzers von F_CPU,
<util/setbaud.h> das nicht geschnallt: wenn man deren Funktionen
nutzen will, muss F_CPU eine ganzzahlige Konstante sein.)
Jörg Wunsch schrieb:> Der Computer soll für den Menschen da sein und nicht umgekehrt.
Das freilich ist dann nicht gegeben, wenn der Computer den
Flash-Speicher so mit Library-Code vollhaut dass der Mensch es dann eben
doch wieder zurechtbiegen muss, damit genügend Platz für das eigentliche
Programm bleibt. ;-)
Mark Brandis schrieb:> Jörg Wunsch schrieb:>> Der Computer soll für den Menschen da sein und nicht umgekehrt.>> Das freilich ist dann nicht gegeben, wenn ...
... man keine Dokumentation liest.
Johann L. schrieb:>> Das freilich ist dann nicht gegeben, wenn ...>> ... man keine Dokumentation liest.
Jein. Einerseits hast Du recht, andererseits darf eine Sache durchaus
auch so sein wie man es intuitiv erwarten würde. Wenn ich nirgendwo in
meinem Code Gleitkommavariablen verwende, dann erwarte ich rein logisch
betrachtet zunächst einmal nicht, dass das Build-System trotzdem die
Gleitkomma-Bibliothek hinzulinkt.
Dass viele Benutzer darüber stolpern, ist doch der beste Beweis dafür,
dass es so wie es ist eben nicht optimal designed ist, bzw. zumindest
widerspricht es der normalen Intuition des Durchschnitts-Users.
Eine von dir vielleicht "intuitiv" genannte Programmierung von Delays
kriegst du bei AVRs nur mit dem, was Microchip dafür zur Verfügung
stellt. Nämlich Delay-Routinen, die eine wählbare Anzahl Taktzyklen
warten, über verschiedenen Funktionen mit Zehnerpotenzen skaliert. Das
ist nämlich die einzige Variante, die völlig ohne Umrechnung auskommt.
Leider ist dieses Verfahren grässlich frequenzanbhängig und dadurch sehr
benutzerunfreundlich. Sobald du absolute Zeiten angeben willst und damit
die Taktfreqenz in eine Umrechnung eingeht, wird die Sache schwierig.
Wie ebenfalls viele Leute bereits bewiesen haben, die sich über seltsame
Delays bei _delay_us(n) mit nicht-konstantem n wunderten.
Ob du dir bei falscher Parametrisierung nun an unpassendster Stelle
Fliesskommarechnung reinziehst, oder 32/64-Bit Integerrechnung, ist
letztlich schnuppe, weil beides gleich falsch. Aber bei
Fliesskommarechnung stehen die Chancen besser, dass du es frühzeitig
merkst.
Hat man eine schnelle Multiplikation, dann kann man Delayroutinen auch
so schreiben, dass diese Umrechnung an kritischer Stelle stattfindet,
ohne das Delay dabei nennenswert zu beeinflussen. Das ist beispielsweise
bei ARMs möglich. Bei AVRs aber mangels schneller Multiplikation nicht,
jedenfalls nicht mit allen Modellen und in grossem Wertebereich.
Vorschlag: Die Delay-Routinen werden mit einem #if geschützt, dessen
zugehöriges #define vor dem #include selbst einzügen muss, analog zum
Takt. Tut man das nicht, dann exististieren sie nicht. Sinnvoller Name
wäre beispielsweise
A. K. schrieb:> Eine von dir vielleicht "intuitiv" genannte Programmierung von Delays> kriegst du bei AVRs nur mit dem, was Microchip dafür zur Verfügung> stellt. Nämlich Delay-Routinen, die eine wählbare Anzahl Taktzyklen> warten, über verschiedenen Funktionen mit Zehnerpotenzen skaliert. Das> ist nämlich die einzige Variante, die völlig ohne Umrechnung auskommt.
Yep, das gab's in der avr-libc ja schon immer, nennt sich dort
_delay_loop_1 und _delay_loop_2. _delay_us und _delay_ms sind nur
deshalb gebaut worden, weil das den Nutzern zu umständlich war.
Ansonsten löst sich die Diskussion eigentlich, wenn man wenigstens die
Compilerwarnungen ansieht, gerade in Wohlgefallen auf. Man nehme
folgenden Code, der die beiden typischen Fehler beinhaltet, die man
in diesem Zusammenhang machen kann (Optimierung abgeschaltet bzw.
keine Konstante übergeben):
1
#define F_CPU 3000000
2
#include<util/delay.h>
3
4
void
5
delay_100(void)
6
{
7
_delay_ms(100);
8
}
9
10
void
11
delay(intms)
12
{
13
_delay_ms(ms);
14
}
Compiliere ich ihn nun ohne Optimierung, gibt's eine Warnung:
1
/usr/lib/gcc/avr/4.5.1/../../../../avr/include/util/delay.h:94:3: warning: #warning "Compiler optimizations disabled; functions from <util/delay.h> won't work as designed"
Wer sowohl Warnungen als auch Doku ignoriert, ich glaube, dem ist
nach menschlichem Ermessen nicht mehr zu helfen.
Schalte ich die Optimierung ein, dann bekomme ich:
1
/usr/lib/gcc/avr/4.5.1/../../../../avr/include/util/delay.h: In function 'delay':
2
/usr/lib/gcc/avr/4.5.1/../../../../avr/include/util/delay.h:152:28: error: __builtin_avr_delay_cycles expects an integer constant.
Jörg Wunsch schrieb:> Wer sowohl Warnungen als auch Doku ignoriert, ich glaube, dem ist> nach menschlichem Ermessen nicht mehr zu helfen.
Da stimme ich zu, weiß aber auch wie die Realität aussieht - wenn man
fünfzehen Jahre alte Warnings im Code sieht, um die sich nie jemand
gekümmert hat... es passiert leider. :-(
Mark Brandis schrieb:> Jörg Wunsch schrieb:>> Wer sowohl Warnungen als auch Doku ignoriert, ich glaube, dem ist>> nach menschlichem Ermessen nicht mehr zu helfen.>> Da stimme ich zu, weiß aber auch wie die Realität aussieht - wenn man> fünfzehen Jahre alte Warnings im Code sieht, um die sich nie jemand> gekümmert hat... es passiert leider.
Du kümmerst dich seit 15 Jahren nicht um deine Warnings und verlangst
stattdessen, daß die Tools per Intuition erahnen, ob du wirklich das
willst was hingeschrieben steht oder doch was anderes? Vielleich keine
exakten Delays und Rundung anstatt exakter Berechnung? Oder doch nicht?
Davon abgesehen: die Warnung bekommt man ja nur, wenn man nicht
optimiert. Wer ein seit 15 Jahren in Produktion befindliches Projekt
laufen hat und dort auf einem AVR ohne Optimierung arbeitet, nun, der
hat irgendwie ganz andere Probleme. ;-)
Johann L. schrieb:> Du kümmerst dich seit 15 Jahren nicht um deine Warnings und verlangst> stattdessen, daß die Tools per Intuition erahnen, ob du wirklich das> willst was hingeschrieben steht oder doch was anderes? Vielleich keine> exakten Delays und Rundung anstatt exakter Berechnung? Oder doch nicht?
Wer lesen kann, ist klar im Vorteil: Es gibt Warnungen in 15 Jahre altem
Code, der nicht von mir stammt. Die Leute, die den geschrieben haben,
sind auch längst nicht mehr da - so wie es halt immer ist.
Und bevor die nächste dumme Frage kommt: Ja, die Warnungen die man
einfach entfernen kann (z.B. nicht mehr benutzte lokale Variablen) habe
ich entfernt. Manchmal steckt hinter einer Warnung aber auch ein
logischer Fehler - ein (sinngemäßes) Beispiel:
1
a==b;
2
warning:statementwithnoeffect
und da man nicht die Intention des ursprünglichen Programmierers kennt,
ist es gar nicht so einfach zu entscheiden was man macht: Den Code an
der Stelle so lassen wie er ist? Unschön, weil er im Grunde genommen
fehlerhaft ist. Den Code ändern? Tja, nur wie, wenn man nicht weiß wie
er eigentlich hätte sein sollen, und keine Requirements/Dokumentation
aus dieser Zeit existieren in denen man nachschauen könnte. Wenn man in
dem Beispiel den Vergleich in eine Zuweisung ändert, funktioniert die
Software vielleicht nicht mehr so wie sie soll...
Es ist schon ein Kreuz mit dem Legacy Code. :-(
Nun, für das gezeigte Statement würde ich mir den erzeugten Code
ansehen: wenn, wie erwartet, der Compiler das statement with noeffect ohnehin wegwirft und das Ergebnis trotzdem funktioniert,
dann hätte ich auch keine Bauchschmerzen, das rauszuschmeißen.
(Ein VCS werdet ihr ja haben, das die Historie dann entsprechend
dokumentiert.)
In diesem konkreten Fall:
Rausschmeissen würde ich es nicht unbedingt.
Aber zumindest auskommentieren, mit einem Kommentar dazu warum das
entfernt wurde (weil es eben sowieso keinen Effekt hat)
Das kommt schon mal vor, dass man Fehler findet, die seit Anbeginn der
Welt im Code drinnen sind, wenn auch selten. (Persönlicher Rekord: ~14
Jahre)
D.h. Falls dieser Vergleich ein Tippfehler ist und eigentlich eine
Zuweisung hätte sein sollen, dann halte mich mir mit Auskommentieren
zumindest das Wissen im Code, dass in dieser Stelle ein zweifelhaftes
Statement stand. So wird nicht vergessen, dass da möglicherweise eine
Zuweisung fehlt - was bis einfach nur noch nie jemandem aufgefallen ist,
bzw. anscheinend keine Auswirkungen hat.
Ich hab auch schon Code gesehen, der ziemlich offensichtlich keinen
Zweck erfüllt und wenn man dann mit dem Programmierer spricht, kommt man
drauf, dass er sich den nur eingebaut hat, damit er eine Position hat,
an die er einen Breakpoint platzieren kann und er vergessen hat, den
Code wieder rauszunehmen.
Wenn die Dinge nicht so klar sind, bleibt nur: analysieren was die
Absicht gewesen sein könnte und dann entscheiden was zu tun ist.
Im absoluten Zweifelsfall den Code so umändern, dass zwar die Warnung
verschwindet, aber der Compiler die Sequenz immer noch gleich übersetzt.
Noch in jeder Firma, in der ich bisher war galt die Devise: Compiliert
wird mit einem sehr hohen Warning Level und Warnungen werden als Fehler
betrachtet. Code der nicht fehlerfrei compiliert, darf unter Androhung
von hohen Strafen auf keinen Fall in die Source Code Verwaltung
eingecheckt werden.