Forum: Mikrocontroller und Digitale Elektronik AVR-Register als Bitfields


von Frank M. (ukw) (Moderator) Benutzerseite


Angehängte Dateien:

Lesenswert?

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:
1
struct PORTB_bits
2
{
3
    uint8_t portb0:1;
4
    uint8_t portb1:1;
5
    uint8_t portb2:1;
6
    uint8_t portb3:1;
7
    uint8_t portb4:1;
8
    uint8_t portb5:1;
9
    uint8_t portb6:1;
10
    uint8_t portb7:1;
11
} __attribute__((__packed__));
12
13
#define BF_PORTB                 (*(volatile struct PORTB_bits *)&PORTB)
14
#define BFM_PORTB0               BF_PORTB.portb0
15
#define BFM_PORTB1               BF_PORTB.portb1
16
#define BFM_PORTB2               BF_PORTB.portb2
17
#define BFM_PORTB3               BF_PORTB.portb3
18
#define BFM_PORTB4               BF_PORTB.portb4
19
#define BFM_PORTB5               BF_PORTB.portb5
20
#define BFM_PORTB6               BF_PORTB.portb6
21
#define BFM_PORTB7               BF_PORTB.portb7

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
int main ()
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
int main ()
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
int main ()
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:
1
struct TCCR0A_bits
2
{
3
    uint8_t wgm00:1;
4
    uint8_t wgm01:1;
5
    uint8_t dummy2:1;
6
    uint8_t dummy3:1;
7
    uint8_t com0b0:1;
8
    uint8_t com0b1:1;
9
    uint8_t com0a0:1;
10
    uint8_t com0a1:1;
11
} __attribute__((__packed__));
12
13
#define BF_TCCR0A                (*(volatile struct TCCR0A_bits *)&TCCR0A)
14
#define BFM_WGM00                BF_TCCR0A.wgm00
15
#define BFM_WGM01                BF_TCCR0A.wgm01
16
#define BFM_COM0B0               BF_TCCR0A.com0b0
17
#define BFM_COM0B1               BF_TCCR0A.com0b1
18
#define BFM_COM0A0               BF_TCCR0A.com0a0
19
#define BFM_COM0A1               BF_TCCR0A.com0a1

Sondern so:
1
struct TCCR0A_bits
2
{
3
    uint8_t wgm0:2;    // WGM00 und WGM01
4
    uint8_t dummy2:1;
5
    uint8_t dummy3:1;
6
    uint8_t com0b:2;    // COM0B0 und COM0B1
7
    uint8_t com0a:2;    // COM0A0 und COM0A1
8
} __attribute__((__packed__));
9
10
#define BF_TCCR0A                (*(volatile struct TCCR0A_bits *)&TCCR0A)
11
#define BFM_WGM0                 BF_TCCR0A.wgm0
12
#define BFM_COM0B                BF_TCCR0A.com0b0
13
#define BFM_COM0A                BF_TCCR0A.com0a0

Dann könnte man statt
1
  TCCR0A |= (1 << COM0A1) | (1 << COM0A0);

einfach schreiben:
1
  BFM_COM0A = 0b11;              // Set OC0A on Compare Match

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

: Verschoben durch Moderator
von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

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
1
PORTB |= BITB3[1] | BitB5[1] | | BitB7[1];

von Patrick B. (p51d)


Lesenswert?

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.

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


Lesenswert?

Du wirst es kaum glauben: sowas gibt es schon, und zwar bei den
ATmega*RF*:
1
/* Timer/Counter0 Interrupt Flag Register */
2
#define TIFR0                           _SFR_IO8(0x15)
3
4
#if !(defined(__ASSEMBLER__) || defined(__NOSTRUCT__))
5
6
struct __reg_TIFR0 {
7
        unsigned int tov0 : 1;  /* Timer/Counter0 Overflow Flag */
8
        unsigned int ocf0a : 1; /* Timer/Counter0 Output Compare A Match Flag */
9
        unsigned int ocf0b : 1; /* Timer/Counter0 Output Compare B Match Flag */
10
        unsigned int : 5;
11
};
12
13
#define TIFR0_struct _SFR_IO8_STRUCT(0x15, struct __reg_TIFR0)
14
15
#endif /* __ASSEMBLER__ */
16
17
  /* TIFR0 */
18
19
#define TOV0                            0
20
#define OCF0A                           1
21
#define OCF0B                           2

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.

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


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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

: Bearbeitet durch Moderator
von Pit S. (pitschu)


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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

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


Lesenswert?

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
1
PMIC.CTRL = PMIC_HILVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_LOLVLEN_bm;

versehentlich zu schreiben:
1
PORTH.DIRSET = PMIC_HILVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_LOLVLEN_bm;

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.

: Bearbeitet durch Moderator
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

Bei den ATmega und ATtiny reichen doch zwei simple Makros:
1
// Macros to read and write single I/O pins
2
#define writeBit(port,bit,value) { if ((value)>0) (port) |= (1<<bit); else (port) &= ~(1<<bit); } 
3
#define readBit(port,bit) (((port) >> (bit)) & 1)
Anwendungsbeispiel:
1
writeBit(PORTB,5,0);
2
writeBit(PORTB,5,1);
3
if (readBit(PINB,5)) { tuwas; };
4
int taster=readBit(PINB,5);
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

von Stefan F. (Gast)


Lesenswert?

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

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


Lesenswert?

Stefan us schrieb:
> Bei den ATmega und ATtiny reichen doch zwei simple Makros:

Du hast das Problem wohl noch gar nicht verstanden, fürchte ich.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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?

: Bearbeitet durch Moderator
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Falk B. (falk)


Lesenswert?

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

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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! :-)

von Stefan F. (Gast)


Lesenswert?

Ich habe tatsächlich nicht verstanden, worauf der TO hinaus wollte. Für 
SFR sind meine Markos nutzlos - logo.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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
struct TCCR0A_bits
2
{
3
    uint8_t wgm00:1;
4
    uint8_t wgm01:1;
5
    uint8_t :1;
6
    uint8_t :1;
7
    uint8_t com0b0:1;
8
    uint8_t com0b1:1;
9
    uint8_t com0a0:1;
10
    uint8_t com0a1:1;
11
} __attribute__((__packed__));
12
13
struct TCCR0A_grouped_bits
14
{
15
    uint8_t wgm0:2;     // WGM00 und WGM01
16
    uint8_t :2;
17
    uint8_t com0b:2;    // COM0B0 und COM0B1
18
    uint8_t com0a:2;    // COM0A0 und COM0A1
19
} __attribute__((__packed__));
20
21
union TCCR0A_union
22
{
23
   struct TCCR0A_bits           b;
24
   struct TCCR0A_grouped_bits   g;
25
   uint8_t                      v;
26
};
27
28
#define BF_TCCR0A               (*(volatile union TCCR0A_union *)&TCCR0A)
29
30
#define BFM_WGM0                BF_TCCR0A.g.wgm0
31
#define BFM_WGM00               BF_TCCR0A.b.wgm00
32
#define BFM_WGM01               BF_TCCR0A.b.wgm01
33
34
#define BFM_COM0B               BF_TCCR0A.g.com0b
35
#define BFM_COM0B0              BF_TCCR0A.b.com0b0
36
#define BFM_COM0B1              BF_TCCR0A.b.com0b1
37
38
#define BFM_COM0A               BF_TCCR0A.g.com0a
39
#define BFM_COM0A0              BF_TCCR0A.b.com0a0
40
#define BFM_COM0A1              BF_TCCR0A.b.com0a1

Dann kann ich schreiben:
1
int main ()
2
{
3
    BFM_COM0A0 = 1;        // wie gehabt: setze Bit COM0A0
4
    BFM_COM0A1 = 1;        // wie gehabt: setze Bit COM0A1
5
 
6
    BFM_COM0A = 0b01;      // neu: Setze "Bitgruppe" COM0A (COM0A0+COM0A1)
7
}

Was macht der Compiler daraus:
1
    BFM_COM0A0 = 1;
2
  46:  84 b5         in  r24, 0x24  ; 36
3
  48:  80 64         ori  r24, 0x40  ; 64
4
  4a:  84 bd         out  0x24, r24  ; 36

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

: Bearbeitet durch Moderator
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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

: Bearbeitet durch Moderator
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Aus Symmetriegründen noch das BF_SET()-Makro:

#define BF_SET(rbf, b)          rbf = (b);

Damit ist

  BF_SET(COM0A, 0b01);

gleichbedeutend mit:

  BF_COM0A = 0b01;

: Bearbeitet durch Moderator
von Falk B. (falk)


Lesenswert?

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

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Frank M. schrieb:
>
> Auszug aus der neuen bf_atmega88a.h:
>
>
1
> struct TCCR0A_bits
2
> {
3
>     uint8_t wgm00:1;
4
>     uint8_t wgm01:1;
5
>     uint8_t :1;
6
>     uint8_t :1;
7
>     uint8_t com0b0:1;
8
>     uint8_t com0b1:1;
9
>     uint8_t com0a0:1;
10
>     uint8_t com0a1:1;
11
> } __attribute__((__packed__));
12
>

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.

Beitrag #5032946 wurde von einem Moderator gelöscht.
Beitrag #5032948 wurde von einem Moderator gelöscht.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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

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


Lesenswert?

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.

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


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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

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


Lesenswert?

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.

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.