Hallo Leute,
ich hab mal ne Frage. Ich habe gesehen, dass Registerzugriffe als
volatile deklariert sind, also werden diese nicht vom avr-gcc optimiert,
oder?
Also wird das mehr Befehle brauchen
1
PORTD|=(1<<PD4);
2
PORTD&=~(1<<PD5);
, als eine andere Version, bei der nur einmal auf den Port zugegriffen
wird.
Ich habs mal so versucht
1
PORTD|=(1<<PD4)&~(1<<PD5);
, es scheint aber so nicht zu funktionieren...
Ich steh wohl ganz schön auf den Schlauch...
Wie müsste der Befehl mit nur einem Portzugriff aussehen?
Danke im Vorraus
N.G.
N. G. schrieb:> Ich habs mal so versuchtPORTD |= (1 << PD4) & ~(1 << PD5);, es scheint> aber so nicht zu funktionieren...
Wie sollte es auch. Mit "|" ein Bit zu löschen, geht nun mal nicht.
N. G. schrieb:> Also wird das mehr Befehle brauchen> , als eine andere Version, bei der nur einmal auf den Port zugegriffen> wird.
Geht es um das Tempo oder um gleichzeitige Veränderung der Pins?
N. G. schrieb:> Also wird das mehr Befehle brauchen> PORTD |= (1 << PD4);> PORTD &= ~(1 << PD5);> , als eine andere Version, bei der nur einmal auf den Port zugegriffen> wird.
Das wird nur 2 Befehle brauchen, also schneller als alle anderen
Varianten, Zeit und Codegröße sind hier also nicht das Problem, jedoch
werden bei dieser Variante die beiden Portpins nicht mehr zur selben
Zeit geschaltet werden, PD5 wird hier einen Taktzyklus später geschaltet
als PD4.
Eberhard F. schrieb:> Zwei Portzugriffe bleiben es aber trotzdem
Ein PORTD |= x; sind auch schon zwei Portzugriffe. Das ist nur die
Kurzform für:
PORTD = PORTD | x;
Bernd K. schrieb:> Das wird nur 2 Befehle brauchen
Zumindest solange es sich um einen AVR handelt und der Port im passenden
Bereich liegt.
> also schneller als alle anderen Varianten
Nicht auf den Standard-AVRs. Die Einzelbitbefehle brauchen 2 Takte, die
4 Ersatzbefehle (IN,2xLogik,OUT) jeweils einen.
Max H. schrieb:> Ein PORTD |= x; sind auch schon zwei Portzugriffe.
Sofern der Port im Adressbereich von 0 - 0x1F liegt, ist das ein
Zugriff, weil der Compiler hier ein 'SBI' einsetzt, bzw. zum Löschen ein
'CBI'. Erst wenn mehrere Bits gleichzeitig bearbeitet werden, wird ein
Read-Modify-Write benutzt.
mfg.
Max H. schrieb:> Ein PORTD |= x; sind auch schon zwei Portzugriffe. Das ist nur die> Kurzform für:> PORTD = PORTD | x;
Kleiner Spass am Rande. So ist
PIND |= 1<<PD2;
zwar per C Definition identisch mit
PIND = PIND | 1<<PD2;
und übersetzt sich bei AVRs i.d.R. in
SBI PIND, PD2
nur ist das nicht identisch mit
IN R16, PIND
OR R16, 1<<D2
OUT PIND, R16
da die SBI Version nur PD2 toggelt, die OUT Version aber zusätzlich
alle Pins, die als 1 gelesen werden. Streng genommen dürften C Compiler
seit Erfindung der Schreibfunktion der PINx Ports die CBI/SBI Befehle
nicht mehr verwenden. Faktisch ist das Ergebnis solcher Operationen auf
PINx nun unspezifiziert und der Programmierer hat die ehrenvolle
Aufgabe, selber drauf zu achten, ob der Compiler die "richtige"
Befehlssequenz erzeugt.
Insoweit ist die Vorstellung, dass es sich in CBI/SBI um getrennte Lese-
und Schreibzugriffe in einem Befehl handelt, mit Vorsicht zu geniessen.
Erst recht bei den Xmegas und den nur "reduced core" Typen, bei denen
diese Befehle nur noch einen Takt benötigen. Man fährt besser, wenn man
dies als einen einzelnen Portzugriff mit spezieller Funktion betrachtet.
A. K. schrieb:> Streng genommen dürften C Compiler> seit Erfindung der Schreibfunktion der PINx Ports die CBI/SBI Befehle> nicht mehr verwenden.
Konsequent und streng genommen einzig logische Maßnahme (hardwareseitig)
wäre gewesen wenn alle bits in den PINX Registern immer 0 lesen würden
sobald das zugehörige Bit im DDRX auf eins steht.
@Bernd K. (prof7bit)
>Konsequent und streng genommen einzig logische Maßnahme (hardwareseitig)>wäre gewesen wenn alle bits in den PINX Registern immer 0 lesen würden>sobald das zugehörige Bit im DDRX auf eins steht.
Nö. Diese Register bilden einfach den Pegel des Pins ab, auch wenn es
als Ausgang geschaltet ist. Das ist vollkommen OK und logisch.
Falk Brunner schrieb:> Nö. Diese Register bilden einfach den Pegel des Pins ab, auch wenn es> als Ausgang geschaltet ist. Das ist vollkommen OK und logisch.
Nö. Nicht wenn sie eine komplett andere Funktion bekommen sobald der Pin
als Ausgang geschaltet ist. Siehe hierzu bitte das Posting von A.K. und
die darin beschriebene Problematik auf die ich mich bezog.
Bernd K. schrieb:> Nö. Nicht wenn sie eine komplett andere Funktion bekommen sobald der Pin> als Ausgang geschaltet ist. Siehe hierzu bitte das Posting von A.K. und> die darin beschriebene Problematik auf die ich mich bezog.
Nö. Siehe hierzu das entsprechende Datenblatt.
mfg.
@ Bernd K. (prof7bit)
>> Nö. Diese Register bilden einfach den Pegel des Pins ab, auch wenn es>> als Ausgang geschaltet ist. Das ist vollkommen OK und logisch.>Nö. Nicht wenn sie eine komplett andere Funktion bekommen sobald der Pin>als Ausgang geschaltet ist. Siehe hierzu bitte das Posting von A.K. und>die darin beschriebene Problematik auf die ich mich bezog.
Keine Sekunde. Eine Anweisung wie
>PIND = PIND | 1<<PD2;
Ist auf dem AVR so oder so Unsinn bzw. falsch, egal ob so oder in der
Kurzform.
PIND |= (1<<PD1);
Nur eine Anweisung ala
PIND = (1<<PD1);
ist sinnvoll, und da ist es egal, ob der Compiler einen SBI oder
Read-Modify-Write erzeugt (Wobei, das gab es auch ein kleines
Problemchen, weil SBI/CBI auch intern einen atomaren Read-Modify-Write
Zugriff macht).
Das ursprüngliche Problem des OP ist sowieso ein anderes.
Falk Brunner schrieb:> Nur eine Anweisung ala> PIND = (1<<PD1);> ist sinnvoll, und da ist es egal, ob der Compiler einen SBI oder> Read-Modify-Write erzeugt
Weder SBI noch R-M-W ist da sinnvoll. Da sollte ein OUT/STS stehen.
A. K. schrieb:> Weder SBI noch R-M-W ist da sinnvoll. Da sollte ein OUT/STS stehen.
"14.2.2 Toggling the Pin
Writing a logic one to PINxn toggles the value of PORTxn, independent on
the value of DDRxn. Note that the SBI instruction can be used to toggle
one single bit in a port."
mfg.
Thomas Eckmann schrieb:> "14.2.2 Toggling the Pin
Weiss ich doch, schrieb ich doch. Aber soll der Compiler wirklich die
eingesetzten Befehle davon abhängig machen müssen, ob da
PINA = 1; // SBI möglich
oder
PORTA = 1; // SBI nicht möglich
steht? Also PINx Portadressen mit __attribute__((sbi)) taggen, um diese
Nischenoptimierung durchführen zu können?
@ A. K. (prx)
>> Nur eine Anweisung ala>> PIND = (1<<PD1);>> ist sinnvoll, und da ist es egal, ob der Compiler einen SBI oder>> Read-Modify-Write erzeugt>Weder SBI noch R-M-W ist da sinnvoll. Da sollte ein OUT/STS stehen.
Ja, stimmt, da war ich wohl ein wenig verwirrt. 8-0
Ich meinte, es ist egal ob dort ein sbi oder out steht.
Aber die Ursache liegt hier, da flackerte eine Erinnerung auf.
In ATmega88 Datenblatt
"Do not use Read-Modify-Write instructions (SBI and CBI) to set or clear
the MPCMn bit. The MPCMn bit shares the same I/O location as the TXCn
Flag and this might accidentally be cleared when using SBI or CBI
instructions."
D.h. SBI/CBI machen atomar in Hardware ein Read-Modify-Write. Diese
Warnung findet man bei diversen anderen Registern ebenfalls wieder!
Damit ist deine Aussage auch FALSCH!
"da die SBI Version nur PD2 toggelt, die OUT Version aber zusätzlich
alle Pins, die als 1 gelesen werden."
Beide Versionen sind falsch!
Beitrag "Re: Kleine Bitmanipulationsfrage"
Nur ein
PIND = (1<<PD1);
->
LDI r16, 1<<PD1
OUT PIND, R16
ist korrekt. Es darf KEINESFALLS ein
SBI PIND, PD1
erzeugt werden!
A. K. schrieb:> Weiss ich doch, schrieb ich doch. Aber soll der Compiler wirklich die> eingesetzten Befehle davon abhängig machen müssen, ob da> PINA = 1; // SBI möglich> oder> PORTA = 1; // SBI nicht möglich> steht? Also PINx Portadressen mit __attribute__((sbi)) taggen, um diese> Nischenoptimierung durchführen zu können?
Das macht er auch nicht:
1
PINC=1;
2
4d6:81e0ldir24,0x01;1
3
4d8:86b9out0x06,r24;6
4
PORTC=1;
5
4da:88b9out0x08,r24;8
Sondern mit '|=' benutzt er den 'sbi':
1
PINC|=1;
2
4d6:309asbi0x06,0;6
3
PORTC|=1;
4
4d8:409asbi0x08,0;8
Der Programmierer muss allerdings wissen, dass man sich mit
1
PINC|=3;
ins Knie schiesst. Denn der Compiler ist nicht sein Kindermädchen.
mfg.
Falk Brunner schrieb:> D.h. SBI/CBI machen atomar in Hardware ein Read-Modify-Write. Diese> Warnung findet man bei diversen anderen Registern ebenfalls wieder!
Ist nicht der Punkt. Bei den PINx Registern fällt man mit dieser
Vorstellung auf die Nase, weil die eben nicht so arbeiten, wie R-M-W
vorgibt. Siehe dort. Für diesen Unfug kann ich nichts, das hat Atmel so
verbockt, wohl an Assembler-Fans denkend.
> Damit ist deine Aussage auch FALSCH!
Meine Aussage bezog sich ausschliesslich auf die PINx Register, nicht
auf die CBI/SBI Befehle im Allgemeinen.
Daraus ergibt sich insgesamt, dass sich die CBI/SBI Befehle
normalerweise wie eine atomare R-M-W Befehlsfolge verhalten, ausser es
handelt sich um PINx Portregister.
Thomas Eckmann schrieb:> Der Programmierer muss allerdings wissen, dass man sich mit> PINC |= 3;> ins Knie schiesst.
Um sich besonders elegant ins Knie zu schiessen, kann man auch in
zentraler Konfiguration
#define XXX_PORT_IN PINA
#define XXX_MYPIN PA2
schreiben, um später mit
XXXX_PORT_IN |= 1<<XXX_MYPIN;
darauf zurückzugreifen.
Wenn man dann aufgrund Kapazitätsengpass oder Gemecker vom Layouter auf
den Mega1280 mit
#define XXX_PORT_IN PINH
#define XXX_STATUSPIN PH1
umsteigt, dann ist die Kniescheibe futsch. Denn der geht nicht mit SBI,
weshalb die volle R-M-W Version mit ihren Nebenwirkungen rauskommt.
Code wie
PINx |= ...;
sollte also besser generell als unspezifiziert betrachtet werden. Dann
entfällt zwar der kurze SBI, aber man ist auf der sicheren Seite.
@ A. K. (prx)
>Daraus ergibt sich insgesamt, dass sich die CBI/SBI Befehle>normalerweise wie eine atomare R-M-W Befehlsfolge verhalten, ausser es>handelt sich um PINx Portregister.
Hab ich soeben an realer Hardware getestet. Stimmt. 8-0
Das nenn ich mal konsequent inkonsequent!
N. G. schrieb:> Ich habe gesehen, dass Registerzugriffe als volatile deklariert> sind, also werden diese nicht vom avr-gcc optimiert, oder?> Also wird das mehr Befehle brauchen
1
PORTD|=(1<<PD4);
2
PORTD&=~(1<<PD5);
> als eine andere Version, bei der nur einmal auf den Port> zugegriffen wird.> Ich habs mal so versucht
1
PORTD|=(1<<PD4)&~(1<<PD5);
Die Frage lässt sich leider nicht zufriedenstellend beantworten.
Version 1 besteht aus 2 Anweisungen und 4 Zugriffen, Version 2 besteht
aus einer Anweisung und 2 Zugriffen. GCC wird die eine Variante nie in
die andere umwandeln oder "optimieren", weil das nicht volatile-korrekt
wäre. Bis dahin herrscht also Klarheit.
Was man hingegen nicht sagen kann, ist, welche Befehlssequenzen genau
erzeugt werden, was insbesondere im Hinblick auf Atomarität wichtig
ist.
Verkompliziert wird die Lage dadurch, daß der AVR-spezifische Teil von
GCC ein PORTD |= als SBI ausgeben kann. Ungeachtet der Frage, ob diese
Transformation nun volatile-korrekt ist oder nicht, erwartet der
Anwender hier üblicherweise ein SBI falls möglich, und das ist im Ende
auch die Rechtfertigung für diese Transformaton.
Blöderweise kann man nicht dafür garantieren, d.h. das Ergebnist ist
sowohl abhängig von Optimierungseinstellungen als auch vom Kontext. Bei
den Xmega, wo die Header i.d.R. eine Lawine von volatile Bitfields und
Unions und tralala lostreten, geht GCC z.B. gerne davon aus, es sei
günstiger, die Adresse in ein Register zu laden und indirekt
zuzugreifen:
1
PORTX|=1<<1;
2
PORTX|=1<<2;
dann also nicht als
1
sbi PORTX, 1
2
sbi PORTX, 2
übersetzt sonder als
1
ldi r30, lo8(PORTX)
2
ldi r31, hi8(PORTX)
3
ld r18, z
4
ori r18, 2
5
st z, r18
6
ld r18, z
7
ori r18, 4
8
st z, r18
Abgesehen von den Blähungen sind die Operationen auf den Ports nicht
mehr atomar, so daß eine ISR, die ebenfalls auf PORTX operiert, den
Kürzeren zieht falls sie zwischen LD / ORI bzw. ORI / ST ausführt.
Die einzige, zufriedenstellende Lösung, die avr-gcc anbieten könnte,
wären neue Built-in Funktionen, die atomare Operation garantieren. Vor
einiger Zeit hatte ich mal in Erwägung gezogen, solche zu
implementieren, bin aber wieder davon abgekommen.... Keine Ahnung ob
sowas verwendet werden würde oder ob es nur toter Code im avr-gcc wäre.
Konkret ein Built-in für Load-Modify-Store, der den Inhalt einer 8-Bit
Speicherstelle folgendermaßen verändert: Für jedes Bit in MASK, das auf
1 ist, wird das entsprechende Bit von *ADDR auf das entsprechende Bit
von VAL gesetzt. Für jedes Bit in MASK, das 0 ist, bleibt der Wert von
*ADDR unverändert.
In C ließe sich solch ein Built-in — hier ldmst genannt — in etwa wie
folgt darstellen:
Im Compiler hat man natürlich bessere Möglichkeiten das zu optimieren,
z.B. kann man sich in den Fällen 4 und 5 den Shift sparen wenn man Bit 0
von VAL verwendet.
Früher gab es auch mal die Makros sbi() und cbi(); wahrscheinlich
geistern die immer noch irgendwo rum. Verwendet werden die aber wohl
nicht mehr...
> es scheint aber so nicht zu funktionieren...> [...] Wie müsste der Befehl mit nur einem Portzugriff aussehen?
Das gibt es keinen, weil ein Bit gesetzt und eins gelöscht wird.
>> es scheint aber so nicht zu funktionieren...>> [...] Wie müsste der Befehl mit nur einem Portzugriff aussehen?>>Das gibt es keinen, weil ein Bit gesetzt und eins gelöscht wird.
Danke, das war zwar nicht die Antwort, die ich mir erhofft hatte... Aber
danke
Auch danke an alle anderen, die Erläuterungen habe ich mit Interesse
verfolgt
N.G. schrieb:>>> es scheint aber so nicht zu funktionieren...>>> [...] Wie müsste der Befehl mit nur einem Portzugriff aussehen?>>>>Das gibt es keinen, weil ein Bit gesetzt und eins gelöscht wird.>> Danke, das war zwar nicht die Antwort, die ich mir erhofft hatte...
Falls es wirklich eine Rolle spielten sollte nur genau einen
Portzugriff zu veranstalten, dann kann man Buchführung betreiben und
merken, welcher Wert gerade im Register steht — vorausgesetzt es ist ein
reines Konfigurations- oder Ausgaberegister das nicht von der Hardware
verändert wird.
Aber die Anwort hattest du wohl auch nicht erhofft, weil das weder
kürzer noch schneller ist als direkt auf dem Port zu operieren.
Johann L. schrieb:>> [...] Wie müsste der Befehl mit nur einem Portzugriff aussehen?>> Das gibt es keinen, weil ein Bit gesetzt und eins gelöscht wird.
Weshalb es bei manchen µCs I/O-Ports gibt, die mehrere Pins gleichzeitig
auf beliebige Werte setzen können, ohne die übrigen Pins zu ändern. Ein
Weg dazu ist ein Maskenregister, mit dem die zu setzenden Pins
ausgewählt werden. Ein anderer Weg nutzt deshalb Ports halber Wortbreite
um beim Schreiben in voller Wortbreite gleichermassen setzen und löschen
zu können.
@ A. K. (prx)
>Weshalb es bei manchen µCs I/O-Ports gibt, die mehrere Pins gleichzeitig>auf beliebige Werte setzen können, ohne die übrigen Pins zu ändern.
Irgendwann ist es auch mal wieder gut. Der AVR ist schon recht flott im
Bit Banging, da kann man alle möglichen Dinge mit machen. Wenn es
wirklich alles gleichzeitig und hochflexibel sein muss, nimmt man halt
einen CPLD/FPGA.
Andere Prozessoren haben da deutlich mehr Probleme.
Die Code-/Laufzeiteinsparung beim Togglen mit PINx ist so lächerlich
gering, daß ich das zugunsten der Lesbarkeit noch nie benutzt habe und
auch nie benutzen werde.
Peter Dannegger schrieb:> Die Code-/Laufzeiteinsparung beim Togglen mit PINx ist so lächerlich> gering, daß ich das zugunsten der Lesbarkeit noch nie benutzt habe und> auch nie benutzen werde.
Meist nicht wichtig, kann es dort interessant werden, wo mehrere Pins
betroffen sind und die atomare Eigenschaft dieser Wackelei wichtig ist.