1. 项目概述
最近在做一个需要大容量存储的项目,W25Q64这颗64Mbit的SPI Flash芯片进入了我的视线。作为嵌入式开发中的"老朋友",W25Q系列以其稳定的性能和亲民的价格,成为了很多项目中外部存储的首选方案。今天我就来详细聊聊如何在STM32平台上,通过硬件SPI和软件模拟SPI两种方式操作这款芯片。
在实际项目中,我们常常会遇到硬件SPI引脚被占用的情况,这时候软件模拟SPI就派上用场了。不过两者在性能和稳定性上的差异,以及具体实现时的注意事项,都是需要开发者心中有数的。接下来,我会从芯片特性、硬件连接、驱动编写到性能测试,一步步带大家深入理解SPI Flash的操作要点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. W25Q64芯片解析
2.1 基本特性与内部结构
W25Q64是Winbond公司推出的一款SPI接口的NOR Flash,容量为64Mbit(8MB),采用3.3V供电。这颗芯片有几个关键特性值得关注:
- 支持标准SPI、Dual SPI和Quad SPI模式
- 分为128个块(Block),每块64KB
- 每个块又分为16个扇区(Sector),每扇区4KB
- 页(Page)大小为256字节
- 擦写寿命典型值10万次
- 数据保存期限20年
芯片内部结构上,W25Q64由多个存储阵列组成,通过内部逻辑控制器管理数据的读写和擦除操作。值得注意的是,Flash存储器的特性决定了它不能像RAM那样直接覆盖写入,必须先擦除再写入。
2.2 关键指令集解析
W25Q64通过SPI接口接收各种指令来实现不同功能,以下是几个最常用的指令:
- 写使能(WREN, 0x06):在执行任何写操作前必须发送
- 页编程(PP, 0x02):写入数据,每次最多写入256字节
- 扇区擦除(SE, 0x20):擦除4KB大小的扇区
- 块擦除(BE, 0xD8):擦除64KB大小的块
- 芯片擦除(CE, 0xC7):擦除整个芯片
- 读数据(READ, 0x03):读取数据
- 读状态寄存器1(RDSR1, 0x05):查询芯片状态
特别注意:每次写操作前必须发送WREN指令,且每次上电后默认是写禁止状态。
3. 硬件设计与连接
3.1 硬件SPI接口配置
以STM32F103系列为例,硬件SPI的典型连接方式如下:
| W25Q64引脚 | STM32引脚 | 功能说明 |
|---|---|---|
| CS | PA4 | 片选信号 |
| DO(IO1) | PA6(MISO) | 主入从出 |
| DI(IO0) | PA7(MOSI) | 主出从入 |
| CLK | PA5(SCK) | 时钟信号 |
| VCC | 3.3V | 电源 |
| GND | GND | 地线 |
硬件SPI的优势在于其时钟频率可以很高(理论上可达18MHz),且由硬件自动处理时钟和数据同步,CPU占用率低。
3.2 软件模拟SPI的GPIO配置
当硬件SPI不可用时,我们可以用任意GPIO模拟SPI时序:
c复制// 定义软件SPI引脚
#define SOFT_SPI_CS_PIN GPIO_PIN_4
#define SOFT_SPI_CS_PORT GPIOA
#define SOFT_SPI_SCK_PIN GPIO_PIN_5
#define SOFT_SPI_SCK_PORT GPIOA
#define SOFT_SPI_MOSI_PIN GPIO_PIN_7
#define SOFT_SPI_MOSI_PORT GPIOA
#define SOFT_SPI_MISO_PIN GPIO_PIN_6
#define SOFT_SPI_MISO_PORT GPIOA
软件SPI的灵活性高,但速度较慢(通常在几百KHz级别),且会占用较多CPU资源。
4. 驱动实现详解
4.1 硬件SPI驱动实现
首先初始化硬件SPI接口:
c复制void SPI1_Init(void)
{
SPI_HandleTypeDef hspi1;
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 9MHz @72MHz PCLK
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
}
然后是基本的读写函数:
c复制uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{
uint8_t RxData;
HAL_SPI_TransmitReceive(&hspi1, &TxData, &RxData, 1, 1000);
return RxData;
}
void
