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

Yazı tipi:

Bastelprojekt 8:
Die Port-Erweiterung 2.0

Es kann der Fall sein, dass sogar die Porterweiterung mit dem Schieberegister 74HC595 und den vorhandenen Ausgängen für ein Projekt nicht ausreichend ist und mehr benötigt werden. Es gibt eine Möglichkeit, noch mehr Pins als Ausgänge zur Verfügung zu stellen. Wir haben gesehen, dass der 74HC595 nur über die Ausgangspins QA bis QH verfügt und das sind genau acht. Bei genauerer Betrachtung fällt noch ein weiterer Pin auf, der möglicherweise auch etwas mit einem Ausgang zu tun hat.

Digitale Porterweiterung 2.0

Das Schieberegister 74HC595 hat neben den acht parallelen Ausgängen einen weiteren, der für uns bisher keine Rolle gespielt hat. Vielleicht ist er dir schon aufgefallen, doch du hattest keinen Grund, danach zu fragen. Werfen wir noch einmal einen Blick auf die Pin-Belegung des Schieberegisters:


Abb. 1: Die Pin-Belegung des Schieberegisters 74HC595‌ (Ausgangspins)

Ich habe die normalen Ausgänge Pin QA bis QH gelb gekennzeichnet und den, auf den es jetzt ankommt, grün. Er hat die Bezeichnung QH" und eine besondere Aufgabe. Es handelt sich dabei um den seriellen Ausgang für ein weiteres Schieberegister‌, das diesen Pin als seriellen Eingang nutzt. Du kannst diese Funktionalität dazu nutzen, mehrere Schieberegister hintereinander zu schalten, um so eine theoretisch unbegrenzte Anzahl von Ausgängen zu erhalten. In der folgenden Grafik können wir die Verbindung beider Bausteine sehen. Sie reicht vom seriellen Ausgang QH" des ersten Schieberegisters bis zum seriellen Eingang DS des zweiten Schieberegisters.

Die Anschlüsse von ST_CP beziehungsweise SH_CP werden einfach von beiden ICs parallel zusammengeschaltet und wie bisher genutzt. Master-Reset und Output-Enabled werden die gleichen festen Potentiale wie bei der Schaltung mit nur einem Schieberegister zugewiesen.


Abb. 2: Die Kaskadierung zweier Schieberegister

Was wir brauchen

Für dieses Bastelprojekt benötigen wir folgende Bauteile:


Tabelle 1: Bauteilliste
Bauteil Bild
Schieberegister 74HC595 2x
LED rot 16x
Widerstand 330Ω 16x
Widerstand 10KΩ 1x
Mikrotaster 1x

Der Schaltplan

Der Schaltplan zeigt uns die 16 LEDs mit ihren 330Ω-Vorwiderständen, die durch die beiden 74HC595-Schieberegister angesteuert werden:


Abb. 3: Das Arduino-Board steuert über drei Signalleitungen die beiden Schieberegister 74HC595 an

Wir sehen außerdem, dass von beiden Schieberegistern die Pins SH_CP und ST_CP parallel angesteuert werden und Master-Reset sowie Output-Enabled die gleichen festen Potentiale zugewiesen wurden. Der serielle Ausgang QH" des ersten Registers ist mit dem seriellen Eingang DS des zweiten Registers verbunden.

Der Schaltungsaufbau

Natürlich kann man die Schaltung auf einem Breadboard aufbauen, doch ich habe mich dazu entschieden, sie auf einer Platine‌ im Eurokartenformat (160mm x 100mm) aufzubauen und aufzulöten. Zusätzlich habe ich die digitalen Ausgänge, die zu den LEDs führen, noch mit Buchsen versehen, um darüber auch andere elektronische Bausteine anzusteuern. Den Reset-Taster habe ich in diesem Beispiel weggelassen, er kann aber bei Bedarf natürlich hinzugefügt werden:


Abb. 4: Der Schaltungsaufbau mit zwei 74HC595-Schieberegistern

Der Arduino-Sketch

Der Sketch-Code erfordert ein erweitertes Basiswissen bezüglich der Bit-Manipulation, die wir bereits im Bastelprojekt 6 kennengelernt haben. Zunächst jedoch erst einmal der Code:

int taktPin = 8; // SH_CP int speicherPin = 9; // ST_CP int datenPin = 10; // DS // Funktion zum Übertragen der Informationen void sendeBytes(int wert) { digitalWrite(speicherPin, LOW); shiftOut(datenPin, taktPin, MSBFIRST, wert >> 8); shiftOut(datenPin, taktPin, MSBFIRST, wert & 255); digitalWrite(speicherPin, HIGH); } void setup() { pinMode(taktPin, OUTPUT); pinMode(speicherPin, OUTPUT); pinMode(datenPin, OUTPUT); } void loop() { sendeBytes(0B0110011001000101); // Zu übertragende Binärzahl = 26181(dez) }

Auf der folgenden Abbildung ist zu erkennen, wie die LEDs entsprechend der übertragenen Binärzahl aufleuchten:


Abb. 5: Die LEDs auf der 74HC595-Platine

Den Code verstehen

Zu Beginn werden wieder die Variablen mit den benötigten Pin-Informationen versorgt und am Anfang der setup-Funktion alle Pins als Ausgänge programmiert. Kommen wir zum eigentlichen und wichtigen Thema der Bit-Manipulation‌. Zuerst eine kurze Wiederholung dessen, was du schon gelernt hast. Die acht Ausgänge eines einzelnen Schieberegisters repräsentieren die acht Bits eines einzelnen Bytes:


Abb. 6: Die acht Ausgänge eines Schieberegisters

Mit diesen 8 Bits (1 Byte) kannst du 28 = 256 verschiedene Bit-Kombinationen darstellen. Wenn wir also Zahlenwerte von 0 bis 255 über die shiftOut-Funktion an das Schieberegister schicken, erreichen wir damit alle Ausgänge (QA bis QH). Haben wir jedoch aufgrund der Kaskadierung zweier Schieberegister‌ doppelt so viele Ausgänge, stehen uns statt 8 nunmehr 16 Bits zur Verfügung. Das ist eine Bandbreite von 216 = 65.536 Bit-Kombinationen. Mit den bisherigen Werten von 0 bis 255 kannst du aber nicht die zusätzlichen 8 Bits erreichen:


Abb. 7: Die 16 Ausgänge zweier Schieberegister

Ich habe in der Darstellung die Ausgänge ein wenig umgruppiert, denn die Stelle mit dem niedrigsten Wert befindet sich in der Regel ganz rechts und die mit dem höchsten Wert ganz links. Daher werden jetzt die Daten von rechts in das erste Schieberegister geschoben und wandern nach links bis in das zweite Register.


Wenn du dir den Sketch-Code ansiehst, könntest du sagen, dass er ziemlich kompliziert aussieht. Du musst jedoch lediglich dem shiftOut-Befehl den zu übertragenden Wert von 26181 als Argument übergeben, und die beiden Schieberegister werden mit dem Wert initialisiert. Also ich meine das beispielsweise so:

shiftOut(datenPin, taktPin, MSBFIRST, 26181);

Im Ansatz ist diese Überlegung vollkommen korrekt, doch an einer Stelle hakt es. Der shiftOut‌-Befehl kann nur ein einzelnes Byte in Richtung Schieberegister übertragen und ist mit einem Wert >255 überfordert. Aus diesem Grund müssen wir einen Trick anwenden. Betrachten wir die Zahl 26181 ab jetzt ausschließlich als Binärzahl, denn damit arbeitet ja der Mikrocontroller:


Abb. 8: Die Dezimalzahl 26181 als 16-Bit-Binärzahl

Die Übertragung muss, wie schon im Sketch zu sehen war, in zwei separaten Schritten erfolgen. Zuerst wird das höherwertige, anschließend das niederwertige Byte mit dem shiftOut-Befehl übertragen. Erst im Anschluss wird der Speicher-Pin (Signal: ST_CP) auf HIGH-Level gesetzt. Wie aber separieren wir die 2x8-Bit-Informationen aus dem 16-Bit-Wort?

Was ist ein Nibble‌?


Der Datenverbund von 4 Bits wird in der Programmierung Nibble genannt. Das nächsthöhere Datenpaket von 8 Bits nennen wir ein Byte. Bei einer Datenbreite von 16 Bits haben wir es mit einem Wort zu tun.

Wir müssen uns der bitweisen Operatoren bedienen, die es uns ermöglichen, einzelne oder mehrere Bits einer Zahl bequem zu modifizieren beziehungsweise anzusprechen. Ich beginne mit dem logischen Verschieben‌ (engl. Shift‌). Die folgende Grafik zeigt das Verschieben‌ der einzelnen Bits eines Bytes um eine Stelle nach rechts:


Abb. 9: Logische Verschiebung nach rechts

Drei wesentliche Dinge sind hierbei erwähnenswert:

 Alle Bits wandern eine Position weiter nach rechts.

 Auf der linken Seite wird eine 0 an die Stelle des Bits mit dem höchsten Wert (MSB = Most Significant Bit) eingefügt, denn die freiwerdende Stelle muss auch mit einen definierten Wert versehen werden.

 Auf der rechten Seite ist kein Platz mehr für das vormalige Bit mit dem niedrigsten Wert (LSB = Least Significant Bit). Es wird in diesem Fall nicht mehr benötigt und verschwindet im Nirwana.

Diese Operation wird mit dem Shift-Operator‌ >>‌ durchgeführt.


Abb. 10: Der Shift-Operator für die logische Verschiebung nach rechts

Die beiden Pfeile weisen nach rechts, das bedeutet, dass alle Bits in diese Richtung verschoben werden. Operand 2 gibt dabei vor, um wie viele Stellen Operand 1 nach rechts verschoben werden soll. Hier ein Beispiel:

byte wert = 0b01000101; // Dezimalzahl 69 void setup() { Serial.begin(9600); Serial.println(wert >> 1, BIN); } void loop(){ /* leer */ }

Die Ausgabe im Serial Monitor lautet 100010. Lass dich nicht verunsichern, denn führende Nullen werden nicht mit ausgegeben. Du siehst übrigens, dass bei der println-Funktion über einen zusätzlichen Parameter gesteuert werden kann, in welchem Format der Wert auszugeben ist. BIN bedeutet binär und deshalb wird dir der Wert nicht in dezimaler, sondern in binärer Form angezeigt. Schau in der Referenz nach, um dich über weitere Optionen zu informieren. Der Wert 1 hinter dem Verschiebeoperator gibt an, um wie viele Stellen verschoben werden soll. Ist es eigentlich auch möglich, Dezimalzahlen mit bitweisen Operatoren zu manipulieren? Eine berechtigte Frage! Da der Mikrocontroller – wie du ja schon weißt – nur mit Einsen und Nullen umgehen kann, behandelt er Dezimalzahlen von Haus aus wie Binärzahlen. Die Antwort lautet also: Ja!

Doch kommen wir zum eigentlichen Thema zurück. Wir müssen aus der 16-Bit-Zahl das Byte mit dem höchsten Wert extrahieren und als 8-Bit-Wert darstellen. Kannst du dir vorstellen, wie das funktionieren soll? Du musst lediglich die 16 Bits um 8 Stellen nach rechts verschieben. Danach befinden sich die acht Bits des Bytes mit dem höchsten Wert an der Stelle des Bytes mit dem niedrigsten Wert. Das wird mit der folgenden Codezeile erreicht:

shiftOut(datenPin, taktPin, MSBFIRST, wert >> 8);

Alle Bits des vormals niederwertigen Bytes (hier dunkelblau) gehen dabei verloren. Auf der linken Seite werden Nullwerte eingeschoben (hier grün). Wir verändern jedoch nicht den eigentlichen Ursprungswert der Variablen wert, sodass die vermeintlich verloren geglaubten Bits immer noch für die nächste Operation zur Verfügung stehen.


Abb. 11: Logische Verschiebung um acht Stellen nach rechts

Der zweite Schritt besteht in der Extrahierung des Bytes mit dem niedrigsten Wert. Für diese Aktion benötigen wir den bitweisen Operator AND‌, der durch das Kaufmanns-Und‌ (&)‌ repräsentiert wird. Um nur bestimmte Bits zu berücksichtigen, wird eine Art Schablone oder Maske über den ursprünglichen Wert gelegt. Wir verwenden dazu folgende Codezeile:

shiftOut(datenPin, taktPin, MSBFIRST, wert & 255);

Der dezimale Wert 255 ist gleichbedeutend mit der Binärzahl 11111111, die als Maske dient. Schau dir die folgende Wahrheitstabelle an, die die logischen Zustände von A bzw. B und deren Verknüpfungsergebnis angibt:


Tabelle 2: Wahrheitstabelle für bitweise Und-Verknüpfung
A B A&B
0 0 0
0 1 0
1 0 0
1 1 1

Das Ergebnis ist nur dann 1, wenn beide Operanden den Wert 1 besitzen.


Abb. 12: Der Wert 255 dient als Maske für die Filterung der unteren acht Bits

Das höherwertige Byte wird bei dieser Operation nicht berücksichtigt, denn die Informationen werden durch die in der Grafik symbolisch dargestellte Lochmaske geblockt.

Troubleshooting

Falls die LEDs nicht oder nur teilweise leuchten, trenn das Board sicherheitshalber vom USB-Anschluss und überprüfe Folgendes:

 Überprüf deine Steckverbindungen auf dem Breadboard darauf, 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?

 Hast du die Schieberegister richtig verkabelt? Kontrollier noch einmal alle Verbindungen, die ja zahlreich sind. Wichtig sind auch die festen Potentiale von Master-Reset und Output-Enabled.

 Überprüfe noch einmal den Sketch-Code auf Richtigkeit. Verbinde das Board wieder mit der USB-Schnittstelle und führe einen kompletten Funktionstest aller LEDs durch. Schalte sie alle an, mach eine Pause und schalte alle wieder aus. Platziere den folgenden Code innerhalb der loop-Funktion:sendeBytes(0b1111111111111111); delay(300);sendeBytes(0b0000000000000000); delay(300);Dieser Test sollte erfolgreich verlaufen, andernfalls hast du ein Problem mit der Verkabelung. Auf jeden Fall wünsche ich dir eine erfolgreiche Fehlersuche!

Was haben wir gelernt?

 Du hast das Schieberegister vom Typ 74HC595 kaskadiert und derart miteinander verbunden, dass du 16 digitale Ausgänge bekommen hast.

 Über die Bit-Manipulation hast du gesehen, wie einzelne Werte manipuliert beziehungsweise gefiltert werden können.

 Da ist zum einen der Shift-Operator, der die Bits sowohl nach rechts als auch nach links verschieben kann ...

 ... und zum anderen der logische Und-Operator, der zum Maskieren einzelner Bits genutzt werden kann.

Der Bit-Manipulations-Workshop

In diesem Workshop wollen wir ein wenig mit der Bit-Manipulation spielen. Ich erwähnte ja schon, dass es noch weitere Operatoren gibt, und wir wollen mal sehen, was man damit anstellen kann.

Der Shift-Operator

Neben dem Shift-Operator‌ >>, der die Bits nach rechts schiebt, gibt es noch den Operator, der für das Verschieben nach links verantwortlich ist. Er wird ebenfalls durch einen Doppelpfeil << repräsentiert, der im Vergleich zum Shift-Operator in die entgegengesetzte Richtung weist. Mit diesem Wissen solltest du in der Lage sein, einen Sketch zu schreiben, bei dem beispielsweise eine einzelne LED endlos vor- und zurückwandert. Ich hatte kurz das Glück, den Shift-Left-Operator‌ bei seiner anspruchsvollen Aufgabe zu beobachten:


Abb. 13: Der Shift-Left-Operator bei der Arbeit

Wir sehen, dass er auf der rechten Seite eine 0 einschiebt und auf der linken eine 1 herunterfällt. Es würde in diesem Fall auch kein Zurückschieben mehr nützen, denn die 1 ist vom Tisch und verloren. Ähnlich verhält es sich mit dem Shift-Operator >>, der nach rechts schiebt.

Der NOT-Operator‌

Kommen wir jetzt zu einem Operator, der sich auf alle Bits gleichermaßen auswirkt. Es handelt sich dabei um den bitweisen NOT-Operator. Er invertiert alle Bits: Aus 0 wird 1 und aus 1 eine 0.


Tabelle 3: Wahrheitstabelle für bitweise NOT-Verknüpfung
A ~A
0 1
1 0

In der folgenden Abbildung habe ich das 16-Bit-Wort mit dem NOT-Operator verknüpft. Du siehst, dass jedes einzelne Bit getoggelt wird:


Abb. 14: Anwendung des NOT-Operators auf ein 16-Bit-Wort

Du kannst zum Beispiel den Test-Sketch von eben ein wenig umschreiben, damit alle LEDs blinken. Um das Ganze etwas flexibler handhaben zu können, habe ich das Bitmuster in die globale Variable bitMuster ausgelagert:

//... int bitMuster; // Globale Bit-Muster-Variable void setup() { //... bitMuster = 0b1111111111111111; // Initialisierung der // Bit-Muster-Variablen } void loop() { sendeBytes(bitMuster); // Senden des Bit-Musters an die Schieberegister bitMuster = ~bitMuster; // Bitweises NOT delay(300); // 300ms Pause }

In der loop-Funktion wird das Bit-Muster angezeigt, im nächsten Schritt invertiert und dann eine kleine Pause eingelegt, um den Wechsel für das Auge sichtbar zu machen. Experimentiere ein wenig mit den Bit-Mustern. Du kannst interessante Effekte erzielen. Hier ein paar Beispiele:

bitMuster = 0b1010101010101010; bitMuster = 0b1111111100000000; bitMuster = 0b1100110011001100; bitMuster = 0b1111000000001111;

Natürlich kannst du auch mehrere unterschiedliche Bit-Muster hintereinander anzeigen. Den Möglichkeiten sind hier keine Grenzen gesetzt.

Der UND-Operator‌

Den UND-Operator hatten wir eben schon erwähnt. Er wird meistens benutzt, um mit einer Maske bestimmte Bits herauszufiltern oder zu ermitteln, ob ein bestimmtes Bit in einem Wert gesetzt ist. Letzteres wollen wir uns genauer anschauen. Nehmen wir an, ich möchte aus irgendeinem Grund wissen, ob das Bit an der Stelle 26 gesetzt ist:


Abb. 15: Überprüfung, ob Bit gesetzt ist

Wir erstellen dafür eine Maske‌, die nur an der interessanten Stelle die Information des zu überprüfenden Wertes durchlässt. In unserem Fall ist das die Stelle 26 mit dem dezimalen Wert 64. Die Überprüfung sieht folgendermaßen aus:

int wert, maske; void setup() { Serial.begin(9600); wert = 0b0110011001000101; // Zu überprüfender Wert maske = 0b0000000001000000; // Bit-Maske if((wert & maske) == maske) Serial.println("Bit ist gesetzt."); else Serial.println("Bit ist nicht gesetzt."); } void loop() { /*leer*/ }

Wenn das Ergebnis des Vergleichs mit dem des Maskenwertes übereinstimmt, ist das zu überprüfende Bit gesetzt, andernfalls nicht. Die Ausgabe im Serial Monitor zeigt in unserem Beispiel, dass das Bit gesetzt ist.

Der ODER-Operator‌

Möchtest du ein einzelnes oder auch mehrere Bits an unterschiedlichen Stellen setzen, ist der ODER-Operator die erste Wahl. Ein Blick auf die Wahrheitstabelle zeigt uns, dass das Ergebnis 1 ist, sobald nur einer der Operanden den Wert 1 aufweist:


Tabelle 4: Wahrheitstabelle für bitweise Oder-Verknüpfung
A B A|B
0 0 0
0 1 1
1 0 1
1 1 1

Das ODER-Zeichen‌ wird durch den senkrechten (Pipe‌-)Strich repräsentiert. Wenn du beispielsweise das Bit an der Position 21 setzen möchtest, verwende die folgende Maske:


Abb. 16: Setzen eines einzelnen Bits

Die Maske hat lediglich an der Position 21 eine 1, was bedeutet, dass bei einer ODER-Verknüpfung nur an dieser einen Stelle eine mögliche Veränderung gegenüber 1 stattfindet. Ich sage absichtlich »mögliche«, da der Bitwert an dieser Stelle vielleicht vor der Verknüpfung schon 1 war. Dann erfolgt natürlich keine Änderung. An den Stellen, an der die Maske eine 0 aufweist, ändert sich nichts.

int wert, maske; void setup() { Serial.begin(9600); wert = 26181; // Ausgangswert 26181 maske = 2; // Bitmaske = 0000000000000010 Serial.println(wert | maske); // Ergebnis = 26183 } void loop(){ /*leer*/ }

Ücretsiz ön izlemeyi tamamladınız.

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