Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/docs/main/docs/configuration/appendix.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,10 @@ enabled = true
# Note: When the `dfu_rp` or `dfu_nrf` feature is enabled, this value is ignored.
# The storage partition is automatically placed after the DFU download slot.
start_addr = 0xA0000
# Number of sectors used for storage, >= 2
# Number of sectors used for storage, >= 2 (defaults to 8 when DFU is enabled)
num_sectors = 16
# Note: When the `dfu_rp` or `dfu_nrf` feature is enabled, this value is ignored.
# The storage partition is automatically placed after the DFU download slot (from rmk-boot.x).
# Clear storage at keyboard boot.
# Set it to true will reset the storage(including keymap, BLE bond info, etc.) at each reboot.
# This option is useful when testing the firmware.
Expand Down
163 changes: 29 additions & 134 deletions docs/docs/main/docs/configuration/bootloader.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@
RMK supports DFU firmware updates via embassy-boot for **RP2040** and **nRF52840**. An embassy-boot based bootloader splits flash into ACTIVE and DFU slots, providing safe updates with automatic rollback on failure.
This is an optional feature of RMK, the default bootloaders of the devices can still be used as usual without runtime updates via USB DFU.

A pre-built embassy-boot based bootloader called **rmk-boot** is available for both platforms. The partition formula is identical:

```text
bootloader+state = 28K (fixed)
storage = 128K (fixed — 32 sectors × 4K for persistent keymap storage)
remaining = flash_size - 28K - 128K
ACTIVE = (remaining - 4K) / 2
DFU = ACTIVE + 4K
A pre-built embassy-boot based bootloader called **rmk-boot** is available for both platforms. RMK integrates with rmk-boot through a linker script called **`rmk-boot.x`**, which is the single source of truth for the flash partition layout. You download it alongside the bootloader binary from the [GitHub releases](https://github.com/rmk-rs/rmk-boot/releases) and place it next to your `Cargo.toml`.

Your project's `build.rs` copies `rmk-boot.x` into the linker's search path and creates a `memory.x` redirect:

```rust
if Path::new("rmk-boot.x").exists() {
fs::write(out.join("rmk-boot.x"), fs::read("rmk-boot.x").unwrap()).unwrap();
fs::write(out.join("memory.x"), "INCLUDE rmk-boot.x\n").unwrap();
println!("cargo:rustc-link-search={}", out.display());
}
```

See the [flashing guide](../user_guide/flash_firmware/use_embassy_boot.mdx) for step-by-step instructions on how to get the bootloader and RMK flashed.
At runtime RMK reads partition offsets from **linker symbols** embedded in `rmk-boot.x` to tell RMK the flash layout.

See the [flashing guide](../user_guide/flash_firmware/use_embassy_boot.mdx) for step-by-step instructions on getting the bootloader and RMK flashed.

## RP2040

Expand All @@ -27,82 +31,27 @@ import { Tab, Tabs, Rust, Toml } from '@theme'

```toml title="keyboard.toml"
[dfu]
# (Optional) Total flash size in bytes. Used to auto-calculate partition addresses.
# Defaults to 2 MB (2097152) when omitted.
# ⚠ You can define your own FLASH_SIZE and offset addresses, but then you must build and
# flash a custom embassy-boot bootloader with a matching memory.x!
flash_size = 2097152

# (Optional) Flash page size in bytes (4096 for RP2040).
page_size = 4096

# (Optional) DFU activity LED pin, default "PIN_25".
led = "PIN_25"
# led = "none" to omit DFU LED

# (Optional) Unlock keys for dfu_lock (physical matrix positions). Only works with dfu_lock feature enabled in Cargo.toml.
# (Optional) Unlock keys for dfu_lock (physical matrix positions).
# Only works with dfu_lock feature enabled in Cargo.toml.
unlock_keys = [[0, 0], [1, 1]]

# ── (Optional) Manual overrides (only if auto-calculation is not suitable) ──
state_offset = 0x6000
state_size = 0x1000
dfu_offset = 0x87000
dfu_size = 528384
```

</Tab>
<Tab label={<Rust />}>

```rust title="main.rs"
// Flash layout using the rmk-boot formula:
// state at 0x6000 (4K), active from 0x7000 (size: (flash_size - 28K (= BOOT2 size + embassy-boot + embassy-boot state) - STORAGE_SIZE (= 128K) - page_size (= 4K)) / 2),
// dfu follows active (active_size + page_size (= 4K))
//
// All offsets (DFU_OFFSET, DFU_SIZE, STORAGE_OFFSET, etc.) are derived
// automatically from FLASH_SIZE below — change only that constant when using rmk-boot.
//
// ⚠ You can define your own FLASH_SIZE and addresses, but then you must build and
// flash a custom embassy-boot bootloader with a matching memory.x!
const FLASH_SIZE: u32 = 2 * 1024 * 1024; // 2 MB (default)
// const FLASH_SIZE: u32 = 4 * 1024 * 1024; // 4 MB
// const FLASH_SIZE: u32 = 8 * 1024 * 1024; // 8 MB
// const FLASH_SIZE: u32 = 16 * 1024 * 1024; // 16 MB
const PAGE_SIZE: u32 = 4 * 1024;
const STORAGE_SIZE: u32 = 128 * 1024; // 32 sectors × 4K after ACTIVE+DFU
const STATE_OFFSET: u32 = 0x6000;
const STATE_SIZE: u32 = 0x1000;
const ACTIVE_OFFSET: u32 = 0x7000; // after 28K bootloader + state
let remaining: u32 = FLASH_SIZE
- 28 * 1024 // size of boot 2 + embassy-boot + embassy-boot state
- STORAGE_SIZE;
let active_size: u32 = (remaining - PAGE_SIZE) / 2; // DFU = ACTIVE + 1 page (embassy-boot requirement)
let dfu_size: u32 = active_size + PAGE_SIZE; // embassy-boot needs that extra page for swap info
let dfu_offset: u32 = ACTIVE_OFFSET + active_size; // dfu after active
let storage_offset: u32 = dfu_offset + dfu_size; // storage after active + dfu
assert!(storage_offset + STORAGE_SIZE == FLASH_SIZE); // sanity check that we fit everything in flash

info!(
"Flash layout: state @ 0x{:04X} ({}K), active @ 0x{:04X} ({}K), dfu @ 0x{:04X} ({}K), storage @ 0x{:04X} ({}K)",
STATE_OFFSET,
STATE_SIZE / 1024,
ACTIVE_OFFSET,
active_size / 1024,
dfu_offset,
dfu_size / 1024,
storage_offset,
STORAGE_SIZE / 1024
// All partition offsets come from rmk-boot.x linker symbols.
let flash = async_flash_wrapper(
rmk::dfu::init_flash_from_linkerscript(p.FLASH)
);

let flash = async_flash_wrapper(rmk::dfu::init_flash(
p.FLASH,
storage_offset,
STORAGE_SIZE,
STATE_OFFSET,
STATE_SIZE,
dfu_offset,
dfu_size,
));

// Optional: assign a DFU activity LED
let mut dfu_led_processor =
rmk::processor::builtin::dfu_led::DfuLedProcessor::new(Output::new(p.PIN_25, Level::Low), false);
Expand Down Expand Up @@ -134,80 +83,27 @@ Add a `[dfu]` section to your `keyboard.toml` or use the Rust API directly.

```toml title="keyboard.toml"
[dfu]
# (Optional) Total flash size in bytes. Used to auto-calculate partition addresses.
# 1 MB flash — auto-calculates ACTIVE (432K) and DFU (436K)
# ⚠ You can define your own FLASH_SIZE and offset addresses, but then you must build and
# flash a custom embassy-boot bootloader with a matching memory.x!
flash_size = 1048576

# (Optional) Flash page size in bytes (4096 for RP2040).
# (Optional) Flash page size in bytes (4096 for nRF52840).
page_size = 4096

# (Optional) DFU activity LED pin, default "P0_15".
led = "P0_15"
# led = "none" to omit DFU LED

# (Optional) Unlock keys for dfu_lock (physical matrix positions). Only works with dfu_lock feature enabled in Cargo.toml.
# (Optional) Unlock keys for dfu_lock (physical matrix positions).
# Only works with dfu_lock feature enabled in Cargo.toml.
unlock_keys = [[0, 0], [1, 1]]

# ── (Optional) Manual overrides (only if auto-calculation is not suitable) ──
state_offset = 0x6000
state_size = 0x1000
dfu_offset = 0x87000
dfu_size = 528384
```

</Tab>
<Tab label={<Rust />}>

```rust title="main.rs"
// Flash layout using the rmk-boot formula:
// state at 0x6000 (4K), active from 0x7000 (size: (flash_size - 28K (= embassy-boot + embassy-boot state) - STORAGE_SIZE (= 128K) - page_size (= 4K)) / 2),
// dfu follows active (active_size + page_size (= 4K))
//
// All offsets (DFU_OFFSET, DFU_SIZE, STORAGE_OFFSET, etc.) are derived
// automatically from FLASH_SIZE below — change only that constant when using
// rmk-boot.
//
// ⚠ You can define your own FLASH_SIZE and addresses, but then you must build and
// flash a custom embassy-boot bootloader with a matching memory.x!
const FLASH_SIZE: u32 = 1024 * 1024; // 1 MB (nRF52840)
const PAGE_SIZE: u32 = 4 * 1024;
const STORAGE_SIZE: u32 = 128 * 1024; // 32 sectors × 4K after ACTIVE+DFU
const STATE_OFFSET: u32 = 0x6000;
const STATE_SIZE: u32 = 0x1000;
const ACTIVE_OFFSET: u32 = 0x7000;
let remaining: u32 = FLASH_SIZE
- 28 * 1024 // bootloader (24K) + state (4K)
- STORAGE_SIZE;
let active_size: u32 = (remaining - PAGE_SIZE) / 2;
let dfu_size: u32 = active_size + PAGE_SIZE;
let dfu_offset: u32 = ACTIVE_OFFSET + active_size;
let storage_offset: u32 = dfu_offset + dfu_size;
assert!(storage_offset + STORAGE_SIZE == FLASH_SIZE);

info!(
"Flash layout: state @ 0x{:04X} ({}K), active @ 0x{:04X} ({}K), dfu @ 0x{:04X} ({}K), storage @ 0x{:04X} ({}K)",
STATE_OFFSET,
STATE_SIZE / 1024,
ACTIVE_OFFSET,
active_size / 1024,
dfu_offset,
dfu_size / 1024,
storage_offset,
STORAGE_SIZE / 1024
// All partition offsets come from rmk-boot.x linker symbols.
let flash = async_flash_wrapper(
rmk::dfu::init_flash_from_linkerscript(p.NVMC)
);

let flash = async_flash_wrapper(rmk::dfu::init_flash(
p.NVMC,
storage_offset,
STORAGE_SIZE,
STATE_OFFSET,
STATE_SIZE,
dfu_offset,
dfu_size,
));

// Optional: assign a DFU activity LED
let mut dfu_led_processor = rmk::processor::builtin::dfu_led::DfuLedProcessor::new(
Output::new(p.P0_15, Level::Low, OutputDrive::Standard),
Expand Down Expand Up @@ -235,21 +131,21 @@ run_all!(

## Partition layout

The bootloader divides flash into regions. The defaults follow the [rmk-boot](https://github.com/rmk-rs/rmk-boot) convention and are automatically calculated from `flash_size`:
The bootloader divides flash into regions. All offsets and sizes come from the `rmk-boot.x` linker script. The default layout with rmk-boot (2MB RP2040, 32K storage) is:

| Region | Offset | Size |
|-----------------|----------------|--------------------------------------|
| Bootloader(s) | `0x0000000` | 28 KB |
| Boot state | `0x6000` | 4 KB |
| Active firmware | `0x7000` | `(flash_size - 28K - 128K - 4K) / 2` |
| Active firmware | `0x7000` | `(flash_size - 28K - 32K - 4K) / 2` |
| DFU download | follows active | `active_size + 4K` |
| Storage | follows DFU | 128 KB |
| Storage | follows DFU | 32 KB (8 sectors × 4K) |

The DFU partition size follows embassy-boot guidelines, the additional page is used for status information during flashing.

All `[dfu]` fields are optional. The partition values are auto-calculated from `flash_size` and `page_size` using the rmk-boot formula. To override them, supply `state_offset`, `state_size`, `dfu_offset`, and `dfu_size` directly — all four must be set together (setting only some of them is a build error) — and auto-calculation is disabled in favor of your values.
The `[dfu]` section is **optional** and configures only DFU behaviour (LED, unlock keys, page size). Partition offsets are read at link time via `rmk-boot.x`.

Your `memory.x` must match this partition layout — see the [flashing guide](../user_guide/flash_firmware/use_embassy_boot.mdx) for details.
Your `memory.x` is generated from `rmk-boot.x` via the `build.rs` pattern shown above. See the [flashing guide](../user_guide/flash_firmware/use_embassy_boot.mdx) for details.

## DFU LED (optional)

Expand All @@ -271,4 +167,3 @@ See the [DFU lock section](../user_guide/flash_firmware/use_embassy_boot.mdx#unl
::: tip
Choose keys that are easy to press simultaneously but not commonly pressed together accidentally.
:::

8 changes: 8 additions & 0 deletions docs/docs/main/docs/configuration/storage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ let rmk_config = RmkConfig {
</Tab>
</Tabs>

## Storage and DFU

When using DFU (`dfu_rp` / `dfu_nrf`), the storage partition is placed **after the DFU download slot**, determined by the `rmk-boot.x` linker script. In this mode:

- `start_addr` is **ignored** — RMK automatically places storage at the address defined in `rmk-boot.x` (`__rmk_boot_storage_offset`).
- The default `num_sectors` is **8** (32 KB, matching rmk-boot's default storage area). You can override `num_sectors` to use fewer sectors, but cannot exceed the allocated area.

If you change the storage size in rmk-boot's `build.rs`, rebuild the bootloader and regenerate `rmk-boot.x`. All partition addresses recalculate automatically.
::: info Storage is re-initialized when the firmware changes

Every firmware build embeds a unique build hash, and on boot RMK compares it with the hash saved in flash. If they differ (for example after flashing a new build), the whole storage area is erased and re-initialized with the firmware's defaults, so saved keymap changes and BLE bonds don't survive a firmware update.
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/main/docs/features/use_rust_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ If you're using **nRF52840**, ensure that you have [Adafruit_nRF52_Bootloader](h

You can check either your microcontroller's datasheet or an existing Rust project for your microcontroller for the correct values.

::: note
If you're using **rmk-boot** with DFU firmware updates (`dfu_rp` / `dfu_nrf`), you don't need to write a `memory.x` manually. Download `rmk-boot.x` alongside the bootloader binary, place it next to your `Cargo.toml`, and configure your `build.rs` as described in the [embassy-boot flashing guide](../user_guide/flash_firmware/use_embassy_boot.mdx).
:::

### Update `main.rs`

The generated `main.rs` needs to be updated as well to use Rust code. You can copy the code from RMK's Rust example, such as <https://github.com/rmk-rs/rmk/blob/main/examples/use_rust/rp2040/src/main.rs> to `src/main.rs` to get started.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/main/docs/getting_started/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ ERROR Keymap reading aborted!
└─ rmk::keymap::{impl#0}::new_from_storage::{async_fn#0} @ /Users/haobogu/Projects/keyboard/rmk/rmk/src/keymap.rs:38
```

If you have more sectors available in your internal flash, you can increase `num_sectors` in `[storage]` section of your `keyboard.toml`, or change `storage_config` in your [`RmkConfig`](https://docs.rs/rmk/latest/rmk/config/struct.RmkConfig.html) if you're using Rust API.
If you have more sectors available in your internal flash, you can increase `num_sectors` in `[storage]` section of your `keyboard.toml`, or change `storage_config` in your [`RmkConfig`](https://docs.rs/rmk/latest/rmk/config/struct.RmkConfig.html) if you're using Rust API. When using DFU (`dfu_rp` / `dfu_nrf`), the storage partition is placed after the DFU slot (via `rmk-boot.x`) and the default is 8 sectors (32 KB).

### OUTDATED: panicked at embassy-executor: task arena is full.

Expand Down
Loading