返回首页

SPI Flash 驱动学习笔记

概述

SPI Flash 是嵌入式系统中常用的外部存储设备,通过 SPI 接口连接,具有容量大、成本低、速度快等特点。

硬件连接

1. 典型连接

┌─────────────┐         ┌─────────────┐
│   MCU       │         │  SPI Flash   │
│             │         │              │
│  MOSI ──────┼────────►│ DI           │
│  MISO ◄─────┼─────────│ DO           │
│  SCK  ──────┼────────►│ CLK          │
│  CS   ──────┼────────►│ CS           │
│             │         │              │
└─────────────┘         └─────────────┘

2. 常用芯片

型号容量电压最大频率
W25Q808Mbit2.7-3.6V104MHz
W25Q1616Mbit2.7-3.6V104MHz
W25Q3232Mbit2.7-3.6V104MHz
W25Q6464Mbit2.7-3.6V104MHz
W25Q128128Mbit2.7-3.6V104MHz

Zephyr SPI API

1. 设备配置

#include <zephyr/drivers/spi.h>

/* SPI 设备配置 */
#define SPI_FLASH_NODE DT_INST(0, jedec_spi_nor)

/* 获取 SPI 设备 */
const struct device *spi_dev = DEVICE_DT_GET(DT_BUS(SPI_FLASH_NODE));

/* SPI 配置 */
struct spi_config spi_cfg = {
    .frequency = DT_PROP(SPI_FLASH_NODE, spi_max_frequency),
    .operation = SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB |
                 SPI_WORD_SET(8),
    .cs = {
        .gpio = GPIO_DT_SPEC_GET(SPI_FLASH_NODE, cs_gpios),
    },
};

2. 基本读写

#include <zephyr/drivers/spi.h>

/* 读取数据 */
int spi_read(const struct device *dev, uint8_t *rx_buf, size_t len)
{
    struct spi_buf rx_buf_struct = {
        .buf = rx_buf,
        .len = len,
    };
    
    struct spi_buf_set rx_bufs = {
        .buffers = &rx_buf_struct,
        .count = 1,
    };
    
    return spi_transceive(dev, NULL, &rx_bufs);
}

/* 写入数据 */
int spi_write(const struct device *dev, const uint8_t *tx_buf, size_t len)
{
    struct spi_buf tx_buf_struct = {
        .buf = (void *)tx_buf,
        .len = len,
    };
    
    struct spi_buf_set tx_bufs = {
        .buffers = &tx_buf_struct,
        .count = 1,
    };
    
    return spi_transceive(dev, &tx_bufs, NULL);
}

3. 完整传输

/* 同时读写 */
int spi_transfer(const struct device *dev, 
                const uint8_t *tx_buf, uint8_t *rx_buf, size_t len)
{
    struct spi_buf tx_buf_struct = {
        .buf = (void *)tx_buf,
        .len = len,
    };
    
    struct spi_buf rx_buf_struct = {
        .buf = rx_buf,
        .len = len,
    };
    
    struct spi_buf_set bufs = {
        .buffers = {&tx_buf_struct, &rx_buf_struct},
        .count = 2,
    };
    
    return spi_transceive(dev, &bufs, &bufs);
}

SPI Flash 命令

1. 命令集

命令字节描述
READ0x03读取数据
FAST_READ0x0B快速读取
READ_STATUS0x05读取状态寄存器
WRITE_ENABLE0x06写使能
WRITE_DISABLE0x04写禁止
PAGE_PROGRAM0x02页编程
SECTOR_ERASE0x20扇区擦除
CHIP_ERASE0xC7芯片擦除
JEDEC_ID0x9F读取 JEDEC ID

2. 状态寄存器

Bit 7: SRP0 - Status Register Protect 0
Bit 6: SEC - Sector/Block Protect
Bit 5: TB - Top/Bottom Protect
Bit 4: BP2 - Block Protect
Bit 3: BP1 - Block Protect
Bit 0: BP0 - Block Protect
Bit 2: WEL - Write Enable Latch
Bit 1: BUSY - Erase/Write in Progress

3. 命令实现

#include <zephyr/drivers/spi.h>

/* JEDEC ID */
#define CMD_JEDEC_ID    0x9F
#define CMD_READ_STATUS 0x05
#define CMD_WRITE_ENABLE 0x06
#define CMD_READ_DATA    0x03
#define CMD_PAGE_PROGRAM 0x02
#define CMD_SECTOR_ERASE 0x20
#define CMD_CHIP_ERASE   0xC7

/* 读取 JEDEC ID */
int flash_read_jedec_id(const struct device *dev, uint8_t *id)
{
    uint8_t tx_buf[4] = {CMD_JEDEC_ID, 0, 0, 0};
    uint8_t rx_buf[4];
    
    int err = spi_transceive(dev, tx_buf, 4, rx_buf, 4);
    if (err < 0) {
        return err;
    }
    
    id[0] = rx_buf[1];  // Manufacturer
    id[1] = rx_buf[2];  // Device ID Part 1
    id[2] = rx_buf[3];  // Device ID Part 2
    
    return 0;
}

/* 读取状态寄存器 */
int flash_read_status(const struct device *dev, uint8_t *status)
{
    uint8_t tx_buf[2] = {CMD_READ_STATUS, 0x00};
    uint8_t rx_buf[2];
    
    int err = spi_transceive(dev, tx_buf, 2, rx_buf, 2);
    if (err < 0) {
        return err;
    }
    
    *status = rx_buf[1];
    return 0;
}

/* 等待空闲 */
int flash_wait_busy(const struct device *dev, uint32_t timeout)
{
    uint8_t status;
    uint32_t start = k_uptime_get_32();
    
    while ((k_uptime_get_32() - start) < timeout) {
        flash_read_status(dev, &status);
        if (!(status & 0x01)) {  // BUSY bit
            return 0;
        }
        k_msleep(1);
    }
    
    return -ETIMEDOUT;
}

/* 写使能 */
int flash_write_enable(const struct device *dev)
{
    uint8_t cmd = CMD_WRITE_ENABLE;
    return spi_write(dev, &cmd, 1);
}

4. 数据读写

/* 读取数据 */
int flash_read_data(const struct device *dev, uint32_t addr, 
                   uint8_t *buf, size_t len)
{
    uint8_t header[4] = {
        CMD_READ_DATA,
        (addr >> 16) & 0xFF,
        (addr >> 8) & 0xFF,
        addr & 0xFF
    };
    
    /* 先发送命令 */
    int err = spi_write(dev, header, 4);
    if (err < 0) {
        return err;
    }
    
    /* 再读取数据 */
    return spi_read(dev, buf, len);
}

/* 页编程(写入) */
int flash_page_program(const struct device *dev, uint32_t addr,
                      const uint8_t *buf, size_t len)
{
    /* 写使能 */
    flash_write_enable(dev);
    
    /* 发送命令和地址 */
    uint8_t header[4] = {
        CMD_PAGE_PROGRAM,
        (addr >> 16) & 0xFF,
        (addr >> 8) & 0xFF,
        addr & 0xFF
    };
    
    /* 发送数据 */
    struct spi_buf tx_bufs[2] = {
        { .buf = header, .len = 4 },
        { .buf = (void *)buf, .len = len }
    };
    
    struct spi_buf_set tx_buf_set = {
        .buffers = tx_bufs,
        .count = 2
    };
    
    int err = spi_transceive(dev, &tx_buf_set, NULL);
    if (err < 0) {
        return err;
    }
    
    /* 等待写入完成 */
    return flash_wait_busy(dev, 500);
}

5. 擦除操作

/* 扇区擦除(4KB) */
int flash_sector_erase(const struct device *dev, uint32_t addr)
{
    /* 写使能 */
    flash_write_enable(dev);
    
    /* 发送擦除命令 */
    uint8_t cmd[4] = {
        CMD_SECTOR_ERASE,
        (addr >> 16) & 0xFF,
        (addr >> 8) & 0xFF,
        addr & 0xFF
    };
    
    int err = spi_write(dev, cmd, 4);
    if (err < 0) {
        return err;
    }
    
    /* 等待擦除完成 */
    return flash_wait_busy(dev, 500);
}

/* 芯片擦除 */
int flash_chip_erase(const struct device *dev)
{
    /* 写使能 */
    flash_write_enable(dev);
    
    uint8_t cmd = CMD_CHIP_ERASE;
    int err = spi_write(dev, &cmd, 1);
    if (err < 0) {
        return err;
    }
    
    /* 等待擦除完成(可能需要较长时间) */
    return flash_wait_busy(dev, 60000);
}

使用 DMA

1. 配置 DMA

# 是否能走 DMA 取决于 SoC 的 SPI 控制器和驱动实现
CONFIG_SPI=y
CONFIG_DMA=y

2. DMA 传输

/* DMA 读取大块数据 */
int flash_dma_read(const struct device *dev, uint32_t addr,
                  uint8_t *buf, size_t len)
{
    uint8_t header[4] = {
        CMD_READ_DATA,
        (addr >> 16) & 0xFF,
        (addr >> 8) & 0xFF,
        addr & 0xFF
    };
    
    struct spi_buf tx_bufs[] = {
        { .buf = header, .len = 4 },
    };
    
    struct spi_buf rx_bufs[] = {
        { .buf = NULL, .len = 4 },  // 丢弃头
        { .buf = buf, .len = len }
    };
    
    struct spi_buf_set tx = { .buffers = tx_bufs, .count = 1 };
    struct spi_buf_set rx = { .buffers = rx_bufs, .count = 2 };
    
    /* 实际是否走 DMA,由 SPI 控制器驱动和缓冲区条件决定 */
    return spi_transceive(dev, &tx, &rx);
}

完整示例

1. Flash 驱动封装

#include <zephyr/kernel.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/device.h>
#include <string.h>

/* Flash 设备结构 */
struct flash_device {
    const struct device *spi;
    struct spi_config config;
    uint32_t size;
    uint32_t page_size;
    uint32_t sector_size;
};

/* 全局 Flash 设备 */
static struct flash_device flash;

/* 初始化 Flash */
int flash_init(void)
{
    /* 获取 SPI 设备 */
    flash.spi = DEVICE_DT_GET(DT_BUS(DT_INST(0, jedec_spi_nor)));
    
    if (!device_is_ready(flash.spi)) {
        printk("SPI device not ready\n");
        return -ENODEV;
    }
    
    /* 获取设备参数 */
    flash.size = DT_INST_PROP(0, size) / 8;
    flash.page_size = DT_INST_PROP(0, page_size);
    flash.sector_size = DT_INST_PROP(0, sector_size);
    
    /* 配置 SPI */
    flash.config.frequency = DT_INST_PROP(0, spi_max_frequency);
    flash.config.operation = SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB |
                             SPI_WORD_SET(8);
    
    printk("SPI Flash: %u MB, page: %u, sector: %u\n",
           flash.size / 1024 / 1024,
           flash.page_size,
           flash.sector_size);
    
    return 0;
}

/* 读取数据 */
int flash_read(uint32_t addr, uint8_t *buf, size_t len)
{
    return flash_read_data(flash.spi, addr, buf, len);
}

/* 写入数据 */
int flash_write(uint32_t addr, const uint8_t *buf, size_t len)
{
    size_t written = 0;
    
    while (written < len) {
        size_t chunk = flash.page_size - (addr % flash.page_size);
        chunk = MIN(chunk, len - written);
        
        int err = flash_page_program(flash.spi, addr + written, 
                                     buf + written, chunk);
        if (err < 0) {
            return err;
        }
        
        written += chunk;
    }
    
    return 0;
}

/* 擦除扇区 */
int flash_erase(uint32_t addr, size_t len)
{
    size_t erased = 0;
    
    while (erased < len) {
        /* 对齐到扇区边界 */
        uint32_t sector_addr = (addr + erased) & ~(flash.sector_size - 1);
        
        int err = flash_sector_erase(flash.spi, sector_addr);
        if (err < 0) {
            return err;
        }
        
        erased += flash.sector_size;
    }
    
    return 0;
}

2. 文件系统集成

/* 使用 SPI Flash 作为 LittleFS 存储 */
#include <zephyr/fs/littlefs.h>
#include <zephyr/drivers/spi.h>

/* Flash 块设备实现 */
static int flash_lfs_read(const struct lfs_config *c, lfs_block_t block,
                         lfs_off_t off, void *buffer, lfs_size_t size)
{
    uint32_t addr = block * c->block_size + off;
    return flash_read(addr, buffer, size);
}

static int flash_lfs_prog(const struct lfs_config *c, lfs_block_t block,
                         lfs_off_t off, const void *buffer, lfs_size_t size)
{
    uint32_t addr = block * c->block_size + off;
    return flash_write(addr, buffer, size);
}

static int flash_lfs_erase(const struct lfs_config *c, lfs_block_t block)
{
    uint32_t addr = block * c->block_size;
    return flash_erase(addr, c->block_size);
}

static int flash_lfs_sync(const struct lfs_config *c)
{
    return 0;
}

/* LittleFS 配置 */
static struct lfs_config fs_config = {
    .context = &flash,
    .read = flash_lfs_read,
    .prog = flash_lfs_prog,
    .erase = flash_lfs_erase,
    .sync = flash_lfs_sync,
    
    .read_size = 256,
    .prog_size = 256,
    .block_size = 4096,
    .block_count = 2048,  // 8MB Flash
    .cache_size = 256,
    .lookahead_size = 32,
};

性能优化

1. 双倍速模式

/* 启用双倍速 SPI */
#define CMD_QUAD_READ    0x6B
#define CMD_QUAD_ENABLE  0x02

2. 内存映射读取

/* 将 Flash 映射到内存地址空间,直接读取 */
#define CMD_READ_MODE   0xFF

/* 进入内存映射模式后可直接读取 */
uint8_t *flash_memory_map = (uint8_t *)0x90000000;
uint8_t data = flash_memory_map[addr];

调试

1. 常用命令

# 读取 JEDEC ID
uart:~$ flash jedec

# 读取数据
uart:~$ flash read 0x1000 256

# 擦除扇区
uart:~$ flash erase 0x1000

# 写入数据
uart:~$ flash write 0x1000 <data>

2. 常见问题

问题原因解决方法
读取失败CS 引脚配置错误检查 GPIO 配置
写入失败未执行写使能调用 write_enable
擦除失败正在忙碌等待 busy 位清除
速度慢频率太低提高 SPI 频率

nRF Connect SDK

1. nRF QSPI

/* nRF54L15 QSPI 配置 */
#include <zephyr/drivers/flash.h>
#include <zephyr/drivers/flash/nor.h>

/* QSPI 配置 */
const struct flash_parameters flash_params = {
    .write_block_size = 1,
    .erase_block_size = 4096,
};

/* 使用外部 Flash */
static const struct device *flash_dev = DEVICE_DT_GET(DT_INST(0, nordic_qspi));

2. JEDEC 设备树

/* device tree */
&spi1 {
    status = "okay";
    cs-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
    
    flash@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;
        spi-max-frequency = <104000000>;
        size = <0x2000000>;
        page-size = <256>;
        sector-size = <4096>;
        jedec-id = <0xef 0x40 0x17>;
    };
};

参考资料


学习日期: 2026-03-21 笔记编号: #57 作者: 小白 🤖