Configuring and Using QUADSPI on STM32H7 for External Serial NOR Flash
QSPI essentials
Quad-SPI (QSPI) is an SPI extension that boosts throughput by transferring data over up to four data pins in parallel. STM32 MCUs expose this as the QUADSPI peripheral. Copmared to classic SPI:
- Pins: CS/NSS, SCK, IO0, IO1, IO2, IO3 (six lines total).
- Command/address phases are typically sent over one line; payload can be sent over 1/2/4 lines.
- External memories like serial NOR/NAND flash and PSRAM commonly support QSPI.
SPI snapshot across STM32 generations
- STM32F1 SPI
- Full-duplex 3‑wire, simplex 2/3‑wire
- 8‑ or 16‑bit frames, master/slave
- HW/SW SS management, CRC, DMA, interrupts
- Prescaler up to PCLK/2
- STM32F4 SPI
- All F1 features + optional TI frame format
- RX/TX FIFOs, HAL driver coverage
- STM32F7 SPI
- All F4 features
- 4–16‑bit configurable data size
- 32‑bit TX/RX FIFO with DMA
- STM32H7 SPI
- All F7 features
- 4–32‑bit frame size
- Separate kernel clock domain, higher achievable SCK
- Programmable delays (SS to first bit, inter-frame), SS polarity, MISO/MOSI swap
- RX sampling point control, Stop/low-power wakeup, programmable FIFO thresholds and transfer sizes
- Larger FIFOs (up to 16×8‑bit)
- Slave underflow behavier configurable
QUADSPI on STM32F7/H7
QUADSPI adds hardware support for common serial flash workflows:
- Modes of operation
- Indirect: CPU issues commands and moves data via registers/FIFO
- Auto‑polling: hardware repeatedly reads a status register and stops on match/mask
- Memory‑mapped: read-only window of external flash on the AHB bus (e.g., 0x90000000), enabling XIP-style fetch or memcpy reads
- Features
- Single/dual/quad I/O, SDR and DDR
- Optional dual‑flash (two devices in parallel) for doubled bus width/capacity
- Integrated RX/TX FIFO, DMA/MDMA support
- 8/16/32‑bit data accesses in indirect mode
Hardware-wise, the QUADSPI FIFO and registers sit on the AHB bus. In dual‑flash, one or two CS lines can be used depending on package/pinout.
Typical HAL flow for QUADSPI indirect transfers
- Configure pins and clocks (GPIO AF, QUADSPI kernel clock)
- Build a QSPI_CommandTypeDef with instruction/address/data modes
- HAL_QSPI_Command()
- HAL_QSPI_Transmit()/HAL_QSPI_Receive() or DMA variants
- Optionally HAL_QSPI_AutoPolling() to wait for BUSY/WEL bits
- Repeat for next operation
Reference driver: Winbond W25Q64 (8 MBytes) on STM32H7
Below is a compact, refactored HAL-based driver for a W25Q64 device. It demonstrates indirect read/write/erase, status polling, and memory-mapped fast read. Naming and structure differ from common examples, but the sequence mirrors the device datasheet.
qspi_w25x.h
#ifndef QSPI_W25X_H
#define QSPI_W25X_H
#include "stm32h7xx_hal.h"
#ifdef __cplusplus
extern "C" {
#endif
/* JEDEC and geometry */
#define W25_TOTAL_SIZE_BYTES (0x800000U) /* 8 MBytes */
#define W25_PAGE_SIZE_BYTES (256U)
#define W25_JEDEC_ID_EXPECTED (0xEF4017U) /* W25Q64 */
/* Memory-mapped base (H7 default mapping) */
#define W25_MEM_BASE (0x90000000UL)
/* Command set */
#define W25_CMD_RESET_EN 0x66
#define W25_CMD_RESET_DEV 0x99
#define W25_CMD_READ_JEDEC_ID 0x9F
#define W25_CMD_WRITE_ENABLE 0x06
#define W25_CMD_READ_SR1 0x05
#define W25_SR1_BUSY 0x01
#define W25_SR1_WEL 0x02
#define W25_CMD_SECTOR_ERASE_4K 0x20
#define W25_CMD_BLOCK_ERASE_32K 0x52
#define W25_CMD_BLOCK_ERASE_64K 0xD8
#define W25_CMD_CHIP_ERASE 0xC7
#define W25_CMD_PP_QUAD 0x32 /* 1-1-4 program */
#define W25_CMD_FAST_READ_QIO 0xEB /* 1-4-4 fast read */
/* Return codes */
typedef enum {
QSPI_W25X_OK = 0,
QSPI_W25X_ERR_INIT = -1,
QSPI_W25X_ERR_TXRX = -2,
QSPI_W25X_ERR_POLL = -3,
QSPI_W25X_ERR_ERASE = -4,
QSPI_W25X_ERR_MAP = -5,
QSPI_W25X_ERR_WREN = -6,
} qspi_w25x_status_t;
/* Public API */
qspi_w25x_status_t qspi_w25x_init(void);
qspi_w25x_status_t qspi_w25x_reset(void);
uint32_t qspi_w25x_read_jedec_id(void);
qspi_w25x_status_t qspi_w25x_read(uint8_t *dst, uint32_t addr, uint32_t len);
qspi_w25x_status_t qspi_w25x_write(const uint8_t *src, uint32_t addr, uint32_t len);
qspi_w25x_status_t qspi_w25x_erase_sector_4k(uint32_t addr);
qspi_w25x_status_t qspi_w25x_erase_block_32k(uint32_t addr);
qspi_w25x_status_t qspi_w25x_erase_block_64k(uint32_t addr);
qspi_w25x_status_t qspi_w25x_erase_chip(void);
qspi_w25x_status_t qspi_w25x_enter_memmap(void); /* read-only window at W25_MEM_BASE */
/* Export QSPI handle for BSP integration if needed */
extern QSPI_HandleTypeDef g_qspi;
#ifdef __cplusplus
}
#endif
#endif /* QSPI_W25X_H */
qspi_w25x.c
#include "qspi_w25x.h"
QSPI_HandleTypeDef g_qspi;
/* --- Low-level MSP — adjust pins for your board --- */
static void qspi_gpio_clock_enable(void)
{
__HAL_RCC_QSPI_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
}
void HAL_QSPI_MspInit(QSPI_HandleTypeDef *hqspi)
{
GPIO_InitTypeDef io = {0};
if (hqspi->Instance != QUADSPI) return;
qspi_gpio_clock_enable();
/* Example mapping:
PB2 -> QSPI_CLK (AF9)
PB6 -> QSPI_BK1_NCS (AF10)
PD11 -> QSPI_BK1_IO0 (AF9)
PE2 -> QSPI_BK1_IO1 (AF9)
PD12 -> QSPI_BK1_IO2 (AF9)
PD13 -> QSPI_BK1_IO3 (AF9)
*/
io.Mode = GPIO_MODE_AF_PP;
io.Pull = GPIO_NOPULL;
io.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
io.Alternate = GPIO_AF9_QUADSPI; io.Pin = GPIO_PIN_2; HAL_GPIO_Init(GPIOB, &io); /* CLK */
io.Alternate = GPIO_AF10_QUADSPI;io.Pin = GPIO_PIN_6; HAL_GPIO_Init(GPIOB, &io); /* NCS */
io.Alternate = GPIO_AF9_QUADSPI; io.Pin = GPIO_PIN_11; HAL_GPIO_Init(GPIOD, &io); /* IO0 */
io.Alternate = GPIO_AF9_QUADSPI; io.Pin = GPIO_PIN_12; HAL_GPIO_Init(GPIOD, &io); /* IO2 */
io.Alternate = GPIO_AF9_QUADSPI; io.Pin = GPIO_PIN_13; HAL_GPIO_Init(GPIOD, &io); /* IO3 */
io.Alternate = GPIO_AF9_QUADSPI; io.Pin = GPIO_PIN_2; HAL_GPIO_Init(GPIOE, &io); /* IO1 */
}
/* Core init: choose a kernel clock in CubeMX/clock tree (e.g., PLL2), then set prescaler here.
* Note: in memory-mapped mode prescaler must not be 0 (driver clock must be valid). */
static qspi_w25x_status_t qspi_core_init(void)
{
g_qspi.Instance = QUADSPI;
g_qspi.Init.ClockPrescaler = 1; /* f_qspi = f_kernel / (Prescaler+1) */
g_qspi.Init.FifoThreshold = 32;
g_qspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
g_qspi.Init.FlashSize = 22; /* 2^(FSIZE+1) bytes => 8 MBytes => FSIZE=22 */
g_qspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_1_CYCLE;
g_qspi.Init.ClockMode = QSPI_CLOCK_MODE_3; /* Many Winbond parts accept Mode 0 or 3 */
g_qspi.Init.FlashID = QSPI_FLASH_ID_1;
g_qspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
if (HAL_QSPI_Init(&g_qspi) != HAL_OK) return QSPI_W25X_ERR_INIT;
return QSPI_W25X_OK;
}
static qspi_w25x_status_t qspi_wait_ready(void)
{
QSPI_CommandTypeDef cmd = {0};
QSPI_AutoPollingTypeDef ap = {0};
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25_CMD_READ_SR1;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_1_LINE;
cmd.DummyCycles = 0;
ap.Match = 0x00;
ap.Mask = W25_SR1_BUSY;
ap.MatchMode = QSPI_MATCH_MODE_AND;
ap.StatusBytesSize = 1;
ap.Interval = 0x10;
ap.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
if (HAL_QSPI_AutoPolling(&g_qspi, &cmd, &ap, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return QSPI_W25X_ERR_POLL;
return QSPI_W25X_OK;
}
static qspi_w25x_status_t qspi_write_enable(void)
{
QSPI_CommandTypeDef cmd = {0};
QSPI_AutoPollingTypeDef ap = {0};
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25_CMD_WRITE_ENABLE;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_NONE;
if (HAL_QSPI_Command(&g_qspi, &cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return QSPI_W25X_ERR_WREN;
/* poll WEL */
cmd.Instruction = W25_CMD_READ_SR1;
cmd.DataMode = QSPI_DATA_1_LINE;
ap.Match = W25_SR1_WEL;
ap.Mask = W25_SR1_WEL;
ap.MatchMode = QSPI_MATCH_MODE_AND;
ap.StatusBytesSize = 1;
ap.Interval = 0x10;
ap.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
if (HAL_QSPI_AutoPolling(&g_qspi, &cmd, &ap, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return QSPI_W25X_ERR_POLL;
return QSPI_W25X_OK;
}
qspi_w25x_status_t qspi_w25x_reset(void)
{
QSPI_CommandTypeDef cmd = {0};
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.Instruction = W25_CMD_RESET_EN;
if (HAL_QSPI_Command(&g_qspi, &cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return QSPI_W25X_ERR_INIT;
if (qspi_wait_ready() != QSPI_W25X_OK) return QSPI_W25X_ERR_POLL;
cmd.Instruction = W25_CMD_RESET_DEV;
if (HAL_QSPI_Command(&g_qspi, &cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return QSPI_W25X_ERR_INIT;
if (qspi_wait_ready() != QSPI_W25X_OK) return QSPI_W25X_ERR_POLL;
return QSPI_W25X_OK;
}
uint32_t qspi_w25x_read_jedec_id(void)
{
QSPI_CommandTypeDef cmd = {0};
uint8_t id[3] = {0};
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25_CMD_READ_JEDEC_ID;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_1_LINE;
cmd.NbData = 3;
if (HAL_QSPI_Command(&g_qspi, &cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return 0;
if (HAL_QSPI_Receive(&g_qspi, id, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return 0;
return ((uint32_t)id[0] << 16) | ((uint32_t)id[1] << 8) | id[2];
}
static qspi_w25x_status_t qspi_page_program_quad(const uint8_t *src, uint32_t addr, uint32_t len)
{
QSPI_CommandTypeDef cmd = {0};
if (len == 0 || len > W25_PAGE_SIZE_BYTES) return QSPI_W25X_OK;
if (qspi_write_enable() != QSPI_W25X_OK) return QSPI_W25X_ERR_WREN;
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25_CMD_PP_QUAD; /* 1-1-4 */
cmd.AddressMode = QSPI_ADDRESS_1_LINE;
cmd.AddressSize = QSPI_ADDRESS_24_BITS;
cmd.Address = addr;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_4_LINES;
cmd.NbData = len;
if (HAL_QSPI_Command(&g_qspi, &cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return QSPI_W25X_ERR_TXRX;
if (HAL_QSPI_Transmit(&g_qspi, (uint8_t *)src, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return QSPI_W25X_ERR_TXRX;
return qspi_wait_ready();
}
qspi_w25x_status_t qspi_w25x_read(uint8_t *dst, uint32_t addr, uint32_t len)
{
if (len == 0) return QSPI_W25X_OK;
QSPI_CommandTypeDef cmd = {0};
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1-4-4 */
cmd.Instruction = W25_CMD_FAST_READ_QIO;
cmd.AddressMode = QSPI_ADDRESS_4_LINES;
cmd.AddressSize = QSPI_ADDRESS_24_BITS;
cmd.Address = addr;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_4_LINES;
cmd.DummyCycles = 6;
cmd.NbData = len;
if (HAL_QSPI_Command(&g_qspi, &cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return QSPI_W25X_ERR_TXRX;
if (HAL_QSPI_Receive(&g_qspi, dst, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return QSPI_W25X_ERR_TXRX;
return qspi_wait_ready();
}
qspi_w25x_status_t qspi_w25x_write(const uint8_t *src, uint32_t addr, uint32_t len)
{
if (len == 0) return QSPI_W25X_OK;
uint32_t cur = addr;
uint32_t remaining = len;
const uint8_t *p = src;
while (remaining > 0)
{
uint32_t page_off = cur % W25_PAGE_SIZE_BYTES;
uint32_t space = W25_PAGE_SIZE_BYTES - page_off;
uint32_t chunk = (remaining < space) ? remaining : space;
qspi_w25x_status_t st = qspi_page_program_quad(p, cur, chunk);
if (st != QSPI_W25X_OK) return st;
cur += chunk;
p += chunk;
remaining -= chunk;
}
return QSPI_W25X_OK;
}
static qspi_w25x_status_t qspi_erase_common(uint8_t opcode, uint32_t addr)
{
QSPI_CommandTypeDef cmd = {0};
if (qspi_write_enable() != QSPI_W25X_OK) return QSPI_W25X_ERR_WREN;
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = opcode;
cmd.AddressMode = (opcode == W25_CMD_CHIP_ERASE) ? QSPI_ADDRESS_NONE : QSPI_ADDRESS_1_LINE;
cmd.AddressSize = QSPI_ADDRESS_24_BITS;
cmd.Address = addr;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_NONE;
if (HAL_QSPI_Command(&g_qspi, &cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
return QSPI_W25X_ERR_ERASE;
/* Long operations (especially chip erase) rely on status polling */
return qspi_wait_ready();
}
qspi_w25x_status_t qspi_w25x_erase_sector_4k(uint32_t addr)
{
return qspi_erase_common(W25_CMD_SECTOR_ERASE_4K, addr);
}
qspi_w25x_status_t qspi_w25x_erase_block_32k(uint32_t addr)
{
return qspi_erase_common(W25_CMD_BLOCK_ERASE_32K, addr);
}
qspi_w25x_status_t qspi_w25x_erase_block_64k(uint32_t addr)
{
return qspi_erase_common(W25_CMD_BLOCK_ERASE_64K, addr);
}
qspi_w25x_status_t qspi_w25x_erase_chip(void)
{
return qspi_erase_common(W25_CMD_CHIP_ERASE, 0);
}
qspi_w25x_status_t qspi_w25x_enter_memmap(void)
{
QSPI_CommandTypeDef cmd = {0};
QSPI_MemoryMappedTypeDef cfg = {0};
/* 1-4-4 fast read mapping */
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25_CMD_FAST_READ_QIO;
cmd.AddressMode = QSPI_ADDRESS_4_LINES;
cmd.AddressSize = QSPI_ADDRESS_24_BITS;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_4_LINES;
cmd.DummyCycles = 6;
cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
cfg.TimeOutPeriod = 0;
if (qspi_w25x_reset() != QSPI_W25X_OK) return QSPI_W25X_ERR_INIT;
if (HAL_QSPI_MemoryMapped(&g_qspi, &cmd, &cfg) != HAL_OK) return QSPI_W25X_ERR_MAP;
return QSPI_W25X_OK;
}
qspi_w25x_status_t qspi_w25x_init(void)
{
if (qspi_core_init() != QSPI_W25X_OK) return QSPI_W25X_ERR_INIT;
if (qspi_w25x_reset() != QSPI_W25X_OK) return QSPI_W25X_ERR_INIT;
uint32_t id = qspi_w25x_read_jedec_id();
if (id != W25_JEDEC_ID_EXPECTED) return QSPI_W25X_ERR_INIT;
return QSPI_W25X_OK;
}
Example: basic use in indirect mode
#include "qspi_w25x.h"
static uint8_t tx_buf[1024];
static uint8_t rx_buf[1024];
void qspi_demo(void)
{
if (qspi_w25x_init() != QSPI_W25X_OK) {
/* handle error */
return;
}
/* Erase, program, read back */
uint32_t test_addr = 0x00010000; /* aligned as needed */
qspi_w25x_erase_block_64k(test_addr);
for (size_t i = 0; i < sizeof(tx_buf); ++i) tx_buf[i] = (uint8_t)i;
qspi_w25x_write(tx_buf, test_addr, sizeof(tx_buf));
qspi_w25x_read(rx_buf, test_addr, sizeof(rx_buf));
}
Memory-mapped read window (read-only)
/***** one-time setup *****/
if (qspi_w25x_enter_memmap() == QSPI_W25X_OK) {
const uint8_t *flash = (const uint8_t *)(W25_MEM_BASE);
uint8_t buffer[256];
uint32_t offset = 0x00020000;
memcpy(buffer, flash + offset, sizeof(buffer));
/* process buffer */
}
Optional: MDMA integration for QUADSPI RX/TX
To reduce CPU overhead for long reads/writes, attach an MDMA channel to QUADSPI. Example setup:
static MDMA_HandleTypeDef g_mdma_qspi;
static void mdma_qspi_init(void)
{
__HAL_RCC_MDMA_CLK_ENABLE();
g_mdma_qspi.Instance = MDMA_Channel1;
g_mdma_qspi.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER;
g_mdma_qspi.Init.Priority = MDMA_PRIORITY_VERY_HIGH;
g_mdma_qspi.Init.Request = MDMA_REQUEST_QUADSPI_FIFO_TH; /* FIFO threshold */
g_mdma_qspi.Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE;
g_mdma_qspi.Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE;
g_mdma_qspi.Init.SourceInc = MDMA_SRC_INC_BYTE;
g_mdma_qspi.Init.SourceDataSize = MDMA_SRC_DATASIZE_BYTE;
g_mdma_qspi.Init.SourceBurst = MDMA_SOURCE_BURST_SINGLE;
g_mdma_qspi.Init.DestinationInc = MDMA_DEST_INC_DISABLE;
g_mdma_qspi.Init.DestDataSize = MDMA_DEST_DATASIZE_BYTE;
g_mdma_qspi.Init.DestBurst = MDMA_DEST_BURST_SINGLE;
__HAL_LINKDMA(&g_qspi, hmdma, g_mdma_qspi);
HAL_MDMA_Init(&g_mdma_qspi);
HAL_NVIC_SetPriority(MDMA_IRQn, 14, 0);
HAL_NVIC_EnableIRQ(MDMA_IRQn);
}
void HAL_QSPI_RxCpltCallback(QSPI_HandleTypeDef *hqspi)
{
/* RX complete flag or notify task */
}
/* In your read path, switch to HAL_QSPI_Receive_DMA() once the command is issued. */
The user-level API can remain unchanged if the internals select DMA for longer transfers, falling back to blocking for short bursts.
Quick reference: W25Qxx instruction codes
/* Protection and status */
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
#define W25X_ReadStatusReg1 0x05
#define W25X_ReadStatusReg2 0x35
#define W25X_ReadStatusReg3 0x15
#define W25X_WriteStatusReg1 0x01
#define W25X_WriteStatusReg2 0x31
#define W25X_WriteStatusReg3 0x11
/* Read */
#define W25X_ReadData 0x03
#define W25X_FastRead 0x0B
#define W25X_FastReadDual 0x3B
#define W25X_FastReadQuadIO 0xEB
/* Program */
#define W25X_PageProgram 0x02
#define W25X_QuadPageProgram 0x32
/* Erase */
#define W25X_SectorErase_4K 0x20
#define W25X_BlockErase_32K 0x52
#define W25X_BlockErase_64K 0xD8
#define W25X_ChipErase 0xC7
/* Power */
#define W25X_PowerDown 0xB9
#define W25X_ReleasePowerDown 0xAB
/* Identification */
#define W25X_DeviceID 0xAB
#define W25X_ManufactDeviceID 0x90
#define W25X_JEDECID 0x9F
/* Addressing mode */
#define W25X_Enable4ByteAddr 0xB7
#define W25X_Exit4ByteAddr 0xE9
Note: 4‑byte addressing is required for devices larger than 16 MBytes (e.g., W25Q256 and above). For 8 MByte devices like W25Q64, 24‑bit addressing is sufficient.