相比比软件读取W25Q64,硬件读取W25Q64只有SPI代码部分不同。(详情结合先前软件读取W25Q64)
1.SPI代码详解
MySPI_W_SS 控制 CS 引脚
cpp
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
控制 W25Q64 的 片选信号(CS)
当为 0 时选中从机
当为 1 时释放从机
非自动控制,因此设置 SPI_NSS = SPI_NSS_Soft
MySPI_Init 初始化硬件 SPI
cpp
void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
// 配置 PA4 为推挽输出(控制 CS)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置 SCK(PA5)和 MOSI(PA7)为复用推挽输出(SPI 输出线)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置 MISO(PA6)为上拉输入(SPI 输入线)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 主机模式
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 全双工
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8 位数据帧
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 先发高位
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
// 降速至适合Flash
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 空闲时 SCK 为低
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 第一个时钟沿采样数据
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件控制片选(我们用 PA4 控制)
SPI_InitStructure.SPI_CRCPolynomial = 7; // CRC 多项式(不启用 CRC 也要写)
SPI_Init(SPI1, &SPI_InitStructure); // 配置 SPI1 寄存器
SPI_Cmd(SPI1, ENABLE); // 使能 SPI1
MySPI_W_SS(1); // 默认不选中任何设备
}
STM32 引脚 | 模拟含义 | 接 W25Q64 引脚 |
---|---|---|
PA4 | 软件 CS | /CS |
PA5 | SCK | CLK |
PA6 | MISO | DO |
PA7 | MOSI | DI |
交换数据函数(发送一个字节,同时接收一个字节)
cpp
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
// 等待发送缓冲区为空
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
SPI_I2S_SendData(SPI1, ByteSend); // 将数据写入 SPI 数据寄存器,启动传输
// 等待接收缓冲区非空(收到数据了)
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
return SPI_I2S_ReceiveData(SPI1); // 读取接收数据(MISO 上收到的字节)
}
SPI 是边发边收的同步通信协议
一旦写入数据,SPI 会自动开始以 SCK 时钟输出每一位
同时也会通过 MISO 接收从机发回的数据
SPI 寄存器背后原理补充
寄存器 | 作用 |
---|---|
SPI_DR |
数据寄存器,写入触发传输,读取取数据 |
SPI_SR.TXE |
发送缓冲区空标志(=1 时表示可以写数据) |
SPI_SR.RXNE |
接收缓冲区非空标志(=1 时表示收到数据) |
SPI_CR1 |
控制 SPI 配置(模式、主从、使能等) |
和软件 SPI 有何不同?
项目 | 软件 SPI(模拟) | 硬件 SPI(本例) |
---|---|---|
时钟控制 | 手动控制 SCK | 自动生成 SCK |
数据读取 | 手动采样 MISO | 自动采样,自动寄存器处理 |
电平操作 | GPIO 推拉电平控制 | SPI1 外设管理 SCK、MOSI、MISO |
优势 | 灵活、调试方便 | 高速稳定、准确同步、自动处理 |
硬件 SPI 读取过程就是:初始化 GPIO + SPI → 控制 CS → 发送指令 + 地址 → 读回数据 → 释放 CS,在 STM32 中全靠 SPI1 寄存器自动完成 SCK、MOSI、MISO 的逻辑,只需处理数据本身。
2.主模式全双工连续传输和非连续传输
什么是主模式、全双工?
术语 | 含义 |
---|---|
主模式 | STM32 控制 SCK 和 CS,是通信的发起者 |
全双工 | MOSI 与 MISO 同时工作,STM32 发数据的同时也能收数据 |
在 STM32 的 SPI 中:
发送数据:通过 SPI_DR 写入,自动输出到 MOSI
接收数据:SPI 自动在时钟作用下采样 MISO,将值写入 SPI_DR,并置位 RXNE
连续传输(Continuous Transmission)
主机一次性发送/接收多个字节,中间不释放 CS,SCK 时钟连续。
用于一次发送长指令、长数据块、连续读写 Flash、LCD、传感器等。
连续传输(典型:读取 W25Q64 数据)
cpp
MySPI_Start(); // CS 拉低,开始通信
MySPI_SwapByte(0x03); // 发送读取数据指令
MySPI_SwapByte(addr >> 16); // 地址高字节
MySPI_SwapByte(addr >> 8); // 地址中字节
MySPI_SwapByte(addr); // 地址低字节
for (i = 0; i < N; i++)
{
buf[i] = MySPI_SwapByte(0x00); // 每发一个 Dummy Byte,同步读一字节
}
MySPI_Stop(); // CS 拉高,结束通信
特点:
CS 一直为低
发送地址后直接连续读取数据
MISO 数据连续有效,SPI 寄存器每次接收 1 字节
优势:
传输高效,不丢时钟
适用于 Flash、屏幕、ADC 等流式通信
非连续传输(Non-Continuous Transmission)
每发送一个/几个字节,就停止通信(CS 拉高),下一次再重新拉低 CS 重新通信。
用于:命令阶段和数据阶段分开的通信,或需手动分段管理状态的外设。
非连续传输(典型:读状态寄存器)
cpp
for (i = 0; i < 3; i++)
{
MySPI_Start(); // 每次都重新选中
MySPI_SwapByte(0x05); // 状态寄存器读取指令
state[i] = MySPI_SwapByte(0x00);
MySPI_Stop(); // 释放从机
}
特点:
每次读取都重新拉低 CS → 通信"断断续续"
部分外设需要重新片选才能响应新指令
每次通信都要"热启动",可能会慢一点
使用场景:
写命令 → 拉高 CS(让从机执行)→ 再拉低 → 读状态
寄存器级访问,结构上需拆分
SPI 全双工连续 vs 非连续传输底层行为对比
项目 | 连续传输 | 非连续传输 |
---|---|---|
CS 是否保持拉低 | 是 | 否,每次拉高再拉低 |
SCK 是否连续 | 是 | 否,中断后时钟重新起始 |
SPI_DR 是否连续使用 | 是 | 否,每次重新启动 |
效率 | 高 | 低(含控制指令间隔) |
外设是否要求 | 适合 Flash、屏幕、Sensor 等 | 有些寄存器访问或状态检查必须断开 |
3. SPI_InitTypeDef 结构体详解
cpp
typedef struct
{
uint16_t SPI_Direction;
uint16_t SPI_Mode;
uint16_t SPI_DataSize;
uint16_t SPI_CPOL;
uint16_t SPI_CPHA;
uint16_t SPI_NSS;
uint16_t SPI_BaudRatePrescaler;
uint16_t SPI_FirstBit;
uint16_t SPI_CRCPolynomial;
} SPI_InitTypeDef;
1.SPI_Direction --- 数据传输方向(数据线模式)
含义:
配置 SPI 是 全双工、半双工 还是 单线接收/发送
可选值:
值 | 含义 |
---|---|
SPI_Direction_2Lines_FullDuplex |
双线全双工(常用) |
SPI_Direction_2Lines_RxOnly |
双线接收(主收从发) |
SPI_Direction_1Line_Rx |
单线接收(用于半双工) |
SPI_Direction_1Line_Tx |
单线发送(用于半双工) |
实际用途:
一般与 Flash、OLED、Sensor 通信,使用全双工即可。
某些 IO 资源受限场合用单线 SPI。
2.SPI_Mode --- 工作模式(主机 or 从机)
可选值:
值 | 含义 |
---|---|
SPI_Mode_Master |
主机(发时钟) |
SPI_Mode_Slave |
从机(收时钟) |
实际用途:
STM32 控制外设时选择 Master,比如连接 W25Q64、MPU9250、OLED 等。
3. SPI_DataSize --- 数据位宽(数据帧大小)
可选值:
值 | 含义 |
---|---|
SPI_DataSize_8b |
每帧 8 位(常用) |
SPI_DataSize_16b |
每帧 16 位 |
注意:
STM32 SPI 的实际发送/接收单位
与外设通信必须匹配外设要求的帧长度
4.SPI_CPOL --- 时钟极性(Clock Polarity)
可选值:
值 | 含义 |
---|---|
SPI_CPOL_Low |
空闲时 SCK 为低电平(常用) |
SPI_CPOL_High |
空闲时 SCK 为高电平 |
举例:
W25Q64 支持 CPOL = 0 或 CPOL = 1
SSD1306 OLED 通常用 CPOL = 0
5. SPI_CPHA --- 时钟相位(Clock Phase)
可选值:
值 | 含义 |
---|---|
SPI_CPHA_1Edge |
第一个边沿采样(常用) |
SPI_CPHA_2Edge |
第二个边沿采样 |
需要 与外设的 SPI 模式匹配(0~3):
SPI 模式 | CPOL | CPHA | 说明 |
---|---|---|---|
Mode 0 | 0 | 0 | 最常用(上升采样) |
Mode 1 | 0 | 1 | 下降沿采样 |
Mode 2 | 1 | 0 | 上升沿采样(空闲高) |
Mode 3 | 1 | 1 | 下降沿采样(空闲高) |
6. SPI_NSS --- NSS(片选)控制方式
可选值:
值 | 含义 |
---|---|
SPI_NSS_Hard |
NSS 硬件控制(主机一般不用) |
SPI_NSS_Soft |
NSS 软件控制(常用) |
配合 SPI_CR1.SSM = 1 和 SSI = 1 表示软件片选模式
如果用的软件方式控制 PA4 管脚实现 CS 拉低/拉高,选择 SPI_NSS_Soft
7. SPI_BaudRatePrescaler --- 波特率分频系数
作用:
配置 SPI 时钟频率(SCK = f_APB2 / Prescaler)
可选值:
值 | 分频 | 举例(72MHz 主频) |
---|---|---|
SPI_BaudRatePrescaler_2 |
/2 | 36MHz |
SPI_BaudRatePrescaler_4 |
/4 | 18MHz |
... |
... | ... |
SPI_BaudRatePrescaler_128 |
/128 | 562.5kHz(常用) |
注意:Flash 等外设通常支持 20MHz 以下,所以一般设置 1~2MHz。
8. SPI_FirstBit --- 先发送哪一位
可选值:
值 | 含义 |
---|---|
SPI_FirstBit_MSB |
高位先传(常用) |
SPI_FirstBit_LSB |
低位先传 |
注意:
大部分外设要求 MSB first,否则会数据错位!
9. SPI_CRCPolynomial --- CRC 多项式(校验用)
CRC 是 SPI 的可选校验机制(不常用)
通常设为 7,但如果没启用 CRC,设置成任意值也无所谓
字段 | 配置 | 解释 |
---|---|---|
Mode | 主机 | STM32 控制 SCK、CS |
Direction | 全双工 | 支持同时读写 |
DataSize | 8 位 | 每次 1 字节数据 |
CPOL/CPHA | 模式 0 | 空闲低,1st 边沿采样 |
NSS | 软件片选 | 使用 MySPI_Start() 控制 CS |
Prescaler | ÷128 | SCK ≈ 562.5kHz |
FirstBit | MSB | 高位先发,符合 Flash 要求 |