229 lines
4.4 KiB
Markdown
229 lines
4.4 KiB
Markdown
Alright, let’s do a **minimal, no-magic FDS example** that actually works for
|
||
**nRF52840 + SoftDevice + “user might pull power instantly”**.
|
||
|
||
I’ll keep this focused on *one tiny config record*.
|
||
|
||
---
|
||
|
||
## What this example assumes
|
||
|
||
* You already have **SoftDevice enabled**
|
||
* You can handle **SoftDevice events** somewhere in your app
|
||
* You want to store ~5–6 bytes reliably
|
||
|
||
This is **pure SoftDevice FDS**, no Embassy storage abstractions.
|
||
|
||
---
|
||
|
||
## 1️⃣ Define your config format
|
||
|
||
Keep it boring and explicit:
|
||
|
||
```rust
|
||
#[repr(C)]
|
||
#[derive(Clone, Copy)]
|
||
struct Config {
|
||
magic: u8,
|
||
version: u8,
|
||
data: [u8; 4],
|
||
}
|
||
|
||
const CONFIG_MAGIC: u8 = 0x42;
|
||
const CONFIG_VERSION: u8 = 1;
|
||
```
|
||
|
||
Total: **6 bytes** 👍
|
||
|
||
---
|
||
|
||
## 2️⃣ FDS constants
|
||
|
||
These must be **non-zero** and stable forever:
|
||
|
||
```rust
|
||
const FDS_FILE_ID: u16 = 0x1111;
|
||
const FDS_RECORD_KEY: u16 = 0x2222;
|
||
```
|
||
|
||
---
|
||
|
||
## 3️⃣ Initialize FDS
|
||
|
||
Call this **after SoftDevice is enabled**:
|
||
|
||
```rust
|
||
use nrf_softdevice::raw;
|
||
|
||
pub fn fds_init() {
|
||
unsafe {
|
||
let ret = raw::fds_init();
|
||
assert_eq!(ret, raw::NRF_SUCCESS);
|
||
}
|
||
}
|
||
```
|
||
|
||
⚠️ FDS is async — init finishes when you receive `FDS_EVT_INIT`.
|
||
|
||
---
|
||
|
||
## 4️⃣ Handling FDS events (important!)
|
||
|
||
You **must** forward SoftDevice events to FDS:
|
||
|
||
```rust
|
||
pub fn on_sd_evt(evt: *const raw::ble_evt_t) {
|
||
unsafe {
|
||
raw::fds_sys_evt_handler(evt as u32);
|
||
}
|
||
}
|
||
```
|
||
|
||
And somewhere in your SD event handler:
|
||
|
||
```rust
|
||
match evt_id {
|
||
raw::FDS_EVT_INIT => {
|
||
// FDS ready
|
||
}
|
||
raw::FDS_EVT_WRITE => {
|
||
// Write completed safely
|
||
}
|
||
raw::FDS_EVT_UPDATE => {
|
||
// Update completed
|
||
}
|
||
_ => {}
|
||
}
|
||
```
|
||
|
||
You don’t need to block — just observe completion.
|
||
|
||
---
|
||
|
||
## 5️⃣ Read config from flash
|
||
|
||
```rust
|
||
pub fn load_config() -> Option<Config> {
|
||
unsafe {
|
||
let mut desc = core::mem::zeroed::<raw::fds_record_desc_t>();
|
||
let mut token = core::mem::zeroed::<raw::fds_find_token_t>();
|
||
|
||
let ret = raw::fds_record_find(
|
||
FDS_FILE_ID,
|
||
FDS_RECORD_KEY,
|
||
&mut desc,
|
||
&mut token,
|
||
);
|
||
|
||
if ret != raw::NRF_SUCCESS {
|
||
return None;
|
||
}
|
||
|
||
let mut record = core::mem::zeroed::<raw::fds_flash_record_t>();
|
||
raw::fds_record_open(&desc, &mut record);
|
||
|
||
let cfg = *(record.p_data as *const Config);
|
||
|
||
raw::fds_record_close(&desc);
|
||
|
||
if cfg.magic == CONFIG_MAGIC && cfg.version == CONFIG_VERSION {
|
||
Some(cfg)
|
||
} else {
|
||
None
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
On boot:
|
||
|
||
* If `None` → use defaults
|
||
* If `Some(cfg)` → you’re golden
|
||
|
||
---
|
||
|
||
## 6️⃣ Write / update config (power-loss safe)
|
||
|
||
This is the *core value* of FDS.
|
||
|
||
```rust
|
||
pub fn save_config(cfg: &Config) {
|
||
unsafe {
|
||
let record = raw::fds_record_t {
|
||
file_id: FDS_FILE_ID,
|
||
key: FDS_RECORD_KEY,
|
||
data: raw::fds_record_chunk_t {
|
||
p_data: cfg as *const _ as *const core::ffi::c_void,
|
||
length_words: (core::mem::size_of::<Config>() + 3) / 4,
|
||
},
|
||
};
|
||
|
||
let mut desc = core::mem::zeroed::<raw::fds_record_desc_t>();
|
||
let mut token = core::mem::zeroed::<raw::fds_find_token_t>();
|
||
|
||
let ret = raw::fds_record_find(
|
||
FDS_FILE_ID,
|
||
FDS_RECORD_KEY,
|
||
&mut desc,
|
||
&mut token,
|
||
);
|
||
|
||
if ret == raw::NRF_SUCCESS {
|
||
raw::fds_record_update(&mut desc, &record);
|
||
} else {
|
||
raw::fds_record_write(&mut desc, &record);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Why this is safe 🔒
|
||
|
||
* Old record stays valid until new one is fully written
|
||
* Power loss = either old config or new config
|
||
* Never corrupted data
|
||
|
||
---
|
||
|
||
## 7️⃣ BLE write flow (typical)
|
||
|
||
```text
|
||
BLE write received
|
||
→ validate data
|
||
→ build Config
|
||
→ save_config()
|
||
→ wait for FDS_EVT_UPDATE
|
||
→ ACK to user
|
||
```
|
||
|
||
If power dies *anywhere* → still safe.
|
||
|
||
---
|
||
|
||
## 8️⃣ Flash usage (don’t forget this!)
|
||
|
||
In your SoftDevice config:
|
||
|
||
* Enable **FDS**
|
||
* Reserve **at least 2 pages** (3 is safer)
|
||
|
||
FDS will garbage-collect automatically.
|
||
|
||
---
|
||
|
||
## TL;DR (real talk)
|
||
|
||
For your exact requirements:
|
||
|
||
* **Yes, this is the right solution**
|
||
* **Yes, it’s more code than 6 bytes deserve**
|
||
* **But it’s bulletproof**
|
||
|
||
If you want next:
|
||
|
||
* 🧵 Wrap this in an **Embassy async interface**
|
||
* 🧪 Add CRC / version migration
|
||
* 🔄 Reduce boilerplate further
|
||
* 🧠 Explain FDS GC & page sizing
|
||
|
||
Just say the word.
|