[特殊字符] SPI 实战:与 Flash 芯片通信,把数据真正“存起来”

💾 SPI 实战:与 Flash 芯片通信,把数据真正“存起来”

“数据能算出来不算本事,能断电还在,才叫工程。”
——嵌入式工程师的尊严时刻


🧠 一、为什么要学 SPI + Flash?

很多新手做项目时会遇到一个现实问题:

  • MCU 掉电后,数据全没了 ❌
  • 想保存参数、日志、历史数据怎么办?

这时候你就会遇到非易失性存储器,而 SPI Flash 是性价比最高、最常见的一种。

它几乎是嵌入式项目的“标配外挂存储”。


在这里插入图片描述

🧩 二、SPI Flash 是什么?

SPI Flash 本质上是一个通过 SPI 总线访问的存储芯片。

常见型号包括:

型号容量厂商
W25Q162MBWinbond
W25Q648MBWinbond
GD25Q324MBGigaDevice
MX25L648MBMacronix

特点:

  • 掉电数据不丢失
  • 通过 SPI 读写
  • 支持页写入、扇区擦除
  • 常用于:参数存储、日志、字库、固件升级(OTA)

🔌 三、SPI Flash 硬件连接(以 W25Q64 为例)

Flash 引脚MCU说明
CSSPI_NSS片选,低有效
CLKSPI_SCK时钟
MOSISPI_MOSI主机发数据
MISOSPI_MISO从机回数据
VCC3.3V大多数 Flash 不耐 5V
GNDGND共地

⚠️ 注意:

  • SPI Flash 一般不耐 5V
  • CS 引脚一定要独立控制

⚙️ 四、CubeMX 配置 SPI(推荐)

  1. 选择 SPI1 → Full-Duplex Master

  2. NSS 选择 GPIO 控制

  3. Clock Polarity / Phase:

    • Mode 0(CPOL=0, CPHA=0)最常见
  4. Data Size:8-bit

  5. BaudRate Prescaler:先选慢一点(如 8 / 16)

CubeMX 会生成:

HAL_SPI_Init(&hspi1);

📜 五、SPI Flash 通信协议核心概念

常用指令表(必须记住)

功能指令
读 JEDEC ID0x9F
写使能0x06
读状态寄存器0x05
扇区擦除(4KB)0x20
页编程(写)0x02
读取数据0x03

SPI Flash 是指令驱动型设备

不发指令,它什么都不干。


🧪 六、基础通信:读取 Flash ID

这是验证 SPI 是否连通的第一步。

uint32_t SPI_Flash_ReadID(void)
{
    uint8_t cmd = 0x9F;
    uint8_t id[3] = {0};

    FLASH_CS_LOW();
    HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
    HAL_SPI_Receive(&hspi1, id, 3, 100);
    FLASH_CS_HIGH();

    return (id[0] << 16) | (id[1] << 8) | id[2];
}

如果你读到类似:

EF 40 17

恭喜你,Flash 已经“开口说话”了。


✍️ 七、写 Flash 的正确流程(非常重要)

❗ 写 Flash ≠ 直接写!

正确流程是:

写使能 → 擦除 → 等待完成 → 页写 → 等待完成

1️⃣ 写使能

void SPI_Flash_WriteEnable(void)
{
    uint8_t cmd = 0x06;

    FLASH_CS_LOW();
    HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
    FLASH_CS_HIGH();
}

2️⃣ 等待 Flash 空闲

void SPI_Flash_WaitBusy(void)
{
    uint8_t cmd = 0x05;
    uint8_t status;

    do {
        FLASH_CS_LOW();
        HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
        HAL_SPI_Receive(&hspi1, &status, 1, 100);
        FLASH_CS_HIGH();
    } while (status & 0x01); // BUSY 位
}

3️⃣ 扇区擦除(4KB)

void SPI_Flash_EraseSector(uint32_t addr)
{
    uint8_t cmd[4] = {
        0x20,
        (addr >> 16) & 0xFF,
        (addr >> 8) & 0xFF,
        addr & 0xFF
    };

    SPI_Flash_WriteEnable();

    FLASH_CS_LOW();
    HAL_SPI_Transmit(&hspi1, cmd, 4, 100);
    FLASH_CS_HIGH();

    SPI_Flash_WaitBusy();
}

4️⃣ 页写(最多 256 字节)

void SPI_Flash_PageWrite(uint32_t addr, uint8_t *buf, uint16_t len)
{
    uint8_t cmd[4] = {
        0x02,
        (addr >> 16) & 0xFF,
        (addr >> 8) & 0xFF,
        addr & 0xFF
    };

    SPI_Flash_WriteEnable();

    FLASH_CS_LOW();
    HAL_SPI_Transmit(&hspi1, cmd, 4, 100);
    HAL_SPI_Transmit(&hspi1, buf, len, 100);
    FLASH_CS_HIGH();

    SPI_Flash_WaitBusy();
}

⚠️ 注意:

  • len <= 256
  • 不能跨页写(否则数据会回绕)

📖 八、读取 Flash 数据

void SPI_Flash_Read(uint32_t addr, uint8_t *buf, uint16_t len)
{
    uint8_t cmd[4] = {
        0x03,
        (addr >> 16) & 0xFF,
        (addr >> 8) & 0xFF,
        addr & 0xFF
    };

    FLASH_CS_LOW();
    HAL_SPI_Transmit(&hspi1, cmd, 4, 100);
    HAL_SPI_Receive(&hspi1, buf, len, 100);
    FLASH_CS_HIGH();
}

🧪 九、完整测试:写入 + 读出验证

uint8_t tx_data[] = "Hello SPI Flash!";
uint8_t rx_data[32];

SPI_Flash_EraseSector(0x000000);
SPI_Flash_PageWrite(0x000000, tx_data, sizeof(tx_data));
SPI_Flash_Read(0x000000, rx_data, sizeof(tx_data));

如果串口打印出的 rx_data 正确,
说明你已经掌握了 SPI Flash 的基本驱动!


🧯 十、常见踩坑合集

问题原因
写不进去忘了写使能
读全是 0xFF没擦除
写完立刻读不对没等 BUSY 清零
写跨页数据错乱超过 256 字节
SPI 通信异常模式不对(CPOL/CPHA)

🧠 十一、工程级建议(进阶)

  • 抽象 Flash 驱动接口(flash_read/write
  • 做参数区 / 日志区地址规划
  • 增加 CRC 校验防止数据损坏
  • 使用环形日志结构
  • 为 OTA 升级预留空间

✅ 十二、总结

SPI Flash 是嵌入式项目里非常关键的一环
它让 MCU 具备了:

  • 掉电保存能力
  • 参数记忆能力
  • 数据记录能力

学会 SPI Flash,你的项目就不再是“断电即失忆”。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欢乐熊嵌入式编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值