前言:在前面的文章中,我们已经系统介绍了 SPI 通信原理 以及 W25Q64 的存储结构和操作特性 。
本篇文章将进入实战阶段,基于 STM32F103C8T6 ,通过 软件 SPI(GPIO 模拟 SPI) 的方式,实现对 W25Q64 外部 Flash 的初始化、ID 读取、扇区擦除、页写入和数据读取,并通过 OLED 显示结果进行验证。
目录
[三、软件 SPI 分层设计思想](#三、软件 SPI 分层设计思想)
[四、软件 SPI 底层实现](#四、软件 SPI 底层实现)
[五、W25Q64 指令宏定义](#五、W25Q64 指令宏定义)
[六、W25Q64 驱动实现](#六、W25Q64 驱动实现)
一、接线图

二、硬件连接说明
本实验使用 STM32F103C8T6 与 W25Q64 通过 SPI 方式连接,引脚定义如下:
| W25Q64 | STM32 |
|---|---|
| CS | PA4 |
| SCK | PA5 |
| MISO | PA6 |
| MOSI | PA7 |
| VCC | 3.3V |
| GND | GND |
SPI 工作模式:Mode 0(CPOL = 0,CPHA = 0)
**三、**软件 SPI 分层设计思想
为了让代码结构清晰,本工程将软件 SPI 分为两层:
1.引脚配置层(GPIO 操作)
-
只关心:SS / SCK / MOSI / MISO
-
提供"写引脚 / 读引脚"的接口
2. 协议层(SPI 时序)
-
基于引脚操作实现:
-
SPI 起始 / 终止
-
字节交换(8 位时序)
-
W25Q64 驱动层 完全不关心 GPIO 细节,只调用 SPI 接口,结构非常清晰。
四、软件 SPI 底层实现
-
SPI 引脚操作函数
#include "stm32f10x.h" // Device header
/引脚配置层/
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
} -
SPI GPIO 初始化
void MySPI_Init(void)
{
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_Init(GPIOA, &GPIO_InitStructure); MySPI_W_SS(1); // CS 默认拉高 MySPI_W_SCK(0); // SPI Mode0:SCK 空闲为低}
-
SPI 起始 / 终止
void MySPI_Start(void)
{
MySPI_W_SS(0);
}void MySPI_Stop(void)
{
MySPI_W_SS(1);
} -
SPI 字节交换(Mode 0)
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00;for (i = 0; i < 8; i++) { MySPI_W_MOSI(ByteSend & (0x80 >> i)); MySPI_W_SCK(1); if (MySPI_R_MISO()) { ByteReceive |= (0x80 >> i); } MySPI_W_SCK(0); } return ByteReceive;}
说明:
-
上升沿发送数据
-
上升沿采样 MISO
-
完全符合 SPI Mode 0 时序
五、W25Q64 指令宏定义
#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H
#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_READ_DATA 0x03
#define W25Q64_JEDEC_ID 0x9F
#define W25Q64_DUMMY_BYTE 0xFF
#endif
六、W25Q64 驱动实现
-
初始化
void W25Q64_Init(void)
{
MySPI_Init();
} -
读取芯片 ID(验证通信是否成功)
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_JEDEC_ID);
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID <<= 8;
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
MySPI_Stop();
} -
写使能与忙等待
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}void W25Q64_WaitBusy(void)
{
uint32_t Timeout = 100000;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
while (MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01)
{
if (--Timeout == 0) break;
}
MySPI_Stop();
} -
扇区擦除(4KB)
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
MySPI_Stop();
W25Q64_WaitBusy();
} -
页写入(不跨页)
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
uint16_t i;
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for (i = 0; i < Count; i++)
{
MySPI_SwapByte(DataArray[i]);
}
MySPI_Stop();
W25Q64_WaitBusy();
} -
数据读取
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for (i = 0; i < Count; i++)
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
}
MySPI_Stop();
}
七、主函数测试与实验现象
uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};
uint8_t ArrayRead[4];
int main(void)
{
OLED_Init();
W25Q64_Init();
W25Q64_ReadID(&MID, &DID);
W25Q64_SectorErase(0x000000);
W25Q64_PageProgram(0x000000, ArrayWrite, 4);
W25Q64_ReadData(0x000000, ArrayRead, 4);
while (1) {}
}
实验现象:
-
OLED 正确显示 MID / DID
-
写入数组与读取数组完全一致
-
说明:
软件 SPI + W25Q64 读写擦除功能完全正常
八、总结
通过本实验,我们完整实现了:
-
软件 SPI 的 GPIO 模拟
-
W25Q64 的 ID 读取
-
扇区擦除、页写入、数据读取
-
STM32 与外部 Flash 的稳定通信