Build keyboard firmware by declaring keymaps in Nickel, powered by Rust.
A 'smart keyboard' is a keyboard with additional bells and whistles, such as alternate key functionality (layers, tap-hold keys, chords, etc.), or RGB effects, etc. -- e.g. QMK, ZMK are popular smart keyboard firmware frameworks.
This project provides a library which handles the keymap behaviour part of this.
-
Nickel allows for concise expression of keymaps, as semicolon's fak showed. (e.g. simple example keymap, a more sophisticated example keymap). Nickel is a configuration language along the lines of "JSON + functions"; its emphasis on correctness and modular configurations make it a powerful configuration language.
-
This project supports the cheap and powerful CH32X035, a 32-bit RISC-V MCU. -- In the same way that semicolon's fak enabled low-budget keyboard designs with the CH552, this project enables keyboard designs using CH32X035.
-
The smart keymap library can be built as a static native library, which makes it easy to integrate into USB HID keyboard examples written in C.
-
As Rust crate, the project also supports using Rust to write keyboard firmware, similar to what keyberon supports.
-
This project also draws inspiration from ideas explored in semickolon's kirei, which emphasised "keys as the main abstraction of keymap behaviour".
The main idea is write a keymap in Nickel (keymap.ncl
), and build keyboard
firmware which uses smart-keymap
with this custom keymap, either using
smart-keymap as a Rust crate, or using libsmart_keymap
as a native library.
See the keyboard firmware section below for keyboard firmware which uses smart-keymap.
To make use of smart-keymap in a "user config" repository, see rgoulter/smart-keyboard-config.
Documentation for features which have been implemented can be found at: https://rgoulter.com/smart-keymap/features.html
Some sample ncl keymaps can be found under tests/ncl/. In particular:
- 48-key basic keymap
- 36-key rgoulter keymap
- used as a 48-key keymap
- 34-key seniply keymap
- 36-key miryoku keymap
Published documentation for the Rust crates can be found at https://rgoulter.com/smart-keymap/doc/smart_keymap/index.html.
Pico42, rp2040-rtic-smart-keyboard firmware.
CH32X-36, ch32x035-usb-device-compositekm-c firmware
CH32X-48, ch32x035-usb-device-compositekm-c firmware
CH32X-75-LPR, ch32x035-usb-device-compositekm-c firmware
WABBLE-60, ch58x-ble-hid-keyboard-c firmware
MiniF4-36, stm32f4-rtic-smart-keyboard firmware, stm32-embassy-smart-keyboard firmware,
See the Keyboards wiki page.
Smart Keymap provides a library of functions. In this sense, it's most similar to TeXitoi's keyberon, where the user has to implement their own keyboard firmware. -- This is in contrast to popular customisable keyboard firmwares such as QMK or ZMK, which are frameworks.
Configuring keymaps is done using Nickel. Keyberon's keymaps are written directly in Rust, QMK's keymaps are written either in C, or with its data-driven configuration using JSON. ZMK's keymaps are written with devicetree.
Smart Keymap draws a lot of inspiration from semickolon's fak and kirei projects, which also use Nickel. Similar to these projects, Smart Keymap supports low-cost MCUs from WCH (CH32X, CH58x).
Some keyboard firmware which uses smart-keymap
:
The firmware under rp2040-rtic-smart-keyboard
has been adapted from the rgoulter/keyboard-labs firmware/keyberon code.
In particular, the firmware under rp2040-rtic-smart-keyboard
has an example for the
Pico42.
DevEnv is used to provide the toolchain dependencies.
Use devenv shell
to enter a shell which has all the tooling installed.
A DevContainer is defined for the project, which can be used to easily get started using e.g. VSCode, GitHub Codespaces, etc..
Using a keymap.ncl
(or the keymap.rs
generated from its definition),
the keyboard firmware can be built with a command such as:
env SMART_KEYMAP_CUSTOM_KEYMAP="$(pwd)/tests/ncl/keymap-42key-dvorak-simple-with-tap_hold/keymap.ncl" \
cargo build \
--release \
--target=thumbv6m-none-eabi \
--package=rp2040-rtic-smart-keyboard
The firmware can be deployed to an RP2040 board (in bootloader mode)
by using cargo run
instead of cargo build
.
The pico42
example can be built / run by adding --example pico42
.
A custom board.ncl
file can be built by setting the SMART_KEYBOARD_CUSTOM_BOARD
variable to its path, and building the rp2040-rtic-smart-keyboard
package's binary.
e.g. with 42key-dvorak/keymap.ncl,
let K = import "keys.ncl" in
# Define tap_hold keys
# by merging a `K.hold` modifier
# with a key.
let A_A = K.A & K.hold K.LeftAlt in
let G_O = K.O & K.hold K.LeftGUI in
let C_E = K.E & K.hold K.LeftCtrl in
let S_U = K.U & K.hold K.LeftShift in
let S_H = K.H & K.hold K.RightShift in
let C_T = K.T & K.hold K.RightCtrl in
let G_N = K.N & K.hold K.RightGUI in
let A_S = K.S & K.hold K.RightAlt in
{
keys = [
K.QUOT, K.COMM, K.DOT, K.P, K.Y, K.F, K.G, K.C, K.R, K.L,
A_A, G_O, C_E, S_U, K.I, K.D, S_H, C_T, G_N, A_S,
K.SCLN, K.Q, K.J, K.K, K.X, K.B, K.M, K.W, K.V, K.Z,
K.LCTL, K.LGUI, K.LALT, K.TAB, K.ESC, K.SPC, K.BSPC, K.RET, K.DEL, K.RALT, K.RGUI, K.RCTL,
],
}
and board-pico42.ncl (for the Pico42).
let C = import "../ncl/codegen/contracts.ncl" in
{
gpio_pins | { _ | C.GpioPin },
board | C.Board = {
usb = {
vid = 0xCAFE,
pid = 0x0005,
manufacturer = "rgoulter keyboard-labs",
product = "Pico42"
},
matrix =
let p = gpio_pins in
{
cols = [
p.GP0,
p.GP1,
p.GP2,
p.GP3,
p.GP4,
p.GP5,
p.GP6,
p.GP7,
p.GP8,
p.GP9,
p.GP10,
p.GP11,
],
rows = [
p.GP14,
p.GP15,
p.GP16,
p.GP17,
],
key_count = 42,
},
keymap_index_for_key = fun { column_index, row_index } =>
let NO = null in
let keymap_indices = [
[ 0, 1, 2, 3, 4, NO, NO, 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14, NO, NO, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, NO, NO, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41],
]
in
let row = std.array.at row_index keymap_indices in
std.array.at column_index row
|> match {
idx if idx != null => 'Key idx,
_ => 'NoKey,
},
},
}
the keyboard firmware can be built & flashed to the RP2040 bootloader with:
env \
SMART_KEYMAP_CUSTOM_KEYMAP="$(pwd)/tests/ncl/keymap-42key-dvorak-simple-with-tap_hold/keymap.ncl" \
SMART_KEYBOARD_CUSTOM_BOARD="$(pwd)/rp2040-rtic-smart-keyboard/examples/board-pico42.ncl" \
cargo run \
--release \
--target=thumbv6m-none-eabi \
--package=rp2040-rtic-smart-keyboard
Firmware for MiniF4-36 rev2021.4 split halves are implemented under stm32f4-rtic-smart-keyboard/examples.
Firmware for MiniF4-36 rev2021.5 split halves are implemented under stm32-embassy-smart-keyboard/examples.
The memory.x
used to link the firmware assumes tinyuf2 is used as the bootloader.
The STM32F4-RTIC examples can be built by running:
make minif4_36-rev2021_4-lhs.uf2
make minif4_36-rev2021_4-rhs.uf2
The keymap file used can be changed by setting SMART_KEYMAP_CUSTOM_KEYMAP
in the make
invocation:
make minif4_36-rev2021_4-lhs.uf2 SMART_KEYMAP_CUSTOM_KEYMAP=path/to/keymap.ncl
The example USB HID keyboard software from the EVT has been adapted to work with smart_keymap lib.
See firmware/ch32x035-usb-device-compositekm-c for more details.
Licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.