When working on embedded projects, do you often encounter situations where you need to store some data? For example, storing configuration parameters or logging information. In such cases, the W25QXX series of Flash memory comes in handy.
Today, we will delve into an open-source W25QXX driver library and see how the experts write code.
1. Introduction to W25QXX Chips
1. Basic Introduction
The W25QXX series ranges from the smallest W25Q80 (1MB) to the largest W25Q02 (256MB), covering various storage needs in embedded systems.
These chips use a serial interface to communicate with the main control chip via SPI or QSPI buses. Compared to parallel Flash, the serial interface occupies fewer pins and simplifies PCB routing, making it particularly suitable for microcontroller systems with limited pin resources.
The W25QXX is widely used in embedded systems:
- Code Storage: Stores application program code, supporting XIP mode for direct execution
- Data Logging: Stores configuration parameters, log files, etc.
- Firmware Upgrade: Serves as a cache for OTA upgrades
- File System: Works with a file system to store multimedia files
2. Core Features
The W25QXX series has several notable features:
Flexible Interface: Supports standard SPI (1-line), dual-line SPI (2-line), and quad-line QSPI (4-line) interfaces. In QSPI mode, read speeds can reach 80MHz, which is quite impressive.
Low Power Design: Operating current as low as 4mA, with only 1μA in standby mode.
Rich Capacity: Ranges from 1MB to 256MB, meeting various application scenarios.
Long Lifespan: Supports 100,000 erase cycles, with data retention of over 20 years.
2. LibDriver W25QXX
LibDriver W25QXX is a full-featured driver launched by LibDriver, providing functions such as Flash reading and writing, and it complies with MISRA standards.
https://gitee.com/libdriver/w25qxx

-
/src directory contains the source files for LibDriver W25QXX.
-
/interface directory contains platform-independent SPI or QSPI bus templates for LibDriver W25QXX.
-
/test directory contains the driver test program for LibDriver W25QXX, which can simply test the essential functions of the chip.
-
/example directory contains programming examples for LibDriver W25QXX.
-
/doc directory contains offline documentation for LibDriver W25QXX.
-
/datasheet directory contains the W25QXX datasheet.
-
/project directory contains project examples for common Linux and microcontroller development boards. All projects use shell scripts for debugging, and detailed information can be found in each project’s README.md.
-
/misra directory contains the MISRA code scanning results for LibDriver.
Online documentation:
https://www.libdriver.com/docs/w25qxx/index.html

3. Driver Architecture Analysis

This open-source driver adopts a layered design, divided from top to bottom into:
-
Application Layer: User’s specific application code
-
Example Layer: Provides both basic and advanced versions of the API
-
Driver Core Layer: Implements all Flash operation functions
-
Interface Layer: Abstracts hardware interfaces for easier portability
4. Source Code Breakdown
1. Core Data Structures
First, let’s take a look at the core data structures of the driver:
typedef enum
{
W25Q80 = 0XEF13U, /**< w25q80 */
W25Q16 = 0XEF14U, /**< w25q16 */
W25Q32 = 0XEF15U, /**< w25q32 */
W25Q64 = 0XEF16U, /**< w25q64 */
W25Q128 = 0XEF17U, /**< w25q128 */
W25Q256 = 0XEF18U, /**< w25q256 */
W25Q512 = 0XEF19U, /**< w25q512 */
W25Q1024 = 0XEF20U, /**< w25q01 */
W25Q2048 = 0XEF21U, /**< w25q02 */
} w25qxx_type_t;
This enumeration defines all supported chip models, with each value corresponding to the chip’s ID. The benefit of this approach is strong code readability, and the compiler performs type checking.
2. Implementation of the Read Function

The implementation logic of the read function:
uint8_t w25qxx_read(w25qxx_handle_t *handle, uint32_t addr, uint8_t *data, uint32_t len)
{
uint8_t res;
uint8_t buf[6];
if (handle == NULL)
{
return2;
}
if (handle->inited != 1)
{
return3;
}
if (handle->spi_qspi == W25QXX_INTERFACE_SPI)
{
// SPI interface processing logic
if (handle->address_mode == W25QXX_ADDRESS_MODE_3_BYTE)
{
// 3-byte address mode
res = a_w25qxx_qspi_write_read(handle, W25QXX_COMMAND_FAST_READ, 1,
addr, 1, 3,
0x00000000, 0x00, 0x00,
8, NULL, 0x00,
data, len, 1);
}
else
{
// 4-byte address mode
// Similar processing...
}
}
// ... Other interface processing
}
The read interface selects different processing flows based on the interface type and address mode, with a unified underlying communication interface for easier portability.
3. Intelligent Processing of the Write Function
The write function is much more complex than the read function because of the Flash’s characteristics that require erasing before writing:
uint8_t w25qxx_write(w25qxx_handle_t *handle, uint32_t addr, uint8_t *data, uint32_t len)
{
uint8_t res;
uint32_t sec_pos;
uint32_t sec_off;
uint32_t sec_remain;
uint32_t i;
sec_pos = addr / 4096; // Calculate sector position
sec_off = addr % 4096; // Calculate offset within the sector
sec_remain = 4096 - sec_off; // Calculate remaining space in the sector
while(1)
{
res = a_w25qxx_read(handle, sec_pos * 4096, handle->buf_4k, 4096); // Read the entire sector
// Check if erasure is needed
for (i = 0; i< sec_remain; i++)
{
if (handle->buf_4k[sec_off + i] != 0xFF)
{
break; // Found non-0xFF data, needs erasure
}
}
if (i < sec_remain) // Needs erasure
{
res = a_w25qxx_erase_sector(handle, sec_pos * 4096); // Erase sector
// Restore original data and write new data
for (i = 0; i<sec_remain; i++)
{
handle->buf_4k[i + sec_off] = data[i];
}
res = a_w25qxx_write_no_check(handle, sec_pos * 4096, handle->buf_4k, 4096);
}
else
{
// Write directly
res = a_w25qxx_write_no_check(handle, addr, data, sec_remain);
}
// ... Handle cross-sector writing
}
}
Flowchart:

- Intelligent Erasure: Only erase sectors when necessary to avoid unnecessary erase/write operations
- Data Protection: Read the entire sector before erasing to ensure no other data is corrupted
- Cross-Sector Handling: Automatically handle writing operations that span across sectors
4. Interface Abstraction Layer Design
The portability of the driver is mainly achieved through the interface abstraction layer:
typedef struct w25qxx_handle_s
{
uint8_t (*spi_qspi_init)(void);
uint8_t (*spi_qspi_deinit)(void);
uint8_t (*spi_qspi_write_read)(uint8_t instruction, uint8_t instruction_line,
uint32_t address, uint8_t address_line, uint8_t address_len,
uint32_t alternate, uint8_t alternate_line, uint8_t alternate_len,
uint8_t dummy, uint8_t *in_buf, uint32_t in_len,
uint8_t *out_buf, uint32_t out_len, uint8_t data_line);
void (*delay_ms)(uint32_t ms);
void (*delay_us)(uint32_t us);
void (*debug_print)(const char *const fmt, ...);
// ... Other members
} w25qxx_handle_t;
By using function pointers, specific hardware operations are abstracted. Users only need to implement these interface functions to use this driver on any platform.
5. Conclusion
If your project requires Flash storage, you might want to try this driver library, as it will significantly enhance your development efficiency!

END
Author:LinuxZn
Source:Embedded MiscellaneousCopyright belongs to the original author. If there is any infringement, please contact for removal..▍Recommended ReadingWhy is C++ rarely used in microcontroller development?Xiaomi is really stingy; one MCU actually handles all functionsUploaded a PCB photo with only 2 lines of silk screen, and GPT-5 helped me solve everything!→ Follow for more updates ←