设备:一主多从 (通过 CS 片选)
+-------+
|Master |---+ MISO
| |---+ MOSI
| |---+ SCK
| |---+ CS1
| |---+ CS2
+-------+ |
| | | |
+-------+ |
|Slave1 |---+---+
+-------+ | |
+-------+ | |
|Slave2 |---+---+
+-------+
| 参数 | 可选值 | 说明 |
|---|---|---|
| 波特率 | f_PCLK / {2,4,8,16,32,64,128,256} | 通信速率 |
| 数据位 | 8 位 / 16 位 | 每帧数据宽度 |
| 比特序 | MSB first / LSB first | 高位/低位先发 |
| CPOL (时钟极性) | 0=空闲低 / 1=空闲高 | SCK 空闲电平 |
| CPHA (时钟相位) | 0=前沿采样 / 1=后沿采样 | 采样时刻 |
Mode 0 (CPOL=0, CPHA=0) ★最常用
SCK ____/``\__/``\__/``\__
MOSI X---X---X---X
MISO X---X---X---X
采样在上升沿 (前沿)
Mode 1 (CPOL=0, CPHA=1)
SCK ____/``\__/``\__/``\__
采样在下降沿 (后沿)
Mode 2 (CPOL=1, CPHA=0)
SCK ```\____/``\____/``\__
采样在下降沿 (前沿)
Mode 3 (CPOL=1, CPHA=1)
SCK ```\____/``\____/``\__
采样在上升沿 (后沿)
| 参数 | 值 |
|---|---|
| 容量 | 64 Mbit = 8 MB |
| 页 | 256 字节 |
| 扇区 (Sector) | 4 KB (16 页) |
| 块 (Block) | 64 KB (16 扇区) |
| 最大时钟 | 104 MHz |
| 擦除寿命 | 约 10 万次 |
关键指令:
| 指令 | 码值 | 说明 |
|---|---|---|
| Write Enable | 0x06 | 写使能 (每写之前必发) |
| Write Disable | 0x04 | 写禁止 |
| Read Status Register | 0x05 | 读状态寄存器 (BUSY=bit0) |
| Read Data | 0x03 | 读数据 (3 字节地址) |
| Page Program | 0x02 | 页编程 (最多 256 字节) |
| Sector Erase | 0x20 | 扇区擦除 (4KB) |
| Block Erase 64KB | 0xD8 | 块擦除 |
| Chip Erase | 0xC7 | 整片擦除 |
| Power Down | 0xB9 | 掉电 |
| Release Power Down | 0xAB | 唤醒 |
| Read ID | 0x90 | 读制造商/设备 ID |
| Read Unique ID | 0x4B | 读唯一 ID |
Flash 特性:只能 1→0 (写 0),擦除恢复为 1。写入前必须擦除。
| 函数 | 说明 |
|---|---|
void SPI_Start(void) |
CS 拉低 |
void SPI_Stop(void) |
CS 拉高 |
uint8_t SPI_SwapByte(uint8_t Byte) |
交换一个字节 (发送+接收) |
| 函数 | 说明 |
|---|---|
SPI_InitTypeDef |
配置结构体 |
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct) |
初始化 SPI |
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState) |
使能 SPI |
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data) |
发送数据 |
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) |
接收数据 |
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint32_t SPI_I2S_FLAG) |
获取标志状态 |
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint32_t SPI_I2S_IT, FunctionalState NewState) |
中断配置 |
SPI_InitTypeDef:
| 成员 | 可选值 | 说明 |
|---|---|---|
SPI_Direction |
SPI_Direction_2Lines_FullDuplex |
双线全双工 |
SPI_Mode |
SPI_Mode_Master / SPI_Mode_Slave |
主/从模式 |
SPI_DataSize |
SPI_DataSize_8b / SPI_DataSize_16b |
数据位 |
SPI_CPOL |
SPI_CPOL_Low / SPI_CPOL_High |
时钟极性 |
SPI_CPHA |
SPI_CPHA_1Edge / SPI_CPHA_2Edge |
时钟相位 |
SPI_NSS |
SPI_NSS_Soft / SPI_NSS_Hard |
NSS 控制方式 |
SPI_BaudRatePrescaler |
SPI_BaudRatePrescaler_* |
波特率预分频 |
SPI_FirstBit |
SPI_FirstBit_MSB / SPI_FirstBit_LSB |
比特序 |
SPI_CRCPolynomial |
7 (默认) | CRC 多项式 |
常用标志:
| 标志 | 说明 |
|---|---|
SPI_FLAG_TXE |
发送缓冲区空 (可写 DR) |
SPI_FLAG_RXNE |
接收缓冲区非空 (可读 DR) |
SPI_FLAG_BSY |
忙标志 |
SPI_FLAG_OVR |
溢出错误 |
/*------------------------------------------------
* SPI_W25Q64.c
* 硬件 SPI1: PA5(SCK), PA6(MISO), PA7(MOSI), PA4(CS)
*------------------------------------------------*/
#include "stm32f10x.h"
#include <stdint.h>
#include <string.h>
/* SPI1 引脚 */
#define W25Q64_CS_PORT GPIOA
#define W25Q64_CS_PIN GPIO_Pin_4
/* W25Q64 指令 */
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
#define W25X_ReadStatusReg 0x05
#define W25X_ReadData 0x03
#define W25X_PageProgram 0x02
#define W25X_SectorErase 0x20
#define W25X_ReadID 0x90
/* ---- CS 控制 ---- */
static void W25Q64_CS_Low(void)
{
GPIO_ResetBits(W25Q64_CS_PORT, W25Q64_CS_PIN);
}
static void W25Q64_CS_High(void)
{
GPIO_SetBits(W25Q64_CS_PORT, W25Q64_CS_PIN);
}
/* ---- 硬件 SPI 初始化 ---- */
static void SPI_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
SPI_InitTypeDef SPI_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);
/* PA5(SCK), PA7(MOSI): 复用推挽 */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
/* PA6(MISO): 浮空输入 */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
/* PA4(CS): 推挽输出 */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS 高 = 释放
/* SPI1: Mode0, 主模式, 8位, 18MHz */
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 72/4=18MHz
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStruct.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStruct);
SPI_Cmd(SPI1, ENABLE);
}
/* ---- SPI 字节交换 ---- */
static uint8_t SPI_SwapByte(uint8_t Byte)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, Byte);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return (uint8_t)SPI_I2S_ReceiveData(SPI1);
}
/* ---- 写指令/地址 (CS 已拉低) ---- */
static void W25Q64_WriteCmd(uint8_t cmd)
{
SPI_SwapByte(cmd);
}
/* ---- 写使能 (每次擦/写之前必须) ---- */
static void W25Q64_WriteEnable(void)
{
W25Q64_CS_Low();
W25Q64_WriteCmd(W25X_WriteEnable);
W25Q64_CS_High();
}
/* ---- 等待空闲 (BUSY=0) ---- */
static void W25Q64_WaitBusy(void)
{
uint8_t sr;
do {
W25Q64_CS_Low();
W25Q64_WriteCmd(W25X_ReadStatusReg);
sr = SPI_SwapByte(0xFF);
W25Q64_CS_High();
} while (sr & 0x01);
}
/* ---- 读 ID ---- */
uint16_t W25Q64_ReadID(void)
{
uint16_t id;
W25Q64_CS_Low();
W25Q64_WriteCmd(W25X_ReadID);
SPI_SwapByte(0x00); SPI_SwapByte(0x00); SPI_SwapByte(0x00); // dummy
id = (uint16_t)SPI_SwapByte(0xFF) << 8; // 制造商 ID
id |= SPI_SwapByte(0xFF); // 设备 ID
W25Q64_CS_High();
return id;
// W25Q64: 制造商=0xEF, 设备=0x16 (EF16)
}
/* ---- 读数据 ---- */
void W25Q64_ReadData(uint32_t addr, uint8_t* buf, uint32_t len)
{
uint32_t i;
W25Q64_CS_Low();
W25Q64_WriteCmd(W25X_ReadData);
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
for (i = 0; i < len; i++)
buf[i] = SPI_SwapByte(0xFF);
W25Q64_CS_High();
}
/* ---- 扇区擦除 (4KB) ---- */
void W25Q64_SectorErase(uint32_t addr)
{
W25Q64_WriteEnable();
W25Q64_WaitBusy();
W25Q64_CS_Low();
W25Q64_WriteCmd(W25X_SectorErase);
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
W25Q64_CS_High();
W25Q64_WaitBusy();
}
/* ---- 页编程 (最多 256 字节) ---- */
void W25Q64_PageProgram(uint32_t addr, const uint8_t* data, uint32_t len)
{
uint32_t i;
if (len > 256) len = 256;
W25Q64_WriteEnable();
W25Q64_WaitBusy();
W25Q64_CS_Low();
W25Q64_WriteCmd(W25X_PageProgram);
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
for (i = 0; i < len; i++)
SPI_SwapByte(data[i]);
W25Q64_CS_High();
W25Q64_WaitBusy();
}
/* ---- 整片擦除 ---- */
void W25Q64_ChipErase(void)
{
W25Q64_WriteEnable();
W25Q64_WaitBusy();
W25Q64_CS_Low();
W25Q64_WriteCmd(0xC7);
W25Q64_CS_High();
W25Q64_WaitBusy();
}
/* ---- 主函数 ---- */
int main(void)
{
uint8_t buf[256];
uint16_t chipID;
/* 初始化 SysTick 延时 (假设已在其他地方实现 Delay_ms) */
SPI_Config();
chipID = W25Q64_ReadID();
/* 写入示例 */
uint8_t writeData[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
W25Q64_SectorErase(0x000000); // 擦除扇区 0
W25Q64_PageProgram(0x000000, writeData, 8); // 写 8 字节
W25Q64_ReadData(0x000000, buf, 8); // 读回验证
while (1);
}
SPI1 与 SPI2 挂载不同总线
NSS 软件控制更灵活
SPI_NSS_Soft 模式下,CS 用普通 GPIO 推挽输出手动拉低/拉高发送的同时也在接收
W25Q64 写入前必擦除
等待 BUSY
页编程不可跨页
Mode0 最常用
GPIO 模式区分