Comprehensive Driver for W25QXX Suitable for General MCUs and Linux Systems

Follow our official account to keep the embedded knowledge flowing!

When working on embedded projects, do you often encounter situations where you need to store some data?

For example, storing configuration parameters or logging data. At this point, the W25QXX series of Flash memory comes into play. Today, we will delve into an open-source W25QXX driver library to see how experts write code.

Introduction to W25QXX Chips

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:

  1. Code Storage: Stores application program code, supports XIP mode for direct execution
  2. Data Logging: Stores configuration parameters, log files, etc.
  3. Firmware Upgrade: Serves as a cache for OTA upgrades
  4. File System: Works with file systems to store multimedia files

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/write cycles, with data retention of over 20 years.

LibDriver W25QXX

LibDriver W25QXX is a comprehensive driver launched by LibDriver, providing functions such as Flash reading and writing, and it complies with MISRA standards.

https://gitee.com/libdriver/w25qxx

Comprehensive Driver for W25QXX Suitable for General MCUs and Linux Systems
  • /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 content 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

Comprehensive Driver for W25QXX Suitable for General MCUs and Linux Systems

Driver Architecture Analysis

Comprehensive Driver for W25QXX Suitable for General MCUs and Linux Systems

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

Source Code Breakdown

Core Data Structures

First, let’s 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 advantage of this approach is strong code readability, and the compiler performs type checking.

Implementation of Read Function

Comprehensive Driver for W25QXX Suitable for General MCUs and Linux Systems

Logic for implementing 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.

Smart Processing of Write Function

The write function is much more complex than the read function because Flash requires 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
        {
            // Directly write
            res = a_w25qxx_write_no_check(handle, addr, data, sec_remain);
        }
        // ... Handle cross-sector writing
    }
}

Flowchart:

Comprehensive Driver for W25QXX Suitable for General MCUs and Linux Systems
  1. Smart Erasure: Erase sectors only when necessary to avoid unnecessary erase/write operations
  2. Data Protection: Read the entire sector before erasing to ensure no other data is corrupted
  3. Cross-Sector Handling: Automatically handle writing operations that span across sectors

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.

Conclusion

If your project requires Flash storage, consider trying this driver library. It will significantly enhance your development efficiency!

Recommended for You:

Data-Driven Programming: Make Your Embedded Code More Elegant!

Lightweight Communication Protocols for Embedded Systems!

Lightweight Circular Buffer Management Library for Embedded Systems!

Leave a Comment