【STM32】软件SPI读写W25Q64芯片

目录

W25Q64模块

W25Q64芯片简介

硬件电路

W25Q64框图

Flash操作注意事项

状态寄存器

​编辑

指令集 INSTRUCTIONS​编辑

​编辑

SPI读写W25Q64代码

硬件接线图

MySPI.c

MySPI.h

W25Q64

W25Q64.c

W25Q64.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

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. 不能跨页写入

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

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

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

相关文章

说说对BOM的理解(常见的BOM对象了解哪些)

文章目录 一、是什么二、window三、location四、navigator五、screen六、history 一、是什么 BOM (Browser Object Model)&#xff0c;浏览器对象模型&#xff0c;提供了独立于内容与浏览器窗口进行交互的对象 其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退&…

四川盐亭清代古寨重现,文物建筑保护引关注

近日&#xff0c;在四川盐亭的五台山深处&#xff0c;一处历经160余年风霜的古山寨遗迹重现天日。寨门巍峨&#xff0c;文字斑驳&#xff0c;诉说着清代同治年间的历史沧桑。然而&#xff0c;岁月侵蚀下&#xff0c;文物保护刻不容缓。温湿度波动等自然因素&#xff0c;对这些珍…

森林气象火险监测站

TH-SL10在广袤无垠的森林中&#xff0c;每一片树叶、每一缕风都蕴含着大自然的秘密。而在这片生机勃勃的绿色世界里&#xff0c;森林气象火险监测站就像是守护宝藏的“千里眼”和“顺风耳”&#xff0c;时刻警惕着潜在的危险。 一、森林气象火险监测站&#xff1a;实时监测的“…

生成式 AI - Diffusion 模型的数学原理(3)

来自 论文《 Denoising Diffusion Probabilistic Model》&#xff08;DDPM&#xff09; 论文链接&#xff1a; https://arxiv.org/abs/2006.11239 Hung-yi Lee 课件整理 文章目录 一、图像生成模型本质上的共同目标二、最大似然估计三、和VAE的关联四、概率计算 一、图像生成模…

GC9008 12V 全桥驱动芯片,可替代TMI8118,应用于摄像机、消费类产品上

GC9008 是一款 12V 全桥驱动芯片&#xff0c;为提供高性价比的方案。它能提供 0.1A 的持续输出电流。可以工作在 4.5~15V 的电源电压上。 具有 PWM&#xff08;IN1/IN2&#xff09;输入接口,与行业标准器件兼容.是 SOP8封装&#xff0c;GC9008D是DIP封装 芯片特点 ● H 桥电机…

计算机服务器中了_locked勒索病毒怎么办?Encrypted勒索病毒解密数据恢复

随着网络技术的不断发展&#xff0c;数字化办公已经成为企业生产运营的根本&#xff0c;对于企业来说&#xff0c;数据至关重要&#xff0c;但网络威胁无处不在&#xff0c;近期&#xff0c;云天数据恢复中心接到很多企业的求助&#xff0c;企业的计算机服务器遭到了_locked勒索…

Python 基于 AI 动物识别技术的研究与实现,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

迁移SVN和GIT的云端数据

在新服务器搭建GIT仓库 教程很多&#xff0c;大致的流程是&#xff1a; 1. 新建linux用户密码专用于git操作 2. 新建git库的存放文件夹并在此初始化git 3. 配置git库所在目录权限 *只需要有一个库和有一个用户&#xff0c;与在windows上建库是一样的。不需要搭建类似gitla…

stable diffusion webui学习总结(2):技巧汇总

一、脸部修复&#xff1a;解决在低分辨率下&#xff0c;脸部生成异常的问题 勾选ADetailer&#xff0c;会在生成图片后&#xff0c;用更高的分辨率&#xff0c;对于脸部重新生成一遍 二、高清放大&#xff1a;低分辨率照片提升到高分辨率&#xff0c;并丰富内容细节 1、先通过…

人力资源智能化管理项目(day10:首页开发以及上线部署)

学习源码可以看我的个人前端学习笔记 (github.com):qdxzw/humanResourceIntelligentManagementProject 首页-基本结构和数字滚动 安装插件 npm i vue-count-to <template><div class"dashboard"><div class"container"><!-- 左侧内…

MyBatis--08--分页插件PageHelper

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.分页插件PageHelper1.1 mysql中 limit 关键字含义1.2 PageHelper 官网https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md](ht…

中国社科院与英国斯特灵大学创新与领导力博士—应该怎样选专业

现如今其实有很多人感觉只是平台成就自己&#xff0c;离开平台自己并无一技之长或过人之处。但是又不想如此安稳过日&#xff0c;一直终老。所以现在大多数人都会去想在职读个博士。 基本上都是在职博士专业为那些希望边工作边获得博士学位的在在职人员开设的&#xff0c;那么&…

uni-app使用uView打开弹出层后输入框聚焦时placeholder错位问题

这里就不放效果了&#xff0c;大概意思就是在使用uView的popus时&#xff0c;在底部弹出后&#xff0c;如果弹窗中的输入框会造成一瞬间的placeholder文字错位&#xff0c;这个问题的主要是因为uView安全区适配导致 uView相关文档 https://www.uviewui.com/components/safeAr…

Vue3

目录 一、 Vue3简介 1. 性能的提升 2. 源码的升级 3. 拥抱TypeScript 4. 新的特性 二、 创建Vue3工程 1. 基于 vue-cli 创建 2. 基于 vite 创建(推荐) 3. 一个简单的效果 三、Vue3核心语法 1. OptionsAPI 与 CompositionAPI &#xff08;1&#xff09;Options API …

UE蓝图 Set节点和源码

文章目录 Set节点说明相关源码 Set节点说明 UE蓝图中的Set节点是用于对变量进行赋值操作的重要组件。它具有多种功能和作用&#xff0c;具体如下&#xff1a; 变量赋值&#xff1a;Set节点可以用于设置不同类型的变量值&#xff0c;包括整数、浮点数、布尔值、字符串等。在游戏…

OpenAI超级视频模型Sora技术报告解读,虚拟世界涌现了

昨天白天&#xff0c;「现实不存在了」开始全网刷屏。 「我们这么快就步入下一个时代了&#xff1f;Sora简直太炸裂了」。 「这就是电影制作的未来」&#xff01; 谷歌的Gemini Pro 1.5还没出几个小时的风头&#xff0c;天一亮&#xff0c;全世界的聚光灯就集中在了OpenAI的So…

node命令yarn --version指向了java

问题描述 本地安装了java、hadoop和nodejs&#xff0c;并配置了环境变量&#xff0c;但是hadoop的bin目录下存在yarn命令&#xff0c;所以使用nodejs的yarn命令启动项目会出现找不到类&#xff0c;此时键入yarn -version也会显示java的版本。 原因分析 由于配置了hadoop环境…

使用Docker Compose搭建Redis哨兵架构

搭建Redis哨兵(sentinel) 之前我们通过深入理解REDIS哨兵原理了解了Redis哨兵(sentinel)的原理&#xff0c;今天我们手动部署一个哨兵架构。要在Docker中搭建Redis哨兵(sentinel)架构&#xff0c;需要Redis的主从实例以及哨兵实例。之前我们已经使用Docker Compose搭建Redis主…

如何一键抠图换背景?分享两个好用的抠图方法

在数字化时代&#xff0c;图片编辑已成为日常生活和工作中不可或缺的一部分。而智能抠图软件&#xff0c;作为近年来兴起的图片处理技术&#xff0c;正引领着图片编辑的新篇章。它利用先进的机器学习和图像识别技术&#xff0c;能够自动识别和分离图片中的主体&#xff0c;实现…

UnityShader——06UnityShader介绍

UnityShader介绍 UnityShader的基础ShaderLab UnityShader属性块介绍 Properties {//和public变量一样会显示在Unity的inspector面板上//_MainTex为变量名&#xff0c;在属性里的变量一般会加下划线&#xff0c;来区分参数变量和临时变量//Texture为变量命名//2D为类型&…