STM32的SPI通信(硬件读取W25Q64)

相比比软件读取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 要求
相关推荐
C灿灿数模26 分钟前
2025国赛数学建模C题详细思路模型代码获取,备战国赛算法解析——决策树
c语言·算法·数学建模
天和地丰2 小时前
AAltium SVN Database Library 配置使用说明
数据库·嵌入式硬件·svn
凤年徐2 小时前
【数据结构与算法】刷题篇——环形链表的约瑟夫问题
c语言·数据结构·c++·算法·链表
Star在努力2 小时前
20-C语言:第21~22天笔记
java·c语言·笔记
艾莉丝努力练剑3 小时前
【C/C++】形参、实参相关内容整理
c语言·开发语言·c++·学习
Naiva4 小时前
【兆易创新】单片机GD32F103C8T6系列入门资料
单片机·嵌入式硬件
Crazy learner4 小时前
深入理解 C 语言中的拷贝函数
服务器·c语言·网络
WD137298015574 小时前
400V降24V,200mA,应用领域:从生活到工业的 “全能电源管家”WD5208
stm32·单片机·嵌入式硬件·51单片机
欲儿4 小时前
Kotlin Native调用C curl
c语言·开发语言·kotlin·语言调用