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

【STM32】软件SPI读写W25Q64芯片

目录

W25Q64模块

W25Q64芯片简介

硬件电路

W25Q64框图

Flash操作注意事项

状态寄存器

​编辑

指令集 INSTRUCTIONS​编辑

​编辑

SPI读写W25Q64代码

硬件接线图

MySPI.c

MySPI.h

W25Q64

W25Q64.c

W25Q64.h

W25Q64_Ins.h

main.c

测试


SPI通信(W25Q64芯片简介,使用SPI读写W25Q64存储器芯片) 

SPI通信文章:【STM32】SPI通信

http://t.csdnimg.cn/ZKzWt

http://t.csdnimg.cn/BE3Gq


W25Q64模块

W25Q64芯片简介

  • W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储字库存储固件程序存储等场景
  • 存储介质:Nor Flash(闪存)
  • 时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
  • 存储容量(24位地址):     
    W25Q40:      4Mbit / 512KByte     
    W25Q80:      8Mbit / 1MByte     
    W25Q16:      16Mbit / 2MByte     
    W25Q32:      32Mbit / 4MByte     
    W25Q64:      64Mbit / 8MByte   
    W25Q128:  128Mbit / 16MByte     
    W25Q256:  256Mbit / 32MByte

在51单片机中学过一款比较经典的存储芯片AT24C02(I2C通信协议),蓝桥杯嵌入式板子上用的也是AT24C02(I2C通信协议),容量一般是kb级别的

W25QXX引脚接线比较简单,接VCC、GND,其他引脚都接GPIO口即可,先用软件SPI

易失性存储器:    一般是SRAM、DRAM

非易失性存储器:一般是E2PROM、Flash,(掉电不丢失,数据存储)

固件程序存储:直接把程序文件下载到外挂芯片里,执行程序时,读取外挂芯片的程序,这就是XIP,就地执行;比如说电脑的BIOS固件,就可以存储在这种非易失性存储器里

  • 时钟线的最大频率是80MHz,相比于STM32F103C8T6,是非常快的,写程序翻转引脚的时候,就不需要加延时了,即使不延时,引脚的翻转频率也达不到80MHz
  • 双重SPI等效的频率:160MHz (Dual SPI) (MISO和MOSI都可以同时发送和接收)(一个时钟信号,发送或接收2位数据)
  • 四重SPI等效的频率:320MHz (Quad SPI)(MISO+MOSI+WP+HOLD引脚,一共四个引脚)(一个时钟信号,发送或接收4位数据)(四位并行)
  1. 24位地址,最大寻址空间是2的24次方 = 16777216bit
  2. 16777216bit / 1024 = 16384kbit
  3. 16384kb / 1024 = 16Mbit
  4. 所以24位地址寻址的最大寻址空间是16Mb(2MB)

硬件电路

左边的图是W25Q64模块的原理图,右上角是芯片引脚定义,右下角这个表,就是每个引脚定义的功能

  • 注意VCC不能接入5V,应该接入3.3V电压
  • WP写保护,低电平有效,WP低电平不能写入,高电平可以写入
  • HOLD数据保持,低电平有效,给HOLD低电平~将时序保存下来,等操作完其他器件,回来给HOLD高电平,继续HOLD之前的时序~~~

W25Q64框图

块 ~ 扇区 ~ 页

一整个存储空间,划分为若干块,对于每个块,又划分为若干扇区,每个扇区划分为很多页,每页256字节

  • 8MB空间,划分成128个块,每块64KB
  • 每一块(64KB)有16个扇区,每个扇区4KB
  • 一页是256字节,一个扇区4KB=4096B,4096/256=16页,一个扇区16页

Flash操作注意事项

写入操作时:

  • 写入操作前,必须先进行写使能(防止误操作)
  • 每个数据位只能由1改写为0不能由0改写为1
  • 写入数据前必须先擦除,擦除后,所有数据位变为1(弥补了上一条)
  • 擦除必须按最小擦除单元进行(扇区)
  • 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
  • 写入操作结束后,芯片进入忙状态,不响应新的读写操作

读取操作时:

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

状态寄存器

BUSY位

BUSY is a read only bit in the status register (S0) that is set to a 1 state when the device is executing a Page Program, Sector Erase, Block Erase, Chip Erase or Write Status Register instruction. During this time the device will ignore further instructions except for the Read Status Register and Erase Suspend instruction (see tW, tPP, tSE, tBE, and tCE in AC Characteristics). When the program, erase or write status register instruction has completed, the BUSY bit will be cleared to a 0 state indicating the device is ready for further instructions.

翻译一下:
BUSY是状态寄存器(S0)中的一个只读位当设备执行页程序、扇区擦除、块擦除、芯片擦除或写状态寄存器指令时,它被设置为1状态。在此期间,设备将忽略除读状态寄存器和擦除暂停指令之外的其他指令(参见交流特性中的tW, tPP, tSE, be和tCE)。当程序、擦除或写状态寄存器指令完成时,BUSY位将被清除为0状态,表明设备已准备好接受进一步的指令。

Write Enable Latch(WEL)写使能锁存位 

Write Enable Latch (WEL) is a read only bit in the status register (S1) that is set to a 1 after executing a Write Enable Instruction. The WEL status bit is cleared to a 0 when the device is write disabled. A write disable state occurs upon power-up or after any of the following instructions: Write Disable, Page Program, Sector Erase, Block Erase, Chip Erase and Write Status Register.

翻译一下:
写使能锁存(WEL)是状态寄存器(S1)中的一个只读位在执行写使能指令后被设置为1。当设备写失能时,WEL状态位被清除为0。写失能状态发生在上电或以下任何指令之后:写禁用、页程序、扇区擦除、块擦除、芯片擦除和写状态寄存器。

WEL位寄存器总结:先写使能,再执行写入数据操作后,不需要手动写失能,因为写入后,顺便就写失能了,所以在进行任何写入操作前,都需要进行写使能,一次写使能,只能运行一条指令

指令集 INSTRUCTIONS

厂商ID = EF

设备ID,用AB或者90读取,设备ID=16,如果用9F读取,设备ID=4017

常用的指令集
写使能0x06
写失能0x04
读指令0x05
页编程0x02
扇区擦除0x20
读取ID0x9F
读取数据0x03

SPI读写W25Q64代码

硬件接线图

  • Vcc ---3.3V
  • CS 片选信号---PA4
  • DO 从机输出---PA6
  • GND
  • CLK 时钟信号---PA5
  • DI 从机输入---PA7

按照硬件SPI接线,这样可以软件SPI和硬件SPI任意切换

程序框架:

  • 先写一个MySPI的底层库 ---SPI 通信层
  • 基于SPI 建一个W25Q64 硬件驱动层代码
  • 主函数调用硬件驱动层代码

PA6是主机输入,配置成上拉输入,其他三个引脚配置成推挽输出即可

然后封装GPIO输出函数,代码在下面,有加注释

交换一个字节(读写一个字节),W25Q64系列,支持模式0和模式3

模式0执行逻辑;先ss下降沿,移出数据;再sck上升沿,移入数据;再sck下降沿,移出数据

(0x80 >> i) 用于挑出数据的某一位或者某几位,用来屏蔽其他的无关位,这种类型的数据,叫做掩码,掩码模型:不会改变数据本身。

SPI通信层代码

MySPI.c

#include "MySPI.h"/*引脚配置层*///封装 置引脚的高低电平的函数都封装起来,换个名称
/*** 函    数:SPI写SS引脚电平* 参    数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1* 返 回 值:无* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平*/
void  MySPI_W_SS(uint8_t BitValue)	//CS引脚(SS引脚)PA4
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);	//根据BitValue,设置SS引脚的电平
}/*** 函    数:SPI写SCK引脚电平* 参    数:BitValue 协议层传入的当前需要写入SCK的电平,范围0~1* 返 回 值:无* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCK为低电平,当BitValue为1时,需要置SCK为高电平*/
void  MySPI_W_SCK(uint8_t BitValue)	//SCK引脚(PA5)
{GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);	//根据BitValue,设置SCK引脚的电平
}/*** 函    数:SPI写MOSI引脚电平* 参    数:BitValue 协议层传入的当前需要写入MOSI的电平,范围0~0xFF* 返 回 值:无* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置MOSI为低电平,当BitValue非0时,需要置MOSI为高电平*/
void  MySPI_W_MOSI(uint8_t BitValue)	//MOSI引脚(PA7)
{GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);	//根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性
}/*** 函    数:I2C读MISO引脚电平* 参    数:无* 返 回 值:协议层需要得到的当前MISO的电平,范围0~1* 注意事项:此函数需要用户实现内容,当前MISO为低电平时,返回0,当前MISO为高电平时,返回1*/
uint8_t  MySPI_R_MISO(void)	//MISO引脚(PA6 输入引脚)
{return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);	//读取MISO电平并返回
}// SPI速度非常快,操作完引脚,就不需要加延时了/*输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入对主机来说,时钟、主机输出、片选都是输出引脚---推挽输出主机输入MISO---输出引脚---选择上拉输入	从机(W25Q64)的DO输出,是主机输入---PA6
*/
/*** 函    数:SPI初始化* 参    数:无* 返 回 值:无* 注意事项:此函数需要用户实现内容,实现SS、SCK、MOSI和MISO引脚的初始化*/
void MySPI_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟                    /*GPIO初始化*/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);				//将PA4、PA5和PA7引脚初始化为推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		//上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 ;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);				//将PA6引脚初始化为上拉输入/*设置默认电平*/MySPI_W_SS(1); 	// SS置高, 默认不选中从机MySPI_W_SCK(0);	// 计划使用模式0, 默认低电平// MOSI 没有明确规定,MISO是输入引脚,不用输出电平状态
}/*协议层*//*** 函    数:SPI起始* 参    数:无* 返 回 值:无*/
void MySPI_Start(void)
{MySPI_W_SS(0);	//拉低SS,开始时序
}/*** 函    数:SPI终止* 参    数:无* 返 回 值:无*/
void MySPI_Stop(void)
{MySPI_W_SS(1);	//拉高SS,终止时序
}//交换一个字节(读写一个字节),W25Q64系列,支持模式0和模式3
//这里选择模式0
//ByteSend是传进来的参数,通过交换一个字节发送出去,接受
/*** 函    数:SPI交换传输一个字节,使用SPI模式0* 参    数:ByteSend 要发送的一个字节* 返 回 值:接收的一个字节*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{	//模式0uint8_t i, Byte_Receive = 0x00;	//这个变量一定要初始化,不然就出错了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//执行逻辑;先ss下降沿,移出数据,再sck上升沿,移入数据,再sck下降沿,移出数据//ss下降沿之后,主机移出数据最高位放到MOSI上,从机移出最高位放到MISO上// (0x80 >> i) 用于挑出数据的某一位或者某几位,用来屏蔽其他的无关位,这种类型的数据,叫做掩码for (i = 0; i < 8; i++){//第一步:写MOSIMySPI_W_MOSI(ByteSend & (0x80 >> i));	//第二步:SCK上升沿,主机和从机同时移入数据MySPI_W_SCK(1);	// 这个上升沿,从机自动读取MOSI的数据,if (MySPI_R_MISO() == 1) {Byte_Receive |= (0x80 >> i);}	//主机读取从机放到MISO上的数据(最高位)//第三步:SCK产生下降沿MySPI_W_SCK(0);}return Byte_Receive;
}/*江科大注释版本uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t i, ByteReceive = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到for (i = 0; i < 8; i ++)						//循环8次,依次交换每一位数据{MySPI_W_MOSI(ByteSend & (0x80 >> i));		//使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线MySPI_W_SCK(1);								//拉高SCK,上升沿移出数据if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}	//读取MISO数据,并存储到Byte变量//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0MySPI_W_SCK(0);								//拉低SCK,下降沿移入数据}return ByteReceive;								//返回接收到的一个字节数据
}*//*	移位模型:移位寄存器的原理掩码模型:不会改变数据本身现在不用掩码方式,用移位模型 稍微修改代码移位模型:不用定义Byte_Receive变量,直接修改传入参数ByteSend,最后返回这个参数不安全,后续想使用ByteSend参数,就没法用了,但是效率比掩码的方法高
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{	//模式0//执行逻辑;先ss下降沿,移出数据,再sck上升沿,移入数据,再sck下降沿,移出数据//ss下降沿之后,主机移出数据最高位放到MOSI上,从机移出最高位放到MISO上// (0x80 >> i) 用于挑出数据的某一位或者某几位,用来屏蔽其他的无关位,这种类型的数据,叫做掩码for (i = 0; i < 8; i++){//第一步:写MOSI,移出数据MySPI_W_MOSI(ByteSend & 0x80);	//放入最高位ByteSend <<= 1;		//ByteSend左移一位,低位补0,空出来了,下面可以直接接收了//第二步:SCK上升沿,主机和从机同时移入数据MySPI_W_SCK(1);	// 这个上升沿,从机自动读取MOSI的数据,if (MySPI_R_MISO() == 1) {ByteSend |= 0x01;}	//把收到的数据放到ByteSend的最低位//第三步:SCK产生下降沿MySPI_W_SCK(0);//下一次循环,还是取ByteSend,在左移1位,取出MISO数据,放到最低位,依次循环8次,交换接收到的数据,就存在ByteSend里了}return ByteSend;
}
*//*SPI 模式1 2 3 的修改方法*/
/*
//在模式模式0基础上 修改成模式1
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{	uint8_t i, Byte_Receive;//执行逻辑;先ss下降沿,sck上升沿,移出数据,再sck下降沿,移入数据  ---  修改程序的相位即可	for (i = 0; i < 8; i++){//第一步:SCK上升沿,主机和从机同时移入数据MySPI_W_SCK(1);	// 这个上升沿,从机自动读取MOSI的数据//第二步:写MOSIMySPI_W_MOSI(ByteSend & (0x80 >> i));	//移出数据//第三步:SCK产生下降沿MySPI_W_SCK(0);//第四步:移入数据if (MySPI_R_MISO() == 1) {Byte_Receive |= (0x80 >> i);}	}return Byte_Receive;
}*//*
//在模式1的基础上,修改成模式3 --- 模式1和模式3的区别:时钟极性不同
//操作:就是所有出现SCK的地方,都取反,初始化里的时钟极性也要翻转!!!
//初始化里的时钟极性也要翻转!!!初始化里的时钟极性也要翻转!!!
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{	uint8_t i, Byte_Receive;//执行逻辑;先ss下降沿,sck上升沿,移出数据,再sck下降沿,移入数据  ---  修改程序的相位即可	for (i = 0; i < 8; i++){//第一步:SCK下降沿,主机和从机同时移入数据MySPI_W_SCK(0);	// 这个下降沿,从机自动读取MOSI的数据//第二步:写MOSIMySPI_W_MOSI(ByteSend & (0x80 >> i));	//移出数据//第三步:SCK产生上升沿MySPI_W_SCK(1);//第四步:移入数据if (MySPI_R_MISO() == 1) {Byte_Receive |= (0x80 >> i);}	}return Byte_Receive;
}*//*SPI模式2:在模式0的基础上,翻转所有的SCK极性*/
//这就是SPI的四种模式修改方法

MySPI.h

#ifndef __MYSPI_H__
#define __MYSPI_H__#include "stm32f10x.h"                  // Device headervoid MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);#endif

W25Q64

W25Q64只支持模式0和模式3

W25Q64_WaitBusy()函数,事前等待&事后等待
        事后等待只需要再写入操作前调用;
        事前等待在写入操作和读取操作之前都得调用

W25Q64.c

#include "W25Q64.h"void W25Q64_Init(void)
{MySPI_Init();
}/*读取ID,第一个字节:厂商ID。设备ID:第二个字节:存储器类型;第三个字节:容量*/
/*** 函    数:W25Q64读取ID号* 参    数:MID 工厂ID,使用输出参数的形式返回* 参    数:DID 设备ID,使用输出参数的形式返回* 返 回 值:无*/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{MySPI_Start();MySPI_SwapByte(W25Q64_JEDEC_ID);			// 0x9F, 读取ID号码指令,这里的返回值没有意义,就不需要了*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); 	// 这次交换,把数据给主机,接收的数据是厂商ID变量*MID,发送的数据任意给,一般给0xFF//  这里是在通信,通信是有时序的,不同时间调用相同的函数,意义就是不一样的*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);  	// 设备ID的高八位(第三次交换)*DID <<= 8;								   	// 高八位移到左边*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); 	// 设备ID的低八位(用或运算,整合数据)MySPI_Stop();
}/*** 函    数:W25Q64写使能* 参    数:无* 返 回 值:无*/
void W25Q64_WriteEnable(void)
{MySPI_Start();							//SPI起始MySPI_SwapByte(W25Q64_WRITE_ENABLE); 	//交换发送 写使能的指令MySPI_Stop();							//SPI终止
}// 发送指令码05,发完指令码,读取状态寄存器,查看是否是忙状态,最低位BUSY,1是忙,0是不忙
/*** 函    数:W25Q64等待忙* 参    数:无* 返 回 值:无*/
void W25Q64_WaitBusy(void) // 等待busy位为0
{uint32_t Timeout;MySPI_Start();								//SPI起始MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);				//交换发送读状态寄存器1的指令Timeout = 100000;							//给定超时计数时间while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)	//循环等待忙标志位{Timeout --;								//等待时,计数值自减if (Timeout == 0)						//自减到0后,等待超时{/*超时的错误处理代码,可以添加到此处*/break;								//跳出等待,不等了}}MySPI_Stop();								//SPI终止
}
/*注意:W25Q64_WaitBusy,事前等待&事后等待事后等待只需要再写入操作前调用;事前等待在写入操作和读取操作之前都得调用
*//*** 函    数:W25Q64页编程* 参    数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF* 参    数:DataArray	用于写入数据的数组(指针传递数组)* 参    数:Count 要写入数据的数量,范围:0~256* 返 回 值:无* 注意事项:写入的地址范围不能跨页*/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{uint16_t i;W25Q64_WriteEnable();						//写入操作前,必须先写使能MySPI_Start();								//SPI起始MySPI_SwapByte(W25Q64_PAGE_PROGRAM);		//交换发送页编程的指令MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位MySPI_SwapByte(Address);					//交换发送地址7~0位for (i = 0; i < Count; i ++)				//循环Count次{MySPI_SwapByte(DataArray[i]);			//依次在起始地址后写入数据}MySPI_Stop();								//SPI终止W25Q64_WaitBusy();							//等待忙,事后等待比较保险
}/*** 函    数:W25Q64扇区擦除(4KB)* 参    数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF* 返 回 值:无*/
void W25Q64_SectorErase(uint32_t Address)
{W25Q64_WriteEnable();						//写使能MySPI_Start();								//SPI起始MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);	//交换发送扇区擦除的指令MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位MySPI_SwapByte(Address);					//交换发送地址7~0位MySPI_Stop();								//SPI终止W25Q64_WaitBusy();							//等待忙,不忙就退出这个函数了
}/*** 函    数:W25Q64读取数据* 参    数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF* 参    数:DataArray 用于接收读取数据的数组,通过输出参数返回* 参    数:Count 要读取数据的数量,范围:0~0x800000* 返 回 值:无*/
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{uint32_t i;MySPI_Start();								//SPI起始MySPI_SwapByte(W25Q64_READ_DATA);			//交换发送读取数据的指令MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位MySPI_SwapByte(Address);					//交换发送地址7~0位for (i = 0; i < Count; i ++)				//循环Count次{DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//依次在起始地址后读取数据}MySPI_Stop();								//SPI终止
}

W25Q64.h

#ifndef __W25Q64_H__
#define __W25Q64_H__#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"     //指令的头文件void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);#endif

W25Q64_Ins.h

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H#define W25Q64_WRITE_ENABLE							0x06	//写使能
#define W25Q64_WRITE_DISABLE						0x04	//写失能
#define W25Q64_READ_STATUS_REGISTER_1				0x05	//读状态寄存器1
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02	//页编程
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20	//扇区擦除
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F	//读取ID号
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3#define W25Q64_DUMMY_BYTE							0xFF	//无用数据#endif

main.c

#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"uint8_t MID;
uint16_t DID;uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};	//定义要写入数据的测试数组
//uint8_t ArrayWrite[] = {0x11, 0x22, 0x33, 0x44};	//定义要写入数据的测试数组
uint8_t ArrayRead[4];								//定义要读取数据的测试数组int main(void)
{/*模块初始化*/OLED_Init();						//OLED初始化W25Q64_Init();						//W25Q64初始化/*显示静态字符串*/OLED_ShowString(1, 1, "MID:   DID:");OLED_ShowString(2, 1, "W:");OLED_ShowString(3, 1, "R:");/*显示ID号*/W25Q64_ReadID(&MID, &DID);			//获取W25Q64的ID号,指针 返回输出参数OLED_ShowHexNum(1, 5, MID, 2);		//显示MID,显示厂商IDOLED_ShowHexNum(1, 12, DID, 4);		//显示DID,显示设备ID/*W25Q64功能函数测试*/W25Q64_SectorErase(0x000000);					//扇区擦除(写之前先进行扇区擦除操作)W25Q64_PageProgram(0x000000, ArrayWrite, 4);	//将写入数据的测试数组写入到W25Q64中W25Q64_ReadData(0x000000, ArrayRead, 4);		//读取刚写入的测试数据到读取数据的测试数组中/*显示数据*/OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);		//显示写入数据的测试数组OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);OLED_ShowHexNum(3, 3, ArrayRead[0], 2);			//显示读取数据的测试数组OLED_ShowHexNum(3, 6, ArrayRead[1], 2);OLED_ShowHexNum(3, 9, ArrayRead[2], 2);OLED_ShowHexNum(3, 12, ArrayRead[3], 2);while (1){}
}

测试

  1. 修改写入的数组内容,如果写入读出一直,则正常读写
  2. 验证掉电不丢失,将扇区擦除和页编程的两行代码注释掉,下载、断电、重新上电看显示是否有变化,无变化则验证成功
  3. 验证一下FLASH擦除之后变成FF的特性,在上面的基础上,将擦除的那一行取消注释,下载测试,读取的数据都是FF就验证成功了
  4. 不擦除直接改写,注释掉擦除的那一行代码,取消注释页编程,然后将写入的数据修改一下,下载测试,(比如说先写入AA、BB、CC、DD,后面改成55、66、77、88,则读出数据是00、22、44、88),成功验证FLASH只能1写0,不能0写1
  5. 不能跨页写入

原创笔记,码字不易,欢迎点赞,收藏~

相关文章:

【STM32】软件SPI读写W25Q64芯片

目录 W25Q64模块 W25Q64芯片简介 硬件电路 W25Q64框图 Flash操作注意事项 状态寄存器 ​编辑 指令集 INSTRUCTIONS​编辑 ​编辑 SPI读写W25Q64代码 硬件接线图 MySPI.c MySPI.h W25Q64 W25Q64.c W25Q64.h W25Q64_Ins.h main.c 测试 SPI通信&#xff08;W25…...

普通中小学校管理信息系统V1.1

普通中小学校管理信息系统 Ordinary Primary and Secondary Schools Management Information System 普通中小学校管理信息系统 Ordinary Primary and Secondary Schools Management Information System...

中国水果采摘机器人行业市场研究及发展趋势分析报告

全版价格&#xff1a;壹捌零零 报告版本&#xff1a;下单后会更新至最新版本 交货时间&#xff1a;1-2天 第一章 2016-2026年中国水果采摘机器人行业总概 1.1 中国水果采摘机器人行业发展概述 机器人技术的发展是一个国家高科技水平和工业自动化程度的重要标志和体现。机器…...

Linux多进程与信号

在多进程的服务程序中&#xff0c;如果子进程收到退出信号&#xff0c;子进程自行退出。如果父进程收到退出信号&#xff0c;应该先向全部的子进程发送退出信号&#xff0c;然后自己再退出。 演示demo程序 #include <iostream> // 包含输入输出流库&#xff0c;用于输…...

Self-attention与Word2Vec

Self-attention&#xff08;自注意力&#xff09;和 Word2Vec 是两种不同的词嵌入技术&#xff0c;用于将单词映射到低维向量空间。它们之间的区别&#xff1a; Word2Vec&#xff1a; Word2Vec 是一种传统的词嵌入&#xff08;word embedding&#xff09;方法&#xff0c;旨在为…...

【Flutter/Android】运行到安卓手机上一直卡在 Running Gradle task ‘assembleDebug‘... 的终极解决办法

方法步骤简要 查看你的Flutter项目需要什么版本的 Gradle 插件&#xff1a; 下载这个插件&#xff1a; 方法一&#xff1a;浏览器输入&#xff1a;https://services.gradle.org/distributions/gradle-7.6.3-all.zip 方法二&#xff1a;去Gradle官网找对应的版本&#xff1a;h…...

医疗实施-客户需求分析

在我的日常系统实施过程中&#xff0c;总会遇到不同角色的客户提出不同类别的需求。有的需求&#xff0c;客户目的想提高操作便携&#xff0c;但会对系统稳定性存在风险&#xff0c;应该拒掉。有些需求紧急而且影响重大&#xff0c;应该紧急处理。有些需求可以做&#xff0c;但…...

调度服务看门狗配置

查看当前服务器相关的sqlserver服务 在任务栏右键&#xff0c;选择点击启动任务管理器 依次点击&#xff0c;打开服务 找到sqlserver 相关的服务&#xff0c; 确认这些服务是启动状态 将相关服务在看门狗中进行配置 选择调度服务&#xff0c;双击打开 根据上面找的服务进行勾…...

AI时代 编程高手的秘密武器:世界顶级大学推荐的计算机教材

文章目录 01 《深入理解计算机系统》02 《算法导论》03 《计算机程序的构造和解释》04 《数据库系统概念》05 《计算机组成与设计&#xff1a;硬件/软件接口》06 《离散数学及其应用》07 《组合数学》08《斯坦福算法博弈论二十讲》 清华、北大、MIT、CMU、斯坦福的学霸们在新学…...

【数据结构和算法初阶(c语言)】数据结构前言,初识数据结构(给你一个选择学习数据结构和算法的理由)

1.何为数据结构 数据结构(Data Structure)是计算机存储、组织数据的方式&#xff0c;指相互之间存在一种或多种特定关系的 数据元素的集合。本质来讲就是在内存中去管理数据方式比如我们的增删查改。在内存中管理数据的方式有很多种&#xff08;比如数组结构、链式结构、树型结…...

LeetCode 0235.二叉搜索树的最近公共祖先:用搜索树性质(不遍历全部节点)

【LetMeFly】235.二叉搜索树的最近公共祖先&#xff1a;用搜索树性质&#xff08;不遍历全部节点&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/ 给定一个二叉搜索树, 找到该树中两个指定节点的最近公…...

【Prometheus】概念和工作原理介绍

目录 一、概述 1.1 prometheus简介 1.2 prometheus特点 1.3 prometheus架构图 1.4 prometheus组件介绍 1、Prometheus Server 2、Client Library 3、pushgateway 4、Exporters 5、Service Discovery 6、Alertmanager 7、grafana 1.5 Prometheus 数据流向 1.6 Pro…...

四川易点慧电子商务有限公司抖音小店:可靠之选,购物新体验

在当今这个网络购物日益盛行的时代&#xff0c;选择一家可靠的电商平台成为了消费者最为关心的问题之一。四川易点慧电子商务有限公司抖音小店作为新兴的电商力量&#xff0c;凭借其独特的魅力和优势&#xff0c;正逐渐成为众多消费者心中的可靠之选。 易点慧电子商务有限公司在…...

SpringBoot自带的tomcat的最大连接数和最大的并发数

先说结果&#xff1a;springboot自带的tomcat的最大并发数是200&#xff0c; 最大连接数是&#xff1a;max-connectionsaccept-count的值 再说一下和连接数相关的几个配置&#xff1a; 以下都是默认值&#xff1a; server.tomcat.threads.min-spare10 server.tomcat.threa…...

TLS1.2抓包解析

1.TLS1.2记录层消息解析 Transport Layer SecurityTLSv1.2 Record Layer: Handshake Protocol: Client HelloContent Type: Handshake (22)Version: TLS 1.0 (0x0301)Length: 253Content Type&#xff1a;消息类型&#xff0c;1个字节。 i 0Version&#xff1a;协议版本&…...

使用两个队列实现栈

在计算机科学中&#xff0c;栈是一种数据结构&#xff0c;它遵循后进先出&#xff08;LIFO&#xff09;的原则。这意味着最后一个被添加到栈的元素将是第一个被移除的元素。然而&#xff0c;Java的标准库并没有提供栈的实现&#xff0c;但我们可以使用两个队列来模拟一个栈的行…...

通过ffmpeg实现视频背景色替换

最近遇到一个需求&#xff0c;希望可以将素材视频的绿幕背景替换为指定的颜色&#xff0c;然后通过裁剪&#xff0c;拼接等处理制作一个新的视频。所以替换背景色成为了重要的一环&#xff0c;看能否通过ffmpeg来实现。通过一番搜索尝试&#xff0c;发现方案可行。下面我整理一…...

后轮位置反馈控制与算法仿真实现

文章目录 1. 后轮反馈控制2. 算法原理3. 算法和仿真实现 1. 后轮反馈控制 后轮反馈控制&#xff08;Rear wheel feedback&#xff09;算法是利用后轮中心的跟踪偏差来进行转向控制量计算的方法&#xff0c;属于Frenet坐标系的一个应用。通过选择合适的李雅普诺夫函数设计控制率…...

实战 vue3 使用百度编辑器ueditor

前言 在开发项目由于需求vue自带对编辑器不能满足使用&#xff0c;所以改为百度编辑器&#xff0c;但是在网上搜索发现都讲得非常乱&#xff0c;所以写一篇使用流程的文章 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、下载ueditor编辑器 一个“…...

N种方法解决1(CTF)

这里遇到的问题&#xff1a;一开始采用的base64解码平台有问题&#xff1b;默认解密出的格式为GBK格式&#xff1b;直接复制粘贴发现无法还原图片&#xff1b;又尝试了其他编码的&#xff1b;发现只有hex格式可以保证图片正常还原&#xff1b; 图片是以二进制存储的&#xff1…...

Istio实战:Istio Kiali部署与验证

目录 前言一、Istio安装小插曲 注意事项 二、Kiali安装三、Istio测试参考资料 前言 前几天我就开始捣腾Istio。前几天在执行istioctl install --set profiledemo -y 的时候老是在第二步就报错了&#xff0c;开始我用的istio版本是1.6.8。 后面查看k8s与istio的版本对应关系后发…...

ASPxGridView中使用PopupEditForm表单字段联动填充

c#中devexpress的控件ASPxGridView中使用PopupEditForm表单字段联动填充 //选择项目名称&#xff0c;自动填充项目编号 <Columns><dx:GridViewDataTextColumn FieldName"id" ReadOnly"True" VisibleIndex"0" Visible"False"…...

基于Pytorch的猫狗图片分类【深度学习CNN】

猫狗分类来源于Kaggle上的一个入门竞赛——Dogs vs Cats。为了加深对CNN的理解&#xff0c;基于Pytorch复现了LeNet,AlexNet,ResNet等经典CNN模型&#xff0c;源代码放在GitHub上&#xff0c;地址传送点击此处。项目大纲如下&#xff1a; 文章目录 一、问题描述二、数据集处理…...

flutter sliver 多种滚动组合开发指南

flutter sliver 多种滚动组合开发指南 视频 https://youtu.be/4mho1kZ_YQU https://www.bilibili.com/video/BV1WW4y1d7ZC/ 前言 有不少同学工作中遇到需要把几个不同滚动行为组件&#xff08;顶部 appBar、内容固定块、tabBar 切换、tabBarView视图、自适应高度、横向滚动&a…...

kafka生产者2

1.数据可靠 • 0&#xff1a;生产者发送过来的数据&#xff0c;不需要等数据落盘应答。 风险&#xff1a;leader挂了之后&#xff0c;follower还没有收到消息。。。。 • 1&#xff1a;生产者发送过来的数据&#xff0c;Leader收到数据后应答。 风险&#xff1a;leader应答…...

【LNMP】云导航项目部署及环境搭建(复杂)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、项目介绍1.1项目环境架构LNMP1.2项目代码说明 二、项目环境搭建2.1 Nginx安装2.2 php安装2.3 nginx配置和php配置2.3.1 修改nginx文件2.3.2 修改vim /etc/p…...

nginx之状态页 日志分割 自定义图表 证书

5.1 网页的状态页 基于nginx 模块 ngx_http_stub_status_module 实现&#xff0c;在编译安装nginx的时候需要添加编译参数 --with-http_stub_status_module&#xff0c;否则配置完成之后监测会是提示语法错误注意: 状态页显示的是整个服务器的状态,而非虚拟主机的状态 server{…...

数字人的未来:数字人对话系统 Linly-Talker + 克隆语音 GPT-SoVITS

&#x1f680;数字人的未来&#xff1a;数字人对话系统 Linly-Talker 克隆语音 GPT-SoVITS https://github.com/Kedreamix/Linly-Talker 2023.12 更新 &#x1f4c6; 用户可以上传任意图片进行对话 2024.01 更新 &#x1f4c6; 令人兴奋的消息&#xff01;我现在已经将强…...

SpringMVC 学习(五)之域对象

目录 1 域对象介绍 2 向 request 域对象共享数据 2.1 通过 ServletAPI (HttpServletRequest) 向 request 域对象共享数据 2.2 通过 ModelAndView 向 request 域对象共享数据 2.3 通过 Model 向 request 域对象共享数据 2.4 通过 map 向 request 域对象共享数据 2.5 通过…...

✅技术社区项目—JWT身份验证

通用的JWT鉴权方案 JWT鉴权流程 基本流程分三步: ● 用户登录成功之后&#xff0c;后端将生成的jwt返回给前端&#xff0c;然后前端将其保存在本地缓存; ● 之后前端与后端的交互时&#xff0c;都将iwt放在请求头中&#xff0c;比如可以将其放在Http的身份认证的请求头 Author…...

5.2 Ajax 数据爬取实战

目录 1. 实战内容 2、Ajax 分析 3、爬取内容 4、存入MySQL 数据库 4.1 创建相关表 4.2 数据插入表中 5、总代码与结果 1. 实战内容 爬取Scrape | Movie的所有电影详情页的电影名、类别、时长、上映地及时间、简介、评分&#xff0c;并将这些内容存入MySQL数据库中。 2、…...

276.【华为OD机试真题】矩阵匹配(二分法—JavaPythonC++JS实现)

🚀点击这里可直接跳转到本专栏,可查阅顶置最新的华为OD机试宝典~ 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目-矩阵匹配二.解题思路三.题解代码Python题解代码…...

java——多线程基础

目录 线程的概述多线程的创建方式一&#xff1a;继承Thread类方式二&#xff1a;实现Runnable接口方式三&#xff1a;利用Callable接口、FutureTask类来实现。Thread常用的方法 线程安全问题线程安全问题概述线程安全问题案例取钱案例描述模拟代码如下&#xff1a;执行结果 线程…...

Python服务器监测测试策略与工具:确保应用的高可用性!

在构建高可用性的应用程序时&#xff0c;服务器监测测试是至关重要的一环。Python作为一种强大的编程语言&#xff0c;提供了丰富的工具和库来帮助我们进行服务器监测测试。本文将介绍一些关键的策略和工具&#xff0c;帮助你确保应用的高可用性。 1. 监测策略的制定&#xff…...

Spring Security源码学习

Spring Security本质是一个过滤器链 过滤器链本质是责任链设计模型 1. HttpSecurity 【第五篇】深入理解HttpSecurity的设计-腾讯云开发者社区-腾讯云 在以前spring security也是采用xml配置的方式&#xff0c;在<http>标签中配置http请求相关的配置&#xff0c;如用户…...

大数据面试总结三

1、hdfs作为分布式存储系统&#xff0c;底层的实现的方式&#xff08;可能不正确&#xff09; 1、底层是一个分布式存储的&#xff0c;底层会将数据进行切分多个block块&#xff08;128M&#xff09;&#xff0c;并存储在不同的节点上面&#xff0c;这种分布式方式有助于提高数…...

AI赚钱套路总结和教程

最近李一舟和Sora 很火&#xff0c;作为第一批使用Sora赚钱的男人&#xff0c;一个清华学美术的跟人讲AI&#xff0c;信的人太多了&#xff0c;钱太好赚了。3年时间&#xff0c;李一舟仅通过卖课就赚了1.75亿元&#xff0c;其中《每个人的人工智能课》收入2786万元&#xff0c;…...

Linux安装jdk、tomcat、MySQL离线安装与启动

一、JDK和Tomcat的安装 1.JDK安装 直接上传到Linux服务器的&#xff0c;上传jdk、tomcat安装包 解压JDK安装包 //解压jdk tar -zxvf jdk-8u151-linux-x64.tar.gz 置环境变量(JAVA_HOME和PATH) vim /etc/profile 在文件末尾添加以下内容&#xff1a; //java environment expo…...

Python爬虫-使用代理伪装IP

爬虫系列&#xff1a;http://t.csdnimg.cn/WfCSx 前言 我们在做爬虫的过程中经常会遇到这样的情况&#xff0c;最初爬虫正常运行&#xff0c;正常抓取数据&#xff0c;一切看起来都是那么的美好&#xff0c;然而一杯茶的功夫可能就会出现错误&#xff0c;比如 403 Forbidden&…...

Typora结合PicGo + 使用Github搭建个人免费图床

文章目录 一、国内图床比较二、使用Github搭建图床三、PicGo整合Github图床1、下载并安装PicGo2、设置图床3、整合jsDelivr具体配置介绍 4、测试5、附录 四、Typora整合PicGo实现自动上传 每次写博客时&#xff0c;我都会习惯在Typora写好&#xff0c;然后再复制粘贴到对应的网…...

【Redis】redis简介与安装

Redis 简介 Redis 是完全开源的&#xff0c;遵守 BSD 协议&#xff08;Berkeley Software Distribution 意思是"伯克利软件发行版&#xff09;&#xff0c;是一个高性能的 key-value 数据库。具有以下几个比较明显的特点&#xff1a; 性能极高 – Redis能读的速度可以达…...

【xss跨站漏洞】xss漏洞利用工具beef的安装

安装环境 阿里云服务器&#xff0c;centos8.2系统&#xff0c;docker docker安装 前提用root用户 安装docker yum install docker 重启docker systemctl restart docker beef安装 安装beef docker pull janes/beef 绑定到3000端口 docker run --rm -p 3000:3000 janes/beef …...

编程笔记 html5cssjs 086 JavaScript 内置对象

编程笔记 html5&css&js 086 JavaScript 内置对象 一、Object二、Array三、String四、Number五、Math六、Date七、RegExp八、Function九、示例小结 JavaScript 内置对象是 JavaScript 语言本身定义的一系列预定义的对象&#xff0c;这些对象在全局作用域中可以直接使用&…...

AttributeError: ‘DataFrame‘ object has no attribute ‘set_value‘怎么修改问题的解决

在jupyternotebook中运行&#xff1a; def remplacement_df_keywords(df, dico_remplacement, roots False):df_new df.copy(deep True)for index, row in df_new.iterrows():chaine row[plot_keywords]if pd.isnull(chaine): continuenouvelle_liste []for s in chaine.…...

Jmeter内置变量 vars 和props的使用详解

JMeter是一个功能强大的负载测试工具&#xff0c;它提供了许多有用的内置变量来支持测试过程。其中最常用的变量是 vars 和 props。 vars 变量 vars 变量是线程本地变量&#xff0c;它们只能在同一线程组内的所有线程中使用&#xff08;线程组内不同线程之间变量不共享&#…...

c#高级-正则表达式

正则表达式是由普通字符和元字符&#xff08;特殊符号&#xff09;组成的文字形式 应用场景 1.用于验证输入的邮箱是否合法。 2.用于验证输入的电话号码是否合法。 3.用于验证输入的身份证号码是否合法。等等 正则表达式常用的限定符总结&#xff1a; 几种常用的正则简写表达式…...

说说UE5中的几种字符串类

在Unreal Engine 5 (UE5) 的C中&#xff0c;与字符串相关的类主要包括&#xff1a; FString&#xff1a; Unreal Engine中用于处理字符串的主要类&#xff0c;提供了丰富的字符串操作方法和功能。 FText&#xff1a; 用于表示本地化文本的类&#xff0c;可以包含多种语言的文本…...

(done) 如何判断一个矩阵是否可逆?

参考视频&#xff1a;https://www.bilibili.com/video/BV15H4y1y737/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 这个视频里还暗含了一些引理 1.若 AX XB 且 X 和 A,B 同阶可逆&#xff0c;那么 A 和 B 相似。原因&#xff1…...

洗眼镜用的超声波清洗机哪一家更好一点?好用超声波清洗机排名

在我们日常生活中&#xff0c;眼镜、首饰、手表等细小物件的清洁一直是一个让人头疼的问题。传统的清洁方法不仅耗时耗力&#xff0c;还可能因为不当的操作而损伤到这些精细的物品。那么&#xff0c;有没有一种既快捷又安全的清洁方式呢&#xff1f;答案就是使用超声波清洗机。…...

(二十二)Flask之上下文管理第三篇【收尾—讲一讲g】

目录: 每篇前言:g到底是什么?生命周期在请求周期内保持数据需要注意的是:拓展—面向对象的私有字段深入讲解一下那句:每篇前言: 🏆🏆作者介绍:【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者🔥🔥本文已…...