This commit is contained in:
Vitalii 2026-02-10 12:22:17 +02:00
parent f7c643d706
commit 511b4ef079
Signed by: SymbX
GPG Key ID: FF51F4E4BCE459EE
1 changed files with 228 additions and 0 deletions

228
fds.md Normal file
View File

@ -0,0 +1,228 @@
Alright, lets do a **minimal, no-magic FDS example** that actually works for
**nRF52840 + SoftDevice + “user might pull power instantly”**.
Ill 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 ~56 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 dont 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)` → youre 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 (dont 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, its more code than 6 bytes deserve**
* **But its 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.