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

三十九、STM32的SPI(软件读写W25Q64)

前言:在前面的文章中,我们已经系统介绍了SPI 通信原理以及W25Q64 的存储结构和操作特性
本篇文章将进入实战阶段,基于STM32F103C8T6,通过软件 SPI(GPIO 模拟 SPI)的方式,实现对W25Q64 外部 Flash 的初始化、ID 读取、扇区擦除、页写入和数据读取,并通过 OLED 显示结果进行验证。

目录

一、接线图

二、硬件连接说明

三、软件 SPI 分层设计思想

四、软件 SPI 底层实现

五、W25Q64 指令宏定义

六、W25Q64 驱动实现

七、主函数测试与实验现象

八、总结


一、接线图

二、硬件连接说明

本实验使用 STM32F103C8T6 与 W25Q64 通过 SPI 方式连接,引脚定义如下:

W25Q64STM32
CSPA4
SCKPA5
MISOPA6
MOSIPA7
VCC3.3V
GNDGND

SPI 工作模式:Mode 0(CPOL = 0,CPHA = 0)

三、软件 SPI 分层设计思想

为了让代码结构清晰,本工程将软件 SPI 分为两层:

1.引脚配置层(GPIO 操作)

  • 只关心:SS / SCK / MOSI / MISO

  • 提供“写引脚 / 读引脚”的接口

2. 协议层(SPI 时序)

  • 基于引脚操作实现:

    • SPI 起始 / 终止

    • 字节交换(8 位时序)

W25Q64 驱动层完全不关心 GPIO 细节,只调用 SPI 接口,结构非常清晰。

四、软件 SPI 底层实现

1. SPI 引脚操作函数

#include "stm32f10x.h" // Device header /*引脚配置层*/ void MySPI_W_SS(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); } void MySPI_W_SCK(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); } void MySPI_W_MOSI(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); } uint8_t MySPI_R_MISO(void) { return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); }

2. SPI GPIO 初始化

void MySPI_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOA, &GPIO_InitStructure); MySPI_W_SS(1); // CS 默认拉高 MySPI_W_SCK(0); // SPI Mode0:SCK 空闲为低 }

3. SPI 起始 / 终止

void MySPI_Start(void) { MySPI_W_SS(0); } void MySPI_Stop(void) { MySPI_W_SS(1); }

4. SPI 字节交换(Mode 0)

uint8_t MySPI_SwapByte(uint8_t ByteSend) { uint8_t i, ByteReceive = 0x00; for (i = 0; i < 8; i++) { MySPI_W_MOSI(ByteSend & (0x80 >> i)); MySPI_W_SCK(1); if (MySPI_R_MISO()) { ByteReceive |= (0x80 >> i); } MySPI_W_SCK(0); } return ByteReceive; }

说明:

  • 上升沿发送数据

  • 上升沿采样 MISO

  • 完全符合 SPI Mode 0 时序

五、W25Q64 指令宏定义

#ifndef __W25Q64_INS_H #define __W25Q64_INS_H #define W25Q64_WRITE_ENABLE 0x06 #define W25Q64_READ_STATUS_REGISTER_1 0x05 #define W25Q64_PAGE_PROGRAM 0x02 #define W25Q64_SECTOR_ERASE_4KB 0x20 #define W25Q64_READ_DATA 0x03 #define W25Q64_JEDEC_ID 0x9F #define W25Q64_DUMMY_BYTE 0xFF #endif

六、W25Q64 驱动实现

1. 初始化

void W25Q64_Init(void) { MySPI_Init(); }

2. 读取芯片 ID(验证通信是否成功)

void W25Q64_ReadID(uint8_t *MID, uint16_t *DID) { MySPI_Start(); MySPI_SwapByte(W25Q64_JEDEC_ID); *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); *DID <<= 8; *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); MySPI_Stop(); }

3. 写使能与忙等待

void W25Q64_WriteEnable(void) { MySPI_Start(); MySPI_SwapByte(W25Q64_WRITE_ENABLE); MySPI_Stop(); } void W25Q64_WaitBusy(void) { uint32_t Timeout = 100000; MySPI_Start(); MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); while (MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) { if (--Timeout == 0) break; } MySPI_Stop(); }

4. 扇区擦除(4KB)

void W25Q64_SectorErase(uint32_t Address) { W25Q64_WriteEnable(); MySPI_Start(); MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); MySPI_SwapByte(Address >> 16); MySPI_SwapByte(Address >> 8); MySPI_SwapByte(Address); MySPI_Stop(); W25Q64_WaitBusy(); }

5. 页写入(不跨页)

void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count) { uint16_t i; W25Q64_WriteEnable(); MySPI_Start(); MySPI_SwapByte(W25Q64_PAGE_PROGRAM); MySPI_SwapByte(Address >> 16); MySPI_SwapByte(Address >> 8); MySPI_SwapByte(Address); for (i = 0; i < Count; i++) { MySPI_SwapByte(DataArray[i]); } MySPI_Stop(); W25Q64_WaitBusy(); }

6. 数据读取

void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count) { uint32_t i; MySPI_Start(); MySPI_SwapByte(W25Q64_READ_DATA); MySPI_SwapByte(Address >> 16); MySPI_SwapByte(Address >> 8); MySPI_SwapByte(Address); for (i = 0; i < Count; i++) { DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); } MySPI_Stop(); }

七、主函数测试与实验现象

uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04}; uint8_t ArrayRead[4]; int main(void) { OLED_Init(); W25Q64_Init(); W25Q64_ReadID(&MID, &DID); W25Q64_SectorErase(0x000000); W25Q64_PageProgram(0x000000, ArrayWrite, 4); W25Q64_ReadData(0x000000, ArrayRead, 4); while (1) {} }

实验现象:

  • OLED 正确显示 MID / DID

  • 写入数组与读取数组完全一致

  • 说明:
    软件 SPI + W25Q64 读写擦除功能完全正常

八、总结

通过本实验,我们完整实现了:

  • 软件 SPI 的 GPIO 模拟

  • W25Q64 的 ID 读取

  • 扇区擦除、页写入、数据读取

  • STM32 与外部 Flash 的稳定通信

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

相关文章:

  • 【PHP高手进阶必备】:深入解读PHP 8.6废弃函数与替代方案(附检测脚本)
  • PyART完全指南:5步掌握气象雷达数据处理核心技能
  • 3分钟掌握在线UML绘图:PlantUML Editor完全使用指南
  • 5分钟快速上手Unitree Go2机器人:ROS2集成终极指南
  • 开源项目维护的3大挑战与5个实用解决方案
  • 夸克网盘自动化配置终极指南:三步搞定懒人追剧神器
  • QQ音乐数据获取技术揭秘:Python工具深度剖析与实战应用
  • Display Driver Uninstaller完整教程:快速解决显卡驱动问题的终极方案
  • 从零构建农业物联网网关,PHP开发者必须掌握的3种主流协议对比
  • OpenCore Legacy Patcher深度解析:老Mac跨代升级的完整技术方案
  • 揭秘空间转录组数据分析:5大R语言聚类算法核心原理与应用
  • 为什么你的田间试验数据总不显著?R语言方差分析常见误区全避坑
  • 农业物联网数据传输稳定性提升指南(PHP网关协议优化全攻略)
  • [收藏必看] 从0到1实战:用LlamaIndex微调Embedding模型,RAG系统检索准确率提升14.6%!
  • 实习面试题-Nginx 配置面试题
  • 大模型Agent面试精选15题(第三辑)LangChain框架与Agent开发的高频面试题
  • OpenCore Legacy Patcher强力解决方案:突破macOS硬件限制的专业指南
  • (新卷,200分)- 评论转换输出(Java JS Python)
  • FLUX.1-dev图文理解能力深度测评:视觉问答与指令微调表现分析
  • 内点法求最优潮流(Matlab代码实现)
  • 如何快速释放Windows磁盘空间:终极存储分析工具完整指南
  • ComfyUI与Redis缓存集成:加速大规模生成任务
  • HunyuanVideo-Foley离线版发布:支持内网部署与私有化音效生成
  • ComfyUI工作流整合Stable Diffusion 3.5 FP8:实现批量生成高效输出
  • 10、Z变换:原理、计算与应用详解
  • 17、FIR和IIR滤波器的结构与实现
  • Mobile Select移动端滚动选择器深度解析:从基础应用到企业级解决方案
  • 22、IIR滤波器的逐步设计
  • 26、数字滤波器中的有限字长效应及相关知识解析
  • ZonyLrcToolsX 歌词下载工具完全使用手册