Kommen wir nun zu einem sehr interessanten Teil: der Bit-Manipulation. Es ist möglich, die Bits über geeignete Operatoren vielfaltig zu manipulieren, was jedoch ein wenig Übung erfordert. Aber wenn man sich diese Art der Programmierung erst einmal erschlossen ist, dann macht es richtig Spaß, die Bits in jeglicher Weise zu verbiegen. Da das Thema dieses Bastelprojektes ja ein Lauflicht ist (was später noch etwas anders als hier realisiert wird), wollen wir doch einmal sehen, wie es möglich ist, eine einzelne LED »wandern« zu lassen. Wir nutzen dazu die sogenannten Bit-Operatoren.
Schiebeoperatoren
Damit eine einzelne LED von einem Bit zum anderen geschoben wird, nutzen wir einen der folgenden Schiebeoperatoren:
>> Bedeutet nach rechts schieben.
<< Bedeutet nach links schieben.
Wie soll das funktionieren? Sehen wir uns dazu die folgenden Inhalte von PORT B und was mit ihnen im Laufe der Zeit passiert:
Abb. 9: Die Inhalte von Register PORT B zu unterschiedlichen Zeiten
Man kann sehen, dass die 1 rechts außen schrittweise nach links wandert. Der kleine rote Punkt markiert die Endposition zur angegebenen Zeit. Wie können wir aber diese 1 von rechts nach links schieben? Der angesprochene Schiebeoperator für das nach links Schieben wird uns gute Dienste leisten. Wir gehen davon aus, dass die 1 auf der rechten Außenposition, die – wir erinnern uns – LSB (Least Significant Bit) genannt wird und das Bit mit dem niedrigsten Wert kennzeichnet, immer als Ausgangsposition für alle Schiebeoperationen genommen wird. Der folgende Sketch-Code übernimmt diese Funktion des Schiebens:
byte pos = 0; // Positionswert void setup() { DDRB = 0b11111111; // PORT B komplett als OUTPUT } void loop() { PORTB = 1 << pos++; // Die 1 nach links schieben if(pos > 5) pos = 0; delay(500); // Kurze Pause von 500ms }
Die Variable tritt in der Funktion als Schiebeweitenangeber in Erscheinung. Zu Beginn hat sie den Wert 0, was bedeutet, dass beim ersten Schleifendurchlauf die 1 auf ihrer Position bleibt, wie das auch bei Zeitmarke t1 der Fall ist. Nach der Abarbeitung des Befehls wird die Variable pos um den Wert 1 erhöht, was wiederum bedeutet, dass beim nächsten Schleifendurchlauf eine Schiebeaktion nach links um eine Position bedeutet. Es muss jedoch darauf hingewiesen werden, dass durch das Schieben nach links auf der rechten Seite eine 0 eingeschoben wird. In gleicher Weise wird bei jedem erneuten Durchlauf verfahren. Ist der Wert von pos jedoch größer 5, was außerhalb unserer LED-Darstellungsmöglichkeit von sechs Bits liegt, wird er mithilfe der if-Anweisung und des nachfolgenden Befehls auf den Wert 0 zurückgesetzt und das Spiel beginnt von vorn.
Einzelne Bits setzen
Jetzt wollen wir einmal sehen, wie es möglich ist, einzelne Bits zu setzen, also mit dem Wert 1 zu versehen, ohne bestehende Bits in ihrem aktuellen Zustand zu beeinflussen. Schauen wir uns die folgende Situation an, wobei die gezeigte Bit-Kombination die Ausgangs-Bit-Maske (Folge von Bits) darstellt:
Die Aufgabe ist es jetzt, das Bit mit der Wertigkeit 8 an Position 3 zu setzen. Wir könnten den folgenden Sketch-Code schreiben, um diese Anforderung zu realisieren:
void setup() { DDRB = 0b11111111; // PORT B komplett als OUTPUT PORTB = 0b00000110; // Ausgangs Bit-Maske delay(500); PORTB = 1 << 3; // 3 Positionen nach links schieben } void loop() { /* leer */ }
Die Ausgangs-Bit-Maske, die 500ms lang zu sehen ist, sieht also wie folgt aus:
Im Anschluss soll dann die LED mit der Wertigkeit 8 zusätzlich zu den schon leuchtenden angehen. Doch was ist das Ergebnis? Schau mal:
Was ist schiefgelaufen? Nun, wir haben eine 1 rechts außen auf das LSB gesetzt und dann diese um 3 Positionen nach links geschoben. Wir erinnern uns, dass beim Schieben immer eine 0 an die vorherige Position eingeschoben wird. Für unser Vorhaben nicht so gut, denke ich. Was also tun? Die Lösung verbirgt sich hinter einem weiteren Operator, der aus der Kategorie der Bit-Operatoren entnommen wird. Es handelt sich um das binäre ODER. Dazu sollten wir uns jedoch die folgende Wertetabelle anschauen, damit wir erkennen, welche Auswirkungen dieser Operator hat:
Tabelle 2: Der binäre ODER-Operator | ||
A | B | Q |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
Die Spalten A und B zeigen zwei logische Ausgangswerte und Q das Ergebnis der binären ODER-Verknüpfung. Der binäre ODER-Operator wird durch den senkrechten Strich | (Pipe-Symbol) gekennzeichnet. Ändern wir nun die folgende Zeile
PORTB = 1 << 3;
in
PORTB |= 1 << 3;
ab, dann funktioniert auch unsere Anforderung, das entsprechende Bit zu setzen, ohne die vorhandenen zu beeinflussen. Das Ergebnis sieht dann wie folgt aus:
Warum ist das aber so? Nun, wenn eine Schiebeaktion schrittweise immer wieder mit der vorherigen Bit-Maske in PORT B mit ODER verknüpft wird, dann bleiben alle Bits, die eine 0 enthalten, unverändert und alle, die eine 1 haben, werden gesetzt.
Einzelne Bits löschen
Gesetzte Bits können natürlich auch wieder gelöscht werden. Nehmen wir an, es würde die folgende Bit-Kombination vorliegen, die mit der nachfolgenden Codezeile erreicht wird:
PORTB = 0b0010110;
Nun möchten wir das Bit mit der Wertigkeit 2 an Position 1 löschen, so dass dort die LED ausgeht. Alle anderen sollen natürlich davon unbeeindruckt ihren Zustand behalten. Dazu verwenden wir wieder einen neuen Operator aus der Kategorie der Bit-Operatoren, den binären UND-Operator. Zum besseren Verständnis dazu die entsprechende Wertetabelle:
Tabelle 3: Der binäre UND-Operator | ||
A | B | Q |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
Das Ergebnis dieser Verknüpfung ist nur 1, wenn beide Eingangswerte den Wert 1 aufweisen. Der binäre UND-Operator wird durch das