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

前言:在前面的文章中,我们已经系统介绍了 SPI 通信原理 以及 W25Q64 的存储结构和操作特性

本篇文章将进入实战阶段,基于 STM32F103C8T6 ,通过 软件 SPI(GPIO 模拟 SPI) 的方式,实现对 W25Q64 外部 Flash 的初始化、ID 读取、扇区擦除、页写入和数据读取,并通过 OLED 显示结果进行验证。

目录

一、接线图

二、硬件连接说明

[三、软件 SPI 分层设计思想](#三、软件 SPI 分层设计思想)

[四、软件 SPI 底层实现](#四、软件 SPI 底层实现)

[五、W25Q64 指令宏定义](#五、W25Q64 指令宏定义)

[六、W25Q64 驱动实现](#六、W25Q64 驱动实现)

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

八、总结


一、接线图

二、硬件连接说明

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

W25Q64 STM32
CS PA4
SCK PA5
MISO PA6
MOSI PA7
VCC 3.3V
GND GND

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 的稳定通信

相关推荐
日更嵌入式的打工仔39 分钟前
(实用向)中断服务程序(ISR)的优化方向
笔记·单片机
想放学的刺客1 小时前
单片机嵌入式试题(第25)嵌入式系统可靠性设计与外设驱动异常处理
stm32·单片机·嵌入式硬件·mcu·物联网
wotaifuzao1 小时前
STM32+FreeRTOS 长期可维护架构设计(事件驱动篇)-- 告别“屎山”代码
c语言·stm32·嵌入式硬件·freertos·状态机·事件驱动·嵌入式架构
淘晶驰AK1 小时前
大学如何自学嵌入式开发?
单片机·嵌入式硬件
yantaohk2 小时前
【2025亲测】中兴B860AV3.2M完美刷机包ATV版本安卓9-解决1G运存BUG,开ADB已ROOT
android·嵌入式硬件·adb·云计算
一路往蓝-Anbo2 小时前
第 1 篇:对象池模式 (Object Pool) —— 裸机下的动态内存革命
jvm·数据库·stm32·单片机·嵌入式硬件·网络协议·tcp/ip
飞凌嵌入式2 小时前
1块集成了4核Cortex-A7高性能CPU、1颗RISC-V MCU、多种高速总线、还兼容树莓派的T153低成本开发板
linux·arm开发·嵌入式硬件·risc-v
大神与小汪3 小时前
STM32WB55蓝牙广播数据
stm32·单片机·嵌入式硬件
芯思路4 小时前
STM32开发学习笔记之七【LCD显示图片】
笔记·stm32·学习
Funing75 小时前
BUCK降压电路如何同时兼顾效率和纹波?——12V 转 3.3V 供电的工程解法
嵌入式硬件·电路·开关电源·buck降压