Dunkelstern

Ducky One 3 conversion to QMK

What

PCB

My partner @chaotin@corteximplant.com was gifted a Ducky One 3 Matcha mechanical keyboard. I converted that thing to a Raspberry Pi Pico (RP2040) chip with QMK firmware.

Why

The firmware of the Ducky keyboard did not compensate for bouncing keys correctly and it seems Ducky abandoned updating the firmware for that device. So the keyboard was basically unusable because all the time key-presses repeated multiple times.

Try to write any length of text with an unreliable keyboard.

There are basically 3 approaches to fixing this:

  1. Reverse engineer the Ducky firmware and firmware update process. This has been done by usedbytes on github: Ducky-Tools on Github for another hardware platform from Ducky, but it would not work for the platform on which the One 3 is based on. Furthermore I would have to reverse engineer the original firmware to find the debouncing code and binary patch that.
  2. Write a new keyboard firmware for the Nuvoton chip that is on the board. This would mean to reverse engineer the PCB first and then learn how to write software for that chip and how to get it onto the internal Flash. It seems there is actually a programming header on the PCB, so at least flashing would be not the biggest problem.
  3. Remove the Nuvoton chip, reverse engineer the PCB enough to find out how to access the keyboard matrix and hot- wire a Raspberry Pi Pico in there. This seemed the most straight forward thing to do, so I started by reverse engineering the PCB.

How

Reverse engineering

PCB Oscilloscope trace

If you have a working keyboard the first thing to try is powering it up and probing around with an oscilloscope to find out where the rows of the keyboard matrix are accessible. You should see a rectangle pulse on each row at around 250 to 1000 Hz. For each row the pulse should be shifted by one pulse length. To find out the row order use two channels of your oscilloscope, trigger on one and look at both channels to see where the pulses are in relation to each other.

Rows marked on a scan

On the One 3 I could find that pulse on each of the switch contacts on the right side and the original firmware scanned with 500 Hz. I found there to be 8 rows with distinct pulses on them, starting with the top row (ESC, F-Keys), then going down the keyboard rows as expected and finished by the Num-pad. The last row were just the four media keys above the Num-pad (Calculator, Mute, Vol down, Vol up).

Columns marked on a scan

The next step was to find the colums that are connected together, I had to find that by probing with a multimeter in continuity mode. My partner was really annoyed after hearing me search for connections with the beeping mulimeter for about an hour...

Usually the keyboards have one diode per key to avoid triggering "wrong keys" if too many keys are pressed at the same time (N-Key rollover). Gladly the One 3 has them near the key switches and I could measure from diode anode (the pad without the line) to other diode anodes.

Bricking the keyboard for real

After finding out what is what i decided to yolo it and remove the Nuvoton chip with hot air:

Nuvoton chip removed

Now the keyboard was dead and I had to come up with a plan to hot-wire a RP2040 into that thing. The first thing I did was to check where to put it. The One 3 has much space in the keyboard case, so this was not a big problem at all.

Raspi Pico stuck to the PCB

Before mounting the RP2040 permanently with VHB tape I had to remove the Micro USB Port (clearance!) and the LED from the prototyping module. I needed the pin that has the LED connected ( GPIO25 ) for one of the indicator LEDs of the keyboard. Alternatively I could have skipped the multimedia keys or not indicate Scroll-Lock (who uses Scroll-Lock?), but the perfectionist in me wanted to have a fully functioning keyboard, so the LED had to go.

Test pads on the back of the Pico

The other test points on the Pico board are connected to GND and USB (thanks for that Raspberry Pi Foundation). This means we can wire the Pico into the original USB port of the keyboard without trying to micro-solder onto the pads of the Micro-USB port (I mean this would not be the smallest connection I've ever made but thanks, no thanks...). As we only running a keyboard in low speed mode I did not bother to make the D+ and D- wires the exact same length or something like that. It will just work fine.

Accessing the USB traces on the PCB

To gain access to the USB-Port of the keyboard I traced where the original chip was connected and found a lot of stuff in the path (resistors, ferrite beads, etc.). We do not need all that stuff for the Pico, so I removed the first item in that path which were some 0604 resistors ( R64 is D- and R65 is D+) and connected the Pico with enamel wire to those pads. GND did come from the programming header ( J3 pin 1) and USB bus power was found at a resettable fuse which was not populated on my board (left pad of R135 ).

USB connected

Now I could try to program the Pico via the original USB-Port... it worked first time!

MVP

Next up: do the minimum viable thing, blink the Num-Lock LED.

For this I tried to reverse engineer the cascade of transistors and mosfets on the PCB and I think they multiplex all the LEDs together with the row pulses, because there are some spots on the PCB where you could solder on LED drivers and full RGB backlighting in addition to the indicator LEDs. I could not make heads or tails of it and tried to find a suitable path.

Driver Pins shorted

To get GND to the LEDs i shorted some pins of one of the not installed LED drivers and replaced some 0805 resistors to be our current limiters. The original resistors in those spots were about 20 Ohms, so I think they overdrove the LEDs by a big margin to compensate for the matrix multiplexing. This is usually not a problem. LEDs release the magic smoke because they get too hot inside, if you can guarantee a 1ms pulse all 20ms for example you could probably overdrive the LED by a factor of 10 and be on the safe side still because it has time to dissipate the heat between the pulses. We're not doing that so I had to replace those resistors ( R128 to R131 ) with something more reasonable. As my partner complained already about the searing brightness of those LEDs I went with the conservative selection of 1.5 kOhms.

I then connected the TP5 from the Pico to the positive terminal of the Num-Lock LED and flashed a simple blink sketch and it worked. Good.

Unbricking in the case of failure

If you somehow brick the firmware on a Pico you can always go back to firmware update mode by holding the BOOTSEL key on the protoboard. The board will be inside the keyboard in the forseeable future, but I wanted to have that option, should anything go wrong in the future, while not having to unscrew the case and push a button once. The One 3 has a DIP switch on the bottom to switch between Mac and Windows modes and deactivate the Windows key, etc. This DIP switch is the ideal candidate for that boot mode selection button. Let's modify it a bit.

At first I removed the diode that probably integrated the DIP switch into the keyboard matrix (why? I don't know...), thats D119 for those following along at home...

I probed the path of the DIP switch and found out that the top 4 pins are shorted together, so I shorted that to GND in addition ( U8 pin 4 is a good location for GND) and connected TP6 to the top pad of the removed diode. Now we can switch to firmware update mode by flicking the 4th DIP switch up and re-connecting the keyboard. Turn the switch off again to boot in keyboard mode.

The copper spider enters the scene

As we have all bottom pads of the Pico connected where they belong we can stick it to the board with VHB tape or a dab of hot glue.

For the matrix connections I just used thin enamel wire again. I have a big roll of that and it makes connecting to small SMD pads relatively easy. Some wire is coated in an enamel that just burns of if you touch it with a soldering iron, this would be ideal as you could add the connections very fast in that way. No such luck with my wire. I mean it burns off the coating if you wait long enough, but at that time the PCB would be damaged for sure. So I removed a bit of the coating by first scraping it with a scalpel and then burning the remainder off with the iron on both ends of the measured wire.

Finished wiring

I wired the matrix in this way:

For the rows you have to use the right terminal of any of the listed key switches.

Row GPIO Switch
0 GP2 Scroll Lock
1 GP3 Insert
2 GP4 Delete
3 GP5 Enter
4 GP6 Up
5 GP7 Left
6 GP8 Num Lock
7 GP9 Calculator

For the columns always go with the top pad of the diode (the one without the line) for the switch in the list.

Column GPIO Switch
0 GP10 Scroll Lock
1 GP11 Insert
2 GP12 Numpad 3
3 GP13 Numpad 2
4 GP14 Numpad 1
5 GP15 Numpad Plus
6 GP16 Numpad 6
7 GP17 Numpad 5
8 GP18 Numpad 4
9 GP19 Numpad 9
10 GP20 Numpad 8
11 GP21 Numpad 7
12 GP22 Volume Up
13 GP26 Volume Down
14 GP27 Mute
15 GP28 Calculator

As you can see it is a good thing that the Numpad is connected as one row below the alpha numeric block. We can avoid running enamel wire over the whole keyboard this way and are safely contained below the Num-pad and arrow key areas.

Finishing touches

To finish the electronics side of the build we have to connect the Caps Lock and Scroll Lock LEDs. I have mapped them to GPIO0 and GPIO1 respectively. Just run a wire to the positive terminal of the leds and we're ready here.

I added some globs of hotmelt glue to secure the fine wires and placed the keyboard in it's lower case at this moment and got to configuring QMK.

QMK

For compiling QMK I would refer to the tutorial . If you want to pull my keyboard configuration you may do so at my QMK fork at my Github . If you're using my fork, make sure to check out the ducky_one_3 branch.

Compile the firmware with the qmk cli by running:

qmk compile --keyboard dunkelstern/ducky_one_3_rp2040 --keymap default
qmk flash

and then connect the keyboard with the DIP switch position 4 turned on and mount the drive that appears.