QSPI Flash Memory Module
This module provides access to QSPI flash memory on the Daisy platform. QSPI flash is used for storing large data like wavetables, samples, and presets.
Features:
- Two operation modes: Memory-Mapped and Indirect Polling
- Read, Write, and Erase operations
- Support for IS25LP080D and IS25LP064A flash chips
- Sector/block erase operations (4KB, 32KB, 64KB)
- Page write operations (256 bytes)
Memory Modes:
- Memory-Mapped Mode: QSPI memory is directly accessible starting at address 0x90000000. Reading is fast but writing is not possible. Use this for code execution from QSPI or fast data access.
- Indirect Polling Mode: Full driver access with read/write/erase operations. Required for writing data to flash.
Flash Chip Sizes:
- IS25LP080D: 8 Mbit (1 MB)
- IS25LP064A: 64 Mbit (8 MB)
Erase Granularity:
- Sector: 4KB (smallest erasable unit)
- Block (32K): 32KB
- Block (64K): 64KB
- Important: Must erase before writing!
Usage Example (Indirect Polling):
import nimphea/per/qspi var qspi: QSPIHandle var config = QSPIConfig() # Configure pins (Daisy Seed defaults) config.device = QSPIDevice.IS25LP064A config.mode = QSPIMode.INDIRECT_POLLING # Initialize if qspi.init(config) == QSPIResult.OK: # Erase sector at address 0 if qspi.eraseSector(0) == QSPIResult.OK: # Write data var data: array[256, uint8] = [1'u8, 2, 3, ...] if qspi.writePage(0, 256, data[0].addr) == QSPIResult.OK: echo "Written successfully"
Usage Example (Memory-Mapped):
import nimphea/per/qspi var qspi: QSPIHandle var config = QSPIConfig() config.device = QSPIDevice.IS25LP064A config.mode = QSPIMode.MEMORY_MAPPED if qspi.init(config) == QSPIResult.OK: # Access memory directly (read-only) let dataPtr = cast[ptr UncheckedArray[uint8]](qspi.getData()) echo "First byte: ", dataPtr[0]
Types
QSPIHandle {.importcpp: "daisy::QSPIHandle", byref.} = object
- QSPI flash memory interface Source Edit
QSPIResult {.importcpp: "daisy::QSPIHandle::Result", size: 4.} = enum OK = "daisy::QSPIHandle::Result::OK", ERR = "daisy::QSPIHandle::Result::ERR"
- Source Edit
QSPIStatus {.importcpp: "daisy::QSPIHandle::Status", pure.} = enum GOOD = "daisy::QSPIHandle::Status::GOOD", E_HAL_ERROR = "daisy::QSPIHandle::Status::E_HAL_ERROR", E_SWITCHING_MODES = "daisy::QSPIHandle::Status::E_SWITCHING_MODES", E_INVALID_MODE = "daisy::QSPIHandle::Status::E_INVALID_MODE"
- Source Edit
Consts
QSPI_BLOCK_32K_SIZE = 32768'u32
- 32KB block size Source Edit
QSPI_BLOCK_64K_SIZE = 65536'u32
- 64KB block size Source Edit
QSPI_MEMORY_MAPPED_BASE = 0x90000000'u32
- Memory-mapped base address Source Edit
QSPI_PAGE_SIZE = 256'u32
- 256 byte page size Source Edit
QSPI_SECTOR_SIZE = 4096'u32
- 4KB sector size Source Edit
Procs
proc alignToPage(address: uint32): uint32 {.inline, ...raises: [], tags: [], forbids: [].}
-
Align an address down to the nearest page boundary (256 bytes).
Example:
let aligned = alignToPage(300) # Returns 256
Source Edit proc alignToSector(address: uint32): uint32 {.inline, ...raises: [], tags: [], forbids: [].}
-
Align an address down to the nearest sector boundary (4KB).
Example:
let aligned = alignToSector(4200) # Returns 4096
Source Edit proc deInit(qspi: var QSPIHandle): QSPIResult {.importcpp: "#.DeInit()", cdecl, ...raises: [], tags: [], forbids: [].}
-
Deinitialize the QSPI peripheral.
Call this before reinitializing QSPI in a different mode.
Returns:
- QSPIResult.OK on success
- QSPIResult.ERR on failure
proc erase(qspi: var QSPIHandle; startAddr: uint32; endAddr: uint32): QSPIResult {. importcpp: "#.Erase(@)", cdecl, ...raises: [], tags: [], forbids: [].}
-
Erase a region of QSPI flash.
Erases will happen in 4KB, 32KB, or 64KB increments depending on the size and alignment of the region.
Important:
- Erasing sets all bits to 1 (0xFF)
- Only works in INDIRECT_POLLING mode
- Minimum erase size is 4KB (one sector)
- Addresses will be aligned to sector boundaries
Parameters:
- startAddr: Start address of region to erase
- endAddr: End address of region to erase
Returns:
- QSPIResult.OK on success
- QSPIResult.ERR on failure or invalid mode
Example:
# Erase first 64KB if qspi.erase(0, 65536) != QSPIResult.OK: echo "Erase failed"
Source Edit proc eraseSector(qspi: var QSPIHandle; address: uint32): QSPIResult {. importcpp: "#.EraseSector(@)", cdecl, ...raises: [], tags: [], forbids: [].}
-
Erase a single 4KB sector.
This is the smallest erasable unit on IS25LP* chips.
Important:
- Erases 4KB starting at the given address
- Address will be aligned to 4KB boundary
- Only works in INDIRECT_POLLING mode
Parameters:
- address: Address within the sector to erase
Returns:
- QSPIResult.OK on success
- QSPIResult.ERR on failure or invalid mode
Example:
# Erase sector 0 (addresses 0-4095) if qspi.eraseSector(0) != QSPIResult.OK: echo "Erase failed"
Source Edit proc getConfig(qspi: QSPIHandle): QSPIConfig {.importcpp: "#.GetConfig()", cdecl, ...raises: [], tags: [], forbids: [].}
-
Get the current QSPI configuration.
Returns: Current QSPIConfig
Source Edit proc getData(qspi: var QSPIHandle; offset: uint32 = 0): pointer {. importcpp: "#.GetData(@)", cdecl, ...raises: [], tags: [], forbids: [].}
-
Get a pointer to QSPI flash memory.
In MEMORY_MAPPED mode, returns a pointer to 0x90000000 + offset. Memory is read-only in this mode.
In INDIRECT_POLLING mode, can be used to verify written data.
Parameters:
- offset: Offset from start of flash memory (default: 0)
Returns: Pointer to flash memory
Example:
# Read data from flash let ptr = cast[ptr UncheckedArray[uint8]](qspi.getData()) for i in 0..<16: echo "Byte ", i, ": ", ptr[i]
Source Edit proc getStatus(qspi: var QSPIHandle): QSPIStatus {.importcpp: "#.GetStatus()", cdecl, ...raises: [], tags: [], forbids: [].}
-
Get the current status of the QSPI module.
Useful for debugging errors.
Returns:
- QSPIStatus.GOOD: No errors
- QSPIStatus.E_HAL_ERROR: Hardware abstraction layer error
- QSPIStatus.E_SWITCHING_MODES: Error switching modes
- QSPIStatus.E_INVALID_MODE: Invalid operation for current mode
proc init(qspi: var QSPIHandle; config: QSPIConfig): QSPIResult {. importcpp: "#.Init(@)", cdecl, ...raises: [], tags: [], forbids: [].}
-
Initialize QSPI peripheral and prepare memory for access.
This will:
- Configure the QSPI peripheral
- Reset the flash chip
- Prepare for memory access
Parameters:
- config: Configuration with device type and mode
Returns:
- QSPIResult.OK on success
- QSPIResult.ERR on failure
Example:
var qspi: QSPIHandle var config = QSPIConfig( device: QSPIDevice.IS25LP064A, mode: QSPIMode.INDIRECT_POLLING ) if qspi.init(config) != QSPIResult.OK: echo "QSPI init failed"
Source Edit proc isPageAligned(address: uint32): bool {.inline, ...raises: [], tags: [], forbids: [].}
- Check if an address is page-aligned (256 byte boundary). Source Edit
proc isSectorAligned(address: uint32): bool {.inline, ...raises: [], tags: [], forbids: [].}
- Check if an address is sector-aligned (4KB boundary). Source Edit
proc sectorCount(startAddr, endAddr: uint32): uint32 {.inline, ...raises: [], tags: [], forbids: [].}
-
Calculate number of sectors between two addresses.
Example:
let sectors = sectorCount(0, 8192) # Returns 2 (8KB = 2 sectors)
Source Edit proc write(qspi: var QSPIHandle; address: uint32; size: uint32; buffer: ptr uint8): QSPIResult {.importcpp: "#.Write(@)", cdecl, ...raises: [], tags: [], forbids: [].}
-
Write data to QSPI flash (multiple pages).
This will automatically handle page boundary crossing.
Important:
- Must erase sectors before writing
- Only works in INDIRECT_POLLING mode
- Slower than writePage for single-page writes
Parameters:
- address: Flash address to start writing
- size: Number of bytes to write
- buffer: Pointer to data buffer
Returns:
- QSPIResult.OK on success
- QSPIResult.ERR on failure or invalid mode
Example:
var data: array[1024, uint8] # Fill data... if qspi.write(0, 1024, data[0].addr) != QSPIResult.OK: echo "Write failed"
Source Edit proc writePage(qspi: var QSPIHandle; address: uint32; size: uint32; buffer: ptr uint8): QSPIResult {.importcpp: "#.WritePage(@)", cdecl, ...raises: [], tags: [], forbids: [].}
-
Write a single page to QSPI flash.
Important:
- Page size is 256 bytes for IS25LP* chips
- Must erase sector before writing
- Only works in INDIRECT_POLLING mode
- Writing to unaligned addresses will fail
Parameters:
- address: Flash address to write to (must be page-aligned)
- size: Number of bytes to write (max 256)
- buffer: Pointer to data buffer
Returns:
- QSPIResult.OK on success
- QSPIResult.ERR on failure or invalid mode
Example:
var data: array[256, uint8] for i in 0..<256: data[i] = uint8(i) if qspi.writePage(0, 256, data[0].addr) != QSPIResult.OK: echo "Write failed"
Source Edit