#define ultraschall_StarteMessungSRF04(PORT, TriggerPulse_PIN) {PORT |= (1<<TriggerPulse_PIN); _delay_us(12); PORT &=~ (1<<TriggerPulse_PIN);}
Wenn ich die {} weglasse dann sagt der Compiler nichts. Jedoch wird in
einem CBuch geschrieben, dass die {} notwendig sind bei mehreren
Anweisungen.
Ich kanns leider nicht ausprobieren, da ich nicht zuhause bin.
Gruß Bro
Frag dich einfach mal, was wohl rauskommt, wenn du
1
if(irgendeine_Bedingung)
2
ultraschall_StarteMessungSRF04;
programmierst und die Klammern weglässt.
Eines der größten Misverständnisse in C ist wohl, dass dem Präprozessor
zuviel Intelligenz zugeschrieben wird.
Der macht Textersetzung! Sonst nichts! Den interessiert keine C-Syntax.
1
#define A B
Text A wird an allen vorkommenden Stellen durch Text B ersetzt. Und erst
dann gehts ab durch den Compiler. (OK. Makro Argumentsubstitution
erfolgt noch. Aber dann ist schon Ende der Fahnenstange)
Sieht das Programm so aus
1
#definer TEST i = 5; j = 8
2
3
intmain()
4
{
5
intk=2;
6
intj=0;
7
inti=0;
8
9
if(k==2)
10
TEST;
11
}
dann bleibt nach der Textersetzung übrig
1
intmain()
2
{
3
intk=2;
4
intj=0;
5
inti=0;
6
7
if(k==2)
8
i=5;j=8;
9
}
Und wenn man das jetzt nach den C-Regeln umbricht und richtig einrückt,
dann steht da
1
intmain()
2
{
3
intk=2;
4
intj=0;
5
inti=0;
6
7
if(k==2)
8
i=5;
9
10
j=8;
11
}
und das dürfte dann nicht wirklich die ursprüngliche Absicht gewesen
sein. Die ursprüngliche Absicht war es wohl, dass auch die Zuweisung an
j von der if-Bedingung abhängt.
Perfektionisten reichen die Klammern noch nicht, weil man sich sonst bei
if (was)
ultraschall_StarteMessungSRF04(x,y);
else
abort();
über die Syntaxfehlermeldung des Compilers wundert. Die schreiben dann
#define macro(x) do{ ... }while(0)
weil nur so das Semikolon dahinter nicht stört.
Floh schrieb:> pfui. Aus.> Für sowas gibts Funktionen.
Aber dauert es nicht einbisschen länger wenn ich für diese Aufgabe ne
Funktion schreibe?
Das sind genau 3 Befehle, mit 3 Parameter.
Also
statt eine Definitionszeile
sowas:
Außerdem kriege ich dann probleme wenn ich einen String PORTA als
Argument bekomme, dann muss ich wieder vergleichen, oder ich arbeite nur
mit Zahlen was noch unübersichtlicher ist.
Also ich finde Textersetzung ist in diesem Fall definitiv besser, meine
Meinung halt.
Gruß Bro
A. K. schrieb:> Perfektionisten reichen die Klammern noch nicht, weil man sich sonst bei> if (was)> ultraschall_StarteMessungSRF04(x,y);> else> abort();> über die Syntaxfehlermeldung des Compilers wundert. Die schreiben dann> #define macro(x) do{ ... }while(0)> weil nur so das Semikolon dahinter nicht stört.
versteh ich nicht^^
Karl heinz Buchegger schrieb:> und das dürfte dann nicht wirklich die ursprüngliche Absicht gewesen> sein. Die ursprüngliche Absicht war es wohl, dass auch die Zuweisung an> j von der if-Bedingung abhängt.
Weiß was du meinst. Danke
Gruß Bro
Brocken Sei schrieb:> Floh schrieb:>> pfui. Aus.>> Für sowas gibts Funktionen.>> Aber dauert es nicht einbisschen länger wenn ich für diese Aufgabe ne> Funktion schreibe?> Das sind genau 3 Befehle, mit 3 Parameter.
Was ist dir lieber:
20 Sekunden mehr aufwand beim Tippen oder 3 Stunden Fehlersuche?
> Außerdem kriege ich dann probleme wenn ich einen String PORTA
du kriegst sinnvollerweise keinen String.
Aber im Prinzip hast du dann schon recht:
So ist nun mal das Leben. Man kann nicht alles haben.
Manchmal muss man Kompromisse eingehen.
> Also ich finde Textersetzung ist in diesem Fall definitiv besser, meine> Meinung halt.
Kommt immer drauf an.
1
#define TWICE(x) 2*x
Gut oder schlecht?
Gut: man spart ein wenig Arbeit
Schlecht: Das Ergebnis von TWICE(2+1) ist nicht identisch zum Ergebnis
von TWICE(3) und mag einen überraschen.
-> Das Fehlerpotential mit Makros ist viel höher. Wenn man das
weitgehend ausschliessen kann und sich sicher ist, was man tut: Es
spricht nichts gegen Makros.
Aber eben nicht in allen Fällen. Sag dann nicht, wir hätten dich nicht
gewarnt.
Brocken Sei schrieb:> A. K. schrieb:>> Perfektionisten reichen die Klammern noch nicht, weil man sich sonst bei>> if (was)>> ultraschall_StarteMessungSRF04(x,y);>> else>> abort();>> über die Syntaxfehlermeldung des Compilers wundert. Die schreiben dann>> #define macro(x) do{ ... }while(0)>> weil nur so das Semikolon dahinter nicht stört.>>> versteh ich nicht^^
Und wieder:
mach die Textsubstitution.
Makros und ihre potentiellen Fallen kann man nur auf die Art verstehen:
Man spielt selbst Präprozessor und ersetzt probehalber das Makro mit dem
zu ersetzenden Text und sieht nach, was da rauskommt.
Brocken Sei schrieb:> Aber dauert es nicht einbisschen länger wenn ich für diese Aufgabe ne> Funktion schreibe?> Das sind genau 3 Befehle, mit 3 Parameter.
Dafür kann man das Stichwort inline verwenden, um dem Compiler zu sagen,
dass er die Funktion auch auflösen darf.
Ich sehe defines deshalb so kritisch, da
- keine Überprüfung der Typen, gibt sehr kriptische Fehlermeldungen
- mögliche Probleme mit anderen defines
inline void ultraschall_StarteMessungSRF04(uint8_t* trigport, uint8_t
trigpin)
{
*trigport |= (1<<trigpin);
_delay_us(12);
*trigport &=~ (1<<trigpin);
}
Aufgerufen wirds dann so:
ultraschall_StarteMessungSRF04(& PORTA, 4);
:-) Hoffe ich hab keinen Wurm drinnen.
Brocken Sei schrieb:> versteh ich nicht^^
Was geht:
if (cond) statement; else ...
if (cond) {...} else ...
Was nicht geht:
if (cond) {...}; else ...
Aber was wieder geht:
if (cond) do{...}while(0); else ...
Es gäbe sonnst noch inline-Funktionen, bei denen auch eine art
Textersetzung durchgeführt wird. Sollten seit dem C99-Standard auch in C
verfügbar sein.
Floh schrieb:> inline void ultraschall_StarteMessungSRF04(uint8_t* trigport, uint8_t> trigpin)> {> *trigport |= (1<<trigpin);> _delay_us(12);> *trigport &=~ (1<<trigpin);> }>> Aufgerufen wirds dann so:> ultraschall_StarteMessungSRF04(& PORTA, 4);>> :-) Hoffe ich hab keinen Wurm drinnen.
Nur eine klitzekleine Warnung. Ist aber nicht weiter schlimm. Du hast
einen 'Modifier' vergessen :-)
Nicht wirklich.
Da muss jeder C Jünger durch.
Entdeckt man Makros, sind sie plötzlich Allheilmittel für alles. Nach
dem 3ten Fehler, den man sich durch die Verwendung von Makros
leichtsinnig eingehandelt hat und den man stundenlang gesucht hat, kehrt
dann meist Ernüchterung ein.
Karl heinz Buchegger schrieb:> Da muss jeder C Jünger durch.> Entdeckt man Makros, sind sie plötzlich Allheilmittel für alles. Nach> dem 3ten Fehler, den man sich durch die Verwendung von Makros> leichtsinnig eingehandelt hat und den man stundenlang gesucht hat, kehrt> dann meist Ernüchterung ein.
Naja da hab ich schlechte Erfahrungen mit lernresistenten Kindern.
Teilweise komplette Programmteile bis ins unleserliche in defines
gesteckt und trotzdem ist die Software natürlich perfekt und die
Hardware (Roboter) schuld daran, dass nix funktioniert.
-.-
Ok,
Karl heinz Buchegger schrieb:> inline void ultraschall_StarteMessungSRF04( volatile uint8_t* trigport, uint8_t
trigpin)
> {> *trigport |= (1<<trigpin);> _delay_us(12);> *trigport &=~ (1<<trigpin);> }
Aber wie soll denn das funktionieren?
Wenn also trigport A gewählt ist, dann ist PORTA, odr nur A das ein
String und kein uint8_t, dh im Endeffekt steht dann
zahl |= (1<<zahl)
die linke Zahl wird nie einen Port darstellen.
Gruß bro
Brocken Sei schrieb:> Aber wie soll denn das funktionieren?
Indem man der Funktion die Adresse des Ports gibt, an der die Ausgabe
erfolgen soll. Steht doch dort
> Wenn also trigport A gewählt ist, dann ist PORTA, odr nur A das ein> String
Da ist gar nichts ein String.
> und kein uint8_t,
Es wird auch kein uint8_t übergeben sondern ein uint8_t*. Genau genommen
sogar ein volatile uint8_t*
> dh im Endeffekt steht dann
Nö.
Das steht nicht da.
PORTA ist keine Zahl (ist es schon, aber anders als du jetzt denkst).
Für dich als C-Programmierer ist PORTA einfach nur eine uint8_t
Variable, die irgendwie magisch mit dem physikalischen Port verknüpft
ist. Wie diese Verknüpfung genau realisiert ist, ist erst mal
uninteressant. Für alle praktischen Zwecke kann man sich PORTA einfach
nur als Variable, so wie i, j, k, HamstiBamsti, AnzahlTicks, ...
vorstellen. Nur das diese Variable vordefiniert ist und einfach so
existiert (wenn man avr/io.h inkludiert)
Und: So wie jede andere Variable, hat auch PORTA eine Adresse im
Speicher, die man ermitteln kann und die man an Funktionen übergeben
kann.
Brocken Sei schrieb:> Aber wie soll denn das funktionieren?> Wenn also trigport A gewählt ist, dann ist PORTA, odr nur A das ein> String und kein uint8_t, dh im Endeffekt steht dann> zahl |= (1<<zahl)> die linke Zahl wird nie einen Port darstellen.
PORTA ist auch nur ein define und steht für eine Speicherstelle. Daher
funktioniert das.
:-)
@Karl && Floh:
Verstehe jetzt, danke euch. Mich hat nur das &PORTA irritiert, jetzt
weiß ich was damit gemeint ist.
Theoretisch könnte ich aber auch die Zeiger weglassen und nur mit die
Variablen arbeiten, so:
SO geht es ja gar nicht.
Da wird beim Aufruf eine Kopie des aktuellen Werts von PORTA
oder was auch imemr übergeben, und diese Kopie wird in der Funktion
verändert.
Davon merkt der Port aber nichts.
So etwas geht erst in C++ mit Referenzen.
Nein so gehts nicht!
Du solltest definitiv ein gutes C-Buch lesen!
Karl Heinz hat da nicht umsonst ein "volatile uint8_t*" drin stehen, der
weis nämlich was er tut.
Klaus Wachtler schrieb:> Da wird beim Aufruf eine Kopie des aktuellen Werts von PORTA> oder was auch imemr übergeben, und diese Kopie wird in der Funktion> verändert.
Stimmt da hast du wohl recht, deswegen auch mit Adressen.
OK, danke
Gruß Bro
... schrieb:> Karl Heinz hat da nicht umsonst ein "volatile uint8_t*" drin stehen, der> weis nämlich was er tut.
Ist das nicht eher Frage der Zeiger, denn indem man *PORT schreibt wird
sofort PORTA eingesetzt da ja die Adresse von PORTA übertragen wurde.
Gruß Bro
Brocken Sei schrieb:> ... schrieb:>> Karl Heinz hat da nicht umsonst ein "volatile uint8_t*" drin stehen, der>> weis nämlich was er tut.>> Ist das nicht eher Frage der Zeiger, denn indem man *PORT schreibt wird> sofort PORTA eingesetzt da ja die Adresse von PORTA übertragen wurde.
ja und?
Deswegen muß trotzdem *PORT als volatile deklariert werden, weil
es sich ja dauernd ändern kann und der Compiler sonst nix davon weiß.
Ich sehe nicht was das volatile hier zu suchen hat, denn dieses wird nur
benötigt wenn der Wert der Variable immer bei jeder Verwendung der
Variable neu aus dem Hauptspeicher geladen wird.
Gruß Bro
Brocken Sei schrieb:> Ist das nicht eher Frage der Zeiger, denn indem man *PORT schreibt wird> sofort PORTA eingesetzt
Du tust dir nichts gutes, wenn du hier 'ersetzen' denkst.
In der Funktion hast du eine Adresse im Speicher an der der Wert zu
finden ist. Ob das jetzt tatsächlich ein Port ist, oder ob das eine
normale Variable ist, spielt keine Rolle. In der Funktion existiert nur
die Adresse im Speicher.
Denk in erster Linie nicht daran, was dir der Compiler hier alles
optimieren kann, sondern denk in C Einheiten. Alles andere
weiterführende ist Sache des Compilers.
Klaus Wachtler schrieb:> weil> es sich ja dauernd ändern kann und der Compiler sonst nix davon weiß.
Hmm, was kann sich dauernd ändern?
Ich rufe die Funktion immer neu auf und gebe ihm immer die selben
Parameter.
Was meinst du jetz?
Gruß Bro
Karl heinz Buchegger schrieb:> In der Funktion hast du eine Adresse im Speicher an der der Wert zu> finden ist. Ob das jetzt tatsächlich ein Port ist, oder ob das eine> normale Variable ist, spielt keine Rolle. In der Funktion existiert nur> die Adresse im Speicher.
Stimmmmmtt, genau, und da kann es passieren, dass sich die Adresse
ändern, is mir grad eingefallen, und deswegen immer neu laden.
Danke Karl.
@Klaus:
Hast recht kappt, wusste nur nicht dass du mit ändern die Adresse meinst
Gruß Bro
(übrigens solltest du dir angewöhnen Namen die ausschliesslich in
Grossbuchstaben geschrieben sind, für Makros zu reservieren. Und auch
umgekehrt: Was kein Makro ist, wird auch nicht komplett gross
geschrieben. Das hier ist eine normale Pointervariable. Also wird sie
nicht komplett gross geschrieben)
hier wird also *PORT verändert. Soweit so gut
1
_delay_us(delayTime_tillSTART_us);
der Compiler findet hier (durch inlining der _delay_us Funktion) raus,
dass dieses *PORT nicht vreändert.
1
*PORT&=~(1<<TriggerPulse_PIN);
und dann wird *PORT noch einmal verändert.
Wenn der COmpiler jetzt clever ist, dann findet er raus, dass beide
Veränderungen von *PORT jedesmal dasselbe Bit betreffen.
Wozu dann die erste Veränderung, wenn die sowieso dann noch einmal
geändert wird?
Eben. Braucht es nicht. Die erste Manipulation fliegt raus!
Brocken Sei schrieb:> Karl heinz Buchegger schrieb:>> In der Funktion hast du eine Adresse im Speicher an der der Wert zu>> finden ist. Ob das jetzt tatsächlich ein Port ist, oder ob das eine>> normale Variable ist, spielt keine Rolle. In der Funktion existiert nur>> die Adresse im Speicher.>> Stimmmmmtt, genau, und da kann es passieren, dass sich die Adresse> ändern,
Nein.
Die Adresse kann sich innerhalb der Funktion nicht ändern. Aber der
Wert, der sich an dieser Adresse im Speicher befindet.
volatile uint8_t *
ist etwas anderes als
uint8_t * volatile
Brocken Sei schrieb:> Stimmmmmtt, genau, und da kann es passieren, dass sich die Adresse> ändern, is mir grad eingefallen, und deswegen immer neu laden.>> Danke Karl.
Blödsinn!
Moment zum nachdenken bitte.
Gruß Bro
Brocken Sei schrieb:> Moment zum nachdenken bitte.
Denken schadet nicht.
Du wirst aber nicht für den Rest deines Lebens ergründen können, was ein
Compiler beim Übersetzen aus deinem Programm macht.
Deshalb einfach der gute Rat:
Wenn eine Variable nicht alleine unter der Obhut des Compilers ist
und/oder mehrere Threads/Interruptroutinen/... auf eine Variable
zugreifen, dann als volatile deklarieren.
Denken hin oder her, es geht sonst irgendwann schief.
Klaus Wachtler schrieb:> Ich habe schon ein paar C-Bücher gelesen, ehrlich!
Das glaub ich Dir aufs Wort und es lag mir fern das zu bezweifeln. Ich
war einfach nur zu langsam beim Tippen.
Mein Post bezog sich eigentlich auf den Post über Deinen:
Beitrag "Re: #define Frage"
Karl heinz Buchegger schrieb:> der Compiler findet hier (durch inlining der _delay_us Funktion) raus,> dass dieses *PORT nicht vreändert.
ok, und?
Karl heinz Buchegger schrieb:> und dann wird *PORT noch einmal verändert.
Ja, und das passt dann auch, denn *PORT = PORTA in dem Fall, die Adresse
bleibt nämlich gleich
Karl heinz Buchegger schrieb:> Wenn der COmpiler jetzt clever ist, dann findet er raus, dass beide> Veränderungen von *PORT jedesmal dasselbe Bit betreffen.
Ja wieso denn?
Steht das nicht schon fest?
Adresse von PORTA ändert sich nicht.
Mit *PORT (PORT = Adresse(PORTA)) spreche ich die Wertigkeit der
"Variable" PORTA an, diese wird dann verändert.
Wieso hat das jetzt was mit den Bits zu tun?
Karl heinz Buchegger schrieb:> Aber der> Wert, der sich an dieser Adresse im Speicher befindet.
Der ändert sich hier 2 mal, zwar genau das selbe Bit aber was solls?
Karl heinz Buchegger schrieb:> volatile uint8_t *>> ist etwas anderes als>> uint8_t * volatile
Gehört das dazu oder ist das eine Bemerkung nebenbei?
Ich glaube mal du meinst damit, dass wenn mehrere Variablen definiert
werden beim ersten alle als volatile definiert werden und beim 2ten nur
das erste?
Gruß Bro
Brocken Sei schrieb:>> und dann wird *PORT noch einmal verändert.>> Ja, und das passt dann auch, denn *PORT = PORTA in dem Fall, die Adresse> bleibt nämlich gleich
PORTA ist in dieser Funktion schon uninteressant.
Interessiert keinen mehr.
Die Funktion hat den Auftrag den Wert an Port (welcher *Port ist) zu
verändern. Nicht mehr und nicht weniger.
Für die Funktion macht es keinen Unterschied ob du schreibst
i = 5
_delay_us( 200 );
i = 8;
die zweite Zuweisung an i überschreibt die erste. Und da _delay_us das i
nicht verändert und auch nicht darauf angewiesen ist, das i einen
speziellen Wert hat (weil sie i gar nicht benutzt), gibt es keinen Grund
die erste Zuweisung überhaupt auszuführen.
Es sei denn, man macht i volatile.
Vergiss endlich einmal dass innerhalb der Funktion *Port identisch ist
mit PORTA. Interessiert keine Sau. Das ist nur "zufällig" so, weil du
die Funktion mit &PORTA aufrufst. Das ist aber völlig irrelevant, wenn
sich der Compiler die Funktion selbst vornimmt. Innerhalb der Funktion
ist das ein ganz normaler Pointer auf einen stink normalen uint8_t.
Nicht mehr und nicht weniger.
Und das darf er eben nicht sein. Wir wollen, dass jede Manipulation von
*Port tatsächlich so ausgeführt wird, wie wir sie hingeschrieben haben.
Daher das volatile.
Karl heinz Buchegger schrieb:> Und da _delay_us das i> nicht verändert und auch nicht darauf angewiesen ist, das i einen> speziellen Wert hat (weil sie i gar nicht benutzt), gibt es keinen Grund> die erste Zuweisung überhaupt auszuführen.
Ok ich muss leider sagen das ergibt keinen Sinn für mich. Was hat i mit
delay_us zu tun??
Gruß Bro
Karl heinz Buchegger schrieb:> Das ist nur "zufällig" so, weil du> die Funktion mit &PORTA aufrufst.
Ist das nicht Grundvoraussetzung dass das ganze funktioniert?
Gruß Bro
Brocken Sei schrieb:> Karl heinz Buchegger schrieb:>> Und da _delay_us das i>> nicht verändert und auch nicht darauf angewiesen ist, das i einen>> speziellen Wert hat (weil sie i gar nicht benutzt), gibt es keinen Grund>> die erste Zuweisung überhaupt auszuführen.>> Ok ich muss leider sagen das ergibt keinen Sinn für mich. Was hat i mit> delay_us zu tun??
Eben. Nichts.
Wozu einen Gartenzaun grün streichen, wenn er 5 Minuten später sowieso
rot gestrichen wird?
Ist alles dasselbe.
Es geht darum, dass das für den Compiler ohne das volatile ansonsten
eine sinnlose Aktion ist, die er straffrei rauswerfen darf.
i = 5;
_delay_us( 200 );
i = 8;
*Port = 27;
_delay_us( 200 );
*Port = 32;
*Port |= ( 1 << 5 );
_delay_us( 200 );
*Port &= ~( 1 << 5 );
All diese 3 Sequenzen sind von genau derselben Machart. Eine Aktion wird
von einer anderen Aktion 'überschrieben'. Überschrieben in dem Sinne,
dass nach Abarbeitung dieser 3 Anweisungen, das Ergebnis
ununterschiedbar davon ist, wie wenn die erste Aktion nie gemacht worden
wäre.
(Wobei ich zugeben muss, dass es für den Compiler im letzten Fall schon
erheblich schwieriger ist, dies tatsächlich festzustellen)
genau darum gehst.
Wenn du
i = 5;
_delay_us( 200 );
i = 8;
ausführst, dann ist danach das Ergbnis nicht zu unterscheiden von
_delay_us( 200 );
i = 8;
Wenn du
*Port = 27;
_delay_us( 200 );
*Port = 32;
programmierst, dann ist das Ergebnis nach der letzten Anweisung nicht zu
unterscheiden von
_delay_us( 200 );
*Port = 32;
Wenn du
*Port |= ( 1 << 5 );
_delay_us( 200 );
*Port &= ~( 1 << 5 );
programmierst, dann ist nach Ausführung dieser 3 Anweisungen das
Ergebnis nicht zu unterscheiden von
_delay_us( 200 );
*Port &= ~( 1 << 5 );
Daher könnte der COmpiler auf die Idee kommen: Ja wenn das so ist, dann
brauch ich doch jeweils die erste Anweisung gar nicht ausführen lassen.
Kommt ja sowieso hinten nach immer dasselbe raus.
Das und nur das, ist genau der Grund warum ein PORTA eine 'volatile
uint8_t' Variable ist und keine gewöhnliche Variable. Und das und nur
das ist auch der Grund warum ein &PORTA den Datentyp volatile uint8_t*
hat. Und diesen Datentyp sollte die Funktion besser auch aufgreifen und
nicht das volatile wegwerfen, den ansonsten kann es innerhalb der
Funktion genau wieder zu dem 'Problem' kommen, dass der Compiler anfängt
mit *Port zu optimieren.
Ok, vielleicht versteh ich was falsch.
Wir übergeben der inline Funktion (die den Vorteil hat, dass sie keine
Sprünge benötigt und dass Datentyp-Übergaben überprüft werden) die
Adresse von PORTA mit &PORTA, das ist klar.
In der Funktion lesen wir diese Adresse mit Port aus, indem wir einen
Zeiger definieren. uint8_t *Port. Dieser Zeiger hat die Adresse von
PORTA.
Mit *Port sagen wir im Prinzip PORTA = ... . Und das eben zwei mal. Was
ist das Problem?
_delay_us hat nichts mit *Port zu tun, oder versteh ich da was falsch?
Gruß Bro
Brocken Sei schrieb:> Karl heinz Buchegger schrieb:>> Das ist nur "zufällig" so, weil du>> die Funktion mit &PORTA aufrufst.>> Ist das nicht Grundvoraussetzung dass das ganze funktioniert?
Der Funktion ist es völlig Wurscht, mit welchem Pointer sie aufgerufen
wird. Die weiß nichts von einem PORTA. Die soll das auch gar nicht
wissen.
Brocken Sei schrieb:> Ok, vielleicht versteh ich was falsch.>> Wir übergeben der inline Funktion (die den Vorteil hat, dass sie keine> Sprünge benötigt und dass Datentyp-Übergaben überprüft werden) die> Adresse von PORTA mit &PORTA, das ist klar.
Ja.
Aber innerhalb der Funktion verliert &PORTA seinen Sonderstatus, wenn du
das volatile weglässt.
> In der Funktion lesen wir diese Adresse mit Port aus, indem wir einen> Zeiger definieren. uint8_t *Port. Dieser Zeiger hat die Adresse von> PORTA.
Zum Beispiel.
Er könnte auch die Adresse von i haben.
Der Funktion ist das egal.
> Mit *Port sagen wir im Prinzip PORTA = ... . Und das eben zwei mal. Was> ist das Problem?
Weißt du was.
Nimm ein Makro.
Mir wird das jetzt zu böd.
Ich wiederhols noch mal, damit ich mich nicht dem Vorwurf aussetzen
muss, ich hätte es nicht versucht, nur weil du es nicht gesehen hast:
Brocken Sei schrieb:> Karl heinz Buchegger schrieb:>> Und da _delay_us das i>> nicht verändert und auch nicht darauf angewiesen ist, das i einen>> speziellen Wert hat (weil sie i gar nicht benutzt), gibt es keinen Grund>> die erste Zuweisung überhaupt auszuführen.>> Ok ich muss leider sagen das ergibt keinen Sinn für mich. Was hat i mit> delay_us zu tun??
Eben. Nichts.
Wozu einen Gartenzaun grün streichen, wenn er 5 Minuten später sowieso
rot gestrichen wird?
Ist alles dasselbe.
Es geht darum, dass das für den Compiler ohne das volatile ansonsten
eine sinnlose Aktion ist, die er straffrei rauswerfen darf.
i = 5;
_delay_us( 200 );
i = 8;
*Port = 27;
_delay_us( 200 );
*Port = 32;
*Port |= ( 1 << 5 );
_delay_us( 200 );
*Port &= ~( 1 << 5 );
All diese 3 Sequenzen sind von genau derselben Machart: Eine Aktion wird
von einer anderen Aktion 'überschrieben'. Überschrieben in dem Sinne,
dass nach Abarbeitung dieser 3 Anweisungen, das Ergebnis
ununterschiedbar davon ist, wie wenn die erste Aktion nie gemacht worden
wäre.
(Wobei ich zugeben muss, dass es für den Compiler im letzten Fall schon
erheblich schwieriger ist, dies tatsächlich festzustellen)
genau darum gehst.
Wenn du
i = 5;
_delay_us( 200 );
i = 8;
ausführst, dann ist danach das Ergbnis nicht zu unterscheiden von
_delay_us( 200 );
i = 8;
Wenn du
*Port = 27;
_delay_us( 200 );
*Port = 32;
programmierst, dann ist das Ergebnis nach der letzten Anweisung nicht zu
unterscheiden von
_delay_us( 200 );
*Port = 32;
Wenn du
*Port |= ( 1 << 5 );
_delay_us( 200 );
*Port &= ~( 1 << 5 );
programmierst, dann ist nach Ausführung dieser 3 Anweisungen das
Ergebnis nicht zu unterscheiden von
_delay_us( 200 );
*Port &= ~( 1 << 5 );
Daher könnte der COmpiler auf die Idee kommen: Ja wenn das so ist, dann
brauch ich doch jeweils die erste Anweisung gar nicht ausführen lassen.
Kommt ja sowieso hinten nach immer dasselbe raus.
Das und nur das, ist genau der Grund warum ein PORTA eine 'volatile
uint8_t' Variable ist und keine gewöhnliche Variable. Und das und nur
das ist auch der Grund warum ein &PORTA den Datentyp volatile uint8_t*
hat. Und diesen Datentyp sollte die Funktion besser auch aufgreifen und
nicht das volatile wegwerfen, den ansonsten kann es innerhalb der
Funktion genau wieder zu dem 'Problem' kommen, dass der Compiler anfängt
mit *Port zu optimieren.
Karl heinz Buchegger schrieb:> Wozu einen Gartenzaun grün streichen, wenn er 5 Minuten später sowieso> rot gestrichen wird?
Mhm, ziemlich verständlich ausgedrückt und jetzt verstehe ich was du
meinst. Ich dachte das hätte was mit der inline Funktion zu tun, stimmt
aber nicht. In den Büchern die ich lese ist volatile kaum verwendet
worden, nur mal erwähnt.
Also im Großen und ganzen läuft das so ab:
Wenn ich Operationen habe, die sich immer wieder überschreiben dann muss
ich diese mit volatile ausstatten?
Habe ich nicht gedacht, denn wenn ich eine Rechnung mit mehreren
Schritten brauche dann definiere ich auch nicht volatile.
ABer ok, wenn das Tatsache ist.
Danke für die HIlfe.
Gruß Bro
... schrieb:> Klaus Wachtler schrieb:>> Ich habe schon ein paar C-Bücher gelesen, ehrlich!>> Das glaub ich Dir aufs Wort und es lag mir fern das zu bezweifeln. Ich> war einfach nur zu langsam beim Tippen.> Mein Post bezog sich eigentlich auf den Post über Deinen:> Beitrag "Re: #define Frage"
(dachte ich mir, konnte mir die Antwort aber leider nicht verkneifen :-)
Brocken Sei schrieb:> Ok, vielleicht versteh ich was falsch.>> Wir übergeben der inline Funktion (die den Vorteil hat, dass sie keine> Sprünge benötigt und dass Datentyp-Übergaben überprüft werden) die> Adresse von PORTA mit &PORTA, das ist klar.
Auch eine inline Funktion ist zuallererst einfach nur eine Funktion.
Du scheinst du mehr hineinzulesen als dahintersteckt.
Funktion ist Funktion. Ob inline oder nicht.
Vor allen Dingen findet da keine irgendwie geartete Textsubstitution
statt. Zumindest nicht konzeptionell.
Karl heinz Buchegger schrieb:> Aber innerhalb der Funktion verliert &PORTA seinen Sonderstatus, wenn du> das volatile weglässt.
Was heißt das?
Gruß Bro
Brocken Sei schrieb:> In den Büchern die ich lese ist volatile kaum verwendet> worden, nur mal erwähnt.
Das liegt einfach daran, daß man es selten braucht - außer bei
dem embedded-Kram.
Ab PCs aufwärts braucht man volatile entweder
- bei hardwarenaher Programmierung iinerhalb des OS oder
Treiebrn
- bei Multithreading (auf das viele Bücher gar nicht
oder nur am Rande eingehen)
Bei Controllern ist aber aber erstens viel mahr HW-naher Kram
dabei, zweitens hat man keine Unterstützung durch ein
ausgefeiltes OS, sondern muß stattdessen mit Interrupts etc.
selber hantieren.
Daß in deinen Büchern kaum volatile vorkommt, heißt also nicht,
daß du es hier nicht brauchst.
volatile ist kein Steckenpferd von komischen Leuten hier,
sondern einfach Alltag.
Karl heinz Buchegger schrieb:> Vor allen Dingen findet da keine irgendwie geartete Textsubstitution
Das ist mir schon bewusst, es ging mir um die Adresse von PORTA, die
übergeben wird.
Wieso verschwindet die?
Ich übergebe sie doch mit &PORTA, ich könnte auch &PORTB schreiben und
es würde PORTB.7 verwendet, ich weiß dass ich da irgendeinen PORT
verwenden kann, falls du das meinst.
Aber nach deinem letzten Post ist es offensichtlich, dass nicht nur das
überschreiben von Variablen in Rechenoperationen das Schlüsselwort
volatile verwendet wird.
Karl heinz Buchegger schrieb:> Er könnte auch die Adresse von i haben.
Ja, aber ich gebe der Funktion &PORTA, also hat es auch die Adresse von
PORTA. Aber wie sich diese ändert ohne volatile verstehe ich nicht.
Gruß Bro
Brocken Sei schrieb:> Also im Großen und ganzen läuft das so ab:> Wenn ich Operationen habe, die sich immer wieder überschreiben dann muss> ich diese mit volatile ausstatten?
Wenn du haben willst, dass jede Operation auf einer Variablen so
ausgeführt wird, wie du sie hinschreibst, dann muss die Variable
volatile sein.
Und bei Ports möchte man das im Allgemeinen. Man möchte haben, dass ein
Port Pin auf 0 geht, selbst wenn er 5 Taktzyklen später wieder auf 1
gesetzt wird.
Bei Portpins ist eben nicht nur das Endergebnis interessant, sondern
auch alle Zwischenschritte. Zwischenschritte die bei normalen Variablen
völlig uninteressant sind.
ob das i in
i = 5;
i = 8;
j = 2 * i;
tatsächlich irgendwann einmal 5 war, interessiert niemanden.
Insbesondere interessiert es bei der Berechnung von 2*i nicht, weil dort
beweisbar i nicht 5 sein kann, sondern 8 ist. Daher muss man diese
Zuweisung mit 5 nicht machen und spart so etwas Rechenzeit.
Bei PortPins wollen wir das aber nicht so haben. Wenn ich mache
PORTA = 5;
PORTA = 8;
dann will ich haben, dass das Port-Register zwischendurch (und sei es
noch so kurz) den Wert 5 angenommen hat. Ich will das deswegen haben,
weil diese Zuweisung einen Nebeneffekt hat: Ein externer Pin, der eine
Elektronik ansteuert, geht kurzfristig auf 1 und dann wieder auf 0.
Das kann aber der Compiler nicht wissen. Der handelt nach seiner
obersten Optimizer-Direktive: Für meinen User an Laufzeit rausholen was
ich kann.
Daher muss man ihm mit einem volatile sagen: Vergiss alles. Ich will,
dass PORTA jeden Wert annimmt, den ich als Programmierer dafür vorsehe.
Klaus Wachtler schrieb:> Bei Controllern ist aber aber erstens viel mahr HW-naher Kram> dabei, zweitens hat man keine Unterstützung durch ein> ausgefeiltes OS, sondern muß stattdessen mit Interrupts etc.> selber hantieren.
Vielleicht sollte ich doch lieber auf 32bit Controllern umsteigen, da
brauch ich mir keine Sorgen um Schnelligkeit und bösen volatiles zu
machen.
na, Spaß^^
Gruß Bro
Brocken Sei schrieb:> Vielleicht sollte ich doch lieber auf 32bit Controllern umsteigen, da> brauch ich mir keine Sorgen um Schnelligkeit und bösen volatiles zu> machen.
Falscher Schluss.
Dort musst du dir um volatile genauso Gedanken machen.
Und noch um 100 andere Kleinigkeiten mehr.
Auch da wirst du entweder Interrupts oder Threads haben, und schon
brauchst du volatile.
Es sei denn, du hast etwas mit Linux drauf und die Threads
kommunizieren nicht über gemeinsame Variablen, sondern ipes
oder sowas.
Dann kannst in der Tat darauf verzichten, und bist wieder bei
dem, was in deinen Büchern steht. Ich fürchte jetzt nur, daß
in denen auch nichts über Pipes steht...
Brocken Sei schrieb:> Karl heinz Buchegger schrieb:>> Vor allen Dingen findet da keine irgendwie geartete Textsubstitution>> Das ist mir schon bewusst, es ging mir um die Adresse von PORTA, die> übergeben wird.> Wieso verschwindet die?
Die Adresse verscheindet nicht.
Aber wenn du das volatile weglässt
void foo( uint8_t * Addr )
{
}
dann ist der POinter innerhalb der Funktion kein Pointer mehr auf einen
volatile Wert
> Ich übergebe sie doch mit &PORTA
Ja
int main()
{
foo( &PORTA ); // hier ist PORTA noch volatile.
// Aber innerhalb der Funktion foo wäre er es nicht
// mehr.
}
>> Er könnte auch die Adresse von i haben.>> Ja, aber ich gebe der Funktion &PORTA,
Das ist aber der Funktion wurscht!
Du kannst ja auch in eine Funktion
void bar( long i )
{
...
}
int main()
{
bar( 5 );
}
einen int hineinstecken und innerhalb der Funktion ist er dann ein long.
Genauso kannst du ausserhalb einer Funktion einen volatile Wert haben,
den in eine Funktion hineinschieben und innerhalb der Funktion ist er es
nicht mehr.
Und ganz genauso kannst du ausserhalb einer Funktionen einen Pointer auf
einen volatile Wert haben und innerhalb der Funkion ist er esnicht mehr.
In
void batz( uint8_t * Addr )
{
*Addr = ...
}
ist Addr ein Pointer auf eine stink normale uint8_t Variable.
Selbst wenn du diese Funktion mit &PORTA aufrufst! Der Pointer von
ausserhalb der Funktion verliert sein volatile, wenn du diese Funktion
aufrufst.
Karl heinz Buchegger schrieb:> Wenn du haben willst, dass jede Operation auf einer Variablen so> ausgeführt wird, wie du sie hinschreibst, dann muss die Variable> volatile sein.
Aber dann gilt das nicht für das oder, weil i einmal von j und einmal
von sich abhängt?:
1
intmain()
2
{
3
inti;
4
intj=5;
5
6
i=2*j;
7
i=4*i;
8
i=5+i;
9
}
Dh. nur Variablen mit direkter Zuweisung werden weg optimiert.
Ansonsten ist jetzt alles klar, sehr schön formuliert.
Danke Karl.
Gruß Bro
Karl heinz Buchegger schrieb:> foo( &PORTA ); // hier ist PORTA noch volatile.
Hmm, hmm.
Karl heinz Buchegger schrieb:>>> Er könnte auch die Adresse von i haben.>>>> Ja, aber ich gebe der Funktion &PORTA,>> Das ist aber der Funktion wurscht!
Wie kann es der Funktion wurscht sein, wenn sie die Adresse doch
bekommt, zwar nicht als volatile weil sie einen uint8_t zeiger erwartet,
aber die adresse würde stimmtn,
ich habe volatile ben so verstanden dass bei direkten Zuweisungen
optimiert wird, was auch verständlich ist. Der Compiler hält es für
überflüssig. Die Adresse von PORTA ist aber trotzdem noch in Port
drinnen, mit oder ohne volatile.
Gruß Bro
Brocken Sei schrieb:> Karl heinz Buchegger schrieb:>> Wenn du haben willst, dass jede Operation auf einer Variablen so>> ausgeführt wird, wie du sie hinschreibst, dann muss die Variable>> volatile sein.>> Aber dann gilt das nicht für das oder, weil i einmal von j und einmal> von sich abhängt?:
genau
> Dh. nur Variablen mit direkter Zuweisung werden weg optimiert.
Wenns nur das wäre.
Meistens kommt man auf umgekehrtem Wege zum volatile
i = 5;
while( i != 8 )
mach irgendwas, in dem kein i vorkommt.
Hier kann der Compiler SChlussfolgern dass i niemals 8 werden kann
while( PINA != 5 )
...
hier ist das aber tödliche, wenn der Compiler annimmt, dass er den Wert
von PINA kennen würde. PINA kann sich ändern, ohne dass der Compiler das
im Code sehen könnte.
Dassselbe mit Variablen, die sowohl in so einer Abfrage als auch in
einer ISR benutzt werden: Für den Compiler ist das nicht ersichtlich,
dass diese Variable ihren Wert ändern kann, denn die ISR Funktion wird
ja nirgends explizit aufgerufen.
Um die Diskussion mal zu drehen:
Die PORT... sind ja letztlich so definiert (vereinfacht):
1
#define PORTA (*(volatile uint8_t *)(0x32))
Wenn ich jetzt &PORTA an eine solche Funktion übergebe:
1
voidblabla(uint8_t*p_port)...
dann ist das einfach deshalb ein Fehler, weil es das volatile
wegcasten würde.
Ohne wissen zu müssen, warum Atmel das mit volatile definiert
hat, hat man einfach &PORTA als volatile uint8_t* zu behandeln,
basta.
Brocken Sei schrieb:> Karl heinz Buchegger schrieb:>> foo( &PORTA ); // hier ist PORTA noch volatile.>> Hmm, hmm.>> Karl heinz Buchegger schrieb:>>>> Er könnte auch die Adresse von i haben.>>>>>> Ja, aber ich gebe der Funktion &PORTA,>>>> Das ist aber der Funktion wurscht!>> Wie kann es der Funktion wurscht sein, wenn sie die Adresse doch> bekommt,
der Funktion ist es wurscht, ob die Adresse die sie bekommt ausserhalb
der Funktion auf einen volatile Wert gezeigt hat oder nicht
void foo( uint8_t * Addr )
{
}
> zwar nicht als volatile weil sie einen uint8_t zeiger erwartet,> aber die adresse würde stimmtn,
Der reine Zahlenwert der Adresse: ja.
Aber volatile ist ja eine Zusatzinformation zu diesem reinen Zahlenwert.
Zusatzinformation die wichtig ist, damit der Compiler weiß, was er alles
anstellen darf mit dem Wert, den er an der angegebenen Adresse findet.
Und diese Zusatzinformation würde ja sconst beim Funktionsaufruf
verloren gehen, wenn du das volatile weglässt.
Karl heinz Buchegger schrieb:> ist Addr ein Pointer auf eine stink normale uint8_t Variable.> Selbst wenn du diese Funktion mit &PORTA aufrufst! Der Pointer von> ausserhalb der Funktion verliert sein volatile, wenn du diese Funktion> aufrufst.
Hm, ok sowie ich deinen Text verstehe geht es hier nicht darum ob die
Adresse ankommt sondern mehr dass die Variable sein Volatile verliert.
PORTA ist als ein volatile, stimmr muss es ja, denn sonst wären meine
ganzen PORT-Zuweisungen nicht mit rein genommen, sondern immer nur das
letzte.
Verständlich.
Vielen dank, ich glaube ich habe volatile verstanden.
Hmm aber das heißt volatile Vairbalen sind nicht unbedingt notwendig bei
Interrupt routinen, wenn sie nur einmal im Zuweisungsprozess vorkommen
oder?
Gruß Bro
Vergiß doch mal deine blöden Zuweisungen!
volatile ist dann nötig, wenn auf einer Variablen oder
sonstigen Speicherstelle mehr passiert, als der Compiler anhand der
aktuellen Funktion sehen kann.
Karl heinz Buchegger schrieb:> Der reine Zahlenwert der Adresse: ja.> Aber volatile ist ja eine Zusatzinformation zu diesem reinen Zahlenwert.> Zusatzinformation die wichtig ist, damit der Compiler weiß, was er alles> anstellen darf mit dem Wert, den er an der angegebenen Adresse findet.> Und diese Zusatzinformation würde ja sconst beim Funktionsaufruf> verloren gehen, wenn du das volatile weglässt.
Ok, sehr gut, habe ich mir gedacht dass das so ist.
Klaus Wachtler schrieb:> dann ist das einfach deshalb ein Fehler, weil es das volatile> wegcasten würde.
Nun ja das muss aber nicht bedeuten, oder besser gesagt es bedeutet
nicht dass es nicht funktionieren würde.
Jetzt weiß ich aber warum hier volatile auch wichtig ist, und auch warum
Atmel dies so definiert haben, und zwar damit man Zuweisungen machen
kann ohne sich sorgen zu machen, dass was optimiert wird.
Gruß Bro
Klaus Wachtler schrieb:> Ohne wissen zu müssen, warum Atmel das mit volatile definiert> hat, hat man einfach &PORTA als volatile uint8_t* zu behandeln,> basta.
Prinzipiell hast du recht, aber ich konnte nicht wissen, dass PORTA als
volatile deklariert ist.
Aber im Großen unf Ganze habe ich volatile jetzt verstanden.
Danke euch beiden, echt feine Sache.
Gruß Bro
Karl heinz Buchegger schrieb:> volatile uint8_t *>> ist etwas anderes als>> uint8_t * volatile>>
Erstmal Respekt für soviel Durchhaltevermögen beim Erklären.
Im ersten Fall ist der Zeiger volatile und zeigt auf non-volatile Daten
und im 2. Beispiel anders herum oder? Wie sieht das Verhalten des
Compilers aus?
Im 1. Fall wird beim Zugriff auf den Zeiger (also pointer1++ ....) der
Pointer-wert, aufgrund des volatile modifieres, auf jeden Fall neu aus
dem Hauptspeicher geladen. Die Daten der Speicheradresse könnten aber
aktuell auch in einem Register liegen oder?
Im 2. Fall wird beim Zugriff auf die Adresse (also *pointer2++ ...) der
Wert der Variable neu aus dem Hauptspeicher geladen
Soweit Richtig?
Genau falsch rum geraten :-)
volatile uint8_t * ist ein normaler Zeiger auf eine flüchtige,
dampfartige uint8_t.
uint8_t * volatile ist ein dampfender Zeiger auf eine solide uint8_t.
volatile uint8_t * volatile ist ein volatiler Zeiger auf eine volatile
uint8_t.
Klaus Wachtler schrieb:> Genau falsch rum geraten :-)>> volatile uint8_t * ist ein normaler Zeiger auf eine flüchtige,> dampfartige uint8_t.>> uint8_t * volatile ist ein dampfender Zeiger auf eine solide uint8_t.>> volatile uint8_t * volatile ist ein volatiler Zeiger auf eine volatile> uint8_t.
Was bedeutet das?
Hab den Unterscheid immer noch nicht.
Kannst du das bitte mit einfachen Worten erklären?
Gruß Bro
Was gibt es da zu erklären?
Es gibt Variablen, die können const sein oder nicht, und sie können
volatile sein oder nicht:
1
uint8_ti;// normale uint8_t
2
volatileuint8_tPORTA;// volatile uint8_t
Halt je nachdem, ob sie sich heimlich ändern können oder nicht.
Bei Zeigern muß man jetzt etwas mehr überlegen: soll der Zeiger selbst
volatile sein? ja oder nein
Soll der Wert, auf den der Zeiger zeigt, volatile sein? ja oder nein.
Mehr ist nicht zu verstehen, man muß es dem Compiler nur noch sagen,
und da muß man es halt so schreiben, daß er es versteht.
Du musst dich nach ihm richten und nicht umgekehrt:
1
uint8_t*p1;// normaler Zeiger auf normale uint8_t
2
volatileuint8_t*p2:// normaler Zeiger auf volatile uint8_t
3
uint8_t*volatilep3:// volatile Zeiger auf normale uint8_t
4
volatileuint8_t*volatilep4:// volatile Zeiger auf volatile uint8_t
Selbiges gilt für const, falls du das mal brauchst (braucht man oft).