Do-It-Yourself Hardware Tastatur

DIY-Reparatur einer defekten Tastatur

STM32F411CE Blackpill

Meine heißgeliebte „daskeyboard 4 Root“ Tastatur habe ich mir 2018 gekauft. Das war das erste mal, dass ich bewusst Geld in das Stück Peripherie gesteckt habe, das ich nahezu den ganzen Tag an den Fingern habe. Und den Kauf habe ich bis zum heutigen Tag nicht bereut. Und sie zeigt auch noch lange keine Ermüdungserscheinungen – von etwas glatt polierten Keycaps abgesehen. Aber die wären ja sogar auch noch austauschbar.

Wie es nun kommen musste, habe ich im Februar dieses Jahr irgendein klebriges Getränk über der Tastatur verschüttet. Ärgerlich und viel Arbeit aber keine Katastrophe – alle Teile der Tastatur sollten wartbar und zu reinigen sein. Eine oberflächliche Reinigung der ausgebauten Faceplate und der Switches hat natürlich nicht gereicht, da das Zeug in die Schalter eingedrungen ist. Faul wie ich bin, dachte ich mir, dass ich die Schalter identifiziere, die „kleben“ und diese auslöte und gezielt reinige.

Oberflächliche Reinigung eher so meh...
Oberflächliche Reinigung eher so meh…

Katastrophaler GND-Short?

Die Idee war grundsätzlich ganz gut. Leider war es nicht ganz so schlau, die zerlegte Tastatur mit Strom zu versorgen, während sie wackelig auf einem Stapel anderer Projekte liegt… Was genau nun in der Folge passiert ist, habe ich nicht zweifelsfrei herausfinden können. Am wahrscheinlichsten habe ich das grounded case mit etwas kurzgeschlossen, was Microcontroller und mind. ein anderes Bauteil nicht so witzig fanden. Gemerkt habe ich es auch tatsächlich erst, als ich aufsteigenden magischen Rauch gesehen habe. Natürlich war es da schon zu spät, auch wenn ich schnell alles abgeschaltet habe.

Die Rauchentwicklung kam von der MCU, die so heiß wurde, dass der Papiersticker, der darauf klebte, angesengt ist. An der Eingangseite ist ebenfalls etwas sehr heiß geworden. Langer Rede kurzer Sinn. Etwa 8 Tasten sowie der Mute Taster funktionierten nach diesem Fauxpas noch. Der USB-Hub war auch noch ok, meckerte aber zeitweise über „infinite loops“ in der Geräte-Baumstruktur. Man kann also sagen, dass das Board Schrott ist. Resigniert hab ich dann zusammen mit meiner Tochter alle Taster ausgelötet und alles gründlich gereinigt. Ich habe dazu die Schalter in warmem Spülwasser gereinigt und anschließend gründlich mehrfach gespült. Die Keycaps ebenso. Beim Spülen kam mir das große Nudelsieb sehr gelegen.

Plan B: PCB reverse engineeren und custom Firmware entwickeln

Normalerweise werden Tastaturen in einer Matrix, in Zeilen und Spalten verkabelt. Allerdings nicht so wie es einfach zu verstehen wäre, sondern so wie vermutlich der Auto-Router jemandes PCB-Designers es entschieden hat. Also musste ich jedes Pad, an dem zuvor das Flexkabel des originales Controllers hing nachverfolgen, um herauszufinden ob es eines Spalte (mit Diode) oder eine Reihe ist und welche Tasten daran angeschlossen sind.

Hierzu habe ich das PCB beidseitig auf dem Flachbettscanner in hoher Auflösung gescannt. Tatsächlich passt es nicht einmal vollständig darauf, so dass ich anschließend 2×2 Teilbilder zusammenfügen musste. Ich habe dann die Vorderseite an der vertikalen Achse gespiegelt und mit 50% Farbaddition transparent über die PCB Rückseite gelegt und jeden Trace auf einer neuen Ebene in gimp nachverfolgt. Vermutlich wäre es an diesem Punkt geschickter gewesen, das ganze in KiCad oder so zu machen, aber gimp hatte ich zur Hand und kann ich einigermaßen bedienen.

PCB-Rückseite mit eingefärbten Leiterbahnen
PCB-Rückseite mit eingefärbten Leiterbahnen

Anschließend hab ich noch ein schönes deutsches ISO keyboard schema gesucht, aber nur das für UK gefunden. Also kurzerhand Inkscape rausgekramt und eines erstellt. Dieses hab ich auch gleich wieder auf wikimedia der Allgemeinheit zur Verfügung gestellt. So bewaffnet hab ich mich nun an die Matrix-Zuordnung gemacht.

gespiegelte PCB-Rückseite mit eingefärbten Leiterbahnen und ISO Tastatur Overlay
gespiegelte PCB-Rückseite mit eingefärbten Leiterbahnen und ISO Tastatur Overlay

Auswahl einer freien Keyboard-Firmware

Eine schnelle Recherche hat da so einige Projekte zutage geführt. In der engeren Auswahl standen QMK, KMK und ZMK. KMK scheint dabei ein MicroPython Projekt zu sein und ZMK zielt eher auf größere 32bit Mikrocontroller. Da ich keine Erfahrung mit irgendeinem dieser Projekte habe, hab ich ein bisschen nach Dokumentationsstand, Repoaktivität und einer Portion Bauchgefühl entschieden. So ist es nun QMK geworden. Den kann man bequem via pip installieren, und bekommt so ein cli tool, mit dem sich Tasks wie das compilieren / flashen oder das Erstellen einer Tastatur / Keymap anstoßen lassen. Parallel klont man das qmk-firmware repository und konfiguriert den Pfad in qmk.

cd Repos
pip3 install --user qmk
git clone git@github.com:qmk/qmk_firmware.git
qmk setup -H ~/Repos/qmk_firmwareCode-Sprache: Bash (bash)

Genauere Details kann man der Schnellstartanleitung des Projekts entnehmen.

Eine neue blanko Tastaturdefinition erstellen

Jetzt kann man eine leere Tastatur generieren lassen, als Parameter übergebe ich für welchen Microcontroller sie genutzt werden soll.

qmk new-keyboard -kb handwired/daskeyboard/daskeyboard4 -t STM32F411Code-Sprache: JavaScript (javascript)

Anschließend werden interaktiv noch Sachen wie eigener Name, github username, sowie das default Layout abgefragt, wo ich fullsize_iso gewählt habe. Das erzeugt nun eine Verzeichnisstruktur, die alle wesentlichen Teile einer Tastaturdefinition enthält. Diese folgt auch schon dem scheinbar vor gar nicht allzu langer Zeit eingeführten Ansatz, des data-driven Developments. Dem folgend, sollten so viele Informationen wie möglich, über die info.json gesetzt werden. Aus dieser wird dann dynamisch der Code generiert und kompiliert.

.
├── config.h
├── info.json
├── keymaps
│   └── default
│       └── keymap.c
├── readme.md
└── rules.mkCode-Sprache: CSS (css)

Zeilen / Spalten der Tastatur ermitteln und Pins der Blackpill zuordnen

Bei den Pins des Controllers muss man ein paar Dinge beachten, einige darf man nicht, andere sollte man nicht benutzen. Details finden sich in der Dokumentation.

## physical
PCB properties                      STM32F411
1   column, diode, 4 switches       C14
2   row, no diode, 13 switches      A13
3   row, no diode, 15 switches      A14
4   column, diode, 3 switches       C15
5   column, diode, 8 switches       A0
6   row, no diode, 14 switches      A1
7   column, diode, 7 switches       A2
8   row, no diode, 13 switches      A3
9   column, diode, 7 switches       A4
10  column, diode, 7 switches       A5
11  column, diode, 8 switches       A6
12  column, diode, 8 switches       A7
13  column, diode, 7 switches       B0
14  column, diode, 7 switches       B1
15  column, diode, 7 switches       B10
16  column, diode, 7 switches       B9
17  row, no diode, 12 switches      B8
18  column, diode, 2 switches       B7
19  column, diode, 2 switches       B6
20  column, diode, 6 switches       B5
21  column, diode, 6 switches       B4
22  row, no diode, 12 switches      B3
23  row, no diode, 10 switches      A15
24  row, no diode, 15 switches      B15
25  NC
26  column, diode, 7 switches       A8Code-Sprache: PHP (php)
## logical
Typ         pin-name    matrix  PCB
rows:       "A13",     0       2
            "A14",     1       3
            "A1",      2       6
            "A3",      3       8
            "B8",      4       17
            "B3",      5       22
            "A15",     6       23
            "B15"      7       24
columns:    "C14",     0       1
            "C15",     1       4
            "A0",      2       5
            "A2",      3       7
            "A4",      4       9
            "A5",      5       10
            "A6",      6       11
            "A7",      7       12
            "B0",      8       13
            "B1",      9       14
            "B10",     10      15
            "B9",      11      16
            "B7",      12      18
            "B6",      13      19
            "B5",      14      20
            "B4",      15      21
            "A8"       16      26Code-Sprache: PHP (php)

Folglich habe ich die rows/cols wie folgt definiert. Die Reihenfolge, in der die Pins aufgeführt werden spiegelt damit den Matrix-Code wieder, beginnend bei „0“.

    "matrix_pins": {
        "cols": ["C14", "C15", "A0", "A2", "A4", "A5", "A6", "A7", "B0", "B1", "B10", "B9", "B7", "B6", "B5", "B4", "A8"],
        "rows": ["A13", "A14", "A1", "A3", "B8", "B3", "A15", "B15"]
    },Code-Sprache: JSON / JSON mit Kommentaren (json)

Matrix Tastaturcodes tastenweise ermitteln

Der nun folgende Schritt war derjenige, der am längsten gedauert hat und maximale Konzentration erfordert hat. Daher hab ich das auch über 2 Abende verteilt gemacht. Ich hatte wirklich keine Lust, in der Zeile zu verrutschen und alles nochmal kontrollieren zu müssen. In der Layout-Definition tauchen folgende Datenfelder auf:

  • „label“ ist einfach eine Freitextbeschriftung (hier analog zur Tastenbeschriftung).
  • „matrix“ ist ein Array und erwartet [<row>, <column>], dies lese ich von meiner hübschen PCB-Montage ab. Leitungen mit Diode sind demnach columns, welche ohne rows. Die Zahl fängt bei 0 an und zählt aufwärts in der Reihenfolge, wie sie in den „matrix_pins“ angegeben wurde.
  • „x“ und „y“ beschreiben die physikalische Position der Taste
  • die optionalen Parameter „w“ und „h“ geben die Breite und die Höhe an

Zum Glück musste ich mich nicht um das physische Layout kümmern, da qmk hat es schon durch Auswahl von „fullsize_iso“ korrekt vorausgefüllt hat.

    "layouts": {
        "LAYOUT_fullsize_iso": {
            "layout": [
                { "label":"Esc", "matrix": [5, 16], "x": 0, "y": 0 },
                { "label":"F1", "matrix": [2, 4], "x": 2, "y": 0 },
                { "label":"F2", "matrix": [2, 5], "x": 3, "y": 0 },
                { "label":"F3", "matrix": [3, 5], "x": 4, "y": 0 },
                { "label":"F4", "matrix": [5, 5], "x": 5, "y": 0 },
                { "label":"F5", "matrix": [0, 1], "x": 6.5, "y": 0 },
                { "label":"F6", "matrix": [5, 8], "x": 7.5, "y": 0 },
                { "label":"F7", "matrix": [3, 3], "x": 8.5, "y": 0 },
                { "label":"F8", "matrix": [2, 3], "x": 9.5, "y": 0 },
                { "label":"F9", "matrix": [2, 2], "x": 11, "y": 0 },
                { "label":"F10", "matrix": [0, 2], "x": 12, "y": 0 },
                { "label":"F11", "matrix": [5, 2], "x": 13, "y": 0 },
                { "label":"F12", "matrix": [6, 2], "x": 14, "y": 0 },
                { "label":"Prt Sc", "matrix": [0, 0], "x": 15.25, "y": 0 },
                { "label":"Scr Lk", "matrix": [1, 0], "x": 16.25, "y": 0 },
                { "label":"Pause", "matrix": [1, 1], "x": 17.25, "y": 0 },
                { "label":"`", "matrix": [2, 16], "x": 0, "y": 1.25 },
                { "label":"1", "matrix": [0, 16], "x": 1, "y": 1.25 },
                { "label":"2", "matrix": [0, 4], "x": 2, "y": 1.25 },
                { "label":"3", "matrix": [0, 5], "x": 3, "y": 1.25 },
                { "label":"4", "matrix": [0, 6], "x": 4, "y": 1.25 },
                { "label":"5", "matrix": [2, 6], "x": 5, "y": 1.25 },
                { "label":"6", "matrix": [2, 7], "x": 6, "y": 1.25 },
                { "label":"7", "matrix": [0, 7], "x": 7, "y": 1.25 },
                { "label":"8", "matrix": [0, 8], "x": 8, "y": 1.25 },
                { "label":"9", "matrix": [0, 3], "x": 9, "y": 1.25 },
                { "label":"0", "matrix": [0, 9], "x": 10, "y": 1.25 },
                { "label":"-", "matrix": [2, 9], "x": 11, "y": 1.25 },
                { "label":"=", "matrix": [2, 8], "x": 12, "y": 1.25 },
                { "label":"Backspace", "matrix": [3, 2], "w": 2, "x": 13, "y": 1.25 },
                { "label":"Ins", "matrix": [2, 11], "x": 15.25, "y": 1.25 },
                { "label":"Home", "matrix": [2, 15], "x": 16.25, "y": 1.25 },
                { "label":"Page Up", "matrix": [2, 14], "x": 17.25, "y": 1.25 },
                { "label":"Num Lk", "matrix": [4, 10], "x": 18.5, "y": 1.25 },
                { "label":"/", "matrix": [4, 11], "x": 19.5, "y": 1.25 },
                { "label":"*", "matrix": [4, 14], "x": 20.5, "y": 1.25 },
                { "label":"-", "matrix": [6, 14], "x": 21.5, "y": 1.25 },
                { "label":"Tab", "matrix": [3, 16], "w": 1.5, "x": 0, "y": 2.25 },
                { "label":"Q", "matrix": [1, 16], "x": 1.5, "y": 2.25 },
                { "label":"W", "matrix": [1, 4], "x": 2.5, "y": 2.25 },
                { "label":"E", "matrix": [1, 5], "x": 3.5, "y": 2.25 },
                { "label":"R", "matrix": [1, 6], "x": 4.5, "y": 2.25 },
                { "label":"T", "matrix": [3, 6], "x": 5.5, "y": 2.25 },
                { "label":"Y", "matrix": [3, 7], "x": 6.5, "y": 2.25 },
                { "label":"U", "matrix": [1, 7], "x": 7.5, "y": 2.25 },
                { "label":"I", "matrix": [1, 8], "x": 8.5, "y": 2.25 },
                { "label":"O", "matrix": [1, 3], "x": 9.5, "y": 2.25 },
                { "label":"P", "matrix": [1, 9], "x": 10.5, "y": 2.25 },
                { "label":"[", "matrix": [3, 9], "x": 11.5, "y": 2.25 },
                { "label":"]", "matrix": [3, 8], "x": 12.5, "y": 2.25 },
                { "label":"Del", "matrix": [2, 10], "x": 15.25, "y": 2.25 },
                { "label":"End", "matrix": [0, 15], "x": 16.25, "y": 2.25 },
                { "label":"Page Down", "matrix": [0, 14], "x": 17.25, "y": 2.25 },
                { "label":"7", "matrix": [1, 10], "x": 18.5, "y": 2.25 },
                { "label":"8", "matrix": [1, 11], "x": 19.5, "y": 2.25 },
                { "label":"9", "matrix": [1, 14], "x": 20.5, "y": 2.25 },
                { "label":"+", "h": 2, "matrix": [1, 15], "x": 21.5, "y": 2.25 },
                { "label":"Caps Lock", "matrix": [3, 4], "w": 1.75, "x": 0, "y": 3.25 },
                { "label":"A", "matrix": [7, 16], "x": 1.75, "y": 3.25 },
                { "label":"S", "matrix": [7, 4], "x": 2.75, "y": 3.25 },
                { "label":"D", "matrix": [7, 5], "x": 3.75, "y": 3.25 },
                { "label":"F", "matrix": [7, 6], "x": 4.75, "y": 3.25 },
                { "label":"G", "matrix": [5, 6], "x": 5.75, "y": 3.25 },
                { "label":"H", "matrix": [5, 7], "x": 6.75, "y": 3.25 },
                { "label":"J", "matrix": [7, 7], "x": 7.75, "y": 3.25 },
                { "label":"K", "matrix": [7, 8], "x": 8.75, "y": 3.25 },
                { "label":"L", "matrix": [7, 3], "x": 9.75, "y": 3.25 },
                { "label":";", "matrix": [7, 9], "x": 10.75, "y": 3.25 },
                { "label":"'", "matrix": [5, 9], "x": 11.75, "y": 3.25 },
                { "label":"#", "matrix": [1, 2], "x": 12.75, "y": 3.25 },
                { "label":"Return", "h": 2, "matrix": [4, 2], "w": 1.25, "x": 13.75, "y": 2.25 },
                { "label":"4", "matrix": [3, 10], "x": 18.5, "y": 3.25 },
                { "label":"5", "matrix": [3, 11], "x": 19.5, "y": 3.25 },
                { "label":"6", "matrix": [3, 14], "x": 20.5, "y": 3.25 },
                { "label":"Shift L", "matrix": [3, 13], "w": 1.25, "x": 0, "y": 4.25 },
                { "label":"\\", "matrix": [5, 4], "x": 1.25, "y": 4.25 },
                { "label":"Z", "matrix": [4, 16], "x": 2.25, "y": 4.25 },
                { "label":"X", "matrix": [4, 4], "x": 3.25, "y": 4.25 },
                { "label":"C", "matrix": [4, 5], "x": 4.25, "y": 4.25 },
                { "label":"V", "matrix": [4, 6], "x": 5.25, "y": 4.25 },
                { "label":"B", "matrix": [6, 6], "x": 6.25, "y": 4.25 },
                { "label":"N", "matrix": [6, 7], "x": 7.25, "y": 4.25 },
                { "label":"M", "matrix": [4, 7], "x": 8.25, "y": 4.25 },
                { "label":",", "matrix": [4, 8], "x": 9.25, "y": 4.25 },
                { "label":".", "matrix": [4, 3], "x": 10.25, "y": 4.25 },
                { "label":"/", "matrix": [6, 9], "x": 11.25, "y": 4.25 },
                { "label":"Shift R", "matrix": [7, 13], "w": 2.75, "x": 12.25, "y": 4.25 },
                { "label":"Up", "matrix": [5, 15], "x": 16.25, "y": 4.25 },
                { "label":"1", "matrix": [7, 10], "x": 18.5, "y": 4.25 },
                { "label":"2", "matrix": [7, 11], "x": 19.5, "y": 4.25 },
                { "label":"3", "matrix": [7, 14], "x": 20.5, "y": 4.25 },
                { "label":"Enter", "h": 2, "matrix": [7, 15], "x": 21.5, "y": 4.25 },
                { "label":"Control L", "matrix": [2, 1], "w": 1.25, "x": 0, "y": 5.25 },
                { "label":"Super L", "matrix": [3, 12], "w": 1.25, "x": 1.25, "y": 5.25 },
                { "label":"Alt L", "matrix": [5, 0], "w": 1.25, "x": 2.5, "y": 5.25 },
                { "label":" ", "matrix": [5, 10], "w": 6.25, "x": 3.75, "y": 5.25 },
                { "label":"Alt R", "matrix": [6, 0], "w": 1.25, "x": 10, "y": 5.25 },
                { "label":"Super R", "matrix": [7, 12], "w": 1.25, "x": 11.25, "y": 5.25 },
                { "label":"Menu", "matrix": [6, 3], "w": 1.25, "x": 12.5, "y": 5.25 },
                { "label":"Control R", "matrix": [4, 1], "w": 1.25, "x": 13.75, "y": 5.25 },
                { "label":"Left", "matrix": [6, 15], "x": 15.25, "y": 5.25 },
                { "label":"Down", "matrix": [6, 10], "x": 16.25, "y": 5.25 },
                { "label":"Right", "matrix": [6, 11], "x": 17.25, "y": 5.25 },
                { "label":"0", "matrix": [5, 11], "w": 2, "x": 18.5, "y": 5.25 },
                { "label":"Del", "matrix": [5, 14], "x": 20.5, "y": 5.25 }
            ]
        }
    }

Code-Sprache: JSON / JSON mit Kommentaren (json)

Firmware compilieren und testen

Zuerst muss allerding noch der tinyuf2 Bootloader aus den Quellen kompiliert und auf die Blackpill geflasht werden. Dieser hat ein paar handfeste Vorteile gegenüber der DFU Variante, wie z. B. Überschreibschutz, die Präsentation des Flash-Speichers als Massenspeicher und Autoflash von uf2 Files, die man dort ablegt.

make BOARD=stm32f411ce_blackpill all
make BOARD=stm32f411ce_blackpill flash

Mit diesen gewonnen Erkenntnissen und der daraus entstandenen Konfiguration sollte sich eine Firmware kompilieren und flashen lassen. Tat sie auch. Nur funktionierte es nicht. Sobald meine Firmware drauf war, meldete sich das Gerät vom USB-Bus ab und war fortan komplett still am Bus – bis ich ihn wieder resetet und in den Bootloader-Modus gezwungen habe. Stellt sich heraus, ich muss neben dem Prozessor auch noch die Blackpill als Board definiert werden. Denn das Board hat einen 25MHz Oszillator verbaut – anstelle den üblichen 8MHz. Erst danach versteht der Computer das Gerät am USB-Bus.

Blackpill sendet erste Keycodes mit QMK Firmware
Blackpill sendet erste Keycodes mit QMK Firmware. (Eine row und column mit Jumper-Kabel gebrückt)

PCB und Blackpill verkabeln

Ich habe meinen Kopf zwei Tage lang gegen die Wand gehauen, um einen Weg zu finden, die Blackpill zusätzlich zu dem original PCB in die Tastatur reinzuquetschen. Das wäre allein aus optischen Gründen wünschenswert, damit die Mediakeys und der Drehencoder nicht leere Löcher bleiben, durch die man reingucken kann. Aber leider habe ich keinen zufriedenstellenden gefunden. Es läuft daher auf weitergehende Zerstörung hinaus – aber dazu später mehr. Denn insgeheim möchte ich auf Mediakeys und Lautstärkeregler nicht verzichten.

Tastatur-PCB mit der Blackpill verkabelt
Tastatur-PCB mit der Blackpill verkabelt
Grundsätzlicher Funktionstest erfolgreich
Grundsätzlicher Funktionstest erfolgreich

Restaurierung der Tastatur Sonderfunktionen

Ich hatte schon weiter oben im Artikel angeteasert, dass ich beide Boards einfach nicht in der Tastatur platziert bekomme, dennoch möchte ich die Mediatasten gerne funktional haben. Aber selbst wenn ich die Platzprobleme gelöst bekäme, hätte ich leider nicht genug freie, nutzbare Pins auf dem Mikrocontroller, um alle Funktionen als „direct“ keys abbilden zu können. Ich bräuchte 2 für den Drehencoder, 3 für die 3 LEDs und 5 für die Medientasten.

Ich komme aber nur auf 5 freie Pins nachdem das Tastatur-PCB bereits 25 belegt hat. Das reicht aber immerhin für den Drehencoder und die LEDs.

Für 'einschneidende' Maßnahmen vorbereitetes altes PCB
Für ‚einschneidende‘ Maßnahmen vorbereitetes altes PCB

Somit ist klar, dass die Taster wegfallen würden, also kann ich auch zu drastischen Maßnahmen greifen, um die Platzprobleme in den Griff zu bekommen. Und damit meine ich die Stichsäge, mit der ich einfach 2 Ausschnitte aus dem originalen Control-Board herausgesägt habe. Übernommen habe ich den rechten Teil, der als Mount für den Drehencoder dient, den ich aber direkt verkabele.

Ferner habe ich die 3 LEDs samt ihrer Vorwiderstände herausgeholt. Diese wurden allerding bislang über den GND-Pin geschaltet und teilten sich eine gemeinsame Power-Rail. Die habe ich einfach mit einem scharfen Messer voneinander getrennt und auch direkt mit der Blackpill verbunden.

Indikator-LEDs für dem PCB Ausschnitt verkabelt.
Indikator-LEDs für dem PCB Ausschnitt verkabelt.

Dabei verwende ich C13 als Num lock, der sich den Pin mit der User-LED der Blackpill teilt. Hat den Vorteil, dass ich Aktivität vom tinyuf2 Bootloader invertiert an der Num Lock LED ablesen kann. Auch dies kann in der info.json gesetzt werden:

    "indicators": {
        "num_lock": "C13",
        "caps_lock": "B14",
        "scroll_lock": "B2",
        "on_state": 1,
    },
    "encoder": {
        "rotary": [
            {
            "pin_a": "B13",
            "pin_b": "B12",
            "resolution": 4
            }
        ]
    },Code-Sprache: JSON / JSON mit Kommentaren (json)
LEDs und Drehschalter mit der MCU verkabelt
LEDs und Drehschalter mit der MCU verkabelt

Wie man sieht, habe ich auch den BOOT und RESET Taster ausgelötet, um noch ein paar mm Platz zu gewinnen. Das macht es allerdings schwieriger, die Blackpill in den Bootloader Modus zu versetzen – insbesondere wenn die Tastatur wieder zusammengebaut ist.

Die Firmware reagiert standardmäßig auf das Drücken der Taste [0, 0] während des Bootens / Einsteckens. Das ist normalerweise die „Esc“ Taste. Allerdings nicht bei diesem PCB. Hier ist „Esc“ an der row 5 und der column 16 angeschlossen. Dies kann bislang ausschließlich in der config.h konfiguriert werden.

#define BOOTMAGIC_LITE_ROW 5
#define BOOTMAGIC_LITE_COLUMN 16Code-Sprache: CSS (css)

Drehencoder mit Leben befüllen

Hierfür wird ein Schnipsel Code in der keymap.c fällig, wobei ich nicht nur die Lautstärkeregelung nachgebaut habe, sondern sie auch um eine Scrollfunktion erweitert habe.

// rotary encoder
bool encoder_update_user(uint8_t index, bool clockwise) {
    // scroll with CTRL
    if (get_mods() & MOD_BIT(KC_CAPS)) {
        if (clockwise) {
            tap_code(KC_WH_U);
        } else {
            tap_code(KC_WH_D);
    }
    // Volume control
    } else {
        if (clockwise) {
            tap_code(KC_VOLD);
        } else {
            tap_code(KC_VOLU);
        }
    }
    return false;
}Code-Sprache: C++ (cpp)

Interessant finde ich, dass der Regler nun viel zuverlässiger funktioniert. Ich hatte das bislang immer auf einen schwammingen Encoder geschoben, aber scheinbar hat die Entprellung der Impulsgeber in Software hier den Unterschied gemacht. Mit dem original-Board ist die Lautstärke gerne mal zwischen 2 benachbarten Lautstärkestufen hin- und hergesprungen. Das ist nun nicht mehr der Fall.

Medientasten nachbauen

Die Tasten für Sleep, Skip Back, Play/Pause, Skip Forward und Mute habe ich auf einem 2. Layer auf die umliegenden Tasten verteilt. Dieser 2. Layer wird ebenfalls wie der Base-Layer in der keymap.c konfiguriert.

Tastenzuordnung Mediafunktionen

Als Modkey hab ich die rechte Super-Taste gewählt, da ich sie so gut wie nie verwende. Ich könnte tatsächlich nicht einmal sagen, ob eine sinnvolle Funktion auf meinem Fedora drauf gelegt ist. Ich verwende den TT(_MEDIA) Modus, der sowohl als momentary Switch funktioniert, allerdings auch einen dauerhaften Wechsel mittels double-tapping erlaubt. ( „tapping“: {„toggle“: 2} )

#define _BASE 0
#define _MEDIA 1

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [_BASE] = LAYOUT_fullsize_iso(
        KC_ESC,           KC_F1,   KC_F2,   KC_F3,   KC_F4,   KC_F5,   KC_F6,   KC_F7,   KC_F8,   KC_F9,   KC_F10,     KC_F11,  KC_F12,     KC_PSCR, KC_SCRL, KC_PAUS,
        KC_GRV,  KC_1,    KC_2,    KC_3,    KC_4,    KC_5,    KC_6,    KC_7,    KC_8,    KC_9,    KC_0,    KC_MINS,    KC_EQL,  KC_BSPC,    KC_INS,  KC_HOME, KC_PGUP,    KC_NUM,  KC_PSLS, KC_PAST, KC_PMNS,
        KC_TAB,  KC_Q,    KC_W,    KC_E,    KC_R,    KC_T,    KC_Y,    KC_U,    KC_I,    KC_O,    KC_P,    KC_LBRC,    KC_RBRC,             KC_DEL,  KC_END,  KC_PGDN,    KC_P7,   KC_P8,   KC_P9,   KC_PPLS,
        KC_CAPS, KC_A,    KC_S,    KC_D,    KC_F,    KC_G,    KC_H,    KC_J,    KC_K,    KC_L,    KC_SCLN, KC_QUOT,    KC_NUHS, KC_ENT,                                   KC_P4,   KC_P5,   KC_P6,
        KC_LSFT, KC_NUBS, KC_Z,    KC_X,    KC_C,    KC_V,    KC_B,    KC_N,    KC_M,    KC_COMM, KC_DOT,  KC_SLSH,             KC_RSFT,             KC_UP,               KC_P1,   KC_P2,   KC_P3,   KC_PENT,
        KC_LCTL, KC_LGUI, KC_LALT,                            KC_SPC,                             KC_RALT, TT(_MEDIA), KC_APP,  KC_RCTL,    KC_LEFT, KC_DOWN, KC_RGHT,    KC_P0,            KC_PDOT
    ),
    [_MEDIA] = LAYOUT_fullsize_iso(
        KC_TRNS,              KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS, KC_TRNS, KC_TRNS,    KC_TRNS,   KC_TRNS,  KC_SLEP,
        KC_TRNS,  KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS, KC_TRNS, KC_TRNS,    KC_TRNS,   KC_TRNS,  KC_TRNS,    KC_MPRV,   KC_MPLY,   KC_MNXT,   KC_MUTE,
        KC_TRNS,  KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS, KC_TRNS,             KC_TRNS,   KC_TRNS,  KC_TRNS,    KC_TRNS,   KC_TRNS,   KC_TRNS,   KC_TRNS,
        KC_TRNS,  KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS, KC_TRNS, KC_TRNS,                                     KC_TRNS,   KC_TRNS,   KC_TRNS,
        KC_TRNS,  KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,          KC_TRNS,               KC_TRNS,              KC_TRNS,   KC_TRNS,   KC_TRNS,   KC_TRNS,
        KC_TRNS,  KC_TRNS,    KC_TRNS,                            KC_TRNS,                            KC_TRNS,    KC_TRNS,    KC_TRNS,    KC_TRNS,                      KC_TRNS,   KC_TRNS,  KC_TRNS,    KC_TRNS,              KC_TRNS
    )
};Code-Sprache: C++ (cpp)

Fazit

War die Reparatur gemessen am Zeitaufwand wirtschaftlich sinnvoll? Auf keinen Fall! Ein Neukauf (den ich zusätzlich auch getätigt habe) wäre schneller, einfacher und sinnvoller gewesen. Aber ich habe dabei etwas gelernt und sogar eine Portion Spaß dabei. Und ich habe ein tolles Stück Technik vor der Müllhalde bewahrt.

Und noch besser: Ich habe mit den Möglichkeiten, dynamisch Keyboard-Layer umzuschalten gerade erst einmal an der Oberfläche der Möglichkeiten gekratzt, die sich daraus ergeben. Da werde ich bestimmt noch ein wenig an meiner Keymap feilen und optimieren.

Und falls jemand mal einen ähnlichen Rettungsversuch an einer solchen Tastatur durchführen möchte, gibts meine keyboard-Definition auf meinem Branch auf github. Vielleicht findet man sie aber auch in Zukunft im master branch von qmk_firmware.

Vollständig funktionsrestaurierte Tastatur. Jetzt mit Extrafunktionen :-)
Vollständig funktionsrestaurierte Tastatur. Jetzt mit Extrafunktionen

Autor

Seit Kindheitstagen ist der Computer sein Begleiter. Was mit Linux anfing, wurde 2005 ein/e Beruf/ung, die weit über den Arbeitsplatz hinausgeht. Durch stetige Weiterentwicklung fasste er auch im *BSD Segment Fuß und bietet mittlerweile professionelle Lösungen im Bereich Hosting, Networking und Infrastruktur an. Als Ausgleich beschäftigt er sich neben Computerspielen mit der Fotografie.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.