当前位置: 首页 > news >正文

HAL_SPI (FLASH)

一、SPI 简介

1、SPI

SPI(Serial Peripheral Interface,串行外设接口),是 STM32 内置的高速、全双工、同步串行通信总线,是嵌入式中传输速度最快、使用最广泛的板级通信协议,主要用于高速外设数据交互。
相较于 IIC、UART,SPI 传输速率极高、时序简单、稳定性强,是屏幕、SPI Flash、SD 卡、高速传感器、以太网模块的核心通信总线。

2、SPI 核心硬件特点

四线通信(标配):SCLK、MOSI、MISO、NSS。
同步通信:完全跟随 SCLK 时钟节拍传输数据。
全双工通信:收发可同时进行,效率远超 IIC。
高速传输:最高可达几十 Mbps,远超 IIC、串口。
主从架构:一个主机可挂载多个从机,依靠片选区分设备。
无应答机制:纯高速移位传输,无校验、无应答。

3、四根信号线功能详解

SCLK(时钟线):主机输出时钟,控制数据传输速率与时序。
MOSI(主机输出、从机输入):主机发送数据、从机接收。
MISO(主机输入、从机输出):从机发送数据、主机接收。
NSS/CS(片选线):低电平选中设备,高电平断开通信。

4、SPI 总线架构

一对一架构:一个主机对应一个从机,最简用法。
一主多从架构:共用SCLK、MOSI、MISO,独立CS片选,分时通信。

5、典型应用场景

显示设备:SPI OLED、TFTLCD、墨水屏高速刷屏。
存储设备:W25Qxx 系列 SPI Flash、SD卡数据读写。
高速传感器:SPI ADC、陀螺仪、气压传感器。
无线模块:ESP8266、NRF24L01 射频通信。

二、SPI 模块详解

1、SPI功能框图

① 外部物理引脚区(橙色边框)

向外引出 SPI 硬件 4 路引脚,是 MCU 和 W25Q64 交互的物理通道:
MOSI:主机输出、从机输入,STM32 往 W25Q64 下发指令、地址、待写入数据;
MISO:主机输入、从机输出,接收 W25Q64 传回的 ID、存储数据、状态信息;
SCK:SPI 同步时钟输出,波特率发生器产生时钟从此引脚输出,把控双方采样时序;
NSS:片选引脚,可选硬件自动控制 / 软件 GPIO 控制两种模式,驱动 W25Q64 项目普遍采用软件模式,外部 GPIO 拉低选中 FLASH。

② 时钟发生主控区域(紫色模块)

波特率发生器:
依据SPI_CR1寄存器内BR[2:0]三位分频配置,对 APB 总线时钟分频生成 SCK 时钟:SPI1 时钟源自 APB2(最高 72MHz)、SPI2/3 源自 APB1(最高 36MHz),分频档位支持 2~256 八档可调,适配 W25Q64 不同通信速率需求;
主控制电路:
读取SPI_CR1所有配置位(CPOL、CPHA、MSTR 主从位、SSM/SSI 片选模式、LSBFIRST 字节序、DFF 数据宽度、双向模式等),统筹整个 SPI 模块的工作模式、采样边沿、NSS 控制逻辑。

③ 数据移位收发缓冲区(绿色数据通路)

这是 SPI 完成全双工收发的核心数据流路径:
发送缓冲区:
CPU 经由地址数据总线写入待发送字节,底层对接 SPI_DR 寄存器;
移位寄存器:
按照 LSBFIRST 设置选择 MSB 先行(适配 W25Q64 必须选用该模式)/LSB 先行,并行数据转换成串行比特,从 MOSI 逐位输出;与此同时 MISO 侧把从机发来的串行比特并行锁存,实现同步全双工交换;
接收缓冲区:
缓存移位寄存器接收完成的数据,CPU 通过总线读取 SPI_DR 即可拿到 FLASH 传回的数据;
关键细节:
SPI 一次移位动作就同步完成一发一收,哪怕我们仅想要向 W25Q64 发送指令,硬件层面依旧会同步完成一次接收,因此代码里读写接口必须设计成「写一个字节同时拿回返回字节」的形式。

④ 寄存器控制与状态反馈区(黄色寄存器模块)

(1)SPI_CR1:主控制寄存器
框图把 CR1 全部有效位做了可视化排布:
LSBFIRST、SPE外设总开关、BR2~BR0分频位、MSTR主从选择、CPOL、CPHA、BIDIMODE、BIDIOE、CRCEN、CRCNEXT、DFF帧宽、RXONLY、SSM、SSI,所有时序、模式、帧格式配置均在此寄存器完成。
(2)SPI_CR2:中断、DMA 配置寄存器
位分布:TXEIE发送空中断、RXNEIE接收中断、ERRIE错误中断、SSOE硬件NSS输出开关、TXDMAEN发送DMA使能、RXDMAEN接收DMA使能;
轮询驱动 W25Q64:全部中断、DMA 位保持关闭;
大批量刷屏、大批量写入 FLASH:开启对应的 DMA 通道降低 CPU 占用;
(3)SPI_SR:状态只读寄存器,代码轮询判断传输的依据
关键标志位适配 FLASH 开发场景:
BSY总线忙位:
BSY=1 代表移位寄存器正在传输,不可发起新的读写,寄存器版代码必须等待 BSY 清零;
TXE发送缓存为空:
TXE 置 1 才可以往 SPI_DR 写入下一条发给 W25Q64 的数据;
RXNE接收缓存有效:
置 1 表示已经收到 FLASH 返回的数据,可以读取 DR;
OVR接收溢出、MODF 主从模式冲突、CRCERR 校验失败:用于排查 SPI 总线通信故障。

2、SPI工作原理

SPI 本质是双向移位寄存器同步交换数据。
主机拉低片选选中从机,主机输出 SCLK 时钟,在每个时钟边沿,主机与从机同时交换 1bit 数据,实现全双工同步收发,8/16bit 传输完成后结束一次通信。

3、工作模式

SPI 模式由 CPOL(时钟极性) 和 CPHA(时钟相位) 组合决定,必须与从机设备完全一致,否则通信乱码、失败。

工作模式CPOL 时钟极性CPHA 时钟相位数据采样规则常用设备
Mode 00(空闲低电平)0(第一个边沿采样)上升沿采样、下降沿移位OLED、TFTLCD、NRF24L01
Mode 10(空闲低电平)1(第二个边沿采样)下降沿采样、上升沿移位部分传感器
Mode 21(空闲高电平)0(第一个边沿采样)下降沿采样、上升沿移位W25Q Flash 常用
Mode 31(空闲高电平)1(第二个边沿采样)上升沿采样、下降沿移位高速外设

4、时钟分频与传输速率

SPI 时钟来源于 APB 总线时钟,通过预分频得到通信时钟,支持 2/4/8/16/32/64/128/256 分频。
主频72M的F103,SPI最高速率可达 36Mbps,是嵌入式最快板内通信总线。

5、数据传输规则

支持 高位先行 MSB(默认、最常用)、低位先行 LSB。
支持 8bit / 16bit 数据帧格式。
全双工收发同时进行,发数据必然同时收数据。

6、SPI 与 IIC / UART 核心区别

通信方式SPIIICUART
通信类型同步全双工同步半双工异步全双工
信号线4根2根2根
速度极高(Mbps级)低(100K/400K)中(百K级)
拓扑多片选、多设备地址寻址多设备一对一通信
应答机制无应答ACK应答无硬件应答

三、CubeMX 标准 SPI 配置步骤

1、CubeMX配置面板

2、配置 Mode

下拉选项对应SPI_InitTypeDef成员工作原理
Full-Duplex MasterMode = SPI_MODE_MASTER
Direction = SPI_DIRECTION_2LINES
双线 MOSI/MISO 独立,主动输出时钟,可同步下发指令、读取返回数据。
Full-Duplex SlaveMode = SPI_MODE_SLAVE
Direction = SPI_DIRECTION_2LINES
STM32 作为从设备,接收外部 MCU 的时钟信号。
Half-Duplex MasterMode = SPI_MODE_MASTER
Direction = SPI_DIRECTION_1LINE
单线双向传输,收发分时复用一根引脚。
Half-Duplex SlaveMode = SPI_MODE_SLAVE
Direction = SPI_DIRECTION_1LINE
单线模式下 STM32 充当从机。
Receive Only MasterMode = SPI_MODE_MASTER
Direction = SPI_DIRECTION_2LINES_RXONLY
主机仅接收 MISO 的数据,无法主动下发 FLASH 指令。
Receive Only SlaveMode = SPI_MODE_SLAVE
开启仅接收模式
STM32 作为从端只接收外部下发数据。
Transmit Only MasterMode = SPI_MODE_MASTER
仅启用 MOSI 发送通路
主机只能发送指令,拿不到 FLASH 传回的 ID、状态寄存器、存储数据。
Transmit Only SlaveMode = SPI_MODE_SLAVE
仅向外发送数据
STM32 充当从端发送数据。

3、Basic Parameters(基础参数组)

界面配置项取值**SPI_InitTypeDef **结构体赋值说明
Frame FormatMotorolahspi1.Init.TIMode = SPI_TIMODE_DISABLEMotorola 原生遵循的标准 SPI 协议
Data Size8 Bitshspi1.Init.DataSize = SPI_DATASIZE_8BIT传输数据宽度
First BitMSB Firsthspi1.Init.FirstBit = SPI_FIRSTBIT_MSB数据传输规则(高位先行 MSB)

4、Clock Parameters(时钟参数组,SPI 时序核心)

配置时钟分频系数:
Prescaler = 4 (根据需要调整)
SPI1 挂载 APB2 总线,主频为 72MHz,分频系数 4:72MHz / 4 = 18MHz,界面显示 Baud Rate=18.0MBits/s。
工作模式配置:
Clock Polarity (CPOL) = Low、Clock Phase (CPHA) = 1 Edge

5、Advanced Parameters(高级参数组)

CRC校验
CRC Calculation = Disabled
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
CS选择(软件片选模式)
MODE:Hardware NSS Signal = Disabled
NSS Signal Type = Software

6、关键参数说明

Clock Prescaler:时钟分频,速度越快传输越快,需兼容外设。
CPOL/CPHA:严格匹配从机设备时序。
NSS Software:软件控制片选,工程最稳定。
First Bit:MSB 高位优先,几乎所有设备通用。

7、配置注意事项

时序模式必须和从机完全一致,否则百分百乱码。
多设备共用SPI总线时,必须分时拉低对应CS片选。
高速通信时降低分频过高,防止外设时序不支持导致丢数。
SPI无硬件应答,通信异常需软件重试机制。

四、SPI编程模块

1、SPI结构体

1.底层硬件寄存器结构体 SPI_TypeDef

映射 STM32 片上 SPI 外设全部物理寄存器地址,是寄存器操作、标准库 / LL/HAL 库底层操作的硬件入口。

typedef struct { __IO uint16_t CR1; // 控制寄存器1 uint16_t RESERVED0; __IO uint16_t CR2; // 控制寄存器2 uint16_t RESERVED1; __IO uint16_t SR; // 状态寄存器 uint16_t RESERVED2; __IO uint16_t DR; // 数据寄存器 uint16_t RESERVED3; __IO uint16_t CRCPR; // CRC多项式寄存器 uint16_t RESERVED4; __IO uint16_t RXCRCR; // 接收CRC寄存器 uint16_t RESERVED5; __IO uint16_t TXCRCR; // 发送CRC寄存器 } SPI_TypeDef;

2.初始化配置结构体 SPI_InitTypeDef

统一封装 SPI 所有时序、帧格式、总线模式参数,CubeMX 自动填充,作为HAL_SPI_Init()入参完成外设初始化。

typedef struct { uint32_t Mode; // 主/从模式 uint32_t Direction; // 传输方向(全双工/单线) uint32_t DataSize; // 数据位宽 8bit/16bit uint32_t CLKPolarity; // CPOL 时钟极性 uint32_t CLKPhase; // CPHA 时钟相位 uint32_t NSS; // 软件/硬件片选模式 uint32_t BaudRatePrescaler; // APB时钟分频系数 uint32_t FirstBit; // MSB/LSB先行 uint32_t TIMode; // TI专属同步模式开关 uint32_t CRCCalculation; // CRC校验使能 uint32_t CRCPoly; // CRC多项式值 } SPI_InitTypeDef;

3.外设句柄结构体 SPI_HandleTypeDef

全局唯一 SPI 外设管理句柄,整合寄存器基地址、时序配置、总线状态、DMA 资源、中断回调,所有 HAL SPI API 均以此结构体指针作为入参。

typedef struct __SPI_HandleTypeDef { SPI_TypeDef *Instance; // 外设基地址 SPI1/SPI2/SPI3 SPI_InitTypeDef Init; // 初始化配置子结构体 HAL_LockTypeDef Lock; // 多任务互斥锁 __IO HAL_SPI_StateTypeDef State; // SPI运行状态机 __IO uint32_t ErrorCode; // 硬件故障错误标志 DMA_HandleTypeDef *hdmatx; // 发送DMA句柄指针 DMA_HandleTypeDef *hdmarx; // 接收DMA句柄指针 // 用户回调函数指针(中断/DMA传输完成触发) void (*TxCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (*RxCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (*TxRxCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (*TxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (*RxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (*ErrorCallback)(struct __SPI_HandleTypeDef *hspi); void (*AbortCpltCallback)(struct __SPI_HandleTypeDef *hspi); } SPI_HandleTypeDef;

4.状态枚举 HAL_SPI_StateTypeDef

typedef enum { HAL_SPI_STATE_RESET = 0x00U, // 外设未初始化 HAL_SPI_STATE_READY = 0x20U, // 空闲就绪,可发起传输 HAL_SPI_STATE_BUSY_TX = 0x21U, // 正在发送数据 HAL_SPI_STATE_BUSY_RX = 0x22U, // 正在接收数据 HAL_SPI_STATE_BUSY_TX_RX = 0x23U, // 全双工同步收发中 HAL_SPI_STATE_ERROR = 0xE0U, // 总线传输故障 HAL_SPI_STATE_ABORT = 0xF0U // 传输中止状态 } HAL_SPI_StateTypeDef;

5.互斥锁结构体 HAL_LockTypeDef

typedef enum { HAL_UNLOCKED = 0x00U, // 无占用,可访问SPI HAL_LOCKED = 0x01U // 正在使用,其他任务阻塞等待 } HAL_LockTypeDef;

6.SPI 错误码宏对应 ErrorCode

#define HAL_SPI_ERROR_NONE 0x00000000U // 无错误 #define HAL_SPI_ERROR_MODF 0x00000001U // 主从模式冲突(硬件NSS常见故障) #define HAL_SPI_ERROR_OVR 0x00000002U // 接收数据溢出 #define HAL_SPI_ERROR_CRC 0x00000004U // CRC校验错误 #define HAL_SPI_ERROR_DMA 0x00000008U // DMA传输异常 #define HAL_SPI_ERROR_TIMEOUT 0x00000010U // 传输超时

2、HAL_SPI_API

1.初始化、注销底层外设函数

HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi); 功能:读取句柄内SPI_InitTypeDef配置,完成 SPI 寄存器时序、模式、分频初始化,开启外设使能位 SPE;CubeMX 生成MX_SPIx_Init核心调用函数。 HAL_StatusTypeDef HAL_SPI_DeInit(SPI_HandleTypeDef *hspi); 功能:复位 SPI 外设所有寄存器,关闭 SPE 使能,清空句柄状态与错误标志。 void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi); 功能:底层硬件初始化回调(Msp=MCU Support Package),由HAL_SPI_Init内部自动调用;CubeMX 自动生成,内部完成 APB 总线时钟开启、SCLK/MOSI/MISO 引脚复用推挽配置。 void HAL_SPI_MspDeInit(SPI_HandleTypeDef *hspi); 功能:底层硬件资源释放回调,HAL_SPI_DeInit内部自动调用;关闭 SPI 总线时钟,GPIO 恢复普通输入模式。

2.动态注册 / 注销回调函数(可选功能宏开关 USE_HAL_SPI_REGISTER_CALLBACKS = 1U)

HAL_StatusTypeDef HAL_SPI_RegisterCallback(SPI_HandleTypeDef *hspi, HAL_SPI_CallbackIDTypeDef CallbackID, pSPI_CallbackTypeDef pCallback); 功能:运行时动态注册自定义回调函数,替代全局弱定义回调;支持多实例 SPI 分时复用不同业务逻辑。 HAL_StatusTypeDef HAL_SPI_UnRegisterCallback(SPI_HandleTypeDef *hspi, HAL_SPI_CallbackIDTypeDef CallbackID); 功能:注销已注册的指定 ID 回调,恢复默认空回调。

3.阻塞轮询 Polling

HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, const uint8_t *pData, uint16_t Size, uint32_t Timeout); 功能:单向阻塞发送,仅输出 MOSI,丢弃 MISO 返回数据;全程轮询 TXE/BSY 标志,超时返回HAL_TIMEOUT。 HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout); 功能:单向阻塞接收,持续输出空时钟脉冲,读取 MISO 数据;忽略 MOSI 输出内容。 HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, const uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout); 功能:全双工同步收发,每发送 1 字节同时接收 1 字节,SPI 标准通信核心 API。

4.中断非阻塞 IT(异步,不阻塞主循环)

HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, const uint8_t *pData, uint16_t Size); 功能:开启 SPI 发送中断,函数立即返回;全部数据发送完成触发HAL_SPI_TxCpltCallback。 HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size); 功能:开启 SPI 接收中断,收到指定长度数据触发HAL_SPI_RxCpltCallback。 HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, const uint8_t *pTxData, uint8_t *pRxData, uint16_t Size); 功能:中断模式全双工同步收发,传输完成触发HAL_SPI_TxRxCpltCallback。

5.DMA 高速传输(零 CPU 搬运,大批量 FLASH 读写)

HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, const uint8_t *pData, uint16_t Size); 功能:DMA 硬件自动搬运内存数据至 SPI 发送寄存器,CPU 不参与字节搬运。 HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size); 功能:DMA 将 MISO 接收数据直接存入内存数组,高速批量读取 FLASH。 HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, const uint8_t *pTxData, uint8_t *pRxData, uint16_t Size); 功能:DMA 全双工同步收发,超大容量连续读写最优方案。 HAL_StatusTypeDef HAL_SPI_DMAPause(SPI_HandleTypeDef *hspi); 功能:暂停当前 DMA 传输,缓存数据保留,可恢复继续传输。 HAL_StatusTypeDef HAL_SPI_DMAResume(SPI_HandleTypeDef *hspi); 功能:恢复被暂停的 DMA 传输,从暂停位置继续搬运数据。 HAL_StatusTypeDef HAL_SPI_DMAStop(SPI_HandleTypeDef *hspi); 功能:彻底关闭 DMA 通道,清空 DMA 寄存器与句柄传输状态。

6.传输中止函数(总线卡死、异常恢复)

HAL_StatusTypeDef HAL_SPI_Abort(SPI_HandleTypeDef *hspi); 功能:阻塞式中止当前任意模式传输,等待总线空闲后返回,同步释放 SPI。 HAL_StatusTypeDef HAL_SPI_Abort_IT(SPI_HandleTypeDef *hspi); 功能:异步中止中断 / DMA 传输,函数立即返回,中止完成触发HAL_SPI_AbortCpltCallback。

7.中断服务总入口(stm32f1xx_it.c 中断函数调用)

void HAL_SPI_IRQHandler(SPI_HandleTypeDef *hspi); 功能:SPI 全局中断统一处理入口,自动区分 TXE/RXNE 传输中断、OVR/MODF 错误中断,内部分发对应回调函数。

8.系统弱定义回调函数(用户可重写,中断 / DMA 完成触发)

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi); 单向发送全部数据完成回调。 void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi); 单向接收指定长度数据完成回调。 void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi); 全双工同步收发全部字节完成回调。 void HAL_SPI_TxHalfCpltCallback(SPI_HandleTypeDef *hspi); DMA 发送缓冲区一半数据传输完成,双缓冲分片处理大数据。 void HAL_SPI_RxHalfCpltCallback(SPI_HandleTypeDef *hspi); DMA 接收缓冲区一半数据接收完成。 void HAL_SPI_TxRxHalfCpltCallback(SPI_HandleTypeDef *hspi); DMA 全双工收发半缓冲区完成回调。 void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi); 通信故障触发(MODF 主从冲突、OVR 溢出、DMA 异常、超时),可调用HAL_SPI_GetError()定位故障。 void HAL_SPI_AbortCpltCallback(SPI_HandleTypeDef *hspi); 调用HAL_SPI_Abort_IT异步中止传输完成后触发。

9.外设状态、错误查询工具函数

HAL_SPI_StateTypeDef HAL_SPI_GetState(const SPI_HandleTypeDef *hspi); 功能:读取 SPI 实时运行状态(复位 / 就绪 / 发送忙 / 接收忙 / 收发忙 / 错误 / 中止),发起新传输前判断总线是否空闲,避免并发冲突。 uint32_t HAL_SPI_GetError(const SPI_HandleTypeDef *hspi); 功能:读取句柄内部错误标志位,返回HAL_SPI_ERROR_MODF/OVR/CRC/DMA/TIMEOUT等掩码,用于排查 W25Q64 读写失败、无返回数据等故障。

五、SPI 应用实例

1、硬件原理图FLASH(W25Q64)

2、CubeMX配置

1.系统时钟配置

启用 HSE 外部晶振,PLL 倍频得到系统主频 72MHz,APB2 预分频设置为 1,SPI1 总线时钟维持 72MHz。

2.SPI1 参数配置(Parameter Settings)

分类配置项取值
Basic ParametersModeFull-Duplex Master
Frame FormatMotorola
Data Size8 Bits
First BitMSB First
Clock ParametersPrescaler4(18MHz 上限速率)
CPOLLow
CPHA1 Edge
Advanced ParametersCRC CalculationDisabled
NSS Signal TypeSoftware

3.NVIC 配置

采用阻塞轮询方案时,SPI 事件中断、错误中断全部取消勾选;后续使用 DMA / 中断异步传输再开启中断并分配合适优先级。

4.DMA 配置(按需开启)

小体量参数存储:DMA 保持关闭;
大批量写入字库、位图资源:开启 SPI1 TX DMA,大数据回读额外开启 RX DMA,DMA 位宽统一设置 Byte。

5.GPIO 细节配置

PA4:GPIO_Output,推挽输出,初始电平 High(默认取消片选)。
PA5/PA6/PA7:CubeMX 自动分配 SPI1 复用推挽,无需手动修改。

6.串口配置(USART1 PA9/PA10)

异步串口,波特率 115200,8N1,用于 printf 打印调试信息。

7.工程生成设置

选择 HAL 库,生成分开的.c/.h 文件。

3、工程源码

1. w25q64.h 驱动头文件

#ifndef __W25Q64_H #define __W25Q64_H #include "main.h" #include "spi.h" // 片选控制宏 PA4 #define W25Q_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define W25Q_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) // W25Q64 标准指令集 #define W25Q_WRITE_EN 0x06U #define W25Q_WRITE_DIS 0x04U #define W25Q_READ_STATUS_REG 0x05U #define W25Q_PAGE_PROG 0x02U #define W25Q_READ_DATA 0x03U #define W25Q_SECTOR_ERASE 0x20U #define W25Q_READ_JEDEC_ID 0x9FU // W25Q64 容量参数 #define W25Q_PAGE_SIZE 256U #define W25Q_SECTOR_SIZE 4096U #define W25Q_ADDR_MAX 0x7FFFFFU // 底层单字节全双工收发 HAL_StatusTypeDef W25Q_SPI_RW(uint8_t tx_data, uint8_t *rx_data); // 读取JEDEC设备ID,W25Q64标准ID:0xEF4017 uint32_t W25Q_Read_JedecID(void); // 轮询等待FLASH擦写内部操作完成 HAL_StatusTypeDef W25Q_Wait_Busy(uint32_t timeout_ms); // 扇区擦除(最小擦除单元4KB) HAL_StatusTypeDef W25Q_Sector_Erase(uint32_t addr); // 页写入,单次写入长度不可超过256字节 HAL_StatusTypeDef W25Q_Page_Write(uint32_t addr, uint8_t *buf, uint16_t len); // 连续任意长度读取存储数据 HAL_StatusTypeDef W25Q_Buffer_Read(uint32_t addr, uint8_t *buf, uint32_t len); #endif

2. w25q64.c 驱动实现文件

#include "w25q64.h" extern SPI_HandleTypeDef hspi1; /** * @brief SPI单字节同步收发 * @param tx_data 发送字节 * @param rx_data 接收字节缓存,传NULL则丢弃接收数据 * @retval HAL状态码 */ HAL_StatusTypeDef W25Q_SPI_RW(uint8_t tx_data, uint8_t *rx_data) { uint8_t rx_tmp = 0xFF; HAL_StatusTypeDef ret = HAL_SPI_TransmitReceive(&hspi1, &tx_data, &rx_tmp, 1, 100); if(rx_data != NULL) *rx_data = rx_tmp; return ret; } /** * @brief 读取W25Q64 JEDEC ID,正常返回0xEF4017 * @retval 24位ID,读取失败返回0xFFFFFF */ uint32_t W25Q_Read_JedecID(void) { uint8_t id_buf[3] = {0}; W25Q_CS_LOW(); W25Q_SPI_RW(W25Q_READ_JEDEC_ID, NULL); W25Q_SPI_RW(0x00, &id_buf[0]); W25Q_SPI_RW(0x00, &id_buf[1]); W25Q_SPI_RW(0x00, &id_buf[2]); W25Q_CS_HIGH(); return ((uint32_t)id_buf[0] << 16) | ((uint32_t)id_buf[1] << 8) | id_buf[2]; } /** * @brief 轮询状态寄存器BUSY位,等待擦写完成 * @param timeout_ms 超时毫秒 * @retval HAL_OK / HAL_TIMEOUT */ HAL_StatusTypeDef W25Q_Wait_Busy(uint32_t timeout_ms) { uint8_t sta_reg = 0xFF; uint32_t tick_start = HAL_GetTick(); while(1) { W25Q_CS_LOW(); W25Q_SPI_RW(W25Q_READ_STATUS_REG, NULL); W25Q_SPI_RW(0x00, &sta_reg); W25Q_CS_HIGH(); // BIT0=BUSY位,0代表芯片空闲 if((sta_reg & 0x01U) == 0U) return HAL_OK; if(HAL_GetTick() - tick_start > timeout_ms) return HAL_TIMEOUT; } } /** * @brief 扇区擦除 4KB * @param addr 目标扇区任意地址 */ HAL_StatusTypeDef W25Q_Sector_Erase(uint32_t addr) { HAL_StatusTypeDef ret; addr &= W25Q_ADDR_MAX; // 1. 发送写使能指令 W25Q_CS_LOW(); W25Q_SPI_RW(W25Q_WRITE_EN, NULL); W25Q_CS_HIGH(); HAL_Delay(1); // 2. 发送扇区擦除指令+24位地址 W25Q_CS_LOW(); W25Q_SPI_RW(W25Q_SECTOR_ERASE, NULL); W25Q_SPI_RW((addr >> 16) & 0xFFU, NULL); W25Q_SPI_RW((addr >> 8) & 0xFFU, NULL); W25Q_SPI_RW(addr & 0xFFU, NULL); W25Q_CS_HIGH(); // 3. 等待擦除完成(最大300ms) ret = W25Q_Wait_Busy(300); return ret; } /** * @brief 单页写入,单次长度≤256字节 * @param addr 写入起始地址 * @param buf 待写入数据数组 * @param len 写入字节长度 */ HAL_StatusTypeDef W25Q_Page_Write(uint32_t addr, uint8_t *buf, uint16_t len) { HAL_StatusTypeDef ret; addr &= W25Q_ADDR_MAX; // 限制不跨页,防止数据覆盖 uint16_t page_remain = W25Q_PAGE_SIZE - (addr % W25Q_PAGE_SIZE); if(len > page_remain) len = page_remain; // 写使能 W25Q_CS_LOW(); W25Q_SPI_RW(W25Q_WRITE_EN, NULL); W25Q_CS_HIGH(); HAL_Delay(1); // 发送页写指令+24位地址 W25Q_CS_LOW(); W25Q_SPI_RW(W25Q_PAGE_PROG, NULL); W25Q_SPI_RW((addr >> 16) & 0xFFU, NULL); W25Q_SPI_RW((addr >> 8) & 0xFFU, NULL); W25Q_SPI_RW(addr & 0xFFU, NULL); // 循环写入数据 for(uint16_t i = 0; i < len; i++) { W25Q_SPI_RW(buf[i], NULL); } W25Q_CS_HIGH(); // 等待编程完成 ret = W25Q_Wait_Busy(100); return ret; } /** * @brief 连续批量读取FLASH存储数据 * @param addr 读取起始地址 * @param buf 接收缓存数组 * @param len 读取字节总数 */ HAL_StatusTypeDef W25Q_Buffer_Read(uint32_t addr, uint8_t *buf, uint32_t len) { HAL_StatusTypeDef ret; addr &= W25Q_ADDR_MAX; uint8_t cmd_tx[4] = {W25Q_READ_DATA, (addr>>16)&0xFFU, (addr>>8)&0xFFU, addr&0xFFU}; W25Q_CS_LOW(); // 先发4字节:读指令 + 24位地址 ret = HAL_SPI_Transmit(&hspi1, cmd_tx, 4, 100); if(ret != HAL_OK) { W25Q_CS_HIGH(); return ret; } // 持续读取目标长度数据 ret = HAL_SPI_Receive(&hspi1, buf, len, 200); W25Q_CS_HIGH(); return ret; }

3. usart.c printf 打印重定向

#include <stdio.h> extern UART_HandleTypeDef huart1; // printf串口输出重定向 int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; }

4. main.c 主函数测试

#include "main.h" #include "spi.h" #include "usart.h" #include "gpio.h" #include "w25q64.h" #include <stdio.h> #include <string.h> // 测试写入数据 uint8_t test_write_buf[] = "STM32F103ZET6 W25Q64 SPI HAL"; uint8_t test_read_buf[128] = {0}; int main(void) { // HAL库初始化、系统时钟、外设初始化 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); MX_USART1_UART_Init(); printf("========= F103ZET6 W25Q64 FLASH TEST =========\r\n"); // 1、读取JEDEC ID,检测板载FLASH硬件是否正常 uint32_t chip_id = W25Q_Read_JedecID(); printf("W25Q64 JEDEC ID = 0x%06X\r\n", chip_id); if(chip_id == 0xFFFFFFU) { printf("ERROR: FLASH No Response! Check SPI Wiring & CS\r\n"); } else if(chip_id == 0xEF4017U) { printf("Detect W25Q64 Success!\r\n"); } else { printf("Unknown FLASH Chip ID\r\n"); } // 2、擦除0号扇区(4KB) printf("Start Sector Erase 0x000000\r\n"); W25Q_Sector_Erase(0x000000U); printf("Erase Complete\r\n"); // 3、向0地址写入测试字符串 printf("Write Data:%s\r\n", test_write_buf); W25Q_Page_Write(0x000000U, test_write_buf, sizeof(test_write_buf)); // 4、读取0地址数据,存入缓存 memset(test_read_buf, 0, sizeof(test_read_buf)); W25Q_Buffer_Read(0x000000U, test_read_buf, sizeof(test_write_buf)); printf("Read Data:%s\r\n", test_read_buf); // 5、读写数据校验 if(memcmp(test_write_buf, test_read_buf, sizeof(test_write_buf)) == 0) { printf("Read Write Verify PASS\r\n"); } else { printf("Read Write Verify FAIL\r\n"); } // 主循环,每秒打印运行状态 while (1) { HAL_Delay(1000); printf("Run......\r\n"); } }

4、实验结果

六、SPI 核心要点与避坑大全

1、必考核心知识点

SPI 四线同步全双工高速总线,传输速度为嵌入式常规总线最快。
核心时序由 CPOL、CPHA 组合成4种工作模式,必须与外设匹配。
无硬件应答、无校验,通信可靠性依赖软件逻辑。
多设备共用总线依靠独立CS片选,分时复用通信。
高速大批量传输必须使用 DMA,避免 CPU 阻塞卡顿。

2、高频坑点

时序模式不匹配,导致百分百乱码、设备无响应。
多设备通信未严格分时操作CS,导致数据串扰。
时钟频率过高,外设不支持导致丢数、花屏。
忘记拉低/拉高片选,通信完全失效。
阻塞式传输刷屏,导致程序卡顿、帧率低。
未做超时与重试,偶尔通信异常无法自愈。

3、工程最佳实践

统一使用软件CS片选,兼容性、灵活性最强。
屏幕、Flash大批量传输优先使用 SPI+DMA。
初始化时序严格对照外设手册,优先Mode0/Mode2。
通信失败增加软件重试机制,提升稳定性。
低速调试用阻塞API,高速工程用DMA非阻塞。

4、总结

SPI是高速同步全双工串行总线,依靠SCLK同步时钟、MOSI/MISO双向传输、CS片选选通设备,通过CPOL/CPHA配置四种时序模式,具备传输速度快、稳定性高的特点,搭配DMA可实现高速流式数据传输,是屏幕、Flash、高速传感器的核心通信外设。

七、全篇总结

SPI 是嵌入式开发中高速通信的核心外设,是屏幕驱动、存储读写、高速外设交互的必备技能。掌握 SPI 四种时序模式、CubeMX参数配置、阻塞/DMA全套API、多设备总线复用逻辑,即可完成绝大多数高速外设项目开发,是嵌入式进阶、面试的核心重点考点。

http://www.jsqmd.com/news/1161732/

相关文章:

  • 2026 重庆黄金回收全攻略:5家合规线下门店、3 种变现渠道详解 - 奢侈品回收评测
  • Go 后台管理系统教程:GoFrame + Vue3 实现 CRUD、RBAC 权限、代码生成器与部署
  • 如何快速上手Symfony Runtime:5分钟完成PHP应用解耦
  • DeepTutor终极指南:如何用AI代理原生架构重塑你的个性化学习体验
  • 无锡金铺藏着的回收内幕,懂行的人从不吃亏 - 全城热点
  • 图形验证码变身智能安全网关:毫秒级人机识别与梯度化风控策略实战
  • SharpKeys完全指南:Windows键盘重映射工具终极教程
  • 百达翡丽上海变现干货,新手卖表不踩坑技巧 - 讯息早知道
  • 2026年7月最新长沙萧邦官方售后联系电话与客户服务中心网点地址 - 萧邦中国官方服务中心
  • 微信聊天记录永久保存秘籍:3步打造你的个人AI记忆库
  • 【电路分析基础习题】2025-6-29
  • OctoScreen插件开发入门:为你的3D打印机添加专属功能的实战教程
  • Wireshark 3.6 实战:从 TCP 三次握手到 HTTP 请求的 5 层协议栈全解析
  • 成都香奈儿回收TOP榜|2026本地闲置包包变现深度调查报告 - 奢侈品回收机构参考
  • 工业级负载控制方案:TPD2015FN与PIC18LF47K40应用设计
  • 2026年7月最新沈阳帝舵官方售后客服中心地址电话及服务网点分布 - 帝舵中国官方服务中心
  • 2026哈尔滨名表上门回收测评:逸程奢侈品回收中心成优质选择 - 逸程奢侈品回收中心
  • 上海虹口杨浦名表回收2026|门店多资金足,十六区通用上门回收各大品牌 - 奢侈品交易观察员
  • 如何快速解密中兴光猫配置文件:小白也能掌握的完整教程
  • L9958与PIC18F45K42实现高性能直流电机控制方案
  • 7天掌握Claude提示工程:Anthropic官方交互式教程完全指南
  • 从零到生产上线:Ollama Open WebUI 3.4.2 + Llama3-70B量化部署全记录(含4bit GGUF加载失败终极解法与显存占用精确测算)
  • 如何3步实现暗黑3操作自动化:智能辅助工具终极指南
  • 大模型压缩技术突破:Nemotron-75B实现吞吐量翻倍的部署优化
  • 物美购物卡回收怕遇黑平台?手把手教你核查正规资质,2026实测京质回收最省心完整记录 - 京质回收
  • 2026年7月南京秦淮LV回收行情 热门款式价格参考 - 融媒生活
  • 【稀缺资源】Notion+ChatGPT知识库黄金配置包(含12个加密模板+Prompt工程手册+审计日志模块):仅限前200位技术负责人领取
  • 南昌漏水维修避坑指南:免砸砖根治,漏水点精准定位 - 润富黄金回收
  • 2026 仪征市防水、防水公司推荐、屋面防水、楼顶防水、卷材防水、厂房屋面防水、办公楼屋面防水、屋面防水修缮公司哪家好?TOP5 权威推荐 + 避坑指南 - 万至防水
  • 实战指南:区域入侵检测(周界入侵)接入AI视频分析平台流程与深度误报优化