【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.hqwc.cn/news/475152.html

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

相关文章

机器学习---学习与推断,近似推断、话题模型

1. 学习与推断 基于概率图模型定义的分布&#xff0c;能对目标变量的边际分布&#xff08;marginal distribution&#xff09;或某些可观测变量 为条件的条件分布进行推断。对概率图模型&#xff0c;还需确定具体分布的参数&#xff0c;称为参数估计或学习问 题&#xff0c;…

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

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

蓝牙耳机哪个品牌质量最好最耐用?蓝牙耳机排行榜前十名分享

​在通勤途中&#xff0c;许多人喜欢通过听音乐来打发时间。如今&#xff0c;无线蓝牙耳机已经取代了有线耳机&#xff0c;让人们摆脱了线缆的束缚。然而&#xff0c;面对市场上众多的蓝牙耳机&#xff0c;许多人仍然不知道该如何选择。我整理出了几款还不错的蓝牙耳机&#xf…

【HarmonyOS】鸿蒙开发之Image组件——第3.1章

图片的放缩类型 Cover&#xff08;默认值&#xff09;&#xff1a;保持图片宽高比进行放缩显示&#xff0c;使得图片完全显示在显示边界外。 Image("https://seopic.699pic.com/photo/50110/8335.jpg_wh1200.jpg").width(100).margin({right:10}).objectFit(ImageFi…

78MXX——线性稳压器电路,用于各种电视机、收录机、电子仪器、设备的稳压电源上,内置短路保护电路,热保护电路

78MXX系列是用于各种电视机、收录机、电子仪器、设备的稳压电源电路。包括78M05、78M06、 78M08、 78M09、 78M10、 78M12、 78M15。 主要特点&#xff1a; ● 极限输出电流: 0.5A ● 固定输出电压: 5V、6V、8V、9V、10V、 12V、 15V ● 内置短路保护电路 ● 内置热保护电路 ●…

今日Arxiv最热大模型论文:大语言模型真的理解上下文了吗?新研究揭示惊人发现

探索大型语言模型的上下文理解能力 在自然语言处理&#xff08; Natural Language Processing,NLP&#xff09;领域&#xff0c;理解上下文是把握人类语言的关键。近年来&#xff0c;大语言模型&#xff08;LLMs&#xff09;在展示对语言的理解方面取得了令人瞩目的成就。然而…

Instagram 账号被封如何申诉?ins账号解封经验分享

不知道各位在玩转海外社媒平台时有没有遇到过Instagram账号异常的情况&#xff0c;比如会出现账号受限、帖子发不出去、账号被封号等情况?Instagram账号如果被封不用马上弃用&#xff0c;我们可以先尝试一下申诉&#xff0c;看看能不能把账号解封。所以今天将会出一篇Instagra…

涌现出来的模拟能力#OpenAI视频生成大模型构建世界模拟器的可行性

Q&#xff1a;Sora出来后&#xff0c;普通人应该怎么办&#xff1f; "Sora的到来带来了机遇和挑战。普通人关注创意和技术&#xff0c;探索表达想法的新方式。&#x1f31f;&#x1f52c;他们制作高质量视频&#xff0c;平衡工作与生活&#xff0c;并拥抱行业变革。梦想成…

动态代理IP如何选择?

IP地址是由IP协议所提供的一种统一的地址格式&#xff0c;通过为每一个网络和每一台主机分配逻辑地址的方式来屏蔽物理地址的差异。根据IP地址的分配方式&#xff0c;IP可以分为动态IP与静态IP两种。对于大部分用户而言&#xff0c;日常使用的IP地址均为动态IP地址。从代理IP的…

Unity3D DrawCall和openGL、光栅化等有何内在联系详解

前言 在Unity3D中&#xff0c;DrawCall是一个重要的概念&#xff0c;它与OpenGL、光栅化等技术有着密切的内在联系。本文将详细解释DrawCall的概念&#xff0c;并给出相关技术的详细解释和代码实现。 对惹&#xff0c;这里有一个游戏开发交流小组&#xff0c;希望大家可以点击…

Android下SF合成流程重学习之GPU合成

Android下SF合成流程重学习之GPU合成 引言 SurfaceFlinger中的图层选择GPU合成(CLIENT合成方式)时&#xff0c;会把待合成的图层Layers通过renderengine(SkiaGLRenderEngine)绘制到一块GraphicBuffer中&#xff0c;然后把这块GraphicBuffer图形缓存通过调用setClientTarget传递…

Mysql 权限与安全管理

0 引言 MySQL是一个多用户数据库&#xff0c;具有功能强大的访问控制系统&#xff0c;可以为不同用户指定允许的权限。MySQL用户可以分为普通用户和root用户。root用户是超级管理员&#xff0c;拥有所有权限&#xff0c;包括创建用户、删除用户和修改用户的密码等管理权限&…