【二十六】SPI FLASH应用
26.1 W25Q64介绍
W25Q64是一种常见的串行闪存器件,它采用SPI(Serial Peripheral Interface)接口协议,具有高速读写和擦除功能,可用于存储和读取数据。 W25Q64芯片容量为64 Mbit(8 MB),其中名称后的数字代表不同的容量选项。不同的型号和容量选项可以满足不同应用的需求,比如W25Q16、W25Q32、W25Q128等。 通常被用于嵌入式设备、存储设备、路由器等高性能电子设备中
W25Q64闪存芯片的内存分配是按照扇区(Sector)和块(Block)进行的,每个扇区的大小为4KB,每个块包含16个扇区,即一个块的大小为64KB

26.2 硬件接口
在开发板中板载了一个W25Q64存储芯片,其引脚的说明,见下表
| CLK | 从外部获取时间,为输入输出功能提供时钟 |
| DI | 标准SPI使用单向的DI,来串行的写入指令,地址,或者数据到FLASH中,在时钟的上升沿 |
| DO | 标准SPI使用单向的DO,来从处于下降边沿时钟的设备,读取数据或者状态 |
| WP | 防止状态寄存器被写入 |
| HOLD | 当它有效时允许设备暂停,低电平:DO引脚高阻态,DI CLK引脚的信号被忽略。高电平:设备重新开始,当多个设备共享相同的SPI信 号的时候该功能可能会被用到 |
| CS | CS高电平的时候其他引脚成高阻态,处于低电平的时候,可以读写数据 |
它与GD32F470的连接如下:
| GD32F470(主机) | W25Q64(从机) | 说明 |
|---|---|---|
| PF6 | /CS | 片选线 |
| PF7 | CLK | 时钟线 |
| PF8 | DO(IO1) | 主机输入从机输出线 |
| PF9 | DI(IO0) | 主机输出从机输入线 |

需要注意的是,我们使用的是硬件SPI方式驱动W25Q64,因此我们需要确定我们设置的引脚是否有硬件SPI外设接口。在GD32F4数据手册中,PF6~9可以复用为SPI4的4根通信线

26.3 配置引脚
使用到了硬件SPI外设,我们需要开启对应的硬件SPI时钟,并且开启引脚的复用功能,绑定复用线
rcu_periph_clock_enable(RCU_GPIOF); // 使用F端口
rcu_periph_clock_enable(RCU_SPI4); // 使能SPI4
//引脚复用
gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_7);
gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_8);
gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_9);
//引脚模式
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7);
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_8);
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9);
//输出模式
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
//开启CS引脚时钟
rcu_periph_clock_enable(RCU_GPIOF);
//配置CS引脚模式
gpio_mode_set(GPIOF, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_6);
//配置CS输出模式
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
//W25Q64不选中
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
26.4 配置SPI
根据W25Q64的数据手册,得到以下配置:
-
SPI配置为了全双工模式,可以同时发送与接收数据
-
GD32F470配置为主机模式,由GD32F470产生时钟,与从机W25Q64进行通信
-
数据的传输以8位进行传输,因为W25Q64有时需要传输8位,有时需要传输16位,有时直接传输24位。为了同时兼容3种长度的传输,选择了以8位数据位进行传输
-
W25Q64支持两种模式,模式0和模式3,这里极性相位配置选择了模式3,即时钟极性为1,时钟相位为1
-
片选方式选择软件控制片选。在硬件SPI中,一个SPI只有一个片选线,这会导致硬件SPI如果选择硬件控制片选信号,只能控制一个从机。我们是希望能够一个SPI可以控制多个从机,因此选择软件方式片选,片选线可以随意设定
-
时钟分频选择2分频,根据W25Q64的数据手册说明,W25Q64的SPI时钟可以达到133MHz,而我们的SPI4的时钟来源为PCLK2=120MHz,虽然SPI的配置中必须要求进行分频,但是分频之后的频率也是低于133Mhz,W25Q64完全可以兼容
-
字节顺序选择高位在前
//SPI参数定义结构体
spi_parameter_struct spi_init_struct;
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 传输模式全双工
spi_init_struct.device_mode = SPI_MASTER; // 配置为主机
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; // 8位数据
spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE; //极性相位
spi_init_struct.nss = SPI_NSS_SOFT; // 软件cs
spi_init_struct.prescale = SPI_PSC_2; //SPI时钟预调因数为2
spi_init_struct.endian = SPI_ENDIAN_MSB; //高位在前
//将参数填入SPI4
spi_init(SPI4, &spi_init_struct);
//使能SPI
spi_enable(SPI4);
SPI的初始化完成,我们需要准备SPI读写步骤。SPI我们配置为了全双工模式,即可以读也可以写。为确保发送和接收数据成功, 在发送时,需要确保发送缓冲区里的数据发送完毕,即发送缓冲区为空,才可以进行下一个数据的发送;在接收时,需要确保接收缓冲区里有数据才能够进行接收
26.5 读取设备ID案例

根据W25Q64的数据手册可知,读取ID的指令有很多个,ABH/90H/92H/94H/8FH等。而90H的命令,在数据手册中给出了其读取的时序图

根据W25Q64的数据手册可知,读取ID的指令有很多个,ABH/90H/92H/94H/8FH等。而90H的命令,在数据手册中给出了其读取的时序图
读取步骤:
1. 将CS端拉低为低电平
2. 发送指令 90H(1001_0000)
3. 发送地址 000000H(0000_0000_0000_0000_0000_0000)
4. 读取制造商ID,根据数据手册可以知道制造商ID为EFh
5. 读取设备ID,根据数据手册可以知道设备ID为16h
6. 恢复CS端为高电平
//读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
//读取设备ID
uint16_t W25Q64_readID(void)
{
uint16_t temp = 0;
//将CS端拉低为低电平
W25QXX_CS_ON(0);
//发送指令90h
spi_read_write_byte(0x90);//发送读取ID命令
//发送地址 000000H
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
//接收数据
//接收制造商ID
temp |= spi_read_write_byte(0xFF)<<8;
//接收设备ID
temp |= spi_read_write_byte(0xFF);
//恢复CS端为高电平
W25QXX_CS_ON(1);
//返回ID
return temp;
}
26.6 写入数据流程案例
写入数据步骤如右图。在FLASH存储器中,每次写入数据都要确保其中的数据为0xFF,是因为FLASH存储器的写入操作是一种擦除-写入操作。 擦除操作是将存储单元中的数据全部置为1,也就是0xFF。然后,只有将要写入的数据位为0的位置才能进行写入操作,将其改变为0。这个过程是不可逆的, 所以在写入数据之前,需要先确保要写入的位置为0xFF,然后再写入数据

26.6.1 写使能
在进行写入操作之前,需要使用到写使能(Write Enable)命令。写使能的作用是启用对闪存芯片的写入操 作。在默认情况下,闪存芯片处于保护状态,禁止对其进行写入操作,主要是为了防止误操作对数据的损坏。写使 能命令可以解除这种保护状态,将闪存芯片设置为可以进行写入操作
通过发送写使能命令,闪存芯片将进入一个特定的状态,使得后续的写入命令可以被接受和执行。在写入数据 之前,需要发送写使能命令来确保闪存芯片处于可写状态。然后,才能发送写入命令将数据写入指定的存储位置。 使用写使能命令可以有效地保护数据的完整性和安全性,防止误操作对数据进行写入或者修改。同时,也能够确保 数据的一致性,避免写入过程中出现错误或者干扰。因此,在使用W25Q64进行写入操作时,需要先发送写使能命 令,以确保闪存芯片处于可写状态,再进行数据的写入操作
W25Q64的数据手册中,关于写使能的时序如下:

操作步骤:
1. 将CS端拉低为低电平
2. 发送指令 06H(0000_0110)
3. 恢复CS端为高电平
具体实现代码如下:
//发送写使能
void W25Q64_write_enable(void)
{
//拉低CS端为低电平
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
//发送指令06h
spi_read_write_byte(0x06);
//拉高CS端为高电平
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
}
26.6.2 器件忙判断
在W25Q64的数据手册中,有3个状态寄存器,可以判断当前W25Q64是否正在传输、写入、读取数据等,我们每一次要对W25Q64进行操作时,需要先判断W25Q64是否在忙。 如果在忙的状态,我们去操作W25Q64,很可能会导致数据丢失,并且操作失败。而判断是否忙,是通过状态寄存器1的S0为进行判断,状态寄存器1的地址为0X05

读取状态寄存器的时序图如下:
1. 拉低CS端为低电平
2. 发送指令05h(0000_0101)
3. 接收状态寄存器值
4. 恢复CS端为高电

具体实现代码如下:
void W25Q64_wait_busy(void)
{
unsigned char byte = 0;
do
{
//拉低CS端为低电平
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
//发送指令05h
spi_read_write_byte(0x05);
//接收状态寄存器值
byte = spi_read_write_byte(0Xff);
//恢复CS端为高电平
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
//判断BUSY位是否为1 如果为1说明在忙,重新读写BUSY位直到为0
}while( ( byte & 0x01 ) == 1 );
}
26.6.3 扇区擦除
W25Q64闪存芯片的内存分配是按照扇区(Sector)和块(Block)进行的,每个扇区的大小为4KB,每个块包含16个扇区,即一个块的大小为64KB
W25Q64闪存芯片的扇区擦除是指将某个特定扇区中的数据全部擦除的操作。擦除操作会将扇区中的所有数据都置为1(即0xFF),恢复到初始状态。下面是W25Q64扇区擦除的一般流程:
1. 写使能(Write Enable):首先,要确保闪存芯片处于可写状态。发送写使能命令,将闪存芯片设置为可写模式,解除写保护
2. 扇区擦除设置(Sector Erase Setup):向W25Q64发送扇区擦除设置命令,并指定要擦除的扇区地址。W25Q64支持多种扇区擦除命令,可以根据需要选择擦除一个或多个扇区
3. 扇区擦除确认(Sector Erase Confirm):等待扇区擦除确认。W25Q64芯片进行擦除操作需要一定的时间,具体时间可参考该芯片的规格书。在擦除操作进行期间,通常会读取状态寄存器忙位的方法来确定擦除是否完成。过早地读取擦除操作中的数据可能会导致不正确的结果
4. 扇区擦除完成:当扇区擦除成功后,状态寄存器将指示擦除操作完成。此时,该扇区中的数据已经全部被擦除为1
扇区擦除操作是一种高级操作,需要小心谨慎地使用。在实际应用中,通常会结合编程逻辑和相应的控制器来管理闪存芯片的擦除和写入操作,以确保数据的安全性和完整性
在使用扇区擦除操作时,有几个注意事项需要特别关注:
1. 擦除范围:要确保擦除的范围是正确的,仅擦除目标扇区,避免误擦除其他扇区中的数据。在执行擦除操作之前,请务必仔细检查要擦除的扇区地址,并确保没有错误
2. 数据备份:由于扇区擦除操作将数据全部擦除为1(0xFF),在执行擦除之前,应该确保重要数据已经备份。擦除后,数据将无法恢复,因此在执行重要数据的扇区擦除操作之前,请务必做好数据备份的工作
扇区擦除的时序图如下:
1. 拉低CS端为低电平
2. 发送指令20h(0010_0000)
3. 发送24位的扇区首地址
4. 恢复CS端为高电平

具体实现代码如下:
以下代码跟扇区擦除时序图有一些差别,多了忙判断和写使能
/**********************************************************
* 函 数 名 称:W25Q64_erase_sector
* 函 数 功 能:擦除一个扇区
* 传 入 参 数:addr=擦除的扇区号
* 函 数 返 回:无
* 作 者:LC
* 备 注:addr=擦除的扇区号,范围=0~15
**********************************************************/
void W25Q64_erase_sector(uint32_t addr)
{
//计算扇区号,一个扇区4KB=4096
addr *= 4096;
W25Q64_write_enable(); //写使能
W25Q64_wait_busy(); //判断忙,如果忙则一直等待
//拉低CS端为低电平
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
//发送指令20h
spi_read_write_byte(0x20);
//发送24位扇区地址的高8位
spi_read_write_byte((uint8_t)((addr)>>16));
//发送24位扇区地址的中8位
spi_read_write_byte((uint8_t)((addr)>>8));
//发送24位扇区地址的低8位
spi_read_write_byte((uint8_t)addr);
//恢复CS端为高电平
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
//等待擦除完成
W25Q64_wait_busy();
}
26.6.4 写入数据
现在写入数据的前置步骤:擦除数据->写使能->判断忙 我们都完成了,只剩下将数据写入到对应地址中保存即可

26.6.5 读取数据
读取数据的时序图如下:
1. 拉低CS端为低电平
2. 发送指令03h(0000_0011)
3. 发送24位读取数据地址
4. 接收读取到的数据
5. 恢复CS端为高电平

具体实现代码如下:
/**********************************************************
* 函 数 名 称:W25Q64_read
* 函 数 功 能:读取W25Q64的数据
* 传 入 参 数:buffer=读出数据的保存地址 read_addr=读取地址 read_length=读去长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q64_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length)
{
uint16_t i;
//拉低CS端为低电平
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
//发送指令03h
spi_read_write_byte(0x03);
//发送24位读取数据地址的高8位
spi_read_write_byte((uint8_t)((read_addr)>>16));
//发送24位读取数据地址的中8位
spi_read_write_byte((uint8_t)((read_addr)>>8));
//发送24位读取数据地址的低8位
spi_read_write_byte((uint8_t)read_addr);
//根据读取长度读取出地址保存到buffer中
for(i=0;i<read_length;i++)
{
buffer[i]= spi_read_write_byte(0XFF);
}
//恢复CS端为高电平
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
}
26.7 SPI FLASH验证
创建两个文件,分别命名为bsp_w25q64.c和bsp_w25q64.h。往里面写入完整代码:
bsp_w25q64.c
#include "bsp_w25q64.h"
void w25q64_init_config(void)
{
//SPI参数定义结构体
spi_parameter_struct spi_init_struct;
rcu_periph_clock_enable(RCU_GPIOF); // 使用F端口
rcu_periph_clock_enable(RCU_SPI4); // 使能SPI4
//引脚复用
gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_7);
gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_8);
gpio_af_set(GPIOF, GPIO_AF_5, GPIO_PIN_9);
//引脚模式
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7);
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_8);
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9);
//输出模式
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
//开启CS引脚时钟
rcu_periph_clock_enable(RCU_GPIOF);
//配置CS引脚模式
gpio_mode_set(GPIOF, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_6);
//配置CS输出模式
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
//W25Q64不选中
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 传输模式全双工
spi_init_struct.device_mode = SPI_MASTER; // 配置为主机
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; // 8位数据
spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE; //极性相位
spi_init_struct.nss = SPI_NSS_SOFT; //软件cs
spi_init_struct.prescale = SPI_PSC_2; //SPI时钟预分频为2
spi_init_struct.endian = SPI_ENDIAN_MSB; //高位在前
//将参数填入SPI4
spi_init(SPI4, &spi_init_struct);
//使能SPI
spi_enable(SPI4);
}
/******************************************************************
* 函 数 名 称:spi_read_write_byte
* 函 数 说 明:硬件SPI的读写
* 函 数 形 参:dat=发送的数据
* 函 数 返 回:读取到的数据
* 作 者:LC
* 备 注:无
******************************************************************/
uint8_t spi_read_write_byte(uint8_t dat)
{
//等待发送缓冲区为空
while(RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_TBE) );
spi_i2s_data_transmit(SPI4, dat);
//等待接收缓冲区为空
while(RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE) );
return spi_i2s_data_receive(SPI4);
}
/******************************************************************
* 函 数 名 称:W25Q64_readID
* 函 数 说 明:读取W25Q64的厂商ID和设备ID
* 函 数 形 参:无
* 函 数 返 回:设备正常返回EF16
* 作 者:LC
* 备 注:无
******************************************************************/
uint16_t W25Q64_readID(void)
{
uint16_t temp = 0;
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
spi_read_write_byte(0x90);//发送读取ID命令
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
//接收数据
temp |= spi_read_write_byte(0xFF)<<8;
temp |= spi_read_write_byte(0xFF);
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
return temp;
}
/**********************************************************
* 函 数 名 称:W25Q64_wait_busy
* 函 数 功 能:判断W25Q64是否忙
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q64_wait_busy(void)
{
unsigned char byte = 0;
do
{
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
spi_read_write_byte(0x05);
byte = spi_read_write_byte(0Xff);
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
}while( ( byte & 0x01 ) == 1 );
}
/**********************************************************
* 函 数 名 称:W25Q64_write_enable
* 函 数 功 能:发送写使能
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q64_write_enable(void)
{
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
spi_read_write_byte(0x06);
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
}
/**********************************************************
* 函 数 名 称:W25Q64_erase_sector
* 函 数 功 能:擦除一个扇区
* 传 入 参 数:addr=擦除的扇区号
* 函 数 返 回:无
* 作 者:LC
* 备 注:addr=擦除的扇区号,范围=0~15
**********************************************************/
void W25Q64_erase_sector(uint32_t addr)
{
addr *= 4096;
W25Q64_write_enable(); //写使能
W25Q64_wait_busy(); //判断忙
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
spi_read_write_byte(0x20);
spi_read_write_byte((uint8_t)((addr)>>16));
spi_read_write_byte((uint8_t)((addr)>>8));
spi_read_write_byte((uint8_t)addr);
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
//等待擦除完成
W25Q64_wait_busy();
}
/**********************************************************
* 函 数 名 称:W25Q64_write
* 函 数 功 能:写数据到W25Q64进行保存
* 传 入 参 数:buffer=写入的数据内容 addr=写入地址 numbyte=写入数据的长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q64_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte)
{ //0x02e21
unsigned int i = 0;
W25Q64_erase_sector(addr/4096);//擦除扇区数据
W25Q64_write_enable();//写使能
W25Q64_wait_busy(); //忙检测
//写入数据
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
spi_read_write_byte(0x02);
spi_read_write_byte((uint8_t)((addr)>>16));
spi_read_write_byte((uint8_t)((addr)>>8));
spi_read_write_byte((uint8_t)addr);
for(i=0;i<numbyte;i++)
{
spi_read_write_byte(buffer[i]);
}
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
W25Q64_wait_busy(); //忙检测
}
/**********************************************************
* 函 数 名 称:W25Q64_read
* 函 数 功 能:读取W25Q64的数据
* 传 入 参 数:buffer=读出数据的保存地址 read_addr=读取地址 read_length=读去长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q64_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length)
{
uint16_t i;
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
spi_read_write_byte(0x03);
spi_read_write_byte((uint8_t)((read_addr)>>16));
spi_read_write_byte((uint8_t)((read_addr)>>8));
spi_read_write_byte((uint8_t)read_addr);
for(i=0;i<read_length;i++)
{
buffer[i]= spi_read_write_byte(0XFF);
}
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
}
bsp_w25q64.h
#ifndef _BSP_W25Q64_H__
#define _BSP_W25Q64_H__
#include "gd32f4xx.h"
void w25q64_init_config(void);
uint16_t W25Q64_readID(void);
void W25Q64_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte);
void W25Q64_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length) ;
#endif
在main.c中编写如下代码:
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "bsp_led.h"
#include "sys.h"
#include "bsp_usart.h"
#include "bsp_w25q64.h"
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
unsigned char buff[10] = {0};
systick_config();
//串口初始化波特率9600
usart_gpio_config(9600U);
//W25Q64初始化
w25q64_init_config();
//获取W25Q64的设备ID
printf("ID = %X\r\n",W25Q64_readID());
//读取0地址长度为5个字节的数据到buff
W25Q64_read(buff, 0, 5);
//输出读取到的数据
printf("buff = %s\r\n",buff);
//往0地址写入7个字节的数据 “嘉立创”
W25Q64_write("嘉立创", 0, 7);
//读取0地址长度为7个字节的数据到buff
W25Q64_read(buff, 0, 7);
//输出读取到的数据
printf("buff = %s\r\n",buff);
while(1)
{
}
}
验证现象:验证读写功能
读取到ID为 0XEF16
从0地址读取出数据为空,因为里面没有数据
写入数据后,再读出数据为嘉立创
