I wrote keyboard firmware in Rust

212 points SuspiciousSegfault

Once again I have wasted by time doing weird stuff in Rust!

I recently built a new keyboard with a lily58 PCB,
and two liatris
rp-2040-based-MCUs.

About a month ago I made a post on r/olkb
about how I managed to get QMK firmware to run on it using both cores.
However, as a maintainer explained in that thread, it was silly since QMK isn't thread safe.

I briefly thought about trying to implement it in broader QMK, but went for a dive into embedded on Rust instead,
ending with me having a keyboard with my exact same layout written completely in Rust in about 6.3 kLoC,
using two cores, with fewer bugs (those bugs were caused by me in my qmk firmware implementation to be fair,
QMK has in my experience been solid).

I did a long form write-up here going into the specifics,
from peripherals and interrupts, to surprising inlining-results, but TLDR:

Is rust a good fit for keyboard firmware?

Well, people have already done something more general than what I did with Keyberon
(I should have looked this up before, but I didn't.). That could probably be used to make something
with the simplicity of use of QMK given some time and effort, if peripherals support
for oleds, rotary encoders, split keyboards, etc. are added.

DevEx

The developer experience was pretty good as per usual, except for debugging. I debugged everything through a serial
USB-connection and that was pretty terrible. When I was about 95% done I told a colleague about this who told me about
probe-rs, my advice is to try that, it'll hopefully make your life easier.

Turns out embedded rust has a lot of great libraries and I could find pretty much everything I needed, just making some minor alterations!

Performance

My favourite part, I spent a lot of time optimizing.

I could measure performance pretty easily in my own implementation, what's of most interest is scan-rate, and press-latency,
I rendered the measurements directly to the on-board oleds, and I was able to get them both pretty low.
One full matrix-scan takes less than 20μs when idle, and a keypress gets discovered and processed in around 60μs.
On QMK, caused by just sleeping 30μs per column, both scan rate and press latency is at a provable minimum
of 180+μs for 6 columns.

This performance gain is minimized by the fact that minimum poll-rate on the OS side is 1000μs,
which turns it from having about 1/3 of QMK's latency to ~11/13ths. I might try to check out Keyberon's performance as well in the future.

Perceived performance

A lot of times people say that you can't tell millisecond differences.
I thought so too before I started developing a WM and got dumped into tty on a regular basis.
There, every key-press felt fast, while when my WM was not crashing it felt slow.
Later I found out that alacritty has a high input latency
(around 25ms on my machine), I switched to xterm which I measured to about 5ms and felt a huge difference.

So the question isn't about the small numbers here, the question is: Does the rust implementation feel faster?
No.

Questions for the community

If anyone knows how to implement NKRO I'd love to hear about it.
Since the keyboard_report Rust's usbd_hid,
osdev only has 6 slots for keycodes I was only
able to implement 6KRO.

Also, if anyone knows of a way to push more than 1 report per os-poll I'd love to hear about that
as well!


We'll se what I get up to next time, thanks for reading!

The code is here if you'd like to have a look!