Angeregt durch PEDAs Tipp in
Beitrag "Re: Einzelne Bits mit WinAvr"
und den Thread
Beitrag "Re: PWM-Pin von AVR deaktivieren"
wäre es vorteilhaft, wenn man generell Bits in sämtlichen AVR-Registern
durch einfache Zuweisungen setzen bzw. löschen könnte.
Dadurch könnte man in C nicht nur einiges kürzer (und weniger
fehlerbehaftet) formulieren, sondern den Compiler auch checken lassen,
ob ein zu setzendes Bit überhaupt im verwendeten Register vorhanden ist,
wie zum Beispiel COM2A0 in TCCR0A.
Ein Setzen eines Bits durch:
PORTB |= (1<<PORTB3);
könnte man dann einfacher schreiben mit:
BF_PORTB.portb3 = 1;
bzw. noch kürzer:
BFM_PORTB3 = 1;
Das Prefix "BF_" stände dann für Bitfield, das Prefix "BFM_" für
Bitfield-Member.
Ein Löschen von Bits wäre analog.
Bisher:
PORTB &= ~(1<<PORTB3);
Neu:
BF_PORTB.portb3 = 0;
Kürzer:
BFM_PORTB3 = 0;
Oder Togglen:
PORTB ^= (1<<PORTB3);
Neu:
BF_PORTB.portb3 ^= 1;
Kürzer:
BFM_PORTB3 ^= 1;
Da von ATMEL entsprechende XML-Beschreibungsdateien namens ATxxxx.xml
bereitgestellt werden, welche Register uhd Registerbits für den
jeweiligen µC vorgesehen sind, könnte man die nötigen Definitionen in
einer µC-spezifischen C-Header-Datei sammeln.
In dieser könnte dann zum Beispiel (auszugsweise) stehen:
Ich habe nun ein kleines PHP-Programm geschrieben, welches die
XML-Dateien von ATMEL interpretiert und daraus eine Header-Datei
erzeugt, z.B. bf_atmega88a.h. Das Programm ist noch als Prototyp zu
sehen. Ich plane zuküünftig einen schrittweisen Ausbau, siehe unten.
Die erzeugte H-Datei habe ich mal an diesen Beitrag angehängt.
Wenn ein typisches LED-Blink-Programm bisher so aussah:
1
#include<stdint.h>
2
#include<avr/io.h>
3
#include<util/delay.h>
4
5
intmain()
6
{
7
// init:
8
DDRB|=(1<<DDB3);// bisher: B3 auf Ausgang
9
10
while(1)
11
{
12
PORTB|=(1<<PB3);// bisher: B3 auf High
13
_delay_ms(500);
14
PORTB&=~(1<<PB3);// bisher: B3 auf Low
15
_delay_ms(500);
16
}
17
}
kann man nun auch alternativ dazu schreiben:
1
#include<stdint.h>
2
#include<avr/io.h>
3
#include<util/delay.h>
4
5
#include"bf_atmega88a.h"
6
7
intmain()
8
{
9
// init:
10
BF_DDRB.ddb3=1;// neu: B3 auf Ausgang
11
12
while(1)
13
{
14
BF_PORTB.portb3=1;// neu: B3 auf High
15
_delay_ms(500);
16
BF_PORTB.portb3=0;// neu: B3 auf Low
17
_delay_ms(500);
18
}
19
}
Oder kürzer:
1
#include<stdint.h>
2
#include<avr/io.h>
3
#include<util/delay.h>
4
5
#include"bf_atmega88a.h"
6
7
intmain()
8
{
9
// init:
10
BFM_DDB3=1;// kürzer: B3 auf Ausgang
11
12
while(1)
13
{
14
BFM_PORTB3=1;// kürzer: B3 auf High
15
_delay_ms(500);
16
BFM_PORTB3=0;// kürzer: B3 auf Low
17
_delay_ms(500);
18
}
19
}
Möchte man dieses Prinzip nicht nur auf PORT-Register, sondern auch auf
jedes andere Register im AVR anwenden, so stößt man eventuell auf
Grenzen - nämlich dann, wenn mehrere Bits auf einmal gesetzt werden
müssen. Ein Beispiel wäre COM0A (COM0A0 + COM0A1) in TCCR0A.
Ein bisheriges Statement
TCCR0A |= (1 << COM0A1) | (1 << COM0A0);
müsste man nun so schreiben:
BFM_COM0A0 = 1;
BFM_COM0A1 = 1;
Das ist aber nicht richtig, da diese beiden Bits nicht zeitgleich
gesetzt werden.
Daher plane ich, das (im Moment noch im Prototypen-Status befindliche)
PHP-Programm derart zu ändern, dass zusammengehörende Bits erkannt und
dann auch als ein Bitfield-Member definiert werden.
Also NICHT so:
Dies (nämlich 0b11) würde exakt der ATMEL-Dokumentation entsprechen:
COM0A1 COM0A0 Description
0 0 Normal port operation, OC0A disconnected.
0 1 Toggle OC0A on Compare Match
1 0 Clear OC0A on Compare Match
1 1 Set OC0A on Compare Match
Die für diese nötige Zusammenfassung von Bitfeld-Membern kann man
durchaus über die XML-Datei, welche den Prozessor beschreibt,
herstellen. Ich arbeite dran...
Ein anderes Thema, was mir bei AVRs immer wieder aufstößt, ist das
Prozessor-unabhängige konfigurieren von PWMs. Die dafür notwendige
Information, auf welchem Portbit zum Beispiel OC0A steckt, findet man
nämlich nicht. Bei jedem Port auf einen anderen AVR muss man dann andere
Portpins auf Ausgang setzen. Auch dieses Problem kann man durch
automatisches Erstellen von Bezeichnern in einer Header-Datei
erschlagen. Doch dieses Thema ist auch schon ein größeres und sollte
erst dann angegangen werden, wenn es ein anwendbares Ergebnis für die
Bitfields von Registern gibt.
Daher sehe ich für dieses Software-Projekt erstmal folgende Prioritäten:
- Zusammenfassung von zusammenhängenden Bits zu einem größeren
Bitfeld, z.B. für COM0A
- Definition von Preprocessor-Konstanten, um z.B. COM0A nicht durch
0b01, sondern durch sprechende Namen zu initialisieren
- Erstellen eines bf_reg.h, welches je nach µC das richtige bf_atxxxx.h
"includiert".
- Erstellung eines Makefiles bzw. Scripts, was sämtliche verfügbare
XML-Dateien durch das PHP-Programm jagt
Wer möchte etwas zur Diskussion beitragen bzw. vielleicht sogar am
Projekt mitarbeiten?
Viele Grüße,
Frank
Super zusammengefasst!
Mir ist grad noch ein (Schnaps?) Idee gekommem für eine mögliche
alternative Schreibweise:
man hängt hinter Bit-Symbole ein statisches Array mit zwei Elelemten,
null und 2^Bit-Wert. Dann könnte man über den Index 0 und 1 darauf
zugreifen, die Schreibweise könnte u.U. so aussehen:
1
TCCR0A=COM0A1[0]|COM0A0[1];
2
TCCR0A=COM0A1[0]+COM0A0[1];// oder und plus ist egal
ist zum Setzen / Löschen von einzelnen Bits nicht so elegant wie
1
BFM_PORTB3=1;
dafür erlaubt es gleichzeitiges Setzen von Bits in einer Instruktion
Mal so aus reiner Neugierde: Habt ihr schon mit Atmel gesprochen?
Eventuell haben die das ja schon, oder möchten dies aufgreifen. Ich kann
mir es als äusserst mühsam vorstellen, wenn plötzlich im Internet
Beispielcode herumgeistert, der nicht mit den offizielen
Compiler-Packeten übereinstimmt (mal ganz vom Support abgesehen).
Oder die haben Unterlagen, mit denen man sowas leichter machen kann
(also nicht nur die Bitfelder erzeugen, sondern gleich auch die
Bezeichnungen der Einstellungen aus den Datenblätter übernehmen).
Nur als Beispiel die Firma Microchip: Die haben früher keine eigenen
Compiler gehabt. Es gab zwar offizielle "Vertreter" mit eigenen
Compilern, aber seit dem Wechsel auf MPLAB X haben sie diese Vertreter
aufgekauft und bieten nun zur IDE auch gleich das komplette
Compilerpaket an.
Zudem haben die den Wechsel von einzelnen Bits auf Bitfeldern und den
entsprechenden Defines auch gleich gemacht und vereinheitlicht.
Wir haben uns damit aber seinerzeit innerhalb von Atmel nicht so
recht durchsetzen können, daher ist das auf diese paar Controller
beschränkt, für die wir hier in Dresden das Generieren der
Headerdateien selbst in der Hand hatten.
Im Prinzip hätte ich auch nichts dagegen, das für die komplette
avr-libc nachzuziehen, wenn entsprechend Bedarf ist, aber:
. Es müsste sich jemand die Arbeit machen und alle Headers nach
diesem Schema neu aufsetzen, und
. idealerweise sollten die Atmel-Ingenieure, die dort für das Einpflegen
neuer Controller zuständig sind, das mittragen
Für letzteres wäre es natürlich hilfreich, die Headers per XSLT direkt
aus dem Atmel-Studio-XML zu generieren und die entsprechende
XSL-Transformation auch gleich mit in der avr-libc abzulegen. Dann
können diese Leute das auch benutzen.
Bei Punkt 1 ist der kritische Teil sicherzustellen, dass nichts, was
in den bisherigen Headers drin ist, dabei unter den Tisch fällt. Das
betrifft insbesondere rückwärtskompatible Namen für Dinge, die mal
im Datenblatt irgendwann anders benannt waren, aber dann im aktuellen
XML/Datenblatt nicht mehr zu finden sind.
Bliebe insgesamt natürlich noch das Problem, dass alle
nicht-GCC-Compiler sowas dann (zumindest vorerst) nicht haben. Damit
leidet bei Code, der das benutzt, die Portabilität.
Auf der Positivseite bleibt, dass die Zuweisung einzelner Bits damit
registersicher wird, ich kann also nicht mehr versehentlich versuchen,
die Bits eines falschen Registers zu modifizieren.
Jörg Wunsch schrieb:> Bei Punkt 1 ist der kritische Teil sicherzustellen, dass nichts, was in> den bisherigen Headers drin ist, dabei unter den Tisch fällt. Das> betrifft insbesondere rückwärtskompatible Namen für Dinge, die mal im> Datenblatt irgendwann anders benannt waren, aber dann im aktuellen> XML/Datenblatt nicht mehr zu finden sind.
Eine Möglichkeit dafür wäre es, dass man für bereits existierende
Dateien nicht den ganzen Header selbst generiert, sondern nur die
jeweiligen struct-Definitionen, und diese dann anschließend in die
existierenden Dateien per Script hinein editieren lässt. Auf diese
Weise ließe sich technisch sicherstellen, dass nichts verloren gehen
kann.
Jörg Wunsch schrieb:> Du wirst es kaum glauben: sowas gibt es schon, und zwar bei den> ATmega*RF*:
Autsch, wusste ich nicht. :-)
> . Es müsste sich jemand die Arbeit machen und alle Headers nach> diesem Schema neu aufsetzen, und
Das traue ich mir zu.
> . idealerweise sollten die Atmel-Ingenieure, die dort für das Einpflegen> neuer Controller zuständig sind, das mittragen
Da habe ich keine dienlichen Connections.
> Bei Punkt 1 ist der kritische Teil sicherzustellen, dass nichts, was> in den bisherigen Headers drin ist, dabei unter den Tisch fällt. Das> betrifft insbesondere rückwärtskompatible Namen für Dinge, die mal> im Datenblatt irgendwann anders benannt waren, aber dann im aktuellen> XML/Datenblatt nicht mehr zu finden sind.
Die Altlasten, die mit "rüber" sollen, sind natürlich blöd.
Statt die alten Header zu bearbeiten, wäre es da nicht einfacher, man
erzeugt per XSLT einen ganz neuen Header, welchen den alten einfach per
#include reinzieht und dann die Neuerungen dazu definiert?
> Bliebe insgesamt natürlich noch das Problem, dass alle> nicht-GCC-Compiler sowas dann (zumindest vorerst) nicht haben. Damit> leidet bei Code, der das benutzt, die Portabilität.
Das stimmt zwar, halte ich aber für vernachlässigbar.
> Auf der Positivseite bleibt, dass die Zuweisung einzelner Bits damit> registersicher wird, ich kann also nicht mehr versehentlich versuchen,> die Bits eines falschen Registers zu modifizieren.
Ja, eben! Das wäre ein großer Gewinn.
Jörg Wunsch schrieb:> Eine Möglichkeit dafür wäre es, dass man für bereits existierende> Dateien nicht den ganzen Header selbst generiert, sondern nur die> jeweiligen struct-Definitionen, und diese dann anschließend in die> existierenden Dateien per Script hinein editieren lässt. Auf diese> Weise ließe sich technisch sicherstellen, dass nichts verloren gehen> kann.
Lass das doch den Compiler (sprich Preprocessor) machen, siehe oben :-)
Bei den Xmegas ist das auch schon immer so gewesen. So wird in den
uralten winavr includes (z.B. iox128a1.h) mit _gm, _gp für group mask
und group position sowie _bm, _bp für bit mask und bit position
gearbeitet. Bis auf Bit-Ebene hat man das nicht gemacht, aber sowas hier
// reduce power by disbling unused modules
PR.PRGEN = PR_AES_bm | PR_RTC_bm | PR_EBI_bm;
PMIC.CTRL = PMIC_HILVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_LOLVLEN_bm;
PORTQ.DIRSET = PIN2_bm; // on board STAT lamp
PORTH.DIRSET = PIN3_bm | PIN5_bm | PIN7_bm;
ist ohne eigene defines möglich.
Ich finde diese Darstellung deutlich besser als bei den AVRs und würde
mich über eine Variante für Tiny und Mega sehr freuen.
Peter Schulten schrieb:> Bei den Xmegas ist das auch schon immer so gewesen. [...]> PORTH.DIRSET = PIN3_bm | PIN5_bm | PIN7_bm;
Vielen Dank für den hilfreichen Hinweis. Hinter XXX_bm versteckt sich
nicht ein Bit, sondern eine Maske. Also wird vermutlich PIN3_BM einfach
als
#define PIN3_bm (1<<3) // 0x08
definiert sein. Wenn man das jetzt noch "registersicher" formulieren
kann, dann wäre das Problem, dass man bei Bitfields "disjunkte Bits"
nicht gleichzeitig setzen kann, hier auch gelöst.
> Ich finde diese Darstellung deutlich besser als bei den AVRs und würde> mich über eine Variante für Tiny und Mega sehr freuen.
Prima, danke für den Zuspruch :-)
Peter Schulten schrieb:> Bei den Xmegas ist das auch schon immer so gewesen.
Jein. Was man dort gemacht hat, ist die Definitionen für die
Bit*werte* (statt Bit*nummer*) bereits vorab mitzuliefern.
Aber registersicher ist das damit trotzdem nicht. Es hindert dich
nämlich keiner dran, statt
Das zu verhindern, ist aber das Ziel der Ideen, um die es in diesem
Thread geht.
Frank M. schrieb:> Statt die alten Header zu bearbeiten, wäre es da nicht einfacher, man> erzeugt per XSLT einen ganz neuen Header, welchen den alten einfach per> #include reinzieht und dann die Neuerungen dazu definiert?
Auf den ersten Blick ja, aber es sieht sche***e aus, und der
Pflegeaufwand in der avr-libc wird dadurch unnötig größer.
Ein zweiter Weg zu dem von mir oben genannten wäre es, aller Header
erstmal per XSLT automatisch zu erzeugen. Danach geht man jeden
einzeln per "avr-gcc -dM -E" durch und schaut nach, ob alle Makros,
die mit den alten Headern existiert haben, auch in den neuen
vorhanden sind. Die paar Fälle, bei denen das nicht der Fall ist
(werden kaum mehr als ein Dutzend sein) pflegt man dann mit der Hand
nach.
Michael Reinelt schrieb:> man hängt hinter Bit-Symbole ein statisches Array mit zwei Elelemten,> null und 2^Bit-Wert. Dann könnte man über den Index 0 und 1 darauf> zugreifen, die Schreibweise könnte u.U. so aussehen:> [...]
Hm. Vielleicht so ähnlich, muss ich mal drüber nachdenken. Ich suche für
das gleichzeitige Setzen von mehreren Bits immer noch eine
"registersichere" Lösung.
Jörg Wunsch schrieb:> Auf den ersten Blick ja, aber es sieht sche***e aus, und der> Pflegeaufwand in der avr-libc wird dadurch unnötig größer.
Okay. Den Pflegaufwand in der avr-libc kannst Du besser beurteilen.
> Ein zweiter Weg zu dem von mir oben genannten wäre es, aller Header> erstmal per XSLT automatisch zu erzeugen. Danach geht man jeden> einzeln per "avr-gcc -dM -E" durch und schaut nach, ob alle Makros,> die mit den alten Headern existiert haben, auch in den neuen> vorhanden sind.
Ja, das könnte funktionieren. Alternativ bliebe noch das
scriptgesteuerte Editieren der bisherigen Header. Auch das ist machbar
und ist nicht über Maßen schwierig.
Auf den ersten Blick scheinen die Makros uneffizient zu sein, aber der
Compiler optimiert das so gut, dass der erzeugte Assembler-Code genau so
kurz ist, wie wenn ich es "zu fuss" geschrieben hätte:
1
PORTB=PORTB & ~32;
2
PORTB=PORTB | 32;
3
if (PINB & 32) { tuwas; };
4
int taster=(PINB >> 5) & 1; // oder taster=(PINB & 32) >> 5
> Ich finde diese Darstellung deutlich besser als bei den AVRs> und würde mich über eine Variante für Tiny und Mega sehr freuen.
Ich glaube das geht nicht, weil die zusammengehörigen Register der Ports
bei ATmega und ATtiny nicht immer in der gleichen Reihenfolge und
gleichem Abstand aufeinander folgen.
Stefan us schrieb:> Bei den ATmega und ATtiny reichen doch zwei simple Makros:
Du hast den Sinn des Ganzen nicht verstanden. Es geht um
Registersicherheit. Lies dazu nochmal die Postings oben.
> Auf den ersten Blick scheinen die Makros uneffizient zu sein, aber der> Compiler optimiert das so gut, dass der erzeugte Assembler-Code genau so> kurz ist, wie wenn ich es "zu fuss" geschrieben hätte:
Stimmt, gerade getestet mit avr-gcc 4.7.2.
BFM_PORTB5 = 1; // BFM-Version
BFM_PORTB5 = 0;
writeBit(PORTB,5,0); // Deine Version
writeBit(PORTB,5,1);
Alle 4 Zeilen werden gleich übersetzt, nämlich mit:
sbi 0x05, 5
bzw.
cbi 0x05, 5
Aber siehst Du den feinen Unterschied im C-Source? Ich muss gar nicht
angeben, in welchen Port ich schreibe. Die bezeichneten Bits sind
automatisch den richtigen Registern zugeordnet. Das mag sich für PORTB
trivial anhören, aber bei TCCR0A & Co ist es ein wesentlicher Vorteil.
Es gibt nämlich durchaus Bits, die je nach AVR-µC in dem einen oder dem
anderen Register stecken, d.h. sie sind "gewandert". Das brauche ich
dann aber gar nicht mehr zu berücksichtigen bzw. beim Port auf einen
anderen µC zu beachten! Außerdem schützt mich das System durch
fälschliche Verwendung von Bits in falschen Registern. Das Ganze
bedeutet die Register-Sicherheit.
Außerdem: Die BFM-Version ist kürzer ;-)
Deine Variante:
1
> int taster=(PINB >> 5) & 1; // oder taster=(PINB & 32) >> 5
Meine Variante:
1
int taster = BFM_PINB5;
Überzeugt Dich vielleicht diese Einfachheit und Sicherheit?
Stefan us schrieb:> Ich glaube das geht nicht, weil die zusammengehörigen Register der Ports> bei ATmega und ATtiny nicht immer in der gleichen Reihenfolge und> gleichem Abstand aufeinander folgen.
Auch das hast Du leider nicht verstanden.
Hast Du Dich vielleicht schon mal gefragt, warum es für jeden AVR-µC in
Deiner avr-gcc-Toolchain ein eigenes Include ioxxxx.h gibt?
Nein? Die Lösung ist eigentlich ganz einfach:
Wenn Du als Programmierer <avr/io.h> "includierst", dann zieht sich
diese generische Include-Datei je nach µC-Typ das dazu passende
ioxxxx.h. Dann stimmen die Bits und Register-Positionen auch.
Diese ioxxxx.h sind irgendwann mal automatisch erzeugt worden, nämlich
aus den XML-Beschreibungsdateien des Herstellers.
Und genau da kann man ansetzen: Man erzeugt neue ioxxxx.h mit
Ergänzungen um die µC-spezifischen BF- und BFM-Definitionen. Dann
braucht sich der Programmierer gar nicht so sehr drum kümmern, für
welchen AVR er gerade seine Software erstellt bzw. compiliert.
Diese Übersetzung der ATmega88A.xml von ATMEL habe ich mal exemplarisch
für den ATmega88A durchgeführt und oben ans erste Posting gepinnt.
Ist es jetzt klarer?
P.S.
Du findest die XML-Dateien im Verzeichnis AVR
Tools\Partdescriptionfiles.
@ Frank M. (ukw) Benutzerseite
>Hm. Vielleicht so ähnlich, muss ich mal drüber nachdenken. Ich suche für>das gleichzeitige Setzen von mehreren Bits immer noch eine>"registersichere" Lösung.
Struct und union für mehrere Register?
Wahrscheinlich geht es aber nicht ohne Macrotricks.
Falk Brunner schrieb:> Struct und union für mehrere Register?
Ja, genau sowas in der Art schwebt mir auch vor - vielleicht auch
verschiedene Structs für ein und dasselbe Register ("verschiedene
Views") oder Unions für die Gruppierung von Bits innerhalb der
Register...
Aber:
> Wahrscheinlich geht es aber nicht ohne Macrotricks.
Wahrscheinlich wird es eher eine Macro-Orgie! :-)
Ich habe einen weiteren Fortschritt für Bitfield-Members größer als 1
Bit zu vermelden.
Ich exerziere das mal exemplarisch für TCCR0A und deren Bits WG00+WG01,
COM0A0+COM0A1, COM0B0+COM0B1 durch.
Auszug aus der neuen bf_atmega88a.h:
1
structTCCR0A_bits
2
{
3
uint8_twgm00:1;
4
uint8_twgm01:1;
5
uint8_t:1;
6
uint8_t:1;
7
uint8_tcom0b0:1;
8
uint8_tcom0b1:1;
9
uint8_tcom0a0:1;
10
uint8_tcom0a1:1;
11
}__attribute__((__packed__));
12
13
structTCCR0A_grouped_bits
14
{
15
uint8_twgm0:2;// WGM00 und WGM01
16
uint8_t:2;
17
uint8_tcom0b:2;// COM0B0 und COM0B1
18
uint8_tcom0a:2;// COM0A0 und COM0A1
19
}__attribute__((__packed__));
20
21
unionTCCR0A_union
22
{
23
structTCCR0A_bitsb;
24
structTCCR0A_grouped_bitsg;
25
uint8_tv;
26
};
27
28
#define BF_TCCR0A (*(volatile union TCCR0A_union *)&TCCR0A)
Wie gehabt: Register TCCR0A holen, Bit setzen, Register speichern
1
BFM_COM0A1 = 1;
2
4c: 84 b5 in r24, 0x24 ; 36
3
4e: 80 68 ori r24, 0x80 ; 128
4
50: 84 bd out 0x24, r24 ; 36
Wie gehabt: Register TCCR0A holen, Bit setzen, Register speichern
1
BFM_COM0A = 0b01;
2
52: 84 b5 in r24, 0x24 ; 36
3
54: 8f 73 andi r24, 0x3F ; 63
4
56: 80 64 ori r24, 0x40 ; 64
5
58: 84 bd out 0x24, r24 ; 36
NEU:
- Register TCCR0A holen
- COM0A0 zurücksetzen!
- COM0A1 setzen
- Register speichern
Damit kann man nun sowohl einzelne Bits als auch zusammengehörende Bits
als Gruppe in einem Rutsch manipulieren.
Jetzt wird vielleicht der eine oder andere denken: "Setzen ist ja ganz
schön, kann ich vielleicht auch innerhalb einer Bitgruppe mehrere Bits
in einem Rutsch setzen, ohne die anderen zurückzusetzen? Also ein
"echtes" OR registersicher durchführen?"
Ja, das geht - und jetzt kommen wir zur heute nachmittag schon
angedrohten Preprocessor-Orgie :-)
Ich erweitere bf_atmega88a.h um folgendes:
1
#define B_COM0A_R TCCR0A
2
#define B_COM0A_U union TCCR0A_union
3
#define B_COM0A_M g.com0a
4
5
#define B_COM0A0_R TCCR0A
6
#define B_COM0A0_U union TCCR0A_union
7
#define B_COM0A0_M b.com0a0
8
9
#define B_COM0A1_R TCCR0A
10
#define B_COM0A1_U union TCCR0A_union
11
#define B_COM0A1_M b.com0a1
12
13
#define BF_OR(rbf, b) do { rbf##_U _x; _x.v = 0; _x.rbf##_M = (b); rbf##_R |= _x.v; } while(0)
Dann kann ich in main() schreiben:
1
BF_OR(B_COM0A,0b01);
Assembler-Output:
1
BF_OR(B_COM0A, 0b01);
2
5a: 84 b5 in r24, 0x24 ; 36
3
5c: 80 64 ori r24, 0x40 ; 64
4
5e: 84 bd out 0x24, r24 ; 36
- Register TCCR0A holen
- COM0A1 setzen und COM0A0 nicht zurücksetzen!
- Register speichern
Hier wird also nichts mehr zurückgesetzt.
Achja, das Macro funktioniert natürlich auch mit einzelnen Bits, also
zum Beispiel:
1
BF_OR(B_COM0A0,1);
Dieses ist im Resultat absolut identisch mit
1
BFM_COM0A0=1;
@Falk: gefällt Dir das so? Ist Dir das "tricky" genug? ;-)
Ein BF_AND()-Makro zum Löschen von Bits innerhalb einer Bitgruppe (oder
eines einzelnen Bits) folgt morgen.
Gruß,
Frank
Nachtrag - jetzt kommt der Wermutstropfen:
Warum setzt der Compiler jetzt nicht mehr per SBI direkt das Bit im SFR,
sondern lädt es in ein Zwischenregister? Da muss ich wohl nochmal ein
wenig forschen...
Nachtrag 2:
Ein
TCCR0A |= (1<<COM0A0)
wird identisch übersetzt, also:
1
TCCR0A |= (1<<COM0A0);
2
4a: 84 b5 in r24, 0x24 ; 36
3
4c: 80 64 ori r24, 0x40 ; 64
4
4e: 84 bd out 0x24, r24 ; 36
Also doch kein Wermutstropfen. SFRs werden wohl anders gehandhabt als
PORT-Register. Na, dann ist die Welt ja wieder in Ordnung :-)
Ich habe noch etwas gefeilt, mir gefiel nicht, dass ich für das
BF_OR()-Makro eine neu eingeführte Konstante B_COM0A brauchte statt dem
bisher verwendeten BFM_COM0A, um einen Konflikt mit dem bereits
eingeführten BFM_COM0A zu vermeiden.
Aber wenn man überlegt, wie der Preprocessor tickt bei Concatenations
mittels ##, dann löst sich der Konflikt in Luft auf.
Daher folgende Änderung:
1
#define BFM_COM0A_R TCCR0A
2
#define BFM_COM0A_U union TCCR0A_union
3
#define BFM_COM0A_M g.com0a
4
5
#define BFM_COM0A0_R TCCR0A
6
#define BFM_COM0A0_U union TCCR0A_union
7
#define BFM_COM0A0_M b.com0a0
8
9
#define BFM_COM0A1_R TCCR0A
10
#define BFM_COM0A1_U union TCCR0A_union
11
#define BFM_COM0A1_M b.com0a1
Und damit sieht das alles jetzt einheitlicher aus:
1
BFM_COM0A0=1;
2
BFM_COM0A1=1;
3
BFM_COM0A=0b01;
4
5
BF_OR(BFM_COM0A,0b01);
6
BF_OR(BFM_COM0A0,1);
7
BF_OR(BFM_COM0A1,1);
Und nun zusätzlich zum OR auch noch das versprochene AND-Makro - vor
Mitternacht:
1
#define BF_OR(rbf, b) do { rbf##_U _x; _x.v = 0; _x.rbf##_M = (b); rbf##_R |= _x.v; } while(0)
2
#define BF_AND(rbf, b) do { rbf##_U _x; _x.v = 0xFF; _x.rbf##_M = (b); rbf##_R &= _x.v; } while(0)
Damit lässt sich nun schreiben:
BF_AND(BFM_COM0A, ~0b11);
Und das ist vom Assembler-Ergebnis absolut identisch mit dem dagegen
schlechter lesbarem Pendant:
TCCR0A &= ~((1<<COM0A0)|(1<<COM0A1));
Assembler-Output hier:
1
TCCR0A &= ~((1<<COM0A0)|(1<<COM0A1));
2
6c: 84 b5 in r24, 0x24 ; 36
3
6e: 8f 73 andi r24, 0x3F ; 63
4
70: 84 bd out 0x24, r24 ; 36
5
BF_AND(BFM_COM0A, ~0b11);
6
72: 84 b5 in r24, 0x24 ; 36
7
74: 8f 73 andi r24, 0x3F ; 63
8
76: 84 bd out 0x24, r24 ; 36
Ergebnis: Kein Unterschied zur "klassischen" Version... aber
registersicher.
Nachtrag:
Manch einer mag sich denken: "Warum schreibt der Kerl nicht einfach:
BFM_COM0A &= ~0b11;
Antwort: Ja, geht auch. Aber man handelt sich nicht nur eine
(behandelbare) Warning ein, sondern der Code ist ineffizienter:
1
BFM_COM0A &= ~0b11;
2
78: 84 b5 in r24, 0x24 ; 36
3
7a: 84 b5 in r24, 0x24 ; 36
4
7c: 8f 73 andi r24, 0x3F ; 63
5
7e: 84 bd out 0x24, r24 ; 36
Der in-Befehl wird nämlich überflüssigerweise 2x durchgeführt.
Bug/Feature im AVR-GCC?
Das gleiche mit dem |= Operator:
1
BFM_COM0A |= 0b11;
2
5a: 84 b5 in r24, 0x24 ; 36
3
5c: 84 b5 in r24, 0x24 ; 36
4
5e: 80 6c ori r24, 0xC0 ; 192
5
60: 84 bd out 0x24, r24 ; 36
Da ist das BFM_OR()-Makro, welches mit nur 3 Assember-Kommandos
auskommt, doch besser:
1
BF_OR(BFM_COM0A, 0b11);
2
66: 84 b5 in r24, 0x24 ; 36
3
68: 80 6c ori r24, 0xC0 ; 192
4
6a: 84 bd out 0x24, r24 ; 36
Vielleicht kann Jörg oder Johann dazu was sagen...
@ Frank M. (ukw) Benutzerseite
>@Falk: gefällt Dir das so? Ist Dir das "tricky" genug? ;-)
Super! Wobei man darüber nachdenken sollte, anstatt nackter Zahlen dort
gleich mit Symbolen zu arbeiten. Z.B.
BFM_COM0A = OCxy_SET;
BFM_COM0A = OCxy_CLEAR;
BFM_COM0A = OCxy_TOGGLE;
BFM_COM0A = OCxy_OFF;
Ich bin gerade auf diesen Beitrag aufmerksam gemacht worden mit der
Frage, ob man das so machen sollte?
Meine Standardantwort ohne ein Nachdenken ist: never ever use bit-fields
for µC register access. Zur Sicherheit habe ich auch nochmal im
C/C++-Standard nachgesehen: die Anordnung der Bits ist (immer noch)
implementierungs-abhängig. Damit scheidet das obige eigentlich aus.
Es sei denn man wechselt nie den Compiler ...
Dieser Hinweis fehlt m.E. hier im Thread.
Wilhelm M. schrieb:> die Anordnung der Bits ist (immer noch) implementierungs-abhängig.
Ja, und? Innerhalb einer Implementierung ist sie jedoch definiert,
und ein Compilerhersteller tut natürlich gut daran, über die
Lebenszeit des Compilers nicht mal hü!, mal hott! zu machen.
Die von dir zitierten Headerfiles sind ohnehin nur für den AVR-GCC
gedacht und geeignet, damit ist alles in Butter. Im Prinzip gehören
diese Dateien in die avr-libc, die als Standardbibliothek für den
AVR-GCC konzipiert ist, allerdings finden die Dateien für neue
Controller (wie hier) den Weg von Atmicrochip in das öffentliche
avr-libc-Projekt oft ein wenig zögerlich.
Jörg W. schrieb:> Wilhelm M. schrieb:>> die Anordnung der Bits ist (immer noch) implementierungs-abhängig.>> Ja, und? Innerhalb einer Implementierung ist sie jedoch definiert,> und ein Compilerhersteller tut natürlich gut daran, über die> Lebenszeit des Compilers nicht mal hü!, mal hott! zu machen.>> Die von dir zitierten Headerfiles sind ohnehin nur für den AVR-GCC> gedacht und geeignet, damit ist alles in Butter. Im Prinzip gehören> diese Dateien in die avr-libc, die als Standardbibliothek für den> AVR-GCC konzipiert ist, allerdings finden die Dateien für neue> Controller (wie hier) den Weg von Atmicrochip in das öffentliche> avr-libc-Projekt oft ein wenig zögerlich.
Wurde aber schon mal beim GCC m.W. gewechselt ...
Wilhelm M. schrieb:> Wurde aber schon mal beim GCC m.W. gewechselt ...
Sie ist nicht pauschal „beim GCC“ definiert, sondern pro Architektur.
Die Endianess des AVR-GCC hingegen ist laut SVN-Log seit dem Import
der Architektur in Revision 31935 am 11. Februar 2000 komplett auf
“little endian”, d.h. sowohl BITS_BIG_ENDIAN, BYTES_BIG_ENDIAN als
auch WORDS_BIG_ENDIAN stehen seitdem auf „0“.
Ob andere Architekturen im GCC an dieser Stelle jemals was geändert
haben (ich bezweifle es, ehrlich gesagt), ist daher für den vorliegenden
Fall völlig irrelevant.
Bitte betrachte die avr-libc (zu der dieses Headerfile logisch gehört)
einfach mal als „zur Implementierung zugehörig“; daher sind solche
Implementierungsdetails an dieser Stelle sowohl zulässig als auch
wünschenswert.
Jörg W. schrieb:> Im Prinzip gehören diese Dateien in die avr-libc
Ich korrigiere mich (der Thread war ja schon paar Jahre alt, nicht
mehr alles hatte ich richtig im Kopf), die hier gepostete Idee ist
erstmal nicht direkt Bestandteil der avr-libc. Sie hätte es allerdings
als Erweiterung werden können, wenn sich jemand die Mühe dafür machen
möchte.
Allerdings bleibt, dass die Idee sowieso ausschließlich auf den AVR-GCC
bezogen war, damit auch die bereits genannte Feststellung, dass für
diesen die Bitanordnung wohl definiert ist und niemand absehbar ist,
der ein Interesse daran haben könnte, das nach 17 Jahren noch zu
ändern.
Jörg W. schrieb:> Wilhelm M. schrieb:>> Wurde aber schon mal beim GCC m.W. gewechselt ...>> Sie ist nicht pauschal „beim GCC“ definiert, sondern pro Architektur.>> Die Endianess des AVR-GCC hingegen ist laut SVN-Log seit dem Import> der Architektur in Revision 31935 am 11. Februar 2000 komplett auf> “little endian”, d.h. sowohl BITS_BIG_ENDIAN, BYTES_BIG_ENDIAN als> auch WORDS_BIG_ENDIAN stehen seitdem auf „0“.
Ok, ich hatte auch mal von einer Option bzw. #pragma gelesen, womit man
die Anordnung umdrehen kann. Die scheint es aber nicht (mehr) zu geben
bzw. nur als inoffiziellem Patch gegeben zu haben.
Im AVR-ABI
https://gcc.gnu.org/wiki/avr-gcc#ABI
ist es leider auch nicht beschrieben, wie die Komponenten angeordnet
werden (obgleich die natürliche Annahme zutrifft, dass es in der
Deklarationsreihenfolge geschieht).
Wie ist es eigentlich beim clang-avr backend?
Gibt es dafür ein Test Macro wie _BYTE_ORDER_ ?
Wilhelm M. schrieb:> obgleich die natürliche Annahme zutrifft, dass es in der> Deklarationsreihenfolge geschieht
Das ist sowieso zwingend vom Standard vorgeschrieben. Es ist nur
nicht vorgeschrieben, ob das erste definierte Bit in einem Byte nun
0x80 oder 0x01 ist.
> Wie ist es eigentlich beim clang-avr backend?
Weiß ich nicht, habe ich noch nicht benutzt.
Da Clang aber GCC-kompatibel sein möchte (und dafür sogar den Makro
_GNUC_ intern setzt), wären die Autoren natürlich schlecht beraten,
wenn sie sich dort im Vergleich zum GCC für etwas anderes entscheiden
würden.