江科大STM32笔记——SPI 及 W25Q64 芯片相关知识

一、SPI 总线基础

(一)定义

SPI(Serial Peripheral Interface)由 Motorola 公司开发,是一种通用数据总线。

(二)通信线组成

  1. SCK(Serial Clock):串行时钟线,用于同步数据传输。
  2. MOSI(Master Output Slave Input):主设备输出从设备输入线,主设备经此向从设备发送数据。
  3. MISO(Master Input Slave Output):主设备输入从设备输出线,从设备经此向主设备发送数据。
  4. SS(Slave Select):从设备选择线,用于选定特定从设备进行通信。

(三)通信特性

  1. 同步:数据传输在时钟信号(SCK)同步下进行。
  2. 全双工:主设备和从设备可同时双向传输数据。

(四)设备连接特点

支持一主多从模式,即一个主设备可连接多个从设备,通过 SS 线选择与之通信的从设备。

二、SPI 设备连接与引脚配置

(一)设备连接方式

  1. 公共线连接:所有 SPI 设备的 SCK、MOSI、MISO 分别相连。其中 SCK 负责同步,MOSI 用于主机向从机发送数据,MISO 用于从机向主机发送数据。
  2. 从机选择连接:主机引出多条 SS 控制线,分别连接各从机的 SS 引脚,通过控制 SS 线电平高低(一般低电平选中)来选择特定从机通信。
  3. 注意事项

在有多个从机的情况下,会出现主机一个 MISO、从机多个 MISO 的情况。为保证从机接收到的数据的一致性,当 SS 为高位(该从机未被使用)时,从机的 MISO 需设置为高阻态(相当于断掉),以此确保正在调用的从机数据能够准确传入主机之中。这样可避免未被选中的从机干扰正在通信的从机数据传输,保障 SPI 通信系统在多从机环境下的稳定性和准确性。

(二)引脚配置

  1. 输出引脚:配置为推挽输出,可提供较强驱动能力,保证数据传输速度与稳定性。
  2. 输入引脚:配置为浮空或上拉输入,可增强抗干扰能力和信号检测准确性。

三、W25Q64 FLASH 存储芯片要点

(一)引脚对应关系

  1. CLK 引脚对应 SPI 总线的 SCK(串行时钟引脚)。
  2. DI 与 DO 引脚对应 SPI 总线的 MOSI 和 MISO,但对应关系取决于芯片是主设备还是从设备。

(二)主从设备下引脚功能

当 W25Q64 芯片作为从设备时:

  1. DI(数据输入)引脚:从设备的数据输入需连接主机的设备输出,即 DI 应连接到主机的 MOSI。
  2. DO(数据输出)引脚:从设备的数据输出应连接主机的 MISO,即 DO 连接到 MISO。

四、应用要点

  1. 应用 SPI 总线时,需牢记其同步、全双工、一主多从特性。
  2. 连接 W25Q64 芯片与主机时,要准确理解引脚功能、连接方式及配置方法,以保障通信正常。

五、移位示意图

  • SPI时序

0模式是上升沿读取,下降沿切换字节,并且ss置0时同时开始传输数据

1模式时下降沿读取,上升沿切换字节,并且SCK置1时才开始传输数据

模式3与1对应,SCK波形取反,CPOL=1

模式2与0对应,SCK波形取反,CPOL=1

注意:模式0与模式3都是上升沿采入——模式1与模式2都是下降沿采入

这两张图展示了 SPI(Serial Peripheral Interface,串行外设接口)的两种工作模式。

共同点

  • 都在描述交换一个字节的过程。
  • 都设定了CPOL = 0,即空闲状态时,SCK(时钟信号)为低电平。

不同点

  • 模式 0CPHA = 0SCK第一个边沿移入数据,第二个边沿移出数据。从波形图上看,在SCK的上升沿进行数据移入,下降沿进行数据移出。
  • 模式 1CPHA = 1SCK第一个边沿移出数据,第二个边沿移入数据 。从波形图上看,在SCK的上升沿进行数据移出,下降沿进行数据移入。

简单来说,两张图都是 SPI 在不同CPHA设置下交换一个字节数据的波形图,区别在于SCK不同边沿上数据的移入和移出操作不同。

初始化阶段

将 SS(即 a)置 0 开始运行。

SS 为片选信号,用于选择要通信的从设备,低电平有效。

此操作目的是选中对应的从设备,使其进入准备接收和发送数据的状态,开启通信流程。

数据传输同步阶段:(模式一)

发送同步时钟(SCK,即 b)至高位。

SCK 为串行时钟信号,在时钟信号的上升沿和下降沿进行数据的发送和接收操作。

其目的是在上升沿时,为主从设备数据的发送和接收提供同步时机,让双方能够在同一时刻进行数据交互。

数据发送与读取阶段

在同步时钟 SCK 上升沿时,MOSI(即 c)和 MISO(即 d)开始读取当前自己。

MOSI 是主输出从输入线,主设备通过它向从设备发送数据;

MISO 是主输入从输出线,从设备通过它向主设备发送数据。

此时,主设备将数据通过 MOSI 线发送,从设备将数据通过 MISO 线发送,双方同时读取各自线上的数据,实现数据的双向传输。

主机读取阶段

当同步时钟 SCK 置 0,即下降沿到来时,主机读取数据。

因为在 SCK 下降沿时,数据在 MISO 线上处于稳定状态,主机可准确读取从设备通过 MISO 线发送过来的数据。

整段数据交换完成阶段

重复上述 SCK 上升沿发送数据、下降沿读取数据的操作,即可完成一整段数据交换。在此过程中,SS 保持低电平选中从设备,SCK 不断产生上升沿和下降沿,MOSI 和 MISO 逐位传输数据。通过多次重复这些操作,逐位完成一整段数据的双向交换。

继续通信阶段

若主设备和从设备之间还有数据需要传输,即继续接收数据时,重复上述所有操作。也就是继续利用 SS、SCK、MOSI、MISO 协同工作,重复时钟信号的上升沿发送数据、下降沿读取数据的过程,实现后续数据的交换。

结束通信阶段

若主设备完成数据交换,不需要继续通信,将 SS(即 a)置 1,从机停止运行,同时将 MISO(即 d)设置至高阻态。

SS 拉高表示取消对从设备的选择,使从设备停止通信操作进入空闲状态;

MISO 高阻态意味着该引脚在电气上与电路断开,不输出信号,避免对其他设备产生干扰。

  • SPI时序

这里的0x06是写使能,0x03是读指令

W25Q64简介

W25Q64

1. 存储区域划分(Block Segmentation)

图的左上角展示了存储区域的划分。可以看到不同的扇区(Sector),每个扇区大小有 4KB 或 1KB ,并且有对应的十六进制地址范围。例如,Sector 15 的地址范围是 0xF000h - 0xFFFFh ,大小为 4KB。扇区是存储设备中用于数据擦除和编程的基本单元。

2. 存储块(Block)

图的右侧展示了不同的存储块(Block),每个块的大小为 64KB ,并且有对应的十六进制地址范围。例如,Block 127 的地址范围是 0x7F000h - 0x7FFFFh 。存储块是由多个扇区组成的,在闪存中,擦除操作通常是以块为单位进行的。

3. 控制逻辑部分

  • Write Control Logic:写控制逻辑,负责管理数据写入操作,/WP(Write Protect)引脚用于控制写保护功能。
  • Status Register:状态寄存器,用于存储芯片的状态信息,如操作是否完成、是否有错误等。
  • SPI Command & Control Logic:SPI(Serial Peripheral Interface)命令和控制逻辑,用于处理通过 SPI 接口发送的命令和数据。相关引脚包括 /HOLD、CLK、/CS、DI(Data In)和 DO(Data Out)。
  • High Voltage Generators:高压发生器,用于在编程和擦除操作时提供所需的高电压。
  • Page Address Latch / Counter 和 Byte Address Latch / Counter:分别用于锁存页地址和字节地址,在闪存操作中,数据是以页为单位进行读写的。

4. 解码和缓冲部分

  • Write Protect Logic and Row Decode:写保护逻辑和行解码,用于选择特定的存储行。
  • Column Decode And 256 - Byte Page Buffer:列解码和 256 字节页缓冲,用于在读写操作时缓存数据。页缓冲器可以提高数据传输的效率,在页编程和读取操作中起到重要作用。

总体来说,这张图详细描述了存储芯片内部的地址组织结构以及实现数据读写、擦除等操作的控制逻辑和电路模块,有助于理解闪存芯片的工作原理和操作流程。

疑问:为什么FLASH操作相对于RAM操作如此繁琐?

  1. Flash 存储器特性
    1. Flash 是一种掉电不丢失的存储器。
    2. 为保证掉电不丢失特性,同时实现足够大的存储容量和较低成本,在其他方面做出妥协,如操作便携性。
  2. Flash 与 RAM 在写入操作上的差异
    1. RAM 写入特点:写入操作简单直接,想写哪里就写哪里,想写多少都可以,并且支持覆盖写入。
    2. Flash 写入特点:写入操作不如 RAM 简单直接,不具备像 RAM 那样想写哪里就写哪里、想写多少就写多少以及覆盖写入的特性 ,需要遵守上面tu'pia

一.SPI通信层配置

  1. 设置void MySPI_Init(void) --引脚初始化

端口设置PA4.5.6.7,其中a6设置GPIO_Mode_IPU,其余设置GPIO_Mode_Out_PP(why)

  1. 从机选择(给这几个GPIO换个名字)

void MySPI_W_SS(uint8_t BitValue)

void MySPI_W_SCK(uint8_t BitValue)

void MySPI_W_MOSI(uint8_t BitValue)

uint8_t MySPI_R_MISO(void)

  1. void MySPI_Init(void) 函数中添加引脚初始化

MySPI_W_SS(1);MySPI_W_SCK(0);

  1. 根据SPI基本时序单元图封装函数

void MySPI_Start(void)

void MySPI_Stop(void)

uint8_t MySPI_SwapByte(uint8_t ByteSend)

注意:MySPI_W_MOSI(ByteSend & (0x80 >> i));//取输入数据最高位(&只要有一个为0,都为0) 

 MySPI_W_SCK(1);//SCK置高位

  if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}

  //主机把从机的高位接受进来(|只要有一个为1,都为1),把第I位设置成1

  //关于MySPI_R_MISO() == 1?刚刚设置ByteReceive = 0x00了嘛,所以如若接收到的这位是0,直接不用管了呀,它本身这个位置上的数就是0

  MySPI_W_SCK(0);//SCK置低位

//交换数据不就是既发送也接受,如果想读取就随便发送然后接收就行,想写入,就发送写入值,不操作接收的返回值即可(ByteReceive = 0x00;已经在函数中设置过了,如果想要读取就改成ByteSend = 0x00后进行逻辑上的调整即可,只要满足主从交换就没问题

  1. 补充:模式123如何改 

模式1

MySPI_W_SCK(1);

MySPI_W_MOSI(ByteSend & (0x80 >> i));

MySPI_W_SCK(0);

if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}

模式3(注意,源代码中MySPI_W_SCK(0改成MySPI_W_SCK(1)

MySPI_W_SCK(0);

MySPI_W_MOSI(ByteSend & (0x80 >> i));

MySPI_W_SCK(1);

if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}

二.SPI上层配置——W25Q64

1.初始化与读ID,保证SPI底层逻辑调用成功

void W25Q64_Init(void)

{ MySPI_Init();}

void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)

{ // 1. 启动 SPI 通信

    MySPI_Start();

    // 2. 发送 JEDEC ID 读取命令

    MySPI_SwapByte(W25Q64_JEDEC_ID);

    // 3. 读取制造商 ID

    *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);

    // 4. 读取设备 ID 的高 8 位

    *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);

    // 5. 将高 8 位左移 8 位

    *DID <<= 8;

    // 6. 读取设备 ID 的低 8 位,并与高 8 位合并

    *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);

    // 7. 停止 SPI 通信

    MySPI_Stop();}

解析:

代码逐行解释
 
  1. MySPI_Start();

    • 调用 MySPI_Start 函数,该函数的作用是启动 SPI 通信。通常,这意味着将 SPI 接口的片选信号(CS)拉低,以选中 W25Q64 芯片。
  2. MySPI_SwapByte(W25Q64_JEDEC_ID);

    • 调用 MySPI_SwapByte 函数,发送 W25Q64_JEDEC_ID 命令。W25Q64_JEDEC_ID 是一个预定义的常量,表示读取 JEDEC ID 的命令码。MySPI_SwapByte 函数会在发送一个字节的同时接收一个字节的数据。
  3. *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);

    • 发送一个虚拟字节 W25Q64_DUMMY_BYTE,并将接收到的字节存储到 MID 指针所指向的内存位置。这个接收到的字节就是 W25Q64 芯片的制造商 ID。
  4. *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);

    • 再次发送一个虚拟字节 W25Q64_DUMMY_BYTE,并将接收到的字节存储到 DID 指针所指向的内存位置。这个接收到的字节是设备 ID 的高 8 位。
  5. *DID <<= 8;

    • 将 DID 的值左移 8 位,为存储设备 ID 的低 8 位腾出空间。
  6. *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);

    • 发送另一个虚拟字节 W25Q64_DUMMY_BYTE,并将接收到的字节(设备 ID 的低 8 位)与 DID 的高 8 位进行按位或运算,合并成一个完整的 16 位设备 ID。
  7. MySPI_Stop();

    • 调用 MySPI_Stop 函数,停止 SPI 通信。通常,这意味着将 SPI 接口的片选信号(CS)拉高,取消对 W25Q64 芯片的选中。

2.写使能与通过读状态寄存器判断是否在忙

void W25Q64_WaitBusy(void)
{
    // 1. 启动 SPI 通信
    MySPI_Start();

    // 2. 发送读取状态寄存器 1 的命令
    MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);

    // 3. 循环检查状态寄存器 1 的最低位(忙碌标志位)
    while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
    {
        // 若最低位为 1,表示芯片处于忙碌状态,继续循环等待
        // 最后一位为Busy位,具体参考芯片手册
    }

    // 4. 停止 SPI 通信
    MySPI_Stop();
}

注意:为了放置死循环意外卡死,可以设置变量Timeout

void W25Q64_WaitBusy(void)
{
	uint32_t Timeout;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	Timeout = 100000;
	while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
	{
		Timeout --;
		if (Timeout == 0)
		{
			break;
		}
	}
	MySPI_Stop();
}

3.页编程函数

解读:先发送指令码02,再发送三个地址(A0-A23),再发送数据

注意:只能发送256个Byte,多出会覆盖第一个

void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
  • *DataArray  为了调用效率提升,设置一个数组提升效率,数组需要指针进行传递,数据类型定义为指针函数
  • uint16_t Count   表示写多少个,注意要使用uint16,写入数据的数量范围为0-256
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i;
	
	W25Q64_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for (i = 0; i < Count; i ++)
	{
		MySPI_SwapByte(DataArray[i]);
	}
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}
 问:为什么Address右移传输?

W25Q64_PageProgram函数中,MySPI_SwapByte(Address >> 16);MySPI_SwapByte(Address >> 8); 和 MySPI_SwapByte(Address); 这几行代码使用右移操作是为了将 32 位的地址 Address 拆分成三个 8 位字节,以便通过 SPI 接口逐字节发送到 W25Q64 芯片。下面详细解释其原理和原因。

1. W25Q64 芯片的通信协议

W25Q64 是一款基于 SPI 接口的闪存芯片,当进行页编程(Page Program)操作时,需要向芯片发送一系列命令和数据。具体来说,首先要发送页编程命令(W25Q64_PAGE_PROGRAM),然后发送 3 字节的地址,最后发送要写入的数据。

2. 地址拆分的必要性

SPI 接口是一种串行通信接口,每次只能传输 8 位(1 字节)的数据。而 Address 是一个 32 位的无符号整数,为了将这个 32 位的地址通过 SPI 接口发送给 W25Q64 芯片,需要将其拆分成 3 个 8 位字节。

3. 右移操作的作用

右移操作(>>)是一种位运算,用于将一个数的二进制表示向右移动指定的位数。在这个函数中,右移操作的具体作用如下:

  • MySPI_SwapByte(Address >> 16);:将 Address 右移 16 位,这样就把 Address 的高 8 位移动到了低 8 位,然后通过 MySPI_SwapByte 函数将这 8 位发送出去。
  • MySPI_SwapByte(Address >> 8);:将 Address 右移 8 位,把 Address 的中间 8 位移动到了低 8 位,再通过 MySPI_SwapByte 函数发送这 8 位。
  • MySPI_SwapByte(Address);:直接使用 Address 的低 8 位,通过 MySPI_SwapByte 函数发送出去。
假设 Address 的值为 0x123456,它的二进制表示为:
0000 0000 0001 0010 0011 0100 0101 0110

Address >> 16 的结果为 0x0012,二进制表示为:
0000 0000 0000 0000 0000 0000 0001 0010
发送的是 0x12

Address >> 8 的结果为 0x1234,二进制表示为:
0000 0000 0000 0000 0001 0010 0011 0100
发送的是 0x34。

Address 本身为 0x123456,发送的是 0x56

这样,通过三次右移操作和 MySPI_SwapByte 函数调用,就将 32 位的地址 0x123456 拆分成三个 8 位字节 0x12、0x34 和 0x56 依次发送给了 W25Q64 芯片。

综上所述,使用右移操作是为了满足 SPI 接口每次只能传输 8 位数据的要求,将 32 位的地址拆分成 3 个 8 位字节进行传输。

4.擦除选项

void W25Q64_SectorErase(uint32_t Address)//只演示扇区擦除
{
    // 使能 W25Q64 的写操作,在进行擦除操作前需要先使能写操作
    W25Q64_WriteEnable();

    // 启动 SPI 通信,开始与 W25Q64 进行数据交互
    MySPI_Start();
    // 发送扇区擦除命令(4KB 扇区),通知 W25Q64 即将进行扇区擦除操作
    MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
    // 将 32 位地址的高 8 位通过 SPI 发送给 W25Q64,指定要擦除的扇区起始地址的高字节
    MySPI_SwapByte(Address >> 16);
    // 将 32 位地址的中间 8 位通过 SPI 发送给 W25Q64,指定要擦除的扇区起始地址的中间字节
    MySPI_SwapByte(Address >> 8);
    // 将 32 位地址的低 8 位通过 SPI 发送给 W25Q64,指定要擦除的扇区起始地址的低字节
    MySPI_SwapByte(Address);
    // 停止 SPI 通信,结束与 W25Q64 的本次数据交互
    MySPI_Stop();

    // 等待 W25Q64 完成扇区擦除操作,在此期间芯片处于忙碌状态,等待其操作完成
    W25Q64_WaitBusy();
}

5.读取数据

  • High Impedance 高阻态
  • 读没有256的限制,Data Out x可以一直读,所以后面的uint16_t Count改成了uint32_t Count,扩大了数字限制
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
    uint32_t i;

    // 启动 SPI 通信,准备与 W25Q64 进行数据交互
    MySPI_Start();

    // 发送读取数据的命令给 W25Q64,告知芯片接下来要进行读取操作
    MySPI_SwapByte(W25Q64_READ_DATA);

    // 将 32 位地址的高 8 位通过 SPI 发送给 W25Q64,指定要读取数据的起始地址的高字节
    MySPI_SwapByte(Address >> 16);
    // 将 32 位地址的中间 8 位通过 SPI 发送给 W25Q64,指定要读取数据的起始地址的中间字节
    MySPI_SwapByte(Address >> 8);
    // 将 32 位地址的低 8 位通过 SPI 发送给 W25Q64,指定要读取数据的起始地址的低字节
    MySPI_SwapByte(Address);

    // 循环读取指定数量的数据
    for (i = 0; i < Count; i++)
    {
        // 向 W25Q64 发送一个哑字节(无实际意义的字节),同时接收从该地址读取到的数据
        // 并将读取到的数据存储到 DataArray 数组中
        DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    }

    // 停止 SPI 通信,结束与 W25Q64 的本次数据交互
    MySPI_Stop();
}
注意:关于 W25Q64_WaitBusy();位置相关问题
事后等待(本次代码方法)
  • 优点:在函数之外等待,能确保芯片处于不忙状态,稳定性和可靠性高。
  • 缺点:等待时程序处于阻塞状态,无法执行其他代码,可能导致整体运行效率降低。
事前等待
  • 优点:写完后无需等待,程序可立即执行其他代码,理论上可提高效率。有可能在执行其他代码的过程中,芯片完成忙碌状态,无需额外等待时间。
  • 缺点:无法完全确定等待时间,存在等待时间不足的风险。若在芯片忙碌时进行读取操作,可能导致数据读取错误。在写入操作和读取操作之前都需要等待,适用场景相对复杂,增加了代码编写的复杂性。
void ......
{
	uint16_t i;

	W25Q64_WaitBusy();//事前等待

	W25Q64_WriteEnable();
	MySPI_Start();

	.....
	for (i = 0; i < Count; i ++)
	{
		....
	}
	MySPI_Stop();
	
}

这样就配置完了,记得去头文件申明一下,并在主函数中调用啊。

6.主函数配置

#include "stm32f10x.h"                  // 包含STM32F10x系列微控制器的头文件,提供了该系列芯片的寄存器定义等相关信息
#include "Delay.h"                      // 包含延时函数的头文件,可用于实现不同时长的延时操作
#include "OLED.h"                       // 包含OLED显示屏驱动的头文件,用于对OLED屏幕进行初始化和显示操作
#include "W25Q64.h"                     // 包含W25Q64闪存芯片驱动的头文件,用于对W25Q64芯片进行读写、擦除等操作

// 定义变量用于存储W25Q64芯片的制造商ID(MID)和设备ID(DID)
uint8_t MID;
uint16_t DID;

// 定义一个用于写入W25Q64芯片的数组,包含4个字节的数据
uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};
// 定义一个用于从W25Q64芯片读取数据的数组,长度为4字节
uint8_t ArrayRead[4];

int main(void)
{
    OLED_Init();
    W25Q64_Init();

    // 在OLED屏幕的第1行第1列开始显示字符串 "MID:   DID:",用于提示接下来要显示的信息
    OLED_ShowString(1, 1, "MID:   DID:");
    // 在OLED屏幕的第2行第1列开始显示字符串 "W:",表示接下来要显示写入的数据
    OLED_ShowString(2, 1, "W:");
    // 在OLED屏幕的第3行第1列开始显示字符串 "R:",表示接下来要显示读取的数据
    OLED_ShowString(3, 1, "R:");

    // 调用W25Q64_ReadID函数读取W25Q64芯片的制造商ID和设备ID,并将结果存储在MID和DID变量中
    W25Q64_ReadID(&MID, &DID);
    // 在OLED屏幕的第1行第5列开始以十六进制形式显示制造商ID,显示2位
    OLED_ShowHexNum(1, 5, MID, 2);
    // 在OLED屏幕的第1行第12列开始以十六进制形式显示设备ID,显示4位
    OLED_ShowHexNum(1, 12, DID, 4);

    // 调用W25Q64_SectorErase函数擦除W25Q64芯片地址为0x000000的扇区,为后续写入数据做准备
    W25Q64_SectorErase(0x000000);
    // 调用W25Q64_PageProgram函数将ArrayWrite数组中的4个字节数据写入W25Q64芯片地址为0x000000的页面
    W25Q64_PageProgram(0x000000, ArrayWrite, 4);

    // 调用W25Q64_ReadData函数从W25Q64芯片地址为0x000000的位置开始读取4个字节的数据,并存储到ArrayRead数组中
    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)
    {
        
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值