Forum: Mikrocontroller und Digitale Elektronik AVR Interrupt Flag löschen. Fundamentalismus und Fehler im DB.


von Thomas E. (thomase)


Angehängte Dateien:

Lesenswert?

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
volatile unsigned char bTimer0 = 0;
4
5
int main(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:  a8 9a         sbi  0x15, 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.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Thomas E. (thomase)


Lesenswert?

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.

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


Lesenswert?

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.

: Bearbeitet durch User
von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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
1
   lds R16,ADCSRA
2
   ori R16,(1<<ADIE)
3
   sts ADCSRA,R16

von Christian K. (the_kirsch)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Christian K. (the_kirsch)


Lesenswert?

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.

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


Lesenswert?

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.

von Christian K. (the_kirsch)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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 …

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Thomas E. (thomase)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

Jörg Wunsch schrieb:
> höchstens ein Befehlswort weniger.

Und ein Register weniger.

von (prx) A. K. (prx)


Lesenswert?

Thomas Eckmann schrieb:
> Das ist doch reiner Fundamentalismus.

Nö. Faulheit.

von Thomas E. (thomase)


Lesenswert?

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.

von Thomas E. (thomase)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Thomas E. (thomase)


Lesenswert?

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.

: Bearbeitet durch User
von Christian K. (the_kirsch)


Lesenswert?

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.

: Bearbeitet durch User
von Thomas E. (thomase)


Lesenswert?

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"
3
#endif

mfg.

: Bearbeitet durch User
von Christian K. (the_kirsch)


Lesenswert?

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) );

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Thomas E. (thomase)


Lesenswert?

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.

von Christian K. (the_kirsch)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Thomas E. (thomase)


Lesenswert?

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.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Thomas E. (thomase)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Thomas Eckmann schrieb:
> Was reitest du ständig auf SBI rum?

Du hast doch damit angefangen …

von Oliver S. (oliverso)


Lesenswert?

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

von Thomas E. (thomase)


Lesenswert?

Jörg Wunsch schrieb:
> Du hast doch damit angefangen …

Ich habe damit angefangen: |=

mfg.

von Thomas E. (thomase)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Thomas E. (thomase)


Lesenswert?

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.

von Christian K. (the_kirsch)


Lesenswert?

Und nochmal, das der Compiler ein
1
TIFR0 |= (1 << TOV0);
in ein
1
SBI TIFR0, (1<<TOV0)
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.

von Thomas E. (thomase)


Lesenswert?

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.
1
UCCSR0A = (1 << TXC0);

mfg.

von Falk B. (falk)


Lesenswert?

@ 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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Christian K. (the_kirsch)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Axel S. (a-za-z0-9)


Lesenswert?

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.

von Axel S. (a-za-z0-9)


Lesenswert?

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.

von Martin S. (led_martin)


Lesenswert?

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

von Christian K. (the_kirsch)


Lesenswert?

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!

von Peter D. (peda)


Lesenswert?

Im polling Mode kann man aber auch das ADSC testen und das ADIF kann 
einem egal sein.

von Axel S. (a-za-z0-9)


Lesenswert?

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?"

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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. ;-)

von Peter D. (peda)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.