Arduino 64-Tasten-Prototyp-Keyboard-Matrix – Eigenes Keyboard mit Schieberegistern bauen
Komponenten und Zubehör
| Komponente | Anzahl |
|---|---|
| Arduino UNO | 1 |
| 1N4148 – Schnelle Schalt-Dioden | 64 |
| Taktile Schalter 6x6x5mm (Top-Aktuierung) | 64 |
| Stiftleisten (Pin Header) 1x8 | 2 |
| 74HC595 Schieberegister | 1 |
Benötigte Werkzeuge
- Lötkolben
Apps und Online-Dienste
- Arduino IDE
Projektbeschreibung
Update: Code wurde verbessert – siehe „Update 1/30/21“ am Ende.
Ich arbeite an einem Projekt mit integriertem Keyboard, was ein Problem aufwarf: Wie binde ich eine Tastatur in das Entwicklungsboard-Prototyp ein? USB-Keyboards oder bestehende Arduino-Keyboards kommen nicht infrage, da das Keyboard im Endprojekt direkt an den Mikrocontroller angebunden ist, der alle anderen Funktionen übernimmt. Deshalb entwarf ich diese grundlegende, PCB-basierte 64-Tasten-Prototyp-Keyboard-Matrix.
Die PCB enthält keine ICs. Reihen und Spalten der Matrix sind direkt mit Stiftleisten verbunden, sodass das Keyboard an einen Arduino oder jeden anderen Mikrocontroller angeschlossen werden kann. Perfekt für Prototypen mit integrierter Tastatur.
Der mitgelieferte, ausführlich kommentierte Code funktioniert mit jedem Arduino-kompatiblen Board mit ausreichend I/O-Pins – 11 Pins werden benötigt. Das Keyboard hat 64 Tasten inklusive Modifier für Shift, Caps, Ctrl, Alt, Fn und „Special“. Zusätzlich sechs frei belegbare Tasten. Jede einzelne Taste kann für jeden Modifier-Zustand individuell definiert werden – deutlich flexibler als existierende Keyboard-Bibliotheken, die die Anpassung stark einschränken.
Der Code gibt Text über Serial aus. Dies lässt sich leicht anpassen (z. B. Ausgabe auf OLED-Display).
Hinweis zur Programmgröße:
Der Code ist recht groß, da er ohne externe Bibliotheken auskommt und vollständige Flexibilität bietet. Auf einem Arduino UNO belegt er 9100 Bytes (28%) Programmspeicher und 394 Bytes (19%) RAM. Er könnte effizienter sein, aber nur so war die gewünschte Tasten-Granularität erreichbar. Realistisches Tastaturverhalten ist implementiert: Shift + Caps Lock → Kleinbuchstabe. FN+ESC macht standardmäßig nichts, ist aber frei konfigurierbar.
Lieferumfang (Selbstbeschaffung):
Schritt 1: Wie eine Keyboard-Matrix funktioniert
Warum eine Matrix? 64 Tasten direkt anzuschließen bräuchte 64 I/O-Pins – mehr als die meisten Boards haben. Eine Matrix reduziert die Pin-Anzahl auf Wurzel der Tastenanzahl (aufgerundet). Hier: 8 Zeilen × 8 Spalten = 16 Pins. Mit Schieberegister für die Zeilen sparen wir 5 Pins → nur 11 Pins nötig.
Scan-Prinzip: Alle Zeilen normalerweise HIGH (Pullups an Spalten halten diese HIGH). Zum Scannen wird eine Zeile auf LOW gezogen. Wird ein Taster in dieser Zeile gedrückt, zieht er die zugehörige Spalte auf LOW → Taste erkannt. Zeile wieder HIGH, nächste Zeile LOW. Der komplette Scan passiert sehr schnell (hier 200×/Sekunde).
Dioden, Ghosting & N-Key-Rollover: Die Dioden (ein Stromrichtung) verhindern „Ghosting“: Ohne Dioden können bestimmte Tastenkombinationen eine nicht gedrückte Taste scheinbar aktivieren (Strom fließt über benachbarte Schalter). Dioden unterbinden das und ermöglichen N-Key-Rollover – beliebig viele Tasten gleichzeitig ohne Fehler.
Pins sparen mit Schieberegister (74HC595): Statt 8 Zeilen-Pins am Arduino nutzen wir 3 Pins (Data, Clock, Latch), um über das Schieberegister 8 Ausgänge zu steuern. Ein Byte wird hineingeschoben, die 8 Ausgänge setzen sich entsprechend. Für Eingänge (Spalten) bräuchte man ein anderes Schieberegister (PISO), was Komplexität, Rauschen und Prell-Probleme bringt – hier verzichtet.
Schritt 2: PCB-Design
Schaltplan-Design (KiCad): Taster- und Dioden-Symbole im 8×8-Grid platziert, zwei 1x8 Stiftleisten-Symbole für Zeilen/Spalten. Eine Tasterseite in Spalten, die andere in Zeilen verbunden.
Footprints & Pinnummern: KiCad-Footprint-Bibliothek genutzt. Kritisch: Pinnummern des Dioden-Symbols stimmen nicht mit Footprint-Pins überein → Dioden werden verpolt bestückt. Beim ersten PCB-Lauf passiert → Ausschuss. Lösung: Eigenes Dioden-Footprint mit getauschten Pinnummern erstellen.
PCB-Layout: Board-Umriss in Fusion 360 gezeichnet, als DXF exportiert, in KiCad (Edge.Cuts) importiert. Tasten layout-technisch an echtes Keyboard angelehnt. Leiterbahnen geroutet – durch unregelmäßiges Tastenlayout teils Vias nötig (2-Lagen-Board). Kupferflächen aufgefüllt (Good Practice).
PCB-Fertigung: Alle Layer als Gerber geplottet, als ZIP gepackt. Direkt bei JLCPCB & Co. hochladbar. Gerber-Files Download
Schritt 3: PCB-Bestückung
Einfach, aber fleißig: Alle Bauteile durchkontaktiert (THT), leicht zu löten. Dioden-Polarität beachten! Markierung auf Diode = Markierung auf PCB. Empfehlung: PCB in Dritte-Hand spannen, alle Dioden einstecken, umlöten, Beinchen abschneiden. Dann alle Taster, dann Stiftleisten. Stecker auf Unterseite → direkt in Steckplatine steckbar.
Schritt 4: Keyboard an Arduino anschließen
Spalten (8 Kabel) → Arduino:
- Spalte 1 → A0
- Spalte 2 → A1
- Spalte 3 → A2
- Spalte 4 → A3
- Spalte 5 → A4
- Spalte 6 → A5
- Spalte 7 → Pin 5
- Spalte 8 → Pin 6
74HC595 auf Steckplatine (Kerbe = Pin 1 beachten!). 5V und GND laut Pinout (zwei Pins je 5V, zwei Pins GND).
3 Steuerleitungen Schieberegister → Arduino:
- Shift (Clock) Pin 11 → Arduino Pin 4
- Shift (Latch) Pin 12 → Arduino Pin 3
- Shift (Data) Pin 14 → Arduino Pin 2
Schieberegister-Ausgänge (Q0–Q7) → Zeilen-Pins (Achtung: untypische Reihenfolge!):
- Zeile 1 → Q0 (Pin 15)
- Zeile 2 → Q1 (Pin 1)
- Zeile 3 → Q2 (Pin 2)
- Zeile 4 → Q3 (Pin 3)
- Zeile 5 → Q4 (Pin 4)
- Zeile 6 → Q5 (Pin 5)
- Zeile 7 → Q6 (Pin 6)
- Zeile 8 → Q7 (Pin 7)
Arduino Pin 0 & 1 (Serial) bleiben frei.
Schritt 5: Arduino-Code flashen
Code wie gewohnt hochladen. Alles detailliert kommentiert. Hauptloop: Timer ruft alle 5ms Tastatur-Scan auf. Dieser setzt Schieberegister pro Zeile, prüft alle Spalten. Gedrückte Tasten → Serial.print.
Tastenbelegung ändern: Ersetzen Sie Serial.print("X"); im entsprechenden if-Block. Z. B. FN+N individuell definieren. Jede Taste mit jedem Modifier anpassbar.
Viele Tasten tun hier nichts (z. B. Backspace), da Serial-Monitor keine Löschung unterstützt. Für eigene Projekte anpassbar.
Einbindung in eigene Projekte: Ersetzen Sie alle Serial.print( durch z. B. display.print( (Arduino IDE: „Ersetzen in allen Dateien“). So lässt sich das Keyboard als Eingabe für komplexere Projekte nutzen.
Update 30.01.2021 – Vollständig überarbeiteter Code (V1.2)
Der neue Code ist komplett neu geschrieben und performanter. Das ursprüngliche Problem: Algorithmus prüfte, ob eine Taste die zuletzt gedrückte war. Bei gleichzeitig gedrückten Tasten ergab sich „fgfgfgfg...“, und schnelles Wiederholen derselben Taste (z. B. „mm“ in „bummer“) funktionierte nicht.
Lösung: Statt „letzte Taste“ wird der gesamte Tastaturzustand mit dem Zustand des vorherigen Loops verglichen. Vorteile:
- Loop läuft viel schneller
- Schnelles Wiederholen derselben Taste funktioniert
- Eleganterer Code
Alle Zeichen in Arrays am Codeanfang – leicht auffindbar und änderbar. Unabhängige Arrays für jeden Modifier. Code deutlich kürzer.
Nachteil: Mehr dynamischer Speicher (RAM), aber deutlich weniger Programmspeicher (Flash). Auf Arduino Uno: 3532 Bytes (10%) Flash, 605 Bytes (29%) RAM.
Bonus: Funktioniert gleich gut auf schnellen MCUs (ARM Cortex-M4). Intervall in Mikrosekunden → gleiche Performance auf jedem Board. Standard: Loop alle 500µs, 8 Loops für kompletten Scan = 4000µs (4ms, 250 Scans/Sek). Anpassbar.
Code
ProtoKeyboardV1.1-Shifted.ino (Original, nur Download)
// Kein Preview verfügbar – bitte Datei herunterladen
ProtoKeyboardV1-Bits.ino (Aktualisierter Code V1.2)
/* Sketch für Prototyping Keyboard V1.2
* by Cameron Coward, 30.01.2021
* Getestet auf Arduino Uno. Benötigt custom PCB & 74HC595.
* Mehr Infos: https://www.hackster.io/cameroncoward/64-key-prototyping-keyboard-matrix-for-arduino-4c9531
*/
const int rowData = 2; // Shift Register Data (Zeilen)
const int rowLatch = 3; // Shift Register Latch (Zeilen)
const int rowClock = 4; // Shift Register Clock (Zeilen)
// Spalten-Eingänge (Pin 0 & 1 = TX/RX → nicht verwendet)
const int colA = A0;
const int colB = A1;
const int colC = A2;
const int colD = A3;
const int colE = A4;
const int colF = A5;
const int colG = 5;
const int colH = 6;
// Schieberegister-Byte pro Zeile (eine 0 = aktive Zeile, Rest 1)
const byte shiftRow[] = {
B01111111, B10111111, B11011111, B11101111,
B11110111, B11111011, B11111101, B11111110
};
byte rowState[8] = {0}; // Aktueller Zustand je Zeile (8 Bits = 8 Spalten)
byte prevRowState[8] = {0}; // Vorheriger Zustand
// ASCII-Codes für Tasten OHNE Modifier (Modifier = 0, da separat geprüft)
const char key[] = {
0, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 61, 0, 9,
113, 119, 101, 114, 116, 121, 117, 105, 111, 112, 91, 93, 92, 7,
97, 115, 100, 102, 103, 104, 106, 107, 108, 59, 39, 0, 0,
122, 120, 99, 118, 98, 110, 109, 44, 46, 47, 0, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0
};
// Shift + Caps aktiv
const char capsShiftKey[] = {
0, 33, 64, 35, 36, 37, 94, 38, 42, 40, 41, 95, 43, 0, 9,
113, 119, 101, 114, 116, 121, 117, 105, 111, 112, 123, 125, 124, 7,
97, 115, 100, 102, 103, 104, 106, 107, 108, 58, 34, 0, 0,
122, 120, 99, 118, 98, 110, 109, 44, 46, 47, 0, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0
};
// Shift gedrückt
const char shiftKey[] = {
0, 33, 64, 35, 36, 37, 94, 38, 42, 40, 41, 95, 43, 0, 9,
81, 87, 69, 82, 84, 89, 85, 73, 79, 80, 123, 125, 124, 7,
65, 83, 68, 70, 71, 72, 74, 75, 76, 58, 34, 0, 0,
90, 88, 67, 86, 66, 78, 77, 44, 46, 47, 0, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0
};
// Ctrl gedrückt
const char ctrlKey[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0
};
// Special (spcl) gedrückt
const char spclKey[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
// Alt gedrückt
const char altKey[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
// Fn gedrückt
const char fnKey[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
// Caps Lock aktiv
const char capsKey[] = {
0, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 61, 0, 9,
81, 87, 69, 82, 84, 89, 85, 73, 79, 80, 91, 93, 92, 7,
65, 83, 68, 70, 71, 72, 74, 75, 76, 59, 39, 0, 0,
90, 88, 67, 86, 66, 78, 77, 44, 46, 47, 0, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0
};
long previousKeyboardMicros = 0;
long keyboardInterval = 500; // µs zwischen Scans
int rowToCheck = 0; // Eine Zeile pro Loop-Durchlauf
bool caps = false;
bool shift = false;
bool capsShift = false;
bool ctrl = false;
bool spcl = false;
bool alt = false;
bool fn = false;
void setup() {
Serial.begin(9600);
pinMode(colA, INPUT_PULLUP); pinMode(colB, INPUT_PULLUP);
pinMode(colC, INPUT_PULLUP); pinMode(colD, INPUT_PULLUP);
pinMode(colE, INPUT_PULLUP); pinMode(colF, INPUT_PULLUP);
pinMode(colG, INPUT_PULLUP); pinMode(colH, INPUT_PULLUP);
pinMode(rowLatch, OUTPUT); pinMode(rowClock, OUTPUT); pinMode(rowData, OUTPUT);
updateShiftRegister(B11111111); // Alle Zeilen HIGH (inaktiv)
}
void loop() { mainTimer(); }
void mainTimer() {
unsigned long currentMicros = micros();
if (currentMicros - previousKeyboardMicros > keyboardInterval) {
previousKeyboardMicros = currentMicros;
checkKeyboard();
}
}
void updateShiftRegister(byte row) {
digitalWrite(rowLatch, LOW);
shiftOut(rowData, rowClock, MSBFIRST, row);
digitalWrite(rowLatch, HIGH);
}
void checkKeyboard() {
// Aktive Zeile setzen
updateShiftRegister(shiftRow[rowToCheck]);
// Alle 8 Spalten prüfen
if (digitalRead(colA) == LOW) bitSet(rowState[rowToCheck], 0); else bitClear(rowState[rowToCheck], 0);
if (digitalRead(colB) == LOW) bitSet(rowState[rowToCheck], 1); else bitClear(rowState[rowToCheck], 1);
if (digitalRead(colC) == LOW) bitSet(rowState[rowToCheck], 2); else bitClear(rowState[rowToCheck], 2);
if (digitalRead(colD) == LOW) bitSet(rowState[rowToCheck], 3); else bitClear(rowState[rowToCheck], 3);
if (digitalRead(colE) == LOW) bitSet(rowState[rowToCheck], 4); else bitClear(rowState[rowToCheck], 4);
if (digitalRead(colF) == LOW) bitSet(rowState[rowToCheck], 5); else bitClear(rowState[rowToCheck], 5);
if (digitalRead(colG) == LOW) bitSet(rowState[rowToCheck], 6); else bitClear(rowState[rowToCheck], 6);
if (digitalRead(colH) == LOW) bitSet(rowState[rowToCheck], 7); else bitClear(rowState[rowToCheck], 7);
// Alle Zeilen HIGH (Verhindert Übersprechen)
updateShiftRegister(B11111111);
rowToCheck++;
if (rowToCheck > 7) { checkPressedKeys(); rowToCheck = 0; }
}
void checkPressedKeys() {
// Modifier-Zustände ermitteln (Positionen im 8x8 Grid)
shift = bitRead(rowState[5], 1) | bitRead(rowState[6], 4);
ctrl = bitRead(rowState[6], 5) | bitRead(rowState[7], 3);
spcl = bitRead(rowState[6], 6) | bitRead(rowState[7], 2);
alt = bitRead(rowState[6], 7) | bitRead(rowState[7], 1);
fn = bitRead(rowState[7], 4);
capsShift = (shift && caps);
// Gesamten Tastaturzustand mit vorherigem vergleichen
for (int i = 7; i >= 0; i--) {
for (int j = 7; j >= 0; j--) {
bool newBit = bitRead(rowState[i], j);
bool prevBit = bitRead(prevRowState[i], j);
if (newBit && !prevBit) { // Flanke LOW→HIGH (Taste gedrückt)
int thisChar = (i * 8) + j;
if (capsShift) processKey(capsShiftKey[thisChar]);
else if (shift) processKey(shiftKey[thisChar]);
else if (ctrl) processKey(ctrlKey[thisChar]);
else if (alt) processKey(altKey[thisChar]);
else if (spcl) processKey(spclKey[thisChar]);
else if (fn) processKey(fnKey[thisChar]);
else if (caps) processKey(capsKey[thisChar]);
else processKey(key[thisChar]);
}
if (newBit) bitSet(prevRowState[i], j); else bitClear(prevRowState[i], j);
}
}
}
void processKey(char receivedKey) {
if (receivedKey == 7) { // Caps Lock Toggle (ASCII 7 = BEL, hier als Marker)
caps = !caps;
} else {
Serial.print(receivedKey);
}
}
PCB-Fertigungsdaten (Custom Parts)
Gesamtes ZIP-Archiv bei PCB-Fertiger (JLCPCB, PCBWay, etc.) hochladen: keyplots_4xipOSLHzg.zip
Schaltplan
(Im Originalprojekt als schematic.pdf verfügbar)
Herstellungsprozess
- Arduino Nano Flight Simulator 2020 Kontrollpanel – LCD & CDI Display
- Arduino-basiertes Schalter- & LED-Panel für Microsoft Flight Simulator 2020
- MIDI-Keyboard zum smarten Klavierlehrer umbauen – Arduino, LEDs & MuseScore
- 8×8 LED-Matrix mit Arduino Uno meistern: Schritt-für-Schritt-Anleitung
- Smartes Temperatur-Monitoring für Schulen – Arduino, Raspberry Pi, Sigfox & ThingSpeak
- Effiziente 8-Bit-I/O-Port-Bibliothek für Arduino – Digitale Steuerung erweitern
- 2,8″ TFT-Shield für Arduino Nano – 320×240 SPI-Display mit Touch
- Isolierter Analogeingang für Arduino: Spannung/Strom galvanisch trennen
- Arduino-Rechner mit 16x2 LCD und Tastatur bauen
- Smartes Indoor-Navigations-Roboter-Kit für Arduino-Projekte