This commit is contained in:
parent
f7c643d706
commit
511b4ef079
|
|
@ -0,0 +1,228 @@
|
||||||
|
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.
|
||||||
Loading…
Reference in New Issue