STM32 学习 —— 个人学习笔记11-1(SPI 通信协议及 W25Q64 简介 & 软件 SPI 读写 W25Q64)

声明

文中内容为观看 BiliBili 视频【STM32入门教程-2023版 细致讲解 中文字幕】后学习并扩展总结。

本文章为个人学习使用,版面观感若有不适请谅解,文中知识仅代表个人观点,若出现错误,欢迎各位批评指正。

一、SPI 通信协议

1.1 简介

SPI(Serial Peripheral Interface,串行外设接口) 是由 Motorola 公司于上世纪 80 年代推出的一种同步串行通信总线,主要用于微控制器与各类外围芯片之间的短距离、高速数据传输,广泛应用于 Flash、传感器、显示屏、ADC、DAC 等外设通信场景。

SPI 采用主从架构,通信过程由主机统一控制,标准硬件接口包含 4 根核心通信线:

  • SCK(Serial Clock): 串行时钟线,由主机产生并输出时钟信号,控制数据传输速率与同步节拍;
  • MOSI(Master Output Slave Input): 主机输出、从机输入数据线,负责主机向从机发送数据;
  • MISO(Master Input Slave Output): 主机输入、从机输出数据线,负责从机向主机回传数据;
  • SS(Slave Select,也常称 CS/Chip Select): 从机片选线,由主机拉低(通常低电平有效)选中对应从机,未被选中的从机处于高阻态,不参与通信。

该协议具备同步、全双工的通信特性: 同步体现在数据收发严格跟随 SCK 时钟同步进行,无需额外校验同步时序;全双工则支持通信双方在同一时钟周期内同时完成数据发送与接收,传输效率较高。

SPI 支持一主多从的总线拓扑结构,单主机可通过扩展多条独立 SS 线,分别挂载多个从设备,实现一对多通信;部分场景下也可通过菊花链方式连接从设备,简化硬件布线。

此外,SPI 无固定的通信速率上限,可根据主机与从机性能灵活配置时钟频率,传输速度远高于 I2C 等同步总线;但协议本身无内置应答机制、地址校验和流控机制,可靠性相对较弱,且仅支持短距离板级通信,同时多从机模式下会占用更多主机 IO 端口。

1.2 SPI 与 I2C 通信协议的异同

SPI 和 I2C 均为主从式同步串行通信总线 ,常用于 MCU 与外设的板级通信。SPI 追求高速全双工 ,硬件简单但占用 IO 较多;I2C 追求极简布线、多设备方便管理,速度相对更低。下面用表格清晰对比两者区别、特点与优势:

对比项 SPI 通信协议 I2C 通信协议
全称 Serial Peripheral Interface Inter Integrated Circuit
信号线 SCK、MOSI、MISO、CS SCL、SDA
通信方式 同步、全双工 同步、半双工
主从结构 一主多从 多主多从
设备选择 硬件片选 CS,无地址 软件地址寻址(7 / 10 位)
传输速率 高,可达几十 MHz 较低,标准 100k / 400k Hz
时钟控制 主机任意控制时钟 主机控制,有总线仲裁
应答机制 无内置应答 有 ACK / NACK 应答
总线占用 多从机需多个 CS 引脚 所有设备共用两根线
协议复杂度 协议简单、无流控 协议较完整,支持仲裁、流控
主要优势 速度快、全双工、实时性好 省 IO、布线简单、多设备易扩展
典型缺点 多从机占用 IO 多、无应答 速率受限、半双工、时序复杂
典型应用 Flash、屏幕、高速 ADC / DAC 传感器、RTC、小型 EEPROM
1.3 SPI 硬件电路

SPI 协议典型的一主多从硬件拓扑结构,核心遵循 "共享总线 + 独立片选" 的设计原则 。所有 SPI 从设备的 SCK(串行时钟)、MOSI(主机输出从机输入)、MISO(主机输入从机输出)引脚均并联连接,统一接入 SPI 主机的对应引脚:主机通过 SCK 向所有从机输出同步时钟,保障数据传输的时序对齐;MOSI 总线由主机向所有从机广播发送数据;MISO 总线则由各从机分时向主机回传数据,未被选中的从机需将 MISO 引脚置为高阻态,避免总线信号冲突。

为实现多从机的独立寻址 ,主机额外引出多条独立的 SS(Slave Select,从机片选)控制线,分别一对一连接至每个从机的 SS 引脚,通过拉低目标从机的 SS 引脚(通常低电平有效)完成从机选中,未被选中的从机保持 SS 高电平,不参与总线通信。

在引脚电气配置上,主机的 SCK、MOSI、SS 引脚,以及从机的 SCK、MOSI、SS 引脚需配置为推挽输出,以提供稳定的电平驱动能力,保障时钟、片选与发送数据的传输质量;主机的 MISO 输入引脚,以及从机的 MISO 引脚需配置为浮空输入或上拉输入,适配 MISO 总线的高阻态切换,提升总线抗干扰能力。

该电路结构充分发挥了 SPI 高速全双工、时序简单的优势,适用于 Flash、显示屏、高速 ADC 等对传输速率要求较高的板级外设通信场景,同时需注意从机数量受主机 IO 引脚资源限制,且需严格规避未选中从机的 MISO 总线冲突问题。

1.4 SPI 移位示意图

SPI 通信的核心本质是主从设备移位寄存器的同步全双工数据交换 ,整个过程由主机波特率发生器生成的 SCK 时钟严格驱动,以 8 位为基本传输单元,实现主从双方数据的双向同步流转。

初始状态下,主机移位寄存器预置待发送数据10101010,从机移位寄存器预置反馈数据01010101,双方移位寄存器均处于就绪状态。当 SCK 时钟开始驱动移位过程,第一个时钟边沿触发时,主机将移位寄存器的最高位 1 经 MOSI 线输出至从机,同时从机将自身移位寄存器的最高位 0 经 MISO 线输出至主机,主从双方同步完成 1 位数据的移位输出。

随着后续 7 个时钟周期依次推进,主从移位寄存器持续沿同一方向逐位右移:主机通过 MOSI 总线将10101010的剩余位依次发送至从机,从机则通过 MISO 总线将01010101的剩余位逐位回传至主机,每一个时钟周期均同步完成 "主机发位、从机收位" 与 "从机发位、主机收位" 的双向数据交互。

经过 8 个完整的时钟周期后,主从移位寄存器完成全部 8 位数据的交换:主机移位寄存器最终接收从机发送的01010101,从机移位寄存器则接收主机发送的10101010,本次字节级数据传输圆满结束。整个过程充分体现了 SPI 同步、全双工的核心特性,SCK 时钟作为统一 "节拍器" 确保主从设备数据传输时序严格对齐,移位寄存器的逐位交互则构建了高效的双向数据通道,可广泛应用于高速外设通信场景。

1.5 SPI 时序基本单元
  • (1)起始条件 & 终止条件
      起始条件:SS 从高电平 切换到低电平 。    终止条件:SS 从低电平 切换到高电平
  • (2)交换一个字节(模式 0)
      CPOL = 0:空闲状态时,SCK 为低电平 。    CPHA = 0:SCK 第一个边沿移入数据 ,第二个边沿移出数据
  • (3)交换一个字节(模式 1)
      CPOL = 0:空闲状态时,SCK 为低电平 。    CPHA = 1:SCK 第一个边沿移出数据 ,第二个边沿移入数据
  • (4)交换一个字节(模式 2)
      CPOL = 1:空闲状态时,SCK 为高电平 。    CPHA = 0:SCK 第一个边沿移入数据 ,第二个边沿移出数据
  • (5)交换一个字节(模式 3)
      CPOL = 1:空闲状态时,SCK 为高电平 。    CPHA = 1:SCK 第一个边沿移出数据 ,第二个边沿移入数据
1.6 SPI 时序样例
  • 发送指令
      向 SS 指定的设备,发送指令(0x06)。
  • 指定地址写
      向 SS 指定的设备,发送写指令(0x02),随后在指定地址(Address[23:0])下,写入指定数据(Data)。
  • 指定地址读
      向 SS 指定的设备,发送读指令(0x03),随后在指定地址(Address[23:0])下,读取从机数据(Data)。

二、串行外设接口闪存存储器

2.1 W25Q64 简介

W25Q64 是华邦电子(Winbond)W25Qxx 系列非易失性存储器的核心型号,该系列芯片以低成本、小型化、使用便捷为核心优势,采用 Nor Flash 闪存介质,支持多模式 SPI 通信,涵盖多种存储容量规格,W25Q64 自身存储容量为 64Mbit(8MByte),广泛应用于数据存储、字库存储、固件程序存储等场景,是嵌入式系统、消费电子、工业控制等领域的主流存储解决方案。

参数类别 具体内容
所述系列 W25Qxx系列(非易失性存储器,低成本、小型化、使用简单)
存储介质 Nor Flash(闪存),非易失性,断电数据不丢失,支持快速读取
时钟频率 标准SPI:80MHz;Dual SPI:160MHz;Quad SPI:320MHz
自身存储容量(24 位地址) 64Mbit / 8MByte
W25Qxx 系列全容量规格(24 位地址) W25Q40:4Mbit/512KByte W25Q80:8Mbit/1MByte W25Q16:16Mbit/2MByte W25Q32:32Mbit/4MByte W25Q64:64Mbit/8MByte W25Q128:128Mbit/16MByte W25Q256:256Mbit/32MByte
主要应用场景 数据存储、字库存储、固件程序存储,适配嵌入式系统、消费电子、工业控制等领域
2.2 W25Q64 硬件电路

W25Q64JV 是华邦电子推出的 64Mbit 串行外设接口(SPI)Flash 存储器,支持标准 SPI、双 / 四通道 SPI 通信,具备高存储密度、低功耗、非易失性等特性,广泛应用于嵌入式系统的程序存储、数据日志与参数固化场景。本电路基于 W25QXX 系列通用设计规范,完成了 W25Q64JV 的最小系统硬件实现,具体电路结构与原理如下:

  • 核心器件与引脚定义

    电路核心器件为 W25Q64JV(图中标注为 U1,型号 W25QXX),采用 SOP-8 封装,引脚功能与连接逻辑严格遵循器件 datasheet 规范:

    (1)电源引脚: 8 脚 VCC 为正电源输入,支持 2.7~3.6V 宽电压供电;4 脚 GND 为接地端,构成完整电源回路。

    (2)SPI 通信引脚:

    1 脚 /CS(片选,SS):SPI 从机使能信号,由主机拉低选中器件,拉高时器件进入待机状态;

    6 脚 CLK(时钟,SCK):SPI 串行时钟输入,由主机输出同步通信时序;

    5 脚 DI(数据输入,MOSI):SPI 主机输出、从机输入,用于主机向 Flash 发送指令与地址;

    2 脚 DO(数据输出,MISO):SPI 主机输入、从机输出,用于 Flash 向主机返回数据与状态。

    (3)控制引脚:

    3 脚 /WP(写保护):硬件写保护控制,电路中通过上拉至 VCC 禁用写保护,使能全擦除 / 编程操作;

    7 脚 /HOLD(保持):总线暂停控制,电路中通过上拉至 VCC 禁用保持功能,保证 SPI 总线持续通信。

    (4)外部接口: 通过 6Pin 排针 J1 实现与主控单元(如 STM32 单片机)的电气连接,引脚定义为:1-DI、2-CLK、3-GND、4-DO、5-CS、6-VCC,兼容标准 SPI 接口时序。

  • 外围电路设计与功能分析

    (1)电源去耦电路

    在 VCC 与 GND 之间并联 0.1μF(104)陶瓷电容 C1,作为电源去耦网络。该设计用于滤除电源线上的高频噪声与纹波,抑制 SPI 高速通信产生的电磁干扰,稳定器件供电电压,避免因电源波动导致的读写错误,符合高速数字电路的电源完整性设计要求。

    (2)状态指示电路

    电路集成了由限流电阻 R1(1kΩ)与发光二极管 D1 组成的电源指示回路:R1 串联在 VCC 与 D1 阳极之间,D1 阴极接地。当 VCC 正常供电时,D1 导通发光,直观指示 Flash 模块的上电状态;1kΩ 电阻用于限制 LED 工作电流(约 2~3mA),在保证指示亮度的同时降低功耗,延长器件寿命。

    (3)控制引脚处理

    /WP/HOLD引脚均为低电平有效,电路中通过直接上拉至 VCC 实现功能禁用:

    /WP上拉:关闭硬件写保护,允许主机执行擦除、编程等写操作,满足嵌入式系统的程序更新与数据存储需求;

    /HOLD上拉:关闭总线保持功能,确保 SPI 总线在通信过程中不会被意外暂停,保证数据传输的连续性与可靠性。

  • 电路设计特点与应用优势

    (1)兼容性与通用性: 电路基于 W25QXX 系列通用设计,可兼容 W25Q16、W25Q32、W25Q64、W25Q128 等全系列 SPI Flash 器件,硬件无需修改即可适配不同存储容量需求;

    (2)可靠性设计: 电源去耦、引脚上拉、状态指示等电路设计,全面覆盖了嵌入式存储系统的抗干扰、故障诊断需求,提升了硬件系统的稳定性与可维护性;

    (3)低功耗与高速性: W25Q64JV 支持 SPI 时钟最高 104MHz(四通道 QSPI 模式),电路设计满足高速通信的信号完整性要求,同时器件待机电流低至 1μA 以下,适配电池供电的低功耗嵌入式场景;

    (4)标准化接口: 采用标准 SPI 通信协议,可直接兼容 STM32、FPGA、MCU 等各类主控单元的硬件 SPI 外设,软件驱动复用性强,降低了系统开发成本。

引脚 功能
VCC、GND 电源(2.7~3.6V)
CS(SS) SPI 片选
CLK(SCK)) SPI 时钟
DI(MOSI) SPI 主机输出从机输入
DO(MISO) SPI 主机输入从机输出
WP 写保护
HOLD 数据保持
2.3 W25Q64 框图

W25Q64 是一款基于 SPI 接口的串行 Flash 存储器,其整体架构围绕分层存储阵列串行控制逻辑 设计,以实现高集成度、低功耗的非易失性数据存储。存储核心为容量 64Mbit(8MB)的 W25Q64BV 存储阵列,采用三级地址分层结构 :以 64KB 为单位划分为 128 个独立块(Block),每个块进一步划分为 4 个 4KB 扇区(Sector),最小可擦除单元为 4KB 扇区,最小编程单元为 256 字节页(Page),该分层结构兼顾了大容量存储与灵活的擦除 / 编程操作粒度。

存储阵列的访问由多级控制逻辑协同实现:SPI 命令与控制逻辑模块作为串行接口中枢,接收 CLK、/CS、DI(IO₀)、DO(IO₁)、/HOLD(IO₃)等 SPI 总线信号,完成命令解析、时序控制与数据收发;地址路径中,页地址锁存 / 计数器与字节地址锁存 / 计数器分别实现存储阵列的页级与字节级寻址,配合列译码逻辑与 256 字节页缓冲器,完成页编程与页读操作的高速数据缓冲;写保护逻辑与行列译码模块协同,实现存储阵列的硬件写保护(/WP 引脚)与访问权限管控,保障数据安全性;状态寄存器用于反馈器件忙闲状态、写保护状态等关键信息,实现主机与器件的状态交互;高压发生器模块为擦除、编程操作提供所需的高电压,状态寄存器则实时反馈器件操作状态,保障时序同步。

该架构通过 SPI 串行接口实现了引脚数量的精简,同时依托分层存储结构与专用缓冲、控制逻辑,在保证非易失性存储可靠性的同时,实现了高速串行数据传输与灵活的存储管理,广泛适用于嵌入式系统、物联网设备等场景的代码存储与数据存储需求。

2.4 Flash 操作注意事项

Flash芯片操作需严格遵循硬件时序及逻辑规则,确保数据读写准确与芯片稳定,读写操作具体要求如下表所示:

操作类型 具体操作要求
写入操作 (1)写入前需先进行写使能 (2)每个数据位仅可由 1 改写为 0,不可由 0 改写为 1 (3)写入前必须按最小擦除单元擦除目标区域,擦除后所有数据位置为 1 (4)连续多字节写入最多不超过一页容量,超出页尾的数据将回到页首覆盖 (5)写入结束后芯片进入忙状态,不响应新的读写操作
读取操作 (1)可直接调用读取时序,无需提前使能,无页容量限制 (2)读取结束后芯片不进入忙状态,但不可在芯片忙状态时发起读取操作

三、软件 SPI 读写 W25Q64

3.1 软件 SPI 读写 W25Q64 的实现
  • 首先,按下图接线方式,搭建面包板电路连接 OLED 显示屏,并将 W25Q64 的 CS、DO、CLK 和 DI 分别与 PA4、PA5、PA6 和 PA7 连接,然后将 DAP-Link / ST-Link 连接到 STM32 最小系统板上,为使 OLED 显示屏的 VCC 和 GND 正确连接正负极,请先连接对应正负极跳线(或直接使用 GPIO 口进行供电)。

  • 直接复制先前演示的已有文件目录,重命名并双击后缀名为 .uvprojx 的文件打开工程文件,并对 main.c 进行修改,工程中所使用的全部头文件其详细内容已放于文末。

    #include "stm32f10x.h" // Device header
    #include "OLED.h"
    #include "SoftSPI.h"
    #include "W25Q64.h"

    uint8_t MID;
    uint16_t DID;

    uint8_t ArrayWrite[] = {0xA1, 0xB2, 0xC3, 0xD4};
    uint8_t ArrayRead[4];

    int main(void)
    {
    OLED_Init();
    OLED_ShowString(1, 5, "SOFT SPI");
    OLED_ShowString(2, 1, "MID: DID:");
    OLED_ShowString(3, 1, "W:");
    OLED_ShowString(4, 1, "R:");

    复制代码
      W25Q64_Init();    
      W25Q64_ReadID(&MID, &DID);
      
      OLED_ShowHexNum(2, 5, MID, 2);
      OLED_ShowHexNum(2, 12, DID, 4); 
      
      W25Q64_SectorErase(0x000000);
      W25Q64_PageProgram(0x000000, ArrayWrite, 4);
      
      W25Q64_ReadData(0x000000, ArrayRead, 4);
      
      OLED_ShowHexNum(3, 3, ArrayWrite[0], 2);
      OLED_ShowHexNum(3, 6, ArrayWrite[1], 2);
      OLED_ShowHexNum(3, 9, ArrayWrite[2], 2);
      OLED_ShowHexNum(3, 12, ArrayWrite[3], 2);
          
      OLED_ShowHexNum(4, 3, ArrayRead[0], 2);
      OLED_ShowHexNum(4, 6, ArrayRead[1], 2);
      OLED_ShowHexNum(4, 9, ArrayRead[2], 2);
      OLED_ShowHexNum(4, 12, ArrayRead[3], 2);
      
      while (1)
      {
      	
      }

    }

  • 程序运行后,OLED 显示屏第一行居中显示 "SOFT SPI" 标识,第二行实时显示通过软件 SPI 读取到的 W25Q64 芯片厂商 ID 和设备 ID,完成芯片身份验证;程序自动对 Flash 芯片 0 地址扇区进行擦除后,写入预设的 4 组十六进制数据,并将写入数据显示在第三行 "W:" 后方,随后读取相同地址的数据并显示在第四行 "R:" 后方,写入与读取的数据完全一致,直观验证了软件 SPI 通信及 W25Q64 擦除、写入、读取功能正常可靠。

四、演示代码关联的头文件与源文件说明

  • OLED 相关头文件请从 STM32 学习 ------ 个人学习笔记4(OLED 显示屏及调试工具) 文末查看,此处不重复展示。

  • SoftSPI.c

    #include "stm32f10x.h" // Device header

    void SoftSPI_W_SS(uint8_t BitValue){
    GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
    }

    void SoftSPI_W_SCK(uint8_t BitValue){
    GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
    }

    void SoftSPI_W_MOSI(uint8_t BitValue){
    GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
    }

    uint8_t SoftSPI_R_MISO(){
    return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
    }

    void SoftSPI_Init(void){
    // 配置 GPIO
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    复制代码
      GPIO_InitTypeDef GPIO_InitStructure;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_Init(GPIOA, &GPIO_InitStructure);
      
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_Init(GPIOA, &GPIO_InitStructure);
      
      // 初始化默认电平
      SoftSPI_W_SS(1);
      SoftSPI_W_SCK(0);

    }

    void SoftSPI_Start(void){
    SoftSPI_W_SS(0);
    }

    void SoftSPI_Stop(void){
    SoftSPI_W_SS(1);
    }

    uint8_t SoftSPI_SwapByte_Mode_0(uint8_t ByteSend){
    uint8_t i, ByteReceive = 0x00;

    复制代码
      for (i=0; i<8; i++){
          SoftSPI_W_MOSI(ByteSend & 0x80 >> i);
          SoftSPI_W_SCK(1);
          if (SoftSPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
          SoftSPI_W_SCK(0);
      }    
      
      return ByteReceive;

    }

    uint8_t SoftSPI_SwapByte_Mode_0_Backup(uint8_t ByteSend){
    uint8_t i;

    复制代码
      for (i=0; i<8; i++){
          SoftSPI_W_MOSI(ByteSend & 0x80);
          ByteSend <<= 1;
          SoftSPI_W_SCK(1);
          if (SoftSPI_R_MISO() == 1){ByteSend |= 0x01;}
          SoftSPI_W_SCK(0);
      }    
      
      return ByteSend;

    }

    uint8_t SoftSPI_SwapByte_Mode_1(uint8_t ByteSend){
    uint8_t i, ByteReceive = 0x00;

    复制代码
      for (i=0; i<8; i++){
          SoftSPI_W_SCK(1);
          SoftSPI_W_MOSI(ByteSend & 0x80 >> i);
          SoftSPI_W_SCK(0);
          if (SoftSPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}        
      }    
      
      return ByteReceive;

    }

  • SoftSPI.h

    #ifndef __SOFTSPI_H
    #define __SOFTSPI_H

    void SoftSPI_Init(void);
    void SoftSPI_Start(void);
    void SoftSPI_Stop(void);
    uint8_t SoftSPI_SwapByte_Mode_0(uint8_t ByteSend);
    uint8_t SoftSPI_SwapByte_Mode_0_Backup(uint8_t ByteSend);
    uint8_t SoftSPI_SwapByte_Mode_1(uint8_t ByteSend);

    #endif

  • W25Q64.c

    #include "stm32f10x.h" // Device header
    #include "SoftSPI.h"
    #include "W25Q64_Ins.h"

    void W25Q64_Init(void){
    SoftSPI_Init();

    }

    void W25Q64_ReadID(uint8_t *MID, uint16_t *DID){
    SoftSPI_Start();
    SoftSPI_SwapByte_Mode_0(W25Q64_JEDEC_ID);

    复制代码
      *MID = SoftSPI_SwapByte_Mode_0(W25Q64_DUMMY_BYTE);
      
      *DID = SoftSPI_SwapByte_Mode_0(W25Q64_DUMMY_BYTE);
      *DID <<= 8;
      *DID |= SoftSPI_SwapByte_Mode_0(W25Q64_DUMMY_BYTE);
      
      SoftSPI_Stop();

    }

    void W25Q64_WriteEnable(void){
    SoftSPI_Start();
    SoftSPI_SwapByte_Mode_0(W25Q64_WRITE_ENABLE);
    SoftSPI_Stop();
    }

    void W25Q64_WaitBusy(void){
    uint32_t Timeout;
    SoftSPI_Start();
    SoftSPI_SwapByte_Mode_0(W25Q64_READ_STATUS_REGISTER_1);
    Timeout = 100000;
    while ((SoftSPI_SwapByte_Mode_0(W25Q64_DUMMY_BYTE) & 0x01) == 0x01){
    Timeout--;
    if (Timeout == 0){
    break;
    }
    }
    SoftSPI_Stop();
    }

    void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count){
    uint16_t i;

    复制代码
      W25Q64_WriteEnable();
      
      SoftSPI_Start();
      SoftSPI_SwapByte_Mode_0(W25Q64_PAGE_PROGRAM); 
      SoftSPI_SwapByte_Mode_0(Address >> 16); 
      SoftSPI_SwapByte_Mode_0(Address >> 8); 
      SoftSPI_SwapByte_Mode_0(Address);
      
      for (i=0; i<Count; i++){
          SoftSPI_SwapByte_Mode_0(DataArray[i]);
      }
      
      SoftSPI_Stop();
      W25Q64_WaitBusy();

    }

    void W25Q64_SectorErase(uint32_t Address){
    W25Q64_WriteEnable();

    复制代码
      SoftSPI_Start();
      SoftSPI_SwapByte_Mode_0(W25Q64_SECTOR_ERASE_4KB);
      SoftSPI_SwapByte_Mode_0(Address >> 16); 
      SoftSPI_SwapByte_Mode_0(Address >> 8); 
      SoftSPI_SwapByte_Mode_0(Address);
      
      SoftSPI_Stop();
      W25Q64_WaitBusy();

    }

    void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count){
    uint32_t i;

    复制代码
      SoftSPI_Start();
      SoftSPI_SwapByte_Mode_0(W25Q64_READ_DATA); 
      SoftSPI_SwapByte_Mode_0(Address >> 16); 
      SoftSPI_SwapByte_Mode_0(Address >> 8); 
      SoftSPI_SwapByte_Mode_0(Address);
      
      for (i=0; i<Count; i++){
          DataArray[i] = SoftSPI_SwapByte_Mode_0(W25Q64_DUMMY_BYTE);
      }
      SoftSPI_Stop();

    }

  • W25Q64.h

    #ifndef __W25Q64_H
    #define __W25Q64_H

    void W25Q64_Init(void);
    void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
    void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
    void W25Q64_SectorErase(uint32_t Address);
    void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);

    #endif

  • W25Q64_Ins.h

    #ifndef __W25Q64_INS_H
    #define __W25Q64_INS_H

    #define W25Q64_WRITE_ENABLE 0x06
    #define W25Q64_WRITE_DISABLE 0x04
    #define W25Q64_READ_STATUS_REGISTER_1 0x05
    #define W25Q64_READ_STATUS_REGISTER_2 0x35
    #define W25Q64_WRITE_STATUS_REGISTER 0x01
    #define W25Q64_PAGE_PROGRAM 0x02
    #define W25Q64_QUAD_PAGE_PROGRAM 0x32
    #define W25Q64_BLOCK_ERASE_64KB 0xD8
    #define W25Q64_BLOCK_ERASE_32KB 0x52
    #define W25Q64_SECTOR_ERASE_4KB 0x20
    #define W25Q64_CHIP_ERASE 0xC7
    #define W25Q64_ERASE_SUSPEND 0x75
    #define W25Q64_ERASE_RESUME 0x7A
    #define W25Q64_POWER_DOWN 0xB9
    #define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
    #define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
    #define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
    #define W25Q64_MANUFACTURER_DEVICE_ID 0x90
    #define W25Q64_READ_UNIQUE_ID 0x4B
    #define W25Q64_JEDEC_ID 0x9F
    #define W25Q64_READ_DATA 0x03
    #define W25Q64_FAST_READ 0x0B
    #define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
    #define W25Q64_FAST_READ_DUAL_IO 0xBB
    #define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
    #define W25Q64_FAST_READ_QUAD_IO 0xEB
    #define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3

    #define W25Q64_DUMMY_BYTE 0xFF

    #endif


文中部分知识参考:B 站 ------ 江协科技;百度百科

相关推荐
LN花开富贵2 小时前
【ROS】鱼香ROS2学习笔记一
linux·笔记·python·学习·嵌入式·ros·agv
yrx0203072 小时前
串口空闲中断+DMA接收+环形缓冲区 && 串口DMA发送+环形缓冲区
stm32·单片机
LCG元3 小时前
STM32实战:基于STM32F103的4G模块(EC20)HTTP通信
stm32·嵌入式硬件·http
IT19953 小时前
Wireshark笔记-对AI连接标准MCP抓包分析
笔记·测试工具·wireshark
克里斯蒂亚诺·罗纳尔达3 小时前
智能体学习23——资源感知优化(Resource-Aware Optimization)
人工智能·学习
小夏子_riotous4 小时前
Docker学习路径——2、安装
linux·运维·分布式·学习·docker·容器·云计算
送外卖的CV工程师4 小时前
STM32+Makefile编译+OpenOCD 烧录调试
stm32·单片机·嵌入式硬件·makefile·调试·烧录·openocd
SteveSenna4 小时前
Trossen Arm MuJoCo自定义1:改变目标物体
人工智能·学习·算法·机器人
羊群智妍5 小时前
2026 AI搜索优化实战:GEO优化全流程
笔记