74HC165-Schieberegister kaskadieren und mehr als 8 Taster verwalten

In meinem letzten Artikel Mit dem 74HC165-Schieberegister Eingabe-Pins und Leitungen einsparen hatte ich die grundsätzliche Funktionsweise des Parallel In Serial Out (kurz PISO) Shift Registers 74HC165N erklärt.

Heute soll es um das Kaskadieren, also das Aneinanderreihen von mehreren 165ern gehen, um mit den vier Steuerleitungen noch mehr als 8 Taster abzufragen. Man kann die Chips nämlich zusätzlich zu den Eingängen A bis H mit über SER mit Daten füttern. Hier schließt man einfach die QH-Leitung des Vorgänger-165ers an.

Das Datenblatt meint dazu:
Multiple SNx4HC165 can be cascaded together to allow more digital inputs to be interfaced with single processor by connecting output of the cascaded shift register QH to serial input SER of the SNx4HC165 and so on. Note this application does not allow the communication to be bi-direction in nature as data can only be read by the processor not written back.
Dabei muss jeder 165er mit den Steuersignalen SH/LD, CLK, CLK INH und natürlich Spannung (VCC, 3.3V) und Masse (GND) versorgt werden, denn alle 165er brauchen die Takt- und Shift-Impulse, um die Bits weiterzuschieben.

Nehmen wir an, wir hätten 24 Taster, die wir auf 3 165er verteilen, jeder einzelne kann 8 Eingänge verwalten, dann sähe ein Verschaltungsschema wie folgt aus:



Wie funktioniert das jetzt genau? Beim ersten Shift-Signal des Mikrocontollers (das ja an alle 3 Chips geht) passiert nun folgendes: Bei jedem weiteren Shift passiert dies: Würde man also mit dem Microcontroller nur 8x shiften, dann bekäme man nur die Bits von Chip 3 (grün). Bei 16x von Chip 3 (grün) und Chip 2 (orange). Shiftet man zuoft, dann bekommt man nur noch Null-Bits zurück, denn da ist nichts mehr, was geliefert werden könnte (oder es ist etwas falsch verdrahtet).

Da fällt mir ein: das letzte Mal haben wir QH und nicht QH verwendet. QH liefert den komplimentären Wert der Bitreihe (also ein binäres NOT), was ganz praktisch war, um die Bits unserer PullUp-Schaltung umzudrehen und eine gedrückte Taste mit einem 1er-Bit für ein Low darzustellen.

Von der Verwendung von QH sollte man allerdings abraten, wenn man mit kaskadierten 165ern arbeitet, denn Chip 1 liefert den Komplimentärwert an Chip 2, der aber dreht durch das QH den Wert beim Weitergeben wieder um. Ergebnis: jedes 2. Byte ist invertiert, der Rest nicht. Das kann man softwaretechnisch zwar wieder ausgleichen, aber es ist dennoch unschön.

Am besten gewöhnt man sich an, immer nur QH zu verwenden, und die Bits per binären NOT in der Software umzudrehen, wenn man das braucht.

Mein Source-Code-Teil für den 74HC165 vom letzten Mal void pulseShift() { // führt eine kurzen Pulse vor dem Auslesen der Bit-States des HC165 auf der Shift-Leitung aus digitalWrite(PinShift, LOW); delayMicroseconds(HC165_pulseLength); digitalWrite(PinShift, HIGH); delayMicroseconds(HC165_pulseLength); } void shiftRegisterRead(int anz165er, byte *states) { // anz165er=Anzahl Chips, normal 1, mehr, wenn kaskadiert. pulseShift(); // Anfangs-Status lt. Datenblatt digitalWrite(PinClockInh, HIGH); digitalWrite(PinClock, LOW); // Clock ein for (int i=0; i < anz165er; i++) { // die 8 Zustände A...H einlesen states[i] = shiftIn(PinData, PinClockInh, MSBFIRST); } digitalWrite(PinClock, HIGH); // Clock aus } bei dem ich ja schon an die Möglichkeit mehrerer kaskadierter 165er gedacht hatte, funktioniert übrigens auch mit mehreren 165er einwandfrei. Hier müssen wir also nichts ändern.

Die Schaltung



Für mein nächstes Projekt, einen Multi-Funktions-Timer habe ich mir ja in den Kopf gesetzt, ich will für jeder der 8fach Siebensegment-Anzeigen einen Taster, um damit die Ziffern auf- und abdrücken zu können und so einen Wert einfach einstellen zu können. Das Raster meiner Lochrasterplatine (1/10") stimmt dankenswerterweise mit dem Abstand der Ziffern auf der Anzeige überein. Das sieht schonmal sauber aus, wenn es auch ein wenig eng ist.

Dazu sollen noch einmal 8 Taster kommen für alle möglichen Funktionen. Wenn ich schon 8 Taster mit einem 165er verwalten kann, dann kann ich auch 8 benutzen, nicht wahr?

Da ich möglichst kurze Leitungen von den 165ern zu den Tastern wollte, habe ich die zwei 165er für die 16 Taster um das Display herum links und rechts under das Display gepackt und ohne Sockel verlötet (sonst wär mir das wieder zu hoch geworden). Unter dem Display ist noch einmal ein hervorstehender MAX7219. Mit ein wenig Klebeknete auf den 165ern und dem 7219er klebt das Display ganz gut auf die Platine. Außerdem ist es links mit den Anschlüssen festgelötet. Der dritte 165er befindet sich in der Nähe der Taster rechts.

Unten findet noch eine BluePill Platz und daneben noch ein Buzzer. Dann ist die Platine aber auch voll.

Mit einem 3D-gedruckten Gehäuse, bei dem dann die 'Taster verlängert werden und nur noch die und das Display von außen zu sehen sind, kann das bestimmt ganz gut aussehen.

Es fehlt noch eine Batteriepufferung. Mal schauen, ob ich einen 2xAAA längs finde dafür und ob der passt.



Der Platzgeiz rächt sich dann beim Löten, denn an jeden Taster müssen GND und ein 10 kΩ-Widerstand angelötet werden, macht zusammen 24 Widerstände, die auf die Rückseite müssen, plus 24 Verdrahtungen der Taster mit den 165ern plus 24 Verdrahtungen der Taster mit Ground. Plus vier Steuerleitungen, die an alle drei 165er gehen. Und dann wär da noch das Display und der Lautsprecher.

Zuerst habe ich einmal ein paar blanke Kupferdrähte (recyclet aus alter Koaxial-Antennen-Kabeln) als Massebahnen auf die Platine gelötet, in der Nähe der Taster, der jeder Taster braucht eine Ground Verbindung. Dann habe ich 3.3V Bahnen auf dieselbe Art gelegt und über die Taster an der anderen Seite über einen Widerstand an 3.3V angeschlossen.

Damit war schon mal so gut wie die Hälfte erledigt. Für den Anschluss der Taster an die 165er habe ich Flachbandkabel genommen, jeweils zu acht Drähten pro Chip. Leider sind die Flachbankkabel doch ein wenig steif gewesen, so dass das eine ziemliche Fummelei war.

Danach ging es an die Steuerleitungen für die 165er, für die ich isolierte Kabel in fünf gestreiften Farben benutzt habe.

Zum Schluss noch die BluePill verdrahtet und eine extra Leitung mit 5V für das Display gelegt. Zuerst hatte ich nur eine 3.3V-Leitung gelegt, 5V bin ich schon gar nicht mehr gewohnt. Aber damit war die Anzeige sehr schwach, wenn denn das Display überhaupt was anzeigte. Hätte ich doch noch einmal meinen eigenen Aritkel MAX7219 Treiber zur Ansteuerung von 8x8 LED-Matrix und 8fach-7Segment-Display verwenden durchgelesen. Mit jetzt 5V funktioniert es wunderbar.

Bei diesen vielen Leitungen nimmt der Test wohl auch einen nicht unerheblichen Zeitraum ein. Auch hier gilt wieder: wer sauber und gewissenhaft arbeitet, der muss später weniger Fehler beseitigen. Das ist beim Löten ganz ähnlich wie bei der Softwareentwicklung.

Bei einem Taster war mir gleich aufgefallen, dass etwas nicht stimmen konnte. Immer wenn ich ihn gedrückt hatte, führte die BluePill einen Reset aus. Und für wahr, hier hatte der eine Taster-Pin einen Kurzschluss.

Um den Rest zu testen, kann man sich einfach über den Serial Monitor die Bitfolgen ausgeben lassen, die man aus der 165er-Kaskade ausgelesen hat.

Um auch gleich die 8fach-7Segment-Anzeige zu testen, habe ich noch eine Kleinigkeit programmiert, so dass mir an den 8 Displaypositionen (oben, unten, rechts) angezeigt wird, welcher Taster gerade gedrückt wurde.

Siehe dazu auch dieser Video, zu dem ich noch ein paar zusätzliche Worte zu der Schaltung verliere:



Source-Code

// defines.h ///////////////////////////////////////////////////////// // (C) 2020 by Oliver Kuhlemann // // Bei Verwendung freue ich mich ueber Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // ///////////////////////////////////////////////////////// #define PinHC165_Shift PA4 #define PinHC165_Clock PA5 #define PinHC165_Data PA6 #define PinHC165_ClockInh PA7 #define HC165_pulseLength 5 #define PinSpeaker PA3 #define Pin7219_DIN PB3 #define Pin7219_CS PB4 #define Pin7219_CLK PB5 // main.cpp ///////////////////////////////////////////////////////// // (C) 2020 by Oliver Kuhlemann // // Bei Verwendung freue ich mich ueber Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // ///////////////////////////////////////////////////////// #include <Arduino.h> #include "defines.h" #include "74hc165.h" #include "max7219_8x7seg.h" //#include // Lib für 8fach-7Segment-Anzeige mit MAX7219 void setup() { Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); pinMode(PinHC165_Data, INPUT); pinMode(PinHC165_Clock, OUTPUT); pinMode(PinHC165_ClockInh, OUTPUT); pinMode(PinHC165_Shift, OUTPUT); pinMode(PinSpeaker, OUTPUT); segInit(); } void loop() { byte states[3]; byte disp[8] = {0,0,0,0,0,0,0,0}; while (1) { shiftRegisterRead(4, states); for (int i=0; i < 3; i++) { states[i] = ~states[i]; // bei PullUp Bits invertieren: gedrückte Tasten als 1 und ungedrückte als 0 } Serial.print(states[0], BIN); Serial.print("\t"); Serial.print(states[1], BIN); Serial.print("\t"); Serial.print(states[2], BIN); Serial.println(); //init for (int i=0; i < 8; i++) { disp[7-i] = 0x0; } //oben for (int i=0; i < 8; i++) { if (states[0] & (1 << i)) disp[7-i] |= 0x40; } //unten for (int i=0; i < 8; i++) { if (states[1] & (1 << i)) disp[7-i] |= 0x8; } //rechts for (int i=0; i < 8; i++) { if (states[2] & (1 << i)) disp[7-i] |= 0x30; } digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN)); segBin(disp); delay (50); } } // 74hc165.h ///////////////////////////////////////////////////////// // (C) 2020 by Oliver Kuhlemann // // Bei Verwendung freue ich mich ueber Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // ///////////////////////////////////////////////////////// void pulseShift() { // führt eine kurzen Pulse vor dem Auslesen der Bit-States des HC165 auf der Shift-Leitung aus digitalWrite(PinHC165_Shift, LOW); delayMicroseconds(HC165_pulseLength); digitalWrite(PinHC165_Shift, HIGH); delayMicroseconds(HC165_pulseLength); } void shiftRegisterRead(int anz165er, byte *states) { // anz165er=Anzahl Chips, normal 1, mehr, wenn kaskadiert. pulseShift(); // Anfangs-Status lt. Datenblatt digitalWrite(PinHC165_ClockInh, HIGH); digitalWrite(PinHC165_Clock, LOW); // Clock ein for (int i=0; i < anz165er; i++) { // die 8 Zustände A...H einlesen states[i] = shiftIn(PinHC165_Data, PinHC165_ClockInh, LSBFIRST); } digitalWrite(PinHC165_Clock, HIGH); // Clock aus } // max7219_8x7seg.h ///////////////////////////////////////////////////////// // (C) 2020 by Oliver Kuhlemann // // Bei Verwendung freue ich mich ueber Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // ///////////////////////////////////////////////////////// // lib_deps = LedControl // https://github.com/wayoda/LedControl // http://wayoda.github.io/LedControl/pages/software #include <LedControl.h> // Lib für 8fach-7Segment-Anzeige mit MAX7219 LedControl lc=LedControl(Pin7219_DIN, Pin7219_CLK, Pin7219_CS, 1); // Anzeige initialisieren // 7 = DIN // 5 = CLK // 6 = CS / LOAD // 1 = 1 MAX7291 void segInit() { lc.shutdown(0,false); // MAX7219 aufwecken (schläft zu Beginn) lc.setIntensity(0,5); // Helligkeit setzen (0 bis 15) lc.clearDisplay(0); // Display leeren } void segText(String strg) { // schreibt String bis 8 Zeichen auf Segmentanzeige, Punkte werden eingeschoben unsigned int siz=strg.length(); char c; int p; int i; p=7; i=0; while (p>=0) { // nächster Buchstabe ein Punkt, Komma, Doppelpunkt? if (i < siz-1) { c=strg[i+1]; if (c=='.' || c==',' || c=='.' || c==':' || c==';') { lc.setChar(0,p,strg[i],true); siz++; i+=2; p--; continue; } } if (i < siz) { lc.setChar(0,p,strg[i],false); } else { lc.setChar(0,p,' ',false); } i++; p--; } } void segBin (uint8_t* bin) { // schreibt 8 Zeichen mit Segmentdefinitionen auf Segmentanzeige, (Punkte durch +0x80) uint8_t c; int p; int i; p=7; i=0; while (p>=0) { c=bin[i]; lc.setRow(0,p,c); i++; p--; } }

Der MultiTimer ist fertig

Natürlich blieb die Software für die Platine nicht bei einem einfachen Test-Programm, sondern wurde mit allen möglichen Funktionen nach und nach erweitert. Die stundenlange Löterei soll sich ja schließlich gelohnt haben.

Und so sieht das gute Stück nun aus:



und bietet (bisher) folgende Funktionen: Für alle, die das Teil mal in Aktion sehen wollen (und auch als kleine Gedächtnisstütze für mich, wie der MultiTimer zu bedienen ist), habe ich ein kleins Video gemacht: