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

深入解析STM32F103 SPI与IIC读写Flash实战:以W25Q128为例

1. SPI与IIC协议基础对比

在嵌入式系统中,SPI和IIC是两种最常用的串行通信协议。先打个比方:SPI就像两个人在用对讲机通话(全双工),而IIC更像是轮流发言的会议(半双工)。这两种协议在STM32F103上操作W25Q128 Flash时,底层实现差异非常明显。

SPI协议特点

  • 四线制(MOSI/MISO/SCLK/CS)
  • 全双工同步通信
  • 时钟速率可达18MHz(STM32F103)
  • 无设备地址机制,靠片选信号选择设备

IIC协议特点

  • 两线制(SDA/SCL)
  • 半双工通信
  • 标准模式100kHz,快速模式400kHz
  • 通过设备地址寻址(W25Q128的IIC地址是0x50)

实测发现,SPI模式下W25Q128的页写入(256字节)仅需1.2ms,而IIC模式需要8ms左右。这是因为SPI的硬件移位寄存器直接处理数据交换,而IIC需要软件模拟每个时钟周期的时序。

2. 硬件连接与初始化配置

2.1 SPI硬件连接方案

以STM32F103ZET6为例,推荐使用SPI1或SPI2接口:

// SPI2引脚配置(PB12-15) GPIOB->CRH &= ~(0xF000F000); // 清除PB13/15设置 GPIOB->CRH |= 0xB000B000; // PB13/15复用推挽输出 GPIOB->CRH |= 0x00030000; // PB12普通推挽输出(CS)

特别注意:W25Q128的HOLD和WP引脚需要上拉到VCC,否则可能无法写入数据。我在调试时就遇到过因为WP引脚接触不良导致写操作失败的坑。

2.2 IIC硬件连接方案

虽然W25Q128主要支持SPI,但通过转换芯片可以用IIC控制:

// IIC1引脚配置(PB6/7) GPIOB->CRL &= ~(0xFF000000); GPIOB->CRL |= 0xFF000000; // PB6/7开漏输出

IIC模式下需要特别注意上拉电阻(通常4.7kΩ),过小的阻值会导致信号上升沿不满足时序要求。曾经因为用了1kΩ电阻导致通信不稳定,折腾了半天才发现问题。

3. 底层驱动实现对比

3.1 SPI基础函数实现

SPI的优势在于STM32硬件原生支持:

uint8_t SPI_ExchangeByte(uint8_t data) { while(!(SPI2->SR & SPI_SR_TXE)); // 等待发送缓冲区空 SPI2->DR = data; while(!(SPI2->SR & SPI_SR_RXNE)); // 等待接收完成 return SPI2->DR; }

这个函数同时完成发送和接收,体现了SPI全双工特性。实测发现,使用DMA传输大批量数据时,SPI速率可以达到IIC的20倍以上。

3.2 IIC基础函数实现

IIC需要完全用GPIO模拟时序:

void IIC_WriteBit(uint8_t bit) { SDA = bit ? 1 : 0; Delay_us(2); // 建立时间 SCL = 1; Delay_us(5); // 高电平保持 SCL = 0; Delay_us(2); // 低电平保持 }

每个字节传输都需要9个时钟周期(8位数据+1位ACK),这就是IIC速度慢的根本原因。在调试IIC时,逻辑分析仪是必备工具,可以清晰看到每个时钟沿的数据变化。

4. W25Q128操作实战

4.1 关键操作指令对比

操作SPI指令码IIC等效操作耗时对比
写使能0x06发送控制字节0x50SPI快3倍
页编程0x02写地址+数据SPI快8倍
扇区擦除0x20特殊命令序列SPI快5倍

4.2 SPI模式下的页编程

这是最常用的写入操作:

void W25Q128_SPI_WritePage(uint32_t addr, uint8_t *data) { SPI_CS_LOW(); SPI_ExchangeByte(0x02); // 页编程指令 SPI_ExchangeByte(addr >> 16); SPI_ExchangeByte(addr >> 8); SPI_ExchangeByte(addr); for(int i=0; i<256; i++) { SPI_ExchangeByte(data[i]); } SPI_CS_HIGH(); W25Q128_WaitBusy(); // 等待写入完成 }

注意点:W25Q128的写入操作必须按页对齐(256字节),跨页写入会导致数据回卷。我曾经因为没注意这点,导致前256字节被意外覆盖。

4.3 IIC模式下的写入操作

虽然非标准但可以实现:

void W25Q128_IIC_Write(uint32_t addr, uint8_t data) { IIC_Start(); IIC_SendByte(0x50<<1); // 设备地址+写 IIC_WaitAck(); IIC_SendByte(addr>>16); // 24位地址 IIC_WaitAck(); IIC_SendByte(addr>>8); IIC_WaitAck(); IIC_SendByte(addr); IIC_WaitAck(); IIC_SendByte(data); IIC_WaitAck(); IIC_Stop(); Delay_ms(5); // 写入周期较长 }

5. 工程选型建议

根据实测数据给出建议:

  1. 高速数据记录:必须选择SPI模式

    • 最高18MHz时钟
    • DMA支持批量传输
    • 适合固件存储、音频数据等
  2. 引脚资源紧张:考虑IIC模式

    • 仅需2个GPIO
    • 适合低速配置存储
    • 多设备共享总线
  3. 混合使用场景:SPI+硬件CS

    • 多个SPI设备时分复用
    • 通过片选切换设备
    • 注意总线负载能力

特别提醒:当使用硬件SPI时,CLK相位和极性必须与W25Q128设置一致(模式0或模式3),否则会出现数据错位。配置示例:

SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 时钟极性 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // 时钟相位

6. 常见问题排查

SPI模式下的典型问题

  1. 数据全为0xFF:检查CS信号是否有效
  2. 随机数据错误:确认CLK极性/相位设置
  3. 写入失败:确保WP引脚已上拉

IIC模式下的典型问题

  1. 无ACK响应:检查设备地址和上拉电阻
  2. 时序错误:用示波器查看SCL/SDA波形
  3. 写入超时:适当延长等待时间

有个容易忽略的点:W25Q128上电后默认处于写保护状态,无论哪种接口都需要先发送写使能命令(0x06)才能修改数据。

7. 性能优化技巧

  1. SPI DMA传输
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI2->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)buffer; DMA_InitStructure.DMA_BufferSize = 256; DMA_Cmd(DMA1_Channel5, ENABLE); SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);
  1. 四线快速读取(需硬件支持):
SPI_ExchangeByte(0xEB); // Fast Read Quad I/O SPI_ExchangeByte(addr>>16); // ...发送地址后切换IO模式
  1. 扇区预擦除:在空闲时提前擦除待用扇区

实测发现,使用DMA传输+四线模式,读取速度可以提升到标准SPI的4倍,这对大容量数据读取非常有利。

8. 代码架构设计建议

推荐的分层架构:

应用层 ├─ 文件系统层(FatFS) ├─ Flash操作层 │ ├─ SPI接口实现 │ └─ IIC接口实现 └─ 硬件抽象层 ├─ SPI驱动 └─ IIC驱动

这种架构下,更换通信接口只需修改Flash操作层的实现,上层应用代码无需变动。我在实际项目中就遇到过需要从SPI切换到QSPI的情况,良好的分层设计让迁移工作变得非常轻松。

最后提醒:W25Q128的扇区擦除(4KB)需要较长时间(典型值400ms),建议在系统空闲时执行擦除操作,或者使用双Bank交替工作模式来避免等待。

http://www.cnnetsun.cn/news/1354705.html

相关文章:

  • 51单片机双机UART通信实战:从协议设计到Proteus仿真全解析
  • ThinkPHP3.2.3安全指南:如何防范常见的SQL注入攻击
  • 突破魔兽争霸III兼容性壁垒:WarcraftHelper实战优化指南
  • Whisper语音识别惊艳案例:会议录音、外语学习转文字真实体验
  • 48tools小红书直播抓取技术攻坚:从故障到突破的全解析
  • LC-3指令集实战:用汇编语言实现简易计算器(附完整代码)
  • SPIRAN ART SUMMONER图像生成效果对比:不同模型版本性能评测
  • 信捷XDH Ethercat A_MOVER指令详解:从参数配置到运动控制实战
  • DELL服务器iDRAC远程安装CentOS 7避坑指南:从ISO映射到系统配置全流程
  • Docker容器时间漂移?Snowflake算法报错Clock moved backwards的3种修复方案
  • 5块钱搞定键盘主控?国产APM32F103实战教程(附PCB免费打样攻略)
  • ROG Zephyrus G14性能突破:GHelper降压超频实战指南
  • GPEN肖像增强新手指南:无需安装环境,打开浏览器就能用
  • 从PHY芯片视角解析:如何实现高精度IEEE 1588v2时间同步
  • 温州市青少年程序设计竞赛(小学组)真题解析与算法精讲
  • Ubuntu18.04有线网络驱动安装与故障排除指南
  • 从subprocess.CalledProcessError到Git仓库状态:深入解析exit status 128的根源与修复策略
  • FireRedASR-AED-L赋能在线教育:实现AI驱动的实时语音作业批改
  • 指纹识别算法优化指南:如何提升MATLAB程序的运行效率与准确率
  • Podman新手必看:6大核心命令模块详解(附常用场景示例)
  • Qwen3-0.6B-FP8新手避坑指南:从环境检查到成功对话的每一步
  • KALI Linux 2024最新版Docker安装避坑指南(附阿里云镜像加速配置)
  • VSCode下载与配置:多模态语义评估引擎的开发工具链
  • MedGemma-X多模态实践:结合自然语言处理的智能报告生成
  • 计算机组成原理视角下的LiuJuan20260223Zimage优化
  • LiveCharts2项目实战:从源码到可执行程序的完整构建指南
  • FireRedASR Pro在在线教育场景落地:实时课堂字幕与内容分析
  • 避坑指南:为什么你的git submodule update --init --recursive总是失败?
  • Qwen3.5-27B保姆级部署教程:开源多模态模型在4×4090D环境免配置启动
  • Leather Dress Collection 生成内容安全与合规性审核方案