Sorbet: Ergonomische Gaming-Tastatur

Übersetzung eines Artikels aus dem Billiam Do- it - yourself- Blog

Einige Zeit, nachdem mein Logitech G13 nicht mehr produziert wurde, brach er mit mir zusammen und ich beschloss, einen Ersatz dafür zu entwickeln, den Sherbet nannte.

Zuerst - was ist passiert:


Tastatur mit Joystick.

Dateien und Montageanleitungen drucken : www.prusaprinters.org/prints/5072-sherbet-gaming-keypad

Design


Ich wollte einen analogen Daumen-Joystick wie den G13 herstellen und entschied mich, einige ergonomische Verbesserungen anderer Tastaturen in das Projekt aufzunehmen - Dactyl-Tastatur , Dactyl-Manuform , Kinesis Advantage und Ergodox . Insbesondere - die Verschiebung der Tasten von der Vertikalen, die Verschiebung der Höhe, die Krümmung der Säulen und eine bequemere Neigung.

Ich habe Tastaturschalter mit niedriger Geschwindigkeit (lineare NovelKeys Kailh Chocs) gewählt, um die Höhe der Tastatur zu verringern - insbesondere, weil mein Schreibtisch höher ist, als Sie für ein komfortables Tastaturset benötigen, und sich darunter ein großes Regal befindet. Zwischen dem Regal und der Tischplatte verbleiben ca. 10 cm, sowohl für die Tastatur als auch für den Pinsel. Wenn Sie keine Probleme mit dem Ort haben, empfehle ich einen kompatibleren Schalter - dann werden Sie keine Probleme mit der Auswahl der Tasten dafür haben. Ich empfehle auch, mit Dactyl oder Dactyl-Manuform zu beginnen, da dieses Projekt viel mehr Zeit und Mühe gekostet hat, als ich erwartet hätte.

Ich begann damit, Schlüssel basierend auf Kailhs Spezifikationen für die Schalter zu modellieren, bevor sie überhaupt zu mir kamen. Dann versuchte ich, eine geeignete Spaltenkrümmung zu finden, druckte einige Testversionen und druckte dann einige weitere, um die vertikale Einkerbung zu überprüfen. Das habe ich bekommen:


Auswahl der Biegeradien Lautsprecherauswahllayout


und Spaltenhöhe


Designtasten, eine Ansicht der ¾-


Ansicht von oben, man kann einen Schaltlautsprecher sehen Vorderansicht


, gesehenes

Schema für die Höhe der ausgebreiteten Spalten , ich begann mit dem Entwerfen von Steckverbindern für Schalter, deren Erfolg erfolgreich sein muss Hauptträgerplatte.


Schlüsselplattenmodell


Zuerst nach dem Entfernen und Reinigen der Stützen drucken.


Platte mit Schaltern.


Nach dem Hinzufügen von Schlüsseln

Ich nahm verschiedene Winkel der Tastatur auf und blieb in einem Winkel von etwa 20 Grad stehen. Und dies wurde erneut gemacht, so dass es einen Platz über der Tastatur gab. Es wäre bequemer, den Winkel etwas größer zu machen, aber es ist immer noch bequemer als das flache G13 und meine aktuelle ergonomische Tastatur. In Fusion 360 habe ich den Neigungswinkel so geändert, dass sich der Rest des Projekts daran anpasst. Nach der Komplikation des Projekts konnte dieser Parameter nicht mehr konfiguriert werden, ohne andere zu beschädigen.


Gedruckte Trägerplatte zur Neigungsauswahl

Bürstenständer


Dann fing ich an, am Bürstenständer zu arbeiten. Ich brauchte einen bequemen Ständer, der zu meiner Hand passte, und ich wollte eine Form dafür machen. Ich habe einen Ton-Ton-Ständer hergestellt, dann mit dem photogrammetrischen Meshroom- Paket (und einer Reihe von Fotos) ein 3D-Modell erstellt und es dann so skaliert, dass seine Größe dem Original entspricht.


Grobe Annäherung an den Stand mit Fimo .


Viele Fotos. Rahmen


des Ständers mit Texturen aus Meshroom.


Das in Fusion 360 importierte Modell.

Dann ging ich einfach die Hauptkonturen des Modells entlang, glättete es und erhielt den gewünschten Druck:


Ein gescanntes Modell mit einem überlagerten 3D-


Modell aus Fimo daneben mit einer geglätteten und gedruckten Version

Es war interessant, dies zu tun, und das Ergebnis war praktisch, aber dann stellte ich fest, dass all diese Biegungen beim Drucken verhindern, dass sich die Hand bewegt, sodass die späteren Versionen alle flach sind. Gut…

Gehäuse


Dann fing ich an, am allgemeinen Fall des Geräts zu arbeiten, und dies stellte sich als die schwierigste Phase von allen heraus. Ich arbeite immer noch unsicher im CAD und habe noch nie etwas so Kompliziertes getan. Versuche, Wege zu finden, um die zusammengesetzten Kurven zu beschreiben und die beiden Oberflächen zu verbinden, erwiesen sich als sehr schwierig und dauerten in diesem Projekt die meiste Zeit.


Ein Computermodell des Gehäuses zusammen mit einem Arduino-Joystick und Daumentasten.

Ich fand auch für mich eine bequemere Umgebung zum Rendern , wodurch die Bilder besser wurden.


Überarbeitetes Computergehäusemodell


Computergehäusemodell, Ansicht in,

Ich habe nur den Daumenbereich gedruckt, um die Bequemlichkeit und Position zu überprüfen. Alles stellte sich als normal heraus, aber das Joystick-Modul war zu voluminös, um genau dort platziert zu werden, wo es aus ergonomischer Sicht am besten wäre.


Ein gedruckter Joystick mit Tasten.

Stattdessen habe ich einen viel kleineren Joy-Con-Controller für den Nintendo Switch von einem Dritthersteller gekauft, der viel näher an den Tasten platziert werden kann und noch Platz für Verbindungen bietet. Es wird an ein (weniger praktisches) Flachkabel mit 5 Drähten von 0,5 mm angeschlossen. Ich habe die Schnittstellenkarte für 6 Kontakte bei Amazon genommen, aber es ist viel billiger, sie von eBay oder AliExpress zu nehmen.


Vergleich von


Joysticks mit Joy-Con Joystick mit Schaltern


Modifiziertes Gehäuse für einen kleinen Joystick

Nachdem ich mit einer Schale fertig war, fügte ich Unterstützung für elektronische Komponenten hinzu. Für den Joystick musste eine kleine Schutzplatte hergestellt werden, die an der Seite des Gehäuses angeschraubt war. Für den Teensy-Mikrocontroller habe ich einen Halter hergestellt, in den er einfach fest sitzt , und Schrauben dafür. Zusätzlicher Platz für Kunststoffklammern und 4 mm Löcher zum Befestigen des Ständers für die Bürste.

Ich verwende auch die Micro-USB-Schnittstelle für den Haupt-USB-Controller, damit sich der Mikrocontroller nicht abnutzt und beschädigt wird. Ich denke, das


Innere des Falles

hat während der gesamten Entwurfsphase insgesamt einen Monat gedauert. Ich werde nicht versuchen, die Anzahl der Stunden zu erraten, die dafür aufgewendet wurden - sagen wir, es gab "viele von ihnen".

Drucken


Nachdem ich so viel Zeit mit Entwerfen und Testen verbracht hatte, schien mir der endgültige Druck des Projekts langweilig und ohne Zwischenfälle zu sein.


Das Drucken mit einer Auflösung von 0,2 mm auf Maker Select Plus dauerte 15 Stunden.


Nachbearbeitung: Entfernen von Backups und Reinigen. Der Schaden erwies sich als gering.


Der obere Teil des Gehäuses mit installierter Elektronik.

Ich habe auch die Abdeckung mit weißem Kunststoff bedruckt und eine Korkbeschichtung darauf geklebt. Um die Abdeckung mit dem Gehäuse zu verbinden, verwende ich M3-Schrauben und eine mit Gewinde versehene Gegenhülse , die durch Erhitzen in den Kunststoff eingeführt wird .


Rutschfeste Korkabdeckung

Gemälde


Während des Entwurfs ging ich mehrere Farblösungen durch und entschied mich schließlich für eine ähnliche. Es gibt nicht viele Farboptionen, da die Tasten nur schwarzweiß sind.


Farbprojekt

Für das endgültige Finish habe ich das Teil zuerst mit 220er Schleifpapier geschliffen, um die Streifen von den Schichten und anderen Problemen zu glätten, und dann alles mit einer Grundierung bedeckt.


Die erste Erdschicht

Nach der Grundierung habe ich Kitt von Bondo verwendet, das Teil (Schleifpapier 220 und 600) geschliffen, dann wieder mit Grundierung, Kitt bedeckt und erneut geschliffen.


Nach zwei Durchgängen mit Grundierung und Kitt und hartem Schleifen.


Eine weitere Schicht mit weißer Grundierung

machte ich einen Streifen mit einer dünnen Vinylfolie und bedeckte diese Stelle dann mit rosa Farbe aus einer Spritzpistole.


Gehäuse- und Farbreste

Nach dem Lackieren habe ich das Teil mit 4-5 Schichten glänzender Beschichtung beschichtet und es dann mit 1200er Körnung geschliffen, um Staub, Flusen und Insekten zu entfernen, und dann erneut lackiert.

Es sieht gut aus, aber die Rauheit und Flecken von überschüssigem Lack sind sichtbar. Ich habe die schlechtesten Stellen ein wenig mit Sandpapier 1200 poliert und dann mit einer speziellen Mischung poliert.


Nach dem Schleifen und Polieren

Versammlung


Ich machte Pickel zur blinden Orientierung, indem ich 1,5 mm Keramikkugeln aus den Lagern in die Tasten drückte. Das Foto zeigt ein zu großes und ein zu kleines Loch, in das ich den Ball gedrückt habe (ohne Kleber). Ich weiß nicht, es wäre besser, es mit Klebstoff in ein großes Loch einzuführen, als den Kunststoff durch Drücken des Balls dorthin zu verformen.



Als ich alle Aufgaben erschöpft hatte, die mir helfen würden, den Aufschub weiter zu bewältigen, begann ich, Drähte anzuschließen, beginnend mit Zeilen und Spalten von Schlüsseln.

In den örtlichen Geschäften fand ich keine dünneren Drähte, und der einzige einadrige Draht, der verkauft wurde, war 22 awg [ Querschnitt 0,325 mm². / ca. perev.] - und es wäre zu schwierig, sich um die Säulenversätze zu biegen und sie in einen kleinen Raum zwischen dem Gehäuse und den Schaltern zu stopfen. Stattdessen habe ich einen 28-AWG-Litzendraht verwendet [ Querschnitt 0,089 mm.kv. / ca. perev. ], das aus einem Flachkabel gezogen, mit einem Abisolierer abgezogen und dann an den Enden Schlaufen gemacht wurden. Mit einem dünneren einadrigen Kabel wäre alles einfacher.


Schlüsselschalter mit gelöteten Zeilen und Spalten Mit


einem Flachkabel verbundene Zeilen und Spalten Zeilen, Spalten, ein Joystick und eine USB-Schnittstellenkarte sind verbunden




Ich habe einen Bürstenständer an zwei M4-Schrauben befestigt, die durch Erhitzen in eine Hülse geschraubt werden, die in den Kunststoff des Hauptkörpers eingeführt wird. Nach dem Einbau der Kupplungen stellte sich heraus, dass die Löcher nicht sehr gut zusammenpassen, sodass ich sie noch nicht zusammenbauen kann. Ich habe vor, das Gehäuse mit dem Loch, in das die M4-Mutter eingesetzt wird, erneut einzugeben. Es ist einfacher, es auszurichten.

In der Praxis müssen Sie die Tastatur irgendwie auf dem Tisch sichern. Selbst mit einem Korkboden und zusätzlichem Gewicht verschiebt sich die Tastatur bei Verwendung des Joysticks.

PS: Ich habe den Ständer überarbeitet und neu getippt, sodass zwei M4-Muttern verwendet wurden und alles einwandfrei funktioniert.

Erledigt!



Fertiges Gehäuse und temporärer Bürstenhalter


Gehäuse, Ansicht von unten

Firmware


Ursprünglich plante ich, QMK als Firmware zu verwenden und eine Poolanforderung mit einer Joystick-Unterstützung zu verwenden, die noch nicht in der Hauptniederlassung enthalten war. QMK unterstützt die neuen ARM Teensy-Controller (Versionen größer als 3.2) jedoch nicht sehr gut. Der folgende Patch unterstützt ARM-Controller noch nicht.

Wenn dieser Patch sowie die ARM-Unterstützung implementiert sind, werde ich die Version mit QMK fertigstellen und veröffentlichen. In der Zwischenzeit habe ich eine Skizze für Arduino entworfen, die auf der Arbeit eines anderen basiert.

Es gibt zwei Modi, einen ist das Standard-QWERTZ-Layout und einen Joystick mit einer Taste, und im zweiten sind alle Tasten den Joystick-Tasten zugeordnet. Daher gibt es genügend Schaltflächen, um den Steam-Controller-Konfigurator zu konfigurieren, und er kann als XInput-Gerät verwendet werden, das eine größere Auswahl an Spielen unterstützt.

Optionaler Code, um dem Gerät einen Namen zu geben
// , . , sherbet.ino.
#include «usb_names.h»

#define PRODUCT_NAME {'s', 'h', 'e', 'r', 'b', 'e', 't'}
#define PRODUCT_NAME_LEN 7

struct usb_string_descriptor_struct usb_string_product_name = {
2 + PRODUCT_NAME_LEN * 2,
3,
PRODUCT_NAME
};



Firmware
/*
Original programming by Stefan Jakobsson, 2019
Released to public domain
forum.pjrc.com/threads/55395-Keyboard-simple-firmware
*/
/*
Copyright 2019 Colin Fein
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the «Software»), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED «AS IS», WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

// Use USB Type: Keybord+Mouse+Joystick

#include <Bounce2.h>

const int ROW_COUNT = 4; //Number of rows in the keyboard matrix
const int COL_COUNT = 6; //Number of columns in the keyboard matrix

const int DEBOUNCE = 5; //Adjust as needed: increase if bouncing problem, decrease if not all keypresses register; not less than 2
const int SCAN_DELAY = 5; //Delay between scan cycles in ms

const int JOYSTICK_X_PIN = 14; // Analog pin used for the X axis
const int JOYSTICK_Y_PIN = 15; // Analog pin used for the Y axis
const bool REVERSE_X = true; // Reverses X axis input
const bool REVERSE_Y = true; // Reverses Y axis input
const int MIN_X = 215; // Minimum range for the X axis
const int MAX_X = 780; // Maxixmum range for the X axis
const int MIN_Y = 280; // Minimum range for the Y axis
const int MAX_Y = 815; // Maximum range for the Y axis
const int BUTTON_COUNT = 1; // Number of joystick buttons

const int JOY_MIN = 0;
const int JOY_MAX = 1023;

Bounce buttons[BUTTON_COUNT];
Bounce switches[ROW_COUNT * COL_COUNT];

boolean buttonStatus[ROW_COUNT * COL_COUNT + BUTTON_COUNT]; //store button status so that inputs can be released
boolean keyStatus[ROW_COUNT * COL_COUNT]; //store keyboard status so that keys can be released

const int rowPins[] = {3, 2, 1, 0}; //Teensy pins attached to matrix rows
const int colPins[] = {11, 10, 9, 8, 7, 6}; //Teensy pins attached to matrix columns
const int buttonPins[] = {12}; //Teensy pins attached directly to switches

int axes[] = {512, 512};

int keyMode = true; // Whether to begin in standard qwerty mode or joystick button mode

// Keycodes for qwerty input
const int layer_rows[] = {
KEY_ESC, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5,
KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T,
KEY_CAPS_LOCK, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G,
MODIFIERKEY_SHIFT, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B
};
// keystroke to use (counting from top left to top right of keypad) to switch between standard qwerty input and joystick buttons
// default uses B+5
const int mode_swap_keystroke[2] = {23, 5};
int pivoted_keystroke[2]; //rows to columns
boolean keystrokeModifier = false; //whether beginning of keystroke is active

// rows to columns
int layer[ROW_COUNT * COL_COUNT];

void setup() {
int i;
//pivot key array for row-to-column diodes
for (i = 0; i < ROW_COUNT * COL_COUNT; i++) {
layer[rotateIndex(i)] = layer_rows[i];

// create debouncers for row pins
Bounce debouncer = Bounce();
debouncer.attach(rowPins[i % ROW_COUNT]);
debouncer.interval(DEBOUNCE);
switches[i] = debouncer;
}

//convert keystroke to (pivoted) indexes
for (i = 0; i < 2; i++) {
pivoted_keystroke[i] = rotateIndex(mode_swap_keystroke[i]);
}

// create debouncers for non-matrix input pins
for (i = 0; i < BUTTON_COUNT; i++) {
Bounce debouncer = Bounce();

debouncer.attach(buttonPins[i], INPUT_PULLUP);
debouncer.interval(DEBOUNCE);
buttons[i] = debouncer;
}

// Ground first column pin
pinMode(colPins[0], OUTPUT);
digitalWrite(colPins[0], LOW);

for (i = 1; i < COL_COUNT; i++) {
pinMode(colPins[i], INPUT);
}

//Row pins
for (i = 0; i < ROW_COUNT; i++) {
pinMode(rowPins[i], INPUT_PULLUP);
}
}

void loop() {
scanMatrix();
scanJoy();
delay(SCAN_DELAY);
}

/*
Scan keyboard matrix, triggering press and release events
*/
void scanMatrix() {
int i;

for (i = 0; i < ROW_COUNT * COL_COUNT; i++) {
prepareMatrixRead(i);
switches[i].update();

if (switches[i].fell()) {
matrixPress(i);
} else if (switches[i].rose()) {
matrixRelease(i);
}
}
}

/*
Scan physical, non-matrix joystick buttons
*/
void scanJoy() {
int i;
boolean anyChange = false;

for (i=0; i < BUTTON_COUNT; i++) {
buttons[i].update();
if (buttons[i].fell()) {
buttonPress(i);
anyChange = true;
} else if (buttons[i].rose()) {
buttonRelease(i);
anyChange = true;
}
}

int x = getJoyDeflection(JOYSTICK_X_PIN, REVERSE_X, MIN_X, MAX_X);
int y = getJoyDeflection(JOYSTICK_Y_PIN, REVERSE_Y, MIN_Y, MAX_Y);
Joystick.X(x);
Joystick.Y(y);

if (x != axes[0] || y != axes[y]) {
anyChange = true;

axes[0] = x;
axes[1] = y;
}

if (anyChange) {
Joystick.send_now();
}
}

/*
Return a remapped and clamped analog value
*/
int getJoyDeflection(int pin, boolean reverse, int min, int max) {
int input = analogRead(pin);
if (reverse) {
input = JOY_MAX — input;
}

return map(constrain(input, min, max), min, max, JOY_MIN, JOY_MAX);
}
/*
Returns input pin to be read by keyScan method
Param key is the keyboard matrix scan code (col * ROW_COUNT + row)
*/
void prepareMatrixRead(int key) {
static int currentCol = 0;
int p = key / ROW_COUNT;

if (p != currentCol) {
pinMode(colPins[currentCol], INPUT);
pinMode(colPins[p], OUTPUT);
digitalWrite(colPins[p], LOW);
currentCol = p;
}
}

/*
Sends key press event
Param keyCode is the keyboard matrix scan code (col * ROW_COUNT + row)
*/
void matrixPress(int keyCode) {
if (keyMode) {
keyPress(keyCode);
} else {
buttonPress(BUTTON_COUNT + keyCode);
}

keystrokePress(keyCode);
}

/*
Sends key release event
Param keyCode is the keyboard matrix scan code (col * ROW_COUNT + row)
*/
void matrixRelease(int keyCode) {
//TODO: Possibly do not trigger keyboard.release if key not already pressed (due to changing modes)
if (keyMode) {
keyRelease(keyCode);
} else {
buttonRelease(BUTTON_COUNT + keyCode);
}

keystrokeRelease(keyCode);
}

/*
Send key press event
*/
void keyPress(int keyCode) {
Keyboard.press(layer[keyCode]);
keyStatus[keyCode]=true;
}

/*
Send key release event
*/
void keyRelease(int keyCode) {
Keyboard.release(layer[keyCode]);
keyStatus[keyCode]=false;
}

/*
Send joystick button press event
Param buttonId 0-indexed button ID
*/
void buttonPress(int buttonId) {
Joystick.button(buttonId + 1, 1);
buttonStatus[buttonId] = true;
}

/*
Send joystick button release event
Param buttonId 0-indexed button ID
*/
void buttonRelease(int buttonId) {
Joystick.button(buttonId + 1, 0);
buttonStatus[buttonId] = false;
}

/*
Listen for keystroke keys, and change keyboard mode when condition is met
*/
void keystrokePress(int keyCode) {
if (keyCode == pivoted_keystroke[0]) {
keystrokeModifier = true;
} else if (keystrokeModifier && keyCode == pivoted_keystroke[1]) {
releaseLayer();
keyMode = !keyMode;
}
}

/*
Listen for keystroke key release, unsetting keystroke flag
*/
void keystrokeRelease(int keyCode) {
if (keyCode == pivoted_keystroke[0]) {
keystrokeModifier = false;
}
}

/*
Releases all matrix and non-matrix keys; called upon change of key mode
*/
void releaseLayer() {
int i;
for (i = 0; i < ROW_COUNT * COL_COUNT; i++) {
matrixRelease(i);
}

for (i=0; i < BUTTON_COUNT; i++) {
if (buttonStatus[i]) {
buttonRelease(i);
}
}
}

/*
Converts an index in a row-first sequence to column-first
[1, 2, 3] [1, 4, 7]
[4, 5, 6] => [2, 5, 8]
[7, 8, 9] [3, 6, 9]
*/
int rotateIndex(int index) {
return index % COL_COUNT * ROW_COUNT + index / COL_COUNT;
}


All Articles