当前位置: 首页 > news >正文

STM32F103之SPI软件读写W25Q64

一、W25Q64简介

1.1 简介       

W25Q64(Nor flash)、   24位地址,64Mbit/8MByte、是一种低成本、小型化、使用简单的非易失性存储器,常用于数据存储、字库存储、固件程序存储等场景 
        时钟频率:最大80MHz(STM32F103系统时钟为72MHz)、160MHz(MOSI,MISO同时发送,同时接受)、320MHz(将引脚DO、DI、WP、HOLD同时接收或发送)

W25Q64封装
引脚描述

VCC:  2.7-3.6V
WP:    写保护,低电平有效,当为低电平时,不可写
HOLD:数据保持、低电平有效;如果在正常读写flash时,突然产生中断,想用SPI去操控其他器件,要是把CS置1,则flash时序就会终止;若将HOLD置0,则时序可以保持原有状态

1.2 框图-地址划分

24位地址,可寻址范围:0x000000-0xFFFFFF;由于W25Q64的容量为8MB,所以其地址范围为0x000000-0x7FFFFF

flash 框图
flash 空间划分

将8MB的空间划分为块(block),每一个块的容量为64KB,共划分128块
将每一个64KB块划分为扇区,一个块可划分为16个扇区,一共有2048个扇区
将每一4KB的扇区划分为页,一个扇区可划分16个页;也可以看作一个flash可划分为32768个页
一页是256字节 ,一块是256页

如果想要往第3个块第210页的第118个字节写入,物理地址是多少呢?
第三个块对应高八位字节:0x020000
第210个页对应中间八位(页地址锁存器):0x00D100
第118个字节对应第八位((字节地址锁存器):0x000075
所以物理地址为:0x02D175(24位地址)


例如写入地址为0x123456,对应的是多少呢
0x12->18,也就是块17(或第18个块)
0x34->52,也就是块17页51(或第52个页)
0x56->86,也就是块17页51的85字节(或第86个字节)

也就是说18*16+52-1=4659,对应4659页的85字节开始写入

当往flash中写入数据时,写入的数据先缓存到256字节页缓冲区,再从缓冲区转到flash里,这需要一定的时间,在这期间,芯片的flash的busy标志位将被置1;
注意:由于缓冲区只有256个字节,所以写入的一个时序连续写入的数据量不能超过256个字节

1.3 读写flash注意事项

写入时
<1>写入操作前必须进行写使能
<2>每个数据位只能由1改写为0,不能由0改写为1
      例如 原来数据为0x78(01111000),想要在本地址直接写入0x11(00010001),就会变为              00010000
<3>写入前必须想先要擦除,擦除后,所有数据位变为1(0xFF表示空白)
<5>擦除必须按最小单元进行(最小单元为扇区(4KB=4096字节))
       想要单独改写某一个字节,可以先将扇区的全部数据读出后,该写完读出来的数据后再          写入或者一个字节占一个扇区
<6>连续写入多字节时,最多写入一页的数据;超过尾页的数据会回到页尾覆盖数据(不能跨页)
<7>写入操作结束后,芯片进入忙状态,不响应新的读写操作(擦除时也会进入忙状态)(可读状态寄存器的busy位)

读取
<1>直接调用读取时序、无需使能、无需额外操作,没有页的限制读取操作结束后不会进入忙状态,但不能在忙状态时读取

 注意:当给定一个地址时,数据会按地址自增写入,且不可跨页写入,如果大于页的容量会从本页头开始覆盖;写使能只对之后跟随的一条指令有效

 二、软件模拟SPI读取flash

SPI是一种协议,软件可以进行模拟,如果板子上没有SPI端口或者SPI的端口被占用时,软件可以进行模拟,通过软件控制四个引脚的高低电平,从而达到硬件的效果,本节进行软件模拟SPI(模式0),与硬件不同的是,对于软件的四个引脚,输入引脚(CS,CLK,MOSI)配置为上拉或浮空输入,输出引脚(MISO)配置为推挽输出
对于SPI不了解的可以看一下https://mpbeta.csdn.net/mp_blog/creation/editor/148934665

引脚配置

/****************************
*@brief    SPI引脚初始化
*@param  void
*@return   void
*@note     PB13->CLK PB14->MISO->DOPB12->CS  PB15->MOSI->DI/
对于软件来说输出引脚配置为推挽输出
输入引脚配置为上拉输入或浮空输入
*@time  2025-6-27
******************************/
void Soft_SPI_Init(void)
{/*1.打开IO引脚时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);/*2.引脚模式配置*/GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP                   ;GPIO_InitStruct.GPIO_Pin=GPIO_Pin_13|GPIO_Pin_15|GPIO_Pin_12 ;//输出配置为推挽输出GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz                  ;GPIO_Init(GPIOB,&GPIO_InitStruct)                            ;GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU                      ;//输入配置为浮空输入GPIO_InitStruct.GPIO_Pin=GPIO_Pin_14                         ;GPIO_Init(GPIOB,&GPIO_InitStruct)                            ;/*3.CS片选默认高电平、CLK默认低电平*/SOFT_SPI_CS(1)                                               ;//宏定义SOFT_SPI_CLK(0)                                              ;//宏定义
}
SPI模式0 时序

对于模式0,时钟默认低电平且在时钟的第一个边沿进行采样 ,有时序图可知,MCU在时钟下降沿通过MOSI输出高位,在时钟上升沿时,采样 从机通过MOSI(对应主机MISO的)输出的电平,在写数据收发时序时只需要盯着主机,从机我们无法干涉

数据交换

/****************************
*@brief  交换数据
*@param     void
*@return    void
*@note     高位先发,SPI模式0
*@time  2025-6-27
******************************/
int8_t Soft_SPI_Dataswap(int8_t Send_Data)
{int8_t Receive_Data=0x00;for(int8_t i=0;i<8;i++){SOFT_SPI_MOSI(Send_Data>>(7-i)&0x01)  ;//高位数据通过MOSI送出SOFT_SPI_CLK(1)                       ;//时钟上升沿//if(Soft_SPI_MISO()==1){Receive_Data |=(0x80>>i);}Receive_Data|=SOFT_SPI_MISO()<<(7-i)  ;//主机从从机的MOSI口接收的高位数据SOFT_SPI_CLK(0)                       ;//时钟下降沿}return Receive_Data;
}

当时钟为低电平时,主机通过 SOFT_SPI_MOSI(Send_Data>>(7-i)&0x01)  将1位数据通过MOSI口送出;当时钟位上升沿时,主机进行采样,通过 Receive_Data|=SOFT_SPI_MISO()<<(7-i)  将从机的MOSI上的数据采入
注意:由于C语言时逐语句进行的,所以不可能发送的同时进行接收,可以利用RTOS或者FPGA

flash函数封装

/****************************
*@brief  读取ID
*@param  uint8_t *MID 厂商IDuint16_t *DID 设备ID
*@return void
*@note   void
*@time  2025-6-28
******************************/
void Soft_SPI_ReadID_W25Q64(uint8_t *MID,uint16_t *DID)
{SOFT_SPI_START;Soft_SPI_Dataswap(SOFT_SPI_R_ID)  ;*MID = Soft_SPI_Dataswap(0XFF)    ;*DID = Soft_SPI_Dataswap(0XFF)    ;*DID <<= 8;*DID |=  Soft_SPI_Dataswap(0XFF);SOFT_SPI_STOP;
}
/****************************
*@brief    写使能
*@param  void
*@return   void
*@note     void
*@time  2025-6-28
******************************/
void Soft_SPI_W_ENABLE_W25Q64(void)
{SOFT_SPI_START;Soft_SPI_Dataswap(SOFT_SPI_W_ENABLE)  ;//指令SOFT_SPI_STOP;
}	
/****************************
*@brief   等待寄存器忙状态
*@param    void
*@return   void
*@note    busy位维1,表示忙
*@time  2025-6-28
******************************/
void Soft_SPI_R_Status_W25Q64_busy(void)
{SOFT_SPI_START;Soft_SPI_Dataswap(SOFT_SPI_GetStatus);//读取指令while(Soft_SPI_Dataswap(0xFF)&0x01)  ;//busy=1时等待SOFT_SPI_STOP;
}	
/****************************
*@brief    页编程
*@param  uint8_t ArrayData[]数据uint32_t Address写入的数据地址uint16_t len     字节个数
*@return   void
*@note     void
*@time  2025-6-28
******************************/
void Soft_SPI_W_Data_W25Q64(uint8_t *ArrayData,uint32_t Address,uint16_t cnt)
{Soft_SPI_W_ENABLE_W25Q64();//写使能SOFT_SPI_START;Soft_SPI_Dataswap(SOFT_SPI_W_DATA);//写指令/*函数Soft_SPI_Dataswap()一次只能交换8位*/Soft_SPI_Dataswap(Address>>16);   //高地址,决定哪块Soft_SPI_Dataswap(Address>>8);    //中间地址,决定哪个扇区Soft_SPI_Dataswap(Address>>0);    //低地址,决定哪个页/*写数据*/for(uint16_t i=0;i<cnt;i++){Soft_SPI_Dataswap(ArrayData[i]);}SOFT_SPI_STOP;Soft_SPI_R_Status_W25Q64_busy();//busy等待
}
/****************************
*@brief   扇区擦除
*@param  uint32_t Address擦除地址
*@return   void
*@note     void
*@time  2025-6-28
******************************/
void Soft_SPI_Sector_Clean_W25Q64(uint32_t Address)
{Soft_SPI_W_ENABLE_W25Q64();//写使能SOFT_SPI_START;Soft_SPI_Dataswap(SOFT_SPI_SECTOR_CLEAN );//指令/*函数Soft_SPI_Dataswap()一次只能交换8位*/Soft_SPI_Dataswap(Address>>16);   //高地址,决定哪块Soft_SPI_Dataswap(Address>>8);    //中间地址,决定哪个扇区Soft_SPI_Dataswap(Address>>0);    //低地址,决定哪个页SOFT_SPI_STOP;Soft_SPI_R_Status_W25Q64_busy();//busy等待
}
/****************************
*@brief    读数据
*@param  uint8_t* ArrayData 输出数据uint32_t Address   读出的数据起始地址uint16_t len       字节个数
*@return   void
*@note     void
*@time  2025-6-28
******************************/
void Soft_SPI_R_Data_W25Q64(uint8_t *ArrayData,uint32_t Address,uint32_t cnt)
{SOFT_SPI_START;Soft_SPI_Dataswap(SOFT_SPI_R_DATA);//写指令/*函数Soft_SPI_Dataswap()一次只能交换8位*/Soft_SPI_Dataswap(Address>>16);   //高地址,决定哪块Soft_SPI_Dataswap(Address>>8);    //中间地址,决定哪个扇区Soft_SPI_Dataswap(Address>>0);    //低地址,决定哪个页/*读数据*/for(uint32_t i=0;i<cnt;i++){ArrayData[i]= Soft_SPI_Dataswap(0xFF);}SOFT_SPI_STOP;
}

上述代码对读取ID,写使能,读数据,写数据,擦除进行了封装,相同的流程是
①片选语句拉低(SOFT_SPI_START)
②flash操作指令(Soft_SPI_Dataswap(指令))
③读/写数据
④片选语句拉低(SOFT_SPI_STOP)
注意:
可以跨页读但不能跨页写;
写之前须写使能;
一次最多写入256字节数据;
再次写入相同地址需要先擦除,再写入;
擦除、写入操作会使busy置1,busy置1不再响应读,写,擦除操作

三、整个代码

运行代码:

http://www.lryc.cn/news/576402.html

相关文章:

  • 力扣第73题-矩阵置零
  • 用鸿蒙打造真正的跨设备数据库:从零实现分布式存储
  • 【区块链】区块链交易(Transaction)之nonce
  • 默克树技术原理
  • Node.js特训专栏-实战进阶:10.MongoDB文档操作与聚合框架
  • 嵌入式硬件与应用篇---寄存器GPIO控制
  • 软件反调试(1)- 基于进程列表的检测
  • Spring AI Alibaba 入门指南:打造企业级 AI 应用
  • 《从 0 到 1 掌握正则表达式:解析串口数据的万能钥匙》
  • Note2.3 机器学习:Adaptive Learning Rate
  • golang中struct中大小写对gin框架的影响
  • 深入剖析AI大模型:Dify的介绍
  • SpringMVC系列(七)(Restful架构风格(下))(完结篇)
  • SpringMVC系列(五)(响应实验以及Restful架构风格(上))
  • 微软人工智能证书AI-102 | 如何快速通过?
  • JavaScript---数组篇
  • 循环向python异步中增加task的几个方法
  • 【unity游戏开发——网络】网络协议、TCP vs UDP 本质区别
  • 卸载Modelsim/Qustasim方法
  • AngularJS Git 提交消息规范
  • Centos 8设置固定IP
  • Linux通过Crontab实现自启动
  • Grab×亚矩阵云手机:以“云端超级节点”重塑东南亚出行与数字生活生态
  • 第十节:Vben Admin 最新 v5.0 (vben5) 快速入门 - 菜单管理(下)
  • docker部署后端服务的脚本
  • 深入详解:决策树算法的概念、原理、实现与应用场景
  • WHERE 子句中使用子查询:深度解析与最佳实践
  • 设计模式精讲 Day 16:迭代器模式(Iterator Pattern)
  • 如何解决电脑windows蓝屏问题
  • VScode使用usb转网口远程开发rk3588