PPT 参考图 -- SPI协议与时序
全双工同步通信本质:SPI 通信时,主从双方各有一个移位寄存器,通过 SCK 同步。一个 SCK 时钟周期内,主机从 MOSI 移出一位数据到从机,同时从机从 MISO 移出一位数据到主机。因此 SPI 的每次数据交换都是同时发送和接收——发送一个字节的同时必然收到一个字节。
移位寄存器工作原理:主机的移位寄存器在 SCK 的驱动下,每来一个时钟沿,将最高位移出到 MOSI,同时从 MISO 移入一位到最低位。8 个 SCK 周期后,主机发送的 8 位数据全部移入从机,从机的 8 位数据全部移入主机。硬件 SPI 的 SPI_I2S_SendData 将数据写入发送缓冲区,然后硬件自动完成移位交换,SPI_I2S_ReceiveData 读取接收缓冲区的内容。
4 种 CPOL/CPHA 模式:
片选 CS 的作用:CS 低电平时从机被选中,开始监听 SCK 和 MOSI 信号;CS 高电平时从机忽略总线信号,MISO 引脚进入高阻态。多从机系统中,同一时刻只能有一个从机的 CS 为低,否则多个从机同时在 MISO 上输出数据会引发总线冲突。
SS 软件 vs 硬件模式:
SPI_NSS_Soft):CS 由普通 GPIO 手动控制,代码中拉低 CS 开始通信,拉高结束。灵活通用,推荐使用SPI_NSS_Hard):CS 由 SPI 外设自动控制,多从机时配置复杂,不推荐W25Q64 页编程限制:写入操作不得跨 256 字节页边界。例如向地址 0x00FF 写入 4 字节,前 1 字节写入页 0 的最后一个字节,后 3 字节会回卷到页 0 的第一个字节,而不是写入下一页。因此跨页写入时必须分两次 Page Program 操作。
设备:一主多从 (通过 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 ```\____/``\____/``\__
采样在上升沿 (后沿)
PPT 参考图 -- W25Q64简介与框图
| 参数 | 值 |
|---|---|
| 容量 | 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) |
交换一个字节 (发送+接收) |
PPT 参考图 -- SPI外设与框图
| 函数 | 说明 |
|---|---|
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 模式区分