目录
- 1.SPI通信协议
- 1.1 简介
- 1.2 硬件电路
- 1.3 移位示意图
- 1.4 SPI时序基本单元
- 1.5 SPI时序
- 2.W25Q64
- 2.1 简介
- 2.2 硬件电路
- 2.3 框图
- 2.3.1 结构介绍
- 2.3.2 混淆
- 2.4 Flash操作注意事项
- 2.4.1 写操作
- 2.4.2 读取操作
- 2.5 芯片手册补充
- 2.5.1 状态寄存器
- 2.5.2 指令集
- 3.软件操作W25Q64
- 4.SPI外设
- 4.1 简介
- 4.2 框图
- 4.3 基本结构
- 4.4 传输时序图
- 4.4.1 主模式全双工连续传输
- 4.4.2 非连续传输
- 4.5 软件/硬件波形对比
- 5.结构体和相关API
- 5.1 结构体
- 5.2 API
- **1.** `void SPI_I2S_DeInit(SPI_TypeDef* SPIx)`
- **2.** `void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct)`
- **3.** `void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct)`
- **4.** `void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState)`
- **5.** `void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState)`
- **6.** `void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState)`
- **7.** `void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)`
- **8.** `uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)`
- **9.** `void SPI_NSSInternalSoftwareConfig(SPI_TypeDef* SPIx, uint16_t SPI_NSSInternalSoft)`
- **10.** `FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG)`
- **11.** `void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG)`
- **12.** `ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT)`
- 6.硬件操作W25Q64
1.SPI通信协议
1.1 简介
- SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
- 四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)
- 同步,全双工
- 支持总线挂载多设备(一主多从)
相比较于I2C:
- SPI协议的传输速度没有严格规定的最大传输速度,取决于芯片厂商的设计需求。
- SPI的设计比较简单粗暴,实现的功能没有I2C的那么多
- SPI的硬件开销比较答,通信线的个数比较多,并且通信过程中经常会有资源浪费的现象。
1.2 硬件电路
所有SPI设备的SCK、MOSI、MISO分别连在一起
主机另外引出多条SS控制线,分别接到各从机的SS引脚
输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
- MOSI:设置为推挽输出,高低电平都有很强的电平驱动能力,使得电平下降沿和上升沿都非常迅速,不像I2C那样,需要通过外部上拉电阻来拉高电平,这个过程并不是一下子完成的,因此可能会影响到数据传输。
- MISO:从机是有多个的,但是主机只有一个输入口,如果三个从机的MISO都一直是推挽输出,就会导致冲突,因此如果从机想要使用推挽输出的模式,就必须在未被SS选中的时候将MISO设置为高阻态模式,防止干扰到别的从设备
1.3 移位示意图
- SPI的移位操作:
-
- 在每个时钟(SCK)的上升沿,主机的移位寄存器会左移一位,将高位的数据通过
MOSI
发送给从机。 - 同时,从机的移位寄存器也会左移一位,将高位的数据通过
MISO
发送给主机。 - 这种操作可以实现主机和从机之间的同步数据交换。
- 在每个时钟(SCK)的上升沿,主机的移位寄存器会左移一位,将高位的数据通过
- 单向数据传输的资源浪费:
-
- 如果主机仅希望从从机接收数据,但并不关心发送给从机的内容,可以将移位寄存器的值预先设置为
0x00
或0xFF
(即发送无效数据)。 - 对于从机来说,它依然会接收主机发送的无效数据,但这些数据对实际应用无意义,因而这部分通信会造成一定的资源浪费。
- 这种资源浪费是SPI协议本身的特点,因为SPI是全双工通信协议,发送与接收同时发生。
- 如果主机仅希望从从机接收数据,但并不关心发送给从机的内容,可以将移位寄存器的值预先设置为
- 数据置换的本质:
-
- SPI通信本质是通过移位寄存器的左右移动,在时钟信号的驱动下实现主机与从机之间数据的字节级交换。
- 在通信过程中,主机和从机在时钟同步的条件下会同时完成一次字节的交换操作,这样主机的高位数据进入从机的低位,而从机的高位数据则进入主机的低位。完成置换后就会发现其实低位的数据就跑到最高位,因此没交换移位是需要左移一次的。如下图是交换后的数据:
- 优化方式:
-
- 如果单纯需要单向数据传输,可以考虑改用其他协议,比如I²C或UART,这些协议支持明确的主从通信方向,能避免资源浪费。
- 如果使用SPI协议,尽量在通信中利用双向数据交换,减少单向传输场景下的无效数据发送。
1.4 SPI时序基本单元
起始条件:SS从高电平切换到低电平
终止条件:SS从低电平切换到高电平
在SPI协议中,有两个值来确定SPI的模式, 定义了时钟信号如何与数据同步 。
CPOLL (时钟极性) :决定时钟信号在空闲状态下的电平。0表示时钟空闲时为低电平,1表示时钟空闲时为高电平。
CPHA (时钟相位) :表示相位, 决定数据采样的时刻。0表示数据在第一个时钟沿采样,1表示在第二个时钟沿采样。
CPOL | CPHA | 模式 | 描述 |
---|---|---|---|
0 | 0 | 0 | SPICLK初始电平为低电平,数据在第一个时钟沿(上升沿)采样(数据移入移位寄存器) |
0 | 1 | 1 | SPICLK初始电平为低电平,数据在第二个时钟沿(下降沿)采样 |
1 | 0 | 2 | SPICLK初始电平为高电平,数据在第一个时钟沿(下降沿)采样 |
1 | 1 | 3 | SPICLK初始电平为高电平,数据在第二个时钟沿(上升沿)采样 |
- MOSI → 从设备采样;
- MISO → 主设备采样。
交换一个字节(模式0)
-
CPOL=0:空闲状态时,SCK为低电平
-
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
-
注意:这里所谓的移入和移出是相对于从设备来说的!!!!!
-
- 第一个上升沿,MOSI的数据移入到从设备的寄存器,也就是说主设备在此之前已经将数据移出到MOSI。于此同时,MISO的数据移入到了主设备的寄存器,也就是从设备在此之前已经将数据移出到MISO
- 第二个上升沿同理。
交换一个字节(模式1)
- CPOL=0:空闲状态时,SCK为低电平
- CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
交换一个字节(模式2)
- CPOL=1:空闲状态时,SCK为高电平
- CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
交换一个字节(模式3)
- CPOL=1:空闲状态时,SCK为高电平
- CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
上面说过,在从机未没选中的时候,其MISO必须置为高阻态模式:
红色标中的就是被置为了高阻态,不过一般不需要去关心,这个从机一般都会自己去做的。
MOSI,来自从设备,可以理解为是从设备引申到主设备的,主设备输出给从设备数据,从设备在该MOSI上采样来自主设备的数据。
1.5 SPI时序
发送指令:
- 向SS指定的设备,发送指令(0x06)
指定地址写:
- 向SS指定的设备,发送写指令(0x02),随后在指定地址(Address[23:0])下,写入指定数据(Data)
指定地址读:
- 向SS指定的设备,发送读指令(0x03),随后在指定地址(Address[23:0])下,读取从机数据(Data)
2.W25Q64
参考资料
- 📎W25Q256FV.PDF
- 📎W25Q16BV.PDF
- 📎W25Q32BV.PDF
- 📎W25Q40BV.PDF
- 📎W25Q64BV.PDF
- 📎W25Q80BV.PDF
- 📎W25Q128BV.PDF
原理图:
- 📎W25Qxx.pdf
2.1 简介
W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景
存储介质:Nor Flash(闪存)
时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)存储容量(24位地址):
型号 | 容量 |
---|---|
W25Q40 | 4Mbit / 512KByte |
W25Q80 | 8Mbit / 1MByte |
W25Q16 | 16Mbit / 2MByte |
W25Q32 | 32Mbit / 4MByte |
W25Q64 | 64Mbit / 8MByte |
W25Q128 | 128Mbit / 16MByte |
W25Q256 | 256Mbit / 32MByte |
2.2 硬件电路
引脚 | 功能 |
---|---|
VCC、GND | 电源(2.7~3.6V) |
CS(SS) | SPI片选 |
CLK(SCK) | SPI时钟 |
DI(MOSI) | SPI主机输出从机输入 |
DO(MISO) | SPI主机输入从机输出 |
WP | 写保护 |
HOLD | 数据保持 |
2.3 框图
2.3.1 结构介绍
24位的地址,最大的寻址范围是16MB(224➗1024➗1024),一个24位的地址就对应1个字节(Byte)的存储数据(这个具体后面讲)
但是对于这个芯片只有8MB,也就是地址空间只用了一半。
\1. Block Segmentation(分块结构)
-
- 存储器被分成多个块(Block)和扇区(Sector)。
- 每个块大小为64KB,每个扇区大小为4KB。
- 通过这种分层结构,便于在不同应用场景中对存储器进行擦除、读取和写入操作。
- 支持按**扇区(4KB)或块(64KB)**擦除操作,提供灵活的存储管理。
- 每个块由16个扇区组成,每个扇区的地址范围为0x??0000到0x??0FFF。块的地址范围是0x00???~-0x7F???(127)
- 总存储容量为64M位(8MB:64Mbit➗8=8MByte — 8bit=1Byte),由128个块组成((8✖1024) KB ➗64KB=128)。
\2. Write Protect Logic and Row Decode(写保护逻辑和行解码)
-
- 用于控制写保护功能,并将输入的地址映射到特定的存储行。
- 提供硬件或软件的写保护,防止存储数据被意外修改。
- 配合页地址和列地址实现对存储器的定位。
\3. SPI Command & Control Logic(SPI命令和控制逻辑)
-
- 通过SPI接口与主机通信,接收命令并执行相应的存储器操作。
- 输入引脚包括:
-
-
- CLK:时钟信号,用于同步数据传输。
- CS:片选信号,低电平激活。
- DI(IO0):数据输入引脚,用于接收主机发送的指令或数据。
- DO(IO1):数据输出引脚,用于输出存储器中的数据到主机。
- /HOLD(IO3):暂停信号,用于中断或暂停当前操作。
-
-
- 提供命令解释功能,如读取、写入、擦除、状态寄存器操作等。
- 控制整个存储器的操作。
\4. Write Control Logic(写入控制逻辑)
-
- 负责管理写入操作,配合SPI命令实现数据的写入。
- 检查写保护状态,确保只有在写保护解除的情况下才能进行写操作。
- 管理写入时需要的高电压生成器。
\5. Status Register(状态寄存器)
-
- 存储芯片的当前状态信息,包括忙碌状态、写保护状态、错误状态等。
- 主机可以通过读取状态寄存器,判断芯片是否处于忙碌状态(Busy)或者是否已写保护。
- 也可用于设置写保护等功能。
\6. High Voltage Generators(高压发生器)
-
- 用于生成执行擦除和写入操作所需的高电压。
- 在写入或擦除操作时提供必要的高电压,确保存储单元的可靠修改。
- 实现掉电不丢失
7. Page Address Latch/Counter 和 Byte Address Latch/Counter(页地址锁存器/计数器 和 字节地址锁存器/计数器)
-
- 用于管理地址的存储和递增。
- 页地址和字节地址配合使用,实现对存储器的逐页(256字节/Byte)或逐字节访问。
- 页地址锁存器/计数器:用于定位目标页(256字节)。
- 字节地址锁存器/计数器:用于定位目标页内的具体字节。
8. Column Decode and 256-Byte Page Buffer(列解码和256字节页缓冲区)
-
- 列解码:用于从存储阵列中选取目标列。与行解码配合,实现完整的存储单元地址选择。
- 页缓冲区:用于暂存数据,支持一次最多256字节的数据写入。提升写入效率,可以一次性将一页数据写入存储阵列。为什么要有缓冲区RAM??因为这个模块的写入是“刻苦铭心”的,也就是永久性存储,写入是需要时间的,而SPI的写入是非常快的,所以需要个缓冲区暂存
- 一个扇区4KB,可以划分16页。1页的地址范围就是0x???00~0x???FF(256字节就说明有256个地址,也就是FF),这个也就是数据的地址范围。
\9. Block Diagram总体工作流程
-
存储分层:
-
- 整个存储空间按块(64KB)和扇区(4KB)划分,便于擦除和管理。
-
SPI通信:
-
- 主机通过SPI接口与芯片通信,发送操作命令和数据。
-
地址管理:
-
- 通过页地址和字节地址锁存器,定位到特定的存储单元。
-
数据传输:
-
- 数据先存入页缓冲区,再通过高压发生器写入存储阵列。
-
状态反馈:
-
- 状态寄存器记录当前芯片的状态,主机可以查询写入或擦除的完成情况。
2.3.2 混淆
注意这个模块的地址空间范围只有一半。
因此当需要写入数据的时候,就需要发送3字节的地址,高两位字节用于找到对应块的对应扇区的对应页,最后一位字节确定页内的地址
- 块号地址确定范围:0x00 ?? ?? ~ 0x7F ?? ??(128块)
- 扇号地址确定范围:0x## 0? ?? ~ 0x## F? ??(16个扇区)
- 扇区内页的地方确定范围:0x## #0 ?? ~ 0x## #F ??(16页)
- **页内的地址确定范围:**0x## ## 00 ~ 0x## ## FF(一页对应256字节)
往哪里存储,这肯定是要有地址去指向的。地址通过层级结构(块 > 扇区 > 页 > 字节)来定位存储空间。而一个地址指向的存储空间,其容量是1字节。
8MB的容量又是怎么算出来的?
24位的地址,不就有224个地址么,而1个地址又对应指向1个字节(Byte)的存储空间,那也就是说存储容量有224Byte,而224➗1024=16348KB、16348➗1024=16MB,即24位的地址其存储容量为16MB
而这里介绍的W25Q64,它只用了一半的地址空间,从块号的地址范围为:00~7F就可以知道,所以这个模块的存储容量就是8MB
将数据 0xAB
写入地址 0x123456
,流程如下:
- 主机发送写命令。
- 主机发送 3 字节地址
0x123456
:
-
12h
:块选择。34h
:页选择。56h
:页内的具体字节位置。
- 主机发送数据
0xAB
。 - 存储器将
0xAB
写入到地址0x123456
对应的存储单元。
2.4 Flash操作注意事项
2.4.1 写操作
- 写入前需要“写使能”
-
- 在进行写入操作之前,必须先发送 写使能指令(Write Enable, WREN),这是一种保护机制,防止意外写入。
- 通过写使能,芯片的状态寄存器会设置一个“写使能位”(WEL, Write Enable Latch)。
- 如果没有先进行写使能,芯片将拒绝任何写入操作。
- 只能从1改写为0,不能从0改写为1
-
- 闪存的基本存储单元是浮动栅极晶体管,写入操作的本质是改变存储单元的电荷状态:
-
-
- 1 → 0:可以通过编程直接完成。
- 0 → 1:需要通过擦除来恢复为1(清空浮动栅的电荷)。
-
-
- 因此,在写入数据前,必须确保目标存储区域已经擦除。
- 写入数据前必须先擦除
-
- 擦除操作会将存储区域中的所有位恢复为1。
- 擦除只能按最小擦除单元(如扇区、块)进行,不能只擦除单个字节。
- 这一步是为了保证存储器可以正确写入新的数据。
- 擦除的最小单元
-
- W25Q64支持不同级别的擦除操作:
-
-
- 扇区擦除(4KB):最小的擦除单位。
- 块擦除(64KB):擦除更大的存储区域。
- 芯片擦除:清空整个存储器。
-
-
- 擦除操作需要一定时间,在擦除完成之前,芯片处于“忙状态”。
- 最多写入一页数据(256字节)
-
- 写入操作是按页(Page)为单位管理的:
-
-
- 一次写入最多 256 字节(1 页)。
- 如果超出页尾地址(例如写入 257 字节),后续数据会回绕到页首,从而覆盖已有数据。
-
-
- 因此,应用程序必须确保每次写入操作不会超过页边界。
- 写入后芯片进入忙状态
-
- 写入数据需要通过内部的高压操作完成,期间芯片会进入忙状态(Busy)。
- 在忙状态下,芯片不会响应新的命令(包括读写操作)。
- 主机可以通过读取状态寄存器中的“忙位”(BUSY)来判断写入操作是否完成。
2.4.2 读取操作
- 直接调用读取时序
-
- 与写入不同,读取操作不需要执行写使能等额外步骤。
- 主机只需发送读取命令和目标地址,存储器即可返回指定地址处的数据。
- 没有页限制
-
- 读取操作不受页的限制,主机可以连续读取任意长度的数据。
- 存储器内部会自动按地址顺序输出数据,直到达到主机指定的读取长度。
- 读取操作结束后不会进入忙状态
-
- 读取操作不会修改存储器的内容,因此不需要涉及复杂的高压操作。
- 芯片在读取完成后立即可用,不会进入忙状态。
- 忙状态时不能读取
-
- 如果芯片正在执行写入或擦除操作,此时是忙状态,无法响应读取请求。
- 主机需要轮询状态寄存器的忙位(BUSY)来确认芯片是否空闲。
操作 | 特点 | 注意事项 |
---|---|---|
写入 | 需先写使能 数据位只能从1改为0 写入前需擦除 最多写入1页 | 写入后芯片进入忙状态,不可立即响应其他命令。 |
擦除 | 必须按最小擦除单元进行 所有位恢复为1 | 擦除速度较慢,应用时需考虑优化操作顺序。 |
读取 | 无需写使能 无页限制,连续读取任意长度 读取结束后不会忙状态 | 忙状态下不能读取,主机需先检查芯片是否空闲。 |
2.5 芯片手册补充
W25Q64BV.PDF
2.5.1 状态寄存器
BUSY是状态寄存器(S0)中的只读位,当设备执行页面程序、扇区擦除、块擦除、芯片擦除或写入状态寄存器指令时,该位被设置为1状态。在此期间,设备将忽略除读取状态寄存器和擦除挂起指令之外的其他指令(请参阅AC特性中的tW、tPP、tSE、tBE和tCE)。当程序、擦除或写入状态寄存器指令完成时,BUSY位将被清除为0状态,表示设备已准备好执行进一步的指令。
写启用锁存器(WEL)是状态寄存器(S1)中的只读位,在执行写启用指令后设置为1。当设备被写禁用时,WEL状态位被清除为0。通电或执行以下任何指令后,会出现写禁用状态:写禁用、页面程序、扇区擦除、块擦除、芯片擦除和写状态寄存器。
暂时最低两位就行了
2.5.2 指令集
可以通过输入表中的相关Instruction给设备来确认设备。
1. Write Enable (06h)
- 写使能指令,设置“写使能锁存器”(WEL,Write Enable Latch)位为 1,允许执行写入或擦除操作。
- 在每次写入或擦除之前必须先执行此指令,否则写入/擦除命令会被忽略。
\2. Write Disable (04h)
- 禁止写操作,清除 WEL 位,防止意外写入或擦除。
- 在完成写入或擦除后,可以通过此指令禁用写功能,增加安全性。
3. Read Status Register-1 (05h)
- 读取状态寄存器 1 的内容,主要包括 Busy 位(芯片是否忙碌)和 WEL 位(是否已启用写入)。
- Busy 位:1 表示芯片忙,0 表示空闲。
- WEL 位:1 表示允许写入,0 表示写入被禁止。
\4. Read Status Register-2 (35h)
- 读取状态寄存器 2,可能包含保护位或其他状态信息(具体参见芯片手册)。
- 用于检测写保护状态等信息。
\5. Write Status Register (01h)
- 写入状态寄存器的内容,用于设置写保护等功能。
- 配置状态寄存器,如启用或禁用某些扇区的写保护。
6. Page Program (02h)
- 写入数据到一页(256 字节)。
- 3 字节地址(A23-A0)用于定位页。
- 后续数据字节(D7-D0)是要写入的实际内容。
- 如果写入的数据超出页边界,多余的数据会覆盖页首。
\7. Quad Page Program (32h)
- 在四线模式下写入一页(256 字节)。
- 相比普通写入,四线模式可加快数据传输速度。
\8. Block Erase (64KB, D8h
- 擦除一个 64KB 的块。
- 3 字节地址(A23-A0),地址指定块的起始位置。
- 在写入新数据前清空整个块。
\9. Block Erase (32KB, 52h)
- 擦除一个 32KB 的块。
- 比 64KB 擦除更小粒度的操作。
10. Sector Erase (4KB, 20h)
- 擦除一个扇区(4KB)。
- 最小擦除单位,适用于需要频繁更新的存储区域。
\11. Chip Erase (C7h/60h)
- 擦除整个芯片,将所有数据位恢复为 1。
- 用于清空整个存储器,耗时最长。
\12. Erase Suspend (75h) / Erase Resume (7Ah)
- 暂停擦除操作或恢复擦除操作。
- 在擦除过程中插入读取操作,灵活调度任务。
\13. Power-down (B9h)
- 将芯片置于省电模式。
- 降低功耗,适用于低功耗应用场景。
\14. High Performance Mode (A3h)
- 启用高性能模式,提升芯片运行速度。
- 满足高速度需求的场景。
\15. Continuous Read Mode Reset (FFh)
- 重置连续读取模式。
- 退出连续读取模式,恢复到普通模式。
\16. Release Power-down or HPM / Device ID (ABh):
- 从省电模式中唤醒芯片,同时返回芯片的设备 ID。
- 唤醒后获取芯片标识信息。
\17. Manufacturer / Device ID (90h)
- 读取制造商 ID 和设备 ID。
- 用于验证芯片型号。
\18. Read Unique ID (4Bh)
- 读取芯片的唯一 ID。
- 适用于设备识别或防伪。
19. JEDEC ID (9Fh)
- 读取 JEDEC 标准的 ID 信息。
- 获取制造商信息、存储器类型和容量。
20. Read Data (03h)
- 普通读取模式,按字节读取数据。
- Byte 2-4 (A23-A0):3 字节地址,指定读取的起始地址。
- Byte 5+ (D7-D0):返回的数据字节。
- 无需“Dummy Byte”。
- 数据传输速度较慢,适用于非时间敏感的读取任务。
- 用于简单的逐字节数据读取。
\21. Fast Read (0Bh)
- 快速读取模式,通过插入一个“Dummy Byte”加速读取。
- Byte 2-4 (A23-A0):3 字节地址。
- Byte 5 (Dummy Byte):1 个无意义的占位字节。
- Byte 6+ (D7-D0):返回的数据字节。
- 相比普通读取,数据传输速度更快。
- 使用更高的 SPI 时钟频率(如 104 MHz)。
- 大容量数据读取场景,注重传输效率。
\22. Fast Read Dual Output (3Bh)
- 双线输出模式,利用
IO0
和IO1
两根数据线同时输出数据,提高速度。 - Byte 2-4 (A23-A0):3 字节地址。
- Byte 5 (Dummy Byte):占位字节。
- Byte 6+ (D7-D0):通过双线传输的数据字节。
- 数据传输效率比普通模式更高。
- 数据量较大且需要较快读取速度的场景。
\23. Fast Read Dual I/O (BBh)
- 双线输入输出模式,地址和数据都通过双线传输。
- Byte 2-3 (A23-A0):3 字节地址。
- Byte 4 (Dummy Byte):占位字节。
- Byte 5+ (D7-D0):通过双线传输的数据字节。
- 地址和数据均通过双线传输,大幅提升数据吞吐能力。
- 用于高性能需求的嵌入式系统。
\24. Fast Read Quad Output (6Bh)
- 四线输出模式,数据通过
IO0
、IO1
、IO2
和IO3
同时输出。 - Byte 2-4 (A23-A0):3 字节地址。
- Byte 5 (Dummy Byte):占位字节。
- Byte 6+ (D7-D0):通过四线传输的数据字节。
- 数据输出速度极高。
- 用于大数据量、高速读取的应用场景。
\25. Fast Read Quad I/O (EBh)
- 四线输入输出模式,地址和数据均通过四线传输。
- Byte 2-3 (A23-A0):地址。
- Byte 4 (Dummy Byte):占位字节。
- Byte 5+ (D7-D0):通过四线传输的数据字节。
- 支持最高速的读写操作。
- 用于存储芯片和处理器之间的高速通信场景。
\26. Octal Word Read Quad I/O (E3h)
- 八线模式读取。
- 最快的数据传输速度。
- 用于存储访问速度要求极高的嵌入式系统。
剩下的一些时序图去看手册就行了。
3.软件操作W25Q64
📎11-1 软件SPI读写W25Q64.zip
User:
- 📎main.c
Hardware:
- 📎MySPI.c
- 📎MySPI.h
- 📎OLED.c
- 📎OLED.h
- 📎OLED_Font.h
- 📎W25Q64.c
- 📎W25Q64.h
- 📎W25Q64_Ins.h
/*** 函 数:SPI交换传输一个字节,使用SPI模式0* 参 数:ByteSend 要发送的一个字节* 返 回 值:接收的一个字节*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t i, ByteReceive = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到for (i = 0; i < 8; i ++) //循环8次,依次交换每一位数据{/*两个!可以对数据进行两次逻辑取反,作用是把非0值统一转换为1,即:!!(0) = 0,!!(非0) = 1*/MySPI_W_MOSI(!!(ByteSend & (0x80 >> i))); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线MySPI_W_SCK(1); //拉高SCK,主设备上升沿移出数据(上面已经调用了),从设备从MOSI移入设备if (MySPI_R_MISO()){ByteReceive |= (0x80 >> i);} //读取MISO数据,并存储到Byte变量//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0MySPI_W_SCK(0); //拉低SCK,下降沿移入数据}return ByteReceive; //返回接收到的一个字节数据
}
需要注意的是不要混淆
移入:
-
MOSI:调用
MySPI_W_MOSI
就相当于将高位移出移位寄存器放入MOSI管,之后MySPI_W_SCK(1);
就是将SCK电平拉高,表明我主设备已经将数据移出放入到MOSI了(同时从设备也已经将数据移出到MISO),这时候从设备就可以移入数据,所以上图时序图中讲到的第一个SCK边沿移入数据是针对于MOSI上的从设备来说的。 -
MISO:而此时对于主设备,讲道理第一个SCK上升沿,从设备移入数据了,主设备应该同步也从MISO将数据移入寄存器,但由于是模拟实现的SPI,程序无法同步实现。
MySPI_W_SCK(1);
所以只能后续紧跟着调用MySPI_R_MISO
将MISO的数据移入进主设备的寄存器,所以上图时序图的第一个SCK边沿移入数据也是中是MISO上的主设备来说。 -
- 只不过,对于MISO,从设备会自己将数据移出到MISO,不需要像主设备这样手动调用一下
MySPI_W_MOSI
将数据移出到MOSI
- 只不过,对于MISO,从设备会自己将数据移出到MISO,不需要像主设备这样手动调用一下
移出:
- 从设备的移出;
MySPI_W_SCK(0);
拉低SCK,其实就是告诉从设备可以将数据移出到MISO - 至于主设备,会在下次循环调用
MySPI_W_MOSI
将数据移出到MOSI。
也就是说对于SCK的上升沿和下降沿其实主要是告诉从设备可以进行移入(MOSI)和移出(MISO)了,至于主设备是需要自己紧跟着在上升沿代码调用后进行移入(MISO),调用前将数据移出(MOSI);在下降沿代码前将数据进行移出(MOSI)
由于是软件实现,主设备无法说做到上升沿时刻同步移出数据,所以只能先移出数据,再将SCK电平置高。
下面来看看模式1的:
/*** 函 数:SPI交换传输一个字节,使用SPI模式1* 参 数:ByteSend 要发送的一个字节* 返 回 值:接收的一个字节*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t i, ByteReceive = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到for (i = 0; i < 8; i ++) //循环8次,依次交换每一位数据{MySPI_W_SCK(1); //拉高SCK,主设备将数据移出到MOSI(下面调用MySPI_W_MOSI),从设备也会将数据移出到MIS/*两个!可以对数据进行两次逻辑取反,作用是把非0值统一转换为1,即:!!(0) = 0,!!(非0) = 1*/MySPI_W_MOSI(!!(ByteSend & (0x80 >> i))); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线MySPI_W_SCK(0); //拉低SCK,主设备下降沿从MISO移入数据(下面调用MySPI_R_MISO),从设备从MOSI移出数据if (MySPI_R_MISO()){ByteReceive |= (0x80 >> i);} //读取MISO数据,并存储到Byte变量//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0}return ByteReceive; //返回接收到的一个字节数据
}
也就是说在上升沿和下降沿代码调用后主设备紧跟着调用相应的函数完成相应的移出移入操作
MySPI_W_SCK(1);
—>MySPI_W_MOSI
移出MySPI_W_SCK(0);
—>MySPI_R_MISO
移入
这个就比较好理解一点。
4.SPI外设
4.1 简介
-
STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
-
可配置8位/16位数据帧、高位先行/低位先行。一般都是用8位数据帧,也就是一个字节,以及高位先行
-
- 串口是低位先行
-
时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)。SPI外设的时钟,其实就是由外设时钟分频而来的,PCLK就是Peripheral Clock外设时钟,APB2的是72MHz,APB1的是36MHz。所以SPI的时钟频率需要看挂载哪个总线上
-
支持多主机模型、主或从操作
-
可精简为半双工/单工通信
-
支持DMA
-
兼容I2S协议
STM32F103C8T6 硬件SPI资源:SPI1(APB1)、SPI2(APB2)
4.2 框图
主机SPI外设框图
1. MOSI、MISO、SCK、NSS 引脚
这些是 SPI 外设的主要硬件引脚:
- MOSI(Master Out Slave In): 主设备发送数据到从设备。
- MISO(Master In Slave Out): 从设备发送数据到主设备。
- SCK(Serial Clock): 时钟信号,由主设备产生,控制数据传输节奏。
- NSS(Slave Select): 片选信号,选择从设备的使能引脚,低电平表示选中。
其中实现了交叉,也就是如果主机为从设备,数据变成从MISO口输出,从MOSI输入
2. 移位寄存器
移位寄存器是 SPI 的核心,用于实现数据的串行传输:
-
数据在 MOSI 和 MISO 之间传输,通过 SCK 时钟信号逐位移入或移出。
-
控制位 LSBFIRST: 控制数据的传输顺序,决定是先传输低位(LSB)还是高位(MSB)。
-
3. 接收缓冲区
用于暂存接收到的数据:
- 当 SPI 接收到数据时,先存入此缓冲区,然后通过程序读取。
- 和移位寄存器配合,实现数据从 MISO 进入。
4. 发送缓冲区
用于暂存需要发送的数据:
- 程序将数据写入此缓冲区后,SPI 会通过 MOSI 按位发送数据。
- 和移位寄存器配合,实现数据的输出。
5. 主控制电路
主控制电路是 SPI 的控制逻辑核心,管理通信模式和数据传输:
- 控制主从模式(通过 MSTR 位)。
- 决定 SPI 的使能状态(通过 SPE 位)。
- 配置单向或双向数据模式(通过 BIDIMODE 位)。
- CRC 校验、帧格式、数据帧大小等参数由此电路管理。
6. 波特率发生器
控制 SPI 的时钟分频,产生通信所需的时钟信号:
- 通过 BR[2:0] 配置时钟分频比,从而控制 SPI 的通信速度。
SPI_CR1寄存器
7. 通信电路
通信电路负责协调数据的发送和接收,包括:
- 检测数据是否接收完成(RXNE)。
- 检测数据是否发送完成(TXE)。
- 判断总线状态(如忙碌状态 BSY)。
8. SPI 控制寄存器(SPI_CR1 和 SPI_CR2)
这些寄存器用于配置 SPI 外设的工作模式:
- SPI_CR1: 配置 SPI 的基本模式,比如主从模式、数据格式(LSB/MSB)、时钟极性(CPOL)和相位(CPHA)。
- SPI_CR2: 控制中断使能(如 RXNEIE、TXEIE),并配置硬件片选功能(SSOE)。
9. 状态寄存器(SPI_SR)
状态寄存器反映 SPI 当前的运行状态:
- TXE(发送缓冲区空): 表示可以写入新的数据。
- RXNE(接收缓冲区非空): 表示接收到新数据。
- BSY(忙): 表示 SPI 正在进行通信。
10. 地址和数据总线
连接 SPI 和系统总线,用于数据的读写和地址的映射:
- 数据通过此总线从 CPU 传输到 SPI 寄存器,或者从 SPI 传输到 CPU。
4.3 基本结构
4.4 传输时序图
4.4.1 主模式全双工连续传输
4.4.2 非连续传输
4.5 软件/硬件波形对比
5.结构体和相关API
5.1 结构体
/** * @brief SPI Init structure definition */typedef struct
{uint16_t SPI_Direction; /*!< Specifies the SPI unidirectional or bidirectional data mode.This parameter can be a value of @ref SPI_data_direction */uint16_t SPI_Mode; /*!< Specifies the SPI operating mode.This parameter can be a value of @ref SPI_mode */uint16_t SPI_DataSize; /*!< Specifies the SPI data size.This parameter can be a value of @ref SPI_data_size */uint16_t SPI_CPOL; /*!< Specifies the serial clock steady state.This parameter can be a value of @ref SPI_Clock_Polarity */uint16_t SPI_CPHA; /*!< Specifies the clock active edge for the bit capture.This parameter can be a value of @ref SPI_Clock_Phase */uint16_t SPI_NSS; /*!< Specifies whether the NSS signal is managed byhardware (NSS pin) or by software using the SSI bit.This parameter can be a value of @ref SPI_Slave_Select_management */uint16_t SPI_BaudRatePrescaler; /*!< Specifies the Baud Rate prescaler value which will beused to configure the transmit and receive SCK clock.This parameter can be a value of @ref SPI_BaudRate_Prescaler.@note The communication clock is derived from the masterclock. The slave clock does not need to be set. */uint16_t SPI_FirstBit; /*!< Specifies whether data transfers start from MSB or LSB bit.This parameter can be a value of @ref SPI_MSB_LSB_transmission */uint16_t SPI_CRCPolynomial; /*!< Specifies the polynomial used for the CRC calculation. */
}SPI_InitTypeDef;
SPI_Direction
-
描述:
指定SPI的数据传输模式,是单向或双向通信。 -
取值范围:
可取以下值(具体定义在@ref SPI_data_direction
中): -
SPI_Direction_2Lines_FullDuplex
:全双工模式(两条线用于发送和接收)。SPI_Direction_2Lines_RxOnly
:仅接收模式(两条线,只有接收)。SPI_Direction_1Line_Rx
:单线接收模式(单独一条线)。SPI_Direction_1Line_Tx
:单线发送模式(单独一条线)。
SPI_Mode
-
描述:
指定SPI的工作模式,是主机模式(Master)还是从机模式(Slave)。 -
取值范围:
可取以下值(具体定义在@ref SPI_mode
中): -
SPI_Mode_Master
:主机模式。SPI_Mode_Slave
:从机模式。
SPI_DataSize
-
描述:
指定SPI传输的数据帧大小。 -
取值范围:
可取以下值(具体定义在@ref SPI_data_size
中): -
SPI_DataSize_8b
:8位数据帧。SPI_DataSize_16b
:16位数据帧。
SPI_CPOL
-
描述:
指定串行时钟的空闲状态(极性),即SCK引脚的电平状态。 -
取值范围:
可取以下值(具体定义在@ref SPI_Clock_Polarity
中): -
SPI_CPOL_Low
:空闲时SCK保持低电平。SPI_CPOL_High
:空闲时SCK保持高电平。
SPI_CPHA
-
描述:
指定串行时钟的采样边沿,即数据采集发生在哪一个时钟边沿。 -
取值范围:
可取以下值(具体定义在@ref SPI_Clock_Phase
中): -
SPI_CPHA_1Edge
:第一个时钟跳变沿采样数据。SPI_CPHA_2Edge
:第二个时钟跳变沿采样数据。
SPI_NSS
-
描述:
指定从属选择(NSS)信号的管理方式,是否由硬件(NSS引脚)管理或通过软件(SSI位)模拟管理。 -
取值范围:
可取以下值(具体定义在@ref SPI_Slave_Select_management
中): -
SPI_NSS_Soft
:NSS由软件管理(使用SSI位)。SPI_NSS_Hard
:NSS由硬件管理(NSS引脚)。
SPI_BaudRatePrescaler
-
描述:
指定波特率分频系数,用于配置发送和接收的SCK时钟。 -
取值范围:
可取以下值(具体定义在@ref SPI_BaudRate_Prescaler
中): -
SPI_BaudRatePrescaler_2
:SCK = fPCLK/2。SPI_BaudRatePrescaler_4
:SCK = fPCLK/4。SPI_BaudRatePrescaler_8
:SCK = fPCLK/8。SPI_BaudRatePrescaler_16
:SCK = fPCLK/16。SPI_BaudRatePrescaler_32
:SCK = fPCLK/32。SPI_BaudRatePrescaler_64
:SCK = fPCLK/64。SPI_BaudRatePrescaler_128
:SCK = fPCLK/128。SPI_BaudRatePrescaler_256
:SCK = fPCLK/256。
SPI_FirstBit
-
描述:
指定数据传输的起始位方向,是从最高有效位(MSB)还是最低有效位(LSB)开始。 -
取值范围:
可取以下值(具体定义在@ref SPI_MSB_LSB_transmission
中): -
SPI_FirstBit_MSB
:从MSB(最高有效位)开始传输。SPI_FirstBit_LSB
:从LSB(最低有效位)开始传输。
SPI_CRCPolynomial
-
描述:
指定用于循环冗余校验(CRC)计算的多项式值。 -
取值范围:
-
- 任意16位无符号整数值(用户根据需求设置)。常用的CRC多项式是标准化的,例如
0x07
、0x1021
等。
- 任意16位无符号整数值(用户根据需求设置)。常用的CRC多项式是标准化的,例如
5.2 API
1. void SPI_I2S_DeInit(SPI_TypeDef* SPIx)
-
作用:
将指定的SPI/I2S外设复位,清除所有配置,恢复到默认状态。 -
参数:
-
SPIx
:需要复位的SPI外设(如SPI1
或SPI2
)。
-
使用实例:
SPI_I2S_DeInit(SPI1); // 复位SPI1外设
2. void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct)
-
作用:
初始化SPI外设,并配置其通信参数。 -
参数:
-
SPIx
:需要初始化的SPI外设(如SPI1
)。SPI_InitStruct
:包含SPI初始化参数的结构体。
-
使用实例:
SPI_InitTypeDef SPI_InitStruct;
SPI_StructInit(&SPI_InitStruct); // 初始化结构体为默认值
SPI_InitStruct.SPI_Mode = SPI_Mode_Master; // 设置为主机模式
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; // 波特率分频
SPI_Init(SPI1, &SPI_InitStruct); // 初始化SPI1
3. void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct)
-
作用:
将SPI_InitTypeDef
结构体填充为默认值。 -
参数:
-
SPI_InitStruct
:指向需要初始化的SPI_InitTypeDef
结构体。
-
使用实例:
SPI_InitTypeDef SPI_InitStruct;
SPI_StructInit(&SPI_InitStruct); // 填充为默认值
4. void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState)
-
作用:
启用或禁用SPI外设。 -
参数:
-
SPIx
:需要控制的SPI外设(如SPI1
)。NewState
:设置状态,ENABLE
表示启用,DISABLE
表示禁用。
-
使用实例:
SPI_Cmd(SPI1, ENABLE); // 启用SPI1
5. void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState)
-
作用:
启用或禁用SPI/I2S的中断。 -
参数:
-
SPIx
:需要控制的SPI/I2S外设(如SPI1
)。SPI_I2S_IT
:指定中断类型,例如SPI_I2S_IT_TXE
(发送缓冲区空中断)。NewState
:设置状态,ENABLE
表示启用,DISABLE
表示禁用。
-
使用实例:
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_TXE, ENABLE); // 启用发送缓冲区空中断
6. void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState)
-
作用:
启用或禁用SPI/I2S的DMA功能。 -
参数:
-
SPIx
:需要控制的SPI/I2S外设(如SPI1
)。SPI_I2S_DMAReq
:指定DMA请求类型,例如SPI_I2S_DMAReq_Tx
(DMA发送请求)。NewState
:设置状态,ENABLE
表示启用,DISABLE
表示禁用。
-
使用实例:
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE); // 启用SPI1的DMA发送请求
7. void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)
-
作用:
通过SPI/I2S外设发送数据。 -
参数:
-
SPIx
:需要操作的SPI/I2S外设(如SPI1
)。Data
:要发送的数据。
-
使用实例:
SPI_I2S_SendData(SPI1, 0x55AA); // 发送数据0x55AA
8. uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)
-
作用:
从SPI/I2S外设读取接收到的数据。 -
参数:
-
SPIx
:需要操作的SPI/I2S外设(如SPI1
)。
-
使用实例:
uint16_t receivedData = SPI_I2S_ReceiveData(SPI1); // 读取接收到的数据
9. void SPI_NSSInternalSoftwareConfig(SPI_TypeDef* SPIx, uint16_t SPI_NSSInternalSoft)
-
作用:
在软件管理模式下配置NSS信号。 -
参数:
-
SPIx
:需要操作的SPI外设(如SPI1
)。SPI_NSSInternalSoft
:设置NSS信号为高电平或低电平。
-
-
SPI_NSSInternalSoft_Set
:设置NSS为高。SPI_NSSInternalSoft_Reset
:设置NSS为低。
-
-
使用实例:
SPI_NSSInternalSoftwareConfig(SPI1, SPI_NSSInternalSoft_Set); // 设置NSS信号为高电平
10. FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG)
-
作用:
检查指定的SPI/I2S标志位状态。 -
参数:
-
SPIx
:需要操作的SPI/I2S外设(如SPI1
)。SPI_I2S_FLAG
:指定要检查的标志位,例如SPI_I2S_FLAG_TXE
(发送缓冲区空标志)。
-
使用实例:
if (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == SET) {// 发送缓冲区为空
}
11. void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG)
-
作用:
清除指定的SPI/I2S标志位。 -
参数:
-
SPIx
:需要操作的SPI/I2S外设(如SPI1
)。SPI_I2S_FLAG
:指定要清除的标志位。
-
使用实例:
SPI_I2S_ClearFlag(SPI1, SPI_I2S_FLAG_OVR); // 清除溢出错误标志
12. ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT)
-
作用:
检查指定的SPI/I2S中断状态。 -
参数:
-
SPIx
:需要操作的SPI/I2S外设(如SPI1
)。SPI_I2S_IT
:指定要检查的中断类型,例如SPI_I2S_IT_RXNE
(接收缓冲区非空中断)。
-
使用实例:
if (SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_RXNE) == SET) {// 接收缓冲区非空
}
6.硬件操作W25Q64
📎11-2 硬件SPI读写W25Q64.zip
User:
- 📎main.c
Hardware:
- 📎MySPI.c📎MySPI.h📎OLED.c📎OLED.h📎OLED_Font.h📎W25Q64.c📎W25Q64.h📎W25Q64_Ins.h