Bitmanipulation

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Bitoperatoren

Bitoperatoren stammen ursprünglich aus dem Bereich des Maschinen-Codes und Assembler, existieren aber auch in den meisten Hochsprachen, wie C.

>>    nach rechts schieben
<<    nach links schieben (Bsp.: a<<b ist das gleiche wie a * 2^b;
          bzw. bei 1<<3 wird die 1 um drei Stellen nach links geschoben)
|     bitweises ODER
&     bitweises UND
^     bitweises exklusives ODER
~     bitweises NICHT

Bitoperatoren in dieser Schreibweise können in Assemblercode für konstante Ausdrücke benutzt werden.

Beispiel:

 
ldi temp, (1<<3) | (1<<1) | (1<<2) | (1<<0)

entspricht:

 
ldi temp, 8 | 2 | 4 | 1

entspricht:

 
ldi temp, 15

Bitmaske

Im Folgenden ist häufiger von dem Begriff Bitmaske die Rede. Damit wird eine Folge von einzelnen Bits bezeichnet, die den Zustand null („0“) oder eins („1“) darstellen können.

Bitmasken werden im allgemeinen dazu verwendet, um unter Anwendung eines Operators (z. B. UND, ODER, XOR) eine Eingabe zu manipulieren. Das Ergebnis ist dann die Anwendung des Operators und der Bitmaske auf die Eingabe.

Wenn ein Operator eine Funktion mit zwei Argumenten ist, dann lässt sich dessen Anwendung wie folgt schreiben:

Ergebnis = Operator( Eingabe, Bitmaske )

Die Bitmaske ist häufig eine Konstante, da diese z. B. die Information über die Position einer Information in einem Register darstellt. Das kann z. B. ein Überlaufflag in einem Timer-Statusregister sein.

Bits setzen

Wenn in einem Byte mehrere Bits auf „eins“ gesetzt werden sollen, wird dies durch eine ODER-Verknüpfung erreicht. Alle Bits, welche in der Bitmaske gleich „1“ sind, werden auf „1“ gesetzt. Alle Bits, die in der Maske auf „0“ gesetzt sind, bleiben unverändert.

AVR-Assembler

sbr r16, 0b11110000     ; setzt Bits 4..7 in r16, ist ein Pseudobefehl,
                        ; funktioniert nur für die Arbeitsregister r16..r31

ori r16, 0b11110000     ; setzt Bits 4..7 in r16, ori ist identisch mit sbr,
                        ; funktioniert nur für die Arbeitsregister r16..r31

sbi PORTB, 5            ; setzt Bit 5 in Port B;
sbi PORTB, PB5          ; identisch, besser lesbar;
                        ; funktioniert nur für die IO-Register 0..0x1F

                        ; Für IO-Register mit IO-Adresse 0x20..0x3F muss
                        ; in/out verwendet werden:
in  r16, TIMSK          ; setzt Bit TOIE1 in TIMSK
sbr r16, (1<<TOIE1)
out TIMSK, r16

                        ; Für IO-Register oberhalb der IO-Adresse 0x3F muss
                        ; lds/sts verwendet werden:
                        ; setzt Bit RXCIE0 in UCSR0B
lds r16, UCSR0B
sbr r16, (1<<RXCIE0)
sts UCSR0B, r16

Man beachte den Unterschied! Eine „5“ würde von sbr als „setze Bit 2 und 0“ gedeutet (=0b00000101), während sbi sie als „setze Bit 5“ versteht. Der Befehl sbr erwartet ein Bitmuster für eine ODER-Verknüpfung, während der Befehl sbi die Bitnummer benötigt. Darauf sind auch die Includefiles von Atmel im AVR-Studio (Assembler) als auch WinAVR (C) ausgelegt. Die Namen der Bits sind als Bitnummer definiert. Das ist wichtig, wenn man Register von großen AVRs manipuliert, z. B. ATmega48. Hier muss aus der Bitnummer über eine Schiebeoperation erst das Bitmuster gemacht werden.

Standard-C

PORTB |= 0xF0;   // Kurzschreibweise, entspricht PORTB = PORTB | 0xF0; bitweises ODER

/* übersichtlicher mittels Bit-Definitionen */
#define MEINBIT0 0
#define MEINBIT1 1
#define MEINBIT2 2

PORTB |= ((1 << MEINBIT0) | (1 << MEINBIT2)); // setzt Bit 0 und 2 in PORTB auf "1"

Die letzte Zeile „entschlüsselt“:

  1. (1 << n) : Zuerst wird durch die „<<“-Ausdrücke eine „1“ n-mal nach links geschoben. Dies ergibt somit (in Binärschreibweise) 0b00000001 für (1 << MEINBIT0) und 0b00000100 für (1 << MEINBIT2).
  2. | : Das Ergebnis wird bitweise ODER-verknüpft, also 0b00000001 oder 0b00000100 wird zu 0b00000101.
  3. |= : Diese Maske wird mit dem aktuellen Inhalt von PORTB bitweise ODER-verknüpft und das Ergebnis PORTB wieder zugewiesen.
PORTB |= variable;         // Kurzschreibweise
PORTB  = PORTB | variable; // lange Schreibweise
Ist PORTB vorher z. B. 0b01111010, dann ist der Inhalt nach der Operation 0b01111010 oder 0b00000101 = 0b01111111, die gewünschten Bits sind somit gesetzt!

Anmerkung: Will man das gezeigte Beispiel der Bitmanipulation auf größere Datentypen anwenden, ist zu beachten, dass der Compiler in der Operation (1 << MEINBIT1) stillschweigend, gemäß den C-Regeln, die 1 als Integer-Typ ansieht. Beim AVR-GCC bedeutet das 16-Bit/signed und die folgende Operation bringt ggf. nicht das gewünschte Ergebnis (Stichwort: „Integer Promotion“).

Angenommen, Bit 15 soll in einer 32-Bit weiten Variable gesetzt werden.

#define MEINBIT15 15
#define MEINBIT42 42

uint32_t reg_32; /* uint32_t definiert per typedef z.&nbsp;B. in stdint.h */
uint64_t reg_64; /* uint64_t definiert per typedef z.&nbsp;B. in stdint.h */

reg_32 |= (1 << MEINBIT15);              /* FEHLER: Setzt die Bits 31..15, da ((int)1 << 15) == 0xFFFF8000 */

reg_32 |= ((uint32_t)1 << MEINBIT15);    /* Hier wird nur Bit 15 gesetzt. */
reg_32 |= (1U << MEINBIT15);             /* */
reg_32 |= (1L << MEINBIT15);             /* andere Schreibweise. */
reg_64 |= (1LL << MEINBIT42);            /* Hier wird nur Bit 42 gesetzt,
                                            andere Schreibweise für 64 Bit (long long). */

Bei Compilern für 32-bit-Controller (z. B. ARM7TDMI) sind Integers per default 32-bit und Konstanten sind somit implizit ebenfalls 32-bit weit. Man sollte aber dennoch die oben gezeigte Vorgehensweise verwenden, um Probleme zu vermeiden, die entstehen könnten, wenn Code unter verschiedenen Plattformen/Compilern verwendet werden soll.

Bits löschen

Wenn in einem Byte mehrere Bits auf „null“ gesetzt werden sollen, wird dies durch eine UND-Verknüpfung erreicht. Alle Bits, welche in der Bitmaske gleich „0“ sind, werden auf „0“ gesetzt. Alle Bits, die in der Maske auf „1“ gesetzt sind, bleiben unverändert.

AVR-Assembler

cbr r16, 0b00001111     ; löscht Bits 0..3 in r16, ist ein Pseudobefehl,
                        ; funktioniert nur für die Arbeitsregister r16..r31

andi r16, 0b11110000    ; löscht Bits 0..3 in r16, andi ist identisch mit cbr,
                        ; funktioniert nur für die Arbeitsregister r16..r31

andi r16, ~0b00001111   ; andere Schreibweise, hier wird die Bitmaske durch ~ invertiert,
                        ; dadurch kann man einfach alle zu löschenden Bit als '1' angeben
                        ; so wie bei den Bitmasken für das Setzen von Bits (positive Logik)

cbi PORTB, 5            ; löscht Bit 5 in Port B
cbi PORTB, PB5          ; identisch, besser lesbar
                        ; funktioniert nur für die IO-Register 0..31

                        ; Für IO-Register mit IO-Adresse 0x20..0x3F muss
                        ; in/out verwendet werden, weil dieser Bereich nicht
                        ; bitadressierbar ist:
in  r16, TIMSK          ; löscht Bit TOIE1 in TIMSK
cbr r16, 1<<TOIE1
out TIMSK, r16

                        ; Für IO-Register oberhalb der IO-Adresse 0x3F muss
                        ; lds/sts verwendet werden:
                        ; löscht Bit RXCIE0 in UCSR0B
lds r16, UCSR0B 
cbr r16, 1<<RXCIE0
sts UCSR0B, r16

Auch hier gilt: Man beachte den Unterschied! Eine „5“ würde von cbr als „lösche Bit 2 und 0“ gedeutet, während cbi sie als „lösche Bit 5“ versteht. Der Befehl cbr erwartet ein Bitmuster für eine UND-NOT-Verknüpfung (nicht zu verwechseln mit NAND), während der Befehl cbi die Bitnummer benötigt. Darauf sind auch die Includefiles von Atmel im AVR-Studio (Assembler) als auch WinAVR (C) ausgelegt. Die Namen der Bits sind als Bitnummer definiert. Das ist wichtig, wenn man Register von großen AVRs manipuliert, z. B. ATmega48. Hier muss aus der Bitnummer über eine Schiebeoperation „<<“ erst das Bitmuster gemacht werden.

Standard-C

PORTB &= 0xF0;   // entspricht PORTB = PORTB & 0xF0; bitweises UND
                 // Bits 0..3 (das "niederwertige" Nibble) werden gelöscht

/* übersichtlicher mittels Bit-Definitionen */
#define MEINBIT0 0
#define MEINBIT1 1  
#define MEINBIT2 2  

PORTB &= ~((1 << MEINBIT0) | (1 << MEINBIT2)); // löscht Bits 0 und 2 in PORTB

Die letzte Zeile entschlüsselt:

  1. (1 << n) : Zuerst wird durch die „<<“-Ausdrücke eine „1“ n-mal nach links geschoben. Dies ergibt somit (in Binärschreibweise) 0b00000001 für (1 << MEINBIT0) und 0b00000100 für (1 << MEINBIT2).
  2. | : Das Ergebnis wird bitweise ODER-verknüpft, also 0b00000001 oder 0b00000100 wird zu 0b00000101.
  3. ~ : Der Wert in der Klammer wird bitweise invertiert, aus 0b00000101 wird 0b11111010.
  4. &= : PORTB wird mit der berechneten Maske UND-verknüpft und das Ergebnis wieder PORTB zugewiesen.
PORTB &= variable;          // Kurzschreibweise
PORTB  = PORTB & variable;  // lange Schreibweise
Ist PORTB vorher z. B. 0b01111111, dann ist der Inhalt nach der Operation 0b01111111 und 0b11111010 = 0b01111010, die gewünschten Bits 0 und 2 sind somit gelöscht.

Die C-Ausdrücke mittels Definitionen von Bitnummern und Schiebeoperator (<<) sehen auf den ersten Blick etwas „erschreckend“ aus und sind mehr „Tipparbeit“, funktionieren aber universell und sind deutlicher und nachvollziehbarer als „handoptimierte“ Konstanten. Bei eingeschalteter Optimierung löst der Compiler die Ausdrücke mit konstanten Werten bereits zur Compilierungszeit auf und es entsteht kein zusätzlicher Maschinencode. Bei AVR sind die Definitionen meist Teil der Entwicklungsumgebungen (bei avr-libc z. B. implizit durch #include <avr/io.h>). Sie entsprechen den Angaben und Beispielen in den Datenblättern und sind damit de facto ein Standard beim Zugriff auf Bits in Hardware-Registern.

Wichtiger Hinweis: Die ODER-Verknüpfung und die anschließende Invertierung kann man nicht vertauschen (Theorem von DeMorgan)! Folgendes Beispiel soll die Richtigkeit der Aussage zeigen:

 ~(0b0001 | 0b0010) == 0b1100
  ~0b0001 | ~0b0010 == 0b1111

Noch ein wichtiger Hinweis: Der Operator ~ mit einem Operanden vom Typ int negiert nur so viele Bits, wie der Typ int hat. Will man ein Bit in einer breiteren Variablen löschen, dann sollte die nach links zu shiftende „1“ den Typ dieser Variablen haben.

Ein Programm, welches das verdeutlicht:

#include <stdio.h>
#include <stdint.h>

int main(int argc, const char* argv[])
{
    int bit = 60;
    uint64_t ui64;
    printf("sizeof(int)=%d\n", (int)sizeof(int));
    
    ui64 = 0xFFFFFFFFFFFFFFFF;    
    ui64 &= ~(1<<60); /* Keine Wirkung bei sizeof(int) < 8 */
    /* gcc warnt sogar:    
     * gcc -Wall bit_clear.c -o bit_clear
     * bit_clear.c: In function ‘main’:
     * bit_clear.c:11:5: warning: left shift count >= width of type [enabled by default]
     */    
    printf("%d\n", ui64!=0xFFFFFFFFFFFFFFFF);

    ui64 = 0xFFFFFFFFFFFFFFFF;    
    ui64 &= ~((uint64_t)1<<60); /* OK */
    printf("%d\n", ui64!=0xFFFFFFFFFFFFFFFF);

    ui64 = 0xFFFFFFFFFFFFFFFF;
    ui64 &= ~(1LL<<60); /* auch OK, und kürzer */
    printf("%d\n", ui64!=0xFFFFFFFFFFFFFFFF);

    ui64 = 0xFFFFFFFFFFFFFFFF;
    ui64 &= ~(1<<bit); /* Ohne Warnung, und funktioniert manchmal, je nach Optimierung */
    printf("%d\n", ui64!=0xFFFFFFFFFFFFFFFF);

    return 0;
}
/* Ausgabe auf meinem PC ohne Optimierung:
 * sizeof(int)=4
 * 0
 * 1
 * 1
 * 1
 * Ausgabe auf meinem PC mit -O2
 * 0
 * 1
 * 1
 * 0
 */

Niederwertigstes gesetztes Bit löschen (Standard-C)

Folgender Code löscht von allen 1-Bits in einer Integer-Variablen das niederwertigste, unabhängig von der Position desselben.

Beispiel: 01101000 → 01100000

uint8_t byte;

byte = irgendwas();

byte = byte & (byte - 1); /* Diese seltsame Operation löscht das
                             niederwertigste 1-Bit */

Beispiel:

Byte  :   01101000
Byte-1:   01100111
Ergebnis: 01100000

Das funktioniert also mit jeder beliebigen Zahl.

Dies kann bspw. zur schnellen Paritätsgenerierung eingesetzt werden:

uint8_t pareven(uint8_t byte) {
  uint8_t par = 0;

  while(byte) {
    byte = byte & (byte - 1);
    par = ~par;
  }
  return par;
}

Das genannte gilt natürlich nicht nur für 8-Bit-Integers, sondern für beliebige, vom Compiler unterstützte Wortlängen.

Bits invertieren

Im allgemeinen Sprachgebrauch oft Toggeln genannt (aus dem Englischen: to toggle). Wenn in einem Byte mehrere Bits invertiert („getoggelt“) werden sollen, wird dies durch eine XOR-Verknüpfung erreicht. Alle Bits, welche in der Bitmaske gleich „1“ sind, werden invertiert. Alle Bits, die in der Maske auf „0“ gesetzt sind, bleiben unverändert.

AVR-Assembler

Bei AVRs erlaubt dies folgender Assemblercode. Hier wird ein Ausgangspin invertiert.

  sbic  PortB, 0    ; Überspringe den nächsten Befehl, wenn das Bit 0 im Port gelöscht ist
  rjmp  ClrBitNow   ; Springe zu ClrBitNow
  sbi   PortB, 0    ; Setze Bit 0 in PortB
  rjmp  BitReady    ; Springe zu BitReady
ClrBitNow:
  cbi  PortB, 0    ; Lösche Bit 0 in PortB
BitReady:

Noch kürzer geht's so: Die zweite Zeile mit dem Befehl ldi lädt die Bitmaske, in welcher die zu toggelnden Bits auf „1“ gesetzt sind. In diesem Beispiel wird das dritte Bit invertiert. Der Vorteil dieser Methode ist neben der Kürze und Übersichtlichkeit auch die Möglichkeit, bis zu acht Bits gleichzeitig zu toggeln. Diese Methode ist natürlich auch auf normale Daten anwendbar, nicht nur auf IO-Ports.

 in     R24, PORTE   ; Daten lesen
 ldi    R25, 0x04    ; Bitmaske laden, hier Bit #2
 eor    R24, R25     ; Exklusiv ODER
 out    PORTE, R24   ; Daten zurückschreiben

Eine andere Möglichkeit gibt es, wenn man nur das 8. Bit kippen will:

 in      r16, PORTB
 subi    r16, 0x80
 out     PORTB, r16

Standard-C

 PORTB ^= (1<<PB0);    /* XOR, Kurzschreibweise für PORTB = PORTB ^ (1<<PB0) */

Neuere ATmegas

Bei den neueren ATmegas (z. B. ATmega48) kann man (auf Ausgang gesetzte) IO-Pins direkt ohne den Umweg über Register toggeln, indem man das entsprechende Bit im PINx-Register setzt:

sbi PIND, 2       ; Bit 2 von Port D toggeln

8051er

cpl bitadresse

Bits prüfen

Will man prüfen, ob ein oder mehrere Bits in einer Variablen gesetzt oder gelöscht sind, muss man sie mit einer Bitmaske UND-verknüpfen. Die Bitmaske muss an den Stellen der zu prüfenden Bits eine „1“ haben, an allen anderen eine „0“.

  • Ist das Ergebnis gleich null, sind alle geprüften Bits gelöscht.
  • Ist das Ergebnis ungleich null, ist mindestens ein geprüftes Bit gesetzt.
  • Ist das Ergebnis gleich der Bitmaske, sind alle geprüften Bits gesetzt.

AVR-Assembler

Der AVR hat spezielle Befehle, um direkt einzelne Bits in den CPU-Registern r0…r31 sowie den IO-Registern 0…0x1F zu prüfen.

; Befehle zur Prüfung von einzelnen Bits

    sbrs    r16,3       ; überspringe den nächsten Befehl, wenn in r16 Bit #3 gesetzt ist
    rjmp    bit_ist_nicht_gesetzt

    sbrc    r16,5       ; überspringe den nächsten Befehl, wenn in r16 Bit #5 gelöscht ist
    rjmp    bit_ist_nicht_geloescht

    sbis    timsk,3     ; überspringe den nächsten Befehl, wenn in timsk Bit #3 gesetzt ist
    rjmp    bit_ist_nicht_gesetzt

    sbic    timsk,5     ; überspringe den nächsten Befehl, wenn in timsk Bit #5 gelöscht ist
    rjmp    bit_ist_nicht_geloescht

; Befehle zur Prüfung von mehreren Bits

    andi    r16,0b1010  ; prüfe Bit #1 und #3 in r16
    breq    alle_bits_sind_geloescht

    andi    r16,0b1010  ; prüfe Bit #1 und #3 in r16
    brne    mind_ein_bit_ist_gesetzt

    andi    r16,0b1010  ; prüfe Bit #1 und #3 in r16
    cpi     r16,0b1010
    breq    alle_bits_sind_gesetzt

Standard-C

    // prüfe, ob Bit 4 in der Variablen tmp gelöscht ist
    // die Klammer ist wichtig!
    if (!(tmp & 0x10)) {
       // hier die Anweisungen, wenn das Bit gelöscht ist
    }

    // prüfe, ob Bit 0 und Bit 4 in der Variablen tmp gelöscht sind
    // die Klammer ist wichtig!
    if ((tmp & 0x11) == 0) {
       // hier die Anweisungen, wenn beide Bits gelöscht sind
    }

    // prüfe, ob Bit 0 oder Bit 4 in der Variablen tmp gesetzt ist
    if (tmp & 0x11) {
       // hier die Anweisungen, wenn mindestens ein Bit gesetzt ist
    }

    // prüfe, ob Bit 0 oder Bit 4 in der Variablen tmp gelöscht sind
    if (~tmp & 0x11) {
       // hier die Anweisungen, wenn mindestens ein Bit gelöscht ist
    }

    // prüfe, ob Bit 4 in der Variablen tmp gesetzt ist
    if (tmp & 0x10) {
       // hier die Anweisungen, wenn das Bit gesetzt ist
    }

    // prüfe, ob Bit 0 und Bit 4 in der Variablen tmp gesetzt sind
    // die Klammer ist wichtig!
    if ((tmp & 0x11) == 0x11) {
       // hier die Anweisungen, wenn beide Bits gesetzt sind
    }

Hilfsfunktionen zur Bitmanipulation in C/C++

Um "einfacher" elementare Bitmanipulationen durchzuführen bietet es sich an einige Hilfsfunktionen zu definieren. Dabei gibt es zwei verschiedene Möglichkeiten diese zu realiseren:

  • Als C-Makro C_Makros
  • Als Inline-Funktion

In beiden Fällen wird bei eingeschalteter Optimierung letztendlich vom Compiler ein sehr kompakter (und identischer!) Code erzeugt, jedoch ist dringend von der Verwendung von Makros abzuraten (siehe Makro )! Im Fehlerfall zeigt der Compiler bei der Verwendung vom Makros keine eindeutigen Fehlermeldungen an, da es sich um simple Ersetzungen handelt - bei der Verwendung von Inline-Funktionen hingegen gibt es eine "brauchbare" Fehlermeldung!

Beispiele - Inline Variante

// Achtung: Zugriffe erfolgen über Pointer
// PORTA, PB2 setzen
BIT_SET(&PORTA, PB2);

// PORTC, PB0 löschen
BIT_CLEAR(&PORTC, PB0);

// PORTA, PB2 direkt setzen
// HIGH
BIT_BOOL_SET(&PORTA, PB2, 1);

// LOW
BIT_BOOL_SET(&PORTA, PB2, 0);

Beispiele - MakroVariante

// Achtung: Zugriffe erfolgen direkt über die Variablen/Portnamen
// PORTA, PB2 setzen
BIT_SET(PORTA, PB2);

// PORTC, PB0 löschen
BIT_CLEAR(PORTC, PB0);

Um die Hilfsfunktionen verwenden zu können einfach folgenden Code in eine neue Header-Datei (z.B. BitIO.h) kopieren:

Hilfsfunktionen als Inline-Methoden

Achtung: Wenn nur ein C Compiler verwendet wird, kennt dieser den Typ "bool" nicht, dieser muss dann vorher definiert werden!

/**
 *  BitIO.h
 *	@author 	Andi Dittrich <http://andidittrich.de>
 *	@version	1.0
 *	@license	MIT Style X11 License
*/


#include <inttypes.h>

#ifndef BITIO_H_
#define BITIO_H_

// set bit
static inline void BIT_SET(volatile uint8_t *target, uint8_t bit) __attribute__((always_inline));
static inline void BIT_SET(volatile uint8_t *target, uint8_t bit){
	*target |= (1<<bit);
};

// set clear
static inline void BIT_CLEAR(volatile uint8_t *target, uint8_t bit) __attribute__((always_inline));
static inline void BIT_CLEAR(volatile uint8_t *target, uint8_t bit){
	*target &= ~(1<<bit);
};

// bit toogle
static inline void BIT_TOGGLE(volatile uint8_t *target, uint8_t bit) __attribute__((always_inline));
static inline void BIT_TOGGLE(volatile uint8_t *target, uint8_t bit){
	*target ^= (1<<bit);
};

// set bit by boolean
static inline void BIT_BOOL_SET(volatile uint8_t *target, uint8_t bit, bool enable) __attribute__((always_inline));
static inline void BIT_BOOL_SET(volatile uint8_t *target, uint8_t bit, bool enable){
	if (enable){
		BIT_SET(target, bit);
	}else{
		BIT_CLEAR(target, bit);
	}
};

#endif /* BITIO_H_ */

Hilfsfunktionen als C-Makro (nicht empfohlen)

/* Bit setzen */
#define set_bit(var, bit) ((var) |= (1 << (bit)))
 
/* Bit löschen */
#define clear_bit(var, bit) ((var) &= (unsigned)~(1 << (bit)))
 
/* Bit togglen */
#define toggle_bit(var,bit) ((var) ^= (1 << (bit)))

Bitmanipulation beim MSP430

Beim MSP430 und dessen Compilern sind die Bitnamen meist anders definiert. Und zwar nicht als Bitnummer, sondern als Bitmuster. Darum schreibt man dort die Bitzugriffe in C anders. Das kann auch bei anderen Mikrocontrollern bzw. C-Compilern so sein. Wichtig ist, dass man seine eignen Definitionen in der gleichen Weise wie der Compiler anlegt, um Verwirrung zu vermeiden, siehe Strukturierte Programmierung auf Mikrocontrollern

// Definition von Bitnamen in den Headerfiles des Compilers

#define PD4 4               // Definition im AVR GCC als Bitnummer
#define PD5 5

#define P14 (1<<4)          // Definition im MSP430 GCC als Bitmuster
#define P15 (1<<5)

// Bitmanipulation im Programm
 
   DDRD = (1<<PD5) | (1<<PD4);   // AVR GCC
   P1DIR = P15 | P14;            // MSP430 GCC

Siehe auch

Weblinks