深入浅出SPI通信协议与STM32实战应用(W25Q128驱动)(实战部分)

1. W25Q128简介

W25Q128 是Winbond推出的128M-bit(16MB)SPI接口Flash存储器,支持标准SPI、Dual-SPI和Quad-SPI模式。关键特性:

  • 工作电压:2.7V~3.6V
  • 分页结构:256页/块,每块16KB,共1024块
  • 支持页编程(256字节/页)
  • 擦除操作支持:扇区擦除(4KB)、块擦除(32/64KB)、全片擦除
  • 最高104MHz时钟频率
  • 超过10万次擦写周期
    在这里插入图片描述

1.1 W25Q128存储架构

W25Q128存储器采用分级存储架构,总容量为16MB(128Mbit)。其物理存储结构按以下层级划分:

  • 顶层划分:256个存储块(Block),每块容量64KB
  • 块内结构:每个存储块包含16个扇区(Sector),每扇区容量4KB
  • 扇区组成:每个扇区细分为16个存储页(Page),每页容量256字节

该存储器的擦除机制具有以下特性:

  • 最小擦除单位为单个扇区(4KB)
  • 执行擦除操作时必须完整处理整个扇区
  • 系统需预置4KB对齐的缓冲区,以满足物理擦除粒度要求在这里插入图片描述

1.2 W25Q128常用指令

W25Q128 有非常多的指令,我们介绍几个常用的指令。

指令(HEX)名称 作用
0x06写使能写入数据/擦除之前,必须先发送该指令
0x05读 SR1判定 FLASH 是否处于空闲状态,擦除用
0x03读数据读取数据
0x02页写写入数据,最多写256字节
0x20扇区擦除扇区擦除指令,最小擦除单位

1.3 W25Q128 操作时序详解

  1. 写使能(06h)- 指令功能‌:执行编程/擦除操作的必要使能信号
    • 操作流程:
      ① CS引脚置低(通信起始)
      ② 发送1字节指令码06h(MSB先发)
      ③ CS引脚置高(通信终止)
    • 状态影响‌:WEL位(SR1)置1,有效时间约50ms

  1. 状态寄存器读取(05h)
    • 指令格式‌:
      ┌──────┬─────────────┐
      | 指令码 | 响应数据(持续可读) |
      └──────┴─────────────┘
    • 操作时序**‌:
      ① CS置低 → 发送05h → 连续读取SR1 → CS置高‌
    • 技术要点‌:
      BUSY位(SR1)监控:0=空闲,1=操作中
      支持全双工SPI模式(MOSI/MISO同步传输)

  1. 数据读取(03h)
    • 指令结构‌:
      ┌──────┬──────────┬─────────┐
      | 03h | 24bit地址 | 数据流(≥1字节) |
      └──────┴──────────┴─────────┘
    • 执行步骤‌:
      1. 起始地址对齐(按页边界自动递增)
      2. 单次读取最大长度:256字节(单页容量)
      3. 跨页读取需重新发送地址指令

  1. 页编程(02h)
    • 技术限制‌:
      • 目标区域必须处于擦除状态(全FFh)
      • 单次写入跨度≤256字节(禁止跨页写入)
    • 操作序列‌:
      [CS↓] → 02h → A23-A0 → Data_1~Data_n → [CS↑]
    • 物理特性‌:
      实际编程时间约0.7-1.2ms(需BUSY状态轮询)

  1. 扇区擦除(20h)
    • 约束条件‌:
      • 擦除粒度:4KB(地址自动对齐到扇区起始)
      • 擦除后状态:全FFh(电平=1)
    • 时序流程‌:
      [CS↓] → 20h → 目标地址(A23-A0) → [CS↑]
    • 耗时范围‌:
      典型值400ms(温度25℃),最大600ms

2. 硬件连接

这篇文章,使用的是W25Q128模块,W25Q128 的模块各个厂家做的各有不同,只是长得不一样而已,使用方式、引脚都是一样的。
通过产品手册如下:在这里插入图片描述

STM32F103C8T6W25Q128 接线方式:

STM32引脚W25Q128引脚功能
PA5CLKSPI时钟
PA6MISO主机输入
PA7MOSI主机输出
PA4CS片选信号
3.3VVCC电源
GNDGND

注意:CS引脚需通过GPIO控制,不可固定接低电平

3. 代码实现

在这里插入图片描述

3.1 SPI及GPIO初始化

在进行初始化时, 我们需要查看参考手册:在这里插入图片描述
按照手册和实际情况进行GPIO口的配置

SPI_HandleTypeDef spi_handle = {0};
void w25q128_spi_init(void)
{spi_handle.Instance = SPI1;spi_handle.Init.Mode = SPI_MODE_MASTER;spi_handle.Init.Direction = SPI_DIRECTION_2LINES;spi_handle.Init.DataSize = SPI_DATASIZE_8BIT;spi_handle.Init.CLKPolarity = SPI_POLARITY_LOW;              /* CPOL = 0 */spi_handle.Init.CLKPhase = SPI_PHASE_1EDGE;                  /* CPHA = 0 */spi_handle.Init.NSS = SPI_NSS_SOFT;spi_handle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;spi_handle.Init.FirstBit = SPI_FIRSTBIT_MSB;spi_handle.Init.TIMode = SPI_TIMODE_DISABLE;spi_handle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;spi_handle.Init.CRCPolynomial = 7;HAL_SPI_Init(&spi_handle);
}void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{if(hspi->Instance == SPI1){GPIO_InitTypeDef gpio_initstruct;//打开时钟__HAL_RCC_GPIOA_CLK_ENABLE();                           // 使能GPIOB时钟__HAL_RCC_SPI1_CLK_ENABLE();//调用GPIO初始化函数gpio_initstruct.Pin = GPIO_PIN_4;          gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;           gpio_initstruct.Pull = GPIO_PULLUP;                    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;          HAL_GPIO_Init(GPIOA, &gpio_initstruct);gpio_initstruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;          gpio_initstruct.Mode = GPIO_MODE_AF_PP;           HAL_GPIO_Init(GPIOA, &gpio_initstruct);gpio_initstruct.Pin = GPIO_PIN_6;          gpio_initstruct.Mode = GPIO_MODE_INPUT;           HAL_GPIO_Init(GPIOA, &gpio_initstruct);}
}

3.2 W25Q128驱动函数

w251128.c

// 交换一个字节
uint8_t w25q128_spi_swap_byte(uint8_t data)
{uint8_t recv_data = 0;HAL_SPI_TransmitReceive(&spi_handle, &data, &recv_data, 1, 1000);return recv_data;
}// 初始化:
void w25q128_init(void)
{w25q128_spi_init();
}// 读取iD值
uint16_t w25q128_read_id(void)
{uint16_t device_id = 0;W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_ManufactDeviceID);w25q128_spi_swap_byte(0x00);w25q128_spi_swap_byte(0x00);w25q128_spi_swap_byte(0x00);device_id = w25q128_spi_swap_byte(FLASH_DummyByte) << 8;device_id |= w25q128_spi_swap_byte(FLASH_DummyByte);W25Q128_CS(1);return device_id;
}// 写使能void w25q128_writ_enable(void)
{W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_WriteEnable);W25Q128_CS(1);
}// 读取状态寄存器1
uint8_t w25q128_read_sr1(void)
{uint8_t recv_data = 0;W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_ReadStatusReg1);recv_data = w25q128_spi_swap_byte(FLASH_DummyByte);W25Q128_CS(1);return recv_data;
}// 等待BUSY
void w25q128_wait_busy(void)
{while((w25q128_read_sr1() & 0x01) == 0x01);
}
// 发送地址
void w25q128_send_address(uint32_t address)
{w25q128_spi_swap_byte(address >> 16);w25q128_spi_swap_byte(address >> 8);w25q128_spi_swap_byte(address);
}
// 读取数据
void w25q128_read_data(uint32_t address, uint8_t *data, uint32_t size)
{uint32_t i = 0;W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_ReadData);w25q128_send_address(address);for(i = 0; i < size; i++)data[i] = w25q128_spi_swap_byte(FLASH_DummyByte);W25Q128_CS(1);
}// 页写
void w25q128_write_page(uint32_t address, uint8_t *data, uint16_t size)
{uint16_t i = 0;w25q128_writ_enable();W25Q128_CS(0);w25q128_spi_swap_byte(FLASH_PageProgram);w25q128_send_address(address);for(i = 0; i < size; i++)w25q128_spi_swap_byte(data[i]);W25Q128_CS(1);//等待空闲w25q128_wait_busy();
}// 擦除
void w25q128_erase_sector(uint32_t address)
{//写使能w25q128_writ_enable();//等待空闲w25q128_wait_busy();//拉低片选W25Q128_CS(0);//发送扇区擦除指令w25q128_spi_swap_byte(FLASH_SectorErase);//发送地址w25q128_send_address(address);//拉高片选W25Q128_CS(1);//等待空闲w25q128_wait_busy();
}
w25q128.h
#ifndef __W25Q128_H__
#define __W25Q128_H__#include "sys.h"#define W25Q128_CS(x)   do{ x ? \HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET): \HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); \}while(0)/* 指令表 */
#define FLASH_ManufactDeviceID                  0x90
#define FLASH_WriteEnable                       0x06
#define FLASH_ReadStatusReg1                    0x05
#define FLASH_ReadData                          0x03
#define FLASH_PageProgram                       0x02
#define FLASH_SectorErase                       0x20
#define FLASH_DummyByte                         0xFFvoid w25q128_init(void);
uint16_t w25q128_read_id(void);
void w25q128_read_data(uint32_t address, uint8_t *data, uint32_t size);
void w25q128_write_page(uint32_t address, uint8_t *data, uint16_t size);
void w25q128_erase_sector(uint32_t address);#endif

3.3 使用示例

#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "w25q128.h"uint8_t data_write[4] = {0xAA, 0xBB, 0xCC, 0xDD};
uint8_t data_read[4] = {0};
int main(void)
{HAL_Init();                         /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */led_init();                         /* 初始化LED灯 */uart1_init(115200);w25q128_init();printf("hello world!\r\n");uint16_t device_id = w25q128_read_id();printf("device id: %X\r\n", device_id);//    w25q128_erase_sector(0x000000);
//    w25q128_write_page(0x000000, data_write, 4);w25q128_read_data(0x000000, data_read, 4);printf("data read: %X, %X, %X, %X\r\n", data_read[0], data_read[1], data_read[2], data_read[3]);while(1){ }
}

4. 注意事项

  1. 擦除操作必需:Flash写入前必须擦除对应区域
  2. 页边界限制:单次写入不可跨页(每页256字节)
  3. 时钟配置:确保SPI时钟不超过芯片规格(建议初始使用较低频率)
  4. 状态检测:重要操作后需检查BUSY位
  5. 电源稳定:Flash操作期间需保持3.3V稳定

5. 常见问题排查

  • 无法读取ID:检查SPI模式(CPOL=0/CPHA=0),确认CS信号时序
  • 写入失败:确保已执行写使能命令,检查电源电压
  • 数据错误:注意地址对齐,排除SPI时钟干扰

完整工程代码可在Gitee获取:https://gitee.com/bad-lemon/mcu-development-record.git


实现效果:通过上述代码可实现W25Q128的读写操作,实测写入速度可达600KB/s,读取速度1.2MB/s(SPI时钟18MHz)。该方案适用于需要大容量非易失存储的物联网设备、数据采集系统等场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/76267.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32 HAL库之EXTI示例代码

外部中断按键控制LED灯 在main.c中 HAL_Init(); 初始化Flash&#xff0c;中断优先级以及HAL_MspInit函数&#xff0c;也就是 stm32f1xx_hal.c 中 HAL_StatusTypeDef HAL_Init(void) {/* Configure Flash prefetch */ #if (PREFETCH_ENABLE ! 0) #if defined(STM32F101x6) || …

查看手机在线状态,保障设备安全运行

手机作为人们日常生活中不可或缺的工具&#xff0c;承载着沟通、工作、娱乐等多种功能。保障手机设备的安全运行是我们每个人都非常重要的任务&#xff0c;而了解手机的在线状态则是其中的一环。通过挖数据平台提供的在线查询工具&#xff0c;我们可以方便快捷地查询手机号的在…

Llama 4全面评测:官方数据亮眼,社区测试显不足之处

引言 2025年4月&#xff0c;Meta正式发布了全新的Llama 4系列模型&#xff0c;这标志着Llama生态系统进入了一个全新的时代。Llama 4不仅是Meta首个原生多模态模型&#xff0c;还采用了混合专家(MoE)架构&#xff0c;并提供了前所未有的上下文长度支持。本文将详细介绍Llama 4…

淘宝API驱动跨境选品:多语言详情页自动翻译与本地化定价

淘宝 API 驱动跨境选品实现多语言详情页自动翻译与本地化定价&#xff0c;为跨境电商业务带来诸多便利与优势&#xff0c;以下是详细介绍&#xff1a; 一、多语言详情页自动翻译 技术原理 借助淘宝的 API 接口&#xff0c;获取商品详情页的各类文本信息&#xff0c;包括标题、描…

MFC工具栏CToolBar从专家到小白

CToolBar m_wndTool; //创建控件 m_wndTool.CreateEx(this, TBSTYLE_FLAT|TBSTYLE_NOPREFIX, WS_CHILD | WS_VISIBLE | CBRS_FLYBY | CBRS_TOP | CBRS_SIZE_DYNAMIC); //加载工具栏资源 m_wndTool.LoadToolBar(IDR_TOOL_LOAD) //在.rc中定义&#xff1a;IDR_TOOL_LOAD BITMAP …

【Java面试系列】Spring Boot微服务架构下的分布式事务处理与性能优化详解 - 3-5年Java开发必备知识

【Java面试系列】Spring Boot微服务架构下的分布式事务处理与性能优化详解 - 3-5年Java开发必备知识 引言 在当今的微服务架构中&#xff0c;分布式事务处理和性能优化是面试中经常被问及的高频话题。随着系统规模的扩大&#xff0c;如何保证数据一致性和系统性能成为了开发者…

【动态规划】 深入动态规划—两个数组的dp问题

文章目录 前言例题一、最长公共子序列二、不相交的线三、不同的子序列四、通配符匹配五、交错字符串六、两个字符串的最小ASCII删除和七、最长重复子数组 结语 前言 问题本质 它主要围绕着给定的两个数组展开&#xff0c;旨在通过对这两个数组元素间关系的分析&#xff0c;找出…

【C++面向对象】封装(上):探寻构造函数的幽微之境

每文一诗 &#x1f4aa;&#x1f3fc; 我本将心向明月&#xff0c;奈何明月照沟渠 —— 元/高明《琵琶记》 译文&#xff1a;我本是以真诚的心来对待你&#xff0c;就像明月一样纯洁无瑕&#xff1b;然而&#xff0c;你却像沟渠里的污水一样&#xff0c;对这份心意无动于衷&a…

JavaScript性能优化(下)

1. 使用适当的算法和逻辑 JavaScript性能优化是一个复杂而重要的话题&#xff0c;尤其是在构建大型应用时。通过使用适当的算法和逻辑&#xff0c;可以显著提高代码的效率和响应速度。以下是一些关键策略和实践&#xff0c;用于优化JavaScript性能&#xff1a; 1.1. 采用适当…

蚂蚁 Flink 实时计算编译任务 Koupleless 架构改造

张冯君&#xff08;远远&#xff09; Koupleless PMC 蚂蚁集团技术工程师 就职于蚂蚁集团中间件团队&#xff0c;参与维护与建设蚂蚁 SOFAArk 和 Koupleless 开源项目、内部 SOFAServerless 产品的研发和实践。 本文 3488 字&#xff0c;预计阅读 11 分钟 业务背景 基于开源 A…

使用pycharm社区版调试DIFY后端python代码

目录 背景 前置条件 DIFY使用的框架 API服务调试配置步骤&#xff08;基于tag为0.15.3的版本&#xff09; 1.配置.env文件 2.关闭docker里面的docker-api-1服务 3.使用DOCKER启动本地环境需要用到的中间件&#xff0c;并暴露端口 注意事项一&#xff1a; 注意事项二&#xff1a…

从 macos 切换到 windows 上安装的工具类软件

起因 用了很多年的macos, 已经习惯了macos上的操作, 期望能在windows上获得类似的体验, 于是花了一些时间来找windows上相对应的软件. 截图软件 snipaste​​​​​​ windows和macos都有的软件, 截图非常好用 文件同步软件 oneDrive: 尝试了不同的同步软件, 还是微软在各…

MySQL体系架构(一)

1.1.MySQL的分支与变种 MySQL变种有好几个,主要有三个久经考验的主流变种:Percona Server,MariaDB和 Drizzle。它们都有活跃的用户社区和一些商业支持,均由独立的服务供应商支持。同时还有几个优秀的开源关系数据库,值得我们了解一下。 1.1.1.Drizzle Drizzle是真正的M…

【项目实训项目博客】prompt初版实践

通过对camel技术的理解&#xff0c;我们向其中添加了市场营销角色的prompt 初版设计如下&#xff1a; chatchainconfig.json { "chain": [ { "phase": "DemandAnalysis", "phaseType": "SimplePhase", "max_turn_step…

[Bond的杂货铺] CKS 证书也到货咯

最近比较忙&#xff0c;忘记写Blog了&#xff1a;&#xff09; 一年前黑五去官网蹲了一手Cyber Monday&#xff0c;买了英文考试券bundle&#xff0c;当时只考了cka,后来cks差点都忘记了。将近一年后&#xff0c;无意中收到官方的提醒邮件&#xff0c;说考试券本已过期&#x…

【回眸】Linux 内核 (十五) 之 多线程编程 上

前言 进程和线程 区别 线程API 1.创建线程 2.线程退出 3.线程等待 4.线程脱离 5. 线程ID获取及比较 6.创建及销毁互斥锁 7.创建及销毁条件变量 8. 等待 9.触发 多线程编程 后记 前言 高产的几天。 进程和线程 区别 进程——资源分配的最小单位&#xff0c;线…

127.0.0.1本地环回地址(Loopback Address)

127.0.0.1 是计算机网络中的一个特殊IPv4地址&#xff0c;称为本地环回地址&#xff08;Loopback Address&#xff09;&#xff0c;主要用于以下用途&#xff1a; 1. 基本定义 本地主机&#xff08;Localhost&#xff09;&#xff1a;该地址始终指向当前正在使用的计算机本身&a…

S7-1200 PLC热电偶和热电阻模拟量模块

热电偶和热电阻模拟量模块 S7-1200 PLC有专用用于对温度进行采集的热电偶模块SM1231 TC和SM 1231RTD。热电偶模块有4AI和8AI两种&#xff0c;下面以SM1231 TC 4AI为例看一下接线图。 该模块一共有4个通道&#xff0c;每个通道有两个接线端子&#xff0c;比如0&#xff0c;0-。…

深度了解向量引论

今天去研究了一个基本数学原理 这个其实需要证明 今天推导了一下这个公式&#xff0c;感觉收获挺大 下面是手工推导过程

Feign修仙指南:声明式HTTP请求的优雅之道

各位在微服务世界摸爬滚打的道友们&#xff01;今天要解锁的是Spring Cloud的绝世神通——Feign&#xff01;这货堪称HTTP界的"言出法随"&#xff0c;只需定义接口&#xff0c;就能自动生成HTTP请求代码&#xff01;从此告别手动拼装URL的苦日子&#xff0c;让你的代…