Im GCC-Tutorial fand ich einen Link auf einen Thread zum Thema "Löschen
von Interrupt-Flags". Die in diesem Thread gemachten Aussagen sind nicht
richtig. Ich habe jetzt aber darauf verzichtet, diesen Thread von 2010
auszugraben.
Beitrag "Re: Problem mit ADC Auto-Trigger interrupt"
Allerdings ist er im Tutorial verlinkt.
Interrupt Flags werden gelöscht, indem man eine 1 an die entsprechende
Bitstelle schreibt. In dem Thread, der sich sinnigerweise auch noch auf
den Atmega168 bezieht, wird behauptet, dass Flags grundsätzlich mit z.B.
1
TIFR0=(1<<TOV0);
zu Löschen seien, da mit Veroderung alle anderen Flags auch gelöscht
werden. Das trifft auf die neueren AVRs nicht zu.
Um diesem Fundamentalismus entgegen zu treten:
Folgendes Testprogramm:
1
//Atmega168
2
3
volatileunsignedcharbTimer0=0;
4
5
intmain(void)
6
{
7
TCCR0B|=(1<<CS01);
8
OCR0A=127;
9
OCR0B=150;
10
11
while(!(TIFR0&(1<<TOV0)));
12
13
TIFR0|=(1<<TOV0);//Breakpoint 0
14
15
bTimer0=1;//Breakpoint 1
16
17
while(1)
18
{
19
}
20
}
Nach der Initialisierung wird gewartet bis das Overflow Flag gesetzt
ist. Die Compare-Flags werden vorher gesetzt. An Breakpoint0 sind also
alle drei Flags gesetzt.
Jetzt wird das Overflow-Flag gelöscht und an Breakpoint1 ist dieses Flag
auch gelöscht. Nur dieses. Denn entgegen der hier sehr häufig
vertretenen Meinung, bezieht sich der sbi-Befehl nur auf dieses eine
Bit.
1
TIFR0|=(1<<TOV0);
2
a2:a89asbi0x15,0;21
Siehe Datenblatt:
"Some of the Status Flags are cleared by writing a logical one to them.
Note that, unlike most other AVRs, the CBI and SBI
instructions will only operate on the specified bit, and can therefore
be used on registers containing such Status Flags. The
CBI and SBI instructions work with registers 0x00 to 0x1F only."
Das gleiche Programm auf einem Atmega644P erzeugt, zumindest an der
relevanten Stelle, den gleichen Binärcode. Und die Register verhalten
sich auch gleich. Siehe Anhänge.
Das dürften sie laut Datenblatt aber nicht:
"Some of the status flags are cleared by writing a logical one to them.
Note that the CBI and SBI instructions will operate on
all bits in the I/O register, writing a one back into any flag read as
set, thus clearing the flag. The CBI and SBI instructions
work with registers 0x00 to 0x1F only."
Demnach müssten alle drei Flags im TIFR0 gelöscht sein. Hier stimmt das
Datenblatt, auch das neueste, nicht mit der Realität überein, sondern
der Controller verhält sich wie alle anderen 'Neuen' auch.
Zu den Neuen gehören, nachdem, was ich bisher gesehen habe, alle
Controller, die mit bis zu 20MHz getaktet werden können und mit PIN
toggeln können und diese haben auch dieses CBI/SBI-Feature.
mfg.
Thomas Eckmann schrieb:> unlike most other AVRs
Die Formulierung zeigt ja deutlich, dass man da mal was nachgebessert
hat.
Andererseits ist es bei den reinen Flag-Registern völlig unnötig: dort
kostet es ja überhaupt nichts, stattdessen gleich das althergebrachte
1
TIFR0=_BV(TOV0);
zu schreiben. (Kommt noch hinzu, dass der OUT-Befehl auf alle 64
IO-Adressen angewandt werden kann, SBI jedoch nur auf die ersten 32
davon.)
Wirklich interessant wäre das Feature ja eher für die Peripherals,
bei denen sich das Interrupt-Status-Flag irgendwo in einem „normalen“
Steuerregister befindet, aber ob die sich nun gerade in einem Bereich
befinden, der auch noch für SBI zugreifbar ist? Ein typischer Kandidad
dieser Kategorie wäre ADCSRA, aber das ist schon so weit jenseits von
Gut und Böse, dass man dort sowieso nur noch mit
Speicherzugriffsbefehlen drauf operieren kann. Dafür wiederum
befindet sich darin natürlich nur genau ein Interruptflag; es
existiert also keinerlei Gefahr, ungewollt ein Flag zu löschen,
wenn man die VerODERung benutzt. Read-modify-write ist da aber in
jedem Falle langsamer als das direkte Neuschreiben des ganzen Ports.
Jörg Wunsch schrieb:> Dafür wiederum> befindet sich darin natürlich nur genau ein Interruptflag; es> existiert also keinerlei Gefahr, ungewollt ein Flag zu löschen,> wenn man die VerODERung benutzt. Read-modify-write ist da aber in> jedem Falle langsamer als das direkte Neuschreiben des ganzen Ports.
Du hast meinen Beitrag entweder nicht richtig gelesen oder nicht
verstanden. Oder ich hab das nicht komplett rübergebracht.
Es geht mir darum, dass bei den 'Neuen' ausschliesslich die Veroderung
zu verwenden ist. Statt der ständig postulierten Zuweisung, wie sie auch
im Tutorial steht:
"Hinweis: Dazu nicht übliche bitweise VerODERung nehmen, sondern eine
direkte Zuweisung machen "
Denn damit:
1
ADCSRA=(1<<ADIF);
schiesst man sich ins Knie.
Das dagegen:
1
TIFR0|=(1<<TOV0);
2
ADCSRA|=(1<<ADIF);
funktioniert immer. Sowohl, je nach Adresse, mit sbi als auch mit RMW.
Und darum kümmert sich der Compiler.
mfg.
Thomas Eckmann schrieb:> schiesst man sich ins Knie.
Inwiefern?
Thomas Eckmann schrieb:> funktioniert immer.Thomas Eckmann schrieb:> Sowohl, je nach Adresse, mit sbi als auch mit RMW.
Kannst du irgendwie belegen, dass es auch mit IN/ORI/OUT bzw.
LDS/ORI/STS funktioniert, also ohne SBI? Und mir erklären, wie das
technisch überhaupt machbar sein sollte?
Mega8 vs Mega88, gleiche Stelle, unter "I/O Memory":
ATmega8: "Some of the Status Flags are cleared by writing a logical one
to them. Note that the CBI and SBI instructions will operate on all bits
in the I/O Register, writing a one back into any flag read as set, thus
clearing the flag. The CBI and SBI instructions work with registers 0x00
to 0x1F only."
ATmega88: "Some of the Status Flags are cleared by writing a logical one
to them. Note that, unlike most other AVRs, the CBI and SBI instructions
will only operate on the specified bit, and can therefore be used on
registers containing such Status Flags. The CBI and SBI instructions
work with registers 0x00 to 0x1F only."
Atmel hat also mit dem ATmega88 das Verhalten des SBI Befehls geändert.
Die Datasheets mancher Typen sind oder waren allerdings tatsächlich
widersprüchlich, wenn sich hinten in der Register Summary aufgrund eines
"copy and paste" Fehlers noch der alte Passus findet, während es vorne
aktualisiert wurde.
Ich ziehe es daher vor, die "=" Methode zu verwenden, denn die ist
tatsächlich auf allen Typen anwendbar. Die "|=" Methode ist nur bei
jenen Typen mit der speziellen und ursprünglich nicht vorhandenen
Eigenschaft des SBI Befehls anwendbar. Und setzt voraus, dass das
Register davon adressierbar ist.
A. K. schrieb:> Ich ziehe es daher vor, die "=" Methode zu verwenden
Moment mal.
> Denn damit:> ADCSRA = (1 << ADIF);>> schiesst man sich ins Knie.
Klar,´denn damit wird auf jeden Fall das gesamte Register beschrieben,
also auch Bits die nicht auf Null gesetzt werden sollten.
Auch bei z.B. ADCSRA, das nicht mit direkten IN/OUT oder CBI/SBI
erreicht werden kann, macht der Kompiler dann ein echtes
Read-Modify-Write
aus
ADCSRA |= (1<<ADIE)
in dieser Form
Matthias Sch. schrieb:> Read-Modify-Write> aus> ADCSRA |= (1<<ADIE)
Das wäre Fatal, da dann auch alle anderen Flags gelöscht werden.
Bei den Interrupt-Flag-Register niemals verodern!
Da sie eigentlich readonly sind, und ein schreiben was anderes macht als
in normalen Registern.
Matthias Sch. schrieb:> Klar,´denn damit wird auf jeden Fall das gesamte Register beschrieben,> also auch Bits die nicht auf Null gesetzt werden sollten.
Jo, man sollte schon das reinschreiben, was da reingehört. Das hat dann
aber nichts mit dem Interrupt-Flag zu tun.
Ich nochmal zum Read-Modify-Write
Beim ADC ist es weniger problematisch da es nur ein Interupt-Flag hat:
da her kann man
ADCSRA |= (1 << ADIF);
schreiben
man kann auch
ADCSRA = ADCSRA;
schreiben, das Bit ist ja schon auf 1.
Aber was macht man bei den Timer-Registern, z.B. TIFR0?
Gut das hat NUR Interrupt-Flags.
daher geht dort ein z. B.
TIFR0 = (1 << TOV0);
Ich hab jetzt nicht alle Register das AVR im Kopf, aber wenn es mehrere
Flag-Bits hat und normale Bits, dann muss man auf jeden fall beim
verodern vorher die anderen Inerruptflags wegmaskieren.
Christian K. schrieb:> Ich hab jetzt nicht alle Register das AVR im Kopf, aber wenn es mehrere> Flag-Bits hat und normale Bits, dann muss man auf jeden fall beim> verodern vorher die anderen Inerruptflags wegmaskieren.
Beispielsweise das TWSSRA vom Tiny841.
Die Interrupt Flags muss man zwar meist nicht explizit löschen, weil das
bereits durch eine andere üblicherweise im Handler durchgeführte Aktion
geschieht, aber es sind noch andere Bits mit gleicher Löschtechnik drin.
Thomas Eckmann schrieb:> "Hinweis: Dazu nicht übliche bitweise VerODERung nehmen, sondern eine> direkte Zuweisung machen ">> Denn damit:> ADCSRA = (1 << ADIF);>> schiesst man sich ins Knie.>> Das dagegen:>> TIFR0 |= (1 << TOV0);> ADCSRA |= (1 << ADIF);>> funktioniert immer. Sowohl, je nach Adresse, mit sbi als auch mit RMW.> Und darum kümmert sich der Compiler.
Wie oben schon erwähnt funktioniert das nur wenn der der Kompiler ein
"SBI" draus macht, oder das Register nur einen Interruptflag hat.
Daher sollte man immer Registerabhäning entscheiden ob man ein "=" oder
ein "|=" macht.
Thomas Eckmann schrieb:> wird behauptet, dass Flags grundsätzlich mit z.B.>> TIFR0 = (1 << TOV0);>> zu Löschen seien, da mit Veroderung alle anderen Flags auch gelöscht> werden. Das trifft auf die neueren AVRs nicht zu.
Das mag ja sein, aber dafür funktioniert die Zuweisung immer, egal ob
alter oder neuer AVR und egal ob im SBI-Breich oder RAM-Bereich.
Daher ist es immer noch gültig, daß man diesem Weg den Vorzug geben
sollte.
Daß Atmel den SBI-Befehl nachträglich geändert hat, mag ja ganz lustig
sein, ist in meinen Augen aber völlig unnötig.
Im Gegenteil, es verleitet zu Fehlern, wenn das Bit nicht mehr im
SBI-Bereich sitzt und dann der Compiler ein RMW draus macht. Der
Compiler kann ja nicht warnen, wenn er SBI nicht benutzen kann.
In der Praxis ist das händische Löschen oft nur nötig, wenn das Bit
nicht beim Eintritt in die ISR gelöscht wird, z.B. beim TWI.
Da benutze ich dann ein Schattenregister. D.h. alle Zuweisungen auf das
TWCR erfolgen auf twcr_shadow und erst am Ende der ISR wird "TWCR =
twcr_shadow" ausgeführt.
Matthias Sch. schrieb:> Klar,´denn damit wird auf jeden Fall das gesamte Register beschrieben,> also auch Bits die nicht auf Null gesetzt werden sollten.
Bei den Registern, für die das „direkte Beschreiben“ gedacht ist,
gibt es nur Interruptflags im ganzen Register. Da das Schreiben
einer Null auf ein Interruptflag ein NOP ist, ist das nach wie vor
eine sinnvolle Variante.
Für die Register, die mehr als nur ein Interruptflag enthalten (wie
eben das genannte ADCSRA) war es hingegen noch nie eine praktikable
Variante. Allerdings ist für ADCSRA eben auch SBI keine praktikable
Variante, da es schlicht nicht per SBI erreichbar ist. (Der ATtiny841
ist damit möglicherweise der erste Controller, bei dem die Methode
überhaupt irgendwie einen praktischen Nährwert hat.
Christian K. schrieb:> Wie oben schon erwähnt funktioniert das nur wenn der der Kompiler ein> "SBI" draus macht
Was er schon beim ach so beliebten „-O0“ ja nicht mehr tut.
Also eigentlich nur für Assemblercode wirklich brauchbar.
Jörg Wunsch schrieb:> (Der ATtiny841> ist damit möglicherweise der erste Controller, bei dem die Methode> überhaupt irgendwie einen praktischen Nährwert hat.
Nein, denn das TWSSRA liegt nicht im SBI Bereich.
Allerdings liegen die Timer-IFRs bei genau jenen AVRs, die neben der SBI
Eigenschaft auch einen gegenüber den alten Modellen renovierten
Registeradressraum haben, als einzige Timer-Register im SBI/SBIS
Bereich. Beim den älteren Megas lag das TIFR noch JWD.
A. K. schrieb:> Allerdings liegen die Timer-IFRs bei genau jenen AVRs, die neben der SBI> Eigenschaft auch einen gegenüber den alten Modellen renovierten> Registeradressraum haben, als einzige Timer-Register im SBI/SBIS> Bereich.
Genau bei den Timer-IFRs braucht man das Feature aber gar nicht …
Jörg Wunsch schrieb:> Genau bei den Timer-IFRs braucht man das Feature aber gar nicht …
Doch. Wenn ohne Interrupt genutzt. Dann brauchst du beides, SBI und
SBIS.
Peter Dannegger schrieb:> Das mag ja sein, aber dafür funktioniert die Zuweisung immer, egal ob> alter oder neuer AVR und egal ob im SBI-Breich oder RAM-Bereich.> Daher ist es immer noch gültig, daß man diesem Weg den Vorzug geben> sollte.
Gewagte These.
"The TWINT Flag must be cleared by software by writing a logic one to
it. Note that this flag is not automatically cleared by hardware when
executing the interrupt routine."
1
TWCR=(1<<TWCINT);//Adr 0xBC
Also habe ich gar keine andere Chance, als mich auf die Schnauze zu
legen. Oder ich bastel mir Shadowregister oder schreib die Konfiguration
jedes Mal neu rein. Das ist zwar nicht besonders schlimm. Allerdings
muss die Funktion, die das bearbeitet, diese auch kennen.
Oder doch lieber:
1
TWCR|=(1<<TWCINT);//Adr 0xBC
Was garantiert immer funktioniert. Auch dann, wenn es mich nicht
interessiert, in welchem Bereich das Register steht. Natürlich unter der
Voraussetzung, dass die Optimierung eingeschaltet ist. Aber selbst bei
Banalfunktionen wie Delay wird selbstverständlich hingenommen, dass die
Optimierung eingeschaltet sein muss.
Alle reinen Flag-Register, auch das TIFR3 beim 1284, die mehrere Flags
enthalten, liegen im SBI-Bereich. Bei denen ist es gleich, ob Zuweisung
oder Veroderung gemacht wird. Alle Konfigurationsregister, die Flags
enthalten, haben nur ein einziges Flag. Hier darf nur durch Veroderung
gelöscht werden. Eine Ausnahme bildet UCSRnA, das drei Flags enthält,
von denen aber nur eines händisch gelöscht werden kann. Die anderen
werden nur automatisch über ISR-Eintritt oder Datenregisterstatus
gelöscht.
Wo ist die Logik, grundsätzlich die Zuweisung zu verwenden und die
Flags, die nicht in reinen Flagregistern liegen, speziell zu behandeln,
während mit der Veroderung mit immer der gleichen Methode immer das
richtige erreicht wird?
Weil das bei obsoleten Controllern einmal so war?
Es ist Zeit, die Paradigmen zu verschieben und dem alten Zeugs, wenn man
es denn unbedingt immer nich verwenden will, die Sonderbehandlung zu
verordnen. Anstatt die Neuen krampfhaft kompatibel zu halten. Und sich
damit der Möglichkeiten zu berauben, die diese in diesem Bereich bieten.
Vor PIN-Toggle wird ja auch ständig gewarnt, weil beim Atmega8 und den
Registern und SBI...
Das ist doch reiner Fundamentalismus.
Zeigt mir einen Controller, bei dem das anders ist. Wie schon oben
geschrieben, die 'Neuen', die mit PIN-Toggle. 20MHz brauchen sie gar
nicht zu können. Denn der Attiny48 gehört auch zu der Kategorie und kann
nur 12MHz.
mfg.
A. K. schrieb:> Dann brauchst du beides, SBI und SBIS.
Bei denen funktioniert doch aber auch ein stinknormales OUT, da man
ja in den TIFRx-Register nur Interruptflags hat. Schneller als OUT
ist SBI ja auch nicht, höchstens ein Befehlswort weniger.
Jörg Wunsch schrieb:> A. K. schrieb:>> Dann brauchst du beides, SBI und SBIS.>> Bei denen funktioniert doch aber auch ein stinknormales OUT, da man> ja in den TIFRx-Register nur Interruptflags hat. Schneller als OUT> ist SBI ja auch nicht, höchstens ein Befehlswort weniger.
Ich frage mich gerade, wie man sich nur immer so krampfhaft drehen und
wenden kann und den Vorteil einfach durch seine Atmega8-Brille nicht
erkennen will.
Das Timerflag löscht du also per Zuweisung. OK.
Und das TXCn im UCSRnA, falls du mal mit Polling den UART bedienst?
Ich nehme für beides die Veroderung. Und liege immer richtig. Und kann
das auch nicht verwechseln.
mfg.
A. K. schrieb:> Thomas Eckmann schrieb:>> Das ist doch reiner Fundamentalismus.>> Nö. Faulheit.
Aber in dem Sinne, dass einer seinen Rasen mit der Nagelschere
beschnippelt, weil er zu faul ist, seinen Rasenmäher aus dem Schuppen zu
holen.
mfg.
Thomas Eckmann schrieb:> Oder doch lieber:>> TWCR |= (1 << TWCINT); //Adr 0xBC
Das Problem ist nicht das TWCINT, sondern wenn ich in der
I2C-Statemaschine ein anderes Bit von TWCR setzen oder löschen will!
Mit &= bzw. |= wird TWCINT schon vorzeitig mit gelöscht.
Peter Dannegger schrieb:> Das Problem ist nicht das TWCINT, sondern wenn ich in der> I2C-Statemaschine ein anderes Bit von TWCR setzen oder löschen will!> Mit &= bzw. |= wird TWCINT schon vorzeitig mit gelöscht.
Das ist richtig. Das hat aber mit dem Verfahren zum Löschen von
Int-Flags nichts zu tun. Sondern liegt in der Eigenart deiner
I2C-Prozedur. Hier könnte man allenfalls Atmel den Vorwurf machen, dem
TWI kein exklusives Flag-Register spendiert zu haben. Genug Platz ist ja
vorhanden. Selbst im SBI-Sektor. Sozusagen die letzte Baustelle, um das
komplett zu entkrampfen.
mfg.
Thomas Eckmann schrieb:> Das Timerflag löscht du also per Zuweisung. OK.> Und das TXCn im UCSRnA, falls du mal mit Polling den UART bedienst?>> Ich nehme für beides die Veroderung. Und liege immer richtig. Und kann> das auch nicht verwechseln
Wenn du in deinen Projekten weißt, das der Compiler daraus ein SBI macht
bitte, aber das kannst du nicht voraussetzen wenn du Quellcode
veröffentlichst die andere weiter benutzen können. Denn je nach
Optimierung wird daraus eben kein SBI.
Oder das Register liegt bei einem anderem AVR nicht im SBI Bereich.
Christian K. schrieb:> Wenn du in deinem Projekten weißt, das der Compiler daraus ein SBI macht> bitte, aber das kannst du nicht voraussetzen wenn du Quellcode> veröffentlichst die andere weiter benutzen können. Denn je nach> Optimierung wird daraus eben kein SBI.
ja.
1
#ifndef __OPTIMIZE__
2
#error "Compiler optimizations disabled; functions from blablabla.* won't work as designed"
Ist das denn so schwer Kontext abhängig zu Händeln
Bei Registern mit einem Flag:
ADCSRA |= (1 << ADIF);
oder auch einfach
ADCSRA = ADCSRA;
Bei reinen Flag-Registern ein:
TIFR0 = (1 << TOV0);
und bei einem Mischmaschregister mit mehreren Flags ein:
REG &= ( RegNonFlagsMask | (1 << Flag) );
Thomas Eckmann schrieb:> Ich frage mich gerade, wie man sich nur immer so krampfhaft drehen und> wenden kann und den Vorteil einfach durch seine Atmega8-Brille nicht> erkennen will.
Warum willst du mir jetzt eine „ATmega8-Brille“ verordnen?
Kann sein, dass ich mit dem Teil auch schon mal was zu tun hatte,
aber wenn, dann genauso marginal wie auch ATmega328 oder dergleichen.
Der AVR, mit dem ich derzeit mit Abstand am meisten zu tun habe
(weil's beruflich ist), ist ein ATmega256RFR2. Der hat zwar vermutlich
das von dir besprochene Feature, aber ich sehe keinen Vorteil drin,
wenn ich vor der Benutzung des Features erst im jeweiligen Datenblatt
nachschlagen muss, ob es nun auch geht, und wenn es bei -O0 schon
nicht mehr geht. (Das benutze ich zwar sehr selten, aber wenn ich
es schon mal nutze, dann ist meine Erwartungshaltung, dass der Code
langsamer und größer wird, aber nicht plötzlich gar nicht mehr geht.)
Dafür ist das einfach zu unwichtig. Braucht man in der Praxis sowieso
viel zu selten: entweder will man die Geräte mit Interrupt benutzen,
dann muss man nur bei der Initialisierung einmalig das (ggf. noch
anhängige) Interruptflag löschen. Wenn es dann zusammen mit anderen
Bits in einem Register ist (wie beim ADC), dann schreibt man das
einfach „in einem Rutsch“ mit den übrigen Bits da hinein.
TWI ist sowieso eine andere Baustelle: dort wird das Bit ja explizit
nicht durch den Aufruf der ISR gelöscht, da das Löschen des Bits die
nächste Bus-Transaktion anwirft. In der Regel ändert man dort in
TWCR ohnehin mehr als nur das eine Bit, sodass man ebenfalls das
ganze Register neu schreiben kann. Es gibt ein oder zwei Fälle,
bei denen man durch das SBI was abkürzen könnte, aber das war's dann
auch schon.
Christian K. schrieb:> Ist das denn so schwer Kontext abhängig zu Händeln
Warum soll man das tun, wenn es unnötig ist?
Bei den Mischmach-Registern gibt es eins, das 3 Flags hat(UCSSR_A), von
denen aber nur eines händisch gelöscht werden kann.
Beim Tiny841 enthält das TWSCRA 2 Flags, die nur nacheinander auftreten
können. Die anderen Flags sind Fehler-Flags, die sowieso eine gesonderte
Behandlung erfordern. Dabei ist dann ziemlich egal, ob das Adress- oder
Data-Flag bei ohnehin korrupten Daten gelöscht wird.
Christian K. schrieb:> und bei einem Mischmaschregister mit mehreren Flags ein:> REG &= ( RegNonFlagsMask | (1 << Flag) );
Vollkommen unnötig.
Jörg Wunsch schrieb:> Warum willst du mir jetzt eine „ATmega8-Brille“ verordnen?
Weil hier ständig mit Atmega8 argumentiert wird. Erfreulich, dass du
doch eine Ausnahme darstellst.
Deine ganzen Ausführungen schön und gut. Aber ursprünglich bezog sich
mein Einwand zur Zuweisung auf das Tutorial. In dem ohne weitere
Kommentierung die Zuweisung als MUSS drinsteht. Das wird auch mit dem
Beispiel am Atmega168 belegt, für den das definitiv nicht zutrifft. Und
der Einsteiger, der die Zuweisung konsequent einsetzt, fliegt auf die
Schnauze.
Dass die 'Speicherersparnis' banal ist, ist mir auch klar.
mfg.
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Interruptflags_l.C3.B6schen
Was stört dich daran jetzt?
Zugegeben die Passage ist ziemlich knapp gehalten.
"Hinweis: Dazu nicht übliche bitweise VerODERung nehmen, sondern eine
direkte Zuweisung machen"
Natürlich sollte man bei der direkten Zuweisung die andere Bits im
Register ebenfalls neu schreiben.
Christian K. schrieb:> Was stört dich daran jetzt?
Dass man ihm widerspricht. ;-)
Die Atmel'sche Regel ist immer anwendbar, aber manchmal umständlicher.
Seine Regel ist manchmal einfacher, aber nicht immer anwendbar.
Wollte man seine Regel ins Tutorial reinschreiben, würde ein Anfänger
überfordernder Roman draus, weil zwar er Altertümer wie den Mega8
ignorieren kann, nicht aber das Tutorial.
A. K. schrieb:> Christian K. schrieb:>> Was stört dich daran jetzt?>> Dass man ihm widerspricht. ;-)
Ja, ja.
Am meisten sört mich, dass das mit einem Controller erklärt wird, bei
dem das Problem gar nicht auftreten kann.
> Die Atmel'sche Regel ist immer anwendbar, aber manchmal umständlicher.> Seine Regel ist manchmal einfacher, aber nicht immer anwendbar.
Ja, wenn die Optimierung abgeschaltet ist.
> Wollte man seine Regel ins Tutorial reinschreiben, würde ein Anfänger> überfordernder Roman draus, weil zwar er Altertümer wie den Mega8> ignorieren kann, nicht aber das Tutorial.
So, wie sie jetzt drinsteht, funktioniert sie bei konsequenter Anwendung
auch nicht immer.
mfg.
Thomas Eckmann schrieb:>> Die Atmel'sche Regel ist immer anwendbar, aber manchmal umständlicher.>> Seine Regel ist manchmal einfacher, aber nicht immer anwendbar.>> Ja, wenn die Optimierung abgeschaltet ist.
Oder bei älteren Controllern, oder bei IO-Registern, die gar nicht
mehr per SBI erreichbar sind.
Dumm eben daran ist nur, dass der Compiler dich in keinem Falle dabei
warnen kann, dass du jetzt im Begriff bist, etwas zu schreiben, was
dann nicht funktionieren wird.
Jörg Wunsch schrieb:> Oder bei älteren Controllern, oder bei IO-Registern, die gar nicht> mehr per SBI erreichbar sind.
Was reitest du ständig auf SBI rum? Der Compiler setzt das doch richtig
um, wenn es im anderen Bereich liegt.
mfg.
Thomas Eckmann schrieb:> Jörg Wunsch schrieb:>> Warum willst du mir jetzt eine „ATmega8-Brille“ verordnen?>> Weil hier ständig mit Atmega8 argumentiert wird. Erfreulich, dass du> doch eine Ausnahme darstellst.
oder Mega16, 32, 64, 128, ...
Mega16 und Mega32 sind dank DIP-Gehäuse immer noch im Bastelbereich in
Gebrauch (der 8er auch), auch wenn es dafür inzwischen auch moderne
Alternativen gibt.
Oliver
Oliver S. schrieb:> Mega16 und Mega32 sind dank DIP-Gehäuse immer noch im Bastelbereich in> Gebrauch
Ja. Aber in einer fernen Zukunft gibt es vielleicht die Atmega164..1284.
mfg.
Thomas Eckmann schrieb:> Was reitest du ständig auf SBI rum? Der Compiler setzt das doch richtig> um, wenn es im anderen Bereich liegt.
Dieser Teil deiner Aussage ist bislang nicht belegt. Er setzt zwar
TIFRx |= bit;
ggf. in
in/load
or
out/store
um, aber wieso das beim TIFR stets genau wie
sbi TIFRx, bit
funktionieren soll erschliesst sich mir nicht.
Thomas Eckmann schrieb:> Aber in einer fernen Zukunft gibt es vielleicht die Atmega164..1284.
Schau dir mal an, welche Controller bei Atmel alle als "A"-Versionen
neu aufgelegt wurden ("A" = "die shrink", also ein komplett neuer
Chip, eventuell auch in einer anderen Fab gefertigt als früher). Du
wirst dich schon wundern, dass da die "altmodischen" Dinger alle noch
mit im Boot sind: ATmega8A, ATmega16A, ATmega32A, sogar ATmega128A,
es gibt sie alle noch (*). Trotz der Alternativen ATmega88, ATmega164
und ATmega1281.
(*) Du kannst dir gewiss sein, dass sie den die shrink nicht wegen
ein paar Tausend Bastlern gemacht haben werden.
Thomas Eckmann schrieb:> Ich habe damit angefangen: |=
Welches aber eben nicht universell anwendbar ist, weil es zumindest
unter manchen Umständen eben Gefahr läuft, Bits zu löschen, die man
eher nicht löschen wollte.
A. K. schrieb:> um, aber wieso das beim TIFR genau wie> sbi TRIFT, bit> funktionieren soll erschliesst sich mir nicht.
Mir auch nicht. Aber das muss es auch nicht. Die Register mit mehreren
Flags, bei denen das relevant ist, liegen alle im SBI-Bereich.
mfg.
umwandelt ist NICHT garantiert!
Deswegen sollte man eine VerODERung in solchen Registern auch nicht
benutzen.
Und der Satz "Dazu nicht übliche bitweise VerODERung nehmen, sondern
eine direkte Zuweisung machen" sollte bei ausreichend Gehirnschmalz
implizieren das man andere Bits bei der direkte Zuweisung ebenfalls neu
setzt.
Jörg Wunsch schrieb:> Welches aber eben nicht universell anwendbar ist, weil es zumindest> unter manchen Umständen eben Gefahr läuft, Bits zu löschen, die man> eher nicht löschen wollte.
Das tut die Zuweisung aber auch.
@ Christian K. (the_kirsch)
>Bei Registern mit einem Flag:>ADCSRA |= (1 << ADIF);>oder auch einfach>ADCSRA = ADCSRA;
Nein. Dann das setzt voraus, dass das Flag gesetzt ist. Wenn man aber an
bestimmten Programmstellen nicht genau weiß, OB es gesetzt ist, kann das
im Einzelfall schief gehen. Hier also besser ein
ADCSRA |= (1 << ADIF);
>Bei reinen Flag-Registern ein:>TIFR0 = (1 << TOV0);
Jo.
>und bei einem Mischmaschregister mit mehreren Flags ein:>REG &= ( RegNonFlagsMask | (1 << Flag) );
OK.
Der Witz an der Sache ist vielmehr, dass der Compiler den Code von
a |= 1<<b
auf den neueren Prozessoren eigentlich nicht mehr mittels
sbi a, b
implementieren dürfte, eben weil das entgegen der C Definition nicht
mehr äquivalent zur Arbeitsweise der "abstract virtual machine" ist.
Formal betrachtet war diese Optimierung des Befehls also ein Schuss ins
Knie.
Ein Problem, dass GCC/AVR nun mit 8051 Compilern teilt, denn deren
implizite Unterscheidung der Portverwendung abhängig vom exakten Befehl
ist auch nicht sauber umsetzbar. Wird da |= in ORI port,mask umgesetzt,
liest der Befehl den Sollzustand, während eine Umsetzung in getrennte
Befehle den Istzustand liest.
Falk Brunner schrieb:>>Bei Registern mit einem Flag:>>ADCSRA |= (1 << ADIF);>>oder auch einfach>>ADCSRA = ADCSRA;>> Nein. Dann das setzt voraus, dass das Flag gesetzt ist. Wenn man aber an> bestimmten Programmstellen nicht genau weiß, OB es gesetzt ist, kann das> im Einzelfall schief gehen.
Aber man will doch gerade das Flag löschen.
Wenn es gesetzt ist =1 muss ich eine 1 schreiben um es zu löschen. Wenn
es nicht gesetzt ist =0, dann ist es egal ob ich eine 0 oder 1 schreibe.
Und warum sollte man das Bit mit einer 1 oder-verknüpfen wenn es eh
schon 1 ist? JA, JA, damit der Compiler eventuell ein SBI draus machen
kann.
Thomas Eckmann schrieb:> Das tut die Zuweisung aber auch.> UCCSR0A = (1 << TXC0);
Es hat ja auch nie jemand behauptet, dass in so einem Falle nur
das Interruptflag gelöscht würde. Aber es wird in diesem Falle halt
klipp und klar genau das ausgeführt, was hingeschrieben worden ist.
Wenn ich aber TIFR1 |= _BV(TOV1); schreibe, dann hängt es vom Kontext
(Optimierung oder nicht? Neuer Controller oder nicht?) ab, ob dabei
nur TOV1 gelöscht wird, oder ggf. auch alle anderen Interruptflags,
die gerade noch gesetzt waren.
Thomas Eckmann schrieb:>> Die Atmel'sche Regel ist immer anwendbar, aber manchmal umständlicher.>> Seine Regel ist manchmal einfacher, aber nicht immer anwendbar.>> Ja, wenn die Optimierung abgeschaltet ist.
Mal ehrlich, das ist ein KO-Kriterium. Wenn C-Code nur unter bestimmten
Optimierungsstufen funktioniert, dann ist er kaputt. Es ist schon
schlimm genug, daß manchmal Compilerbugs dieses Verhalten hervorrufen.
Da werde ich einen Teufel tun und C-Code schreiben, dessen korrekte
Funktion davon abhängt wie der Compiler ihn umsetzt.
Falk Brunner schrieb:> @ Christian K. (the_kirsch)>>>Bei Registern mit einem Flag:>>ADCSRA |= (1 << ADIF);>>oder auch einfach>>ADCSRA = ADCSRA;>> Nein. Dann das setzt voraus, dass das Flag gesetzt ist. Wenn man aber an> bestimmten Programmstellen nicht genau weiß, OB es gesetzt ist, kann das> im Einzelfall schief gehen.
Nein. Das Bit ist danach in jedem Fall rückgesetzt. Entweder weil es nie
gesetzt war oder weil man es gerade rückgesetzt hat.
> Hier also besser ein>> ADCSRA |= (1 << ADIF);
Daran genauso wie wie an "ADCSRA = ADCSRA" stört mich, daß es des
Register ganz unnötigerweise erst lesen muß.
Das einfachste und vor allem am leichtesten lesbarste ist IMNSHO immer
noch ADCSRA = (1 << ADIF). Denn es sagt genau aus, was man machen will:
"schreib eine 1 auf das ADIF Bit und lösche es dadurch".
Daß der Compiler dafür erst eine Konstante in das Scratch-Register laden
muß - geschenkt! Wenn die Zeit dafür schon nicht reicht, kann man gleich
Assembler nehmen und SBI benutzen.
Auch im Assembler spart SBI zeitlich nichts, SBI braucht 2 Taktzyklen,
da kann man es also auch gleich auf die unkritische Art machen:
LDI r16, (1 << ADIF) | ADC_CONF_A_FESTE_BITS
OUT ADCSRA, r16
Sind auch 2 Taktzyklen
Mit freundlichem Gruß - Martin
Axel Schwenke schrieb:> Das einfachste und vor allem am leichtesten lesbarste ist IMNSHO immer> noch ADCSRA = (1 << ADIF). Denn es sagt genau aus, was man machen will:> "schreib eine 1 auf das ADIF Bit und lösche es dadurch".
Das löscht aber alle Bits des Registers und das ist definitiv nicht
gewollt!
Christian K. schrieb:> Axel Schwenke schrieb:>> Das einfachste und vor allem am leichtesten lesbarste ist IMNSHO immer>> noch ADCSRA = (1 << ADIF). Denn es sagt genau aus, was man machen will:>> "schreib eine 1 auf das ADIF Bit und lösche es dadurch".>> Das löscht aber alle Bits des Registers und das ist definitiv nicht> gewollt!
Hmm. Ich sehe gerade, daß dieses Beispiel (das ich von meinen Vorpostern
übernommen habe), tatsächlich ungeeignet ist. Denn in der Tat ist ADCSRA
eines der Register, die normale Konfigurationsbits und ein Interrupt-
Flag gemischt enthalten. Für solche Register muß man zum Rücksetzen des
Flags Read-Modify-Write machen. Bzw. ganz spezifisch für ADCSRA geht
sogar ohne Modify [1], weil es nur genau ein Interrupt-Flag gibt.
Mein Argument bezog sich auf Statusregister, die ausschließlich
Interrupt-Flags enthalten. Bei denen das Schreiben einer 0 auf eine
Bitposition also ein NOP ist.
[1] mit Modify ist aber besser lesbar. Wenn man den Code nicht gerade
gestern geschrieben hat, wird man sich sonst selber an den Kopf fassen
und fragen "Warum lese ich da das Register und schreibe es gleich
zurück? Ist das nicht ein NOP?"
Axel Schwenke schrieb:> "Warum lese ich da das Register und schreibe es gleich zurück? Ist das> nicht ein NOP?"
Dafür hat der Gott der Programmierer ja den Kommentar erfunden. ;-)
Es ist oftmals so, daß erst sehr spät eingeführte Änderungen vom
Programmierer zugunsten der Portabilität und Lesbarkeit überhaupt nicht
mehr verwendet werden.
Z.B. habe ich noch nie das Togglen über das Pin-Bit benutzt, sondern nur
die klassische C-Schreibweise.