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
172 changes: 34 additions & 138 deletions docs/docs/main/docs/configuration/bootloader.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@
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:
A pre-built embassy-boot based bootloader called **rmk-boot** is available for both platforms. RMK integrates with rmk-boot through a **`memory.x`** file that ships alongside the bootloader. When using rmk-boot’s prebuilt binaries, download the matching `rmk-*-memory.x` from the [GitHub releases](https://github.com/rmk-rs/rmk-boot/releases).
When building rmk-boot yourself, it will create its matching `rmk-memory.x` in the project directory of rmk-boot.
In either way, rename the file to `memory.x` and place it next to your projects `Cargo.toml`.

```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
```
At runtime RMK reads partition offsets from **linker symbols** embedded in `memory.x` — you never compute or hardcode partition addresses yourself.

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.
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 +23,26 @@ 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
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 All @@ -114,7 +54,6 @@ rmk::dfu::mark_booted();
let unlock_keys: &[(u8, u8)] = &[(0, 0), (1, 1)];
let mut dfu_lock = ::rmk::dfu::DfuLock::new(unlock_keys, &keymap);
// Then add dfu_lock in run_all!()
// ...
run_all!(
// other processors ...
dfu_led_processor,
Expand All @@ -134,80 +73,26 @@ 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
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 All @@ -222,7 +107,6 @@ let unlock_keys: &[(u8, u8)] = &[(0, 0), (1, 1)];
let mut dfu_lock = ::rmk::dfu::DfuLock::new(unlock_keys, &keymap);

// Then add dfu_lock in run_all!()
// ...
run_all!(
// other processors ...
dfu_led_processor,
Expand All @@ -235,21 +119,34 @@ 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 linker symbols in the `memory.x` file from rmk-boot. The default layout with rmk-boot (2MB RP2040, 32K storage) is:

| Region | Offset | Size |
|-----------------|----------------|--------------------------------------|
| Bootloader(s) | `0x0000000` | 28 KB |
| Bootloader(s) | `0x0000000` | 24 KB |
| Boot state | `0x6000` | 4 KB |
| Active firmware | `0x7000` | `(flash_size - 28K - 128K - 4K) / 2` |
| DFU download | follows active | `active_size + 4K` |
| Storage | follows DFU | 128 KB |
| Active firmware | `0x7000` | `(flash_size - 28K (Bootloader) - 32K (Storage) - 4K (1 Page)) / 2` |
| DFU download | follows active | `active_size + 4K (1 Page)` |
| 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 from `memory.x` — you do **not** set `state_offset`, `dfu_offset`, or `flash_size` in `keyboard.toml`.

## Custom bootloader

Your `memory.x` must match this partition layout — see the [flashing guide](../user_guide/flash_firmware/use_embassy_boot.mdx) for details.
If you built your own embassy-boot bootloader, add these six symbols with matching values to your `memory.x` (all values are flash-relative offsets):

```text
__rmk_boot_state_offset = 0x6000;
__rmk_boot_state_size = 0x1000;
__rmk_boot_dfu_offset = 0xF3000;
__rmk_boot_dfu_size = 0xED000;
__rmk_boot_storage_offset = 0x1E0000;
__rmk_boot_storage_size = 0x20000;
```

Make sure `FLASH : ORIGIN` in your `MEMORY` block starts at your ACTIVE partition. RMK's `init_flash_from_linkerscript()` picks up the symbols at runtime.

## DFU LED (optional)

Expand All @@ -271,4 +168,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.
:::

11 changes: 10 additions & 1 deletion docs/docs/main/docs/configuration/storage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,17 @@ 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.
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.

:::
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
Loading