The History of Cue2Keys: From Development Start to Initial Sales (Part 2)
Table of Contents
- Top
- Key Module
- Prototype: Breadboard
- Alpha: TRRS Cable PCB
- Beta: USB Cable PCB
- Production Version: HY Cable Board
- Key Case Edition
- Prototype: Left–Right Connection Method
- Alpha: Block Assembly Method
- Beta: Minor Changes
- Production: Studs and Recess
- Base Edition
- Alpha: Triangular Pyramid
- Beta: Plate Combination
- Production: Optimization via Cutouts
- Rotary Encoder
- Pendant
- MCU Selection
- I2C Switch/Multiplexer
- OLED Display
- Case
- Trackball
- ADNS-5050 and the Ball
- 6-pin ZH Connection
- Challenge with Bearings
- Case Design
- Hub
- Firmware
- Summary
Cue2Keys is a DIY keyboard that I developed for a little over a year from early 2024 to March 2025 and launched for sale. Development is ongoing, but as a milestone I decided to document the history up to the first sales.
In this series, I share in detail the trial-and-error process from prototype to product. The article got long, so I split it into two parts: in the first part I summarize motivations and overarching concepts, and in the second part I describe the evolution of each module and technical details.
Here’s the first part.
From here, I’ll go through each module’s evolution and details.
Key Module
The key module is the core of Cue2Keys. Its development went through four major phases before reaching the current form.
Prototype: Breadboard
Verifying I2C connections on a breadboard
I first verified I2C connections on a breadboard. I used a Pro Micro (ATmega32U4) and the I/O expander MCP23017. I also tested running I2C through a USB cable. USB cables have two data lines, D+ and D-, and VCC/GND for a total of four, which matches the minimum lines required for I2C, so I used them. At this point I also used QMK to verify that things worked as a keyboard including the firmware. You could call this the very first Cue2Keys.
Alpha: TRRS Cable PCB
After verifying the circuit on a breadboard, I considered actual connection methods. The options were two: a daisy-chain approach where key modules connect to each other via cables, and a hub approach where each module connects to a hub. It’s a hub approach now, but my initial decisions were “I don’t want to increase the number of hub modules” and “key modules will be placed in close proximity,” so I adopted daisy-chain.
Circuit and PCB design were also necessary. Among keyboard PCBs, Cue2Keys has especially strict constraints on parts placement. That’s because I place an IC and DIP switch per 4-key block, which makes for many parts, and I need to make the 4-key modules alignable at 19.05mm
(1U) intervals. That’s the width including the case, so the usable PCB width is at most 17mm
, cutting 1 mm off each side. While satisfying this constraint, I also had to place two connectors for daisy-chaining. I could put connectors at both the head and tail of the PCB, but that would reduce placement flexibility and make row-stagger impossible, so I decided to place the connector only at the head. First, I tried the familiar TRRS jacks used in audio and DIY keyboards, thinking two might fit side by side.
From this phase, I started ordering PCBs from JLCPCB, who I’ve relied on ever since. I had already done basic verification (test orders), so I tried PCBA from this point. The shape was already quite close to the current one.
4-key module PCB with TRRS connection
With PCBA, you can use a variety of parts supported by JLCPCB. MCP23017 was too large, but after browsing I learned there are various I2C-capable I/O expanders, and I adopted PCA9555 here. 16-bit is overkill, but price didn’t change much by bit width and the QFN package was small, so I remember trying it. The DIP switch was 4-bit here, unlike now. The IC only supports 3-bit, so 1 bit is wasted, but I recall 4-bit was cheaper. I also supported hot-swap for switches at this point, using the standard Kailh Switch Socket. Together with the TRRS jack, I remember soldering those myself.
As an aside, I prioritized parts choices that allow PCBA for Cue2Keys. The biggest reason is the strict size constraints, but also because soldering is tough. If I did it myself, I’d probably mass-produce defects, so I proceeded assuming PCBA to minimize soldering.
While I tried TRRS cables at first, I found when placing two for a 4-key module, they interfered with many cables. TRRS cables are also harder to source than you’d think, and they get confused with TRS cables. I looked for other connectors, but ended up returning to the idea from breadboard testing—USB cables. There’s a risk of mistakenly connecting to a PC, but they’re highly available, low-cost, and tough, so I adopted them.
Beta: USB Cable PCB
My concern with USB cables was that the connector would be large and interfere. Surprisingly the width is slim, and two fit vertically with room to spare. Since I found right-angle connectors at JLCPCB, I could PCBA them and get things working well. At this point I also prepared cases and bases with a Bambu Lab A1 mini, and the foundation was formed.
Base and 4 keys without switches. You can glimpse the challenges of printing
4-key and 5-key USB versions
Connected in large numbers. Keycaps are also 3D printed
As the look suggests, USB cables are reliable and easy to plug/unplug. Downsides are height (thickness) and that the cables are stiff and bulky. What happens when the height increases is that the connection to the base becomes unstable—the moment arm increases. For key modules where connection to the base is crucial, lower height is better for stability. Also, if modules aren’t adjacent, the cable might not reach, reducing placement freedom. Making cables longer makes them get in the way—an unavoidable tradeoff.
I was making all sorts of working prototypes at this stage
A joystick prototype as well
At this point I wanted to build something that worked, so I manufactured a lot of PCBs. I used JLCPCB’s panelization, but V-cut was expensive, so I created my own panel data using mouse bites and went all-in, ordering 20 units x 5 panels for a total of 100 key module PCBAs. Mouse bites separate easily with nippers, and while there are burrs, they worked fine. I clearly didn’t expect the shape to change afterwards. Of course I didn’t need 100; most went to waste after design changes. It happens.
Panelization I worked hard on. And it went to waste
Production Version: HY Cable Board
My search for cables continued. Right-angle connectors are rarer than you’d think, so I considered placing two connectors side by side within 1U, which means each connector needs to fit within about 8mm
. There are few options that small placed side by side. I then switched from a daisy-chain to a hub approach and tried making a hub. That way the PCB only needs one sideways connector, greatly reducing height. It also increases the freedom of usable cables. While adding a hub module is a negative in terms of management, placement freedom improves and it can also be used for tilting and tenting, so in the end I think it was a good decision.
Here I adopted a 20 cm 4-pin HY cable. HY is also known as a Grove cable and used by M5Stack. It has decent availability and, being a 4-pin cable of a convenient handling size, I adopted it.
Current form
I also switched the I/O expander to the more cost-effective TCA9535. Limiting to Texas Instruments products, they provide comparison tables that make it easy to compare specs graphically, which helped. I kept searching across sites like Digi-Key while checking availability and price on JLCPCB. The current constraint of up to eight key modules per channel also comes from the TCA9535. TI’s I/O expanders typically support about 1–8 address settings, and eight modules per hand seemed sufficient, so I settled on that spec.
Example comparison of I/O expanders
Currently, the components contributing most to height are the DIP switch and the HY connector. Both are important components, so reducing height requires quite a bit of ingenuity.
For HY cables, the 4 pins are assigned in the order GND, VCC, SDA, SCL. The facing module uses the same order, so they connect with a reverse cable.
I2C connector pinout
For the product manufacturing, I had very little time, and quantities were somewhat predictable, so I tried panelization with V-cut. Using V-cut puts it under Standard PCBA, which is overkill, but since the per-unit cost came in lower than expected, I proceeded. You can simply snap them apart cleanly, which is very convenient for boards where placement in the case is tight, such as the key modules.
Key Case Edition
The case that houses the key module PCBs also went through various changes.
Prototype: Left–Right Connection Method
In the earliest case, assuming key modules would be adjacent, there was no base and the cases linked left–right. At that time the bottom was still flat, and I was thinking of fastening adjacent key cases together with screws. I worked on this for a while, but switched to making a base and case separately due to issues like these:
- Side walls are under 1 mm thick, so hole strength is a problem
- Strongly fastening cases together is difficult (and they rotate unless fixed in multiple places)
- Too small for hands to get in; attaching fasteners is difficult
- Low flexibility (must be adjacent left–right)
Earliest key case
Alpha: Block Assembly Method
Prompted by the advice “if it’s block fastening, isn’t it LEGO®?”, I switched to a method of fastening by combining studs—placing a studded case surrounded by a rim onto a studded base. This change dramatically increased placement flexibility. Dimensions are discussed later.
Early version of the block assembly method
At this point, the difficulty of accurately printing studs and circles surfaced. Depending on print orientation, circles tend to have burrs. Burrs sometimes help fastening, but sometimes they get in the way and prevent seating; in any case, I couldn’t achieve LEGO®-like strong fastening. The profile isn’t identical to LEGO® anyway—these are customized bumps and recesses sized for 1U and fine movement—so they end up hard to mold cleanly.
Problems aside, the rough shape and mechanism were already like the product version at this stage. The PCB’s height is fixed by a pedestal at the entrance and a slit at the rear. I prepared internal clearance to pass connectors and hot-swap sockets, and the PCB is secured by inserting the key switch. The design was complex, but it secures surprisingly well. The feel is on the stiff side by nature of this mechanism, so I think it would be interesting to make it tunable.
Slit section
Beta: Minor Changes
Looking for ways to strengthen retention, I tried removing the rim groove. Strength improved a bit, but the issues remained. The base print also didn’t turn out very well.
Minor change. Printing the base is still difficult
Production: Studs and Recess
As seen in the key module PCBs, switching to a hub reduced height. Ultimately, case height was reduced by 8mm
. This much makes a visible difference in appearance.
Case height comparison (side)
Case height comparison (front)
Still unsatisfied and wanting stronger retention, after a few days of pondering I had a revelation: “Why am I combining studs?”
Influenced by the preconception of LEGO’s stud-to-stud fastening, I hadn’t considered that if one side has studs, the other side could have recesses. That insight led to the current form: recesses on the key case side and studs on the base. Height can’t be reduced further, but retention improved slightly.
For the product version, I tested the design with FDM (A1 mini), then switched to resin printing at JLC3DP for mass production because maintaining precision was hard. The molding precision is very high; conversely, there’s little variation, so using the same dimensions as FDM sometimes made retention weak. I placed several orders to test different hole sizes and eventually found a combination that secures well. It required very fine adjustments, and slight molding variation is unavoidable, so I’m still looking for a fastening method less sensitive to precision.
As mentioned in the first part, the key switch itself secures the case. As a product, I include provisional key switches so it arrives in a secured state.
Base Edition
I started designing the base around the same time as the key modules. Although LEGO® publishes its standard, it doesn’t match key size (19.05mm
square) as-is. So I arranged holes spaced at 4.75mm
, which is one quarter of 19mm
.
In the current version, studs and recesses are combined, each with 2mm
height. If hole sizes match exactly, parts won’t insert; I set stud diameter to 3.05mm
and recess diameter to 3.07mm
. That’s for high-precision printing like resin; for FDM I allowed about 0.2mm
extra clearance.
I paid close attention to tuning the base. Here’s how it evolved.
Alpha: Triangular Pyramid
Early triangular-pyramid base design
The early base used a triangular pyramid with fixed tilt/tent angles. Ambitiously assuming horizontal placement as well, I arranged studs on the top and sides. Printing with a 3D printer worked as expected, but the fixed angle, high print difficulty with frequent failures, and high filament consumption per print were problems. The inside was hollow, which was convenient for running cables.
Cables could be routed in the hollow section
Beta: Plate Combination
From the natural notion of combining blocks, I tried making a flat plate and stacking angled chips as needed to form a triangular pyramid. Print difficulty remained high, but it was easier than a monolithic pyramid. Angle adjustment became possible via chip combinations. As before, I could attach chips with anti-slip, but one side of the chip lacked bumps/recesses. At this stage, both top and bottom surfaces had studs.
A base that looks like it printed well
The underside of the same base. Some areas didn’t print well
In any case, printing the base caused accuracy to drop rapidly toward the edges, and bed adhesion worsened near the ends, which was frustrating. The base is near the A1 mini’s maximum size and long in shape, making it sensitive to shrinkage and warping. On top of that, it has many circles that require precision—very tricky. Even with careful first-layer settings, prints often failed, so I switched to JLC3DP resin at this stage. As mentioned earlier, I tried several hole sizes and verified combinations that worked well.
Production: Optimization via Cutouts
The base is large and consumes a lot of filament, which directly impacts cost. Inspired by weight-reduction holes in Mini 4WD, I tried cutting holes. Modules are at least 1U in size and don’t need to be fastened at every hole, so it works fine. Randomly distributed holes provide sufficient strength, and as a side benefit I can route and tidy cables on the underside.
Early product base. The current one is slightly larger
I later revised the product version to slightly increase width. You can attach multiple bases together to create a larger one, but fastening is difficult, so I enlarged the base itself. I’d like to modularize the base too so it can support various shapes, but there are challenges with joints and rigidity, and I haven’t found a good approach yet—so research continues.
Rotary Encoder
This module has a relatively straightforward design and shape, so there isn’t much to write. I verified with a rotary encoder from Akizuki and selected EC12D1524403 from the EC12 series as a good fit.
There was one unexpected trap. To avoid address collisions with the key modules, I used TCA9534A, but its address is only configurable from 0x38
to 0x3F
. If that rings a bell, you’re sharp: the SSD1306 display used on the pendant is fixed at 0x3C
, so depending on settings they collide. Since the rotary encoder didn’t take much effort, I got to manufacturing late, and it was difficult to swap the IC after noticing this. As a workaround, I limited the configurable address to just the lower 2 bits. This creates a limitation of only four devices per channel, but I had no choice and released it as-is.
Why only two DIP switches are usable
Pendant
The pendant is the “brain” where the firmware runs, connected to the PC. Since many verifications were done on development boards and there weren’t major changes from the initial version, I’ll record the current state rather than a history. The notable ICs used are:
- MCU: RP2040
- Flash memory (12 MB): W25Q128JVSIQ
- I2C switch/multiplexer: PCA9546A
- OLED display: HS96L03W2C03
MCU Selection
I adopted the RP2040 (Raspberry Pi Pico’s MCU) early. It’s easy to handle, feature-rich, well-proven, has abundant reference designs in the official documentation, and is inexpensive—very practical.
When making a module, you need to choose whether to solder on a Raspberry Pi Pico or design a circuit with the RP2040 itself. The Pico is very cheap, so using it directly would likely be more stable and reduce total cost. But to avoid doing my own soldering and to learn, I designed a circuit around the RP2040.
RP2040 has a reference circuit in the Pico’s official datasheet, and by making the layout similar to the official one, it’s relatively easy to design. Very helpful. I once ended up with a board that didn’t work for unknown reasons, but the second try worked fine. Unintended at first, a benefit over using a Pico was that I could mount a 12 MB flash. This proved very important for usability: I can remap 160 keys with 8+ layers via VIA and store various parameters.
I2C Switch/Multiplexer
The I/O expander (PCA9555) used for key modules has only eight address choices. That means it can only handle up to 5×8 = 40 keys and can’t even form a 60% keyboard. I remember thinking “I’m stuck.” After researching, I found the I2C switch/multiplexer. As the name implies, it allows switching a single I2C bus across multiple channels to mitigate address conflicts.
Concept diagram of I2C switch/multiplexer
I used the PCA9546A. It’s an I2C device that lets you control which channel is active. I mounted this on the pendant to form the current 4-channel design. As described above, since one channel can handle up to eight key modules, I determined four channels were sufficient.
OLED Display
I mounted a display on the pendant for showing information. I bought an SSD1306-based OLED from Akizuki for testing, and I use HS96L03W2C03 with PCBA. Like the mouse sensor, a driver is available for the OLED, so it was very easy to implement. Since the pendant is independent, I chose a relatively large display. Cue2Keys has many dynamic parameters, so I implemented page switching for the OLED. I also display the firmware version and other info that’s useful for debugging.
Case
The case didn’t change much overall. Initially it was a straight I-shape with more distinct bottom and top tabs. I later changed it to a T-shape for better directionality, and used a looser retention relying on print layer lines. While the tabs are less likely to break, the retention is a bit too loose—something to improve. The text on the case was prepared as embossed and printed. It was a small bonus, but seems worthwhile; since readability changes with light angle, I’m considering multi-color printing for clarity.
Trackball
The trackball module was challenging to develop for multiple reasons. The two biggest issues were:
First, the uniqueness of the IC. Mouse sensors are generally hard to procure, and at the scale of a few dozen units I had to buy from resellers like AliExpress. I couldn’t get official wholesale at small volumes, and there were no sensors I could buy via JLCPCB or PCBA[1], so I resigned myself to manual soldering. Additionally, I generally only found SPI-connected sensors, so unlike other modules I needed a different interface and cable. Second, case design. Even a slight misalignment can cause movement to fail to register, requiring delicate tuning. The ball must sit properly, spin, and not fall out.
As a result of all the differences from other modules, I ended up making use of a wide range of JLC group companies—JLCPCB, LCSC (Custom Cables), and JLCMC—for this module.
ADNS-5050 and the Ball
For the trackball module, I referenced the meishi trackball I had on hand. I chose the same ADNS-5050 sensor. QMK already has a driver, which was reassuring. For the ball, I used the reliable Perixx 34mm pearl white.
With the ADNS-5050, you need a separate LED from the sensor. I couldn’t find the exact one used in the meishi trackball and struggled, but after wandering JLCPCB’s catalog, I found a right-angle LED that looked promising (XL-C4040UBC). I chose a blue LED this time. I saw somewhere that it might improve reading accuracy compared to typical red LEDs, so I tried it.
But in use I discovered it doesn’t recognize red balls. I should’ve realized this before assembling, but it makes sense given the datasheet and color properties. The blue LED (XL-C4040UBC) has a peak wavelength of 460nm
—indigo to blue. This light shines on the trackball, and the ADNS-5050 reacts to the reflected light. The fact that we see color means the object reflects the wavelengths it doesn’t absorb. A red ball, roughly speaking, absorbs wavelengths other than red. So with a blue LED, much of it gets absorbed. The ADNS-5050’s response peaks around 680nm
, and it’s only about 60% responsive at 460nm
. In other words, it’s tuned for red; if much of the light is absorbed, there isn’t enough reflected light for detection.
Relationship between light wavelength and color ref: http://ene.ed.akita-u.ac.jp/~ueda/education/sentan/photochemistry/principle.htm
ADNS-5050 response characteristics by wavelength
In the product version I used a nearly white ball, which works with the blue LED. The faint glow through the case is a fun touch.
The softly glowing ball
As an aside, I realized that the reason trackballs were typically red is related to the prevalence and low cost of red LEDs. Modern high-performance sensors often use infrared, freeing you from constraints of visible light. Reaction still depends on material and other factors, but the set of usable balls grows and there’s less to worry about.
6-pin ZH Connection
Ideally I would have handled this via I2C like the key modules, but mouse sensors like the ADNS-5050 are commonly SPI—and for some reason often 3-wire (shared MOSI/MISO). That means the 4-pin cable used for I2C doesn’t have enough lines. I considered an SPI–I2C bridge IC, but it would be a very expensive solution for what I wanted to do. So I adopted a 6-pin ZH connector. Since it’s SPI, I also had to connect directly to the pendant.
The six pins are assigned in order as GND, VCC, SDIO, SCLK, NCS, and NRESET. The pendant side uses the same order and connects with a reverse cable. I didn’t end up using NRESET. Since I never needed to reset only the trackball, assigning MOTION would be more useful.
Trackball cable pin assignment
ZH has a 1.5mm
pitch, so it’s smaller than HY at 2.0mm
, but it secures well. However, ZH has poor availability as a finished cable. I couldn’t find any 6-pin ZH reverse cables with both ends terminated and of reasonable length, but I managed to find a 30 cm cable, A06ZR06ZR28H305A, on Marutsu Online.
I didn’t think the pendant would be that far away, but I received feedback that 30 cm was too short, so I ultimately used Custom Cable from LCSC. To summarize, it’s a service that makes cables to your spec. I failed a few times (due to my own incorrect specs), but I got it manufactured, and the cable now included with the trackball is 100cm
. The price also ended up slightly cheaper than the original as long as you make a certain quantity.
Challenge with Bearings
Since I was going to the trouble of making a trackball, I wanted to try bearings, so I decided from the outset to use them. I used to be a trackball user, and I longed for smooth initial motion and ease of maintenance. I had the impression that bearings are expensive parts, and indeed when I looked in physical stores, many were 150–300 yen each. But after searching on JLCMC, I found some for under 50 yen. I therefore looked for small bearings on JLCMC and designed around those. When they arrived, the bearing I chose as “small” (BCTA-P-605) turned out larger than expected, which made the design challenging.
Related to the case design, it’s hard to print axles with a 3D printer, so I used a round brass spacer as the axle and combined it with washers and screws to secure each bearing so that it rotates.
Parts needed to secure one bearing
Case Design
The trackball module’s case was the area that required the most trial and error. In particular, adjusting the distance between the lens and the ball was difficult; even small differences in height often caused it to stop detecting movement. A design challenge was that adding bearings changes the height. It often happened that it worked fine without bearings but failed once bearings were installed because the height changed. I needed to satisfy conditions where overall height is kept low, there’s an appropriate distance that doesn’t interfere with the sensor, claws prevent the ball from popping out, and the ball makes good contact with the bearings. I iterated over a long period, and the current form is what finally worked.
The case is split into a lower base section and an upper section for the rest. They snap together using tabs on the lower part. You don’t generally remove them, but I adjusted the fit so that even if you do, the tabs won’t crack.
Hub
At present the hub simply lines up connectors, so there’s no particular circuit-design trick. The case is also a relatively straightforward shape. There are no noteworthy development episodes.
The 3D models that have appeared in the text so far are published on GitHub.
Firmware
I forked QMK, modified only the user portion, and published it on GitHub. It supports the full modular approach and VIA (REMAP), underpinning the system. To realize the concept, I prepared fairly fine-grained adjustable parameters such as trackball and auto mouse layer tuning, and rotary encoder resolution.
To realize this, you need to dig somewhat into and understand QMK’s internal processing.
First, since key inputs arrive via I2C, you can’t realize it with the default processing. I wrote about the details in my book “An Introduction to DIY Keyboards: How to Make a Modular Keyboard”, so I’ll skip them here, but the key input part is relatively easy to modify, so I adapted it to fit the system.
I also ended up writing a driver for the trackball. That’s because there’s no consideration for a state where “two or more” trackballs are “possibly connected.” It’s not an issue for normal keyboards, but for Cue2Keys, where modules may be connected or removed, it requires handling. I wrote a recognition part and a separate driver that processes multiple trackballs individually and merges their movement. The processing for the sensor itself could mostly reuse existing code.
Furthermore, changing encoder resolution is another stumbling point. As 25keys’ article explains, at present it’s hard to realize without touching core parts. For this firmware, to make it easier to follow the latest versions, I forced it to work by modifying only the userland portion. Behavior is a bit suspicious, but it works plausibly.
In general, for well-known keyboards, if you want to make a certain level of custom adjustments, end users need to build somewhere, generate a binary, and flash it. This doesn’t align with the concept of making “dynamic” adjustments, which has been a persistent concern. QMK has many weak functions that users can override, offering flexibility, so for now I’m getting by without touching the core parts, but it’s unclear how this will be in the future.
Summary
I was so late in writing this article that it required an archaeological investigation. I really need to publish development logs regularly.
Developing a keyboard over more than a year was more difficult and challenging than I imagined. From prototype to product, new problems appeared at every stage, and there were many days when I worried whether it would really come together. In the end, it worked out, and in the process of solving those problems, I gained a lot of technical insight. What was most striking was the experience that constraints breed creativity. In the process of finding optimal solutions within various constraints—height limits, size limits, cost limits—unexpected solutions emerged.
It was a big achievement that the modular system actually worked, including ideas that were initially just concepts: splitting keys and a mechanism to secure them, firmware modifications, and more. I implemented all the basic functions, and I’m glad I could add features that support the concept, like firmware parameter adjustments. On the other hand, there’s still much to pursue as a keyboard, many functions that could be added, constraints that could be removed, and manufacturing improvements to be made. Cue2Keys is still a work in progress, which means there’s lots of room for improvement, and I hope it grows into an even more attractive product.
Development is proceeding little by little, but with many new efforts, it looks like it will be next year before I can ship as a product. For future updates, I’d appreciate it if you check Cue2Keys on SNS. For major announcements, I plan to share them on (self-declared) Cue2Keys Day, 9/29 (add the announcement day to Google Calendar).
Even when a product page existed, quoting failed when I tried. Consign Parts is possible—where I procure the parts and ship them to JLCPCB for PCBA. ↩︎