news 2026/4/23 16:53:20

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
三十九、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 的稳定通信

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

Zotero Reference插件完全指南:从安装到高效文献管理

Zotero Reference是一款专为Zotero设计的PDF参考文献插件&#xff0c;通过智能解析PDF文件中的参考文献信息&#xff0c;帮助学术研究者快速构建文献网络&#xff0c;提升文献管理效率。无论你是文献管理新手还是资深研究者&#xff0c;这款插件都能让你的学术工作事半功倍。 【…

作者头像 李华
网站建设 2026/4/23 10:45:22

【资深架构师亲授】:Rust编写PHP扩展的7个编译优化关键点

第一章&#xff1a;Rust-PHP扩展编译优化概述在现代高性能Web开发中&#xff0c;PHP作为广泛使用的脚本语言&#xff0c;其执行效率在复杂计算场景下常成为瓶颈。通过引入Rust编写PHP扩展&#xff0c;不仅可以利用Rust的内存安全与零成本抽象特性&#xff0c;还能显著提升关键路…

作者头像 李华
网站建设 2026/4/23 10:44:06

农业传感器数据存储实战(PHP高性能存储架构设计)

第一章&#xff1a;农业传感器数据存储的挑战与PHP架构选型在现代农业系统中&#xff0c;传感器网络持续采集土壤湿度、气温、光照强度等关键数据&#xff0c;这些数据具有高频次、高并发和持久化写入的特点。传统的单体式PHP应用在处理此类场景时面临显著性能瓶颈&#xff0c;…

作者头像 李华
网站建设 2026/4/22 23:56:44

终极指南:5分钟掌握particles.js粒子物理系统

终极指南&#xff1a;5分钟掌握particles.js粒子物理系统 【免费下载链接】particles.js A lightweight JavaScript library for creating particles 项目地址: https://gitcode.com/gh_mirrors/pa/particles.js 想要为网站添加专业级动态背景却苦于复杂的物理计算&…

作者头像 李华
网站建设 2026/4/23 13:37:14

PHP 8.6错误码突变预警:升级前必须验证的7个兼容性问题

第一章&#xff1a;PHP 8.6 错误码定义的重大变更概述PHP 8.6 在错误处理机制上进行了重要调整&#xff0c;尤其在错误码的定义与分类方面引入了更清晰、一致的规范。这些变更旨在提升开发者调试效率&#xff0c;增强跨版本兼容性&#xff0c;并为未来扩展预留空间。统一错误码…

作者头像 李华