news 2026/8/30 6:49:04

三十九、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/8/30 13:01:31

PyART完全指南:5步掌握气象雷达数据处理核心技能

PyART完全指南&#xff1a;5步掌握气象雷达数据处理核心技能 【免费下载链接】pyart The Python-ARM Radar Toolkit. A data model driven interactive toolkit for working with weather radar data. 项目地址: https://gitcode.com/gh_mirrors/py/pyart PyART&#x…

作者头像 李华
网站建设 2026/8/29 21:35:54

3分钟掌握在线UML绘图:PlantUML Editor完全使用指南

3分钟掌握在线UML绘图&#xff1a;PlantUML Editor完全使用指南 【免费下载链接】plantuml-editor PlantUML online demo client 项目地址: https://gitcode.com/gh_mirrors/pl/plantuml-editor 还在为复杂的UML绘图软件头疼吗&#xff1f;PlantUML Editor这款在线工具帮…

作者头像 李华
网站建设 2026/8/29 17:20:25

5分钟快速上手Unitree Go2机器人:ROS2集成终极指南

5分钟快速上手Unitree Go2机器人&#xff1a;ROS2集成终极指南 【免费下载链接】go2_ros2_sdk Unofficial ROS2 SDK support for Unitree GO2 AIR/PRO/EDU 项目地址: https://gitcode.com/gh_mirrors/go/go2_ros2_sdk Unitree Go2 ROS2 SDK为宇树科技GO2系列机器人&…

作者头像 李华
网站建设 2026/8/30 3:26:32

开源项目维护的3大挑战与5个实用解决方案

在开源软件生态中&#xff0c;项目维护是一项充满挑战却又极具价值的工作。以trackerslist项目为例&#xff0c;这个专门收集公共网络资源服务器列表的开源项目&#xff0c;展现了社区协作在技术资源管理中的重要作用。该项目目前维护着112个公共服务器&#xff0c;涵盖UDP、HT…

作者头像 李华
网站建设 2026/8/30 3:26:32

夸克网盘自动化配置终极指南:三步搞定懒人追剧神器

夸克网盘自动化配置终极指南&#xff1a;三步搞定懒人追剧神器 【免费下载链接】quark-auto-save 夸克网盘签到、自动转存、命名整理、发推送提醒和刷新媒体库一条龙 项目地址: https://gitcode.com/gh_mirrors/qu/quark-auto-save 当你想追的剧集持续更新&#xff0c;却…

作者头像 李华