155 lines
9.4 KiB
Markdown
155 lines
9.4 KiB
Markdown
+++
|
|
title = "Rust on the Ferris Sweep"
|
|
date = 2025-03-25T17:28:15+01:00
|
|
draft = true
|
|
[cover]
|
|
image = "keyboard-with-rust"
|
|
+++
|
|
|
|
The other day, I stumbled upon [RMK](https://github.com/haobogu/rmk),
|
|
a keyboard firmware written in Rust.
|
|
Given that my [Ferris Sweep](../ferris-sweep-keyboard/) has a Ferris the crab logo on the silkscreen,
|
|
it felt only fitting that I flash it with RMK.
|
|
|
|
Since I first built it, my Ferris Sweep has been running [QMK](https://qmk.fm/),
|
|
a very mature C-based keyboard firmware.
|
|
QMK is a great project, and doing basic keymaps for an already-supported keyboard is straightforward and well-documented.
|
|
However if you are designing your own keyboard, or want to use certan advanced QMK features,
|
|
you wont be able to use QMKs JSON-based 'data driven' features.
|
|
Instead, you will have to use its C macro based configuration,
|
|
which can be daunting and may require understanding QMKs complex build system.
|
|
|
|
RMK is a much newer project than QMK.
|
|
QMK provides premade configurations for over 1,000 keyboards,
|
|
allowing you to build someone else's design and get straight to designing a keymap,
|
|
without having to fuss about with matrices or pin mappings.
|
|
RMK has no such definitions, just documentation of how to write them and a few example projects,
|
|
which made the journey to put it on my Ferris Sweep interesting enough to write about.
|
|
|
|
## Configuring the firmware
|
|
|
|
Now, the setup between RMK and QMK is a bit different.
|
|
In QMK, you clone the entire QMK repo, make any keymap modifications you want,
|
|
and compile.
|
|
RMK, being written in a language with a decent package manager,
|
|
just has you make a repo from a small template that depends on the RMK crate.
|
|
RMK provides a tool, `rmkit`, to help you setup this repo.
|
|
|
|
I started by installing `rmkit` and a few embedded tools via cargo, running:
|
|
```
|
|
cargo install rmkit flip-link elf2uf2-rs probe-rs-tools cargo-make cargo-binutils llvm-tools
|
|
```
|
|
|
|
Then I ran `rmkit init` and answered a few questions about my keyboard,
|
|
which generated my initial template.
|
|
From there, I modified `.cargo/config.toml` to use `elf2uf2-rs`,
|
|
as my keyboard does not have an exposed debug header, which would be needed for `probe-rs`.
|
|
The docs mentioned I may have to modify a file called `memory.x`,
|
|
which defines the microcontrollers memory map,
|
|
but I found the template already had the correct memory map for the RP2040.
|
|
|
|
### `keyboard.toml`
|
|
|
|
That was the easy part done.
|
|
The next step was to configure a file called `keyboard.toml`.
|
|
This file is used by RMK to configure everything from the type of microcontroller we use,
|
|
to defining which pins on the microcontroller correspond to which keys,
|
|
and even configuring the initial keymap,
|
|
all at compile time.
|
|
Similar to QMK, RMK does provide you with the option to configure your keyboard using Rust code directly,
|
|
but I wasn't doing anything fancy enough to justify that.
|
|
|
|
The first part of the `keyboard.toml` contains some basic metadata about your keyboard,
|
|
like its name, USB ID, and what microcontroller it uses.
|
|
I lifted all this info from [QMK's Ferris Sweep info.json](https://github.com/gabevenberg/qmk_firmware/blob/personal/keyboards/ferris/sweep/info.json) for consistencies sake.
|
|
|
|
Then I had to configure the pin mappings, defining which pin correspond to each key (in the Ferris Sweeps case, as it is a direct wire, where each pin corresponds to exactly 1 key),
|
|
or defining which pins correspond to rows and columns of the keyboard matrix (in the case of most larger keyboards.)
|
|
This proved more difficult than expected,
|
|
as I planned to take the pin mappings from QMKs pin mapping configuration file,
|
|
the [`keyboard.json`](https://github.com/gabevenberg/qmk_firmware/blob/personal/keyboards/ferris/sweep/info.json).
|
|
However, it was unusable, as the `keyboard.json` gives pin mappings for a different microcontroller,
|
|
the ATmega32U4, specifically for the Aurdino Pro-Micro board.
|
|
QMK does some black magic at compile time in order to rewrite these pin mappings to their RP2040 equivalents,
|
|
but I was not able to figure out said magic in order to do the same by hand.
|
|
In the end, I cloned the [repo](https://github.com/davidphilipbarr/Sweep) for the Ferris Sweep itself and looked at the PCB design in Kicad,
|
|
Tracing where each pin went and cross referencing with the elite-pi (my specific RP2040 board) [usage guide](https://docs.keeb.io/elite-pi-guide) to get the pin number.
|
|
|
|
Another thing I had to do was define how the 2 microcontrollers in each half of the keyboard communicate with each other,
|
|
and define the pins that are used in that communication.
|
|
Due to being a 'direct wire' design,
|
|
the Ferris Sweep only has one IO pin on each micro that's not dedicated to a key.
|
|
This means it has to use whats called 'half-duplex' UART,
|
|
where a single wire is used for 2 way communication.
|
|
Luckily, RMK supports this mode of communication through the RP2040's [PIO](https://www.raspberrypi.com/news/what-is-pio/) feature.
|
|
Unfortunately, this ability to use half-duplex over PIO is only on RMK's master branch,
|
|
and has not made it into a stable release as of this writing.
|
|
This simply means I had to do some fiddling in the `Cargo.toml` file to instruct cargo to fetch the latest commit from git, rather than getting the latest published version of the package.
|
|
|
|
After the *painful* process of tracing each pin and defining the pin mappings,
|
|
we can define the keymap.
|
|
My [keymap](https://github.com/gabevenberg/qmk_firmware/blob/personal/keyboards/ferris/keymaps/almost_default/keymap.json) is not especially fancy,
|
|
other than its extensive use of layers and tap-hold Keybinds.
|
|
(keybinds that do a different thing on being held down than they do on tapping them)
|
|
All the features I use are [well documented](https://haobogu.github.io/rmk/keyboard_configuration.html#layout) by RMK,
|
|
so while porting the keymap was tedious, it was not especially difficult or noteworthly.
|
|
|
|
### `vial.json`
|
|
|
|
The next and most complicated step, was creating a `vial.json`.
|
|
This file is used by the [VIAL](https://get.vial.today/) software to allow you to remap your keyboard without re-flashing the microcontrollers,
|
|
and for 'reasons', the keyboard will fail to boot if your `vial.json` does not match your keymap.
|
|
The JSON file [provided by the via project](https://github.com/the-via/keyboards/blob/master/src/ferris/sweep/sweep.json) 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](https://www.keyboard-layout-editor.com/),
|
|
and follow [vials guide](https://get.vial.today/docs/porting-to-via.html) to remake the `vial.json` in the right layout.
|
|
Ill be honest, I still don't fully understand what I was doing, I just messed around until it worked.
|
|
|
|
## Flashing and debugging
|
|
|
|
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 in the RMK source code,
|
|
but for the long term, I've made a [PR](https://github.com/HaoboGu/rmk/pull/291) mirroring [QMKs solution](https://github.com/qmk/qmk_firmware/blob/6d0e5728aa61b442885d48caf49d29e5c60e8197/platforms/chibios/drivers/vendor/RP/RP2040/serial_vendor.c#L133) to this problem.
|
|
|
|
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 fork, but its still Rust.
|
|
|
|
The final firmware repo is [here](https://github.com/gabevenberg/ferris-sweep-rmk),
|
|
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 key debouncing might be different,
|
|
but, apart from a few repeated keys (which should be fixed once configurable debouncing [lands](https://github.com/HaoboGu/rmk/issues/289),
|
|
it was a very serviceable keyboard firmware.
|
|
Obviously, being a much newer project than QMK,
|
|
it has not yet implemented some of the more advanced features QMK has,
|
|
such as support for displays, per-key RGB, or pointing devices.
|
|
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.
|