Search code examples
rustembeddedraspberry-pi-picomemory-mapping

RPI pico write to flash programatically


I am trying to write a program for the pico in rust, that needs to save data between possible shutdowns. It would fit into the flash with the program and the flash is "according to the internet" writable.

I have copied some embassy pico template repo and changed memory.x to contain a section .log that is 1M in size.

MEMORY {
    BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
    FLASH : ORIGIN = 0x10000100, LENGTH = 1M - 0x100
    LOG   : ORIGIN = 0x10100000, LENGTH = 1M
    RAM   : ORIGIN = 0x20000000, LENGTH = 256K
}

EXTERN(BOOT2_FIRMWARE)

SECTIONS {
    /* ### Boot loader */
    .boot2 ORIGIN(BOOT2) : {
        KEEP(*(.boot2));
    } > BOOT2
    /* Data log */
    .log : {
        *(.log);
       . = ALIGN(4);
    } > LOG
} INSERT BEFORE .text;

Then in my code I have

#[link_section = ".log"]
static mut LOG: MaybeUninit<[u8; 1 << 20]> = MaybeUninit::uninit();

fn foo() {
    let log = unsafe { LOG.assume_init_mut() };
}

But I can see from the behavior and debugging that assigning to the array does nothing. The section is initially zeroed out and stays like that even after assignment.

I have verified this with a simple test function, that checks if the memory is writable.

fn test_writable(log: &mut [u8]) -> bool {
    let prev = log[0];
    let masked = prev ^ 0xb5;
    log[0] = masked;
    if log[0] == masked {
        log[0] = prev;
        true
    } else {
        if log[0] == prev {
            error!("Log is not writable");
        } else {
            error!("Log writing is incorrect. Prev: {}, expected: {}, current: {}", prev, masked, log[0]);
        }
        false
    }
}

Am I using MaybeUninit wrong, is the memory map wrong or something else?


Solution

  • The RPIPico does memory-map its flash memory to "normal" addresses for reading, so you can simply read from flash addresses. For writing, that flash is not directly addressable - writing directly to flash addresses doesn't achieve anything useful.

    Note the below is valid for the C API functions - you'll need to find the proper Rust equivalents in your SDK.

    You need to use the API functions

    flash_range_erase (offset, size)
    flash_range_write (offset, buffer, size)
    

    to first erase a certain block in flash, then write it.

    To complicate matters a bit, apparently both functions will only work properly with interrupts disabled. Also, you need to make sure you don't erase/write to a flash area that holds your program.

    Flash on the RPi 2040 must be handled in blocks of 4k - That means, you must make sure you're writing to the next block to the next address "behind" your program in flash that can be evenly divided by 4096. Your last program address is conveniently provided by the libraries in the flash_binary_end variable (which is an absolute address, while the two above functions want offsets relatively to the start of the flash memory, which can be found by the constant XIP_BASE).

    So, in order to write a block of data at blockof length 4096 to flash memory behind your program code, you can conceptually do the following:

    void *flashAddress = (flash_binary_end + 4096) & 0xfffff000;
    int flashOffset = flashAddress - XIP_BASE;
    uint32_t ints = save_and_disable_interrupts();
    flash_range_erase (flashOffset, 4096);
    flash_range_write (flashOffset, block, 4096)
    restore_interrupts (ints);