AI-translated from the Chinese original View original
After ten years of typing in Dvorak, I still wanted a keyboard that supported my layout natively. Commercial options were limited, so I had been relying on operating-system remapping to adapt the keyboards I used.
By the end of the three pandemic years, the free Dell keyboard I had used in the office for years had shiny keycaps and worn-off legends. The custom-keyboard market was thriving, but much of the customization involved choosing combinations of standard parts. It reminded me of assembling the miniature four-wheel-drive cars I played with as a child. I wanted to try designing the keyboard itself.
Custom PCBs had become affordable, and 3D printing and CNC machining were increasingly accessible. I decided to build a keyboard of my own.
Choosing a Layout
My reasons for switching from QWERTY to Dvorak years earlier were mostly about typing efficiency and comfort.
-
Speed and efficiency. Dvorak places frequently used letters on the home row to reduce finger travel.
-
Less finger fatigue. Shorter movements appealed to me as someone who spends long hours typing.
-
Typing comfort. Its letter arrangement aims to make common sequences more comfortable.
-
Workload across both hands. The layout aims to distribute typing work more evenly.
The heat maps show frequently used keys concentrated on the home row, with all five vowels under the left hand. The benefits may be less apparent when entering Chinese, but as a programmer who types a great deal of English, I had found Dvorak comfortable over the previous ten years.
Keyboard shortcuts are its biggest inconvenience for me. Many applications position their shortcuts around QWERTY, and those combinations become awkward after remapping. Key-mapping tools or dedicated shortcuts on a Dvorak-oriented keyboard can compensate.
Defining the Requirements
Around 2015, I bought two TypeMatrix 2030 keyboards. Their built-in support for Dvorak was the main attraction.
Several aspects of the design worked well for me.
-
Native Dvorak support
-
Dedicated alternatives for common copy-and-paste shortcuts
-
Centrally placed Backspace, Delete, and Enter keys that either hand could reach
-
Switching between Dvorak and QWERTY
-
A compact footprint with a layered numeric keypad
-
A regular ortholinear grid whose appearance I preferred to staggered rows
My reservations came from using them.
-
Basic construction with membrane switches and a simple tray-style case
-
More right-side multimedia keys than I needed
-
No provision for spill resistance
-
A price of US$110 each that felt high for the build quality
Both keyboards eventually failed after water spills. The conductive membrane traces were damaged, especially by hot water, leaving individual keys unusable. Repairing those fine traces on plastic film was impractical.
I liked the TypeMatrix layout enough that I would have kept using it if the keyboards had survived. My own design therefore started from its layout and functions, with changes to suit my preferences.
I had also used standard-height mechanical keyboards, but disliked their long travel for sustained typing. Quiet operation mattered more to me than tactile feedback, and linear switches felt comfortable for prolonged use. After trying several switches, I settled on low-profile linear switches.
The basic goals were as follows.
-
Follow the TypeMatrix 2030 layout approach
-
Support Dvorak
-
Keep Enter and other editing keys in the center
-
-
Remove unnecessary multimedia keys
-
Support switching between layout layers
-
Use low-profile linear switches
-
Use a wired connection
My first revision followed the original closely, including 0.75U-high function keys, several unusual 2U keys, and a 5 × 1.25U spacebar. Finding ready-made keycaps for those dimensions was difficult, especially for low-profile switches. Custom PBT tooling made little sense for a one-off keyboard. After validating the first revision’s circuit and firmware, I adjusted the layout to fit commercially available keycaps.
Keyboard Layout Editor can be used to design the basic layout online.
The function keys became 1U. A 0.25U gap between the function row and the main typing area accommodated the centered 2U Backspace and 2.5U Enter keys. I changed the spacebar to the common 6.25U size and removed less-used keys such as right Alt and right Ctrl. The revised layout could use off-the-shelf keycaps.
Circuit Design
With the layout decided, I moved on to the electronics. I wanted a straightforward keyboard without elaborate extras or key backlighting. The familiar ATmega32U4 had enough GPIO pins for the design. An STM32 was another possibility, but I saw little benefit in the extra complexity and cost for this project.
Switch Matrix
A keyboard’s input mechanism is a matrix of switches. The MCU scans rows and columns through its GPIO pins to determine which switches are closed. I used a column-to-row diode orientation, placing a diode in series with every switch to prevent unwanted current paths when several keys are held simultaneously.
Since I had already defined the physical layout, drawing the matrix mainly involved arranging each switch and diode in its intended row and column.
USB Interface
A USB Type-C connector carries power and data. The keyboard has modest data-rate requirements, so the surrounding circuit mainly addresses ESD protection, shielding, and supply filtering.
-
A USBLC6-2SC6 diode array provides ESD protection. Discrete protection devices could serve the same purpose, with a different board-area trade-off.
-
A ferrite bead and capacitors filter the supply, with a resettable fuse in the power path.
-
A 1 MΩ resistor and capacitor connect the connector shield to ground. The host’s USB grounding also contributes to the shielding arrangement.
The keyboard could operate without all of those protective components, but I wanted protection against faults and accidental static discharge. For a keyboard I was building for myself, the small additional cost was worthwhile.
The Type-C connector’s A5 and B5 pins are the configuration-channel contacts. For this USB-powered device, each needs its own pull-down resistor. Sharing one resistor can cause compatibility problems with some cables and power sources.
MCU Connections
The MCU circuit connects power, ground, data, and a crystal, with most remaining GPIO pins reserved for the switch matrix. Assigning matrix pins after considering the physical PCB layout helps reduce crossed traces and unnecessary vias.
A reset control is needed for firmware flashing. I left two small pads that can be bridged with a metal tool when necessary, avoiding a separate switch that would rarely be used.
Status LEDs
With GPIO pins left over, I added a few LEDs for lock and layout status. Each LED needs a current-limiting resistor. The resistance depends on the LED and desired brightness, and I preferred subdued indicators that would remain comfortable to look at.
PCB Layout
The layout exported by Keyboard Layout Editor can be imported into online plate-generation tools to obtain a CAD drawing. That provides precise switch positions for placing the PCB footprints.
The switches and through-hole LEDs occupy the front of the board. Surface-mount components on that side would interfere with assembly. Double-sided SMT assembly also costs more, so I placed all surface-mount components on the back.
I wanted flexibility in switch selection, covering Kailh Choc v1/v2 and Cherry low-profile switches. Combining pad and hole positions, following examples from other GitHub projects, let me create a compatible footprint. The arrangement ruled out hot-swap sockets. I already preferred soldered switches, so that trade-off suited me.
A standard 1U key pitch is 0.75 inches, or 19.05 mm. A 0.79375 mm grid, equal to 19.05 mm divided by 24, made component placement convenient. I used 0.25 mm signal traces, approximately 10 mil, and 0.40 mm power traces for additional current margin. The actual load was modest because the keyboard had no backlighting.
I also paid attention to several routing details.
-
Route the USB data pair together with matched lengths and a continuous reference plane.
-
Provide ground copper around the USB connector, MCU, and crystal where appropriate.
-
Keep the crystal close to the MCU.
I then placed the component footprints and routed the connections. Most switch footprints were rotated 90 degrees to simplify routing. Vertical traces generally ran on the top layer and horizontal traces on the bottom, reducing vias. Keeping components on the grid made the layout tidy. Rotating the MCU 45 degrees helped with pin access.
I put the USB connector at the top center for convenient cable placement. Its protection circuitry occupied enough space that the neighboring switches needed different orientations. A mirrored arrangement made the result look orderly.
The ortholinear layout kept placement regular, but larger keys needed stabilizers. Mounting conventional stabilizers on the plate would add height to a low-profile keyboard. Several commercial designs instead used low-profile stabilizers mounted directly to the PCB. Wire stabilizers used on other keyboard designs offered another possibility. I made a small test PCB to compare the mounting arrangements.
The low-profile MX-style stabilizers mounted on the PCB felt best and were easiest to work with. Their height also matched the switches. I added the corresponding mounting holes at the larger keys.
Finally, I reduced excess board area and tightened the space around the through-hole LEDs to control size, cost, and weight. After adding a few optional screw holes and silkscreen markings, the PCB design was complete.
Enclosure Design
The circuit and PCB still needed an enclosure to become a usable keyboard. Before drawing the case, I had to choose a mounting structure.
Choosing a Mount
Keyboard builders have developed several common mounting arrangements, each with different mechanical characteristics.
-
Tray mount. The PCB attaches directly to posts in a one-piece case. It has few parts and can be inexpensive to manufacture and assemble. Support-point placement can make the feel vary across the keyboard.
-
Case mount. In top-mount and bottom-mount designs, the plate attaches to the upper or lower case. The plate distributes typing loads and structural support, with its material strongly influencing the feel. The PCB carries less mechanical load.
-
Sandwich mount. An enlarged plate is clamped between the upper and lower case. Assembly is straightforward, but the mating parts need consistent dimensions. Rigid clamping produces a relatively firm feel.
-
Plateless mount. The PCB itself supports the switches, using an arrangement similar to tray or top mounting. It can feel softer, while placing more mechanical load on the PCB.
-
Integrated plate. The upper case and plate form a single part, reducing the part count. Its rigid structure can also amplify typing noise.
-
Gasket mount. Cushioning material separates the plate assembly from the case, softening the feel compared with a rigid sandwich mount. The additional materials and mounting details can increase cost.
I prefer a relatively firm but consistent typing feel. Top mount suited that preference best. Its parts were more involved to design, but I wanted to try it.
Mechanical Design
Having chosen low-profile switches, I wanted to minimize the enclosure height. The upper and lower parts needed to locate and support the keyboard without interfering with its components. Keeping the design simple also reduced material and weight. A smaller internal cavity helped limit hollow typing noise.
I followed a workflow I knew well. I first drew precise, layered outlines in CAD, then built the solid models in SolidWorks from those profiles. Although I could have done the whole job in a 3D tool, I liked the precision and efficiency of command-driven CAD. Drawing the parts brought back memories of mechanical-drawing classes at university more than a decade earlier, including drawing by hand.
Exporting the PCB model let me check component clearances in the mechanical assembly. Collision checks were useful when reducing the case thickness and positioning reinforcement ribs. The USB opening also required careful checking because it spans the upper and lower case.
The enclosure has three main parts, the upper case, switch plate, and lower case. Their fastening points follow the top-mount arrangement. I considered ordering the plate from a specialist keyboard supplier, but ultimately chose the same aluminum alloy and anodized finish as the upper case, so I had both CNC-machined together. Cutouts and recessed areas reduced weight where material was unnecessary. A slightly wider left border holds three holes for the through-hole LEDs, avoiding separate light pipes.
I allowed roughly 0.1–0.2 mm for fit between mating parts. The lower case would be 3D-printed in ABS. Its tolerances could be looser, but the material’s flexibility made the actual fit manageable. Two lengths of M2 flat-head screws hold the assembly together, with threaded holes in the aluminum upper case.
Firmware
The hardware needed firmware to implement its input behavior. For a wired ATmega32U4 keyboard, QMK was an established framework that already provided the core keyboard functions.
Writing code is my day job, so starting a QMK project from its documentation was straightforward. With a nonstandard layout, I had expected to work directly in code from the outset. I also prefer organizing the implementation in a way I can comfortably read and maintain.
QMK supplies keycode definitions, layer switching and combinations, shortcut handling, matrix scanning, and much of the other basic behavior. My main work was defining the keymaps and layout-switching logic. MCU pin assignments go in info.json.
I used four layers for the default Dvorak layout, an alternative QWERTY layout, function combinations, and a numeric keypad.
enum mx_layers {
_DVORAK = 0,
_QWERTY,
_FN,
_NUMPAD
};
enum mx_keycodes {
TG_DV = SAFE_RANGE,
NUM_00
};
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
// Dvorak Layout (Default)
[_DVORAK] = LAYOUT_dvorak_matrix(
KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_DEL, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12,
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_BSPC,KC_6, KC_7, KC_8, KC_9, KC_0, KC_LBRC,KC_RBRC,
KC_TAB, KC_QUOT,KC_COMM,KC_DOT, KC_P, KC_Y, KC_F, KC_G, KC_C, KC_R, KC_L, KC_SLSH,KC_EQL,
KC_A, KC_O, KC_E, KC_U, KC_I, KC_ENT, KC_D, KC_H, KC_T, KC_N, KC_S, KC_MINS,
KC_LSFT,KC_SCLN,KC_Q, KC_J, KC_K, KC_X, KC_B, KC_M, KC_W, KC_V, KC_Z, KC_BSLS,KC_RSFT,
KC_LCTL,KC_LGUI, KC_LALT, KC_SPC, KC_HOME,KC_UP, KC_END, KC_PGUP,
MO(_FN), KC_LEFT,KC_DOWN,KC_RGHT,KC_PGDN
),
// Qwerty Layout
[_QWERTY] = LAYOUT_dvorak_matrix(
KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_DEL, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12,
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_BSPC,KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS,KC_EQL,
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_A, KC_S, KC_D, KC_F, KC_G, KC_ENT, KC_H, KC_J, KC_K, KC_L, KC_SCLN,KC_QUOT,
KC_LSFT,KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM,KC_DOT, KC_SLSH,KC_BSLS,KC_RSFT,
KC_LCTL,KC_LGUI, KC_LALT, KC_SPC, KC_HOME,KC_UP, KC_END, KC_PGUP,
MO(_FN), KC_LEFT,KC_DOWN,KC_RGHT,KC_PGDN
),
// Functions
[_FN] = LAYOUT_dvorak_matrix(
_______,TG_DV, _______,_______,_______,_______,KC_INS, _______,_______,_______,_______,KC_PSCR,_______,TG(_NUMPAD),
_______,_______,_______,_______,_______,_______,KC_VOLU,_______,_______,_______,_______,_______,_______,_______,
_______,_______,_______,_______,_______,_______, _______,_______,_______,_______,KC_PSLS,KC_PAST,KC_PMNS,
_______,_______,_______,_______,_______,KC_VOLD,_______,_______,_______,KC_P7, KC_P8, KC_P9,
KC_CAPS,_______,_______,_______,_______,_______, _______,_______,_______,KC_P4, KC_P5, KC_P6, KC_PPLS,
_______,C(KC_INS), S(KC_INS), KC_MUTE, KC_P1, KC_P2, KC_P3, KC_PENT,
_______, KC_P0, NUM_00, KC_PDOT,KC_PENT
),
// Number Pad
[_NUMPAD] = LAYOUT_dvorak_matrix(
_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,
_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,_______,
_______,_______,_______,_______,_______,_______, _______,_______,_______,_______,KC_PSLS,KC_PAST,KC_PMNS,
_______,_______,_______,_______,_______,_______,_______,_______,_______,KC_P7, KC_P8, KC_P9,
_______,_______,_______,_______,_______,_______, _______,_______,_______,KC_P4, KC_P5, KC_P6, KC_PPLS,
_______,_______, _______, _______, KC_P1, KC_P2, KC_P3, KC_PENT,
_______, KC_P0, NUM_00, KC_PDOT,KC_PENT
),
};
The custom key handlers switch between Dvorak and QWERTY and provide a double-zero key, similar to the 00 key on some calculators. Following TypeMatrix’s approach, Fn combinations on the left side provide copy and paste shortcuts that remain convenient with Dvorak.
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
// Debug Output
#ifdef CONSOLE_ENABLE
uprintf("KL: kc: 0x%04X, col: %2u, row: %2u, pressed: %u, time: %5u, int: %u, count: %u\n", keycode, record->event.key.col, record->event.key.row, record->event.pressed, record->event.time, record->tap.interrupted, record->tap.count);
#endif
switch (keycode) {
case TG_DV:
// Toggle Dvorak Layer
if (record->event.pressed) {
if (IS_LAYER_ON_STATE(default_layer_state, _QWERTY)) {
default_layer_set(1 << _DVORAK);
set_single_persistent_default_layer(_DVORAK);
} else {
default_layer_set(1 << _QWERTY);
set_single_persistent_default_layer(_QWERTY);
}
}
return false;
case NUM_00:
// Double Zero
if (record->event.pressed) {
SEND_STRING("00");
}
return false;
default:
return true;
}
}
After compiling the firmware with QMK, I used QMK Toolbox and the PCB’s reset pads to flash it onto the MCU.
Manufacturing and Assembly
I ordered the PCB and SMT assembly through JLCPCB. The assembly service could place the surface-mount components along with the board order. Since I designed the circuit in KiCad, my workflow involved importing it into EasyEDA, associating the fabrication parts, checking the result, and uploading the board and assembly files. At order confirmation, I paid particular attention to the orientation and polarity of the MCU, crystal, ICs, and diodes. Automatic placement still needed a manual check.
For the upper case and plate, I used JLC’s 3D Monkey CNC service with 6061 aluminum and a space-gray anodized finish. The same service printed the ABS lower case. Threaded features required separate 2D drawings with machining notes, which were straightforward to prepare in SolidWorks.
I also considered softer 5-series aluminum and pilot holes that could be tapped during screw installation as a possible way to reduce machining cost.
Once the orders were placed, there was a short wait. The PCB arrived relatively quickly. CNC machining and 3D printing took a little longer, possibly because I ordered during the National Day holiday and there was a backlog. Everything was finished in about a week.
The machined upper case and plate fit together neatly. Snapping switches into the plate was satisfying, a little like assembling Lego.
Next, I positioned the switches in the plate, soldered them to the PCB, and installed the stabilizers and wires. The Kailh 1350 stem design limited keycap choices, particularly with switches rotated 90 degrees. I ultimately chose Cherry low-profile switches for their cross-shaped stems, keycap compatibility, and relatively quiet operation.
The unusual Dvorak arrangement needed uniform-profile keycaps, with the same shape across rows. I chose a set from the NuPhy Air96 that suited the anodized case. Their PBT material also appealed to me for long-term use and resistance to developing a shiny surface.
The finished space-gray aluminum case and predominantly gray keycaps looked good together.
Afterword
I planned to use the keyboard for a while before deciding what to change in another revision. The Fn combinations felt slightly awkward because I had been conservative about key spacing. I also wanted a rotary control, but finding a smooth, non-detented encoder that fit the low-profile height was proving harder than expected.
The project gave me a taste of the enjoyment that technology creators such as He Tongxue and Zhihui Jun find in designing their own devices. I had never taken an electronic product through the entire process from an initial idea to manufacturing. Completing it over roughly two weeks of spare time was satisfying. It reminded me of the resourceful engineers in Jules Verne’s nineteenth-century novels, solving one practical problem after another through engineering.
Access to affordable manufacturing also made a strong impression. For a small personal project, turning an idea into a physical object was remarkably accessible. I wondered what further designs might become possible if custom integrated-circuit fabrication became similarly approachable one day.
References
Last modified on 2023-12-12
About Me Next
Techno Keyer — Bringing Retro Displays and Morse Code Together