Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions libraries/QSPI/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
zephyr_library()

zephyr_library_sources(QSPI.cpp)

zephyr_library_include_directories(.)
128 changes: 128 additions & 0 deletions libraries/QSPI/QSPI.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#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 "QSPI flash device not found in device tree"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#warning "QSPI flash device not found in device tree"
#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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use either NULL or nullptr in the file for consistency.

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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not verify the device can accept more commands, but simply that it's enabled and has passed init(). Since this is already tested in begin, IMO it's better to return true here.

}

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);
}

void QSPIClass::end() {
flash_dev = nullptr;
initialized = false;
}

// Create global instance
QSPIClass QSPI;
51 changes: 51 additions & 0 deletions libraries/QSPI/QSPI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#ifndef QSPI_H
#define QSPI_H

#include <Arduino.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/devicetree.h>

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);

// End/deinitialize
void end();

private:
const struct device *flash_dev;
bool initialized;
};

extern QSPIClass QSPI;

#endif
50 changes: 50 additions & 0 deletions libraries/QSPI/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# QSPI Library

This library provides a simple Arduino-style interface for accessing QSPI (Quad SPI) flash memory on Arduino Zephyr boards.

## Features

- Initialize and configure QSPI flash
- Read data from QSPI flash memory
- Write data to QSPI flash memory
- Erase sectors/blocks
- Get flash information (size, sector size, page size)


### Device Tree Setup
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure the following part is relevant for Arduino users, only a selected few know about any of this 🙂
Maybe you can move this info to a more specific file, such as README.zephyr.md?


Added to `arduino_giga_r1_stm32h747xx_m7.overlay` file:

```dts
&quadspi {
pinctrl-0 = <&quadspi_clk_pf10 &quadspi_bk1_ncs_pb6
&quadspi_bk1_io0_pf8 &quadspi_bk1_io1_pf9
&quadspi_bk1_io2_pf7 &quadspi_bk1_io3_pf6>;
pinctrl-names = "default";
status = "okay";

qspi_flash: qspi-nor-flash@90000000 {
compatible = "st,stm32-qspi-nor";
reg = <0x90000000 DT_SIZE_M(16)>;
qspi-max-frequency = <80000000>;
size = <DT_SIZE_M(16) * 8>;
spi-bus-width = <4>;
status = "okay";
};
};
```

### Configuration Setup

Added to the `arduino_giga_r1_stm32h747xx_m7.conf`:

```kconfig
CONFIG_SPI_STM32_QSPI=y
CONFIG_SPI_NOR=y
CONFIG_SPI_NOR_SFDP_DEVICETREE=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_FLASH_PAGE_LAYOUT=y
```

No changes were needed to the llext.
101 changes: 101 additions & 0 deletions libraries/QSPI/examples/BasicQSPI.ino
Original file line number Diff line number Diff line change
@@ -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 <QSPI.h>

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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,23 @@
};


&quadspi {
pinctrl-0 = <&quadspi_clk_pf10 &quadspi_bk1_ncs_pg6
&quadspi_bk1_io0_pd11 &quadspi_bk1_io1_pd12
&quadspi_bk1_io2_pe2 &quadspi_bk1_io3_pf6>;
pinctrl-names = "default";
status = "okay";

qspi_flash: qspi-nor-flash@0 {
compatible = "st,stm32-qspi-nor";
reg = <0>;
size = <DT_SIZE_M(128)>; /* 128 Mbits (16 MB) */
qspi-max-frequency = <72000000>;
spi-bus-width = <4>;
status = "okay";
};
};

&flash0 {
partitions {
user_sketch: partition@e0000 {
Expand Down
Loading