二十六、SPI-FLASH应用
W25Q128介绍
W25Q128 是一种常见的串行闪存器件,它采用 SPI(Serial Peripheral Interface)接口协议,具有高速读写和擦除功能,可用于存储和读取数据。W25Q128 芯片容量为 128 Mbit(16 MB),其中名称后的数字代表不同的容量选项。不同的型号和容量选项可以满足不同应用的需求,通常被用于嵌入式设备、存储设备、路由器等高性能电子设备中。
W25Q128 闪存芯片的内存分配是按照扇区(Sector)和块(Block)进行的,每个扇区的大小为 4KB,每个块包含 16 个扇区,即一个块的大小为 64KB。
硬件接口
在高配版本的开发板中板载了一个 W25Q128 存储芯片,其引脚的说明,见下表。
CLK | 从外部获取时钟,为输入输出功能提供时钟 |
---|---|
DI | 标准 SPI 使用单向的 DI,来串行的写入指令,地址,或者数据到 FLASH 中。 |
DO | 标准 SPI 使用单向的 DO,读取数据或者状态。 |
WP | 防止状态寄存器被写入 |
HOLD | 当它有效时允许设备暂停,低电平:DO 引脚高阻态,DI CLK 引脚的信号被忽略。高电平:设备重新开始,当多个设备共享相同的 SPI 信 号的时候该功能可能会被用到 |
CS | CS 高电平的时候其他引脚成高阻态,处于低电平的时候,可以读写数据 |
它与 STM32F407 的接线如下:
STM32F407(主机) | W25Q1282(从机) | 说明 |
---|---|---|
PA4(SPI1_NSS) | CS(NSS) | 片选线 |
PA5(SPI1_SCK) | CLK | 时钟线 |
PA6(SPI_MISO) | DO(IO1)(MISO) | 主机输入从机输出线 |
PA7(SPI_MOSI) | DI(IO0)(MOSI) | 主机输出从机输入线 |
需要注意的是,我们使用的是硬件 SPI 方式驱动 W25Q128,因此我们需要确定我们设置的引脚是否有硬件 SPI 外设接口。在 STM32F407 数据手册中,PA4~7 可以复用为 SPI1 的 4 根通信线。
配置引脚
我们先宏定义一下。
#define BSP_GPIO_RCU RCC_AHB1Periph_GPIOA // GPIO时钟
#define BSP_SPI_RCU RCC_APB2Periph_SPI1 // SPI时钟
#define BSP_SPI_NSS_RCU RCC_AHB1Periph_GPIOA // CS引脚时钟
#define BSP_GPIO_PORT GPIOA
#define BSP_GPIO_AF GPIO_AF_SPI1
#define BSP_SPI SPI1
#define BSP_SPI_NSS GPIO_Pin_4 // 软件CS
#define BSP_SPI_SCK GPIO_Pin_5
#define BSP_SPI_SCK_PINSOURCE GPIO_PinSource5
#define BSP_SPI_MISO GPIO_Pin_6
#define BSP_SPI_MISO_PINSOURCE GPIO_PinSource6
#define BSP_SPI_MOSI GPIO_Pin_7
#define BSP_SPI_MOSI_PINSOURCE GPIO_PinSource7
#define W25QXX_CS_ON(x) GPIO_WriteBit(BSP_GPIO_PORT, BSP_SPI_NSS, x ? Bit_SET : Bit_RESET)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
使用到了硬件 SPI1 外设,我们需要开启对应的硬件 SPI1 时钟,并且开启引脚的复用功能,绑定复用线。
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能 GPIO 时钟 */
RCC_AHB1PeriphClockCmd (BSP_GPIO_RCU, ENABLE);
/* SPI时钟使能 */
RCC_APB2PeriphClockCmd(BSP_SPI_RCU, ENABLE);
/* 设置引脚复用 */
GPIO_PinAFConfig(BSP_GPIO_PORT, BSP_SPI_SCK_PINSOURCE, BSP_GPIO_AF);
GPIO_PinAFConfig(BSP_GPIO_PORT, BSP_SPI_MISO_PINSOURCE, BSP_GPIO_AF);
GPIO_PinAFConfig(BSP_GPIO_PORT, BSP_SPI_MOSI_PINSOURCE, BSP_GPIO_AF);
/* 配置SPI引脚引脚SCK */
GPIO_InitStructure.GPIO_Pin = BSP_SPI_SCK;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(BSP_GPIO_PORT, &GPIO_InitStructure);
/* 配置SPI引脚MISO */
GPIO_InitStructure.GPIO_Pin = BSP_SPI_MISO;
GPIO_Init(BSP_GPIO_PORT, &GPIO_InitStructure);
/* 配置SPI引脚MOSI */
GPIO_InitStructure.GPIO_Pin = BSP_SPI_MOSI;
GPIO_Init(BSP_GPIO_PORT, &GPIO_InitStructure);
/* 配置SPI引脚CS */
GPIO_InitStructure.GPIO_Pin = BSP_SPI_NSS;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(BSP_GPIO_PORT, &GPIO_InitStructure);
/* CS引脚高电平 */
W25QXX_CS_ON(1);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
配置 SPI
根据 W25Q128 的数据手册,得到以下配置:
- SPI 配置为了全双工模式,可以同时发送与接收数据;
- STM32F407 配置为主机模式,由 STM32F407 产生时钟,与从机 W25Q128 进行通信;
- 数据的传输以 8 位进行传输。
- 片选方式选择软件控制片选。在硬件 SPI 中,一个 SPI 只有一个片选线,这会导致硬件 SPI 如果选择硬件控制片选信号,只能控制一个从机。我们是希望能够一个 SPI 可以控制多个从机,因此选择软件方式片选,片选线可以随意设定。
- 时钟分频选择 4 分频,根据 W25Q128 的数据手册说明,W25Q128 的 SPI 时钟可以达到 30MHz,而我们的 SPI0 的时钟来源为 PCLK2=84MHz,SPI 的配置中必须要求进行分频,分频之后的频率是 21MH,低于 30Mhz,W25Q1282 完全可以兼容。
- 字节顺序选择高位在前。
SPI_InitTypeDef SPI_InitStructure;
/* FLASH_SPI 模式配置 */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 传输模式全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 配置为主机
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8位数据
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 极性相位
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件cs
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; // SPI时钟预调因数为2
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位在前
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(BSP_SPI, &SPI_InitStructure);
/* 使能 FLASH_SPI */
SPI_Cmd(BSP_SPI, ENABLE);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SPI 的初始化完成,我们需要准备 SPI 读写步骤。SPI 我们配置为了全双工模式,即可以读也可以写。为确保发送和接收数据成功,在发送时,需要确保发送缓冲区里的数据发送完毕,即发送缓冲区为空,才可以进行下一个数据的发送;在接收时,需要确保接收缓冲区里有数据才能够进行接收。
uint8_t spi_read_write_byte(uint8_t dat)
{
//等待发送缓冲区为空
while(RESET == SPI_I2S_GetFlagStatus(BSP_SPI, SPI_I2S_FLAG_TXE) );
//通过SPI4发送一个字节数据
SPI_I2S_SendData(BSP_SPI, dat);
//等待接收缓冲区不空标志
while(RESET == SPI_I2S_GetFlagStatus(BSP_SPI, SPI_I2S_FLAG_RXNE) );
//读取并返回在SPI读取到的单字节数据
return SPI_I2S_ReceiveData(BSP_SPI);
}
2
3
4
5
6
7
8
9
10
11
12
读取设备 ID 案例
根据 W25Q128 的数据手册可知,读取 ID 的指令使用 90H 就好。在数据手册中给出了其读取的时序图。
读取步骤:
- 将 CS 端拉低为低电平;
- 发送指令 90H(1001_0000);
- 发送地址 000000H(0000_0000_0000_0000_0000_0000);
- 读取制造商 ID,根据数据手册可以知道制造商 ID 为 EFh;
- 读取设备 ID;
- 恢复 CS 端为高电平;
实现代码:
//读取芯片ID
//读取设备ID
uint16_t W25Q128_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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
写入数据流程案例
写入数据步骤如右图。在 FLASH 存储器中,每次写入数据都要确保其中的数据为 0xFF,是因为 FLASH 存储器的写入操作是一种擦除-写入操作。擦除操作是将存储单元中的数据全部置为 1,也就是 0xFF。然后,只有将要写入的数据位为 0 的位置才能进行写入操作,将其改变为 0。这个过程是不可逆的,所以在写入数据之前,需要先确保要写入的位置为 0xFF,然后再写入数据。 这种擦除-写入的操作是由于 FLASH 存储器的特殊结构决定的。FLASH 存储器中的存储单元是通过电子门的状态进行控制的,每个门可以存储一个二进制位。擦除操作需要将门的状态恢复为初始状态,即全部为 1。然后通过改变门的状态,将需要存储的数据位改变为 0。所以在写入数据之前,需要确保存储单元的状态为 1,以便进行正确的写入操作。 另外,FLASH 存储器的擦除操作是以块为单位进行的,而不是单个存储单元。所以如果要写入数据的位置上已经有数据存在,需要进行擦除操作,将整个块的数据都置为 1,然后再写入新的数据。这也是为什么在 FLASH 写入数据之前需要确保其中的数据为 0xFF 的原因。
写使能
在进行写入操作之前,需要使用到写使能(Write Enable)命令。写使能的作用是启用对闪存芯片的写入操作。在默认情况下,闪存芯片处于保护状态,禁止对其进行写入操作,主要是为了防止误操作对数据的损坏。写使能命令可以解除这种保护状态,将闪存芯片设置为可以进行写入操作。
通过发送写使能命令,闪存芯片将进入一个特定的状态,使得后续的写入命令可以被接受和执行。在写入数据之前,需要发送写使能命令来确保闪存芯片处于可写状态。然后,才能发送写入命令将数据写入指定的存储位置。使用写使能命令可以有效地保护数据的完整性和安全性,防止误操作对数据进行写入或者修改。同时,也能够确保
数据的一致性,避免写入过程中出现错误或者干扰。因此,在使用 W25Q128 进行写入操作时,需要先发送写使能命令,以确保闪存芯片处于可写状态,再进行数据的写入操作。W25Q128 的数据手册中,关于写使能的时序如下:
操作步骤:
- 将 CS 端拉低为低电平;
- 发送指令 06H(0000_0110);
- 恢复 CS 端为高电平;
具体实现代码如下:
//发送写使能
void W25Q128_write_enable(void)
{
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令06h
spi_read_write_byte(0x06);
//拉高CS端为高电平
W25QXX_CS_ON(1);
}
2
3
4
5
6
7
8
9
10
器件忙判断
在 W25Q1282 的数据手册中,有 3 个状态寄存器,可以判断当前 W25Q128 是否正在传输、写入、读取数据等,我们每一次要对 W25Q128 进行操作时,需要先判断 W25Q128 是否在忙。如果在忙的状态,我们去操作 W25Q128,很可能会导致数据丢失,并且操作失败。而判断是否忙,是通过状态寄存器 1 的 S0 为进行判断,状态寄存器 1 的地址为 0X05。
读取状态寄存器的时序图如下:
- 拉低 CS 端为低电平;
- 发送指令 05h(0000_0101);
- 接收状态寄存器值;
- 恢复 CS 端为高电平;
具体实现代码如下:
/**********************************************************
* 函 数 名 称:W25Q128_wait_busy
* 函 数 功 能:检测线路是否繁忙
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q128_wait_busy(void)
{
unsigned char byte = 0;
do
{
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令05h
spi_read_write_byte(0x05);
//接收状态寄存器值
byte = spi_read_write_byte(0Xff);
//恢复CS端为高电平
W25QXX_CS_ON(1);
//判断BUSY位是否为1 如果为1说明在忙,重新读写BUSY位直到为0
}while( ( byte & 0x01 ) == 1 );
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
扇区擦除
W25Q128 闪存芯片的内存分配是按照扇区(Sector)和块(Block)进行的,每个扇区的大小为 4KB,每个块包含 16 个扇区,即一个块的大小为 64KB。
W25Q128 闪存芯片的扇区擦除是指将某个特定扇区中的数据全部擦除的操作。擦除操作会将扇区中的所有数据都置为 1(即 0xFF),恢复到初始状态。下面是 W25Q128 扇区擦除的一般流程:
- 写使能(Write Enable):首先,要确保闪存芯片处于可写状态。发送写使能命令,将闪存芯片设置为可写模式,解除写保护。
- 扇区擦除设置(Sector Erase Setup):向 W25Q128 发送扇区擦除设置命令,并指定要擦除的扇区地址。W25Q128 支持多种扇区擦除命令,可以根据需要选择擦除一个或多个扇区。
- 扇区擦除确认(Sector Erase Confirm):等待扇区擦除确认。W25Q1282 芯片进行擦除操作需要一定的时间,具体时间可参考该芯片的规格书。在擦除操作进行期间,通常会读取状态寄存器忙位的方法来确定擦除是否完成。过早地读取擦除操作中的数据可能会导致不正确的结果。
- 扇区擦除完成:当扇区擦除成功后,状态寄存器将指示擦除操作完成。此时,该扇区中的数据已经全部被擦除为 1。
扇区擦除操作是一种高级操作,需要小心谨慎地使用。在实际应用中,通常会结合编程逻辑和相应的控制器来管理闪存芯片的擦除和写入操作,以确保数据的安全性和完整性。
在使用扇区擦除操作时,有几个注意事项需要特别关注:
- 擦除范围:要确保擦除的范围是正确的,仅擦除目标扇区,避免误擦除其他扇区中的数据。在执行擦除操作之前,请务必仔细检查要擦除的扇区地址,并确保没有错误。
- 数据备份:由于扇区擦除操作将数据全部擦除为 1(0xFF),在执行擦除之前,应该确保重要数据已经备份。擦除后,数据将无法恢复,因此在执行重要数据的扇区擦除操作之前,请务必做好数据备份的工作。
扇区擦除的时序图如下:
- 拉低 CS 端为低电平;
- 发送指令 20h(0010_0000);
- 发送 24 位的扇区首地址;
- 恢复 CS 端为高电平;
具体实现代码如下:
以下代码跟扇区擦除时序图有一些差别,多了忙判断和写使能。
/**********************************************************
* 函 数 名 称:W25Q128_erase_sector
* 函 数 功 能:擦除一个扇区
* 传 入 参 数:addr=擦除的扇区号
* 函 数 返 回:无
* 作 者:LC
* 备 注:addr=擦除的扇区号,范围=0~4096。
W25Q128将16M的容量分为256个块(Block),每个块大小为64K(64000)个字节,每个块又分为16个扇区(Sector),每个扇区4K个字节。
W25Q128的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。
**********************************************************/
void W25Q128_erase_sector(uint32_t addr)
{
//计算扇区号,一个扇区4KB=4096
addr *= 4096;
W25Q128_write_enable(); //写使能
W25Q128_wait_busy(); //判断忙,如果忙则一直等待
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令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端为高电平
W25QXX_CS_ON(1);
//等待擦除完成
W25Q128_wait_busy();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
写入数据
现在写入数据的前置步骤:擦除数据-> 写使能-> 判断忙 我们都完成了,只剩下将数据写入到对应地址中保存即可。
具体写入数据代码如下:
/**********************************************************
* 函 数 名 称:W25Q128_write
* 函 数 功 能:写数据到W25Q128进行保存
* 传 入 参 数:buffer=写入的数据内容 addr=写入地址 numbyte=写入数据的长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q128_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte)
{
unsigned int i = 0;
//擦除扇区数据
W25Q128_erase_sector(addr/4096);
//写使能
W25Q128_write_enable();
//忙检测
W25Q128_wait_busy();
//写入数据
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令02h
spi_read_write_byte(0x02);
//发送写入的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);
//根据写入的字节长度连续写入数据buffer
for(i=0;i<numbyte;i++)
{
spi_read_write_byte(buffer[i]);
}
//恢复CS端为高电平
W25QXX_CS_ON(1);
//忙检测
W25Q128_wait_busy();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
读取数据
读取数据的时序图如下:
- 拉低 CS 端为低电平;
- 发送指令 03h(0000_0011);
- 发送 24 位读取数据地址;
- 接收读取到的数据;
- 恢复 CS 端为高电平;
具体实现代码如下:
/**********************************************************
* 函 数 名 称:W25Q128_read
* 函 数 功 能:读取W25Q128的数据
* 传 入 参 数:buffer=读出数据的保存地址 read_addr=读取地址 read_length=读去长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q128_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length)
{
uint16_t i;
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令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端为高电平
W25QXX_CS_ON(1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
硬件 SPI FLASH 验证
创建两个文件,分别命名为 spi_flash.c 和 spi_flash.h。往里面写入完整代码:
spi_flash.c
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-08-02 LCKFB-LP first version
*/
#include "spi_flash.h"
#include "board.h"
/**********************************************************
* 函 数 名 称:bsp_spi_init
* 函 数 功 能:初始化SPI
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void bsp_spi_init(void)
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能 GPIO 时钟 */
RCC_AHB1PeriphClockCmd (BSP_GPIO_RCU, ENABLE);
/* SPI时钟使能 */
RCC_APB2PeriphClockCmd(BSP_SPI_RCU, ENABLE);
/* 设置引脚复用 */
GPIO_PinAFConfig(BSP_GPIO_PORT, BSP_SPI_SCK_PINSOURCE, BSP_GPIO_AF);
GPIO_PinAFConfig(BSP_GPIO_PORT, BSP_SPI_MISO_PINSOURCE, BSP_GPIO_AF);
GPIO_PinAFConfig(BSP_GPIO_PORT, BSP_SPI_MOSI_PINSOURCE, BSP_GPIO_AF);
/* 配置SPI引脚引脚SCK */
GPIO_InitStructure.GPIO_Pin = BSP_SPI_SCK;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(BSP_GPIO_PORT, &GPIO_InitStructure);
/* 配置SPI引脚MISO */
GPIO_InitStructure.GPIO_Pin = BSP_SPI_MISO;
GPIO_Init(BSP_GPIO_PORT, &GPIO_InitStructure);
/* 配置SPI引脚MOSI */
GPIO_InitStructure.GPIO_Pin = BSP_SPI_MOSI;
GPIO_Init(BSP_GPIO_PORT, &GPIO_InitStructure);
/* 配置SPI引脚CS */
GPIO_InitStructure.GPIO_Pin = BSP_SPI_NSS;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(BSP_GPIO_PORT, &GPIO_InitStructure);
/* CS引脚高电平 */
W25QXX_CS_ON(1);
/* FLASH_SPI 模式配置 */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 传输模式全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 配置为主机
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8位数据
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 极性相位
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件cs
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; // SPI时钟预调因数为2
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位在前
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(BSP_SPI, &SPI_InitStructure);
/* 使能 FLASH_SPI */
SPI_Cmd(BSP_SPI, ENABLE);
W25QXX_CS_ON(1); // 片选拉高
}
uint8_t spi_read_write_byte(uint8_t dat)
{
//等待发送缓冲区为空
while(RESET == SPI_I2S_GetFlagStatus(BSP_SPI, SPI_I2S_FLAG_TXE) );
//通过SPI4发送一个字节数据
SPI_I2S_SendData(BSP_SPI, dat);
//等待接收缓冲区不空标志
while(RESET == SPI_I2S_GetFlagStatus(BSP_SPI, SPI_I2S_FLAG_RXNE) );
//读取并返回在SPI读取到的单字节数据
return SPI_I2S_ReceiveData(BSP_SPI);
}
//读取芯片ID
//读取设备ID
uint16_t W25Q128_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;
}
//发送写使能
void W25Q128_write_enable(void)
{
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令06h
spi_read_write_byte(0x06);
//拉高CS端为高电平
W25QXX_CS_ON(1);
}
/**********************************************************
* 函 数 名 称:W25Q128_wait_busy
* 函 数 功 能:检测线路是否繁忙
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q128_wait_busy(void)
{
unsigned char byte = 0;
do
{
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令05h
spi_read_write_byte(0x05);
//接收状态寄存器值
byte = spi_read_write_byte(0Xff);
//恢复CS端为高电平
W25QXX_CS_ON(1);
//判断BUSY位是否为1 如果为1说明在忙,重新读写BUSY位直到为0
}while( ( byte & 0x01 ) == 1 );
}
/**********************************************************
* 函 数 名 称:W25Q128_erase_sector
* 函 数 功 能:擦除一个扇区
* 传 入 参 数:addr=擦除的扇区号
* 函 数 返 回:无
* 作 者:LC
* 备 注:addr=擦除的扇区号,范围=0~4096。
W25Q128将16M的容量分为256个块(Block),每个块大小为64K(64000)个字节,每个块又分为16个扇区(Sector),每个扇区4K个字节。
W25Q128的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。
**********************************************************/
void W25Q128_erase_sector(uint32_t addr)
{
//计算扇区号,一个扇区4KB=4096
addr *= 4096;
W25Q128_write_enable(); //写使能
W25Q128_wait_busy(); //判断忙,如果忙则一直等待
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令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端为高电平
W25QXX_CS_ON(1);
//等待擦除完成
W25Q128_wait_busy();
}
/**********************************************************
* 函 数 名 称:W25Q128_write
* 函 数 功 能:写数据到W25Q128进行保存
* 传 入 参 数:buffer=写入的数据内容 addr=写入地址 numbyte=写入数据的长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q128_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte)
{
unsigned int i = 0;
//擦除扇区数据
W25Q128_erase_sector(addr/4096);
//写使能
W25Q128_write_enable();
//忙检测
W25Q128_wait_busy();
//写入数据
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令02h
spi_read_write_byte(0x02);
//发送写入的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);
//根据写入的字节长度连续写入数据buffer
for(i=0;i<numbyte;i++)
{
spi_read_write_byte(buffer[i]);
}
//恢复CS端为高电平
W25QXX_CS_ON(1);
//忙检测
W25Q128_wait_busy();
}
/**********************************************************
* 函 数 名 称:W25Q128_read
* 函 数 功 能:读取W25Q128的数据
* 传 入 参 数:buffer=读出数据的保存地址 read_addr=读取地址 read_length=读去长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q128_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length)
{
uint16_t i;
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令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端为高电平
W25QXX_CS_ON(1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
spi_flash.h
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-08-02 LCKFB-LP first version
*/
#ifndef __SPI_FLASH_H__
#define __SPI_FLASH_H__
#include "stm32f4xx.h"
#define BSP_GPIO_RCU RCC_AHB1Periph_GPIOA // GPIO时钟
#define BSP_SPI_RCU RCC_APB2Periph_SPI1 // SPI时钟
#define BSP_SPI_NSS_RCU RCC_AHB1Periph_GPIOA // CS引脚时钟
#define BSP_GPIO_PORT GPIOA
#define BSP_GPIO_AF GPIO_AF_SPI1
#define BSP_SPI SPI1
#define BSP_SPI_NSS GPIO_Pin_4 // 软件CS
#define BSP_SPI_SCK GPIO_Pin_5
#define BSP_SPI_SCK_PINSOURCE GPIO_PinSource5
#define BSP_SPI_MISO GPIO_Pin_6
#define BSP_SPI_MISO_PINSOURCE GPIO_PinSource6
#define BSP_SPI_MOSI GPIO_Pin_7
#define BSP_SPI_MOSI_PINSOURCE GPIO_PinSource7
#define W25QXX_CS_ON(x) GPIO_WriteBit(BSP_GPIO_PORT, BSP_SPI_NSS, x ? Bit_SET : Bit_RESET)
void bsp_spi_init(void);
uint8_t spi_read_write_byte(uint8_t dat);
uint16_t W25Q128_readID(void);
void W25Q128_write_enable(void);
void W25Q128_wait_busy(void);
void W25Q128_erase_sector(uint32_t addr);
void W25Q128_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte);
void W25Q128_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
在 main.c 中编写如下代码:
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-08-02 LCKFB-LP first version
*/
#include "board.h"
#include "bsp_uart.h"
#include <stdio.h>
#include "spi_flash.h"
#include <string.h>
int main(void)
{
board_init();
uart1_init(115200U);
/* SPI初始化 */
bsp_spi_init();
// 缓存区数组定义
unsigned char buff[20] = {0};
printf("\r\n=========【开始】========\r\n");
// 擦除Flash扇区0
printf("\r\n【1】擦除Flash扇区0......\r\n");
W25Q128_erase_sector(0);
printf("Flash扇区0擦除完成!!\r\n");
delay_ms(200);
//获取W25Q128的设备ID
printf("\r\n【2】读取设备的id......\r\n");
printf("设备ID = %X\r\n",W25Q128_readID());
//读取0地址长度为10个字节的数据到buff
printf("\r\n【3】读取0地址长度为10个字节的数据到buff......\r\n");
W25Q128_read(buff, 0, 10);
//输出读取到的数据
printf("读取到的数据= %s\r\n",buff);
delay_ms(200);
//往0地址写入10个字节的数据 “立创开发板”
printf("\r\n【4】往0地址写入10个字节的数据 “立创开发板”......\r\n");
W25Q128_write((uint8_t *)"立创开发板", 0, 10);
// 等待写入完成
delay_ms(200);
printf("数据写入成功!\r\n");
//读取0地址长度为10个字节的数据到buff
printf("\r\n【5】读取0地址长度为10个字节的数据到buff......\r\n");
W25Q128_read(buff, 0, 10);
//输出读取到的数据
printf("读取到的数据= %s\r\n",buff);
delay_ms(1000);
// 因为是从0地址写的,所以擦除第0个扇区
printf("\r\n【6】功能测试完毕擦除写入的数据......\r\n");
W25Q128_erase_sector(0);
delay_ms(200);
// 清除缓存区
memset(buff,0,sizeof(buff));
printf("\r\n=========【结束】========\r\n");
while(1)
{
delay_ms(100);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
验证现象:验证读写功能。
读取到 ID 为 EF17;
写入数据后,再读出数据为 立创开发板;
这一章节的代码
在开发板介绍百度网盘链接中:立创·梁山派·天空星STM32F407VET6开发板资料/第03章软件资料/代码例程/013硬件SPI(flash)。
软件 SPI FLASH 验证
创建两个文件,分别命名为 spi_flash.c 和 spi_flash.h。往里面写入完整代码:
spi_flash.c
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-08-02 LCKFB-LP first version
*/
#include "spi_flash.h"
#include "board.h"
/**********************************************************
* 函 数 名 称:bsp_spi_init
* 函 数 功 能:初始化SPI
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void bsp_spi_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure1;
GPIO_InitTypeDef GPIO_InitStructure2;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitStructure1.GPIO_Pin = BSP_SPI_NSS|BSP_SPI_SCK|BSP_SPI_MOSI;
GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_OUT ; // 推挽输出
GPIO_InitStructure1.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(BSP_GPIO_PORT, &GPIO_InitStructure1);
GPIO_InitStructure2.GPIO_Pin = BSP_SPI_MISO;
GPIO_InitStructure2.GPIO_Mode = GPIO_Mode_IN ; // 输入模式
GPIO_Init(BSP_GPIO_PORT, &GPIO_InitStructure2);
W25QXX_CS_ON(1); // 片选拉高
W25QXX_SCK_ON(1); // 时钟拉高
}
uint8_t spi_read_write_byte(uint8_t dat)
{
uint8_t i;
uint8_t rxData = 0;
for(i = 0; i < 8; i++)
{
W25QXX_SCK_ON(0);
delay_us(1);
//数据发送
if(dat & 0x80)
{
W25QXX_MOSI_ON(1);
}
else
{
W25QXX_MOSI_ON(0);
}
dat <<= 1;
delay_us(1);
W25QXX_SCK_ON(1);
delay_us(1);
//数据接收
rxData <<= 1;
if( W25QXX_MISO_ON() )
{
rxData |= 0x01;
}
delay_us(1);
}
W25QXX_SCK_ON(0);
return rxData;
}
//读取芯片ID
//读取设备ID
uint16_t W25Q128_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;
}
//发送写使能
void W25Q128_write_enable(void)
{
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令06h
spi_read_write_byte(0x06);
//拉高CS端为高电平
W25QXX_CS_ON(1);
}
/**********************************************************
* 函 数 名 称:W25Q128_wait_busy
* 函 数 功 能:检测线路是否繁忙
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q128_wait_busy(void)
{
unsigned char byte = 0;
do
{
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令05h
spi_read_write_byte(0x05);
//接收状态寄存器值
byte = spi_read_write_byte(0Xff);
//恢复CS端为高电平
W25QXX_CS_ON(1);
//判断BUSY位是否为1 如果为1说明在忙,重新读写BUSY位直到为0
}while( ( byte & 0x01 ) == 1 );
}
/**********************************************************
* 函 数 名 称:W25Q128_erase_sector
* 函 数 功 能:擦除一个扇区
* 传 入 参 数:addr=擦除的扇区号
* 函 数 返 回:无
* 作 者:LC
* 备 注:addr=擦除的扇区号,范围=0~4096。
W25Q128将16M的容量分为256个块(Block),每个块大小为64K(64000)个字节,每个块又分为16个扇区(Sector),每个扇区4K个字节。
W25Q128的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。
**********************************************************/
void W25Q128_erase_sector(uint32_t addr)
{
//计算扇区号,一个扇区4KB=4096
addr *= 4096;
W25Q128_write_enable(); //写使能
W25Q128_wait_busy(); //判断忙,如果忙则一直等待
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令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端为高电平
W25QXX_CS_ON(1);
//等待擦除完成
W25Q128_wait_busy();
}
/**********************************************************
* 函 数 名 称:W25Q128_write
* 函 数 功 能:写数据到W25Q128进行保存
* 传 入 参 数:buffer=写入的数据内容 addr=写入地址 numbyte=写入数据的长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q128_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte)
{
unsigned int i = 0;
//擦除扇区数据
W25Q128_erase_sector(addr/4096);
//写使能
W25Q128_write_enable();
//忙检测
W25Q128_wait_busy();
//写入数据
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令02h
spi_read_write_byte(0x02);
//发送写入的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);
//根据写入的字节长度连续写入数据buffer
for(i=0;i<numbyte;i++)
{
spi_read_write_byte(buffer[i]);
}
//恢复CS端为高电平
W25QXX_CS_ON(1);
//忙检测
W25Q128_wait_busy();
}
/**********************************************************
* 函 数 名 称:W25Q128_read
* 函 数 功 能:读取W25Q128的数据
* 传 入 参 数:buffer=读出数据的保存地址 read_addr=读取地址 read_length=读去长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q128_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length)
{
uint16_t i;
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令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端为高电平
W25QXX_CS_ON(1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
spi_flash.h
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-08-02 LCKFB-LP first version
*/
#ifndef __SPI_FLASH_H__
#define __SPI_FLASH_H__
#include "stm32f4xx.h"
#define BSP_GPIO_RCU RCC_AHB1Periph_GPIOA // GPIO时钟
#define BSP_GPIO_PORT GPIOA // 端口
#define BSP_SPI_NSS GPIO_Pin_4 // CS
#define BSP_SPI_SCK GPIO_Pin_5 // SCK
#define BSP_SPI_MISO GPIO_Pin_6 // MISO
#define BSP_SPI_MOSI GPIO_Pin_7 // MOSI
#define W25QXX_SCK_ON(x) GPIO_WriteBit(BSP_GPIO_PORT, BSP_SPI_SCK, x ? Bit_SET : Bit_RESET)
#define W25QXX_MOSI_ON(x) GPIO_WriteBit(BSP_GPIO_PORT, BSP_SPI_MOSI, x ? Bit_SET : Bit_RESET)
#define W25QXX_CS_ON(x) GPIO_WriteBit(BSP_GPIO_PORT, BSP_SPI_NSS, x ? Bit_SET : Bit_RESET)
#define W25QXX_MISO_ON() GPIO_ReadInputDataBit(BSP_GPIO_PORT, BSP_SPI_MISO)
void bsp_spi_init(void);
uint8_t spi_read_write_byte(uint8_t dat);
uint16_t W25Q128_readID(void);
void W25Q128_write_enable(void);
void W25Q128_wait_busy(void);
void W25Q128_erase_sector(uint32_t addr);
void W25Q128_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte);
void W25Q128_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
在 main.c 中编写如下代码:
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-08-02 LCKFB-LP first version
*/
#include "board.h"
#include "bsp_uart.h"
#include <stdio.h>
#include "spi_flash.h"
#include <string.h>
int main(void)
{
board_init();
uart1_init(115200U);
/* SPI初始化 */
bsp_spi_init();
// 缓存区数组定义
unsigned char buff[20] = {0};
printf("\r\n=========【开始】========\r\n");
// 擦除Flash扇区0
printf("\r\n【1】擦除Flash扇区0......\r\n");
W25Q128_erase_sector(0);
printf("Flash扇区0擦除完成!!\r\n");
delay_ms(200);
//获取W25Q128的设备ID
printf("\r\n【2】读取设备的id......\r\n");
printf("设备ID = %X\r\n",W25Q128_readID());
//读取0地址长度为10个字节的数据到buff
printf("\r\n【3】读取0地址长度为10个字节的数据到buff......\r\n");
W25Q128_read(buff, 0, 10);
//输出读取到的数据
printf("读取到的数据= %s\r\n",buff);
delay_ms(200);
//往0地址写入10个字节的数据 “立创开发板”
printf("\r\n【4】往0地址写入10个字节的数据 “立创开发板”......\r\n");
W25Q128_write((uint8_t *)"立创开发板", 0, 10);
// 等待写入完成
delay_ms(200);
printf("数据写入成功!\r\n");
//读取0地址长度为10个字节的数据到buff
printf("\r\n【5】读取0地址长度为10个字节的数据到buff......\r\n");
W25Q128_read(buff, 0, 10);
//输出读取到的数据
printf("读取到的数据= %s\r\n",buff);
delay_ms(1000);
// 因为是从0地址写的,所以擦除第0个扇区
printf("\r\n【6】功能测试完毕擦除写入的数据......\r\n");
W25Q128_erase_sector(0);
delay_ms(200);
// 清除缓存区
memset(buff,0,sizeof(buff));
printf("\r\n=========【结束】========\r\n");
while(1)
{
delay_ms(100);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
验证现象:验证读写功能。
读取到 ID 为 EF17;
写入数据后,再读出数据为 立创开发板;
这一章节的代码
在开发板介绍百度网盘链接中:立创·梁山派·天空星STM32F407VET6开发板资料/第03章软件资料/代码例程/014软件SPI(flash)。