Kitabı oku: «Mit Arduino die elektronische Welt entdecken», sayfa 13

Yazı tipi:

Die Bit-Manipulation

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 Kaufmanns-Und &‌ ausgedrückt. Alle Bits, die in der Verknüpfungsmaske den Wert 0 haben, werden gelöscht, die eine 1 haben, bleiben unverändert. Der Sketch-Code dazu sieht wie folgt aus:

void setup() { DDRB = 0b11111111; // PORT B komplett als OUTPUT PORTB = 0b00010110; // Ausgangs-Bit-Maske delay(500); PORTB &= 0b11111101; } void loop() { /* leer */ }

Durch die Zeile

PORTB &= 0b11111101;

ist quasi eine Maske erstellt worden und an der Stelle, an der sich die 0 befindet, gibt es kein Durchkommen für bestehende Bits.

Troubleshooting

Falls die LEDs nicht nacheinander zu leuchten beginnen, dann trenne das Board sicherheitshalber vom USB-Anschluss und geh bitte folgende Dinge durch:

 Überprüfe deine Steckverbindungen auf dem Breadboard daraufhin, ob sie wirklich der Schaltung entsprechen.

 Achte auf mögliche Kurzschlüsse.

 Sind die LEDs richtig herum eingesteckt worden? Denk an die richtige Polung!

 Haben die Widerstände die korrekten Werte?

 Überprüfe noch einmal den Sketch-Code auf Richtigkeit.

Was haben wir gelernt?

 Du hast eine Sonderform einer Variablen kennengelernt, die es dir ermöglicht, mehrere Werte des gleichen Datentyps aufzunehmen. Sie wird Array-Variable genannt. Ihre einzelnen Elemente werden durch einen Index angesprochen.

 Die for-Schleife ermöglicht es dir, eine oder mehrere Codezeilen mehrfach auszuführen. Die Steuerung erfolgt über eine sogenannte Laufvariable, die innerhalb der Schleife arbeitet und mit einem bestimmten Startwert initialisiert wird. Über eine Bedingung hast du festgelegt, wie lange die Schleife durchlaufen werden soll. Damit hast du die Kontrolle darüber, welchen Wertebereich die Variable verarbeitet.

 Über eine Blockbildung durch das geschweifte Klammerpaar kannst du mehrere Befehle zu einem Block zusammenfassen, die bei einer for-Schleife allesamt ausgeführt werden.

 Die gerade genannte Laufvariable wird dazu benutzt, den Index eines Arrays zu manipulieren, um damit die einzelnen Array-Elemente anzusprechen.

 Über die Manipulation der Register haben wir die digitalen Pins eines Ports angesteuert und so ein Lauflicht realisiert.

Der Lauflicht-Workshop

Wenn du Lust hast, kannst du nun im Lauflicht-Workshop das Gelernte auf neue Fragestellungen übertragen und damit prüfen, ob du alles verstanden hast. In dem Workshop möchte ich dich dazu animieren, das Lauflicht in verschiedenen Mustern blinken zu lassen. Es gibt dabei unterschiedliche Varianten:

 Immer nur in eine Richtung mit einer LED (das kennst du bereits).

 Vor und zurück mit einer oder mehreren LEDs.

 Vor und zurück zur selben Zeit (zwei LEDs, die sich aufeinander zu bewegen).

 Zufallsauswahl der einzelnen LEDs.

Für eine zufällige Ansteuerung einer LED benötigst du eine weitere Funktion, die du bisher noch nicht kennengelernt hast. Sie nennt sich random‌, was übersetzt so viel wie ziellos oder zufällig bedeutet. Die Syntax dieser Funktion gibt es in zwei Varianten:

1. Variante

Wenn du einen zufälligen Wert in einem Bereich von 0 bis zu einer vor dir festgelegten Obergrenze generieren möchtest, verwende die nachfolgende Variante:


Abb. 10: Der Befehl random mit einem Argument

Wichtig ist jedoch, dass der oberste Wert, den du angibst, immer exklusiv ist, also nicht zu dem von dir festgelegten Zahlenbereich gehört. In diesem Beispiel generierst du also Zufallszahlen in einem Bereich von 0 bis 6.

2. Variante

Wenn du einen zufälligen Wert im Bereich von Untergrenze bis Obergrenze generieren möchtest, verwende die in der folgenden Abbildung dargestellte Variante:


Abb. 11: Der Befehl random mit zwei Argumenten

Dieser Befehl generiert Zufallszahlen im Bereich von 2 bis 5. Auch hier gilt wieder, dass der oberste Wert exklusiv ist. Dieser Umstand ist manchmal eine Fehlerquelle beim Programmieren, die man nicht so leicht findet. Die einzige Möglichkeit, diesen Fehler zu vermeiden, ist, ihn sich gut zu merken. Und nun viel Spaß bei deinem Lauflicht-Workshop. Vielleicht findest du ja auch noch weitere Möglichkeiten, was du mit dem Erlernten anstellen kannst.

Bastelprojekt 7:
Die Port-Erweiterung

Im Lauflicht-Bastelprojekt (Bastelprojekt 6) hast du gesehen, wie du über die Ansteuerung mehrerer LEDs ein Lauflicht programmieren kannst. Da dein Arduino-Board jedoch nur eine begrenzte Anzahl digitaler Ausgänge besitzt, können dir diese wertvollen Ressourcen irgendwann knapp werden, wenn du dein Lauflicht mit weiteren LEDs versehen möchtest. Vielleicht willst du ja nicht nur digitale Ausgänge ansteuern, sondern auch ein paar Sensoren an digitalen Eingängen anschließen. Es liegt in der Natur der Sache, dass dir dann immer weniger digitale Pins zur Verfügung stehen. Wie kommen wir aus diesem Dilemma heraus? Um dieses Problem geht es in diesem Bastelprojekt.

Eine digitale Port-Erweiterung

Es gibt mehrere Möglichkeiten zur Porterweiterung, von denen ich dir hier eine ausführlich vorstellen werde. Ich möchte hierfür ein Schieberegister verwenden. Die Frage, die du dir jetzt bestimmt stellst, ist: »Was ist ein Schieberegister‌ und wie arbeitet es?« Du kommst bei diesem Projekt das erste Mal mit einem integrierten Schaltkreis‌‌ (IC = Integrated Circuit‌‌) in Berührung. Das ist ein elektronisches Bauteil, das mit deinem Arduino-Board verbunden wird. Ein Schieberegister ist eine Schaltung, die über ein Taktsignal‌ gesteuert wird und mehrere Ausgänge besitzt, die hintereinander angeordnet sind. Bei jedem Takt wird der Pegel, der am Eingang des Schieberegisters anliegt, an den nächsten Ausgang weitergereicht. So wandert diese Information durch alle vorhandenen Ausgänge. Auf dem folgenden Bild sehen wir die fleißigen Arbeiter in einem Schieberegister, die einen logischen HIGH-Pegel an die nächste Stelle weiterreichen:


Abb. 1: Und hepp...

Der integrierte Schaltkreis namens 74HC595‌, den wir für unsere Zwecke verwenden, besitzt einen seriellen Eingang, in den wir die Daten hineinschieben, und acht Ausgänge, die mit internen Speicherregistern versehen sind, um die Zustände zu halten. Es werden zur Versorgung lediglich drei digitale Pins benötigt, die den Baustein mit Daten versehen, der seinerseits seine acht Ausgänge ansteuert. Das ist schon eine enorme Einsparung, denn der Schaltkreis 74HC595 lässt sich kaskadieren, sodass eine fast unbegrenzte Erweiterung der digitalen Ausgänge möglich wird. Was bedeutet das? Schauen wir uns dazu die einzelnen Ein- und Ausgänge dieses Schaltkreises genauer an. In der folgenden Abbildung siehst du die Pin-Belegung des 74HC595‌, und zwar in einer Ansicht von oben auf das entsprechende Gehäuse:


Abb. 2: Die Pin-Belegung des Schieberegisters 74HC595

Was bedeutet ein waagerechter Strich über einer Pin-Bezeichnung?


Wenn sich über einer Pin-Bezeichnung ein waagerechter Strich wie beispielsweise bei Pin 10 und Pin 13 in Abbildung 2 befindet, sind das Signaleingänge, die LOW-aktiv arbeiten. Für einen Master-Reset an Pin 10 bedeutet das, dass der Reset bei einem LOW-Pegel ausgelöst wird und im Normalbetrieb mit der Versorgungsspannung Vcc verbunden sein muss.

Auf der folgenden Abbildung sehen wir das Prinzip eines Schieberegisters mit den zwei Stufen der Verarbeitung:


Abb. 3: Das Prinzip des Schieberegisters 74HC595

Stufe 1

Das Shift-Register‌ auf der linken Seite nimmt die seriellen Daten über den DS-Pin (14) an und speichert sie zwischen. Mit einer positiven Flanke (Wechsel von 0 auf 1) am SH_CP-Pin (11) wird jeder Wert des Datenstroms einzeln in das interne Register übernommen.

Stufe 2

Nun müssen die Daten noch in das Storage-Register‌ übernommen werden, damit die vormals seriellen Daten parallel an den Ausgängen QA bis QH zur Verfügung stehen. Das erfolgt über eine positive Flanke (Wechsel von 0 auf 1) am ST_CP-Pin (12). Damit die Übertragung an die Ausgänge erfolgreich ist, muss der OE-Pin für Output-Enable (13) mit Masse verbunden sein, da dieser LOW-aktiv ist. Die folgende Tabelle zeigt die Bedeutung der Pins:


Tabelle 1: Bedeutung der Pins des Schieberegisters 74HC595
Pin Bedeutung
VCC Versorgungsspannung +5V
GND Masse 0V
QA – QH Parallele Ausgänge 1 bis 8
QH" Serieller Ausgang (Eingang für ein zweites Schieberegister)
MR Master-Reset (LOW-aktiv)
SH_CP Schieberegister Takteingang (Shift-Register clock input)
ST_CP Speicherregister Takteingang (Storage-Register clock input)
OE Ausgang aktivieren (Output enable/LOW-aktiv)
DS Serieller Eingang (Serial data input)

Die Funktionsweise des Schieberegisters kann man folgendermaßen beschreiben: Wenn der Takt am Schieberegister Takteingang SH_CP von LOW auf HIGH wechselt, wird der Pegel am seriellen Eingang DS gelesen, in eines der internen Shiftregister übertragen und zwischengespeichert. Das Speichern in diese Register bedeutet jedoch noch keinesfalls eine Übertragung zu den Ausgängen QA bis QH. Erst durch einen Taktimpuls am Speicherregister ST_CP von LOW auf HIGH werden alle Informationen der internen Shift-Register an die Ausgänge transferiert. Das ist sinnvoll, denn erst wenn alle Informationen am seriellen Eingang gelesen wurden, sollen sie an den Ausgängen erkannt werden. Den Wechsel des logischen Pegels von LOW auf HIGH nennt man Taktflankensteuerung‌, weil eine Aktion erst ausgeführt wird, wenn ein Pegelwechsel‌ in der beschriebenen Weise stattfindet.

Aber werfen wir doch mal einen Blick in das Innere des Schieberegisters und beobachten, was da vor sich geht. Hier sehen wir SH_CP bei der Arbeit. Wenn er die Fahne von LOW auf HIGH setzt, wandert der potenzielle Anwärter, der sich in der DS-Area befindet, in das nächste Shift-Register und wartet dort auf seine weitere Reise zum Ausgang.


Abb. 4: Kollege SH_CP bei der Abfertigung der seriellen Daten

Auf dem nächsten Bild siehst du ST_CP bei der Arbeit, der für das Freigeben der Daten in den internen Shift-Registern an die Ausgänge verantwortlich ist.


Abb. 5: Kollege ST_CP gibt die Daten der Shift-Register an die Ausgänge frei

Wenn er die Fahne von LOW auf HIGH setzt, öffnen sich die Türen der internen Shift-Register, und erst dann können die Daten den Weg zum Ausgang finden. Wir werden den bildlich beschriebenen Vorgang einmal in mehreren Sketchen nachbilden, damit du die Arbeitsweise des Schieberegisters live miterleben kannst. Wir fangen ganz simpel zu Fuß an, und du wirst am Ende sehen, dass es für die ganzen Aktionen, die wir hier einzeln und im Detail ausführen, einen komfortablen Befehl gibt, der dir die Arbeit abnimmt und vieles erleichtert.

Ein konventionelles Schieberegister

Den einen oder anderen mag es vielleicht interessieren, wie ein Schieberegister‌ intern aufgebaut ist und es gibt dafür zahlreiche Ansätze mit unterschiedlichen Bausteinen. Sehen wir uns hierzu ein einfaches Beispiel an. Schaltet man mehrere sogenannte Flipflops‌ (eine Schaltung, die zwei stabile Zustände annehmen kann) in Reihe hintereinander, so erhält man besagtes Schieberegister. Auf dem folgenden Schaltplan habe ich ein Schieberegister mit 4 D-Flipflops realisiert.

Informationen zum D-Flipflop sind unter der folgenden Internetadresse zu finden:


https://de.wikipedia.org/wiki/Flipflop#D-Flipflop


Abb. 6: Ein Schieberegister aus D-Flipflops

Über den Eingang Data In werden die zu übernehmenden Pegel an den Eingang angelegt und mit dem Takt mittels Clock jeweils weitergereicht. Die Bits werden nacheinander – also seriell – in die Schaltung eingeschoben. Die Ausgänge Q1 bis Q4 zeigen nun diese seriell eingeschobene Information parallel an. Parallel bedeutet, dass die Bits gleichzeitig ohne zeitliche Verzögerung an den Ausgängen zur Verfügung stehen.

Türler ve etiketler

Yaş sınırı:
0+
Hacim:
1504 s. 1241 illüstrasyon
ISBN:
9783946496298
Yayıncı:
Telif hakkı:
Bookwire
İndirme biçimi:
Metin
Ortalama puan 5, 1 oylamaya göre
Metin
Ortalama puan 0, 0 oylamaya göre