> 技术文档 > 【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

这篇文章是针对 SPI 接口原理与配置 的系统性整理与讲解,内容分为四大部分:① SPI接口原理;② SPI寄存器库函数配置;③ 结合硬件原理图和代码针对W25Qxx配置的讲解。④ 基于 STM32 标准库的 W25Qxx SPI Flash 驱动完整代码 实例。适用于 STM32 系列 MCU 和 W25Qxx FLASH 的开发场景。

📌第一部分:SPI 接口原理

SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。应用于:Flash、EEPROM、RTC、ADC、DAC、DSP、LCD 等设备。

参考资料:ST官方SPI资料:《STM32中文参考手册V10》第23章 串行外设接口SPI

SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。

SPI内部硬件结构简明图

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

信号线 作用 主从方向 MOSI 主发从收 主机输出,从机输入 MISO 主收从发 主机输入,从机输出 SCK 时钟信号线 主机输出 NSS/CS 片选信号线 主机输出,低电平有效

SPI接口一般使用4条线通信:

  • MISO 主设备数据输入,从设备数据输出。
  • MOSI 主设备数据输出,从设备数据输入。
  • SCLK 时钟信号,由主设备产生。
  • CS 从设备片选信号,由主设备控制。

SPI 通信通过移位寄存器实现数据交换,主机每发送一个字节的同时,从机也返回一个字节(同步收发)。

SPI接口框图

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

SPI工作原理总结

① 硬件上为4根线。
② 主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。
③ 串行移位寄存器通过MOSI信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过MISO信号线返回给主机。这样,两个移位寄存器中的内容就被交换。
④ 外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

SPI特征

STM32 SPI接口可配置为支持SPI协议或者支持I2S音频协议,默认是SPI模式。可以通过软件切换到I2S方式。

支持 8位/16位 数据帧主从模式可选可配置 MSB 或 LSB 先传时钟分频器和极性/相位可调支持 DMA、中断、CRC 校验支持多 SPI 模块(SPI1、SPI2、SPI3)

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

2种NSS模式
🔌 NSS(片选)管理
  • 软件 NSS 管理: 由程序控制 NSS 引脚电平;
  • 硬件 NSS 管理: 自动拉低/拉高 CS 信号(当 SPI_CR2.SSOE 置位)。

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

⚙️ 时钟相位与极性(CPOL/CPHA)

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi
【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi
【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

CPOL CPHA 空闲时钟 采样时机 0 0 低 第一个边沿(上升沿) 0 1 低 第二个边沿(下降沿) 1 0 高 第一个边沿(下降沿) 1 1 高 第二个边沿(上升沿)

CPOL/CPHA 的组合决定了数据在 SCK 的哪个边沿采样与传输。

数据帧格式

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

📶 SPI 状态标志

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

标志 含义 TXE 发送缓冲区空 RXNE 接收缓冲区非空 BSY SPI 总线忙碌中
🧨 SPI 中断
中断事件 标志位 控制位 TX 发送空 TXE TXEIE RX 接收非空 RXNE RXNEIE 错误 OVR、MODF、CRCERR ERRIE

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

🧭 SPI 引脚 & GPIO 配置(3个SPI)

SPI1 Mini板子用SPI1
【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi
SPI2 战舰/精英用SPI2
【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi
SPI3
【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

SPI引脚配置(中文参考手册8.11小节)

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

模式选择(Mini板 SPI1 引脚配置):
【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

引脚 功能 GPIO 配置 PA5 SCK 推挽复用输出 PA6 MISO 浮空输入 PA7 MOSI 推挽复用输出 PA4 NSS 推挽输出 or 软件模拟

📌第二部分:SPI 库函数与寄存器配置

常用寄存器
寄存器功能SPI控制寄存器1(SPI_CR1)控制寄存器1(主从、CPOL、CPHA、速度等)SPI控制寄存器2(SPI_CR2)控制寄存器2(中断、DMA配置)SPI状态寄存器(SPI_SR)状态寄存器(TXE、RXNE、BSY)SPI数据寄存器(SPI_DR)数据寄存器(收发数据)SPI_I2S配置寄存器(SPI_I2S_CFGR)SPI_I2S预分频寄存器(SPI_I2SPR)
SPI相关库函数:
void SPI_I2S_DeInit(SPI_TypeDef* SPIx);void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);void SPI_DataSizeConfig(SPI_TypeDef* SPIx, uint16_t SPI_DataSize);FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
程序配置过程:

① 配置相关引脚的复用功能,使能 SPIx 时钟
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
② 初始化SPIx,设置SPIx工作模式
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
③ 使能SPIx
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) ;
⑤ 查看SPI传输状态
SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE);

⚙️ SPI 初始化流程
// 1. GPIO 配置GPIO_Init(GPIOA, &GPIO_InitStructure);// 2. SPI 参数结构体初始化SPI_InitTypeDef SPI_InitStructure;SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_Init(SPI1, &SPI_InitStructure);// 3. 启用 SPISPI_Cmd(SPI1, ENABLE);
📤 SPI 数据收发
// 发送SPI_I2S_SendData(SPI1, 0x55);while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);// 接收while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);uint8_t data = SPI_I2S_ReceiveData(SPI1);

📌第三部分:W25Qxx 配置与操作

📌 W25Qxx 概述

容量:W25Q64 为 8MB,W25Q128 为 16MB
擦除单位:4KB 扇区(Sector)= 4096 字节
支持页编程、扇区擦除、块擦除、读 JEDEC ID 等功能
最小写单位:页(Page)= 256 字节
最小擦除单位:扇区(Sector)= 4KB

硬件连接

STM32:
【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi
战舰/精英:
【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi
Mini:
【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

W25Q128(W25Q64)16M(8M) 的容量分为 256(128) 个块 (Block),每个块大小为 64K 字节,每个块又分为 16 个扇区(Sector),每个扇区4K个字节。W25Qxx的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。这样我们需要给W25Qxx开辟一个至少4K的缓存区,这样对SRAM要求比较高,要求芯片必须有4K以上SRAM才能很好的操作。

(查手册)W25Qxx命令

在我的资源中上传了 W25Q64数据手册.pdf,需要的朋友可跳转至我的资源区自行下载。

目录:
【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

常用命令
命令 说明 0x06 Write Enable 0x04 Write Disable 0x05 Read Status Register 0x01 Write Status Register 0x03 Read Data 0x02 Page Program 0x20 Sector Erase 0xD8 Block Erase (64KB) 0xC7 Chip Erase 0x9F Read JEDEC ID

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

写入流程解析

写入一个地址流程:

  1. 判断目标地址中的数据是否为 0xFF
    • 是 → 直接写入
    • 否 → 先 擦除扇区,再写入
  2. 若跨扇区写入:
    • 需要分段处理,逐个扇区处理
  3. 使用缓存区 W25QXX_BUFFER
    • 读取 → 修改 → 擦除 → 写入缓存 → 一次性写入
W25QXX_Write函数思路

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi
【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

① 根据要写的起始地址,确定要写的起始区域的Sector号以及在起始 sector中的偏移量。
② 根据要写的起始地址和字节数,确定要写的数据是否跨sector。
③ 确定好要操作的sector以及sector的地址范围。
④ 对每一个sector,先遍历要写的地址区域保存的数据是不是0xff,如果都是,就不用擦除。如果有不是0xff的区域,先读出里面的数据,保存在缓存W25QXX_BUFFER,然后擦除里面的内容。然后把这个sector要操作的数据,写到缓存。最后一次性吧缓存W25QXX_BUFFER的数据写到这个对应的sector

示例:W25QXX 函数伪代码
void W25QXX_Write(uint8_t* buffer, uint32_t addr, uint16_t size){ // 1. 判断是否跨扇区 // 2. 遍历每个扇区 // 3. 若不为0xFF:擦除扇区、读缓存、写缓存 // 4. 最后写入数据}

第四部分:基于 STM32 标准库的 W25Qxx SPI Flash 驱动完整代码

示例一:

驱动支持:

  • 页写(Page Program)
  • 字节读(Read Data)
  • 扇区擦除(4KB)
  • JEDEC ID 读取

适用于 W25Q64/W25Q128 等 Winbond 系列 Flash,支持 SPI 接口。

📦 文件结构
W25Qxx_Driver/├── w25qxx.h└── w25qxx.c
w25qxx.h 头文件
#ifndef __W25QXX_H__#define __W25QXX_H__#include \"stm32f10x.h\"// SPI & GPIO 定义(可按实际修改)#define W25QXX_SPI  SPI1#define W25QXX_CS_PORT GPIOA#define W25QXX_CS_PIN  GPIO_Pin_4// 指令集#define W25X_WRITE_ENABLE 0x06 #define W25X_WRITE_DISABLE 0x04 #define W25X_READ_STATUS_REG1 0x05 #define W25X_READ_DATA 0x03 #define W25X_PAGE_PROGRAM 0x02 #define W25X_SECTOR_ERASE 0x20 #define W25X_CHIP_ERASE 0xC7 #define W25X_READ_JEDEC_ID 0x9F #define W25QXX_PAGE_SIZE 256#define W25QXX_SECTOR_SIZE 4096// 函数声明void W25QXX_Init(void);void W25QXX_WriteEnable(void);uint8_t W25QXX_ReadSR(void);uint32_t W25QXX_ReadID(void);void W25QXX_ReadData(uint8_t *buf, uint32_t addr, uint16_t len);void W25QXX_PageProgram(uint8_t *buf, uint32_t addr, uint16_t len);void W25QXX_SectorErase(uint32_t addr);#endif
w25qxx.c 源文件
#include \"w25qxx.h\"// ⏹ 片选控制#define W25QXX_CS_LOW() GPIO_ResetBits(W25QXX_CS_PORT, W25QXX_CS_PIN)#define W25QXX_CS_HIGH() GPIO_SetBits(W25QXX_CS_PORT, W25QXX_CS_PIN)// SPI发送接收static uint8_t SPI1_Transfer(uint8_t data){ while(SPI_I2S_GetFlagStatus(W25QXX_SPI, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(W25QXX_SPI, data); while(SPI_I2S_GetFlagStatus(W25QXX_SPI, SPI_I2S_FLAG_RXNE) == RESET); return SPI_I2S_ReceiveData(W25QXX_SPI);}// 初始化Flash驱动void W25QXX_Init(void){ // SPI 初始化由外部完成 W25QXX_CS_HIGH();}// 写使能void W25QXX_WriteEnable(void){ W25QXX_CS_LOW(); SPI1_Transfer(W25X_WRITE_ENABLE); W25QXX_CS_HIGH();}// 读取状态寄存器1uint8_t W25QXX_ReadSR(void){ uint8_t status = 0; W25QXX_CS_LOW(); SPI1_Transfer(W25X_READ_STATUS_REG1); status = SPI1_Transfer(0xFF); W25QXX_CS_HIGH(); return status;}// 等待Flash空闲static void W25QXX_WaitBusy(void){ while(W25QXX_ReadSR() & 0x01); // BUSY位为1表示忙}// 读取 JEDEC IDuint32_t W25QXX_ReadID(void){ uint32_t id = 0; W25QXX_CS_LOW(); SPI1_Transfer(W25X_READ_JEDEC_ID); id |= SPI1_Transfer(0xFF) << 16; id |= SPI1_Transfer(0xFF) << 8; id |= SPI1_Transfer(0xFF); W25QXX_CS_HIGH(); return id;}// 扇区擦除(4KB)void W25QXX_SectorErase(uint32_t addr){ W25QXX_WriteEnable(); W25QXX_CS_LOW(); SPI1_Transfer(W25X_SECTOR_ERASE); SPI1_Transfer((addr >> 16) & 0xFF); SPI1_Transfer((addr >> 8) & 0xFF); SPI1_Transfer(addr & 0xFF); W25QXX_CS_HIGH(); W25QXX_WaitBusy();}// 页编程(最多256字节,不能跨页)void W25QXX_PageProgram(uint8_t *buf, uint32_t addr, uint16_t len){ if(len > 256) len = 256; // 限制最大长度 W25QXX_WriteEnable(); W25QXX_CS_LOW(); SPI1_Transfer(W25X_PAGE_PROGRAM); SPI1_Transfer((addr >> 16) & 0xFF); SPI1_Transfer((addr >> 8) & 0xFF); SPI1_Transfer(addr & 0xFF); for(uint16_t i = 0; i < len; i++) { SPI1_Transfer(buf[i]); } W25QXX_CS_HIGH(); W25QXX_WaitBusy();}// 读取数据void W25QXX_ReadData(uint8_t *buf, uint32_t addr, uint16_t len){ W25QXX_CS_LOW(); SPI1_Transfer(W25X_READ_DATA); SPI1_Transfer((addr >> 16) & 0xFF); SPI1_Transfer((addr >> 8) & 0xFF); SPI1_Transfer(addr & 0xFF); for(uint16_t i = 0; i < len; i++) { buf[i] = SPI1_Transfer(0xFF); } W25QXX_CS_HIGH();}
示例:使用 W25Qxx 驱动
#include \"w25qxx.h\"uint8_t tx_buf[256] = {0};uint8_t rx_buf[256] = {0};void Flash_Test(void){ // 填充数据 for(int i = 0; i < 256; i++) tx_buf[i] = i; // 擦除扇区 W25QXX_SectorErase(0x000000); // 写入数据 W25QXX_PageProgram(tx_buf, 0x000000, 256); // 读取数据 W25QXX_ReadData(rx_buf, 0x000000, 256);}
注意:页写不可跨页(256字节为单位)写前必须 Write Enable写/擦除后需 WaitBusy()擦除单位为 4KB(0x1000)扩展支持(可按需添加):功能建议命令读取状态寄存器20x35页写跨页处理W25QXX_Write() 封装多页写块擦除(64KB)0xD8高速读(Fast Read)0x0BJEDEC ID校验0xEF4017(W25Q64)/ 0xEF4018(W25Q128)等

示例二:

实验完整代码(标准库实现)(模块化)

实验目的:学习STM32 SPI接口的使用,驱动W25Q128实现SPI FLASH数据读写.硬件资源:1,DS0(连接在PB5)2,串口1(波特率:115200,PA9/PA10连接在板载USB转串口芯片CH340上面)3,ALIENTEK 2.8/3.5/4.3/7寸TFTLCD模块(通过FSMC驱动,FSMC_NE4接LCD片选/A10接RS) 4,按键KEY0(PE4)/KEY1(PE3)5,SPI2(PB12/13/14/15)6,W25Q128(SPI FLASH芯片,连接在SPI2上)实验现象:本实验通过KEY1按键来控制W25Q128的写入,通过另外一个按键KEY0来控制W25Q128的读取。并在LCD模块上面显示相关信息。DS0提示程序正在运行。同时,我们可以通过USMART控制读取W25QXX的ID或者整片擦除。
因为在 Keil 软件中,函数设计是模块划分的(代码整理):

📄 spi.h

#ifndef __SPI_H#define __SPI_H#include \"sys.h\"#define SPIx SPI1 void User_SPI_Init(void); //初始化SPI口void SPI_SetSpeed(u8 SpeedSet); //设置SPI速度 u8 SPI_ReadWriteByte(u8 TxData);//SPI总线读写一个字节 #endif

📄 spi.c

#include \"spi.h\" //以下是SPI模块的初始化代码,配置成主机模式,访问SD Card/W25Q64/NRF24L01(数据手册可在我的资源中下载 W25Q64数据手册.pdf) //SPI口初始化//这里针是对SPIx的初始化void User_SPI_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );//PORTB时钟使能 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE );// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PA5 / PA7 复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOAGPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 ;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //PA6 上拉输入GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA GPIO_SetBits(GPIOA, GPIO_Pin_5 | GPIO_Pin_7 ); //PB13/14/15上拉 默认输出高电平SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//设置SPI工作模式:设置为主SPISPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//设置SPI的数据大小:SPI发送接收8位帧结构SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步时钟的空闲状态为高电平SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//串行同步时钟的第二个跳变沿(上升或下降)数据被采样SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//定义波特率预分频的值:波特率预分频值为256SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始 ***SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC值计算的多项式 *****SPI_Init(SPIx, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器 SPI_Cmd(SPIx, ENABLE); //使能SPI外设SPI_ReadWriteByte(0xff);//启动传输  } //SPI 速度设置函数//SpeedSet://SPI_BaudRatePrescaler_2 2分频 //SPI_BaudRatePrescaler_8 8分频 //SPI_BaudRatePrescaler_16 16分频 //SPI_BaudRatePrescaler_256 256分频  void SPI_SetSpeed(u8 SPI_BaudRatePrescaler){ assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));SPIx->CR1&=0XFFC7;SPIx->CR1|=SPI_BaudRatePrescaler;//设置SPIx速度 SPI_Cmd(SPIx,ENABLE); } //SPIx 读写一个字节//TxData:要写入的字节//返回值:读取到的字节u8 SPI_ReadWriteByte(u8 TxData){u8 retry=0; while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位{retry++;if(retry>200)return 0;} SPI_I2S_SendData(SPIx, TxData); //通过外设SPIx发送一个数据retry=0;while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET) //检查指定的SPI标志位设置与否:接受缓存非空标志位{retry++;if(retry>200)return 0;} return SPI_I2S_ReceiveData(SPIx); //返回通过SPIx最近接收的数据 }

📄 w25qxx.h

#ifndef __FLASH_H#define __FLASH_H #include \"sys.h\"  //W25X系列/Q系列芯片列表 //W25Q80 ID 0XEF13//W25Q16 ID 0XEF14//W25Q32 ID 0XEF15//W25Q64 ID 0XEF16//W25Q128 ID 0XEF17#define W25Q80 0XEF13 #define W25Q16 0XEF14#define W25Q32 0XEF15#define W25Q64 0XEF16#define W25Q1280XEF17#define NM25Q80 0X5213 #define NM25Q16 0X5214#define NM25Q32 0X5215#define NM25Q64 0X5216#define NM25Q1280X5217#define NM25Q256 0X5218extern u16 W25QXX_TYPE;//定义W25QXX芯片型号 #defineW25QXX_CS PAout(4) //W25QXX的片选信号 //////////////////////////////////////////////////////////////////////////// //指令表#define W25X_WriteEnable0x06 #define W25X_WriteDisable0x04 #define W25X_ReadStatusReg0x05 #define W25X_WriteStatusReg0x01 #define W25X_ReadData0x03 #define W25X_FastReadData0x0B #define W25X_FastReadDual0x3B #define W25X_PageProgram0x02 #define W25X_BlockErase0xD8 #define W25X_SectorErase0x20 #define W25X_ChipErase0xC7 #define W25X_PowerDown0xB9 #define W25X_ReleasePowerDown0xAB #define W25X_DeviceID0xAB #define W25X_ManufactDeviceID0x90 #define W25X_JedecDeviceID0x9F void W25QXX_Init(void);u16 W25QXX_ReadID(void); //读取FLASH IDu8 W25QXX_ReadSR(void); //读取状态寄存器 void W25QXX_Write_SR(u8 sr); //写状态寄存器void W25QXX_Write_Enable(void); //写使能 void W25QXX_Write_Disable(void);//写保护void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead); //读取flashvoid W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//写入flashvoid W25QXX_Erase_Chip(void); //整片擦除void W25QXX_Erase_Sector(u32 Dst_Addr);//扇区擦除void W25QXX_Wait_Busy(void);  //等待空闲void W25QXX_PowerDown(void); //进入掉电模式void W25QXX_WAKEUP(void);//唤醒#endif

📄 w25qxx.c

#include \"w25qxx.h\" #include \"spi.h\"#include \"delay.h\"#include \"usart.h\"u16 W25QXX_TYPE=W25Q128;//默认是W25Q128//4Kbytes为一个Sector//16个扇区为1个Block//W25Q128//容量为16M字节,共有128个Block,4096个Sector  //初始化SPI FLASH的IO口void W25QXX_Init(void){ GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );//PORTB时钟使能 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // PA4 推挽  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA,GPIO_Pin_4); W25QXX_CS=1;//SPI FLASH不选中User_SPI_Init(); //初始化SPISPI_SetSpeed(SPI_BaudRatePrescaler_2);//设置为18M时钟,高速模式W25QXX_TYPE=W25QXX_ReadID();//读取FLASH ID. } //读取W25QXX的状态寄存器//BIT7 6 5 4 3 2 1 0//SPR RV TB BP2 BP1 BP0 WEL BUSY//SPR:默认0,状态寄存器保护位,配合WP使用//TB,BP2,BP1,BP0:FLASH区域写保护设置//WEL:写使能锁定//BUSY:忙标记位(1,忙;0,空闲)//默认:0x00u8 W25QXX_ReadSR(void) { u8 byte=0; W25QXX_CS=0; //使能器件 SPI_ReadWriteByte(W25X_ReadStatusReg); //发送读取状态寄存器命令 byte=SPI_ReadWriteByte(0Xff); //读取一个字节 W25QXX_CS=1; //取消片选 return byte; } //写W25QXX状态寄存器//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!void W25QXX_Write_SR(u8 sr) { W25QXX_CS=0; //使能器件 SPI_ReadWriteByte(W25X_WriteStatusReg);//发送写取状态寄存器命令 SPI_ReadWriteByte(sr);  //写入一个字节 W25QXX_CS=1; //取消片选  } //W25QXX写使能//将WEL置位 void W25QXX_Write_Enable(void) {W25QXX_CS=0; //使能器件  SPI_ReadWriteByte(W25X_WriteEnable); //发送写使能 W25QXX_CS=1; //取消片选  } //W25QXX写禁止//将WEL清零 void W25QXX_Write_Disable(void) { W25QXX_CS=0; //使能器件  SPI_ReadWriteByte(W25X_WriteDisable); //发送写禁止指令 W25QXX_CS=1; //取消片选  } //读取芯片ID//返回值如下: //0XEF13,表示芯片型号为W25Q80 //0XEF14,表示芯片型号为W25Q16 //0XEF15,表示芯片型号为W25Q32 //0XEF16,表示芯片型号为W25Q64 //0XEF17,表示芯片型号为W25Q128 u16 W25QXX_ReadID(void){u16 Temp = 0; W25QXX_CS=0; SPI_ReadWriteByte(0x90);//发送读取ID命令 SPI_ReadWriteByte(0x00); SPI_ReadWriteByte(0x00); SPI_ReadWriteByte(0x00); Temp|=SPI_ReadWriteByte(0xFF)<<8; Temp|=SPI_ReadWriteByte(0xFF); W25QXX_CS=1; return Temp;} //读取SPI FLASH //在指定地址开始读取指定长度的数据//pBuffer:数据存储区//ReadAddr:开始读取的地址(24bit)//NumByteToRead:要读取的字节数(最大65535)void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead) { u16 i; W25QXX_CS=0; //使能器件  SPI_ReadWriteByte(W25X_ReadData); //发送读取命令  SPI_ReadWriteByte((u8)((ReadAddr)>>16)); //发送24bit地址  SPI_ReadWriteByte((u8)((ReadAddr)>>8)); SPI_ReadWriteByte((u8)ReadAddr); for(i=0;i<NumByteToRead;i++){ pBuffer[i]=SPI_ReadWriteByte(0XFF); //循环读数  }W25QXX_CS=1; } //SPI在一页(0~65535)内写入少于256个字节的数据//在指定地址开始写入最大256字节的数据//pBuffer:数据存储区//WriteAddr:开始写入的地址(24bit)//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!! void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite){ u16 i; W25QXX_Write_Enable();  //SET WEL W25QXX_CS=0; //使能器件  SPI_ReadWriteByte(W25X_PageProgram); //发送写页命令  SPI_ReadWriteByte((u8)((WriteAddr)>>16)); //发送24bit地址  SPI_ReadWriteByte((u8)((WriteAddr)>>8)); SPI_ReadWriteByte((u8)WriteAddr); for(i=0;i<NumByteToWrite;i++)SPI_ReadWriteByte(pBuffer[i]);//循环写数 W25QXX_CS=1; //取消片选 W25QXX_Wait_Busy(); //等待写入结束} //无检验写SPI FLASH //必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!//具有自动换页功能 //在指定地址开始写入指定长度的数据,但是要确保地址不越界!//pBuffer:数据存储区//WriteAddr:开始写入的地址(24bit)//NumByteToWrite:要写入的字节数(最大65535)//CHECK OKvoid W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) { u16 pageremain; pageremain=256-WriteAddr%256; //单页剩余的字节数 if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节while(1){ W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);if(NumByteToWrite==pageremain)break;//写入结束了 else //NumByteToWrite>pageremain{pBuffer+=pageremain;WriteAddr+=pageremain;NumByteToWrite-=pageremain; //减去已经写入了的字节数if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节else pageremain=NumByteToWrite; //不够256个字节了}}; } //写SPI FLASH //在指定地址开始写入指定长度的数据//该函数带擦除操作!//pBuffer:数据存储区//WriteAddr:开始写入的地址(24bit)//NumByteToWrite:要写入的字节数(最大65535) u8 W25QXX_BUFFER[4096]; void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) { u32 secpos;u16 secoff;u16 secremain; u16 i; u8 * W25QXX_BUF; W25QXX_BUF=W25QXX_BUFFER; secpos=WriteAddr/4096;//扇区地址 secoff=WriteAddr%4096;//在扇区内的偏移secremain=4096-secoff;//扇区剩余空间大小  //printf(\"ad:%X,nb:%X\\r\\n\",WriteAddr,NumByteToWrite);//测试用 if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节while(1) {W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容for(i=0;i<secremain;i++)//校验数据{if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除 }if(i<secremain)//需要擦除{W25QXX_Erase_Sector(secpos);//擦除这个扇区for(i=0;i<secremain;i++) //复制{W25QXX_BUF[i+secoff]=pBuffer[i]; }W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区 }else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间. if(NumByteToWrite==secremain)break;//写入结束了else//写入未结束{secpos++;//扇区地址增1secoff=0;//偏移位置为0  pBuffer+=secremain; //指针偏移WriteAddr+=secremain;//写地址偏移  NumByteToWrite-=secremain;//字节数递减if(NumByteToWrite>4096)secremain=4096;//下一个扇区还是写不完else secremain=NumByteToWrite;//下一个扇区可以写完了} }; }//擦除整个芯片 //等待时间超长...void W25QXX_Erase_Chip(void) {  W25QXX_Write_Enable();  //SET WEL  W25QXX_Wait_Busy(); W25QXX_CS=0; //使能器件  SPI_ReadWriteByte(W25X_ChipErase); //发送片擦除命令 W25QXX_CS=1; //取消片选  W25QXX_Wait_Busy(); //等待芯片擦除结束} //擦除一个扇区//Dst_Addr:扇区地址 根据实际容量设置//擦除一个山区的最少时间:150msvoid W25QXX_Erase_Sector(u32 Dst_Addr) { //监视falsh擦除情况,测试用  printf(\"fe:%x\\r\\n\",Dst_Addr); Dst_Addr*=4096; W25QXX_Write_Enable();  //SET WEL  W25QXX_Wait_Busy(); W25QXX_CS=0; //使能器件  SPI_ReadWriteByte(W25X_SectorErase); //发送扇区擦除指令  SPI_ReadWriteByte((u8)((Dst_Addr)>>16)); //发送24bit地址  SPI_ReadWriteByte((u8)((Dst_Addr)>>8)); SPI_ReadWriteByte((u8)Dst_Addr); W25QXX_CS=1; //取消片选   W25QXX_Wait_Busy(); //等待擦除完成} //等待空闲void W25QXX_Wait_Busy(void) { while((W25QXX_ReadSR()&0x01)==0x01); // 等待BUSY位清空} //进入掉电模式void W25QXX_PowerDown(void) { W25QXX_CS=0; //使能器件  SPI_ReadWriteByte(W25X_PowerDown); //发送掉电命令 W25QXX_CS=1; //取消片选   delay_us(3); //等待TPD } //唤醒void W25QXX_WAKEUP(void) { W25QXX_CS=0; //使能器件  SPI_ReadWriteByte(W25X_ReleasePowerDown);// send W25X_PowerDown command 0xAB W25QXX_CS=1; //取消片选   delay_us(3); //等待TRES1} 

📄 key.h

#ifndef __KEY_H#define __KEY_H #include \"sys.h\"//#define KEY0 PEin(4) //PE4//#define KEY1 PEin(3)//PE3 //#define WK_UP PAin(0)//PA0 WK_UP#define KEY0 GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_1)//读取按键0//#define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)//读取按键1#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)//读取按键3(WK_UP)  #define KEY0_PRES 1//KEY0按下#define KEY1_PRES 2//KEY1按下#define WKUP_PRES 3//KEY_UP按下(即WK_UP/KEY_UP)void KEY_Init(void);//IO初始化u8 KEY_Scan(u8); //按键扫描函数#endif

📄 key.c

#include \"stm32f10x.h\"#include \"key.h\"#include \"sys.h\" #include \"delay.h\" //按键初始化函数void KEY_Init(void) //IO初始化{ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);//使能PORTA,PORTE时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;//KEY0-KEY1GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入 GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIOE4,3//初始化 WK_UP-->GPIOA.0 下拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成输入,默认下拉 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0}//按键处理函数//返回按键值//mode:0,不支持连续按;1,支持连续按;//0,没有任何按键按下//1,KEY0按下//2,KEY1按下//3,KEY3按下 WK_UP//注意此函数有响应优先级,KEY0>KEY1>KEY_UP!!u8 KEY_Scan(u8 mode){ static u8 key_up=1;//按键按松开标志if(mode)key_up=1; //支持连按 if(key_up&&(KEY0 == 0 || WK_UP == 1)){delay_ms(10);//去抖动 key_up=0;if(KEY0==0)return KEY0_PRES;else if(KEY0 == 0)return KEY0_PRES;else if(WK_UP == 0)return WKUP_PRES;}else if(KEY0==1 && WK_UP==1)key_up=1; return 0;// 无按键按下}

📄 main.c

#include \"led.h\"#include \"delay.h\"#include \"key.h\"#include \"sys.h\"#include \"usart.h\" #include \"w25qxx.h\"  //要写入到W25Q64的字符串数组const u8 TEXT_Buffer[]={\"练习两年半的实习生!\"};#define SIZE sizeof(TEXT_Buffer) int main(void) { u8 key;u16 i=0;u8 datatemp[SIZE];u32 FLASH_SIZE; u16 id = 0; delay_init(); //延时函数初始化  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级uart_init(115200); //串口初始化为115200LED_Init(); //初始化与LED连接的硬件接口KEY_Init();//按键初始化 W25QXX_Init();//W25QXX初始化printf(\"WKUP_PRES:Write KEY0:Read \\r\\n\");while(1){id = W25QXX_ReadID();if (id == NM25Q32 || id == W25Q32)break;printf(\"W25Q32 Check Failed! \\r\\n\");delay_ms(500);printf(\"Please Check! \\r\\n\");delay_ms(500);LED0=!LED0;//DS0闪烁}printf(\"W25Q32 Ready! \\r\\n\");FLASH_SIZE=32*1024*1024;//FLASH 大小为16M字节while(1){key=KEY_Scan(0);if(key==WKUP_PRES)//KEY1按下,写入W25QXX{printf(\"Start Write W25Q32.... \\r\\n\");W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE);//从倒数第100个地址处开始,写入SIZE长度的数据printf(\"W25Q32 Write Finished! \\r\\n\");}if(key==KEY0_PRES)//KEY0按下,读取字符串并显示{printf(\"Start Read W25Q32.... \\r\\n\");W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE);//从倒数第100个地址处开始,读出SIZE个字节printf(\"The Data Readed Is: %s \\r\\n\",datatemp);}i++;delay_ms(10);if(i==20){LED0=!LED0;//提示系统正在运行i=0;} }}

【STM32】SPI接口原理与配置(提供完整实例代码)_stm32 spi

模块 重点 SPI 原理 四线通信、全双工、同步、CPOL/CPHA STM32 配置 GPIO + SPI_Init + SPI_Cmd 状态控制 TXE、RXNE、BSY 三大标志 中断支持 TXEIE、RXNEIE、ERRIE W25Qxx 操作 先判断再擦除,跨扇区需分段处理 注意事项 写入前需使能写操作(0x06),擦除单位为4KB

以上。这便是 SPI 接口原理与配置 完整的系统性整理与解析,以及在开发中使用 STM32 系列 MCU 和 W25Qxx FLASH 的驱动开发代码。

以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!