techWebsite/content/posts/rmk-ferris-sweep/index.md

6.9 KiB

+++ title = "Rust on the Ferris Sweep" date = 2025-03-10T17:28:15+01:00 draft = true [cover] image = "keyboard-with-rust" +++

The other day, I stumbled upon RMK, a keyboard firmware written in Rust. Given that my Ferris Sweep has a Ferris the crab logo on the silkscreen, it felt only fitting that I flash it with RMK.

However, RMK is a much newer project than QMK. QMK provides premade configurations for almost keyboard out there (or at least, every keyboard that allows flashing your own firmware), allowing you to build someone else's design and get straight to designing a keymap, without having to fuss about matrices or pin mappings. RMK has no such definitions, just documentation of how to write them and a few example projects, making this journey to put it on my Ferris Sweep interesting enough to write about.

Configuring the firmware

Unlike QMK, you don't clone the entire RMK repo, rather you make your own repo, which uses Rusts package manager, cargo, to depend on RMK. Setup of this repo is aided by the rmkit command line tool.

I started by installing a few tools via cargo, running

cargo install rmkit flip-link elf2uf2-rs probe-rs-tools

Then I ran rmkit init and answered a few questions about my keyboard, which generated a template for me to further modify. I modified .cargo/config.toml to use elf2uf2-rs as documented in the templates README, as I cant exactly set up a debug link on an already assembled keyboard. The docs say I need to modify the memory.x file for my microcontroller, but I found the rmkit tool had already set it with the right contents.

Then came the hard work, configuring keyboard.toml. I followed the docs, carefully started porting my Ferris Sweep layout to the keyboard.toml.

First off, for the keyboard metadata, I simply took from QMK's Ferris Sweep info.json.

Then for the tricky part. We have to define our pin mappings using the peripheral names as defined by embassy This is hard because the QMK mappings given in the keyboard.json are for the Aurdino pro-micro pin names, and I wasn't able to figure out what magic QMK does to convert those into the RP2040 Elite-pi equivalents. In the end, I just cloned the Ferris sweep repo and opened the files in Kicad, cross referencing the schematic with the elite-pis usage guide One stumbling block is that the Ferris sweep ran out of pins to use full-duplex UART, and therefore only uses half-duplex. To solve this, RMK supports (only on the RP2040) a PIO driver for half duplex, gated behind a crate feature in cargo.toml. With it, we can set our Tx and Rx pins to the same pin. In order to do this, however, I had to set the rmk dependency to a direct link to the Github repo, because as of the time of writing, the rp2040_pio feature had not made it into a release.

After the painstaking process of tracing all the pins and putting them in the matrix, we can define the keymap. My keymap is a bit complex, so it took some time to port. It was mostly tedium rather than anything truly head scratching, however.

vial.json

Next, and the most complicated step, was creating a vial.json. The JSON file provided by the via project was wrong, as it laid out the keyboard as a 8x5, rather than a 4x10 that I had done in RMK. So I had to take that file, load it into the keyboard layout editor, and follow vials guide to remake the vial.json in the right layout.

Flashing and troubleshooting

Finally, I could flash my keyboard. I flashed it, and... It didn't work. The left hand side, the one plugged into USB, worked fine, all keys worked. But the right hand side, connected to the main by TRRS, did nothing. A day or 2 of investigation later revealed that the half-duplex serial implementation only used the RP2040's internal pull-up resistors, which for my keyboard and TRRS cable, were insufficient for a baud rate of 115200. This was (temporarily) fixed by setting a lower baud rate, but for the long term, I've made a PR mirroring QMKs solution to this problem. (Ill update this article when it gets merged.)

But, after all that, I had a Ferris Sweep running Rust, just as the silkscreen demands. Granted, until the PR gets merged and a new release is cut, its running a modified version of RMK from my own repo, but Rust is Rust.

The final firmware repo is here, if you want to use it for your own RP2040, wired Ferris Sweep, or just want an example to help you along.

Using RMK.

I then proceeded to use RMK for a few tasks, including writing this article. It did not feel the exact same as QMK, I think some of the timings on tap-hold and debouncing might be different, but, apart from a few repeated keys (which should be fixed once configurable debouncing lands, it was a very serviceable keyboard firmware. The next release is focusing on input devices, so in the future RMK will also support keyboards with encoders and pointing devices like trackballs and touchpads.

Final thoughts

Due to the difficulty I had in setting up RMK, I would not recommend it to anyone who is not already familiar with configuring another keyboard firmware, and you should have at least some knowledge of microcontrollers, such as how to figure out your keyboards pinout. However, if you are interested and have some prior experience, it has some really cool ideas, such as the use of an async runtime in a microcontroller (thanks to embassy), and compile time parsing of its keyboard.toml file with a proc-macro to generate code for the firmware without forcing the user to write Rust. It is also a great example of a complex Rust codebase using Embassy.

As for myself, I plan on continuing to use RMK for my Ferris Sweep, and will probably use it for any other keyboard I build that it supports. I also plan on continuing to contribute to the project, fixing issues as I find them.