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 { unsafe { let mut desc = core::mem::zeroed::(); let mut token = core::mem::zeroed::(); 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_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::() + 3) / 4, }, }; let mut desc = core::mem::zeroed::(); let mut token = core::mem::zeroed::(); 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.