New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reading/writing to flash #257
Comments
Same question crossed my mind recently. That is why I looked into using an SD card with my Raspberry Pi Pico board - for stuff I would usually put into the EEPROM of my Arduino. Storing stuff in flash has it's limited write cycles, but for occasionally storing user settings this seems fine (in contrast to logging data, which could also be implemented with some care). The main downside is of course, that every firmware update could potentially move the first unused byte offset of the flash and thus requires some more care for keeping settings across firmware updates. I would love some control and reserve the first 1MB of the flash for program data and the remaining space for storing data. |
As far a can see, you can use the I was planning on using a tiny board which has 8MB of flash so if I can restrict the size of the program to say 2MB it would leave plenty of room for data. I would prefer to restrict the number of components I use and I don't need that many write cycles so using the flash seems like the optimal solution. |
To read from flash, you can just The HAL currently doesn't have support for writing or erasing flash. You could look at the pico-sdk for inspiration, but I believe the steps would be:
An alternative to disabling all interrupts would be to re-enable during the (relatively expensive) erase cycle, by temporarily replacing the interrupt vector table with a copy in RAM where every vector:
This is assuming that QSPI flash chips can suspend erase operations - I haven't checked, but I know that parallel NOR flash chips can, and I've seen this approached used on other MCUs that have NOR flash (albeit only on a chip that had only two IRQ handlers). Yes, you would want to change memory.x to ensure that at least part of the flash chip is unaffected by programming your application, and is guaranteed to not contain program data. I guess in theory you could read/modify/write the currently running program, but that's probably not advisable. |
Here's a pico-sdk example written in C: https://github.com/raspberrypi/pico-examples/blob/master/flash/program/flash_program.c |
Oh, hey, there's even ROM funcs to do most of the work: https://github.com/raspberrypi/pico-sdk/blob/2062372d203b372849d573f252cf7c6dc2800c0a/src/rp2_common/hardware_flash/flash.c |
Yeah, they still have a function in RAM for coordinating the whole thing though. Also note it's pretty much impossible to do this safely while using both cores unless we have some way of ensuring that the second core is parked. Ditto for DMA accessing flash. |
Thanks, I have had a look at what you linked. If I understand correctly, I can use something like this unsafe {
hal::rom_data::flash_range_program(addr, data.as_ptr(), data.len());
} to write a That function should handle the XIP and cache flushing. I would still be responsible for disabling interrupts and not using two cores. |
That's not enough. See the datasheet, section 2.8.3.1.3. Flash Access Functions. You need to call more than one function, and: "Note that, in between the first and last calls in this sequence, the SSI is not in a state where it can handle XIP accesses, |
Thanks, I was getting confused thinking I was calling this function rather than the actual function in the ROM. So I would need something more like this; unsafe {
let connect_internal_flash = hal::rom_data::connect_internal_flash;
let flash_exit_xip = hal::rom_data::flash_exit_xip;
let flash_range_program = hal::rom_data::flash_range_program;
let flash_flush_cache = hal::rom_data::flash_flush_cache;
let flash_enter_cmd_xip = hal::rom_data::flash_enter_cmd_xip;
connect_internal_flash();
flash_exit_xip();
flash_range_program(addr, data.as_ptr(), data.len());
flash_flush_cache();
flash_enter_cmd_xip();
} The datasheet says that I should avoid calling |
Yes that's why there's multiple boot2 binaries. Their job is to enable high speed read mode and XIP. |
I spent a little bit of time reading how the pico-sdk handles the second core during flash writes. |
scratches head Wait, how does this work? I don't get it. |
Does https://raspberrypi.github.io/pico-sdk-doxygen/group__multicore__lockout.html help? |
Oh, ok. So Core B hooks the SIO FIFO interrupt with a ram func, and when Core A wants to enter a critical section it writes to the FIFO, which triggers an interrupt on Core B. The IRQ handler on Core B pops a reply in the FIFO and spins until it gets an "all clear" at which point the interrupt ends and Core B resumes what it was doing. The bit I was missing was that A can trigger and interrupt on B with a FIFO write. Got it! |
Also, that is totally going to knock out the video on a Neotron. Note to self - the screen will go blank during a self-update of the firmware! |
You could replace the lockout function on the second core with something providing video, as long as it runs from RAM. Instead of busy looping, waiting for the release message. |
Do we have a good mechanism for ensuring an entire call stack is in RAM, and not just the top function? |
Just as an FYI: I tried putting something together and it runs without hanging/panicking but it doesn't actually seem to write anything: pub const BLOCK_SIZE: u32 = 65536;
pub const SECTOR_SIZE: usize = 4096;
pub const PAGE_SIZE: u32 = 256;
pub const SECTOR_ERASE: u8 = 0x20;
pub const BLOCK32_ERASE: u8 = 0x52;
pub const BLOCK64_ERASE: u8 = 0xD8;
pub const FLASH_START: u32 = 0x1000_0000;
pub const FLASH_END: u32 = 0x1020_0000; // It's a 2MByte flash chip
#[inline(never)]
#[link_section = ".data.ram_func"]
fn write_flash() {
// Temp hard-coded locations for testing purposes:
let addr = FLASH_END - 4096;
let encoded: [u8; 4] = 22_u32.to_le_bytes(); // Just a test
let mut buf = [200; 4096];
buf[0] = encoded[0];
buf[1] = encoded[1];
buf[2] = encoded[2];
buf[3] = encoded[3];
unsafe {
cortex_m::interrupt::free(|_cs| {
rom_data::connect_internal_flash();
rom_data::flash_exit_xip();
rom_data::flash_range_erase(addr, SECTOR_SIZE, BLOCK_SIZE, SECTOR_ERASE);
rom_data::flash_range_program(addr, buf.as_ptr(), buf.len());
rom_data::flash_flush_cache(); // Get the XIP working again
rom_data::flash_enter_cmd_xip(); // Start XIP back up
});
}
defmt::println!("write_flash() Complete"); // TEMP
}
#[inline(never)]
#[link_section = ".data.ram_func"]
fn read_flash() -> &'static mut [u8] {
// Temp hard-coded locations for testing purposes:
let addr = (FLASH_END - 4096) as *mut u8;
let my_slice = unsafe { slice::from_raw_parts_mut(addr, 256) };
my_slice
} When I was fooling around I swear I got it to write stuff but that was back when it was crashing/hanging like crazy. Now I can't seem to get it to write any data at all. I even verified by dumping the entire flash to a file using |
Are you sure the |
Also, the |
Sorry, me again:
Is that right? I think you're telling ROM there's a special way to erase |
I've got it working! My problem was that when you use Anyway, here's the code that works: pub const BLOCK_SIZE: u32 = 65536;
pub const SECTOR_SIZE: usize = 4096;
pub const PAGE_SIZE: u32 = 256;
// These _ERASE commands are highly dependent on the flash chip you're using
pub const SECTOR_ERASE: u8 = 0x20; // Tested and works with W25Q16JV flash chip
pub const BLOCK32_ERASE: u8 = 0x52;
pub const BLOCK64_ERASE: u8 = 0xD8;
/* IMPORTANT NOTE ABOUT RP2040 FLASH SPACE ADDRESSES:
When you pass an `addr` to a `rp2040-hal::rom_data` function it wants
addresses that start at `0x0000_0000`. However, when you want to read
that data back using something like `slice::from_raw_parts()` you
need the address space to start at `0x1000_0000` (aka `FLASH_XIP_BASE`).
*/
pub const FLASH_XIP_BASE: u32 = 0x1000_0000;
pub const FLASH_START: u32 = 0x0000_0000;
pub const FLASH_END: u32 = 0x0020_0000;
pub const FLASH_USER_SIZE: u32 = 4096; // Amount dedicated to user prefs/stuff
#[inline(never)]
#[link_section = ".data.ram_func"]
fn write_flash(data: &[u8]) {
let addr = FLASH_END - FLASH_USER_SIZE;
unsafe {
cortex_m::interrupt::free(|_cs| {
rom_data::connect_internal_flash();
rom_data::flash_exit_xip();
rom_data::flash_range_erase(addr, SECTOR_SIZE, BLOCK_SIZE, SECTOR_ERASE);
rom_data::flash_range_program(addr, data.as_ptr(), data.len());
rom_data::flash_flush_cache(); // Get the XIP working again
rom_data::flash_enter_cmd_xip(); // Start XIP back up
});
}
defmt::println!("write_flash() Complete"); // TEMP
}
fn read_flash() -> &'static mut [u8] {
let addr = (FLASH_XIP_BASE + FLASH_END - FLASH_USER_SIZE) as *mut u8;
let my_slice = unsafe { slice::from_raw_parts_mut(addr, FLASH_USER_SIZE as usize) };
my_slice
} ...and here's the code I was using to test it out (I bound it to a keystroke on my numpad): let data = crate::read_flash();
defmt::println!("Flash data[0]: {:?}", data[0]);
defmt::println!("Incrementing data[0] by 1...");
let mut buf = [0; 256];
if data[0] == u8::MAX {
buf[0] = 0;
} else {
buf[0] = data[0] + 1;
}
crate::write_flash(&buf);
let data2 = crate::read_flash();
defmt::println!("Flash data[0]: {:?}", data2[0]); The output of which looks like this:
...and I confirmed that the data survives reboots/power cycle (so it wasn't just a trick of optimization). Speaking of optimization, I had a lot of trouble trying to get this to work until I specified [profile.release]
codegen-units = 1
debug = 2
debug-assertions = false
incremental = false
lto = 'fat' # <-- HERE
opt-level = 3
overflow-checks = false However, to be thorough I just tested all the
Note that you can put Other notes:
|
Well you have to pass something as the 3rd and 4th argument and that's what worked 🤷 . Don't assume I know what I'm doing haha. |
The comment in the bootrom source code explains those parameters:
|
Perhaps it would be possible to add abstrations based on https://github.com/rust-embedded-community/embedded-storage for this, to make it a bit easier for everyone to use? |
I am working on some functions which cover the 'needs to run from RAM' requirement: |
I think the issue is that all functions have to be executed from RAM, however, the HAL definitions for the rom_table_lookup can be compiled into flash (since there is no #[inline(always)] depending on your optimization settings (same holds for the rom_hword_as_ptr function). when I compile the following code snippet: #[inline(never)]
} wih lto-='off' causes hang since it the code in RAM is jumping to code in flash: 20000000 <__sdata>: 2000000c <__Thumbv6MABSLongThunk_rp2040_hal::rom_data::flash_enter_cmd_xip::he084f9a4ab71acef>: 20000014 <$d>: with lto='thin', no jump to FLASH memory 20000000 <__sdata>: 20000014 <$d.24>: So I think this has to be fixed in the HAL by adding the #[inline(never)] option |
I don't think That's why I implemented the relevant parts in assembly: https://github.com/jannic/rp2040-flash/blob/master/src/lib.rs#L189 |
@jannic On the higher-level API of your rp2040-flash crate (using it in an ongoing project; appreciate your work). I wrote a slightly more ergonomic contraption based on your original example and can prepare a pull request with either an example update or to include the suggested interface into the crate, if you find it suitable. Open to discuss improvements you may find necessary. |
Hi @werediver, |
Is it possible to use this hal to read/write to the flash storage on the pico?
I would like to use the flash space, which is not my program, to store and retrieve data.
The text was updated successfully, but these errors were encountered: