STM32 mit 4x4 Keypad/Tastatur, OLED und Neopixel-4x4-Matrix

Mein 8Bit-Breadboard-Computer-Projekt braucht noch eine Eingabemöglichkeit. Da dort der Datenbus 8 bit breit ist, habe ich mich für eine 4x4-Keyboard-Matrix entschieden, die über 8 Ein/Ausgänge abgefragt werden kann.

Bevor ich mich an die Hard- und Software-Integration auf dem Breadboard-Computer machen, will ich das Ganze erst einmal wieder auf der Arduino-Plattform austesten, um schon hier etwaige Problemchen erkennen und umgehen zu können. In 6502 Assembler ist halt alles noch ein bisschen komplizierter und das erste Testen übernehme ich dann doch lieber auf der Arduino-Plattform.

Das Test-Projekt wird aus einer STM32 Bluepill, den 4x4-Keypads, einem 128x64 Pixel OLED und einer 4x4 Neopixel-RGB-LED-Matrix zur Anzeige der Tastendrücke bestehen.

Die 4x4 KeyPads / Tastaturen

Zuerst einmal zu den 4x4-Keypads. Ich habe ein paar Typen ausgetestet, die dem Funktionsprinzip mit 8 Leitungen folgen.

Der Aufbau der 4x4-Keypads ist eigentlich ganz einfach: Es gibt vier horizontale Leitungen und 4 vertikale Leitungen, die einmal waagerecht und einmal senkrecht unter den Tasten durchführen. Beim Drücken auf eine Taste wird an dem entsprechenden Punkt wird nun die dort durchlaufenden waagerechte mit der dort durchlaufenden senkrechten Leitung verbunden.



Den Mikrocontroller lasse ich nun nacheinander durch A, B, C und D Strom schicken, indem ich den entsprechenden Pin auf High setze. Dann kann ich die Leitungen 1 bis 4 auslesen, ob dort der Strom angekommen ist (der Pin High ist).

Nehmen wir an, die Taste an Position C3 wird gedrückt...

4x4 Keypad mit Mikrotastern


Hier sind die gebräuchlichen Mikrotaster als Taster verbaut. Auf der Vorderseite erkennt man, dass die unteren vier Pins mit den waagerechten Linien verbunden sind.

Auf der Rückseite erkennt man, dass die (von der Vorderseite aus gesehen) oberen vier Pins mit den waagerechten Linien verbunden sind. Also genauso wie in dem Diagramm oben.

Die Tasten haben keine Beschriftung, sondern sind einfach nur mit S1 bis S16 durchnummeriert, und zwar, so scheint es, mit der Header-Leiste nach links ausgerichtet. Aber das ist nicht weiter von Bedeutung, denn natürlich können wir in der Software jede Taste einen beliebigen Ausgabewert zuweisen.


Ärgerlich ist, dass der Header aus meiner Sicht genau falsch herum eingelötet ist. Diesmal wenigsten gerade, aber trotzdem schaut die Pinreihe nach oben statt nach unten, so wie ich sie gerne in ein Breadboard gesteckt hätte. Das Auslöten ist immer eine Menge Arbeit und wenn man nicht aufpasst, nimmt dabei die Platine (je nach dessen Qualität) Schaden. Da man die ausgelötete Headerleiste meist sowieso nicht wieder in die Löcher von der anderen Seite bekommt, bin ich dazu übergegangen, die Headerleiste hinten jedem Pin durchzuknipsen und die Pins dann einzeln auszulöten und eine neue Headerleiste einzusetzen. Würde die Headerleisten nicht eingelötet mitgeliefert, könnte man sich in der Fabrik die Arbeit sparen und ich könnte meinen Header sorum einlöten, wie ich das für richtig halte. Aber das nur am Rande.

Die Mikrotaster machen einen sehr guten Kontakt und die Tastendrücke sind eindeutig. Nur leider sind die Taster klein und eng beieinander, so dass es sich nicht sehr komfortabel darauf tippen lässt. Auch für 3D-gedruckten Tastenkappen und eigene Beschriftungen ist es ein wenig eng.

4x4 Keypad Telefontastatur


Diese Telefon-Tastatur ist etwas teurer (aus China um die 4 Euro, aus Deutschland ca. 7 Euro) und erinnert an eine Telefontastatur (die eigentlich 4x3 Tasten hat ) mit zusätzlichen Tasten A bis D.

Es lässt sich gut auf ihr tippen und auch die Header sind nicht zugelötet, so dass ich die Orientierung selbst frei wählen kann.

Allerdings ist beim Durchmessen mit dem Multimeter aufgefallen, dass ein Widerstand von 200 bis 300 Ohm zwischen den Leitungen beim Tastendruck zu messen ist. Besser ist natürlich ein geringerer Widerstand. Das garantiert ein schnelles und sauberes Ansprechen. Nachdem mit dem Multimeter nur schlechte Ergebnisse zustande kamen, habe ich mich entschlossen, die Tastatur zu öffnen und näher zu betrachten.



Leider ist das Keypad nicht geschraubt, sondern billigst verschlossen, indem man Plastik-Pins durch Löcher gesteckt und dann geschmolzen hat. Also musste ich mit dem Skalpell ran, um die Tastatur zu öffnen.

Wie erwartet traf ich im Inneren auf Gumminoppen, die niedergedrückt auf einer Leiterplatte zwei dicht nebeneinander liegende Leiterbahnen kurzschließen. Die Gumminoppen waren mit einer eher schlechter als besser leitenden Schickt bezogen. Ich habe über 200 Ohm auf ein paar Millimeter gemessen. Zuerst wollte ich den alten Trick anwenden und die Fläche mit Bleistift "anmalen". Dabei reibt sich das gut leitende Graphit auf die Gumminoppen ab und stellt die Leitfähigkeit wieder her. Aber dann erinnerte ich mich daran, dass ich noch Silberleitlack von einer Reparatur einer Cherry-Tastatur von vor Jahren übrig hatte und nahm dann die, da das eine noch bessere Lösung darstellte.

Danach waren nur noch 3 Ohm Widerstand zu messen und die Tastatur gut zu bedienen. Nach getaner Arbeit und nachdem der Silberleitlack getrocknet war, musste ich die Tastatur wieder mit Epoxy zukleben, damit sie nicht auseinander fällt. Dumm, dass sich der Hersteller Schrauben gespart hat. Qualität stellt man sich anders vor.

4x4 Folientastatur


Günstigter als die Telefontastatur sind die Folientastaturen mit ungefähr einem bis zwei Euro pro Stück. Die Qualität ist allerdings noch grausamer.

Ich kenne diese Folientastaturen eigentlich so, dass sich unter den Tastenbeschriftungen kleine Metallkuppeln aus Federstahl befinden, die man mit Tastendruck eindrückt und die dann eine Leiterbahnkreuzung darunter kurzschließen. Der Federstahl sollte ein paar tausend Betätigungen aushalten, immer wieder zurückschnellen und ein halbwegs annehmbaren Druckpunkt (Stichwort Feedback) zurückgeben.

Doch die Exemplare, die ich bekomme habe, sind sozusagen kaputtgespart. Es liegen einfach nur zwei Folien mit eher schlecht leitender Beschichtung unter dem Tastenpunkten, die dann mit mehr oder weniger Kraft zusammengedrückt werden müssen, um den Widerstand in einen auswertbaren Bereich - wortwörtlich - zu drücken. Die Metallkuppeln hat man sich gespart. Das Zurückfedern der Tasten übernimmt die Form der Plastikfolie oben drauf. Dass das nicht lange halten kann, ist jetzt schon klar.


Vorteil an den Folientastaturen ist allerdings, dass die flach sind und dass man beliebig aufkleben kann. Die Klebefolien ist bereits auf der Rückseite vorhanden. Bis zu einem gewissen Grad mögen sie auch mit Feuchtigkeit und Dreck klarkommen.

Trotzdem würde ich eine bessere Version mit Metallkuppeln bevorzugen. Das vorliegende Modell kann nicht lange halten. Auch wenn ich mit dem Multimeter grotige Ergebnisse hatte und schon dachte, die Folientastaturen wären kaputt, haben sie am STM32 dann doch funktioniert. Das hat mich dann schon überrascht

4x4 Sensor-Tastatur


Ein wenig exotischer ist das folgende Modell HW-136, eine Sensortasten-Matrix mit 16 Tasten und 8229BSF (TTP229)-Controller. Wie bei Sensortasten üblich braucht sie natürlich eine eigene Stromversorgung. Ich habe es mit 5V versucht, was gut funktioniert hat.

Danach konnte ich an den Pins OUT1 bis OUT8 genau wie mit den anderen einen Kurzschluss bei einem Tastendruck messen. Allerdings war dieser zwischen 0,5 und ca. 1,5 Sekunden lang. Und das ist natürlich schlecht. Drücke ich in normalem Tempo zwei mal hintereinander auf die selbe Ziffer, etwa bei einer Telefonnummer, dann habe ich ein durchgehendes 1-Sekunden Signal, egal, wie oft ich die Taste hintereinander drücken. Und bevor das Signal nicht abgeebbt ist, kann ich auch keine neue Taste drücken.


Das ist natürlich extrem nervig, zwischen jedem Tastendruck mindestens 2 Sekunden warten zu müssen. So kann man nichts gescheit eingeben.

Die Tastatur macht nur einen Sinn, wenn man bis zu 16 Funktionen hat, einmal eine Taste drückt und dann etwa geschieht, dass mindestens zwei Sekunden dauert, meinetwegen an einem Snack-Automaten die Wahl des Süßigkeitsfachs, dass man wählen will. Aber für das anvisierte Projekt ist das nichts.

Ein Vorteil der Tastatur, den ich nicht verschweigen will, ist es, dass man sie auch über I2C steuern kann. Man muss nur die entsprechende Lötbrücke setzen und kann dann mit dem 2-Wire-Protokoll und SDA und SCL arbeiten. Die Verarbeitung übernimmt der integrierte Controller. Aber auch I2C kommt für das 6502er-Projekt nicht in Frage. Hier möchte ich es möglichst simpel.

Vielleicht einmal in einem anderen Projekt. Ärgerlich bei dieser Platine war der - mal wieder - falsch rum eingelötete Header (12 Pins, vielen Dank!). Und dann frage ich mich, warum man die Elektronik nicht auf die Rückseite gepackt hat, so dass man eine plane Frontseite hätte und die dann z. B. hinter eine dünne Scheibe kleben könnte, um die Tastatur halbwegs wetter- und Vandalen-sicher anzubringen. Aber nein. Die Elektronik ist auf der selben Seite wie die Tastatur und macht den Einbau schwieriger. Man wird immer einen Buckel oberhalb der Tastatur haben, unter der sich die Elektronik versteckt. Und da die so nah an den Tasten 1 bis 4 dran ist, stört dieser Buckel natürlich auch beim Tippen. Manchmal frage ich mich, warum sich manche Hersteller zu wenig Gedanken über die Praktikabilität machen...

Das OLED-Display

Als Anzeige, welche Taste gedrückt wird, kommt das bewährte OLED-Display zum Einsatz, welches ich in diesem Artikel schon ausführlich beschrieben habe.

Hier soll stehen, welche Tastennummer (hardwaretechnisch gesehen) und welche dazu gehörige Tastenbezeichnung die gedrückte Taste hat.

Die 4x4 LED-Neopixel-Matrix (WS2812B)


Und da ich mein 4x4 Neopixel-Matrix-Modul auch endlich einmal ausprobieren wollte und das ideal hier ins Projekt passt, kommt der WS2812B-Ableger mit 16 LEDs auch zum Einsatz. Wie immer bei WS2812B-Modulen sind hier kleine RGB-LEDs in Reihe geschaltet, die über ein spezielles Protokoll über nur eine Leitung angesprochen werden. So kann man jede der 16 LED in einer von 16,7 Mio. Farb- bzw. Helligkeitsabstufungen (24 bit) leuchten lassen.

Neopixel hatte ich schon einmal als 24-LED-Ring besprochen. Allerdings auf dem Arduino Uno und nicht auf dem STM32.

Die auf dem Arduino verwendete Library von Adafruit tat es leider nicht auf meinem STM32 und auch die FastLED-Library wollte nicht. Obwohl bei beiden die Angabe zu finden war, dass sie mit dem STM32 kompatibel sein sollen. Pustekuchen. Und ein Grund sich zu ärgern, wenn man seine Zeit verschwendet, Beispiele in den Code zu kopieren, die dann nicht kopieren. Leute! Bitte schreibt nur STM32-Kompatibel in eure Feature-List, wenn ihr das auch getestet habt. Ihr erspart euch die Wut der User, die euretwegen ihre Zeit verschwenden.


Die Library von Roger Clark funktionierte dann, allerdings musste ich den Code noch geringfügig anpassen, da ich SPI2 statt SPI1 benutzen wollen, weil der MOSI-Pin PA7 von SPI ja schon von meiner Tastatur belegt war. SPI2 benutzt PB15 und der Pin war noch frei.

Die LEDs sind übrigens in Schlangenlinien miteinander verbunden. Das hat natürlich wieder Kostenspargründe. So gann man die Leiterbahnen kürzer halten. Statt 1, 2, 3, 4 - nächster Zeile 5, 6, 7, 8 usw. geht die Reihenfolge 3 4 11 12 2 5 10 13 1 6 9 14 0 7 8 15 Wenn man wollte, könnte man die 16 RGB-LEDs durch weitere erweitern, indem man den Bus bei OUT auf der Rückseite des Moduls mit dem IN des nächsten Moduls verbindet. So könnte man 4 4x4 Module auch zu einem Neopixel 8x8 Modul zusammensetzen. Die Platinenausmaße sind auf das Aneinanderfügen ausgelegt.

Aufbau auf dem Breadboard



Weil es sich einfach anbietet (und aus Gründen der Faulheit ;)) habe ich Heaer mit der 8 Pins der Tastaturen einfach an die Pins PA0 bis PA7 der Blue Pill gesteckt. Bei der Folientastatur musste ich noch ein paar Male-Male-Jumperkabel zwischenstecken. Die anderen konnte ich (mit richtig herum eingelötetem Header) dann einfach an dieser Stelle einstecken.

Der I2C-Bus an PB6 und PB7 (orange Kabel) führt an das OLED, das ja über I2C angesprochen wird.

Und über PB15 (SPI2 MOSI) steuere ich die Neopixel-Matrix.

Die Software

Die Software soll:

Video

Natürlich habe die Software dann getestet. Das Ergebnis sieht man in folgendem Video. Außerdem weitere Informationen zu meinen Optimierungen:



Source-Code

Wer das Projekt (oder etwas ähnliches) nachbauen will, hier der Source-Code. Der eigentliche Code zur Abfrage der Tastatur ist nur ein paar Zeilen lang. Dafür muss man keine Library bemühen, obwohl ich weiß, dass es welche für KeyPads gibt. Da ich aber den Code sowieso für die 6502-Portierung selbst schreiben muss, habe ich das gleich getan.

platformio.ini (klicken, um diesen Abschnitt aufzuklappen)
[env:genericSTM32F103CB] platform = ststm32 board = genericSTM32F103CB framework = arduino monitor_speed = 115200 upload_protocol = dfu ; dfu = STM32duino Bootloader lib_deps = U8g2 ; für OLED SSD1306 I2C WS2812B ; für 4x4 NeoPixel; WS2812B by Roger Clark
main.cpp (klicken, um diesen Abschnitt aufzuklappen)
//////////////////////////////////////////////////////// // (C) 2020 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // //////////////////////////////////////////////////////// #include <Arduino.h> #include <U8x8lib.h> // OLED https://github.com/olikraus/u8g2/wiki/u8x8reference // fontlist: https://github.com/olikraus/u8g2/wiki/fntlist8x8 U8X8_SSD1306_128X64_NONAME_HW_I2C oled(/* reset=*/ U8X8_PIN_NONE); // Adafruit NeoPixel by Adafruit; lässt sich nicht für STM32 kompilieren! #include <Adafruit_NeoPixel.h> // FastLED by Daniel Garcia; schmeisst immer nur "Invalid pin specified" oder andere Fehler #include <FastLED.h> //WS2812B by Roger Clark #include <WS2812B.h> #define NUM_LEDS 16 #define pinPixel PB15; //SPI2 MOSI, Anpassung WS2812B.cpp war nötig! WS2812B pixel = WS2812B(NUM_LEDS); int pinsOut[] = {PA0, PA1, PA2, PA3 }; int pinsIn[] = {PA4, PA5, PA6, PA7 }; //Anordnung der Pixel-Num, die zählen nämlich schlangenförmig hoch // 3 4 11 12 // 2 5 10 13 // 1 6 9 14 // 0 7 8 15 byte pixorder[] = {3, 4, 11, 12, 2, 5, 10, 13, 1, 6, 9, 14, 0, 7, 8, 15} ; void pixelOn (byte pixelNum) { pixel.setPixelColor(pixelNum, pixel.Color(20, 15, 0)); pixel.show(); } void pixelOff (byte pixelNum) { pixel.setPixelColor(pixelNum, pixel.Color(0, 0, 0)); pixel.show(); } byte getKey(){ // wartet auf Tastendruck in Endlosschleife while (1) { for (int i=0; i < 4; i++) { // durch jeweils einen der Out-Pins Strom schicken... for (int j=0; j < 4; j++) { digitalWrite(pinsOut[j], j==i ? HIGH : LOW ); } // .. und checken, ob der Strom an einem In-Pin wieder rauskommt for (int j=0; j < 4; j++) { if (digitalRead(pinsIn[j]) == HIGH) { pixelOn(pixorder[i*4+j]); // warten, bis Taste wieder loggelassen while (digitalRead(pinsIn[j]) == HIGH) delay(1); pixelOff(pixorder[i*4+j]); return i*4+j; } } } delay(1); } } void setup() { Serial.begin(115200); oled.begin(); oled.clearDisplay(); oled.setFont(u8x8_font_inb46_4x8_r); // nächsten 2 Zeilen müssen in WS2812B.cpp, Zeile 53 gemacht werden, in pixel.begin() // damit statt SPI1 (Pin PA7, bereits besetzt) SPI2 (Pin PB15) benutzt wird //SPI.setModule(2); //SPI.begin(); pixel.begin(); // Sets up the SPI pixel.show(); // Clears the pixel, as by default the pixel data is set to all LED's off. //pixel.setBrightness(8); // das führt nur zu Scherereien, funktioniert nicht richtig //nicht benutzen, sondern Helligkeit über geringere Farbwerte steuern! } void loop() { byte keynum=99; //Telefontastatur //byte keyorder[] = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0} ; //char keys[] = {'1', '2', '3', 'A', '4', '5', '6', 'B', '7', '8', '9', 'C', '*', '0', '#', 'D'} ; //Folientastatur //byte keyorder[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} ; //char keys[] = {'1', '2', '3', 'A', '4', '5', '6', 'B', '7', '8', '9', 'C', '*', '0', '#', 'D'} ; //Mikrotaster byte keyorder[] = {15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0} ; char keys[] = {'1', '2', '3', 'A', '4', '5', '6', 'B', '7', '8', '9', 'C', '*', '0', '#', 'D'} ; for (int i=0; i < 4; i++) { pinMode(pinsOut[i], OUTPUT); pinMode(pinsIn[i], INPUT_PULLDOWN); } delay(500); // test der NeoPixel, wie verdrahtet for (int i=0; i < 16; i++) { pixelOn(i); delay(100); pixelOff(i); delay(1); // ist nötig, sondern leuchtet gar nichts } delay (500); // test der NeoPixel, sortiert for (int i=0; i < 16; i++) { pixelOn(pixorder[i]); delay(100); pixelOff(pixorder[i]); delay(1); // ist nötig, sondern leuchtet gar nichts } oled.print("Key!"); while (1) { keynum = getKey(); // wartet solange, bis eine Taste gedrückt wurde oled.clear(); oled.print(keynum); oled.print(","); oled.print(keys[keyorder[keynum]]); delay (1); } }