Allow flash programming of partial pages (<256 bytes) #1169
Labels
No labels
bazel
bluetooth
board settings
boot_stage2
bug
build
c++
can't reproduce
clang
clib-interface
cmsis
code cleanliness
code size
compiler version issues
cyw43_driver
debug
discussion
documentation
duplicate
dynamic_clock
elf2uf2
enhancement
errata
filesystem
flash layout
floating point
FreeRTOS
hardware_adc
hardware_claim
hardware_clocks
hardware_dma
hardware_exception
hardware_flash
hardware_gpio
hardware_i2c
hardware_irq
hardware_pio
hardware_pwm
hardware_rcp
hardware_rtc
hardware_spi
hardware_sync
hardware_uart
hardware_vreg
hardware_watchdog
hardware_xosc
help wanted
host
IAR
installation
invalid
linker scripts
lto
lwip
mbedtls
missing API
needs reporter detail
networking
newlib
newlib-multicore
non-c
non-cmake
non-gcc
openocd
orphan PR
OS integration
pico_async_context
pico_base
pico_binary_info
pico_flash
picolibc
pico_lwip
pico_malloc
pico_multicore
pico_printf
pico_runtime
pico_stdio
pico_stdio_usb
pico_sync
pico_time
pico_util
pioasm
port
question
review
review2
rtos interop
runtime init
static library
svd
test
tinyusb
vscode
wifi integration
wontfix
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-raspberrypi/pico-sdk#1169
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The current
hardware_flash/flash.cimplementation validates parameters toflash_range_program(uint32_t flash_offs, const uint8_t *data, size_t count)ensuring that 'flash_offs' is on a page boundary (% 256 == 0) as well as bytecountis a multiple of a full page (256), which is also documented in the API documentation:Implementation: /src/rp2_common/hardware_flash/flash.c:
When implementing EEPROM emulation or a File System backed by the QSPI (NOR) Flash (above the XIP address range), it severely restricts the potential speed and increases SRAM requirements, as every writes to Flash will require a read-modify-write cycle:
flash_range_program()The SPI connected Serial Flash IC used with the RP2040, does not have ANY restrictions regarding starting address or number of bytes written, when using the
Page Program (02h)instruction (see Winbond W25Q16JV datasheet, section 9.2.13 for details):Would it be possible to add a facility (library function) allowing partial page programming at arbitrary addresses, using an API similar to the
flash_range_program(uint32_t flash_offs, const uint8_t *data, size_t count)function?This function may likely restrict all data to be written to be located in ONE 256 byte page, and not allow wrapping (to the beginning of the page, which is the standard behavior of the underlying Serial Flash.
Also want to support this meaning. Such an approach will also reduce the lifetime of the flash itself.
@taraskornuta
Allowing partial page writes will not, in any way, reduce the lifetime of the flash. It is page erasures that wear out flash, not page writes (which should obviously not be overlapping). For an effective EEPROM simulation in Flash, allowing partial page writes will reduce the number of flash erase operations required, and thus reduce the wear of the flash, increasing the lifetime of it.
I guess adding such an API / function would introduce the possibility of trying to overwrite some already-written data (i.e. not freshly-erased space), and thus introduces the possibility that what you thought you'd written into flash, won't be the same as what you actually read back from flash? 🤔
I know it's probably not the easiest solution, but instead of trying to write partial-pages <256 bytes, how hard would it be to modify your application to buffer write-data in RAM until you have a full page-worth to be written to flash?
@lurch it wouldn't be at all difficult to buffer 256 bytes of data in RAM before writing to Flash, but it isn't always appropriate to do so:
There are many EEPROM emulation (in Flash) library designs with wear leveling and garbage collection, using partial page writes (and rewrites zeroing flag bits), and I would appreciate if the Pico SDK would not prevent me from implementing such a library.
I fail to see any good reason for this arbitrary restriction in the Pico SDK, but would be happy to gain some insight into the reason – and protecting programmers from their own stupidity isn't a good reason. A partial page program method would likely only allow writing to bytes within ONE page, with no wrapping.
Sounds reasonable to me to add an API to do this. It could be made optional returning an error if the flash didn't support partial page writes (is that even a thing?).
@peterharperuk i have looked at a LOT of serial Nor flash datasheets, including those used by Adafruit, Pimoroni, seed, sparkfun and waveshare (many of which uses slow generic SPI access at half speed, compared to the "original" Winbond W25QxxJV), and they all seem to support partial page writes. They may differ in "wrap-at-end-of-page" behavior, but it would be reasonable for a partial page programming function to restrict writes to a single page without exceeding the end, typically causing wrap to beginning of (256 byte) page anyway.
The existing
flash_range_programfunction could easily be extended to also supportflash_offsoutside page boundaries ifcountis less than 256, raising an error ifcountbytes will not fit within the page pointed to byflash_offs.github.com/raspberrypi/pico-sdk@2062372d20/src/rp2_common/hardware_flash/flash.c (L86-L106)... but since the implementation in flash.c calls on similarly restricted
flash_page_program()methods in /bootrom/program_flash_generic.c, the only way forward is to reimplement a refactored version of theflash_page_programmethods from allowingcount < 256 bytesat arbitrary (byte aligned) ´flash_offs' addresses will be to refactor flash_page_program methods from /bootrom/program_flash_generic.c and place them in RAM using__no_inline_not_in_flash_func' like theirpico-sdk:/src/rp2_common/hardware_flash/flash.c` brethren.I may give it a try, implementing an updated version supporting partial page programming with a few of code samples making byte-sized updates, or even bit-sized updates (flipping flash bits individually from 1 to zero), resulting in as many as 256*8 calls to
flash_range_program(uint32_t flash_offs, const uint8_t *data, size_t count)for each page in a 4KB sector (a grand total of 32,768 calls toflash_range_programon that sector), before erasing the sector and starting over flipping individual 1 bits to zero bits. Doing that 100 times will reveal whether the Flash sector has wear equalling 100 erasures, or if the 3,276,800flash_range_programhas caused additional wear to the sector, rendering it unusable.Any update on this issue?
Just in case someone wants to know. I was able to get around this issue and write the exact size I want to flash by writing a custom function that prepares the data for 256 byte write.
I am writing structs to flash by mapping the data manually from structs to flash addresses (I'd rather control the mapping of the struct to flash myself instead of relying on compiler).
Things go roughly like this (sorry, my project is not even near the point of making it public but this part seems to work when I tested ~15 minutes rewriting values etc):
2.1 write 0xFF everywhere to the buff. Just to ensure there are no unnecessary bit flips by mistake
2.2 if the values being written next are not located at the beginning of the page, read existing values from the flash and copy to the start of the buff
2.3 if I am updating something that has old entries, flip single bit from the previous valid struct from 1 -> 0 to make it outdated (and ok to be deleted later on)
2.4 write the new values next after existing data to the buff
(2.5 rest of the buff stays 0xFF)
Seems to work pretty well and I am practically able to flip any single bit from the array. In my code I am grouping structs with identical id always within same page to ensure I can flip all old value bits to 0 during the same write and without chasing around the whole region I have reserved for those values.
I've also planned the structs accordingly. The ones being written most are the smallest by design.
Hope you are able to understand what I am trying to explain here.