If you have any questions or feedback, pleasefill out this form
This post is translated by ChatGPT and originally written in Mandarin, so there may be some inaccuracies or mistakes.
Although the title mentions a guide to avoiding pitfalls, this article will focus more on the operational principles of keyboards and their circuitry.
Detecting Key Presses
As mentioned in the previous article, a keyboard is essentially a circuit made up of multiple switches. We can use microcontroller GPIO pins to detect whether a switch is pressed. The specific method involves connecting one terminal of the switch to power and the other to ground. When a key is pressed, the circuit completes, and the microcontroller can sense this change, sending out the corresponding signal. We will discuss what these signals actually are in the following sections.
However, there is a drawback to this approach: each key requires a separate pin. For a keyboard with 104 keys, the microcontroller would need 104 pins. Usually, microcontrollers don’t have that many available pins, and some pins may be needed for other purposes.
Scanning and Keyboard Matrix
In practice, we connect the keyboard circuitry in a matrix configuration. Each matrix connects to a pin on the microcontroller, which means that for a keyboard with 60 keys, a 6x10 matrix would only need 16 pins.
We can apply a high voltage to R1, R2, and R3 sequentially, then read the voltage at C1, C2, and C3 to determine which key has been pressed. For example, when the key in the diagram below is pressed, C1 will detect a high voltage, indicating which key was activated.
The sequence here is:
- Set R1 to high voltage
- Read C1
- Read C2
- Read C3
- Set R2 to high voltage
- Read C1
- Read C2
- Read C3
- Set R3 to high voltage
- Read C1
- Read C2
- Read C3
Ideally, the number of rows and columns in the matrix would be the same, but in practice, this isn’t always done. One reason is that microcontrollers typically have enough pins, and another is that forcing the two quantities to match can complicate layout. For example, keyboards tend to have more rows than columns.
A common question arises: what happens if keys in R1 and R2 are pressed simultaneously? Would that be undetectable? This is because the detection is not simultaneous but sequential. Generally, there’s no need for concern; most microcontrollers operate at a speed of at least 16MHz, which is significantly faster than human reaction times. The microcontroller will remember the pressed status, and when it reads again and finds a change, it will send a release signal.
Ghosting
The circuit discussed earlier has a potential problem: if R3 also has a switch pressed, part of the current may flow across, leading to misinterpretation. The solution is to add diodes. Diodes allow current to flow in one direction only, thereby preventing ghosting.
HID (Human Interface Device)
Next, we’ll cover the logic for sending signals to the computer. Early keyboards used PS/2 connectors, but USB and Bluetooth are now the most common.
HID standardizes protocols for various common input devices, such as keyboards, mice, and controllers, reducing the need for custom drivers. As long as a device adheres to HID specifications, it can operate without special drivers.
HID comprises input reports, descriptors, and output reports. The descriptor usually describes the device, including its purpose, name, manufacturer, and ID. Once the computer reads these reports, it knows how to handle the device.
USB
Generally, microcontrollers come with USB functionality, allowing data to be transmitted in USB format.
Similar to HID, data can typically be transmitted over USB or Bluetooth. However, this is a technical detail that engineers care about; USB is divided into two types: host and device.
Input devices like mice and keyboards are typically much slower than the CPU, so if the CPU had to wait for input from these devices, it would be highly inefficient. Instead, the computer’s USB controller (host) continuously polls the device for data, calling the CPU only when there’s data in the buffer. Therefore, the keyboard’s role is just to keep sending data, while the rest is managed by the host.
Usually, how to write the HID format is handled by libraries, allowing you to focus on mapping the corresponding keys without worrying about the underlying code.
Here’s an example from a Raspberry Pi Pico:
static void send_hid_report(uint8_t report_id, uint32_t btn)
{
// skip if hid is not ready yet
if ( !tud_hid_ready() ) return;
switch(report_id)
{
case REPORT_ID_KEYBOARD:
{
// use to avoid send multiple consecutive zero report for keyboard
static bool has_keyboard_key = false;
if ( btn )
{
uint8_t keycode[6] = { 0 };
keycode[0] = HID_KEY_A;
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode);
has_keyboard_key = true;
}
...
}
This example demonstrates a single button that always sends the A key. However, from the code, we can infer how to write it. The code starts by declaring an array keycode
. The length of 6 indicates that it can send 6 keys simultaneously, but only keycode[0]
is used here, and it calls tud_hid_keyboard_report
to send the HID report.
PCB Design and Layout
I don’t have much experience in PCB design, but I want to emphasize that anyone with basic knowledge can create a circuit board. A well-known open-source software for PCB design is Kicad, which is completely free.
With this software, you can design your desired circuit and it also supports PCB layout. You can place your designed circuit onto the PCB and route the connections.
Once everything is complete, you can output the schematic (in a specific file format) and upload it to a PCB manufacturing website, and you can expect to receive the finished product in a few weeks. Common affordable manufacturers include JLCPCB and PCBWay.
QMK
In the custom keyboard community, some enthusiasts are highly dedicated to customization, such as how LEDs light up, how layouts are adjusted, incorporating knobs on the keyboard, and even adding an LCD screen. Everyone has their own standards, so in addition to showcasing various hardware crafts, the firmware also demands high levels of customization.
Theoretically, as long as you have a microcontroller and circuitry, you can write keyboard firmware. However, it would be cumbersome to modify the code every time you change the keyboard layout, especially if you need to rewrite it for a different microcontroller. Therefore, some enthusiasts have developed highly customizable firmware that can automatically generate the corresponding firmware as long as the keyboard settings are defined, without writing a single line of code.
Currently, the most famous firmware in the keyboard community is QMK, an open-source firmware derived from TMK. It is quite common among keyboards, and even brands like Keychron support QMK settings.
The features mentioned earlier are mostly present in QMK. It supports defining key positions, macros, LCD and LED control, Bluetooth, Joysticks, and even MIDI—essentially, any feature you can think of is likely available in QMK. Additionally, many customizable keyboards are supported; for example, the Zoom65 I’m currently using can be found here along with its definition file.
Moreover, I was quite surprised to discover that it now supports Web USB, allowing direct modification of key mappings online, and after making changes, you can adjust the firmware settings directly.
I haven’t yet researched the details of this implementation, but I suspect there’s corresponding code that communicates directly with the keyboard firmware via Web USB. For those interested, you can refer to VIA.
Conclusion
Nowadays, many circuit boards and enclosures have open-source models and designs available for reference. While creating a keyboard "from scratch" still poses some challenges, it’s much easier compared to the past. However, just understanding the underlying processes makes it clear that creating a keyboard is no simple task.
If you found this article helpful, please consider buying me a coffee ☕ It'll make my ordinary day shine ✨
☕Buy me a coffee