Technische Informatik; Hardwarenahe Programmierung

Bitmanipulationen Erweitert

Inhalt

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

  • Als C/C++-Makro
  • Als (Inline-)Funktion
  • Als struct/union Bitfeld

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 Suche und Ersetzungen handelt - bei der Verwendung von Inline-Funktionen hingegen gibt es eine "brauchbare" Fehlermeldung! Ebenso abzuraten sind die Bitfelder für Bitmanipulationen, da der Einsatz meist den Code komplizierter macht als mit Verschiebe und Maskier-Operatoren.

Verwendung von Funktionen

BitmanipFunctions.cpp

int setBit(int x, unsigned char position)
{
  int mask = 1 << position;
  return x | mask;
}

int clearBit(int x, unsigned char position)
{
  int mask = 1 << position;
  return x & ~mask;
}

int modifyBit(int x, unsigned char position, bool newState)
{
  int mask = 1 << position;
  int state = int(newState); // relies on true = 1 and false = 0
  return (x & ~mask) | (-state & mask);
}

int flipBit(int x, unsigned char position)
{
  int mask = 1 << position;
  return x ^ mask;
}

bool isBitSet(int x, unsigned char position)
{
  x >>= position;
  return (x & 1) != 0;
}

Verwendung von Markros

MakroDefinitionen.cpp

#define BOOL(x) (!(!(x)))
#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))

Dies ist eine allgemeine Lösung für eine ganze Klasse von Problemen. Es ist natürlich möglich und sogar angebracht, jedes Mal, wenn eines dieser Makros benötigt wird, das Äquivalent eines dieser Makros mit expliziten Maskenwerten neu zu schreiben, aber warum? Denke daran, dass die Makrosubstitution im Präprozessor erfolgt und der generierte Code die Tatsache widerspiegelt, dass die Werte vom Compiler als konstant angesehen werden - dh., es ist genauso effizient, die verallgemeinerten Makros zu verwenden, als das Rad jedes Mal neu zu erfinden, wenn Sie dies benötigen Bit-Manipulation durchführen.

Testen bzw. erweitere den folgenden Code:

TestMakro.cpp

#include "mbed.h"

BusOut myleds(LED1, LED2, LED3, LED4);

#define BOOL(x) (!(!(x)))
#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))

int bitmanip(int word) {
      word = BitSet(word, 2);
      word = BitSet(word, 7);
      word = BitClr(word, 3);
      word = BitFlp(word, 9);
      return word;
}

int main() {
    uint16_t mask  = 0x000f;    // 00000000 00001111b mask lower Nibble
    uint16_t value = 0x0055;    // 00000000 01010101b
    while(1) {
        scanf("%d", &value);
        value=bitmanip(value);
        value=BitSet(value, 8);
        myleds = value & mask;
        printf("%x\n", value)
        wait(0.25);
    }
}

struct/union bit fields

Eine andere Variante wäre noch mit einer C/C++ struct ein Bitfeld zu erzeugen und mit einer union für z.B. einen Buszugriff zu überlagern (siehe auch unions and bit fields. Bei einer union haben sowohl byte als auch bit einen überlappenden (gemeinsam genutzten) Speicherort. Speichere einen Wert als Byte und lese dann Bit, um die entsprechenden Bitwerte für den Wert zu erhalten. Oder wie hier in dem Programm speichere die einzelnen Bits und gebe das Nibble auf die vier blauen Leds aus:

StructUnionRegister.cpp

#include "mbed.h"

BusOut myleds(LED1, LED2, LED3, LED4);

struct nibble {	
    uint8_t a:1;
    uint8_t b:1;
    uint8_t c:1;               
    uint8_t d:1; 
} ;

union bits { 
    uint8_t byte;
    struct nibble bit;
};

bits reg;

int main() {
    while(1) {
        reg.bit.a=1;
        reg.bit.c=1;
        myleds = reg.byte;
        printf("%d\n", reg.byte);
        wait(0.25);
    }
}

Unions werden in Netzwerkprotokollen verwendet. Sie können auch nützlich sein, um den Polymorphismus in C++ auszutricksen. Im Allgemeinen sind sie ein spezieller Anwendungsfall. Aber für die behandelten Beispiel sind Verschiebungen und Masken viel sauberer Code, sodass Sie für diese Aufgabe wahrscheinlich keine struct/union verwenden würden.

Aufgaben Maskieren und Verschieben

Verwende Funktionen mit entsprechenden Parametern und Rückgabewerten für die Aufgaben von Bitmanipulationen-Grundlegend.

Aufgaben Register

1. Aufgabe [UARTnLCR]:

Das UART Line Control Register UARTnLCR (Seite 336) bestimmt das Format der Daten, die übertragen werden und ist folgend aufgeteilt:

BitSymbolValueDescriptionReset Value
01:00Word Length005-bit character length0
016-bit character length
107-bit character length
118-bit character length
2Stop Bit Select01 stop bit0
12 stop bits
3Parity Enable0Disable parity generation and checking0
1Enable parity generation and checking.
05:04Parity Select00Odd parity
01Even Parity.
10Forced "1" stick parity
11Forced "0" stick parity
6Break Control0Disable break transmission0
1Enable break transmission
7DLAB0Disable access to Divisor Latches
1Enable access to Divisor Latches
  1. Setzen Sie das Register auf folgende Werte: 8-bit character, 2 stop bits, enable forced 1 stick parity,
  2. Das Register ist auf den Wert aus Aufgabe 1 gestetzt. Ändere das Parity Select auf Even parity und DLAB Enable access.
  3. Das Register ist auf den Wert aus Aufgabe 2 gestetzt. Resetiere das Register NUR mit den Reset Values.
  4. Schreibe eine Funktion "uartNlcr(...)" mit den entsprechenden Bits als Parameter und setzte die Register-Bits im Funktionskörper entsprechend und gib diesen Wert zurück an den Funktionsaufruf.
  5. u.ä.

2. Aufgabe [UARTnLSR]:

Beim NXP LPC1768 ist das Line Status Register UARTnLSR (Seite 339) ein schreibgeschütztes Register, das die Statusinformationen zu den UARTn TX- und RX-Blöcken bereitstellt:

BitSymbolValueDescriptionReset Value
0Receiver Data Ready (RDR)0The UARTn receiver FIFO is empty.
1The UARTn receiver FIFO is not empty.0
1Overrun Error (OE)0Overrun error status is inactive.
1Overrun error status is active.0
2Parity Error (PE)0Parity error status is inactive.
1Parity error status is active.0
3Framing Error (FE)0Framing error status is inactive.
1Framing error status is active.0
4Break Interrupt (BI)0Break interrupt status is inactive.
1Break interrupt status is active.0
5Transmitter Holding Register Empty (THRE))0UnTHR contains valid data.
1UnTHR is empty.1
6Transmitter Empty (TEMT)0UnTHR and/or the UnTSR contains valid data.
1UnTHR and the UnTSR are empty.1
7Error in RX FIFO (RXFE)0UnRBR contains no UARTn RX errors or UnFCR[0]=0.
1UARTn RBR contains at least one UARTn RX error.0
31:8-Reserved, the value read from a reserved bit is not defined.NA
  1. Schreibe ein entsprechendes Mbed-Programm, dass die einzelnen Statusinformationen abfragt und entsprechende Meldungen mittels printf-Nachrichten ausgibt.
  2. Erweitere obiges Mbed-Programm um eine Funktion, die eine Nachricht ausgibt und die 4 Leds blinken, wenn ein Fehler aufgetreten ist.
  3. Erweitere obiges Mbed-Programm um eine Funktion, die überprüft ob die Reset Values im Register stehen.
  4. u.ä.

All wikipages