news 2026/7/10 12:24:14

三十九、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/7/10 10:29:37

【高级进阶】Laravel 13多模态事件监听的4种高性能应用场景

第一章&#xff1a;Laravel 13多模态事件监听的核心机制Laravel 13 引入了多模态事件监听机制&#xff0c;允许开发者在单一事件触发时&#xff0c;同步响应多种处理模式&#xff0c;包括同步执行、队列异步处理、条件性广播等。该机制通过增强事件分发器&#xff08;Event Dis…

作者头像 李华
网站建设 2026/7/10 11:57:02

Applite:让Mac软件管理告别命令行的智能助手

Applite&#xff1a;让Mac软件管理告别命令行的智能助手 【免费下载链接】Applite User-friendly GUI macOS application for Homebrew Casks 项目地址: https://gitcode.com/gh_mirrors/ap/Applite 还在为复杂的命令行操作而头疼吗&#xff1f;Applite为你带来革命性的…

作者头像 李华
网站建设 2026/7/10 11:52:56

用代码绘制动态艺术:particles.js物理动画深度解析

用代码绘制动态艺术&#xff1a;particles.js物理动画深度解析 【免费下载链接】particles.js A lightweight JavaScript library for creating particles 项目地址: https://gitcode.com/gh_mirrors/pa/particles.js 在数字艺术的世界里&#xff0c;有一种特殊的视觉语…

作者头像 李华
网站建设 2026/7/10 11:55:46

明日方舟界面美化终极指南:5步打造专属游戏UI

明日方舟界面美化终极指南&#xff1a;5步打造专属游戏UI 【免费下载链接】arknights-ui H5 复刻版明日方舟游戏主界面 项目地址: https://gitcode.com/gh_mirrors/ar/arknights-ui 想要为《明日方舟》打造独一无二的个性化界面吗&#xff1f;Arknights-UI项目为您提供了…

作者头像 李华
网站建设 2026/7/10 12:09:30

数据库管理工具无限试用终极指南:告别14天限制的完整方案

数据库管理工具无限试用终极指南&#xff1a;告别14天限制的完整方案 【免费下载链接】navicat_reset_mac navicat16 mac版无限重置试用期脚本 项目地址: https://gitcode.com/gh_mirrors/na/navicat_reset_mac 还在为专业数据库管理工具的试用期到期而烦恼吗&#xff1…

作者头像 李华
网站建设 2026/7/10 12:04:20

R语言在气象预测中的应用(十年经验精华总结)

第一章&#xff1a;R语言在气象预测中的趋势分析概述R语言凭借其强大的统计计算与可视化能力&#xff0c;已成为气象科学领域中趋势分析的重要工具。随着全球气候变化加剧&#xff0c;对气温、降水、风速等气象要素的长期趋势进行建模与预测变得尤为关键。R提供了丰富的包生态系…

作者头像 李华