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.
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.
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.Würde man in einen einzelnen 74HC165 hineinschauen, dann wäre dort die Logik wie folgt aufgebaut:

Was in SER hinein geschoben wird, wird also bei jedem Shift zwischen den Latches A-H eins nach rechts geschoben und schließlich bei QH wieder hinaus geschoben: SER --> A -> B -> C -> D -> E -> F -> G -> H --> QH.
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?
- Am 1. Chip (rosa) sind die Taster 1 bis 8 an A bis H angelötet, was hier die Binär-Sequenz 0101 1010 ausmacht
- Gleichzeitig ist QH des 1. Chips mit SER des 2. Chips verbunden.
- Am 2. Chip (orange) sind die Taster 9 bis 16 an A bis H angelötet, was hier zufällig auch die Binär-Sequenz 0101 1010 ausmacht
- Gleichzeitig ist QH des 2. Chips mit SER des 3. Chips verbunden.
- Am 3. Chip (grün) sind die Taster 17 bis 24 an A bis H angelötet, was hier zufällig auch die Binär-Sequenz 0101 1010 ausmacht
- Gleichzeitig ist QH des 2. Chips mit Data In unseres Mikrocontollers verbunden.
- Chip 3 (grün) gibt den Zustand von seinem H (Low, 0) an den Mikroprozessor und wirft dieses Bit weg, schiebt die anderen 7 Bits nach rechts und bekommt ganz links einen freien Platz
- Chip 2 (orange) gibt den Zustand von seinem H (Low, 0) an Chip 3 weiter, schiebt ihn zusagen rechts raus und füllt damit den freien Platz des ersten Bits von Chip 3. Selbst hat Chip 2 jetzt auch eine Lücke an linkestens Bit.
- Chip 1 (rosa) gibt den Zustand von seinem H (Low, 0) an Chip 2 weiter, schiebt ihn zusagen rechts raus und füllt damit den freien Platz des ersten Bits von Chip 2. Selbst hat Chip 1 jetzt auch eine Lücke an linkestens Bit. Diese wird jetzt von niemanden mehr gefüllt.
- Chip 3 gibt sein rechtes Bit an den Microcontroller und holt sich sein neues linkes (A) von Chip 2 (das ganz rechts, H) und dieser wiederum holt sich ein Bit von Chip 1
- nach 8 Shifts haben wir alle acht Bitzustände von Chip 1 in Chip 2 gesichert und alle acht ursprünglichen Bits von Chip 2 wurden an Chip 3 weitergereicht. Chip 3 hat alles ursprüngliche an den Microcontroller weitergereicht.
- nach 16 Shifts stehen zwar die jetztigen Zustände von Chip 1 in Chip 2, aber die interessieren uns nicht mehr und werden auch nicht mehr in die MCU übertragen.
- nach 24 Shifts bekommt die MCU auch die letzten acht Bits aus Chip 3 geliefert.
Würde man jetzt zum Beispiel zweimal die Auslese-Schleife durchlaufen und aus Versehen insgesamt 48 mal shiften, würde das nichts ändern, weil die Signale ja immer noch an den Tastern anliegen. Diese kämen einfach erneut rein. Es sei denn, man lässt zuviel Zeit zwischen den Shifts vergehen oder hat einen Taster schon wieder losgelassen.
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:
- Datums- / Zeitanzeige auf 3 Arten
- Stoppuhr mit ganzen Sekunden und bis zu 49 Tage und Hunderstel Sekunden und bis 99 Stunden
- Countdown-Timer mit Alarm bei Ablauf und einigen Presets z. B. zum Eierkochen
- Schachuhr mit einstellbarer Gesamzeitzeit und Bonuszeit pro Zug, Anzeige für 2. Spieler auf dem Kopf (relativ zum 1. Spieler), so dass dieser seine Zeit besser ablesen kann.
- Würfelsimulator für 1 bis 6 Würfel und 0 bis 9 Würfe. Halten von Würfeln bei mehreren Würfeln möglich, z. B. für Yahtzee oder Coolie
- Frequenzgenerator 10 bis 9999 Hertz
- Metronom 10 bis 999 BPM
- Counter / Zähler für 2 oder 4 zu zählende Dinge/Ereignisse
- System-Test, ob alle Knöpfe richtig funktionieren
