news 2026/7/11 2:38:50

STM32 SPI通信实战:从基础配置到W25Q64闪存读写

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32 SPI通信实战:从基础配置到W25Q64闪存读写

1. SPI通信基础与STM32硬件配置

SPI(Serial Peripheral Interface)是一种高速全双工同步串行通信协议,由摩托罗拉公司设计,广泛应用于嵌入式系统中。它只需要四根信号线就能实现主从设备之间的数据交换,非常适合连接Flash存储器、传感器等外设。

SPI的四根信号线分别是:

  • SCK(Serial Clock):时钟信号,由主设备产生
  • MOSI(Master Output Slave Input):主设备输出,从设备输入
  • MISO(Master Input Slave Output):主设备输入,从设备输出
  • SS(Slave Select):从设备片选信号

在STM32中配置SPI外设时,我们需要重点关注几个关键参数:

  1. 时钟极性(CPOL):决定SCK空闲时的电平状态
  2. 时钟相位(CPHA):决定数据在时钟的哪个边沿采样
  3. 波特率分频:设置SPI通信速率
  4. 数据帧格式:8位或16位数据

以STM32F1系列为例,硬件SPI1的引脚通常对应:

  • PA4:SS(软件控制)
  • PA5:SCK
  • PA6:MISO
  • PA7:MOSI

2. W25Q64闪存芯片特性解析

W25Q64是Winbond公司推出的64M-bit串行Flash存储器,采用SPI接口通信,具有以下特点:

  • 支持标准SPI(模式0和模式3)
  • 最高支持104MHz时钟频率
  • 支持扇区擦除(4KB)、块擦除(32KB/64KB)和整片擦除
  • 页编程操作(每页256字节)
  • 写保护功能和保持/暂停功能

实际项目中我发现,W25Q64对时序要求比较严格。有一次调试时发现写入失败,最后发现是SPI时钟相位配置错误。正确的配置应该是:

  • CPOL=0(空闲时低电平)
  • CPHA=0(第一个边沿采样)

3. STM32 SPI外设初始化实战

下面是一个完整的SPI初始化代码示例,采用STM32标准外设库:

void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; SPI_InitTypeDef SPI_InitStruct; // 1. 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // 2. 配置GPIO // SCK和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); // MISO配置为上拉输入 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStruct); // SS配置为普通推挽输出 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); // 默认不选中 // 3. 配置SPI参数 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; // 模式0 SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge; // 模式0 SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 18MHz @72MHz PCLK SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStruct.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStruct); // 4. 使能SPI SPI_Cmd(SPI1, ENABLE); }

调试时有个小技巧:如果SPI通信不正常,可以先用逻辑分析仪抓取SCK、MOSI和MISO的波形,确认时序是否符合预期。我曾经遇到过因为GPIO速度配置过低导致波形畸变的问题,将GPIO_Speed提高到50MHz后解决。

4. W25Q64驱动开发与功能实现

4.1 基本读写函数封装

首先需要封装几个基础函数:

// 片选控制 void W25Q64_CS(uint8_t state) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)state); } // 发送一个字节并接收返回值 uint8_t SPI1_ReadWriteByte(uint8_t data) { while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(SPI1, data); while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); return SPI_I2S_ReceiveData(SPI1); } // 读取设备ID(用于检测芯片是否正常) uint32_t W25Q64_ReadID(void) { uint32_t id = 0; W25Q64_CS(0); SPI1_ReadWriteByte(0x9F); // JEDEC ID指令 id |= SPI1_ReadWriteByte(0xFF) << 16; id |= SPI1_ReadWriteByte(0xFF) << 8; id |= SPI1_ReadWriteByte(0xFF); W25Q64_CS(1); return id; }

4.2 闪存操作指令实现

W25Q64的操作都需要通过特定指令实现,常见指令如下:

#define W25X_WriteEnable 0x06 #define W25X_WriteDisable 0x04 #define W25X_ReadStatusReg 0x05 #define W25X_WriteStatusReg 0x01 #define W25X_ReadData 0x03 #define W25X_PageProgram 0x02 #define W25X_SectorErase 0x20

实现写使能和等待写入完成函数:

void W25Q64_WriteEnable(void) { W25Q64_CS(0); SPI1_ReadWriteByte(W25X_WriteEnable); W25Q64_CS(1); } void W25Q64_WaitBusy(void) { W25Q64_CS(0); SPI1_ReadWriteByte(W25X_ReadStatusReg); while((SPI1_ReadWriteByte(0xFF) & 0x01) == 0x01); W25Q64_CS(1); }

4.3 扇区擦除与页编程

Flash存储器必须先擦除才能写入,擦除的最小单位是扇区(4KB):

void W25Q64_SectorErase(uint32_t addr) { W25Q64_WriteEnable(); W25Q64_CS(0); SPI1_ReadWriteByte(W25X_SectorErase); SPI1_ReadWriteByte((addr >> 16) & 0xFF); SPI1_ReadWriteByte((addr >> 8) & 0xFF); SPI1_ReadWriteByte(addr & 0xFF); W25Q64_CS(1); W25Q64_WaitBusy(); }

页编程函数(最多写入256字节):

void W25Q64_PageProgram(uint32_t addr, uint8_t *data, uint16_t len) { W25Q64_WriteEnable(); W25Q64_CS(0); SPI1_ReadWriteByte(W25X_PageProgram); SPI1_ReadWriteByte((addr >> 16) & 0xFF); SPI1_ReadWriteByte((addr >> 8) & 0xFF); SPI1_ReadWriteByte(addr & 0xFF); for(uint16_t i=0; i<len; i++) { SPI1_ReadWriteByte(data[i]); } W25Q64_CS(1); W25Q64_WaitBusy(); }

5. 实际应用中的注意事项

  1. 时序问题:W25Q64对指令时序有严格要求,每个指令后需要适当延时
  2. 写保护:写入前必须发送Write Enable指令,且WP引脚需接高电平
  3. 跨页写入:单次写入不能跨页(256字节边界)
  4. 擦除时间:扇区擦除需要较长时间(典型值100ms)
  5. 电源稳定性:在写入和擦除操作期间必须保证电源稳定

一个完整的读写流程示例:

  1. 读取设备ID确认通信正常
  2. 擦除目标扇区
  3. 等待擦除完成
  4. 写入数据
  5. 读取回校验
uint8_t buf[256], rbuf[256]; memset(buf, 0xAA, 256); // 擦除第一个扇区 W25Q64_SectorErase(0x000000); // 写入数据 W25Q64_PageProgram(0x000000, buf, 256); // 读取验证 W25Q64_ReadData(0x000000, rbuf, 256); if(memcmp(buf, rbuf, 256) == 0) { printf("Verify OK!\n"); }

在项目开发中,建议将W25Q64的操作封装成独立的驱动模块,便于移植和维护。对于需要频繁读写的场景,可以考虑实现缓存机制来减少擦写次数,延长Flash寿命。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/5 11:49:09

Java项目智能客服系统实战:从零搭建到生产环境部署

Java项目智能客服系统实战&#xff1a;从零搭建到生产环境部署 摘要&#xff1a;本文针对Java开发者构建智能客服系统时面临的架构设计复杂、NLP集成困难等痛点&#xff0c;详细讲解基于Spring Boot和开源NLP工具的技术方案。通过完整的代码示例演示对话引擎实现&#xff0c;提…

作者头像 李华
网站建设 2026/6/30 12:33:42

轻量高效模型推荐:T4 GPU上0.5秒内完成推理

轻量高效模型推荐&#xff1a;T4 GPU上0.5秒内完成推理 1. 为什么你需要一个“真懂中文”的图像识别模型 你有没有遇到过这样的情况&#xff1a;上传一张办公室照片&#xff0c;模型返回“woman, office, laptop”&#xff0c;但你真正需要的是“白领女性”“商务休闲装”“日…

作者头像 李华
网站建设 2026/7/4 2:25:44

基于Ollama构建智能客服系统的架构设计与实战避坑指南

背景痛点&#xff1a;规则引擎的“三座大山” 过去三年&#xff0c;我维护过两套传统客服系统&#xff1a;一套基于正则关键词&#xff0c;一套基于 DSL 规则树。它们能跑&#xff0c;但越跑越沉&#xff1a; 冷启动像“搬砖”——新业务上线前&#xff0c;运营同学要穷举 20…

作者头像 李华
网站建设 2026/7/1 14:25:07

ADB可视化工具:重新定义Android设备管理体验

ADB可视化工具&#xff1a;重新定义Android设备管理体验 【免费下载链接】adb_kit 使用 Flutter 开发的 ADB GUI 客户端 项目地址: https://gitcode.com/gh_mirrors/ad/adb_kit Android设备管理工具在开发和测试过程中扮演着关键角色&#xff0c;但传统命令行操作模式一…

作者头像 李华
网站建设 2026/7/1 7:28:53

极速掌控Steam游戏资源:Onekey工具如何重构你的游戏管理体验

极速掌控Steam游戏资源&#xff1a;Onekey工具如何重构你的游戏管理体验 【免费下载链接】Onekey Onekey Steam Depot Manifest Downloader 项目地址: https://gitcode.com/gh_mirrors/one/Onekey 当你在Steam平台积累了上百款游戏&#xff0c;是否曾因重装系统丢失所有…

作者头像 李华