Sherbet: clavier de jeu ergonomique

Traduction d'un article du blog de bricolage de Billiam

Quelque temps après que mon Logitech G13 ne soit plus produit, il est tombé en panne avec moi et j'ai décidé de développer un remplacement pour celui-ci, que Sherbet a appelé.

Tout d'abord - ce qui s'est passé:


Clavier avec joystick.Imprimer les

fichiers et les instructions de montage: www.prusaprinters.org/prints/5072-sherbet-gaming-keypad

Conception


Je voulais créer un joystick analogique comme le G13 et j'ai décidé d'inclure plusieurs améliorations ergonomiques des autres claviers dans le projet - clavier Dactyl , Dactyl Manuform , Kinesis Advantage et Ergodox . Plus précisément - le décalage des touches de la verticale, le décalage en hauteur, la courbure des colonnes et une inclinaison plus pratique.

J'ai choisi des commutateurs de clavier à basse vitesse (NovelKeys Kailh Chocs linéaires) pour réduire la hauteur du clavier - en particulier parce que mon bureau est plus haut que ce dont vous avez besoin pour un ensemble de clavier confortable et qu'il y a une grande étagère en dessous. Entre l'étagère et le dessus de table, il reste environ 10 cm, à la fois pour le clavier et pour la brosse. Si vous n'avez aucun problème avec l'endroit, je recommande un interrupteur plus compatible - alors vous n'aurez pas de problème avec le choix des boutons pour cela. Je recommande également de commencer par Dactyl ou Dactyl-Manuform, car ce projet m'a pris beaucoup plus de temps et d'efforts que je ne l'aurais imaginé.

J'ai commencé par modéliser les clés en fonction des spécifications de Kailh pour les commutateurs avant même qu'elles ne me soient parvenues, puis j'ai essayé de trouver une courbure de colonne pratique, j'ai imprimé quelques versions de test, puis j'en ai imprimé quelques autres pour vérifier l'indentation verticale. C'est ce que j'ai obtenu:


Sélection des rayons de courbure


Disposition de la sélection des haut-parleurs et hauteur des colonnes


Touches de conception, une vue de la


vue ¾ d'en haut, on peut voir un haut-parleur


décalé Vue de face, vu le

schéma choisi de la hauteur des colonnes , j'ai commencé à concevoir des connecteurs pour des commutateurs qui doivent réussir plaque de support principale.


Modèle de plaque à clés


Première impression après retrait et nettoyage des supports


Plaque avec interrupteurs


Après l'ajout de clés

J'ai pris différents angles du clavier et je me suis arrêté à un angle d'environ 20 degrés. Et cela a de nouveau été fait pour qu'il y ait une place au-dessus du clavier. Il serait plus pratique de rendre l'angle un peu plus grand, mais c'est toujours plus pratique que le G13 plat et mon clavier ergonomique actuel. Dans Fusion 360, j'ai rendu l'angle d'inclinaison modifiable pour que le reste du projet s'y adapte. Après avoir compliqué le projet, ce paramètre ne pouvait plus être configuré sans casser les autres.


Plaque de support imprimée pour la sélection d'inclinaison

Support de brosse


J'ai ensuite commencé à travailler sur le porte-balais. J'avais besoin d'un support confortable adapté à ma main et je voulais lui fabriquer un moule. J'ai fait un support d'argile d'argile, puis utilisé le package photogrammétrique Meshroom (et un tas de photos) pour créer un modèle 3D, puis je l'ai mis à l' échelle pour que sa taille corresponde à l'original.


Approximation approximative du support à l'aide de pâte polymère.


Beaucoup de photos.


Séparation du support avec des textures de Meshroom.


Le modèle a été importé dans Fusion 360.

Ensuite, j'ai parcouru les contours principaux du modèle, je l'ai lissé et j'ai obtenu l'impression souhaitée:


un modèle numérisé avec un modèle 3D superposé


de pâte polymère à côté. avec une version lissée et imprimée

C'était intéressant de le faire, et le résultat était pratique, mais j'ai trouvé que lors de l'impression, tous ces plis empêchaient la main de bouger, donc les versions ultérieures sont toutes plates. Bien…

Logement


Puis j'ai commencé à travailler sur le cas général de l'appareil, et cela s'est avéré être l'étape la plus difficile de toutes. Je travaille toujours de manière incertaine en CAO et je n’ai jamais rien fait de si compliqué auparavant. Les tentatives pour trouver des moyens de décrire les courbes composites et de relier les deux surfaces se sont avérées très difficiles et ont pris la plupart du temps dans ce projet.


Un modèle informatique du boîtier avec un joystick Arduino et des boutons de pouce.

Je me suis également trouvé un environnement plus pratique pour le rendu , grâce auquel les images sont devenues meilleures.


Modèle de boîtier d'ordinateur refait Modèle de


boîtier d'ordinateur, voir,

J'ai imprimé uniquement la zone du pouce pour vérifier sa commodité et son emplacement. Tout s'est avéré normal, mais le module du joystick était trop volumineux pour être placé exactement là où il serait le meilleur du point de vue de l'ergonomie.


Un joystick imprimé avec des boutons.

Au lieu de cela, j'ai acheté un contrôleur Joy-Con beaucoup plus petit pour la Nintendo Switch auprès d'un fabricant tiers, qui peut être placé beaucoup plus près des touches, et il y a encore de la place pour la connexion. Il se connecte à un câble plat (moins pratique) de 5 fils 0,5 mm. J'ai pris la carte d'interface pour 6 contacts sur Amazon, mais il est beaucoup moins cher de les prendre sur eBay ou AliExpress.


Comparaison des


joysticks par Joy-Con Joystick avec interrupteurs


Boîtier modifié pour un petit joystick

Ayant fini avec une coque, j'ai ajouté le support pour les composants électroniques. Pour le joystick, il a fallu réaliser un petit panneau de protection, vissé sur le côté du boîtier. Pour le microcontrôleur Teensy, j'ai fait un support, dans lequel il s'insère simplement fermement, et des vis pour cela. Espace supplémentaire pour les pinces en plastique et trous de 4 mm pour fixer le support pour la brosse.

J'utilise également l'interface micro USB pour le contrôleur USB principal afin que le microcontrôleur ne s'use pas et ne soit pas endommagé.


L'intérieur du boîtier,

je pense, pour toute cette phase de conception, cela a pris un total d'un mois. Je n'essaierai pas de deviner le nombre d'heures qui y ont été consacrées - disons qu'il y en avait «beaucoup».

Impression


Après avoir passé tant de temps à concevoir et tester, il m'a semblé que l'impression finale du projet était ennuyeuse et sans incident.


L'impression avec une résolution de 0,2 mm sur Maker Select Plus a pris 15 heures


Post-traitement: suppression des sauvegardes et nettoyage. Les dommages se sont avérés minimes.


La partie supérieure du boîtier avec l'électronique installée.J'ai

également imprimé le couvercle avec du plastique blanc et collé un revêtement en liège. Pour connecter le couvercle au corps, j'utilise des vis M3 et un manchon d'accouplement fileté inséré dans le plastique par chauffage .


Housse de liège antidérapante

La peinture


Au cours de la conception, j'ai examiné plusieurs solutions de couleur et j'ai finalement opté pour une solution similaire. Il n'y a pas beaucoup d'options de couleur, car les touches sont uniquement en noir et blanc.


Projet de coloration

Pour la finition finale, j'ai d'abord poncé la pièce avec du papier de verre grain 220 pour lisser les bandes des couches et d'autres problèmes, puis j'ai tout recouvert d'un apprêt.


La première couche de terre

Après l'apprêt, j'ai utilisé du mastic de Bondo, poncé la pièce (papier de verre 220 et 600), puis à nouveau recouvert d'apprêt, de mastic et poncé à nouveau.


Après deux passes avec un apprêt et du mastic, et un ponçage dur.


Une autre couche, avec un apprêt blanc

, j'ai fait une bande avec un mince film de vinyle, puis j'ai recouvert cet endroit de couleur rose avec un pistolet.


Boîtier et résidus de peinture

Après la peinture, j'ai enduit la pièce de 4 à 5 couches de revêtement brillant, puis l'ai poncée avec du grain 1200 pour éliminer la poussière, les peluches et les insectes, puis j'ai à nouveau verni.

Il a l'air bien, mais la rugosité et les taches de vernis en excès sont visibles. J'ai poli les pires endroits un peu avec du papier de verre 1200, puis je l'ai poli avec un composé spécial.


Après meulage et polissage

Assemblée


J'ai fait des boutons pour l'orientation des stores en appuyant sur des billes en céramique de 1,5 mm des roulements dans les touches. La photo montre un trou trop grand et un trop petit, dans lequel j'ai pressé la balle (sans colle). Je ne sais pas, il vaudrait mieux l'insérer dans un grand trou avec de la colle que de déformer le plastique en y poussant la balle.



Après avoir épuisé toutes les tâches qui pourraient m'aider à gérer la procrastination, j'ai commencé à connecter des fils, en commençant par des rangées et des colonnes de clés.

Je n'ai pas trouvé de fils plus fins dans les magasins locaux, et le seul fil unipolaire vendu était de 22 awg [ section transversale de 0,325 mm2. / environ. perev.] - et il serait trop difficile de se plier autour des décalages de colonne et de le fourrer dans un petit espace entre le boîtier et les commutateurs. Au lieu de cela, j'ai utilisé un fil toronné de 28 awg [ section 0,089 mm.kv. / environ. perev. ], qui a été tiré d'un câble plat, dénudé avec une pince à dénuder, puis fait des boucles aux extrémités. Avec un câble unipolaire plus fin, tout serait plus simple.


Interrupteurs à clé avec lignes et colonnes soudées


Lignes et colonnes connectées à un câble plat Des lignes, des colonnes, un joystick et une carte d'interface USB sont connectés




J'ai fait un porte-brosse fixé à deux vis M4, qui sont vissées dans un manchon inséré dans le plastique du corps principal par chauffage. Après avoir installé les raccords, il s'est avéré que les trous ne correspondent pas très bien, donc je ne peux pas encore les assembler. Je prévois de retaper le boîtier avec le trou dans lequel l'écrou M4 sera inséré, il sera plus facile de l'aligner.

En pratique, vous devez en quelque sorte sécuriser le clavier sur la table. Même avec un fond en liège et un poids supplémentaire, le clavier se déplace lorsque vous utilisez le joystick.

PS: J'ai refait et retapé le support pour qu'il utilise deux écrous M4, et tout fonctionne bien.

Terminé!



Boîtier prêt à l'emploi et support de brosse temporaire


Boîtier, vue de dessous

Firmware


Initialement, j'avais prévu d'utiliser QMK comme firmware, en utilisant une demande de pool avec un support joystick qui n'est pas encore entré dans la branche principale. Cependant, QMK ne prend pas très bien en charge les nouveaux contrôleurs ARM Teensy (versions supérieures à 3.2). Le patch suivant ne prend pas encore en charge les contrôleurs ARM.

Si ce correctif est implémenté, ainsi que le support ARM, je terminerai et publierai la version avec QMK. En attendant, j'ai esquissé un croquis pour Arduino, basé sur le travail de quelqu'un d'autre.

Il a deux modes, l'un est la disposition QWERTY standard et un joystick avec un bouton, et le second est l'endroit où toutes les touches sont affectées aux boutons du joystick. En conséquence, il y a suffisamment de boutons pour configurer le configurateur du contrôleur Steam, et il peut être utilisé comme un appareil XInput avec la prise en charge d'une plus large gamme de jeux.

Code facultatif pour donner un nom à l'appareil
// , . , 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