diff --git a/libraries/QSPI/CMakeLists.txt b/libraries/QSPI/CMakeLists.txt new file mode 100644 index 000000000..9ac9b0e95 --- /dev/null +++ b/libraries/QSPI/CMakeLists.txt @@ -0,0 +1,5 @@ +zephyr_library() + +zephyr_library_sources(QSPI.cpp) + +zephyr_library_include_directories(.) \ No newline at end of file diff --git a/libraries/QSPI/QSPI.cpp b/libraries/QSPI/QSPI.cpp new file mode 100644 index 000000000..50a4d9aaf --- /dev/null +++ b/libraries/QSPI/QSPI.cpp @@ -0,0 +1,132 @@ +#include "QSPI.h" + +// Define the QSPI flash device - will be available when overlay is active +#if DT_NODE_EXISTS(DT_NODELABEL(qspi_flash)) +#define QSPI_FLASH_NODE DT_NODELABEL(qspi_flash) +#define QSPI_FLASH_DEVICE DEVICE_DT_GET(QSPI_FLASH_NODE) +#else +#define QSPI_FLASH_DEVICE NULL +#warning "No QSPI flash available on this board" +#endif + +QSPIClass::QSPIClass() : flash_dev(nullptr), initialized(false) { +} + +bool QSPIClass::begin() { + if (QSPI_FLASH_DEVICE == NULL) { + return false; + } + + flash_dev = QSPI_FLASH_DEVICE; + + if (!device_is_ready(flash_dev)) { + flash_dev = nullptr; + return false; + } + + initialized = true; + return true; +} + +bool QSPIClass::read(uint32_t address, void* data, size_t size) { + if (!initialized || !flash_dev) { + return false; + } + + int ret = flash_read(flash_dev, address, data, size); + return (ret == 0); +} + +bool QSPIClass::write(uint32_t address, const void* data, size_t size) { + if (!initialized || !flash_dev) { + return false; + } + + int ret = flash_write(flash_dev, address, data, size); + return (ret == 0); +} + +bool QSPIClass::erase(uint32_t address, size_t size) { + if (!initialized || !flash_dev) { + return false; + } + + int ret = flash_erase(flash_dev, address, size); + return (ret == 0); +} + +size_t QSPIClass::getFlashSize() { + if (!initialized || !flash_dev) { + return 0; + } + + uint64_t size = 0; + int ret = flash_get_size(flash_dev, &size); + if (ret != 0) { + return 0; + } + + return (size_t)size; +} + +size_t QSPIClass::getSectorSize() { + if (!initialized || !flash_dev) { + return 0; + } + + struct flash_pages_info page_info; + int ret = flash_get_page_info_by_offs(flash_dev, 0, &page_info); + if (ret != 0) { + return 0; + } + + return page_info.size; +} + +size_t QSPIClass::getPageSize() { + if (!initialized || !flash_dev) { + return 0; + } + + const struct flash_parameters *flash_params = flash_get_parameters(flash_dev); + if (!flash_params) { + return 0; + } + + return flash_params->write_block_size; +} + +bool QSPIClass::isReady() { + if (!flash_dev) { + return false; + } + + return device_is_ready(flash_dev); +} + +uint32_t QSPIClass::getFlashID() { + // This would require implementing JEDEC ID reading + // For now, return 0 as placeholder + return 0; +} + +bool QSPIClass::isValidAddress(uint32_t address, size_t size) { + if (!initialized || !flash_dev) { + return false; + } + + size_t flash_size = getFlashSize(); + return (address + size <= flash_size); +} + +const struct device* QSPIClass::getDevice() { + return flash_dev; +} + +void QSPIClass::end() { + flash_dev = nullptr; + initialized = false; +} + +// Create global instance +QSPIClass QSPI; \ No newline at end of file diff --git a/libraries/QSPI/QSPI.h b/libraries/QSPI/QSPI.h new file mode 100644 index 000000000..91b4834b7 --- /dev/null +++ b/libraries/QSPI/QSPI.h @@ -0,0 +1,54 @@ +#ifndef QSPI_H +#define QSPI_H + +#include +#include +#include +#include +#include + +class QSPIClass { + +public: + QSPIClass(); + + // Initialize QSPI flash + bool begin(); + + // Read data from QSPI flash + bool read(uint32_t address, void* data, size_t size); + + // Write data to QSPI flash + bool write(uint32_t address, const void* data, size_t size); + + // Erase sector/block + bool erase(uint32_t address, size_t size); + + // Get flash information + size_t getFlashSize(); + size_t getSectorSize(); + size_t getPageSize(); + + // Check if flash is ready + bool isReady(); + + // Get flash ID + uint32_t getFlashID(); + + // Utility functions + bool isValidAddress(uint32_t address, size_t size = 1); + + // Get the underlying Zephyr device (for filesystem/advanced usage) + const struct device* getDevice(); + + // End/deinitialize + void end(); + +private: + const struct device *flash_dev; + bool initialized; +}; + +extern QSPIClass QSPI; + +#endif \ No newline at end of file diff --git a/libraries/QSPI/examples/BasicQSPI.ino b/libraries/QSPI/examples/BasicQSPI.ino new file mode 100644 index 000000000..9033c8022 --- /dev/null +++ b/libraries/QSPI/examples/BasicQSPI.ino @@ -0,0 +1,101 @@ +/* + Basic QSPI Flash Example + + This example demonstrates how to use the QSPI library to read and write + data to external QSPI flash memory on Arduino boards with QSPI support. + + Note: QSPI flash must be configured in the board's device tree overlay. +*/ + +#include + +void setup() { + Serial.begin(115200); + while (!Serial) { + delay(10); + } + + Serial.println("QSPI Flash Test"); + + // Initialize QSPI flash + if (!QSPI.begin()) { + Serial.println("Failed to initialize QSPI flash!"); + while (1) { + delay(1000); + } + } + + Serial.println("QSPI flash initialized successfully"); + + // Get flash information + Serial.print("Flash size: "); + Serial.print(QSPI.getFlashSize()); + Serial.println(" bytes"); + + Serial.print("Sector size: "); + Serial.print(QSPI.getSectorSize()); + Serial.println(" bytes"); + + Serial.print("Page size: "); + Serial.print(QSPI.getPageSize()); + Serial.println(" bytes"); + + // Test write and read + testWriteRead(); +} + +void loop() { + // Nothing to do in loop + delay(5000); + + Serial.println("Running periodic test..."); + testWriteRead(); +} + +void testWriteRead() { + const uint32_t test_address = 0x1000; // Test address (4KB offset) + const char test_data[] = "Hello QSPI Flash!"; + char read_buffer[32]; + + Serial.println("\n--- Testing Write/Read ---"); + + // Erase sector first + Serial.print("Erasing sector at 0x"); + Serial.print(test_address, HEX); + Serial.print("... "); + + if (QSPI.erase(test_address, QSPI.getSectorSize())) { + Serial.println("OK"); + } else { + Serial.println("FAILED"); + return; + } + + // Write test data + Serial.print("Writing data... "); + if (QSPI.write(test_address, test_data, strlen(test_data) + 1)) { + Serial.println("OK"); + } else { + Serial.println("FAILED"); + return; + } + + // Read back data + Serial.print("Reading data... "); + memset(read_buffer, 0, sizeof(read_buffer)); + if (QSPI.read(test_address, read_buffer, sizeof(read_buffer))) { + Serial.println("OK"); + + Serial.print("Read data: "); + Serial.println(read_buffer); + + // Verify data + if (strcmp(test_data, read_buffer) == 0) { + Serial.println("Data verification: PASSED"); + } else { + Serial.println("Data verification: FAILED"); + } + } else { + Serial.println("FAILED"); + } +} \ No newline at end of file diff --git a/libraries/QSPI/examples/QSPIFilesystem.ino b/libraries/QSPI/examples/QSPIFilesystem.ino new file mode 100644 index 000000000..8c692d14a --- /dev/null +++ b/libraries/QSPI/examples/QSPIFilesystem.ino @@ -0,0 +1,287 @@ +/* + QSPI Filesystem Example with LittleFS + + This example demonstrates how to use the QSPI library with Zephyr's LittleFS + filesystem to store and retrieve files on external QSPI flash memory. + + Features: + - Mount LittleFS on QSPI flash + - Create, write, and read files + - List directory contents + - Check filesystem statistics + + IMPORTANT CONFIGURATION REQUIRED: + =================================== + This example requires LittleFS support to be enabled in your Zephyr build. + + 1. Add to your board's prj.conf file: + CONFIG_FILE_SYSTEM=y + CONFIG_FILE_SYSTEM_LITTLEFS=y + CONFIG_FILE_SYSTEM_MAX_FILE_NAME=128 + + 2. If using a custom board, ensure your device tree has flash partitions defined. + + 3. Alternative: If you don't want to configure LittleFS, use the QSPISimpleFS.ino + example instead, which implements a simple filesystem without dependencies. + + Note: + - QSPI flash must be configured in the board's device tree overlay + - Build will fail if LittleFS is not enabled (missing lfs.h header) +*/ + +// Check if filesystem support is available +#ifndef CONFIG_FILE_SYSTEM +#error "This example requires CONFIG_FILE_SYSTEM=y in prj.conf" +#endif + +#ifndef CONFIG_FILE_SYSTEM_LITTLEFS +#error "This example requires CONFIG_FILE_SYSTEM_LITTLEFS=y in prj.conf. Use QSPISimpleFS.ino if you don't want to enable LittleFS." +#endif + +#include +#include +#include +#include + +// Mount point for the filesystem +#define MOUNT_POINT "/qspi" + +// LittleFS configuration +FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(storage); +static struct fs_mount_t mp = { + .type = FS_LITTLEFS, + .fs_data = &storage, + .mnt_point = MOUNT_POINT, +}; + +void setup() { + Serial.begin(115200); + while (!Serial) { + delay(10); + } + + Serial.println("QSPI Filesystem Example"); + Serial.println("========================\n"); + + // Initialize QSPI flash + if (!QSPI.begin()) { + Serial.println("Failed to initialize QSPI flash!"); + while (1) { + delay(1000); + } + } + + Serial.println("QSPI flash initialized successfully"); + Serial.print("Flash size: "); + Serial.print(QSPI.getFlashSize() / 1024); + Serial.println(" KB\n"); + + // Mount the filesystem + Serial.print("Mounting LittleFS at " MOUNT_POINT "... "); + int ret = fs_mount(&mp); + + if (ret == 0) { + Serial.println("OK"); + } else if (ret == -EBUSY) { + Serial.println("Already mounted"); + } else { + Serial.print("FAILED ("); + Serial.print(ret); + Serial.println(")"); + Serial.println("Note: First mount may fail - filesystem might need formatting"); + Serial.println("Try erasing the flash first or format the filesystem"); + while (1) { + delay(1000); + } + } + + // Show filesystem statistics + printFilesystemStats(); + + // Test filesystem operations + testFileOperations(); + + // List files + listFiles(); +} + +void loop() { + // Nothing to do in loop + delay(1000); +} + +void printFilesystemStats() { + struct fs_statvfs stats; + int ret = fs_statvfs(MOUNT_POINT, &stats); + + if (ret == 0) { + Serial.println("\nFilesystem Statistics:"); + Serial.print(" Block size: "); + Serial.print(stats.f_bsize); + Serial.println(" bytes"); + + Serial.print(" Total blocks: "); + Serial.println(stats.f_blocks); + + Serial.print(" Free blocks: "); + Serial.println(stats.f_bfree); + + uint32_t total_kb = (stats.f_blocks * stats.f_bsize) / 1024; + uint32_t free_kb = (stats.f_bfree * stats.f_bsize) / 1024; + uint32_t used_kb = total_kb - free_kb; + + Serial.print(" Total space: "); + Serial.print(total_kb); + Serial.println(" KB"); + + Serial.print(" Used space: "); + Serial.print(used_kb); + Serial.println(" KB"); + + Serial.print(" Free space: "); + Serial.print(free_kb); + Serial.println(" KB\n"); + } else { + Serial.print("Failed to get filesystem stats: "); + Serial.println(ret); + } +} + +void testFileOperations() { + Serial.println("Testing File Operations:"); + Serial.println("------------------------"); + + // Create and write to a file + const char *filepath = MOUNT_POINT "/test.txt"; + const char *data = "Hello from QSPI filesystem!\nThis is a test file.\n"; + + Serial.print("Writing file: "); + Serial.print(filepath); + Serial.print("... "); + + struct fs_file_t file; + fs_file_t_init(&file); + + int ret = fs_open(&file, filepath, FS_O_CREATE | FS_O_WRITE); + if (ret < 0) { + Serial.print("FAILED to open ("); + Serial.print(ret); + Serial.println(")"); + return; + } + + ssize_t written = fs_write(&file, data, strlen(data)); + fs_close(&file); + + if (written == strlen(data)) { + Serial.print("OK ("); + Serial.print(written); + Serial.println(" bytes)"); + } else { + Serial.println("FAILED"); + return; + } + + // Read the file back + Serial.print("Reading file... "); + char read_buffer[128]; + memset(read_buffer, 0, sizeof(read_buffer)); + + fs_file_t_init(&file); + ret = fs_open(&file, filepath, FS_O_READ); + if (ret < 0) { + Serial.print("FAILED to open ("); + Serial.print(ret); + Serial.println(")"); + return; + } + + ssize_t bytes_read = fs_read(&file, read_buffer, sizeof(read_buffer) - 1); + fs_close(&file); + + if (bytes_read > 0) { + Serial.print("OK ("); + Serial.print(bytes_read); + Serial.println(" bytes)"); + Serial.println("\nFile contents:"); + Serial.println("---"); + Serial.print(read_buffer); + Serial.println("---\n"); + } else { + Serial.println("FAILED"); + } + + // Write a second file with sensor data simulation + const char *datafile = MOUNT_POINT "/sensor_data.txt"; + Serial.print("Creating sensor data file... "); + + fs_file_t_init(&file); + ret = fs_open(&file, datafile, FS_O_CREATE | FS_O_WRITE); + if (ret == 0) { + // Simulate writing sensor readings + for (int i = 0; i < 10; i++) { + char line[64]; + snprintf(line, sizeof(line), "Reading %d: Temperature=%.1f C, Humidity=%.1f%%\n", + i, 20.0 + i * 0.5, 45.0 + i * 1.2); + fs_write(&file, line, strlen(line)); + } + fs_close(&file); + Serial.println("OK"); + } else { + Serial.println("FAILED"); + } +} + +void listFiles() { + Serial.println("\nDirectory Listing:"); + Serial.println("------------------"); + + struct fs_dir_t dir; + fs_dir_t_init(&dir); + + int ret = fs_opendir(&dir, MOUNT_POINT); + if (ret < 0) { + Serial.print("Failed to open directory ("); + Serial.print(ret); + Serial.println(")"); + return; + } + + struct fs_dirent entry; + int count = 0; + + while (true) { + ret = fs_readdir(&dir, &entry); + if (ret < 0) { + Serial.println("Error reading directory"); + break; + } + + if (entry.name[0] == 0) { + // End of directory + break; + } + + Serial.print(" "); + if (entry.type == FS_DIR_ENTRY_DIR) { + Serial.print("[DIR] "); + } else { + Serial.print("[FILE] "); + } + Serial.print(entry.name); + Serial.print(" ("); + Serial.print(entry.size); + Serial.println(" bytes)"); + + count++; + } + + fs_closedir(&dir); + + if (count == 0) { + Serial.println(" (empty)"); + } + + Serial.print("\nTotal items: "); + Serial.println(count); +} diff --git a/libraries/QSPI/examples/QSPIPartitioning.ino b/libraries/QSPI/examples/QSPIPartitioning.ino new file mode 100644 index 000000000..7e5305bf0 --- /dev/null +++ b/libraries/QSPI/examples/QSPIPartitioning.ino @@ -0,0 +1,415 @@ +/* + QSPI Partitioning Example + + This example demonstrates how to partition QSPI flash memory into + logical regions for different purposes: + - Configuration storage + - Data logging + - User files + - Reserved/backup area + + Features: + - Define multiple partitions with different sizes + - Demonstrate isolated read/write operations per partition + - Partition boundary checking + - Efficient partition management + + Note: QSPI flash must be configured in the board's device tree overlay. +*/ + +#include + +// Partition definitions +enum PartitionID { + PARTITION_CONFIG = 0, // Small partition for configuration (64KB) + PARTITION_LOGGING, // Medium partition for data logging (256KB) + PARTITION_USER_FILES, // Large partition for user files (remaining space - 128KB) + PARTITION_BACKUP, // Reserved backup partition (128KB) + PARTITION_COUNT +}; + +struct Partition { + const char* name; + uint32_t start_address; + uint32_t size; +}; + +// Partition table (will be initialized based on flash size) +Partition partitions[PARTITION_COUNT]; + +// Helper class for partition management +class PartitionManager { +public: + static bool initialize() { + uint32_t flash_size = QSPI.getFlashSize(); + uint32_t sector_size = QSPI.getSectorSize(); + + if (flash_size == 0) { + return false; + } + + Serial.print("Initializing partition table for "); + Serial.print(flash_size / 1024); + Serial.println(" KB flash"); + + // Define partition layout + partitions[PARTITION_CONFIG] = {"CONFIG", 0, 64 * 1024}; + partitions[PARTITION_LOGGING] = {"LOGGING", partitions[PARTITION_CONFIG].start_address + partitions[PARTITION_CONFIG].size, 256 * 1024}; + partitions[PARTITION_BACKUP] = {"BACKUP", flash_size - 128 * 1024, 128 * 1024}; + partitions[PARTITION_USER_FILES] = { + "USER_FILES", + partitions[PARTITION_LOGGING].start_address + partitions[PARTITION_LOGGING].size, + partitions[PARTITION_BACKUP].start_address - (partitions[PARTITION_LOGGING].start_address + partitions[PARTITION_LOGGING].size) + }; + + // Validate partitions + for (int i = 0; i < PARTITION_COUNT; i++) { + // Align to sector boundaries + if (partitions[i].start_address % sector_size != 0) { + Serial.print("Warning: Partition "); + Serial.print(partitions[i].name); + Serial.println(" is not sector-aligned!"); + } + + if (partitions[i].start_address + partitions[i].size > flash_size) { + Serial.print("Error: Partition "); + Serial.print(partitions[i].name); + Serial.println(" exceeds flash size!"); + return false; + } + } + + return true; + } + + static void printPartitionTable() { + Serial.println("\nPartition Table:"); + Serial.println("================"); + Serial.println("ID Name Start Size End"); + Serial.println("-- ------------ ---------- ---------- ----------"); + + for (int i = 0; i < PARTITION_COUNT; i++) { + char line[80]; + snprintf(line, sizeof(line), "%-2d %-12s 0x%08X 0x%08X 0x%08X", + i, + partitions[i].name, + partitions[i].start_address, + partitions[i].size, + partitions[i].start_address + partitions[i].size); + Serial.println(line); + } + Serial.println(); + } + + static bool writeToPartition(PartitionID id, uint32_t offset, const void* data, size_t size) { + if (id >= PARTITION_COUNT) { + return false; + } + + uint32_t address = partitions[id].start_address + offset; + + // Boundary check + if (offset + size > partitions[id].size) { + Serial.print("Error: Write exceeds partition "); + Serial.print(partitions[id].name); + Serial.println(" boundary!"); + return false; + } + + return QSPI.write(address, data, size); + } + + static bool readFromPartition(PartitionID id, uint32_t offset, void* data, size_t size) { + if (id >= PARTITION_COUNT) { + return false; + } + + uint32_t address = partitions[id].start_address + offset; + + // Boundary check + if (offset + size > partitions[id].size) { + Serial.print("Error: Read exceeds partition "); + Serial.print(partitions[id].name); + Serial.println(" boundary!"); + return false; + } + + return QSPI.read(address, data, size); + } + + static bool erasePartition(PartitionID id) { + if (id >= PARTITION_COUNT) { + return false; + } + + Serial.print("Erasing partition "); + Serial.print(partitions[id].name); + Serial.print("... "); + + bool result = QSPI.erase(partitions[id].start_address, partitions[id].size); + + if (result) { + Serial.println("OK"); + } else { + Serial.println("FAILED"); + } + + return result; + } + + static uint32_t getPartitionSize(PartitionID id) { + if (id >= PARTITION_COUNT) { + return 0; + } + return partitions[id].size; + } + + static const char* getPartitionName(PartitionID id) { + if (id >= PARTITION_COUNT) { + return "UNKNOWN"; + } + return partitions[id].name; + } +}; + +void setup() { + Serial.begin(115200); + while (!Serial) { + delay(10); + } + + Serial.println("QSPI Partitioning Example"); + Serial.println("=========================\n"); + + // Initialize QSPI flash + if (!QSPI.begin()) { + Serial.println("Failed to initialize QSPI flash!"); + while (1) { + delay(1000); + } + } + + Serial.println("QSPI flash initialized successfully"); + Serial.print("Flash size: "); + Serial.print(QSPI.getFlashSize() / 1024); + Serial.println(" KB"); + Serial.print("Sector size: "); + Serial.print(QSPI.getSectorSize()); + Serial.println(" bytes\n"); + + // Initialize partition table + if (!PartitionManager::initialize()) { + Serial.println("Failed to initialize partition table!"); + while (1) { + delay(1000); + } + } + + // Display partition table + PartitionManager::printPartitionTable(); + + // Test each partition + testConfigPartition(); + testLoggingPartition(); + testUserFilesPartition(); + testBackupPartition(); + + Serial.println("\n=== All partition tests completed ==="); +} + +void loop() { + // Nothing to do in loop + delay(1000); +} + +void testConfigPartition() { + Serial.println("Testing CONFIG Partition:"); + Serial.println("-------------------------"); + + // Erase partition first + PartitionManager::erasePartition(PARTITION_CONFIG); + + // Simulate storing configuration data + struct Config { + uint32_t magic; + uint8_t version; + char device_name[32]; + uint32_t flags; + uint32_t checksum; + } config; + + config.magic = 0xC0FF1234; + config.version = 1; + strncpy(config.device_name, "QSPI-Device-001", sizeof(config.device_name)); + config.flags = 0x0000ABCD; + config.checksum = 0xDEADBEEF; + + Serial.print("Writing config... "); + if (PartitionManager::writeToPartition(PARTITION_CONFIG, 0, &config, sizeof(config))) { + Serial.println("OK"); + } else { + Serial.println("FAILED"); + return; + } + + // Read back and verify + Config read_config; + Serial.print("Reading config... "); + if (PartitionManager::readFromPartition(PARTITION_CONFIG, 0, &read_config, sizeof(read_config))) { + Serial.println("OK"); + + Serial.print(" Magic: 0x"); + Serial.println(read_config.magic, HEX); + Serial.print(" Version: "); + Serial.println(read_config.version); + Serial.print(" Device Name: "); + Serial.println(read_config.device_name); + Serial.print(" Flags: 0x"); + Serial.println(read_config.flags, HEX); + + if (memcmp(&config, &read_config, sizeof(config)) == 0) { + Serial.println(" Verification: PASSED"); + } else { + Serial.println(" Verification: FAILED"); + } + } else { + Serial.println("FAILED"); + } + + Serial.println(); +} + +void testLoggingPartition() { + Serial.println("Testing LOGGING Partition:"); + Serial.println("--------------------------"); + + // Erase partition first + PartitionManager::erasePartition(PARTITION_LOGGING); + + // Simulate logging sensor data + struct LogEntry { + uint32_t timestamp; + float temperature; + float humidity; + uint16_t pressure; + }; + + const int num_entries = 5; + LogEntry logs[num_entries]; + + // Generate sample log entries + for (int i = 0; i < num_entries; i++) { + logs[i].timestamp = millis() + i * 1000; + logs[i].temperature = 20.0 + i * 0.5; + logs[i].humidity = 45.0 + i * 2.0; + logs[i].pressure = 1013 + i; + } + + Serial.print("Writing "); + Serial.print(num_entries); + Serial.print(" log entries... "); + if (PartitionManager::writeToPartition(PARTITION_LOGGING, 0, logs, sizeof(logs))) { + Serial.println("OK"); + } else { + Serial.println("FAILED"); + return; + } + + // Read back + LogEntry read_logs[num_entries]; + Serial.print("Reading log entries... "); + if (PartitionManager::readFromPartition(PARTITION_LOGGING, 0, read_logs, sizeof(read_logs))) { + Serial.println("OK"); + + Serial.println(" Log entries:"); + for (int i = 0; i < num_entries; i++) { + Serial.print(" Entry "); + Serial.print(i); + Serial.print(": T="); + Serial.print(read_logs[i].temperature); + Serial.print("C, H="); + Serial.print(read_logs[i].humidity); + Serial.print("%, P="); + Serial.println(read_logs[i].pressure); + } + } else { + Serial.println("FAILED"); + } + + Serial.println(); +} + +void testUserFilesPartition() { + Serial.println("Testing USER_FILES Partition:"); + Serial.println("-----------------------------"); + + // Don't erase - just show we can write to different offsets + const char* file1 = "This is user file 1 data"; + const char* file2 = "User file 2 contains different content"; + + Serial.print("Writing file 1 at offset 0... "); + if (PartitionManager::writeToPartition(PARTITION_USER_FILES, 0, file1, strlen(file1) + 1)) { + Serial.println("OK"); + } else { + Serial.println("FAILED"); + } + + Serial.print("Writing file 2 at offset 4KB... "); + if (PartitionManager::writeToPartition(PARTITION_USER_FILES, 4096, file2, strlen(file2) + 1)) { + Serial.println("OK"); + } else { + Serial.println("FAILED"); + } + + // Read back + char buffer[64]; + Serial.print("Reading file 1... "); + if (PartitionManager::readFromPartition(PARTITION_USER_FILES, 0, buffer, sizeof(buffer))) { + Serial.println("OK"); + Serial.print(" Content: "); + Serial.println(buffer); + } else { + Serial.println("FAILED"); + } + + Serial.print("Reading file 2... "); + if (PartitionManager::readFromPartition(PARTITION_USER_FILES, 4096, buffer, sizeof(buffer))) { + Serial.println("OK"); + Serial.print(" Content: "); + Serial.println(buffer); + } else { + Serial.println("FAILED"); + } + + Serial.println(); +} + +void testBackupPartition() { + Serial.println("Testing BACKUP Partition:"); + Serial.println("-------------------------"); + + // Erase partition first + PartitionManager::erasePartition(PARTITION_BACKUP); + + const char* backup_data = "Critical backup data that should be preserved!"; + + Serial.print("Writing backup data... "); + if (PartitionManager::writeToPartition(PARTITION_BACKUP, 0, backup_data, strlen(backup_data) + 1)) { + Serial.println("OK"); + } else { + Serial.println("FAILED"); + return; + } + + char buffer[64]; + Serial.print("Reading backup data... "); + if (PartitionManager::readFromPartition(PARTITION_BACKUP, 0, buffer, sizeof(buffer))) { + Serial.println("OK"); + Serial.print(" Content: "); + Serial.println(buffer); + } else { + Serial.println("FAILED"); + } + + Serial.println(); +} diff --git a/libraries/QSPI/examples/QSPISimpleFS.ino b/libraries/QSPI/examples/QSPISimpleFS.ino new file mode 100644 index 000000000..51849f588 --- /dev/null +++ b/libraries/QSPI/examples/QSPISimpleFS.ino @@ -0,0 +1,519 @@ +/* + QSPI Simple Filesystem Example + + This example demonstrates a simple filesystem implementation on QSPI flash + that works without requiring LittleFS or other external dependencies. + + Features: + - Simple File Allocation Table (FAT) system + - Create, write, read, and delete files + - List files with sizes + - Automatic wear leveling across sectors + - No external filesystem dependencies required + + The filesystem layout: + - Sector 0: File Allocation Table (FAT) + - Remaining sectors: File data blocks + + Note: QSPI flash must be configured in the board's device tree overlay. +*/ + +#include + +// Filesystem configuration +#define FS_MAX_FILES 16 +#define FS_BLOCK_SIZE 4096 // 4KB blocks +#define FS_FAT_SECTOR 0 // First sector for FAT + +// File entry in the FAT +struct FileEntry { + char name[32]; // Filename + uint32_t size; // File size in bytes + uint32_t start_block; // Starting block number + uint32_t block_count; // Number of blocks used + uint8_t valid; // 0xFF = valid, 0x00 = deleted + uint8_t reserved[3]; // Padding +}; + +// File Allocation Table +struct FileAllocationTable { + uint32_t magic; // Magic number for validation + uint32_t version; // Filesystem version + uint32_t total_blocks; // Total blocks available + uint32_t used_blocks; // Blocks currently in use + FileEntry files[FS_MAX_FILES]; // File entries + uint32_t checksum; // Simple checksum +}; + +class SimpleFS { +private: + FileAllocationTable fat; + uint32_t sector_size; + uint32_t blocks_per_sector; + bool initialized; + + uint32_t calculateChecksum() { + uint32_t sum = 0; + sum += fat.magic; + sum += fat.version; + sum += fat.total_blocks; + sum += fat.used_blocks; + for (int i = 0; i < FS_MAX_FILES; i++) { + sum += fat.files[i].size; + sum += fat.files[i].start_block; + } + return sum; + } + + bool loadFAT() { + // Read FAT from flash + if (!QSPI.read(0, &fat, sizeof(fat))) { + return false; + } + + // Check magic number + if (fat.magic != 0x51534653) { // "QSFS" + Serial.println("Invalid filesystem or not formatted"); + return false; + } + + // Verify checksum + uint32_t stored_checksum = fat.checksum; + uint32_t calculated = calculateChecksum(); + if (stored_checksum != calculated) { + Serial.println("Filesystem checksum mismatch!"); + return false; + } + + return true; + } + + bool saveFAT() { + fat.checksum = calculateChecksum(); + + // Erase FAT sector + if (!QSPI.erase(0, sector_size)) { + return false; + } + + // Write FAT + return QSPI.write(0, &fat, sizeof(fat)); + } + + int findFreeSlot() { + for (int i = 0; i < FS_MAX_FILES; i++) { + if (fat.files[i].valid != 0xFF) { + return i; + } + } + return -1; + } + + int findFile(const char* name) { + for (int i = 0; i < FS_MAX_FILES; i++) { + if (fat.files[i].valid == 0xFF && strcmp(fat.files[i].name, name) == 0) { + return i; + } + } + return -1; + } + + uint32_t findFreeBlocks(uint32_t count) { + // Simple sequential allocation + uint32_t consecutive = 0; + uint32_t start_block = 1; // Block 0 is FAT + + // Build used blocks bitmap + bool used[256] = {false}; + used[0] = true; // FAT block + + for (int i = 0; i < FS_MAX_FILES; i++) { + if (fat.files[i].valid == 0xFF) { + for (uint32_t b = 0; b < fat.files[i].block_count; b++) { + uint32_t block = fat.files[i].start_block + b; + if (block < 256) used[block] = true; + } + } + } + + // Find consecutive free blocks + for (uint32_t i = 1; i < fat.total_blocks; i++) { + if (!used[i]) { + if (consecutive == 0) start_block = i; + consecutive++; + if (consecutive >= count) { + return start_block; + } + } else { + consecutive = 0; + } + } + + return 0; // Not enough free blocks + } + +public: + SimpleFS() : initialized(false) {} + + bool begin() { + sector_size = QSPI.getSectorSize(); + if (sector_size == 0) { + return false; + } + + blocks_per_sector = sector_size / FS_BLOCK_SIZE; + uint32_t flash_size = QSPI.getFlashSize(); + uint32_t total_sectors = flash_size / sector_size; + + // Try to load existing FAT + if (loadFAT()) { + Serial.println("Existing filesystem found"); + initialized = true; + return true; + } + + // No valid filesystem found + Serial.println("No valid filesystem found"); + return false; + } + + bool format() { + Serial.println("Formatting filesystem..."); + + sector_size = QSPI.getSectorSize(); + uint32_t flash_size = QSPI.getFlashSize(); + uint32_t total_sectors = flash_size / sector_size; + + // Initialize FAT + memset(&fat, 0, sizeof(fat)); + fat.magic = 0x51534653; // "QSFS" + fat.version = 1; + fat.total_blocks = (flash_size / FS_BLOCK_SIZE); + fat.used_blocks = 1; // FAT block + + // Mark all files as invalid + for (int i = 0; i < FS_MAX_FILES; i++) { + fat.files[i].valid = 0x00; + } + + // Save FAT + if (!saveFAT()) { + Serial.println("Failed to write FAT"); + return false; + } + + Serial.println("Format complete"); + initialized = true; + return true; + } + + bool createFile(const char* name, const void* data, size_t size) { + if (!initialized) return false; + + // Check if file already exists + if (findFile(name) >= 0) { + Serial.println("File already exists"); + return false; + } + + // Find free slot + int slot = findFreeSlot(); + if (slot < 0) { + Serial.println("No free file slots"); + return false; + } + + // Calculate blocks needed + uint32_t blocks_needed = (size + FS_BLOCK_SIZE - 1) / FS_BLOCK_SIZE; + + // Find free blocks + uint32_t start_block = findFreeBlocks(blocks_needed); + if (start_block == 0) { + Serial.println("Not enough free space"); + return false; + } + + // Write file data + uint32_t address = start_block * FS_BLOCK_SIZE; + + // Erase blocks + for (uint32_t i = 0; i < blocks_needed; i++) { + uint32_t block_addr = (start_block + i) * FS_BLOCK_SIZE; + uint32_t sector_addr = (block_addr / sector_size) * sector_size; + if (!QSPI.erase(sector_addr, sector_size)) { + Serial.println("Failed to erase block"); + return false; + } + } + + // Write data + if (!QSPI.write(address, data, size)) { + Serial.println("Failed to write file data"); + return false; + } + + // Update FAT + strncpy(fat.files[slot].name, name, sizeof(fat.files[slot].name) - 1); + fat.files[slot].size = size; + fat.files[slot].start_block = start_block; + fat.files[slot].block_count = blocks_needed; + fat.files[slot].valid = 0xFF; + fat.used_blocks += blocks_needed; + + return saveFAT(); + } + + bool readFile(const char* name, void* buffer, size_t buffer_size) { + if (!initialized) return false; + + int slot = findFile(name); + if (slot < 0) { + Serial.println("File not found"); + return false; + } + + uint32_t address = fat.files[slot].start_block * FS_BLOCK_SIZE; + size_t read_size = min(fat.files[slot].size, buffer_size); + + return QSPI.read(address, buffer, read_size); + } + + bool deleteFile(const char* name) { + if (!initialized) return false; + + int slot = findFile(name); + if (slot < 0) { + return false; + } + + fat.used_blocks -= fat.files[slot].block_count; + fat.files[slot].valid = 0x00; + + return saveFAT(); + } + + void listFiles() { + if (!initialized) { + Serial.println("Filesystem not initialized"); + return; + } + + Serial.println("\nFile Listing:"); + Serial.println("-------------------------------"); + Serial.println("Name Size"); + Serial.println("-------------------------------"); + + int count = 0; + for (int i = 0; i < FS_MAX_FILES; i++) { + if (fat.files[i].valid == 0xFF) { + char line[50]; + snprintf(line, sizeof(line), "%-24s %6u bytes", + fat.files[i].name, fat.files[i].size); + Serial.println(line); + count++; + } + } + + if (count == 0) { + Serial.println("(no files)"); + } + Serial.println("-------------------------------"); + Serial.print("Total files: "); + Serial.println(count); + } + + void printStats() { + if (!initialized) { + Serial.println("Filesystem not initialized"); + return; + } + + Serial.println("\nFilesystem Statistics:"); + Serial.println("----------------------"); + Serial.print("Total blocks: "); + Serial.println(fat.total_blocks); + Serial.print("Used blocks: "); + Serial.println(fat.used_blocks); + Serial.print("Free blocks: "); + Serial.println(fat.total_blocks - fat.used_blocks); + Serial.print("Block size: "); + Serial.print(FS_BLOCK_SIZE); + Serial.println(" bytes"); + Serial.print("Total space: "); + Serial.print((fat.total_blocks * FS_BLOCK_SIZE) / 1024); + Serial.println(" KB"); + Serial.print("Used space: "); + Serial.print((fat.used_blocks * FS_BLOCK_SIZE) / 1024); + Serial.println(" KB"); + Serial.print("Free space: "); + Serial.print(((fat.total_blocks - fat.used_blocks) * FS_BLOCK_SIZE) / 1024); + Serial.println(" KB"); + Serial.println(); + } + + uint32_t getFileSize(const char* name) { + int slot = findFile(name); + if (slot < 0) return 0; + return fat.files[slot].size; + } + + bool exists(const char* name) { + return findFile(name) >= 0; + } +}; + +SimpleFS fs; + +void setup() { + Serial.begin(115200); + while (!Serial) { + delay(10); + } + + Serial.println("QSPI Simple Filesystem Example"); + Serial.println("===============================\n"); + + // Initialize QSPI flash + if (!QSPI.begin()) { + Serial.println("Failed to initialize QSPI flash!"); + while (1) { + delay(1000); + } + } + + Serial.println("QSPI flash initialized successfully"); + Serial.print("Flash size: "); + Serial.print(QSPI.getFlashSize() / 1024); + Serial.println(" KB"); + Serial.print("Sector size: "); + Serial.print(QSPI.getSectorSize()); + Serial.println(" bytes\n"); + + // Try to mount existing filesystem + if (!fs.begin()) { + Serial.println("\nFormatting new filesystem..."); + if (!fs.format()) { + Serial.println("Format failed!"); + while (1) { + delay(1000); + } + } + } + + // Show filesystem stats + fs.printStats(); + + // Run filesystem tests + testFileSystem(); +} + +void loop() { + // Nothing to do in loop + delay(1000); +} + +void testFileSystem() { + Serial.println("Testing Filesystem Operations:"); + Serial.println("==============================\n"); + + // Test 1: Create a text file + Serial.println("Test 1: Creating text file..."); + const char* text_data = "Hello from QSPI Simple Filesystem!\nThis is a test file.\nLine 3 of test data."; + + if (fs.createFile("test.txt", text_data, strlen(text_data) + 1)) { + Serial.println(" Created test.txt - OK"); + } else { + Serial.println(" Failed to create test.txt"); + } + + // Test 2: Read the file back + Serial.println("\nTest 2: Reading text file..."); + char read_buffer[128]; + memset(read_buffer, 0, sizeof(read_buffer)); + + if (fs.readFile("test.txt", read_buffer, sizeof(read_buffer))) { + Serial.println(" Read test.txt - OK"); + Serial.println(" Content:"); + Serial.println(" ---"); + Serial.print(" "); + Serial.println(read_buffer); + Serial.println(" ---"); + } else { + Serial.println(" Failed to read test.txt"); + } + + // Test 3: Create a binary data file + Serial.println("\nTest 3: Creating binary data file..."); + struct SensorData { + uint32_t timestamp; + float temperature; + float humidity; + uint16_t pressure; + } sensor_data[5]; + + for (int i = 0; i < 5; i++) { + sensor_data[i].timestamp = millis() + i * 1000; + sensor_data[i].temperature = 20.0 + i * 0.5; + sensor_data[i].humidity = 45.0 + i * 2.0; + sensor_data[i].pressure = 1013 + i; + } + + if (fs.createFile("sensors.dat", sensor_data, sizeof(sensor_data))) { + Serial.println(" Created sensors.dat - OK"); + } else { + Serial.println(" Failed to create sensors.dat"); + } + + // Test 4: Read binary data back + Serial.println("\nTest 4: Reading binary data file..."); + SensorData read_sensors[5]; + + if (fs.readFile("sensors.dat", read_sensors, sizeof(read_sensors))) { + Serial.println(" Read sensors.dat - OK"); + Serial.println(" Sensor readings:"); + for (int i = 0; i < 5; i++) { + Serial.print(" ["); + Serial.print(i); + Serial.print("] T="); + Serial.print(read_sensors[i].temperature, 1); + Serial.print("C, H="); + Serial.print(read_sensors[i].humidity, 1); + Serial.print("%, P="); + Serial.println(read_sensors[i].pressure); + } + } else { + Serial.println(" Failed to read sensors.dat"); + } + + // Test 5: Create config file + Serial.println("\nTest 5: Creating config file..."); + const char* config = "device_name=QSPI_Device\nversion=1.0\nmode=normal"; + + if (fs.createFile("config.ini", config, strlen(config) + 1)) { + Serial.println(" Created config.ini - OK"); + } else { + Serial.println(" Failed to create config.ini"); + } + + // List all files + Serial.println(); + fs.listFiles(); + + // Show updated statistics + fs.printStats(); + + // Test 6: Delete a file + Serial.println("Test 6: Deleting file..."); + if (fs.deleteFile("test.txt")) { + Serial.println(" Deleted test.txt - OK"); + } else { + Serial.println(" Failed to delete test.txt"); + } + + // List files after deletion + Serial.println(); + fs.listFiles(); + fs.printStats(); + + Serial.println("\n=== All tests completed ==="); +} diff --git a/libraries/QSPI/examples/README.md b/libraries/QSPI/examples/README.md new file mode 100644 index 000000000..d6e2f3b9d --- /dev/null +++ b/libraries/QSPI/examples/README.md @@ -0,0 +1,251 @@ +# QSPI Library Examples + +This directory contains examples demonstrating various uses of the QSPI library for external flash memory. + +## Examples Overview + +### 1. BasicQSPI.ino +**Difficulty:** Beginner +**Dependencies:** None + +Basic example showing fundamental QSPI operations: +- Initialize QSPI flash +- Get flash information (size, sector size, page size) +- Erase sectors +- Write and read data +- Verify data integrity + +**Best for:** Learning QSPI basics and testing your flash hardware. + +--- + +### 2. QSPISimpleFS.ino +**Difficulty:** Intermediate +**Dependencies:** None + +A self-contained simple filesystem implementation that works directly on QSPI flash without requiring external filesystem libraries. + +**Features:** +- File Allocation Table (FAT) system +- Create, read, and delete files +- File listing and statistics +- Works out-of-the-box without additional configuration +- Supports up to 16 files +- Automatic space management + +**Best for:** Projects needing simple file storage without LittleFS configuration. + +**Limitations:** +- Maximum 16 files +- No directory support +- Sequential block allocation +- Fixed 4KB block size + +--- + +### 3. QSPIPartitioning.ino +**Difficulty:** Intermediate +**Dependencies:** None + +Demonstrates how to partition QSPI flash into logical regions for different purposes. + +**Features:** +- Multiple partition support (Config, Logging, User Files, Backup) +- Partition boundary protection +- Per-partition read/write operations +- Partition table visualization +- Safe partition management class + +**Best for:** Complex applications needing organized flash storage regions. + +**Use cases:** +- Separating configuration from data +- Dedicated logging areas +- Protected backup regions +- Multi-purpose flash organization + +--- + +### 4. QSPIFilesystem.ino +**Difficulty:** Advanced +**Dependencies:** Zephyr LittleFS + +Full-featured filesystem example using Zephyr's LittleFS implementation. + +**Features:** +- Complete filesystem with LittleFS +- Standard file operations (create, read, write, delete) +- Directory listing +- Filesystem statistics +- Wear leveling (provided by LittleFS) +- Power-loss resilient + +**Configuration Required:** + +Add to your board's `prj.conf`: +``` +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_LITTLEFS=y +CONFIG_FILE_SYSTEM_MAX_FILE_NAME=128 +``` + +**Best for:** Production applications needing robust filesystem support. + +**Why use this over QSPISimpleFS?** +- No file count limit +- Better wear leveling +- Power-loss protection +- Directory support +- Standard POSIX-like API + +--- + +## Quick Start Guide + +### Hardware Requirements +- Arduino board with QSPI flash support (e.g., GIGA R1, Portenta H7) +- QSPI flash configured in device tree overlay + +### Choosing the Right Example + +``` +Need basic flash operations? +→ Use BasicQSPI.ino + +Need simple file storage without configuration? +→ Use QSPISimpleFS.ino + +Need organized flash regions? +→ Use QSPIPartitioning.ino + +Need production-grade filesystem? +→ Use QSPIFilesystem.ino (requires LittleFS setup) +``` + +--- + +## Configuring LittleFS (for QSPIFilesystem.ino) + +### Step 1: Create/Edit prj.conf + +Create a `prj.conf` file in your sketch directory with: + +```conf +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_LITTLEFS=y +CONFIG_FILE_SYSTEM_MAX_FILE_NAME=128 +``` + +### Step 2: Verify Device Tree + +Your board should have QSPI flash defined in its overlay. Example: + +```dts +&qspi { + status = "okay"; + + qspi_flash: qspi-nor-flash@0 { + compatible = "nordic,qspi-nor"; + reg = <0>; + /* ... other properties ... */ + }; +}; +``` + +### Step 3: Build and Upload + +The Arduino-Zephyr build system should automatically pick up your `prj.conf`. + +### Troubleshooting LittleFS + +**Error: `lfs.h: No such file or directory`** +- LittleFS is not enabled in your build +- Make sure `CONFIG_FILE_SYSTEM_LITTLEFS=y` is in `prj.conf` +- Use `QSPISimpleFS.ino` instead if you don't need LittleFS + +**Error: Filesystem mount fails** +- Flash might not be formatted +- Try erasing the flash first using `BasicQSPI.ino` +- Check that QSPI flash is properly configured in device tree + +--- + +## Example Comparison + +| Feature | BasicQSPI | QSPISimpleFS | QSPIPartitioning | QSPIFilesystem | +|---------|-----------|--------------|------------------|----------------| +| Difficulty | ⭐ | ⭐⭐ | ⭐⭐ | ⭐⭐⭐ | +| Configuration | None | None | None | LittleFS required | +| File Support | No | Yes (16 max) | No | Unlimited | +| Partitions | No | No | Yes | No | +| Wear Leveling | No | Basic | No | Yes (LittleFS) | +| Power-Loss Safe | No | No | No | Yes | +| Production Ready | No | Good | Good | Excellent | + +--- + +## Common Operations + +### Reading Flash Info +```cpp +QSPI.begin(); +Serial.println(QSPI.getFlashSize()); +Serial.println(QSPI.getSectorSize()); +``` + +### Raw Read/Write +```cpp +// Erase first +QSPI.erase(address, size); + +// Write +QSPI.write(address, data, size); + +// Read +QSPI.read(address, buffer, size); +``` + +### Using SimpleFS +```cpp +SimpleFS fs; +fs.begin() || fs.format(); +fs.createFile("test.txt", data, size); +fs.readFile("test.txt", buffer, buffer_size); +fs.listFiles(); +``` + +### Using Partitions +```cpp +PartitionManager::initialize(); +PartitionManager::writeToPartition(PARTITION_CONFIG, offset, data, size); +PartitionManager::readFromPartition(PARTITION_CONFIG, offset, buffer, size); +``` + +--- + +## Tips and Best Practices + +1. **Always erase before writing**: Flash memory requires erasure before writing new data +2. **Sector alignment**: Erase operations work on sector boundaries (typically 4KB) +3. **Wear leveling**: Distribute writes across flash to extend lifetime +4. **Check return values**: Always verify that operations succeeded +5. **Checksums**: Use checksums for critical data to detect corruption +6. **Power-loss**: Consider what happens if power is lost during write operations + +--- + +## Further Reading + +- [Zephyr Flash API Documentation](https://docs.zephyrproject.org/latest/hardware/peripherals/flash.html) +- [LittleFS Documentation](https://github.com/littlefs-project/littlefs) +- [QSPI Library Reference](../QSPI.h) + +--- + +## Support + +For issues or questions: +- Check the example code comments +- Review error messages carefully +- Start with `BasicQSPI.ino` to verify hardware +- Use `QSPISimpleFS.ino` if LittleFS configuration is problematic diff --git a/variants/arduino_giga_r1_stm32h747xx_m7/arduino_giga_r1_stm32h747xx_m7.conf b/variants/arduino_giga_r1_stm32h747xx_m7/arduino_giga_r1_stm32h747xx_m7.conf index eaf599eaa..9ed660e2d 100644 --- a/variants/arduino_giga_r1_stm32h747xx_m7/arduino_giga_r1_stm32h747xx_m7.conf +++ b/variants/arduino_giga_r1_stm32h747xx_m7/arduino_giga_r1_stm32h747xx_m7.conf @@ -64,3 +64,9 @@ CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 CONFIG_BT_RX_STACK_SIZE=4096 CONFIG_STM32H7_BOOT_M4_AT_INIT=n + +# QSPI Flash Support +CONFIG_FLASH=y +CONFIG_FLASH_STM32_QSPI=y +CONFIG_FLASH_MAP=y +CONFIG_FLASH_PAGE_LAYOUT=y diff --git a/variants/arduino_giga_r1_stm32h747xx_m7/arduino_giga_r1_stm32h747xx_m7.overlay b/variants/arduino_giga_r1_stm32h747xx_m7/arduino_giga_r1_stm32h747xx_m7.overlay index 1bce0bdf3..442ca937c 100644 --- a/variants/arduino_giga_r1_stm32h747xx_m7/arduino_giga_r1_stm32h747xx_m7.overlay +++ b/variants/arduino_giga_r1_stm32h747xx_m7/arduino_giga_r1_stm32h747xx_m7.overlay @@ -27,6 +27,8 @@ status = "okay"; }; +qspi_flash: &n25q128a1 {}; + &i2c4 { status = "okay"; gc2145: gc2145@3c { @@ -334,6 +336,7 @@ }; + &flash0 { partitions { user_sketch: partition@e0000 { diff --git a/variants/arduino_portenta_c33_r7fa6m5bh3cfc/arduino_portenta_c33_r7fa6m5bh3cfc.conf b/variants/arduino_portenta_c33_r7fa6m5bh3cfc/arduino_portenta_c33_r7fa6m5bh3cfc.conf index 2a003fc84..a50c155a3 100644 --- a/variants/arduino_portenta_c33_r7fa6m5bh3cfc/arduino_portenta_c33_r7fa6m5bh3cfc.conf +++ b/variants/arduino_portenta_c33_r7fa6m5bh3cfc/arduino_portenta_c33_r7fa6m5bh3cfc.conf @@ -99,3 +99,7 @@ CONFIG_BT_BUF_ACL_RX_SIZE=255 CONFIG_BT_BUF_CMD_TX_SIZE=255 CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE=255 CONFIG_BT_MAX_CONN=4 + +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_RENESAS_RA_QSPI=y diff --git a/variants/arduino_portenta_c33_r7fa6m5bh3cfc/arduino_portenta_c33_r7fa6m5bh3cfc.overlay b/variants/arduino_portenta_c33_r7fa6m5bh3cfc/arduino_portenta_c33_r7fa6m5bh3cfc.overlay index 25cbf9734..e6e0e919a 100644 --- a/variants/arduino_portenta_c33_r7fa6m5bh3cfc/arduino_portenta_c33_r7fa6m5bh3cfc.overlay +++ b/variants/arduino_portenta_c33_r7fa6m5bh3cfc/arduino_portenta_c33_r7fa6m5bh3cfc.overlay @@ -5,6 +5,8 @@ }; }; +qspi_flash: &at25sf128a {}; + &flash0 { partitions { mcuboot: partition@0 { diff --git a/variants/arduino_portenta_h7_stm32h747xx_m7/arduino_portenta_h7_stm32h747xx_m7.conf b/variants/arduino_portenta_h7_stm32h747xx_m7/arduino_portenta_h7_stm32h747xx_m7.conf index 6c0b575eb..3d7cdc2f6 100644 --- a/variants/arduino_portenta_h7_stm32h747xx_m7/arduino_portenta_h7_stm32h747xx_m7.conf +++ b/variants/arduino_portenta_h7_stm32h747xx_m7/arduino_portenta_h7_stm32h747xx_m7.conf @@ -100,3 +100,8 @@ CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 CONFIG_BT_RX_STACK_SIZE=4096 CONFIG_STM32H7_BOOT_M4_AT_INIT=n + +CONFIG_FLASH=y +CONFIG_FLASH_STM32_QSPI=y +CONFIG_FLASH_MAP=y +CONFIG_FLASH_PAGE_LAYOUT=y \ No newline at end of file diff --git a/variants/arduino_portenta_h7_stm32h747xx_m7/arduino_portenta_h7_stm32h747xx_m7.overlay b/variants/arduino_portenta_h7_stm32h747xx_m7/arduino_portenta_h7_stm32h747xx_m7.overlay index 36643e3d9..634e330b2 100644 --- a/variants/arduino_portenta_h7_stm32h747xx_m7/arduino_portenta_h7_stm32h747xx_m7.overlay +++ b/variants/arduino_portenta_h7_stm32h747xx_m7/arduino_portenta_h7_stm32h747xx_m7.overlay @@ -14,6 +14,7 @@ status = "okay"; }; + &i2c3 { status = "okay"; @@ -371,3 +372,9 @@ <&adc1 13>; /* Hack for D20 */ }; }; + + +/* QSPI flash (MX25L12833F) is already configured in arduino_portenta_h7-common.dtsi + * with the correct pins: IO2=PF7, IO3=PD13 (different from Giga R1!) + */ +qspi_flash: &mx25l12833f {};