pub struct FlashArray<const N: usize>;target_os=none only.Expand description
A device abstraction for type-safe persistent storage in flash memory.
This struct provides a generic flash-block storage system for Raspberry Pi Pico,
allowing you to store any serde-compatible type in the device’s internal flash.
You choose the number of storage blocks at compile time. Each block holds up to 3900 bytes of postcard-serialized data (a hardware-determined 4 KB flash block minus metadata space).
§Features
- Type safety: Hash-based type checking prevents reading data written under a
different Rust type name. The hash is derived from the full type path
(for example,
app1::BootCounter). Trying to read a different types returnsOk(None). Structural changes (adding or removing fields) do not change the hash, but may cause deserialization to fail and return an error. - Postcard serialization: A compact,
no_std-friendly binary format.
§Block allocation
Conceptually, flash is treated as an array of fixed-size erase blocks counted from the end of memory backward. Your code can split that array using destructuring assignment and hand individual blocks to subsystems that need persistent storage.
⚠️ Warning: Pico 1 and Pico 2 store firmware, vector tables, and user data in the same flash device. Allocating too many blocks can overwrite your firmware.
§Example
use device_envoy::flash_array::FlashArray;
/// Boot counter (newtype) that wraps at 10.
/// Stored with `postcard` (Serde).
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy)]
struct BootCounter(u8);
impl BootCounter {
const fn new(value: u8) -> Self {
Self(value)
}
fn increment(self) -> Self {
Self((self.0 + 1) % 10)
}
}
async fn example() -> device_envoy::Result<Infallible> {
let p = embassy_rp::init(Default::default());
// Create a flash array. You can destructure it however you like.
let [mut boot_counter_flash_block] = FlashArray::<1>::new(p.FLASH)?;
// Read boot counter from flash then increment.
// FlashArray includes a runtime type hash so values are only loaded
// if the stored type matches the requested type; mismatches yield `None`.
let boot_counter = boot_counter_flash_block
.load()?
.unwrap_or(BootCounter::new(0)) // Default to 0 type not present
.increment();
// Write incremented counter back to flash.
// This example writes once per power-up (fine for a demo; don't write in a tight loop).
// Flash is typically good for ~100K erase cycles per block.
boot_counter_flash_block.save(&boot_counter)?;
info!("Boot counter: {}", boot_counter.0);
future::pending().await // Keep running
}Implementations§
Source§impl<const N: usize> FlashArray<N>
impl<const N: usize> FlashArray<N>
Sourcepub fn new(peripheral: Peri<'static, FLASH>) -> Result<[FlashBlock; N]>
pub fn new(peripheral: Peri<'static, FLASH>) -> Result<[FlashBlock; N]>
Reserve N contiguous blocks and return them as an array that you can destructure however you like.
See FlashArray for usage examples.
Examples found in repository?
27async fn inner_main(spawner: embassy_executor::Spawner) -> Result<Infallible> {
28 let p = embassy_rp::init(Default::default());
29
30 let [wifi_flash] = FlashArray::<1>::new(p.FLASH)?;
31
32 let wifi_auto = WifiAuto::new(
33 p.PIN_23, // CYW43 power
34 p.PIN_24, // CYW43 clock
35 p.PIN_25, // CYW43 chip select
36 p.PIN_29, // CYW43 data
37 p.PIO0, // WiFi PIO
38 p.DMA_CH0, // WiFi DMA
39 wifi_flash,
40 p.PIN_13, // Button for reconfiguration
41 PressedTo::Ground,
42 "PicoAccess", // Captive-portal SSID
43 [], // Any extra fields
44 spawner,
45 )?;
46
47 let (_stack, _button) = wifi_auto.connect(|_event| async move { Ok(()) }).await?;
48
49 future::pending().await // run forever
50}More examples
28async fn inner_main(spawner: embassy_executor::Spawner) -> Result<Infallible> {
29 let p = embassy_rp::init(Default::default());
30
31 let [wifi_flash] = FlashArray::<1>::new(p.FLASH)?;
32
33 let wifi_auto = WifiAuto::new(
34 p.PIN_23, // CYW43 power
35 p.PIN_24, // CYW43 clock
36 p.PIN_25, // CYW43 chip select
37 p.PIN_29, // CYW43 data
38 p.PIO0, // WiFi PIO
39 p.DMA_CH0, // WiFi DMA
40 wifi_flash,
41 p.PIN_13, // Button for reconfiguration
42 PressedTo::Ground,
43 "PicoAccess", // Captive-portal SSID
44 [], // Any extra fields
45 spawner,
46 )?;
47
48 let (stack, _button) = wifi_auto
49 .connect(|event| async move {
50 match event {
51 WifiAutoEvent::CaptivePortalReady => {
52 defmt::info!("Captive portal ready");
53 }
54 WifiAutoEvent::Connecting { .. } => {
55 defmt::info!("Connecting to WiFi");
56 }
57 WifiAutoEvent::ConnectionFailed => {
58 defmt::info!("WiFi connection failed");
59 }
60 }
61 Ok(())
62 })
63 .await?;
64
65 // The stack is ready for network operations (for example, NTP or HTTP).
66 defmt::info!("WiFi connected");
67
68 loop {
69 if let Ok(addresses) = stack.dns_query("google.com", DnsQueryType::A).await {
70 defmt::info!("google.com: {:?}", addresses);
71 } else {
72 defmt::info!("google.com: lookup failed");
73 }
74 Timer::after(Duration::from_secs(15)).await;
75 }
76}40async fn inner_main(_spawner: Spawner) -> Result<()> {
41 info!("Flash Storage Example");
42
43 // Initialize hardware
44 let p = embassy_rp::init(Default::default());
45
46 // Initialize Flash device
47 let [_, _, _, mut string_block, mut config_block] = FlashArray::<5>::new(p.FLASH)?;
48
49 info!("Part 1: Storing data to flash");
50 string_block.save(&String::<64>::try_from("Hello, Flash Storage!")?)?;
51 config_block.save(&SensorConfig {
52 name: String::<32>::try_from("Temperature")?,
53 sample_rate_hz: 1000,
54 enabled: true,
55 })?;
56
57 info!("Part 2: Reading data from flash");
58 let string: Option<String<64>> = string_block.load()?;
59 assert!(string.as_deref() == Some("Hello, Flash Storage!"));
60 let config: Option<SensorConfig> = config_block.load()?;
61 assert!(
62 config
63 == Some(SensorConfig {
64 name: String::<32>::try_from("Temperature")?,
65 sample_rate_hz: 1000,
66 enabled: true,
67 })
68 );
69
70 info!("Part 3: Reading a different type counts as empty");
71 // Try to read the string block as a SensorConfig
72 let wrong_type_result: Option<SensorConfig> = string_block.load()?;
73 assert!(wrong_type_result.is_none());
74
75 info!("Part 4: Clearing flash blocks");
76 string_block.clear()?;
77 config_block.clear()?;
78
79 info!("Part 5: Verifying cleared blocks");
80 let string: Option<String<64>> = string_block.load()?;
81 assert!(string.is_none());
82 let config: Option<SensorConfig> = config_block.load()?;
83 assert!(config.is_none());
84
85 info!("Flash Storage Example Complete!");
86 loop {
87 embassy_time::Timer::after_secs(1).await;
88 }
89}26async fn inner_main(spawner: embassy_executor::Spawner) -> Result<core::convert::Infallible> {
27 let p = embassy_rp::init(Default::default());
28
29 let [wifi_flash, website_flash, timezone_flash] = FlashArray::<3>::new(p.FLASH)?;
30
31 static WEBSITE_STATIC: TextFieldStatic<32> = TextField::new_static();
32 let website_field = TextField::new(
33 &WEBSITE_STATIC,
34 website_flash,
35 "website",
36 "Website",
37 "google.com",
38 );
39
40 // Create timezone field
41 static TIMEZONE_STATIC: TimezoneFieldStatic = TimezoneField::new_static();
42 let timezone_field = TimezoneField::new(&TIMEZONE_STATIC, timezone_flash);
43
44 let wifi_auto = WifiAuto::new(
45 p.PIN_23, // CYW43 power
46 p.PIN_24, // CYW43 clock
47 p.PIN_25, // CYW43 chip select
48 p.PIN_29, // CYW43 data
49 p.PIO0, // WiFi PIO
50 p.DMA_CH0, // WiFi DMA
51 wifi_flash,
52 p.PIN_13, // Button for reconfiguration
53 PressedTo::Ground,
54 "PicoAccess", // Captive-portal SSID
55 [website_field, timezone_field], // Custom fields
56 spawner,
57 )?;
58
59 let (stack, _button) = wifi_auto
60 .connect(|event| async move {
61 match event {
62 WifiAutoEvent::CaptivePortalReady => {
63 defmt::info!("Captive portal ready");
64 }
65 WifiAutoEvent::Connecting {
66 try_index,
67 try_count,
68 } => {
69 defmt::info!(
70 "Connecting to WiFi (attempt {} of {})...",
71 try_index + 1,
72 try_count
73 );
74 }
75 WifiAutoEvent::ConnectionFailed => {
76 defmt::info!("WiFi connection failed");
77 }
78 }
79 Ok(())
80 })
81 .await?;
82
83 let website = website_field.text()?.unwrap_or_default();
84 let offset_minutes = timezone_field
85 .offset_minutes()?
86 .ok_or(Error::MissingCustomWifiAutoField)?;
87 defmt::info!("Timezone offset minutes: {}", offset_minutes);
88
89 loop {
90 let query_name = website.as_str();
91 if let Ok(addresses) = stack
92 .dns_query(query_name, embassy_net::dns::DnsQueryType::A)
93 .await
94 {
95 defmt::info!("{}: {:?}", query_name, addresses);
96 } else {
97 defmt::info!("{}: lookup failed", query_name);
98 }
99
100 embassy_time::Timer::after(embassy_time::Duration::from_secs(15)).await;
101 }
102}32async fn inner_main(spawner: Spawner) -> Result<Infallible> {
33 info!("Starting Console Clock with WiFi");
34
35 // Initialize RP2040 peripherals
36 let p = embassy_rp::init(Default::default());
37
38 // Use two blocks of flash storage: Wi-Fi credentials + timezone
39 let [wifi_credentials_flash_block, timezone_flash_block] = FlashArray::<2>::new(p.FLASH)?;
40
41 // Define timezone field for captive portal
42 static TIMEZONE_FIELD_STATIC: TimezoneFieldStatic = TimezoneField::new_static();
43 let timezone_field = TimezoneField::new(&TIMEZONE_FIELD_STATIC, timezone_flash_block);
44
45 // Set up WiFi via captive portal
46 let wifi_auto = WifiAuto::new(
47 p.PIN_23, // CYW43 power
48 p.PIN_24, // CYW43 clock
49 p.PIN_25, // CYW43 chip select
50 p.PIN_29, // CYW43 data pin
51 p.PIO0, // CYW43 PIO interface
52 p.DMA_CH0, // CYW43 DMA channel
53 wifi_credentials_flash_block,
54 p.PIN_13, // Reset button pin
55 PressedTo::Ground,
56 "www.picoclock.net",
57 [timezone_field],
58 spawner,
59 )?;
60
61 // Connect to WiFi
62 let (stack, _button) = wifi_auto
63 .connect(|event| async move {
64 match event {
65 WifiAutoEvent::CaptivePortalReady => {
66 info!("Captive portal ready - connect to WiFi network");
67 }
68 WifiAutoEvent::Connecting {
69 try_index,
70 try_count,
71 } => {
72 info!(
73 "Connecting to WiFi (attempt {} of {})...",
74 try_index + 1,
75 try_count
76 );
77 }
78 WifiAutoEvent::ConnectionFailed => {
79 info!("WiFi connection failed!");
80 }
81 }
82 Ok(())
83 })
84 .await?;
85
86 info!("WiFi connected successfully!");
87
88 // Create ClockSync device with timezone from WiFi portal
89 let timezone_offset_minutes = timezone_field
90 .offset_minutes()?
91 .ok_or(Error::MissingCustomWifiAutoField)?;
92 static CLOCK_SYNC_STATIC: ClockSyncStatic = ClockSync::new_static();
93 let clock_sync = ClockSync::new(
94 &CLOCK_SYNC_STATIC,
95 stack,
96 timezone_offset_minutes,
97 Some(ONE_SECOND),
98 spawner,
99 );
100
101 info!("WiFi connected, entering event loop");
102
103 // Main event loop - log time on every tick
104 loop {
105 let tick = clock_sync.wait_for_tick().await;
106 let time_info = tick.local_time;
107 info!(
108 "Current time: {:04}-{:02}-{:02} {:02}:{:02}:{:02}",
109 time_info.year(),
110 u8::from(time_info.month()),
111 time_info.day(),
112 time_info.hour(),
113 time_info.minute(),
114 time_info.second(),
115 );
116 }
117}34async fn inner_main(spawner: Spawner) -> Result<Infallible> {
35 info!("Starting LCD Clock with WiFi");
36
37 // Initialize RP2040 peripherals
38 let p = embassy_rp::init(Default::default());
39
40 // Initialize CharLcd
41 static CHAR_LCD_STATIC: CharLcdStatic = CharLcd::new_static();
42 let char_lcd = CharLcd::new(&CHAR_LCD_STATIC, p.I2C0, p.PIN_5, p.PIN_4, spawner)?;
43
44 // Use two blocks of flash storage: Wi-Fi credentials + timezone
45 let [wifi_credentials_flash_block, timezone_flash_block] = FlashArray::<2>::new(p.FLASH)?;
46
47 // Define timezone field for captive portal
48 static TIMEZONE_FIELD_STATIC: TimezoneFieldStatic = TimezoneField::new_static();
49 let timezone_field = TimezoneField::new(&TIMEZONE_FIELD_STATIC, timezone_flash_block);
50
51 // Set up WiFi via captive portal
52 let wifi_auto = WifiAuto::new(
53 p.PIN_23, // CYW43 power
54 p.PIN_24, // CYW43 clock
55 p.PIN_25, // CYW43 chip select
56 p.PIN_29, // CYW43 data pin
57 p.PIO0, // CYW43 PIO interface
58 p.DMA_CH0, // CYW43 DMA channel
59 wifi_credentials_flash_block,
60 p.PIN_13, // Reset button pin
61 PressedTo::Ground,
62 "www.picoclock.net",
63 [timezone_field],
64 spawner,
65 )?;
66
67 // Connect to WiFi
68 let (stack, _button) = wifi_auto.connect(|_event| async move { Ok(()) }).await?;
69
70 // Create ClockSync device with timezone from WiFi portal
71 let timezone_offset_minutes = timezone_field
72 .offset_minutes()?
73 .ok_or(Error::MissingCustomWifiAutoField)?;
74 static CLOCK_SYNC_STATIC: ClockSyncStatic = ClockSync::new_static();
75 let clock_sync = ClockSync::new(
76 &CLOCK_SYNC_STATIC,
77 stack,
78 timezone_offset_minutes,
79 Some(ONE_SECOND),
80 spawner,
81 );
82
83 info!("Entering main event loop");
84
85 // Main orchestrator loop - owns LCD and displays the clock
86 loop {
87 let tick = clock_sync.wait_for_tick().await;
88 let time_info = tick.local_time;
89 let mut text = String::<64>::new();
90 let (hour12, am_pm) = if time_info.hour() == 0 {
91 (12, "AM")
92 } else if time_info.hour() < 12 {
93 (time_info.hour(), "AM")
94 } else if time_info.hour() == 12 {
95 (12, "PM")
96 } else {
97 #[expect(clippy::arithmetic_side_effects, reason = "hour guaranteed 13-23")]
98 {
99 (time_info.hour() - 12, "PM")
100 }
101 };
102 fmt::Write::write_fmt(
103 &mut text,
104 format_args!(
105 "{:2}:{:02}:{:02} {}\n{:04}-{:02}-{:02}",
106 hour12,
107 time_info.minute(),
108 time_info.second(),
109 am_pm,
110 time_info.year(),
111 u8::from(time_info.month()),
112 time_info.day()
113 ),
114 )
115 .map_err(|_| Error::FormatError)?;
116 char_lcd.write_text(text, 0).await;
117 }
118}Auto Trait Implementations§
impl<const N: usize> Freeze for FlashArray<N>
impl<const N: usize> RefUnwindSafe for FlashArray<N>
impl<const N: usize> Send for FlashArray<N>
impl<const N: usize> Sync for FlashArray<N>
impl<const N: usize> Unpin for FlashArray<N>
impl<const N: usize> UnwindSafe for FlashArray<N>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CheckedAs for T
impl<T> CheckedAs for T
Source§fn checked_as<Dst>(self) -> Option<Dst>where
T: CheckedCast<Dst>,
fn checked_as<Dst>(self) -> Option<Dst>where
T: CheckedCast<Dst>,
Source§impl<Src, Dst> CheckedCastFrom<Src> for Dstwhere
Src: CheckedCast<Dst>,
impl<Src, Dst> CheckedCastFrom<Src> for Dstwhere
Src: CheckedCast<Dst>,
Source§fn checked_cast_from(src: Src) -> Option<Dst>
fn checked_cast_from(src: Src) -> Option<Dst>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more