当前位置: 首页 > news >正文

SPI协议软件实现 W25QXX flash 存储器

SPI 协议知识点

  • SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
  • 四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)
  • 同步,全双工
  • 支持总线挂载多设备(一主多从)
    在这里插入图片描述
  • 所有SPI设备的SCK、MOSI、MISO分别连在一起
  • 主机另外引出多条SS控制线,分别接到各从机的SS引脚
  • 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
    在这里插入图片描述在这里插入图片描述
    通讯过程:
  1. 主设备拉低目标从设备的 CS 引脚。
  2. 主设备开始输出 SCLK 时钟信号。
  3. 主设备通过 MOSI 发送数据,同时从设备可以通过 MISO 发送数据(全双工)。
  4. 数据按位在时钟边沿传输,常见有四种模式。
  5. 主设备通信完毕后,拉高 CS 引脚,结束传输。

优点:

  • 简单、高速(常见速度几十 Mbps)
  • 全双工通信
  • 支持多个从设备(每个一个 CS)

缺点:

  • 每个从设备需要一个独立 CS 引脚,不适合大规模设备扩展
  • 无协议层,软件必须自己处理数据完整性
  • 通信距离短,一般用于板级通信

✅ 七、典型应用

  • SD 卡模块
  • Flash 存储芯片(如 W25Qxx)
  • OLED 屏幕
  • 各类传感器(如加速度计)

SPI 时序基本单元

  • 起始条件:SS从高电平切换到低电平
  • 终止条件:SS从低电平切换到高电平
    在这里插入图片描述
SPI 的时序由两个参数控制:
  • CPOL(时钟极性):SCLK 空闲时是高电平还是低电平。
  • CPHA(时钟相位):在哪个边沿采样数据(第一个还是第二个边沿)。
模式CPOLCPHA描述
Mode 000空闲低电平,第一上升沿采样
Mode 101空闲低电平,第二下降沿采样
Mode 210空闲高电平,第一下降沿采样
Mode 311空闲高电平,第二上升沿采样

主从必须使用相同的模式才能正常通信。

  • 交换一个字节(模式0)最多使用
    • CPOL=0:空闲状态时,SCK为低电平
    • CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
      在这里插入图片描述
  • 交换一个字节(模式1)
    • CPOL=0:空闲状态时,SCK为低电平
    • CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
      在这里插入图片描述
  • 交换一个字节(模式2)
    • CPOL=1:空闲状态时,SCK为高电平
    • CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
      在这里插入图片描述
  • 交换一个字节(模式3)
    • CPOL=1:空闲状态时,SCK为高电平
    • CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
      在这里插入图片描述

W25QXX flash 存储器

W25QXX 是 Winbond 公司生产的一系列串行 NOR Flash 存储器芯片,广泛应用于嵌入式系统中,用于存储代码、配置、用户数据、图像、语音等信息


🧠 一、W25QXX 是什么?
  • 类型:串行 NOR Flash(非易失性存储器)

  • 接口:支持 SPI 协议(支持标准 SPI、Dual SPI、Quad SPI)

  • 容量范围

    • 常见型号有:W25Q16 (2MB)、W25Q32 (4MB)、W25Q64 (8MB)、W25Q128 (16MB)、W25Q256 (32MB)
  • 供电电压:2.7V ~ 3.6V(通常为 3.3V)


🧱 二、存储结构(以 W25Q64 为例)
单位大小数量说明
芯片总容量64Mbit = 8MB
块(Block)64KB128 个可擦除单元
扇区(Sector)4KB2048 个最小可擦除单元
页(Page)256 字节32,768 页写入数据的基本单位

❗ 注意:写入单位是页(Page),擦除单位是扇区(Sector)或块(Block)


⚙️ 三、基本功能命令
功能指令码(Hex)描述
写使能0x06必须先发送,才可写入/擦除
写禁止0x04禁止后续写入/擦除操作
读数据0x03读取 Flash 数据
快速读0x0B加上 dummy byte,提高读取速度
页写入0x02写入 1~256 字节数据
扇区擦除(4KB)0x20擦除一个扇区
块擦除(32K/64K)0x52/0xD8擦除大范围数据
全片擦除0xC7/0x60擦除整个 Flash
读状态寄存器0x05检查 BUSY、写保护位等
写状态寄存器0x01配置状态寄存器位
JEDEC ID0x9F读取芯片厂商和型号信息

🔁 四、使用流程(典型写入)
  1. 写使能(0x06)
  2. 擦除扇区(0x20 + 地址)
  3. 写使能(0x06)
  4. 页编程(0x02 + 地址 + 数据)
  5. 轮询状态寄存器(0x05),等待 BUSY 清除

📌 五、特性总结
特性说明
速度最高可达 104MHz(SPI),可选 Quad SPI 模式
擦除寿命每个扇区支持约 10 万次擦写
掉电保存非易失性,断电数据仍然保留
尺寸小常用 SOP-8、WSON、USON 等封装
广泛使用常见于 STM32、ESP32、FPGA、WiFi 模块、路由器等产品中

🔍 六、常见应用场景
  • 存储系统启动代码(Bootloader)
  • 存储图像、字库、音频文件(适合大容量 W25Q128、256)
  • 数据记录与配置参数存储
  • 替代 EEPROM,提供更大容量和更快速度

🧪 七、识别芯片(JEDEC ID)

你可以通过发送 0x9F 命令读取芯片 ID,例如:

  • W25Q64 的返回值是:

    0xEF 0x40 0x17
    
    • 0xEF: Winbond 厂商 ID
    • 0x40: 存储类型
    • 0x17: 容量(0x17 表示 64Mbit = 8MB)

利用 SPI 写W25QXX 示例代码

  • 写一个字节 / 读取一个字节
  • 写使能
  • 扇区擦除
  • 写入数据
  • 读取数据
  • 分文件结构清晰

🗂️ 文件结构说明
W25QXX_Driver/
├── w25qxx.h             // 头文件
├── w25qxx.c             // 实现文件
├── soft_spi.h           // 软件 SPI 接口头文件
├── soft_spi.c           // 软件 SPI 实现

soft_spi.h:软件 SPI 接口头文件
#ifndef __SOFT_SPI_H__
#define __SOFT_SPI_H__#include <stdint.h>void MySPI_Init(void);
void MySPI_CS_L(void);
void MySPI_CS_H(void);
void MySPI_W_MOSI(uint8_t bit);
void MySPI_W_SCK(uint8_t bit);
uint8_t MySPI_R_MISO(void);
uint8_t MySPI_SwapByte(uint8_t byte_send);#endif

soft_spi.c:软件 SPI 实现(需你根据硬件定义填 GPIO 操作)
#include "soft_spi.h"
#include <your_gpio_driver.h>  // 替换为实际的GPIO操作头文件void MySPI_Init(void) {// 初始化 GPIO 方向// 设置 SCK, MOSI 为输出,MISO 为输入
}void MySPI_CS_L(void) {// 拉低 CS
}void MySPI_CS_H(void) {// 拉高 CS
}void MySPI_W_MOSI(uint8_t bit) {// 设置 MOSI 引脚电平
}void MySPI_W_SCK(uint8_t bit) {// 设置 SCK 引脚电平
}uint8_t MySPI_R_MISO(void) {// 读取 MISO 电平return 0; // 实际应返回 MISO 电平
}uint8_t MySPI_SwapByte(uint8_t byte_send) {uint8_t i, byte_recv = 0x00;for (i = 0; i < 8; i++) {MySPI_W_MOSI((byte_send & 0x80) != 0);byte_send <<= 1;MySPI_W_SCK(1);  // 上升沿采样if (MySPI_R_MISO()) {byte_recv |= (0x80 >> i);}MySPI_W_SCK(0);  // 下降沿传输}return byte_recv;
}

w25qxx.h:W25QXX 驱动接口
#ifndef __W25QXX_H__
#define __W25QXX_H__#include <stdint.h>void W25QXX_Init(void);
void W25QXX_WriteEnable(void);
void W25QXX_WaitBusy(void);
void W25QXX_EraseSector(uint32_t addr);
void W25QXX_WriteByte(uint32_t addr, uint8_t data);
uint8_t W25QXX_ReadByte(uint32_t addr);#endif

w25qxx.c:W25QXX 驱动实现
#include "w25qxx.h"
#include "soft_spi.h"void W25QXX_Init(void) {MySPI_Init();MySPI_CS_H();  // 默认拉高 CS
}void W25QXX_WriteEnable(void) {MySPI_CS_L();MySPI_SwapByte(0x06);  // Write EnableMySPI_CS_H();
}void W25QXX_WaitBusy(void) {uint8_t status;do {MySPI_CS_L();MySPI_SwapByte(0x05);        // Read Status Registerstatus = MySPI_SwapByte(0xFF);MySPI_CS_H();} while (status & 0x01);         // 等待 BUSY 清除
}void W25QXX_EraseSector(uint32_t addr) {W25QXX_WriteEnable();MySPI_CS_L();MySPI_SwapByte(0x20);            // Sector EraseMySPI_SwapByte((addr >> 16) & 0xFF);MySPI_SwapByte((addr >> 8) & 0xFF);MySPI_SwapByte(addr & 0xFF);MySPI_CS_H();W25QXX_WaitBusy();
}void W25QXX_WriteByte(uint32_t addr, uint8_t data) {W25QXX_WriteEnable();MySPI_CS_L();MySPI_SwapByte(0x02);            // Page ProgramMySPI_SwapByte((addr >> 16) & 0xFF);MySPI_SwapByte((addr >> 8) & 0xFF);MySPI_SwapByte(addr & 0xFF);MySPI_SwapByte(data);MySPI_CS_H();W25QXX_WaitBusy();
}uint8_t W25QXX_ReadByte(uint32_t addr) {uint8_t data;MySPI_CS_L();MySPI_SwapByte(0x03);            // Read DataMySPI_SwapByte((addr >> 16) & 0xFF);MySPI_SwapByte((addr >> 8) & 0xFF);MySPI_SwapByte(addr & 0xFF);data = MySPI_SwapByte(0xFF);MySPI_CS_H();return data;
}

✅ 示例用法(main.c)
#include "w25qxx.h"int main(void) {W25QXX_Init();// 擦除地址 0x000000 所在扇区W25QXX_EraseSector(0x000000);// 写入一个字节 0xABW25QXX_WriteByte(0x000000, 0xAB);// 读取验证uint8_t data = W25QXX_ReadByte(0x000000);if (data == 0xAB) {// 写入成功}while (1);
}

效果

我遇到的问题:我的协议和通讯都写好了,但是呢一直对W25QXX的读写特性不了解,导致一直无法进行写和读操作,经过一顿排错以后才知道,如果要进行写,那么一定进行写使能,把要写的区域进行擦除,然后在进行写使能,然后再去写目标地址,最后能读!

Flash 操作注意事项 坑 :
写入操作时:

  • 写入操作前,必须先进行写使能
  • 每个数据位只能由1改写为0,不能由0改写为1
  • 写入数据前必须先擦除,擦除后,所有数据位变为1
  • 擦除必须按最小擦除单元进行
  • 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
  • 写入操作结束后,芯片进入忙状态,不响应新的读写操作
    读取操作时:
  • 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

代码仓库
发送 0xAB数据
然后读取它
在这里插入图片描述
在这里插入图片描述

相关文章:

  • HJ20 密码验证合格程序【牛客网】
  • 创建你的第一个Agent Society(CAMEL )
  • 解锁 YOLOv8 新潜能:EfficientViT 主干网络的优化实践与实验数据解读
  • 省赛备考~全国青少年信息素养大赛-图形化编程复赛/省赛-模拟题-自动行驶
  • 视频监控管理平台EasyCVR工业与公共安全监控:监控中心与防爆系统如何集成?
  • Node.js 24发布:性能与安全双提升
  • iframe加载或者切换时候,短暂的白屏频闪问题解决
  • 开源Vue表单设计器FcDesigner中组件联动的配置教程
  • clock的时钟频率check代码
  • 关于FPGA 和 ASIC设计选择方向的讨论
  • 仅修改文件名会导致文件的MD5值发生变化吗?
  • 关于C++使用位运算交换变量值的分析
  • node.js如何实现双 Token + Cookie 存储 + 无感刷新机制
  • Frp Dockr Mysql内网映射
  • 乘法逆元:费马小定理(利用快速乘法幂)(JAVA)
  • JAVA批量发送邮件(含excel内容)
  • 在 Excel xll 自动注册操作 中使用东方仙盟软件2————仙盟创梦IDE
  • 算法打卡第三天
  • C#开发利器:SharpBoxesCore全解析
  • 49、c# 能⽤foreach 遍历访问的对象需满足什么条件?
  • 中国华能:1-4月新能源装机突破1亿千瓦,利润总额再创新高
  • 张永宁任福建宁德市委书记
  • 述评:赖清德当局上台一年恶行累累
  • 和平会谈两天后,俄对乌发动冲突爆发以来最大规模无人机袭击
  • 广东高州发生山体滑坡,造成2人遇难4人送医救治1人失联
  • 常州新型碳材料集群产值近二千亿,请看《浪尖周报》第24期