-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
External SPI/QSPI flash implementation with backup system for node configuration #9015
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
base: develop
Are you sure you want to change the base?
Conversation
… it without error(?)
…pecially with a phone connected (out of ram?)
…from/to the flash file stream. Avoid reading/writing the whole structure as a binary blob so that we don't copy large structures from flash to RAM which was causing RAM exhaustion
…ruitnrf52 AND updated adafruit/SdFat (which gives some new warnings during compiling) or use the original modified meshtastic nrf52 framework AND old sdfat library version ( fix override error: Either use both the updated framework-arduinoadafruitnrf52 AND updated adafruit/SdFat (which gives some new warnings during compiling) or use the original modified meshtastic nrf52 framework AND old sdfat library version (#@2.2.54). We might make our repository with updated framework and meshtastic edits? adafruit/Adafruit_nRF52_Arduino@master...geeksville:Adafruit_nRF52_Arduino:master
…xternal flash can decide to use it
…). TO DO: there are many files where we re-define flashtrasnport, fatfs and include the libraries. we should find where to do that only ONE time
…finitions are now in a single place
…implementation for loadFromDisk. if it works, commented code can be removed in future
idk why it was still there
… also shown on screen when restoring or updating the backup
- Changed function signatures for check_fat12 and format_fat12 to return bool. - Enhanced NodeDB.cpp with new internal backup functions for loading and saving protobuf data. - Implemented internal backup loading for device state, configuration, module configuration, and channel files. - Added support for saving preferences and configurations backups to internal flash. - Improved error handling and logging during backup and restore operations. - Updated AdminModule.cpp to handle backup preferences removal for both FSCom and external flash using manual CLI backup command. - Modified ff.c to include the renamed fatfs_ff.h instead of the old ff.h.
…e does not disappear from the speaker) (meshtastic#8884) * Fix meshtastic#8883 * Fix crash when delete not inicialized rtttlFile
…y) (meshtastic#8888) * Resolve meshtastic#8887 * Update src/modules/ExternalNotificationModule.cpp Co-authored-by: Copilot <[email protected]> * Update src/modules/ExternalNotificationModule.cpp Co-authored-by: Copilot <[email protected]> * use canBuzz method * trunk fmt --------- Co-authored-by: Copilot <[email protected]>
…stic#8379) * Add mesh/Default.h include. * Reflacter OnScreenKeyBoard Module, do not interrupt keyboard when new message comes. * feat: Add long press scrolling for Joystick and upDown Encoder on baseUI frames and menus. * refactor: Clean up code formatting and improve readability in Screen and OnScreenKeyboardModule * Fix navigation on UpDownEncoder, default was RotaryEncoder while bringing the T_LORA_PAGER * Update src/graphics/draw/MenuHandler.cpp Co-authored-by: Copilot <[email protected]> * Update src/modules/OnScreenKeyboardModule.h Co-authored-by: Copilot <[email protected]> * Update src/graphics/draw/NotificationRenderer.cpp Co-authored-by: Copilot <[email protected]> * Optimize the detection logic for repeated events of the arrow keys. * Fixed parameter names in the OnScreenKeyboardModule::start * Trunk fix * Reflator OnScreenKeyboard Input checking, make it simple * Simplify long press logic in OnScreenKeyboardModule. --------- Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements comprehensive external SPI/QSPI flash support for nRF52 architectures, enabling devices like the MeshLink to use larger external flash chips as primary storage for device preferences and the NodeDB. The implementation includes a critical configuration mirroring system that backs up essential settings to internal flash for automatic recovery if external storage fails or is formatted.
Key Changes:
- External flash integration using Adafruit_SPIFlash and FatFS filesystem for nRF52 devices
- Critical config mirroring mechanism that backs up config, moduleConfig, channels, and deviceState to internal flash
- Atomic file operations support for external filesystem with SafeFile updates
- Storage statistics display on device screens showing both external and internal flash usage
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 20 comments.
Show a summary per file
| File | Description |
|---|---|
| variants/nrf52840/meshlink/variant.h | Added LED pin usage comments; commented USE_EXTERNAL_FLASH define |
| variants/nrf52840/meshlink/variant.cpp | Minor formatting cleanup (empty lines) |
| variants/nrf52840/meshlink/platformio.ini | Added USE_EXTERNAL_FLASH build flag and Adafruit SPIFlash/SdFat library dependencies |
| src/modules/Telemetry/Sensor/NAU7802Sensor.cpp | Updated calibration file loading to support external flash |
| src/modules/AdminModule.cpp | Updated backup removal to support external flash filesystem |
| src/mesh/mesh-pb-constants.h | Added commented-out MAX_NUM_NODES increase for external flash |
| src/mesh/mesh-pb-constants.cpp | Updated protobuf stream callbacks to support both File and File32 types for external flash |
| src/mesh/ffconf.h | New FatFS configuration file with minimized features (FF_FS_MINIMIZE=3) |
| src/mesh/fatfs_ff.h | FatFS header file for filesystem operations |
| src/mesh/diskio.h | Low-level disk I/O interface header for FatFS |
| src/mesh/NodeDB.h | Changed loadFromDisk return type from void to int |
| src/mesh/NodeDB.cpp | Core implementation: internal flash backup/restore functions, disk I/O callbacks, updated load/save logic with fallback support |
| src/graphics/draw/DebugRenderer.cpp | Added storage statistics display for external and internal flash on nRF52 |
| src/SafeFile.h | Extended preprocessor guard to include USE_EXTERNAL_FLASH |
| src/SafeFile.cpp | Added external flash implementation of SafeFile with atomic writes |
| src/FSCommon.h | Added external flash types and function declarations |
| src/FSCommon.cpp | Implemented external flash filesystem operations (format, check, copy, rename, list, delete) |
| .vscode/settings.json | Added fstream C++ file association |
Comments suppressed due to low confidence (1)
src/modules/Telemetry/Sensor/NAU7802Sensor.cpp:131
- The loadCalibrationData function uses the readcb callback which expects a File pointer for FSCom but a File32 pointer for external flash. However, the pb_istream_t stream is initialized with meshtastic_Nau7802Config_size as the bytes_left parameter. This should be the actual file size, not the maximum protobuf size, to avoid potential issues with reading past the end of the file. This inconsistency with how loadProto handles file sizes could cause decode errors.
#ifdef USE_EXTERNAL_FLASH
auto file = fatfs.open(nau7802ConfigFileName, FILE_READ);
#else
auto file = FSCom.open(nau7802ConfigFileName, FILE_O_READ);
#endif
bool okay = false;
if (file) {
LOG_INFO("%s state read from %s", sensorName, nau7802ConfigFileName);
pb_istream_t stream = {&readcb, &file, meshtastic_Nau7802Config_size};
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ed define) Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
|
I would like to suggest that FSCommon and new files from this PR be moved to separate directory like |
Good idea |
…sions of such files with the new locations
|
@phaseloop how does it look now? |
…kup buffers in both backupPreferences and restorePreferences. We now grab rawBackup = backup.get(), validate it, and only then memset/populate/use it, preventing undefined behavior on allocation failure
FatFS actually supports file renaming, so we can use the correct safefile logic.
…R and returns false; getFiles and listDir treat any name/path truncation or buildPath failure as fatal—logging an error and returning immediately. Aligned the external-flash stack buffers to the protobuf field size so paths/names match meshtastic_FileInfo::file_name. This keeps our path buffers consistent with the serialized field.
… when the external flash isn’t initialized: status/initialize now return STA_NOINIT when flashInitialized is false, and read/write/ioctl return RES_NOTRDY if flash isn’t ready.
…og an error when a path would overflow, avoiding silent failure. Added an error log when creating the internal backup directory fails, while still returning the creation result.
…early: percent and fill width now directly divide by total. Added a retry with better diagnostics before deleting internal backups: loadProtoFromInternal now logs attempt count and file size, retries decode once, and only removes the mirror after repeated failure.
|
Should be good now, let me know if i missed something |
…t only offers read(void*, size_t) returning the number of bytes read. Switching readMessageRecord to read lets the same templated helper work for both FatFS (File32) and FSCom File objects. -readBytes (Arduino Stream API) is a convenience wrapper that fills a char*, blocks until length or timeout, and returns how many bytes were read. read on both FatFS and LittleFS returns immediately with the count actually read and works with uint8_t*. Functionally we only care that the full record length is returned; otherwise we abort. -MessageStore.cpp:252-310 We left the FSCom header byte read using readBytes (one byte) but the per-record read now uses read via the template so both backends share the same record path. -added a couple log warnings - More external flash implementation in other areas
|
@HarukiToreda i had to modify messagestore to support external flash. Let me know if the edits are ok for you. |
- Replaced dangerous ring buffer wrapping with safer failure mode in storeTextInPool() - Added null pointer checks in getTextFromPool() and storeTextInPool() - Added bounds checking to prevent accessing invalid memory offsets - Enhanced writeMessageRecord() to handle empty/invalid text gracefully - Added warnings for stale offsets that may have been invalidated **SafeFile Improvements:** - Added logging for openfile non fullatomic operations - Improved error reporting when openfile operations fail **Debug/Logging:** - Added informative log messages in saveToFlash() for debugging - Moved "MessageStore loaded from flash" log from Screen.cpp to MessageStore.cpp where it belongs, otherwise it would report success even when it actually failed
|
This is a lot to review in one patch. Would you be able to move some changes into another PR? Eg the move to the filesystem subdirectory |
Yes I can try that 👌🏻 |
|
@ponzano I missed your comment - I'll take a look soon |
|
Would this feature be compatible with add-on storage modules from RAK, such as the RAK15001? None of the available RAK storage modules specify they are QSPI, but the RAK15001 does mention it uses SPI (not ideal, requires too many GPIOs). Or how about this RAK EEPROM module, that uses I2C (even better) instead? Could your code be extended to support this? |
|
I am not in favor of using FatFS on any flash based storage. Would it be feasible to use LittleFS here since it accounts for wear levelling out of the box? |
It would, we just need to slightly modify the #define that specifies qspi to spi, probably to be done in variant.h file for each board |
Uh I didn't know that, I'll take a look and see if and how to use that instead |
🤝 Attestations
This pull request implements comprehensive support for using external SPI/QSPI flash as the primary storage medium for device preferences and the Node Database (NodeDB), specifically targeting nRF52 architectures.
The key motivation behind these changes is to leverage the larger capacity of external flash chips found on boards like the MeshLink. This could allow for a significantly larger NodeDB (not increased by default) and frees the space taken up by NodeDB from the limited internal flash. To ensure reliability, it introduces a "Critical Config Mirroring" mechanism that keeps a mirror of essential device settings to the internal filesystem, allowing for automatic recovery if the external storage fails or is formatted.
This also allows for a seamless update and downgrade with firmware versions without external flash enabled.
Key Changes
External Flash Integration (USE_EXTERNAL_FLASH)
Primary Storage Switch: When USE_EXTERNAL_FLASH is defined, the system now initializes and mounts the external flash using Adafruit_SPIFlash and a FatFs filesystem (FatVolume).
Updated FSCommon.cpp and FSCommon.h to reuse file operations already present (getFiles, listDir, rmDir, copyFile). The logic selects between the internal filesystem (FSCom/LittleFS) and the new external FatFS instance based on the build configuration.
Formatting & Integrity: Added format_fat12() and check_fat12() to handle the initialization and formatting of the external flash partition if it is corrupt or uninitialized and to verify it can be mounted correctly.
Critical Configuration Mirroring (nRF52)
Backup Strategy: A mirroring mechanism has been added to NodeDB.cpp. When saving critical protobuf configurations (config, moduleConfig, channels, deviceState) to the external flash, a copy is also saved to the internal flash.
Automatic Restoration: During boot (loadFromDisk), if the primary external storage fails to load these critical files (e.g., due to corruption or a fresh flash chip), the system automatically attempts to restore them from the internal mirror.
Safe Recovery: This ensures that even if the external filesystem is wiped (factory reset or filesystem corruption), the device retains its identity, private keys, and LoRa channel settings.
Seamless update: Since the backup system simply makes an exact copy, the structure is the same it was used before external flash integration. On the first boot after updating, it will fail to mount and load from external flash (because it's still new) and it will instead load the configurations from internal flash and then save it to external and begin using that instead. It will also delete the nodeDB from internal flash to free up space since it won't be used anymore (only saved to external).
FatFs Implementation
Embedded Source: Included the FatFs source code (src/mesh/ff.c, src/mesh/fatfs_ff.h, src/mesh/diskio.h) directly in the source tree. This ensures a consistent filesystem implementation across platforms where the SDK might not provide it, while conditionally excluding it for ESP32 platforms (which already have their own implementation of FatFS).
Debugging & Diagnostics
Storage Stats: Updated DebugRenderer.cpp to display usage statistics for the External Flash (If present) and Internal Flash on the device screen for NRF52 devices.
Added logic to calculate and display Internal Flash usage alongside the External Flash stats, helping developers visualize the storage distribution between firmware, internal mirroring, and external data.
Atomic File Operations
SafeFile Updates: Updated SafeFile.cpp to support atomic writes on the external filesystem by writing to a temporary file and renaming it upon successful completion, mimicking the behavior previously available only on the internal FS.
Benefits
Starting point for other developers and contributors to leverage a larger storage also on NRF52 devices where it's always been limited.
Provides a fallback for critical device configuration, which adds resilience especially important for nodes installed in remote areas.
It has been tested on MeshLink boards, a RAK4631 and a LilyGo T3S3
It has been tested with NodeDB limited to 250 instead of 80 with no issues (even with nodedb full), but due to the increase in heap usage i left it commented, to be discussed together if it's something we actually need to increase.
Let me know if something needs to be modified!
This PR has been part of the work of the MeshLink Team at Politecnico di Torino University
Agnese Ponzano