3x5 Custom Dactyl Macropad Met Draaiknop

by RandyQ2 in Circuits > Microcontrollers

1141 Views, 19 Favorites, 0 Comments

3x5 Custom Dactyl Macropad Met Draaiknop

IMG_20230122_223711__01.jpg

Ik heb een macropad met een draaiknop gemaakt, dat speciaal ontworpen is voor gebruik met Photoshop en ZBrush. De macropad bevat 14 knoppen die specifieke shortcuts bevatten voor deze programma's, 1 draaiknop die ook shortcuts bevat voor deze programma's en nog 1 knop waarmee ik kan switchen naar verschillende lagen waarbij weer andere shortcuts zijn gekoppeld aan de knoppen. Hierdoor kan je snel en efficiënt werken in deze programma's zonder steeds naar het menu te hoeven navigeren of een heel groot toetsenbord naast je te hebben.

Supplies

Onderdelen

  • Arduino Pro Micro
  • USB naar micro USB kabel
  • 1N4148 Diodes
  • Dun koperdraad
  • Jumper wires
  • Ky-040 rotary encoder
  • Krimpkousen
  • Cherry MX switches (tenminste 15)
  • Keycaps (tenminste 15)
  • Een behuizing voor de rotary encoder (ik heb hiervoor de knop van de philips htl4111b soundbar gebruikt)
  • Soldeerbout met soldeer
  • 3D-printer

Software

  • QMK MSYS
  • QMK Toolbox
  • Arduino IDE (optioneel, alleen voor prototype)
  • Visual Studio Code (kan ook een andere source-code editor zijn)

Prototype Met Breadboard En Arduino IDE

Wiring Components.jpg
8.jpg
Macropad prototype

Op de site waar ik het schema gemaakt heb, kon ik geen Arduino Pro Micro vinden. Neem de pins van de Arduino UNO gewoon over behalve pin 13. Omdat de Arduino UNO geen pin 16 heeft, heb ik deze in het schema vervangen met pin 13. Zoals te zien op het schema gaat van de rijen van de button-matrix de rode draad naar pin 16, de blauwe draad van de rij ernaast naar pin 10, en de blauwe draad van de rij daar weer naast naar pin 5 van de Arduino Pro Micro. Van de kolommen van de button-matrix gaat de rode draad naar pin 6, de gele draad naar pin 7, en de zwarte draad naar pin 8 van de Arduino Pro Micro. Bij de rotary encoder gaat de blauwe draad van de CLK pin naar pin 3, de gele draad van de DT pin naar pin 2, de rode draad van de SW pin naar pin 4, en de rode draad van de GND pin naar de GND pin op de Arduino Pro Micro.


#include <Encoder.h>
#include <Keypad.h>
#include <Keyboard.h>


// Pin numbers for the encoder
const int encoderPinA = 2;
const int encoderPinB = 3;


// Create an encoder object
Encoder myEncoder(encoderPinA, encoderPinB);


// Variable to store the previous position
long previousPosition = 0;


// Define the number of rows and columns for the keypad
const byte ROWS = 3;
const byte COLS = 3;


// Define the keys on the buttons of the keypad
char keys[ROWS][COLS] = {
    {'A','B','C'},
    {'D','E','F'},
    {'4','5','6'}
};


// Define the pins for the rows and columns of the keypad
byte rowPins[ROWS] = {16,10,5}; 
byte colPins[COLS] = {6,7,8}; 


// Initialize an instance of the Keypad class
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);


void setup() {
  // Start the Keyboard library
  Keyboard.begin();
}


void loop() {
  // Read the encoder's position
  long encoderPosition = myEncoder.read();


  // Check if the encoder has been rotated
  if (encoderPosition != previousPosition) {
    // Check the direction of rotation
    if (encoderPosition > previousPosition) {
      // Clockwise rotation
      Keyboard.press(']');
      Keyboard.release(']');
    } else {
      // Counter-clockwise rotation
      Keyboard.press('[');
      Keyboard.release('[');
    }


    // Update the previous position
    previousPosition = encoderPosition;
  }


  // Get the key pressed on the keypad
  char key = keypad.getKey();


  // If a key is pressed
  if (key) {
    // Press and release the key as if it were a real keyboard
    Keyboard.press(key);
    delay(100);
    Keyboard.release(key);
  }
}


Hierboven is mijn Arduino IDE code te zien met uitleg wat alles doet. Om deze script werkend te krijgen op de arduino pro micro volg je deze stappen:

  1. Download de externe libraries voor de rotary encoder en button-matrix van de volgende links: https://github.com/PaulStoffregen/Encoder en https://github.com/Chris--A/Keypad
  2. Voeg deze libraries toe aan de Arduino IDE door naar Sketch > Include Library > Add .ZIP Library te gaan en de gedownloade .ZIP bestanden te selecteren.
  3. Selecteer het juiste board in de Arduino IDE door naar Tools > Board > Arduino AVR Boards te gaan en Arduino Leonardo te selecteren.
  4. Sluit de Arduino Pro Micro aan op je pc en selecteer de juiste poort bij Tools > Port.
  5. Upload de code naar de Arduino Pro Micro door op Verify te klikken en vervolgens op Upload.
In het geval er een error optreedt tijdens het uploaden, kun je proberen om snel nadat je op upload hebt geklikt, met een jumper wire de GND en RST pin kort met elkaar te verbinden door met 1 uiteinde van de draad de GND pin aan te raken en met de ander de RST pin.


Obstakels

Tijdens de prototyping fase ben ik een aantal obstakels tegengekomen. In eerste instantie wilde ik de Nice!Nano gebruiken in plaats van de Arduino Pro Micro. Dit kwam omdat de Nice!Nano Bluetooth functionaliteit heeft en ook nog eens een drop-in-replacement is voor de Arduino Pro Micro, hierdoor zou ik gemakkelijk kunnen wisselen tussen deze twee zonder mijn wiring aan te hoeven passen. Helaas heb ik mijn Nice!Nano gesloopt doordat ik de bootloader heb bijgewerkt terwijl dat helemaal niet nodig was. In een poging om de Nice!Nano te repareren, heb ik geprobeerd een ST-link te gebruiken, maar mijn laptop kon de verbinding tussen de ST-link en de Nice!Nano niet vinden. Uiteindelijk heb ik ervoor gekozen om toch de Pro Micro te gebruiken.

Als tip zou ik willen geven om bij gebruik van de Nice!Nano zorgvuldig te controleren of een bootloader-update echt nodig is.

Een ander probleem waar ik tegenaan liep was dat ik allerlei verschillende voorbeelden opzocht voor de button-matrix wiring, maar niks werkte. Na nog eens de video te bestuderen van Joe Scotto over het handwiren van een keyboard aan de hand van matrix wiring (https://www.youtube.com/watch?v=hjml-K-pV4E) is het toch gelukt om een werkende button-matrix te creëren. In de matrix op het breadboard waren geen diodes nodig aangezien de buttons met 4 pins al een ingebouwde diode hebben.

Als het niet lukt om je wiring goed te krijgen zou ik de video van Joe Scotto en mijn schema goed bestuderen.

3D-modelleren Van De Behuizing

IMG_20230106_181448 (1).jpg
test models.jpg
test models2.jpg
Firstfullmodel.jpg
b7f9c2804ad62a5aab9bc09cdcc735a0.png
finalmodel.jpg
finalmodelprinted.jpg
Doosje.jpg

Ik wilde dat mijn macropad zo gevormd was dat de belasting op je pols zo min mogelijk zou zijn. Na het onderzoeken van verschillende opties, ontdekte ik dat dactyl keyboards een fijne ergonomische vorm hebben en besloot ik deze als basis voor mijn ontwerp te gebruiken. Om de knoppen op de juiste plekken te plaatsen, begon ik met het gebruiken van de website https://pashutk.com/ergopad/. Hier kon ik met elke vinger op mijn tablet tikken en werden de beste plekken voor knoppen weergegeven die gemakkelijk bereikbaar waren zonder dat ik mijn hand veel zou hoeven te bewegen. Op het 1ste plaatje is te zien hoe dit er bij mij uitzag.

Vervolgens begon ik met het modelleren van de behuizing in Maya. Hierbij maakte ik twee testmodellen: één voor de omhulling van de keyswitches waarbij één iets omhoog was getild en één voor de omhulling van de draaiknop, om te testen of deze goed zouden printen en of de keyswitches en draaiknop zouden passen (deze testmodellen zijn te zien in de 2de en 3de afbeelding). De omhulling voor de keyswitches was gelijk goed, maar de omhulling van de draaiknop moest ik een paar keer aanpassen omdat het deel waar de encoder op stond eerst te hoog was en te dun. Nadat ik goede testresultaten had zag, ging ik verder met het modelleren van de gehele behuizing. Hiervoor gebruikte ik keyboards van https://bastardkb.com/ als voorbeeld en volgde ik deze tutorial van BastardKB: https://www.youtube.com/watch?v=scoX8NZv4MI&list=WL&index=7. Ik plaatste het 1ste plaatje dat ik had gemaakt op de tablet onder de keyboard om de plekken voor de keys te modelleren zodat deze ongeveer overeenkwamen met de plekken op dat plaatje. Nadat ik klaar was met het modelleren van alle omhullingen van de keys en de uiteindelijke omhulling van de draaiknop, maakte ik alles aan elkaar vast en sneed ik de onderkant plat in Maya (dit eindresultaat is te zien in de 4de afbeelding). Omdat de omhulling van de draaiknop nu schuin was, kon ik het oorspronkelijke plankje niet gebruiken waar de encoder op zou staan, na wat dingen uit te proberen vond ik een oplossing, ik had een los plankje gemodelleerd die je met secondelijm vast zou moeten lijmen (dit plankje is te zien in het 5de plaatje). Nu was alles klaar en hoefde ik alleen nog de model van de behuizing mooi glad te krijgen, hiervoor heb ik de model ge-exporteerd naar zbrush, gaf ik aan welke randen scherp moesten blijven en heb ik vervolgens mijn model ge-subdivide. Het eindresultaat is te zien in het 6de en 7de plaatje. Ook had ik een doosje gemodelleerd waar de female header pins in de deksel gelijmd konden worden, waardoor de Pro Micro gemakkelijk in het doosje kon worden geplaatst en verbonden kon worden met de wiring. Helaas paste het doosje echter niet goed in de behuizing van de keyboard en had ik ook geen schroeven die klein genoeg waren, dus besloot ik het doosje uiteindelijk niet te gebruiken (dit doosje is te zien in het 8ste plaatje).

Als je zelf een dactyl keyboard wilt ontwerpen en modelleren raad ik aan om bastardkb te bekijken

Het in Elkaar Zetten Van De Keyboard

1.jpg
2.jpg
3.jpg
4.jpg
5.jpg
7.jpg

Voor mijn keyboard wilde ik 15 normale knoppen en 1 draaiknop. Als ik elke knop los zou verbinden aan mijn pro micro zou ik wel 34 pins nodig hebben, en zoveel pins zijn er natuurlijk niet. Daarom zijn de normale knoppen in matrix ge-wired (net zoals bij het prototype). Elke knop heeft 2 pins. Op het moment dat je voor elke rij van alle knoppen 1 van de pins met elkaar verbind en voor elke kolom van alle knoppen de andere pin met elkaar verbind, dan heb je alleen voor elke kolom en voor elke rij 1 pin nodig op de pro micro (dus in plaats van 34 pins totaal, 8 pins voor de normale knoppen + 4 pins voor de draaiknop = 12 pins totaal). Doordat er intersecties ontstaan tussen de kolommen en rijen kunnen de verschillende knoppen alsnog los van elkaar worden gelezen. Aan de hand van deze video van Joe Scotto https://www.youtube.com/watch?v=hjml-K-pV4E ben ik mijn keyboard gaan wiren.

  • Ik begon met alle keyswitches in mijn behuizing te klikken (afbeelding 1).
  • Vervolgens heb ik met koperdraad elke knop in een kolom met elkaar verbonden en heb ik deze koperdraden vast gesoldeerd aan elke pin, om dit makkelijker te maken heb ik het koperdraad steeds om de pin heen gewikkeld (afbeelding 2).
  • Hierna heb ik om alle pins die over waren een diode gewikkeld en heb ik elke diode in een rij met elkaar verbonden om zo de rijen van de matrix te creëren. Deze diodes nog niet aan elkaar vast solderen (afbeelding 3).
  • Vervolgens markeer je alle delen waar de diodes het koperdraad aanraken en doe je om die delen van de diodes een krimpkous. De koperdraden mogen namelijk de diodes niet aanraken (afbeelding 4).
  • Daarna kan je alle diodes wel vast solderen en is de matrix klaar.
  • Soldeer vervolgens draden aan elke kolom en rij en soldeer de draad van de bovenste rij (gezien van boven als je de keyboard normaal zou neerleggen) aan pin 14 van de pro micro, de draad van de middelste rij aan pin 16, de draad van de onderste rij aan pin 10, de draad van de 1ste kolom (gezien van boven als je de keyboard normaal zou neerleggen) aan pin 9, de draad van de 2de kolom aan pin 8, de draad van de 3de kolom aan pin 7, de draad van de 4de kolom aan pin 6 en de draad van de 5de kolom aan pin 5. Je kan de draden direct aan je pro micro solderen maar je zou ook de draden aan female header pins kunnen solderen zodat je de pro micro nog los zou kunnen halen zonder alle draden weer los te hoeven halen (afbeelding 5).
  • Daarna bevestig je de rotary encoder op het plankje en lijm je het plankje vast in de omhulling voor de draaiknop.
  • Tot slot verbind je met een draad de CLK pin van de encoder aan pin 3 van de pro micro en soldeer je deze vast, de DT pin aan pin 2, de SW pin aan pin 4, en de GND pin aan de GND pin. (afbeelding 6).

Om mijn keyboard dicht te maken heb ik een onderkantje uitgesneden uit karton en deze vastgeplakt aan de behuizing, maar je zou ook een onderkantje van harder materiaal kunnen maken en deze vast kunnen schroeven aan de behuizing.

Het koperdraad was erg lastig te solderen, misschien is het handiger om een ander soort draad hiervoor te gebruiken.

Programmeren Met QMK

6.jpg

Voor mijn keyboard heb ik mijn script geschreven met QMK.

QMK (Quantum Mechanical Keyboard) is een firmware voor mechanische toetsenborden. Het is een open-source project dat zich richt op het aanpassen en verbeteren van de functionaliteit van mechanische toetsenborden. Met QMK kun je bijvoorbeeld de toetsen van je toetsenbord hermappen, macros instellen, lay-outs aanpassen en nog veel meer.
QMK is geschreven in C

Om te beginnen met QMK is het handig om op deze site te kijken https://docs.qmk.fm/#/newbs en ook weer de video van Joe Scotto te bekijken https://www.youtube.com/watch?v=hjml-K-pV4E.

Na het installeren van QMK MSYS en QMK Toolbox opende ik QMK MSYS. In QMK MSYS heb ik de volgende stappen doorlopen:

  • Ik heb de command qmk new-keyboard uitgevoerd.
  • Vervolgens heb ik de keyboard een naam gegeven.
  • Daarna heb ik mijn github naam doorgegeven.
  • Hierna mijn echte naam.
  • Vervolgens wordt er gevraagd wat je keyboard layout is, aangezien ik een custom handwired keyboard heb gemaakt heb ik 51 voor none of the above aangegeven.
  • Hierna wordt er gevraagd welke microcontroller je gebruikt, voor de Arduino Pro Micro is dit atmega32u4.
  • Daarna heb ik de command cd keyboards/hier de naam die je aan de keyboard hebt gegeven uitgevoerd gevolgd door de command code . . Hierdoor worden de scripts voor de keyboard geopend in Visual Studio Code.

Om te testen of al mijn knoppen werkten heb ik een test script geschreven.

Mijn info.json script zag er zo uit:

{
    "manufacturer": "Randy Querreveld",
    "keyboard_name": "rqps",
    "maintainer": "Welfon80MG",
    "bootloader": "caterina",
    "diode_direction": "COL2ROW",
    "features": {
        "bootmagic": true,
        "command": false,
        "console": false,
        "extrakey": true,
        "mousekey": true,
        "nkro": true
    },
    "matrix_pins": { // define the pins that the columns and rows in the matrix use
        "cols": ["B5", "B4", "E6", "D7", "C6"],
        "rows": ["B3", "B2", "B6"]
    },
    "processor": "atmega32u4",
    "url": "",
    "usb": {
        "device_version": "1.0.0",
        "pid": "0x0000",
        "vid": "0xFEED"
    },
    "layouts": {
        "LAYOUT_ortho_3x5": { // define the layout of the buttons/keys in the matrix
            "layout": [
                { "matrix": [0, 0], "x": 0, "y": 0 },
                { "matrix": [0, 1], "x": 0, "y": 1 },
                { "matrix": [0, 2], "x": 0, "y": 2 },
                { "matrix": [0, 3], "x": 0, "y": 3 },
                { "matrix": [0, 4], "x": 0, "y": 4 },
                { "matrix": [1, 0], "x": 1, "y": 0 },
                { "matrix": [1, 1], "x": 1, "y": 1 },
                { "matrix": [1, 2], "x": 1, "y": 2 },
                { "matrix": [1, 3], "x": 1, "y": 3 },
                { "matrix": [1, 4], "x": 1, "y": 4 },
                { "matrix": [2, 0], "x": 2, "y": 0 },
                { "matrix": [2, 1], "x": 2, "y": 1 },
                { "matrix": [2, 2], "x": 2, "y": 2 },
                { "matrix": [2, 3], "x": 2, "y": 3 },
                { "matrix": [2, 4], "x": 2, "y": 4 }
            ]
        }
    }
}


Mijn keymap.c script zag er zo uit:

#include QMK_KEYBOARD_H

const uint16_t PROGREM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [0] = LAYOUT_ortho_3x5(
        KC_1,  KC_0,  KC_3,  KC_4,  KC_5,
        KC_6,  KC_7,  KC_8,  KC_9,  KC_A,
        KC_B,  KC_C,  KC_D,  KC_E,  KC_F
    ) /* These lines assign keys to the different positions on the keyboard.
         In this case, the key at the top left is assigned as "KC_1",
         the key to the right of it is assigned as "KC_0", and so on. */
};

#if defined(ENCODER_MAP_ENABLE)
const uint16_t PROGREM encoder_map[][NUM_ENCODERS[2] = {
    [0] = { ENCODER_CCW_CW(KC_RBRC, KC_LBRC) }, // This line assigns the encoder to control the right and left bracket keys (KC_RBRC and KC_LBRC)
};
#endif


Mijn rules.mk script zag er zo uit:

ENCODER_ENABLE = yes
ENCODER_MAP_ENABLE = yes


En tot slot zag mijn config.h script er zo uit:

#pragma once

#define ENCODERS_PAD_A { D1 } //define the encoder pins
#define ENCODERS_PAD_B { D0 }
#define ENCODER_RESOLUTION 4


Toen mijn scripts af waren moest ik in de QMK MSYS de command qmk compile -kb hier de naam van je keyboard -km default uitvoeren en vervolgens met de QMK Toolbox mijn Pro Micro flashen.

Voor het flashen moet je eerst de GND en RST pin op je pro micro kort met elkaar verbinden.


Mijn keyboard werkte nu, op het moment dat ik op een knop drukte werd de correcte keycode doorgegeven aan mijn computer. Nu moest ik alleen nog de belangrijkste shortcuts voor photoshop en zbrush aan mijn keyboard toevoegen en layers creëren.

Ik begon met het opschrijven van de belangrijkste shortcuts voor photoshop. Vervolgens heb ik een tabel gemaakt met de shortcuts die ik wilde gebruiken, elke rij is 1 knop en elke kolom is 1 layer. Zo was de bovenste rij voor de draaiknop (afbeelding 1).

Ik begon hierna met het programmeren van de eerste layer. Ik wilde voor sommige knoppen dat wanneer je de knop ingedrukt houd er iets anders gebeurde dan wanneer je snel op de knop klikt. Mijn nieuwe keymaps zagen er nu zo uit

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX COLS] = { 
    [0] = LAVOUT_ortho_3x5(
        LCTL(KC_S), RCS(KC_Z), LCTL_D_T(LCS(I)), LCTL(KC H), KC_S,
        LSFT_T(KC_L), KC_2_7(KC_K), KC_S_T(KC_E), LCTL(KC MINUS), LCTL(KC_EQUAL),
        LCIL_T(LCTL(KC_RBRC)), KC_LEFT_ALT, KC_O_T(KC_B), KC_R_T(KC_C), KC_SPACE_T(KC_V)
        )
}

Dit werkte niet en toen ik om hulp vroeg kreeg ik te horen dat dit niet mogelijk was met als uitleg: keycodes in qmk are 16 bits wide, basic keycodes are 8 bits, so to have a keycode on hold and a keycode on tap, you've now exhausted the entire space. Na nog een beetje door de qmk docs te kijken vond ik een workaround https://docs.qmk.fm/#/mod_tap. Het was namelijk mogelijk om onder andere nieuwe keycodes te creëren. Hierdoor was het toch gelukt om de eerste layer precies zo te krijgen als ik wilde. Hierna heb ik de andere layers toegevoegd en een layer cycling systeempje gecreëerd met de hulp hiervan https://github.com/qmk/qmk_firmware/pull/19069/commits/6af9db1d07ea78d9a460b3046d611443ec45f7ee. Om van mijn test script naar mijn final script te gaan heb ik alleen maar aanpassingen gemaakt in de keymap.c script

Mijn final keymap.c script is hieronder te zien. Ik heb notes toegevoegd om de code een beetje uit te leggen.

#include QMK_KEYBOARD_H

enum keycodes { // create custom keycodes
  KC_CYCLE_LAYERS = SAFE_RANGE,
  ROTATE_R,
  ROTATE_L,
  BHP
};

#define LAYER_CYCLE_START 0 //1st layer on the cycle
#define LAYER_CYCLE_END   5 //last layer on the cycle


const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { //keymapping
    [0] = LAYOUT_ortho_3x5(
        LCTL(KC_S),   RCS(KC_Z),   LT(C(KC_D),RCS(KC_I)),  LCTL(KC_H),   KC_TRNS,
        LSFT_T(KC_L),    LT(2,KC_K),   LT(5,KC_E),   LCTL(KC_MINUS),    LCTL(KC_EQUAL),
        LT(KC_LEFT_CTRL, C(KC_RBRC)),   KC_LEFT_ALT,   LT(0,KC_B),    LT(KC_R,KC_C),  LT(KC_SPACE,KC_V)
    ),
   
    [1] = LAYOUT_ortho_3x5(
        LCTL(KC_S),   RCS(KC_Z),   LT(C(KC_D),RCS(KC_I)),  LCTL(KC_H),   KC_TRNS,
        LSFT_T(KC_L),    LT(2,KC_K),   LT(5,KC_E),   KC_LBRC,    KC_RBRC,
        LT(KC_LEFT_CTRL, C(KC_RBRC)),   KC_LEFT_ALT,   LT(0,KC_B),    LT(KC_R,KC_C),  LT(KC_SPACE,KC_V)
    ),
   
    [2] = LAYOUT_ortho_3x5(
        LT(C(KC_S), C(KC_MINUS)),   RCS(KC_Z),   LT(C(KC_D),RCS(KC_I)),  LCTL(KC_H),   KC_TRNS,
        LSFT_T(KC_L),    LT(2,KC_K),   LT(5,KC_E),   KC_LBRC,    KC_RBRC,
        LT(KC_LEFT_CTRL, C(KC_RBRC)),   LT(KC_LEFT_ALT, C(KC_EQUAL)),   LT(0,KC_B),    LT(KC_R,KC_C),  LT(KC_SPACE,KC_V)
    ),

   [3] = LAYOUT_ortho_3x5(
        LT(C(KC_S), C(KC_MINUS)),   RCS(KC_Z),   LT(C(KC_D),RCS(KC_I)),  LCTL(KC_H),   KC_TRNS,
        LSFT_T(KC_L),    LT(2,KC_K),   LT(5,KC_E),   KC_LBRC,    KC_RBRC,
        LT(KC_LEFT_CTRL, C(KC_RBRC)),   LT(KC_LEFT_ALT, C(KC_EQUAL)),   LT(0,KC_B),    LT(KC_R,KC_C),  LT(KC_SPACE,KC_V)
    ),
 
   [4] = LAYOUT_ortho_3x5(
        LT(C(KC_S), C(KC_MINUS)),   RCS(KC_Z),   LT(C(KC_D),RCS(KC_I)),  LCTL(KC_H),   KC_TRNS,
        LSFT_T(KC_L),    LT(2,KC_K),   LT(5,KC_E),   KC_LBRC,    KC_RBRC,
        LT(KC_LEFT_CTRL, C(KC_RBRC)),   LT(KC_LEFT_ALT, C(KC_EQUAL)),   LT(0,KC_B),    LT(KC_R,KC_C),  LT(KC_SPACE,KC_V)
    ),

   [5] = LAYOUT_ortho_3x5(
        BHP,   KC_1,   KC_6,  KC_F,   KC_TRNS,
        LSFT_T(KC_2),    KC_5,   KC_4,   KC_LBRC,    KC_RBRC,
        KC_LEFT_CTRL,   KC_LEFT_ALT,   KC_3,    LSFT(KC_X),  LCTL(KC_D)
    )
};

#if defined(ENCODER_MAP_ENABLE)
const uint16_t PROGMEM encoder_map[6][NUM_ENCODERS][2] = {
    [0] = {ENCODER_CCW_CW(KC_RBRC, KC_LBRC)},   // if encoder is turned clockwise it will do ] en when turned counter-clockwise it will do [

    [1] = {ENCODER_CCW_CW(LCTL(KC_EQUAL), LCTL(KC_MINUS))}, // when CW do CTRL + when CCW do CTRL -

    [2] = {ENCODER_CCW_CW(KC_RIGHT, KC_LEFT)}, // when CW do right arrow key when CCW do left arrow key

    [3] = {ENCODER_CCW_CW(ROTATE_R, ROTATE_L)}, // when CW do R Mouseclick and mouse move to the right when CCW do R Mouseclick and mouse move to the left

    [4] = {ENCODER_CCW_CW(RCS(KC_Z), LCTL(KC_Z))}, // when CW do CTRL SHIFT Z when CCW do CTRL Z

    [5] = {ENCODER_CCW_CW(RCS(KC_Z), LCTL(KC_Z))} // when CW do CTRL SHIFT Z when CCW do CTRL Z
};
#endif

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    switch (keycode) {
        case KC_TRNS:
            // our logic will happen on presses, nothing is done on releases
            if (!record->event.pressed) {
                // already handled the keycode, let QMK know it so no further code is run uselessly
                return false;
            }
            uint8_t current_layer = get_highest_layer(layer_state);
            // Check if we are within the range, if not quit
            if (current_layer > LAYER_CYCLE_END || current_layer < LAYER_CYCLE_START) {
                return false;
            }
            // If we are in range, turn next layer on without affecting other layers, so we don't break `KC_TRNS`
            if (current_layer < LAYER_CYCLE_END) {
                layer_on(current_layer + 1);
                return false;
            }
            // Getting here means we are on the last layer of the cycle
            layer_move(LAYER_CYCLE_START); // move to starting layer, ie turn OFF all other layers (default one is always ON, tho)
            return false;

        case LT(0, KC_B):
            if (record->tap.count && record->event.pressed) {
                tap_code16(KC_B); // Intercept tap function to send B
            } else if (record->event.pressed) {
                tap_code16(KC_0); // Intercept hold function to send 0
            }
            return false;
        case LT(5, KC_E):
            if (record->tap.count && record->event.pressed) {
                tap_code16(KC_E); // Intercept tap function to send E
            } else if (record->event.pressed) {
                tap_code16(KC_5); // Intercept hold function to send 5
            }
            return false;
        case LT(2, KC_K):
            if (record->tap.count && record->event.pressed) {
                tap_code16(KC_K); // Intercept tap function to send K
            } else if (record->event.pressed) {
                tap_code16(KC_2); // Intercept hold function to send 2
            }
            return false;
        case LT(KC_R, KC_C):
            if (record->tap.count && record->event.pressed) {
                tap_code16(KC_C); // Intercept tap function to send C
            } else if (record->event.pressed) {
                tap_code16(KC_R); // Intercept hold function to send R
            }
            return false;
        case LT(KC_SPACE, KC_V):
            if (record->tap.count && record->event.pressed) {
                tap_code16(KC_V); // Intercept tap function to send V
            } else if (record->event.pressed) {
                tap_code16(KC_SPACE); // Intercept hold function to send Spacebar
            }
            return false;
        case LT(C(KC_D), RCS(KC_I)):
            if (record->tap.count && record->event.pressed) {
                tap_code16(RCS(KC_I)); // Intercept tap function to send Ctrl Shift I
            } else if (record->event.pressed) {
                tap_code16(C(KC_D)); // Intercept hold function to send Ctrl D
            }
            return false;
        case LT(C(KC_S), C(KC_MINUS)):
            if (record->tap.count && record->event.pressed) {
                tap_code16(C(KC_MINUS)); // Intercept tap function to send Ctrl -
            } else if (record->event.pressed) {
                tap_code16(C(KC_S)); // Intercept hold function to send Ctrl S
            }
            return false;
        case LT(KC_LEFT_CTRL, C(KC_RBRC)):
            if (record->tap.count && record->event.pressed) {
                tap_code16(C(KC_RBRC)); // Intercept tap function to send Ctrl ]
            } else if (record->event.pressed) {
                tap_code16(KC_LEFT_CTRL); // Intercept hold function to send Ctrl
            }
            return false;
        case LT(KC_LEFT_ALT, C(KC_EQUAL)):
            if (record->tap.count && record->event.pressed) {
                tap_code16(C(KC_EQUAL)); // Intercept tap function to send Ctrl +
            } else if (record->event.pressed) {
                tap_code16(KC_LEFT_ALT); // Intercept hold function to send Alt
            }
            return false;
        case ROTATE_R:
            if (record->event.pressed) {
                register_code(KC_R); // do R
                register_code(KC_BTN1); // do left mouse button                
                register_code(KC_MS_RIGHT);  // move mouse to right
            } else {
                unregister_code(KC_R); // release
                unregister_code(KC_BTN1);                
                unregister_code(KC_MS_RIGHT);
            }
            return false;
        case ROTATE_L:
            if (record->event.pressed) {
                register_code(KC_R); // do R
                register_code(KC_BTN1); // do left mouse button                
                register_code(KC_MS_LEFT);  // move mouse to left
            } else {
                unregister_code(KC_R); //release
                unregister_code(KC_BTN1);                
                unregister_code(KC_MS_LEFT);
            }
            return false;
        case BHP:
            if (record->event.pressed) {
                register_code(KC_B); // do B
                register_code(KC_H); // do H
                register_code(KC_P); // do P                
            } else {
                unregister_code(KC_B); // release
                unregister_code(KC_H);
                unregister_code(KC_P);                
            }
            return false;
    }
    return true;
}

Video Eindproduct

3x5 dactyl keypad RQ

Conclusie

Ik heb ontzettend veel geleerd tijdens dit project. Zo heb ik geleerd hoe ik moet solderen, programmeren in Arduino IDE met c/c++ en in QMK met C. Daarnaast heb ik geleerd hoe ik een custom dactyl keyboard ontwerp. Ik ben ook erg tevreden met mijn eindresultaat en zal deze zeker vaak gebruiken.