W25Q64 驱动--基于SPI2接口
前言
(1)本系列是基于STM32的项目笔记,内容涵盖了STM32各种外设的使用,由浅入深。
(2)小编使用的单片机是STM32F105RCT6,项目笔记基于小编的实际项目,但是博客中的内容适用于各种单片机开发的同学学习和使用。
学习目标
本章有四个任务:
- 读取W25Q64,制造商ID 和特定芯片的ID,验证SPI驱动
- W25Q64的读操作
- W25Q64的写操作
- 25Q64验证测试,完成25q64的数据存储
- 提醒:基于SPI协议的25Q64读写操作,大家只要掌握把代码嫖过去,使用成功即可,至于SPI读取数据原理,大家只要了解时序图,知道是按时序图写程序就行。
任务一:读取SPI的ID,验证SPI通讯
获取芯片ID的操作时序图

获取芯片ID函数
//读取芯片ID W25X64的ID:0XEF16
unsigned short mt_flashReadID(void)
{unsigned short Temp = 0; hal_spi2CSDrive(0); //片选拉低 hal_spi2ReadWriteByte(0x90);//发送读取ID命令 hal_spi2ReadWriteByte(0x00); hal_spi2ReadWriteByte(0x00); hal_spi2ReadWriteByte(0x00); Temp|=hal_spi2ReadWriteByte(0xFF)<<8; //读取制造商ID,高八位Temp|=hal_spi2ReadWriteByte(0xFF); //读取设备ID,低八位hal_spi2CSDrive(1); //片选拉高 return Temp;
}
编写flash初始化代码,并调用读芯片ID函数。验证SPI通讯
void mt_flashInit(void)
{static unsigned short produid;hal_spi2Init(); produid = mt_flashReadID();}
mt_flash.h 头文件
#ifndef _MT_FLASH_H
#define _MT_FLASH_H#define FLASH_PAGE_SIZE 4096
//指令表
#define W25X_WriteEnable 0x06
#define W25X_ReadStatusReg 0x05
#define W25X_ReadData 0x03
#define W25X_PageProgram 0x02
#define W25X_SectorErase 0x20
#define W25X_ManufactDeviceID 0x90 void mt_flashInit(void);#endif
修改main.c文件,并通过仿真读取芯片ID
#include "stm32f10x.h"
#include "hal_timer.h"
#include "hal_led.h"
#include "hal_gpio.h"
#include "mt_flash.h"int main(void)
{hal_LedInit();hal_GpioConfig_init(); hal_timerInit();mt_flashInit();while (1){ }
}
仿真结果图,可见芯片ID为EF16,说明SPI通讯接通

到此,验证SPI通讯成功。
任务二:25Q64读操作
读操作只有一个函数:
void mt_flashRead(unsigned char *pBuffer,unsigned int ReadAddr,unsigned int NumByteToRead);
25Q64读操作时序图

//pBuffer-读取数据存储地址,ReadAddr-Flash地址,NumByteToRead-读取字节数
void mt_flashRead(unsigned char *pBuffer,unsigned int ReadAddr,unsigned int NumByteToRead)
{ unsigned char *pBuff;unsigned short i,num; unsigned int RdAddr;RdAddr = ReadAddr;num = NumByteToRead;pBuff = pBuffer;hal_spi2CSDrive(0); //使能器件 hal_spi2ReadWriteByte(0x03); //发送读取命令 -
// 00 12 34 56Hhal_spi2ReadWriteByte((unsigned char )((RdAddr)>>16)); //发送24bit地址 hal_spi2ReadWriteByte((unsigned char )((RdAddr)>>8)); hal_spi2ReadWriteByte((unsigned char )RdAddr); for(i=0;i<num;i++){ pBuff[i]=hal_spi2ReadWriteByte(0XFF); //循环读数 }hal_spi2CSDrive(1); //取消片选
}
任务三:25Q64写操作
写操作有三种方式,即三个函数:页写、块写、任意写,在使用过程中根据需要选择调用即可。
1、25Q64数据的页写操作
页写操作代码
//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void mt_flashWritePage(unsigned char * pBuffer,unsigned int WriteAddr,unsigned short NumByteToWrite)
{unsigned char *pBuff;unsigned short i,num; unsigned int wAddr;pBuff = pBuffer;wAddr = WriteAddr;num = NumByteToWrite;mt_flashWriteEnable(); //SET WEL hal_spi2CSDrive(0); //使能器件 hal_spi2ReadWriteByte(W25X_PageProgram); //发送写页命令 hal_spi2ReadWriteByte((unsigned char )((wAddr)>>16)); //发送24bit地址 hal_spi2ReadWriteByte((unsigned char )((wAddr)>>8)); hal_spi2ReadWriteByte((unsigned char )wAddr); for(i=0;i<num;i++)hal_spi2ReadWriteByte(pBuff[i]);//循环写数 hal_spi2CSDrive(1); mt_flashWaitBusy(); //等待写入结束
}
//SPI_FLASH写使能
//将WEL置位
void mt_flashWriteEnable(void)
{hal_spi2CSDrive(0); //使能器件 hal_spi2ReadWriteByte(W25X_WriteEnable); //发送写使能 0x06hal_spi2CSDrive(1); //取消片选
} //读取SPI_FLASH的状态寄存器
//BIT7 6 5 4 3 2 1 0
//SPR RV TB BP2 BP1 BP0 WEL
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
unsigned char mt_flashReadSR(void)
{ unsigned char byte=0; hal_spi2CSDrive(0); //使能器件 hal_spi2ReadWriteByte(W25X_ReadStatusReg); //发送读取状态寄存器命令 byte=hal_spi2ReadWriteByte(0Xff); //读取一个字节 hal_spi2CSDrive(1); //取消片选 return byte;
}
//等待空闲
void mt_flashWaitBusy(void)
{ while ((mt_flashReadSR()&0x01)==0x01); // 等待BUSY位清空
}
2、25Q64扇区写操作 4096
我们每个扇区有4K的存储空间,从上面可以看出,页操作只能写256个字节。 如何实现块写?
应用场景:
举例,如果起始地址:200(十进制)写长度为500个字节的数据
代码
//FLASH 块写操作
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void mt_flashWrite_Secor(unsigned char * pBuffer,unsigned int WriteAddr,unsigned short NumByteToWrite)
{ unsigned char *pBuff; 数据地址指针unsigned short num; unsigned int wAddr; ///写的起始地址unsigned short pageremain; pBuff = pBuffer;num = NumByteToWrite;wAddr = WriteAddr;pageremain=256-wAddr%256; //单页剩余的字节数 if(num<=pageremain)pageremain=num;//不大于256个字节while(1){ mt_flashWritePage(pBuff,wAddr,pageremain);if(num==pageremain)break;//写入结束了else //NumByteToWrite>pageremain{pBuff+=pageremain;wAddr+=pageremain; //200 56 100num-=pageremain; //减去已经写入了的字节数if(num>256)pageremain=256; //一次可以写入256个字节else pageremain=num; //不够256个字节了} }
}
3、25Q64任意写操作(常用)
应用场景
现在要对起始地址是 4000(十进制)写长度为6000个字节的数据。
函数功能说明
增加自动翻页写操作
//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256)
void mt_flashWrite(unsigned char * pBuffer,unsigned int WriteAddr,unsigned short NumByteToWrite) { unsigned char SPI_FLASH_BUF[4096];
// unsigned char testaa[200];unsigned char *pBuff;unsigned int secpos; ///需要写的起始的扇区unsigned short secoff; ///写入到额起始扇区的 偏移地址unsigned short secremain; ///第一个写入扇区需要写入的数据的个数 unsigned short i,num; unsigned int wAddr;pBuff = pBuffer;wAddr = WriteAddr;num = NumByteToWrite; secpos=wAddr/4096;//扇区地址 secoff=wAddr%4096;//在扇区内的偏移secremain=4096-secoff;//扇区剩余空间大小 if(num<=secremain) ///num 是需要写入数据的格式 如果需要写入的数据的个数小于本扇区剩余的个数secremain=num;//不大于4096个字节 在同一个区里面写while(1) { mt_flashRead(SPI_FLASH_BUF,secpos*4096,4096);//读出整个扇区的内容mt_flashEraseSector(secpos);//擦除这个扇区for(i=0;i<secremain;i++) //复制{SPI_FLASH_BUF[i+secoff]=pBuff[i];}mt_flashWrite_Secor(SPI_FLASH_BUF,secpos*4096,4096);//写入整个扇区 写已经擦除了的,直接写入扇区剩余区间. if(num==secremain) ///需要写入的数据长度和 数据长度一致的话,break;//写入结束了else//写入未结束{secpos++;//扇区地址增1secoff=0;//偏移位置为0 pBuff+=secremain; //指针偏移wAddr+=secremain;//写地址偏移 num-=secremain; //字节数递减if(num>4096)secremain=4096; //下一个扇区还是写不完else secremain=num; //下一个扇区可以写完了} } } //擦除一个扇区
//Dst_Addr:扇区地址 0~511 for w25x16
//擦除一个扇区的最少时间:45ms,最大300ms
void mt_flashEraseSector(unsigned int Dst_Addr)
{ unsigned int DstAddr;DstAddr = Dst_Addr;DstAddr*=4096;mt_flashWriteEnable(); //SET WEL mt_flashWaitBusy(); hal_spi2CSDrive(0); //使能器件 hal_spi2ReadWriteByte(W25X_SectorErase); //发送扇区擦除指令 hal_spi2ReadWriteByte((unsigned char )((DstAddr)>>16)); //发送24bit地址 hal_spi2ReadWriteByte((unsigned char )((DstAddr)>>8)); hal_spi2ReadWriteByte((unsigned char )DstAddr); hal_spi2CSDrive(1); //取消片选 mt_flashWaitBusy(); //等待擦除完成
}
任务四:测试25Q64读取
目标
起始地址是:4000 ,写6000个数据,再读取6000;
测试函数
unsigned char falshtest[6000];
void mt_flash_test(void)
{unsigned int i;unsigned int falshdadrx;falshdadrx = 4000;for(i=0;i< 6000;i++){falshtest[i] = i;} mt_flashWrite(&falshtest[0],falshdadrx,6000);for(i=0;i< 6000;i++){falshtest[i] = 0;} mt_flashRead(&falshtest[0],falshdadrx,6000);
}
修改初始化程序:
unsigned char falshtest[6000];
void mt_flash_test(void)
{unsigned int i;unsigned int falshdadrx;falshdadrx = 4000;for(i=0;i< 6000;i++){falshtest[i] = i;} mt_flashWrite(&falshtest[0],falshdadrx,6000);for(i=0;i< 6000;i++){falshtest[i] = 0;} mt_flashRead(&falshtest[0],falshdadrx,6000);
}
测试验证
通过仿真的形式,验证Flash 25Q64的读写操作。
实现方法
先定义一个长度为6000的数组,对数组初始化;用任意写函数,将此数组里的内容写入指定地址,再对数组清零,最后读取指定地址中的内容,若此内容同之前初始化的数组中内容一致,则测试成功。
相关文章:
W25Q64 驱动--基于SPI2接口
前言 (1)本系列是基于STM32的项目笔记,内容涵盖了STM32各种外设的使用,由浅入深。 (2)小编使用的单片机是STM32F105RCT6,项目笔记基于小编的实际项目,但是博客中的内容适用于各种单片…...
禁用无线键盘指定按键
文章目录 前言主体 前言 睡一觉把键盘压坏了一个按键,一开机键盘就自动打出这个字母,我在想用其他按键平替这个字母即可,使用软件修改内部的映射,那么使用autoHotkey软件是十分容易做到的 主体 letter_replace.ahk 创建一个如此命名的文件,然后输入命令即可 a::b 代表平替 a…...
分数规划(二分)
链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 题目描述 小咪是一个土豪手办狂魔,这次他去了一家店,发现了好多好多(n个)手办,但他是一个很怪的人,每次只想买k个手办&a…...
Vue2向Vue3过度Vue3状态管理工具Pinia
目录 1. 什么是Pinia2. 手动添加Pinia到Vue项目3. Pinia基础使用4. getters实现5. action异步实现6. storeToRefs工具函数7. Pinia的调试8. Pinia持久化插件 1. 什么是Pinia Pinia 是 Vue 的专属的最新状态管理库 ,是 Vuex 状态管理工具的替代品 2. 手动添加Pinia到…...
STM32--SPI通信与W25Q64(1)
文章目录 前言SPI通信硬件电路移位过程 SPI时序起始与终止条件交换一个字节 W25Q64硬件电路框图 FLASH操作注意事项软件SPI读写W25Q64 前言 USART串口链接入口 I2C通信链接入口 SPI通信 SPI(Serial Peripheral Interface)是一种高速的、全双工、同步的串…...
版本控制工具Git常见用法
Git 是一个非常强大和灵活的版本控制工具,提供了许多命令和功能来管理代码的版本、分支、合并等。以下是一些 Git 的详细用法: 配置相关命令: 设置用户名和邮箱: git config --global user.name "Your Name" git conf…...
Multisim软件安装包分享(附安装教程)
目录 一、软件简介 二、软件下载 一、软件简介 Multisim软件是一款电路仿真和设计软件,由美国国家仪器公司(National Instruments)开发。它提供了一个交互式的图形界面,使用户能够轻松地构建和仿真电路。以下是Multisim软件的详…...
【android12-linux-5.1】【ST芯片】HAL移植后开机卡死
按照ST的官方readme移植HAL后开机一直卡在android界面,看logcat提示写文件时errorcode:-13。查下资料大致明白13错误码是权限不足,浏览代码在写文件的接口加日志后,发现是需要写iio:device*/buffer/enable这类文件的时候报错的。千…...
线程池也就那么一回事嘛!
线程池详讲 一、线程池的概述二、线程池三、自定义线程池四、线程池工作流程图五、线程池应用场景 一、线程池的概述 线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过…...
设计模式(11)观察者模式
一、概述: 1、定义:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。 2、结构图: public interface S…...
开源的安全性:挑战与机会
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...
wireshark 流量抓包例题重现
[TOC](这里写目录标题 wireshark抓包方法wireshark组成 wireshark例题 wireshark抓包方法 wireshark组成 wireshark的抓包组成为:分组列表、分组详情以及分组字节流。 上面这一栏想要显示,使用:CtrlF 我们先看一下最上侧的搜索栏可以使用的…...
Smartbi电子表格软件版本更新,首次推出Excel轻应用和语音播放
Smartbi电子表格软件又又又更新啦! 此次更新,首次推出了新特性——Excel轻应用和语音播报。另外,还对产品功能、Demo示例、配套文档进行了完善和迭代。 低代码开发Excel轻应用 可实现迅速发布web应用 业务用户的需求往往都处于“解决问题”…...
ElasticSearch简介、安装、使用
一、什么是ElasticSearch? Elasticsearch 是 Elastic Stack 核心的分布式搜索和分析引擎。 Logstash 和 Beats 有助于收集、聚合和丰富您的数据并将其存储在 Elasticsearch 中。 Kibana 使您能够以交互方式探索、可视化和分享对数据的见解,并管理和监…...
Navicat 连接 mysql 问题
需要将mysql配置文件设置为远程任意ip可登陆,注释掉一下两行配置 # bind-address>->--- 127.0.0.1 # mysqlx-bind-address>-- 127.0.0.1Cant connect to MySQL server on "192.168.137.139 (10013 "Unknown error") 检查Navicat是否联网H…...
Adobe Media Encoder软件安装包分享(附安装教程)
目录 一、软件简介 二、软件下载 一、软件简介 Adobe Media Encoder是一款由Adobe公司开发的视频和音频后期制作软件,它集成了多种编码格式和输出选项,可以帮助用户将视频和音频文件转换成适合各种用途的格式。该软件可以与Adobe Premiere Pro、After …...
[C#][原创]操作注册表一些注意点
C#注册表只需要引入 using Microsoft.Win32; C#注册表操作都是通过2个类Registry和RegistryKey进行所有操作。但是有些基本注意事项经常忘记,不常用就很容易忘记。 第一,打开注册表,第2个bool参数问题: RegistryKey key Regi…...
“华为杯”研究生数学建模竞赛2016年-【华为杯】C题:基于无线通信基站的室内三维定位问题
目录 摘 要: 一、问题的重述 1.1 问题背景 1.2 具体要求 1.3 数据分析...
双目视觉之-棋盘格标定板制作
棋盘格设计地址: https://markhedleyjones.com/projects/calibration-checkerboard-collection 包括A0,A1,A2,A3和A4多种规格的棋盘格标定板,支持自定义设置棋盘格grid宽度和高度。 基于Matlab的双目视觉标定流程和O…...
自然对数底e的一些事
自然对数底e的一些事 走的人多了就成了路 中国清代数学家李善兰(1811—1882) 凡此变数中函彼变数者,则此为彼之函数 自然对数底也是使用习惯 🍉 李善兰把function翻译为函数,函就是包含,含有变量ÿ…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
2025-05-08-deepseek本地化部署
title: 2025-05-08-deepseek 本地化部署 tags: 深度学习 程序开发 2025-05-08-deepseek 本地化部署 参考博客 本地部署 DeepSeek:小白也能轻松搞定! 如何给本地部署的 DeepSeek 投喂数据,让他更懂你 [实验目的]:理解系统架构与原…...
CVE-2023-25194源码分析与漏洞复现(Kafka JNDI注入)
漏洞概述 漏洞名称:Apache Kafka Connect JNDI注入导致的远程代码执行漏洞 CVE编号:CVE-2023-25194 CVSS评分:8.8 影响版本:Apache Kafka 2.3.0 - 3.3.2 修复版本:≥ 3.4.0 漏洞类型:反序列化导致的远程代…...
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is not explicitly defined.
这个警告表明您在使用Vue的esm-bundler构建版本时,未明确定义编译时特性标志。以下是详细解释和解决方案: 问题原因: 该标志是Vue 3.4引入的编译时特性标志,用于控制生产环境下SSR水合不匹配错误的详细报告1使用esm-bundler…...
OCC笔记:TDF_Label中有多个相同类型属性
注:OCCT版本:7.9.1 TDF_Label中有多个相同类型的属性的方案 OCAF imposes the restriction that only one attribute type may be allocated to one label. It is necessary to take into account the design of the application data tree. For exampl…...
基于机器学习的智能故障预测系统:构建与优化
前言 在现代工业生产中,设备故障不仅会导致生产中断,还会带来巨大的经济损失。传统的故障检测方法依赖于人工巡检和定期维护,这种方式效率低下且难以提前预测潜在故障。随着工业物联网(IIoT)和机器学习技术的发展&…...
