之前在项目中调过的SPI FLASH的型号有M25P64、GD25Q64、以及W25Q64等......
今天就来简单介绍以下SPI读写串行FLASH
兆易创新的GD25Q64手册在网上还不好找,然后我记得手机里面有M25P64的手册,找了好久,找到是找到了,但是过期了~
害!那咱们还是以最简单的W25Q64来讲解吧
本节呢,还是先看手册~这个手册网上害挺多的,大家可以自行下载哈,或者看我翻译的也行~
一、W25Q64简介
1.1 特点
W25Q64,是一种NOR FLASH。容量为64M bit = 8M Byte(8M 字节),实际上将8M字节分成0~127个block(块),每个块都64字节块,块又细分位0-15Sector(扇区),每个扇区大小为4k。
支持高达80MHz的时钟频率,可以真正的支持XIP。
灵活的架构,具有4KB扇区,
擦除选项:
统一扇区擦除:可以统一擦除每个包含4K字节数据的扇区。
块擦除:芯片支持块擦除,可选择32K和64K字节的块。
编程能力:
该芯片允许一次性编程从1到256字节,提供了数据存储的粒度。
1.2 引脚定义
1. IO0 和 IO1 用于标准和双 SPI 指令 2. IO0 – IO3 用于四线 SPI 指令
引脚具体定义如下:
8.1 封装类型 W25Q64BV提供8引脚塑料208毫米宽SOIC(封装代码SS)和8x6毫米WSON(封装代码ZE),如图1a和1b所示。另一种封装选择是300毫米宽的8引脚PDIP(图1c)。W25Q64BV还提供16引脚塑料300毫米宽SOIC(封装代码SF),如图1d所示。封装的图表和尺寸在本数据表的末尾进行了说明。
8.2 片选信号(/CS) SPI芯片选择(/CS)引脚用于启用和禁用设备操作。当/CS为高电平时,设备未选择,串行数据输出(DO,或IO0,IO1,IO2,IO3)引脚处于高阻抗状态。未选择时,设备的功耗将保持在待机水平,除非正在进行内部擦除、编程或状态寄存器周期。当/CS拉低时,设备将被选择,功耗将增加到活动水平,可以向设备写入指令并从设备读取数据。上电后,必须在接受新指令之前将/CS从高电平转变为低电平。/CS输入必须在上电时跟踪VCC供电电平(参见“写保护”和图31)。如果需要,可以使用/CS上的上拉电阻来实现这一点。
8.3 串行数据输入、输出和IO(DI、DO和IO0、IO1、IO2、IO3) W25Q64BV支持标准SPI、双SPI和四线SPI操作。标准SPI指令使用单向DI(输入)引脚,在串行时钟(CLK)输入引脚的上升沿将指令、地址或数据串行写入设备。标准SPI还使用单向DO(输出)在CLK下降沿时从设备读取数据或状态。
双SPI和四线SPI指令使用双向IO引脚,在CLK上升沿时串行写入指令、地址或数据到设备,并在CLK下降沿时从设备读取数据或状态。四线SPI指令要求在状态寄存器-2中设置的非易失性四线使能位(QE)为1。当QE=1时,/WP引脚变为IO2,/HOLD引脚变为IO3。
8.4 写保护(/WP) 写保护(/WP)引脚可用于防止写入状态寄存器。与状态寄存器的块保护(SEC、TB、BP2、BP1和BP0)位以及状态寄存器保护(SRP)位一起使用,可以硬件保护部分或整个存储器数组。/WP引脚为低电平有效。当状态寄存器-2的QE位设置为四线I/O时,由于该引脚用于IO2,因此不可用硬件写保护(Hardware Write Protect)功能。请参见图1a、1b、1c和1d以获取四线I/O操作的引脚配置。
8.5 暂停(/HOLD) /HOLD引脚允许在设备被主动选择时暂停设备。当/HOLD拉低时,当/CS为低时,DO引脚将处于高阻抗状态,DI和CLK引脚上的信号将被忽略(不关心)。当/HOLD被拉高时,设备操作可以恢复。当多个设备共享相同的SPI信号时,/HOLD功能可能很有用。/HOLD引脚为低电平有效。当状态寄存器-2的QE位设置为四线I/O时,由于该引脚用于IO3,因此/HOLD引脚功能不可用。请参见图1a-d以获取四线I/O操作的引脚配置。
8.6 串行时钟(CLK) SPI串行时钟输入(CLK)引脚提供串行输入和输出操作的定时。("具体请参阅SPI操作")
1.3 常见电路解法:
写保护和hold都是低电平有效,我们这里不使用,就直接接高电平,将写保护关闭,芯片的输出一定要接STM32的输入。
1.4 存储器框图
注意:FLASH的存储特性如下:
- 在写入数据之前,必须先擦除,
- 擦除时会把所有的数据全部重置为1
- 写入数据之前必须把1的数据位改为0
- 擦除时,必须按照最小单位来擦除(最小的擦除单位一般就是扇区,也就是说4KB,具体得看芯片数据手册)
NOR FLASH可以一个字节一个字节的读写(所以能支持XIP);NAND FLASH必须以块或扇区为单位进行读写;
1.5 状态寄存器
11.1.1 BUSY 是状态寄存器(S0)中的只读位,当设备执行页面编程、扇区擦除、块擦除、芯片擦除或写状态寄存器指令时,该位设置为1。在此期间,设备将忽略除了读状态寄存器和擦除暂停指令之外的任何其他指令(请参阅AC特性中的tW、tPP、tSE、tBE和tCE)。当程序、擦除或写状态寄存器指令完成时,BUSY位将被清零为0,表示设备已准备好接受进一步的指令。
11.1.2 写使能锁存器(WEL) 是状态寄存器(S1)中的只读位,在执行写使能指令后被设置为1。当设备被禁止写入时,WEL状态位被清零为0。写禁止状态在上电后或执行以下任何指令后发生:写禁止、页面编程、扇区擦除、块擦除、芯片擦除和写状态寄存器。
11.1.3 块保护位(BP2、BP1、BP0)是状态寄存器(S4、S3 和 S2)中的非易失性读/写位,提供写保护的控制和状态。块保护位可以使用写状态寄存器指令进行设置(请参阅AC特性中的tW)。可以使用块保护位保护整个、部分或不保护内存数组免受编程和擦除指令的影响(请参阅状态寄存器内存保护表)。块保护位的出厂默认设置为0,即数组的任何部分都未受保护。
读出的结果如下,这样我们就知道总线忙不忙了(即内部时序是否完成)
STM32通过SPI接口给flash发送指令集(一些命令),比如想读取状态寄存器,就给flash发命令,这样flash就会给我们回复状态了。
1.6 指令集
11.2 指令 W25Q64BV的指令集包括二十七个基本指令,通过SPI总线完全受控制(请参阅指令集表)。指令由芯片选择(/CS)的下降沿引发。输入到DI的第一个数据字节提供指令代码。DI输入上升沿时取样数据,最高有效位(MSB)首先。
指令的长度从单个字节到多个字节不等,可能后跟地址字节、数据字节、虚拟字节(不关心)以及在某些情况下是这些的组合。指令完成于芯片选择(/CS)的上升沿。每个指令的时钟相对时序图包含在图4到30中。所有读取指令可以在任何时钟位之后完成。然而,所有写入、编程或擦除的指令必须在字节边界上完成(在完整的8位被时钟后,/CS被拉高),否则指令将被终止。这一特性进一步保护设备免受意外写入的影响。此外,在内存被编程或擦除时,或者在写入状态寄存器时,所有指令都将被忽略,直到编程或擦除周期完成。
带括号的数据是从flash传输给STM32控制器的,没带括号的这些字节内容就是由控制器传输给flash的。
第一个字节都是控制器通过SPI接口发送给flash的。比如我们想知道状态寄存器的值,那么就给flash发送05,flash收到这个命令代码之后,就会在flash通过SPI通讯的第二个字节,酒吧状态寄存器的值,通过SPI返回给微控器STM32了。
下面可以结合时序去详细说明
本质上来说它都是通过SPI协议向flash发送数据,或者从flash读取数据。但是它规定了一些特殊的时序,比如第一个字节,假如我们通过SPI协议,第一个字节发送0X05,通过DO引脚,就相当于发送了一个读取状态寄存器的命令了,flash接收到这个数据之后,就会认为这是一个读取状态寄存器的命令,对于我们的微控器而言,它就是一个普通的数据而已,那么flash就会根据约定返回这个寄存器的值,通过D0引脚在第二个字节,给微控器返回一个字节(后面是重复发送,而我们只要读一次就可以了)。如果我们没有结束通讯的话,它会一直返回这个状态寄存器的值,那么我们的微控器接收到这个字节之后,直接读取它的最低位,就知道它的BUSY位是否为1了,这样就能确认FLASH是否处于忙的状态了。
对于flash来说,还有很多指令集,比如说Block Erase(64KB) 还有Sector Erase(4KB),这个用的比较多,我们此处可以介绍一下:
在写入数据之前,必须对flash进行擦除的操作,擦除的时候,又以扇区为单位,每一次擦除的时候都会擦除4KB。这个命令的代码是0x20
比如我们向flash发送0x20,后面三个字节是地址,flash的地址是24位,所以我们要通过三个字节来传输要擦除的地址,flash在接收到这个命令和地址之后,就会对相应地址进行擦除,擦除之后,我们又可以读取状态寄存器来了解是否擦除完成,擦除完成那么我们就可以对其进行写入数据了。
写入数据的代码是0x02
比如我们先给flash 发送0x02命令,发送完命令,接着是地址,后面紧接着是数据。
读数据的命令是0X03,那么我们先发送03,接着是地址,地址发送完成,flash会给微控器返回数据。
还有几个比较重要的如下:
Dummy就是空白字节(可以是任意数据)
先发送0x90 然后dummy dummy 第四个字节是00 然后第五个字节flash就会返回EF啊 16h这些值。
通过读取这些值,判断flash是否可以连接正确。
Flash的ID号如下:(每次可以先读取ID号,看是否跟4017的值相等,我们的微控器跟flash是否能正常通讯)
POWER down,让其进去低功耗模式
将其从低功耗模式唤醒,flash也会返回一个ID号
1.7 读取时序
下面来对时序进行分析,就拿Read Data举例说明吧
命令是0x03,再发送24位地址(需要读取的三个字节的地址) ,然后flash就会一直返回数据,返回的数据是没有限制的,一直返回到数据结束为止。(发送接收是严格分开的,大家注意时序哈)
Fast Read的时序跟这个没区别,就是比我们普通的读取速度要快。
接下来是写入
写入 命令代码为0x02,然后写入命令代码之后,然后发送24位地址,以及要写入的数据(写入都是这些都是通过DI引脚的)
一次最大可以写256字节。
擦除的话,是需要以4096为最小单位进行擦除的(Sector 4 KB),发送地址一定要发送对应Sector的最低地址。(严格要求以4096对齐)。
二、硬件设计
写代码之前注意一定要对应硬件原理图哈,SPI 串行FLASH 硬件连接图如下:
FLASH 芯片(型号:W25Q64)是一种使用SPI 通讯协议的NOR FLASH存
储器, 它的CS/CLK/DIO/DO 引脚分别连接到了STM32 对应的SPI 引脚
NSS/SCK/MOSI/MISO 上,其中STM32 的NSS 引脚虽然是其片上SPI 外设的硬件引脚,但
实际上后面的程序只是把它当成一个普通的GPIO,使用软件的方式控制NSS 信号,所以
在SPI 的硬件设计中,NSS 可以随便选择普通的GPIO,不必纠结于选择硬件NSS 信号。
FLASH 芯片中还有WP 和HOLD 引脚。WP 引脚可控制写保护功能,当该引脚为低电
平时,禁止写入数据。我们直接接电源,不使用写保护功能。HOLD 引脚可用于暂停通讯,
该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。
我们直接接电源,不使用通讯暂停功能。
关于FLASH 芯片的更多信息,可参考其数据手册《W25Q64》来了解。若您使用的实
验板FLASH 的型号或控制引脚不一样,只需根据提供的工程修改即可,程序的控制原理相
同。
三、软件设计
3.1 编程要点
- 初始化通讯使用的目标引脚以及端口时钟
- 使能SPI外设时钟
- 配置SPI的外设模式、地址、速率等参数并使能SPI外设。
- 编写基本SPI 按字节收发的函数;
- 编写对FLASH 擦除及读写操作的的函数;
- 编写测试程序,对读写数据进行校验。
3.2. 代码分析
SPI 硬件相关宏定义,把SPI 硬件相关的配置都以宏的形式定义到 “bsp_spi_ flash.h”文件中
先上传个初版的程序,功能我测了,亲测可用
bsp_spi_ flash.h如下:
#ifndef __SPI_FLASH_H
#define __SPI_FLASH_H
#include "stm32f10x.h"
/**********************************SPI参数定义**********************************/
#define FLASH_SPIx SPI1
#define FLASH_SPI_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_CLK RCC_APB2Periph_SPI1
#define FLASH_SPI_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_GPIO_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_SCK_PORT GPIOA
#define FLASH_SPI_SCK_PIN GPIO_Pin_5
#define FLASH_SPI_MISO_PORT GPIOA
#define FLASH_SPI_MISO_PIN GPIO_Pin_6
#define FLASH_SPI_MOSI_PORT GPIOA
#define FLASH_SPI_MOSI_PIN GPIO_Pin_7
#define FLASH_SPI_CS_PORT GPIOA
#define FLASH_SPI_CS_PIN GPIO_Pin_4
/*CS 引脚配置*/
#define FLASH_SPI_CS_HIGH GPIO_SetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_PIN);
#define FLASH_SPI_CS_LOW GPIO_ResetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_PIN);
/*等待超时时间*/
#define SPIT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
/*信息输出*/
#define FLASH_DEBUG_ON 0
#define FLASH_INFO(fmt,arg...) printf("<<-FLASH-INFO->> "fmt"\n",##arg)
#define FLASH_ERROR(fmt,arg...) printf("<<-FLASH-ERROR->> "fmt"\n",##arg)
#define FLASH_DEBUG(fmt,arg...) do{\
if(FLASH_DEBUG_ON)\
printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
#define DUMMY 0x00
#define READ_JEDEC_ID 0x9f
#define EARSE_SECTOR 0x20
#define READ_STATUE 0x05
#define READ_DATA 0x03
#define WRITE_ENABLE 0x06
#define WRITE_DATA 0x02
void SPI_FLASH_Init(void) ;
uint32_t SPI_Read_ID(void);
void SPI_Erase_Sector(uint32_t addr);
void SPI_WaitForWriteEnd(void );
void SPI_Read_data(uint32_t addr,uint8_t *readBuff,uint32_t numByteToRead);
void SPI_Write_data(uint32_t addr,uint8_t *writeBuff,uint32_t numByteToWrite);
#endif /* __SPI_FLASH_H */
bsp_spi_flash.c
/**
******************************************************************************
* @file bsp_i2c_ee.c
* @author STMicroelectronics
* @version V1.0
* @date 2013-xx-xx
* @brief SPI FLASH(W25Q64)应用函数bsp
******************************************************************************
* @attention
******************************************************************************
*/
#include "./flash/bsp_spi_flash.h"
#include "./usart/bsp_usart.h"
uint16_t EEPROM_ADDRESS;
static __IO uint32_t SPITimeout = SPIT_FLAG_TIMEOUT;
static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);
/**
* @brief SPI I/O配置
* @param 无
* @retval 无
*/
static void SPI_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能与 SPI 有关的时钟 */
FLASH_SPI_APBxClock_FUN ( RCC_APB2Periph_SPI1, ENABLE );
FLASH_SPI_GPIO_APBxClock_FUN ( RCC_APB2Periph_GPIOA, ENABLE );
/* MISO、MOSI、SCK*/
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 推挽复用输出
GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 推挽复用输出
GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入或带上拉输入
GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);
/* 初始化CS引脚,使用软件控制,所以直接设置成推挽输出*/
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);
FLASH_SPI_CS_HIGH;
}
/**
* @brief SPI 工作模式配置
* @param 无
* @retval 无
*/
static void SPI_Mode_Configu(void)
{
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2 ;//波特率最高支持80M stm2分配也才36M
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //模式0 和模式3都可以,此处配置成模式3 俩都配置成1
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CRCPolynomial = 0; //不使用CRC校验,数值随便写
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双线全双工
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//MSB先行
SPI_InitStructure.SPI_Mode = SPI_Mode_Master ;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_Init(SPI1, &SPI_InitStructure);//写入配置寄存器
SPI_Cmd(SPI1,ENABLE);
}
/**
* @brief SPI 外设(FLASH)初始化
* @param 无
* @retval 无
*/
void SPI_FLASH_Init(void)
{
SPI_GPIO_Config();
SPI_Mode_Configu();
}
/*向flash 发送并接收一个字节 发送接收是同步的*/
/* 想要发送或者接收一个字节的时候,都可以直接调用这个函数*/
uint8_t SPI_FLASH_Send_Byte(uint8_t data)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
//检查并等待至TX缓冲区为空
while(SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE) == RESET)
{
if( 0 == (SPITimeout--)) return SPI_TIMEOUT_UserCallback(0);
}
//程序执行到此处,TX缓冲区为空
SPI_I2S_SendData (SPI1, data) ;
/* 什么时候能确认已经发送完毕呢 检测一个标志8位数据都已经发送完了 */
//检查并等待RX缓冲区非空
SPITimeout = SPIT_FLAG_TIMEOUT;
while(SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE) == RESET)
{
if( 0 == (SPITimeout--)) return SPI_TIMEOUT_UserCallback(1);
}
//程序执行到此处,说明数据发送完毕,并接收到一字 字节
return SPI_I2S_ReceiveData( SPI1 ) ;
}
//接收数据会忽略发送线上的数据
uint8_t SPI_FLASH_Read_Byte(void)
{
return SPI_FLASH_Send_Byte(DUMMY);
}
//读取ID号
uint32_t SPI_Read_ID(void)
{
uint32_t flash_id;
//片选使能
FLASH_SPI_CS_LOW;
SPI_FLASH_Send_Byte(READ_JEDEC_ID);
flash_id = SPI_FLASH_Send_Byte(DUMMY);
flash_id <<= 8;
flash_id |= SPI_FLASH_Send_Byte(DUMMY);
flash_id <<= 8;
flash_id |= SPI_FLASH_Send_Byte(DUMMY);
FLASH_SPI_CS_HIGH;
return flash_id;
}
//FLASH写入使能
void SPI_Write_Enable(void)
{
FLASH_SPI_CS_LOW;
SPI_FLASH_Send_Byte(WRITE_ENABLE);
FLASH_SPI_CS_HIGH;
}
//擦除FLASH指定扇区
void SPI_Erase_Sector(uint32_t addr)
{
SPI_Write_Enable();
FLASH_SPI_CS_LOW;
SPI_FLASH_Send_Byte(EARSE_SECTOR);
SPI_FLASH_Send_Byte((addr>>16)&0xff);
SPI_FLASH_Send_Byte((addr>>8)&0xff);
SPI_FLASH_Send_Byte(addr&0xff);
FLASH_SPI_CS_HIGH;
SPI_WaitForWriteEnd();//擦除和写入都是需要等待内部时序的
}
//读取FLASH的指定扇区
void SPI_Read_data(uint32_t addr,uint8_t *readBuff,uint32_t numByteToRead)
{
FLASH_SPI_CS_LOW;
SPI_FLASH_Send_Byte(READ_DATA);
SPI_FLASH_Send_Byte((addr>>16)&0xff);
SPI_FLASH_Send_Byte((addr>>8)&0xff);
SPI_FLASH_Send_Byte(addr&0xff);
while(numByteToRead--)
{
*readBuff = SPI_FLASH_Send_Byte(DUMMY);
readBuff++;
}
FLASH_SPI_CS_HIGH;
}
//向FLASH写入内容 最多之前写256 写之前必须保证写使能
void SPI_Write_data(uint32_t addr,uint8_t *writeBuff,uint32_t numByteToWrite)
{
SPI_Write_Enable();
FLASH_SPI_CS_LOW;
SPI_FLASH_Send_Byte(WRITE_DATA);
SPI_FLASH_Send_Byte((addr>>16)&0xff);
SPI_FLASH_Send_Byte((addr>>8)&0xff);
SPI_FLASH_Send_Byte(addr&0xff);
while(numByteToWrite--)
{
SPI_FLASH_Send_Byte(*writeBuff);
writeBuff++;
}
FLASH_SPI_CS_HIGH;
SPI_WaitForWriteEnd();//擦除和写入都是需要等待内部时序的
}
//等待FLASH 内部时序操作完成
void SPI_WaitForWriteEnd(void )
{
uint8_t status_reg = 0;
FLASH_SPI_CS_LOW;
SPI_FLASH_Send_Byte(READ_STATUE);
do{
status_reg = SPI_FLASH_Send_Byte(DUMMY);
}
while((status_reg & 0x01) == 1);
FLASH_SPI_CS_HIGH;
}
/**
* @brief Basic management of the timeout situation.
* @param errorCode:错误代码,可以用来定位是哪个环节出错.
* @retval 返回0,表示SPI读取失败.
*/
static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
/* Block communication and all processes */
FLASH_ERROR("SPI 等待超时!errorCode = %d",errorCode);
return 0;
}
/*********************************************END OF FILE**********************/
main.c
/**
******************************************************************************
* @file main.c
* @author fire
* @version V1.0
* @date 2013-xx-xx
* @brief SPI FLASH(W25Q64)测试,测试信息通过USART1打印在电脑的超级终端
******************************************************************************
* @attention
******************************************************************************
*/
#include "stm32f10x.h"
#include "./led/bsp_led.h"
#include "./usart/bsp_usart.h"
#include "./flash/bsp_spi_flash.h"
#include <string.h>
uint8_t readBuff[4096] = {0};//定义成全局变量 就不会存储在栈空间了 而是内存上
uint8_t writeBuff[4096] = {0};
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
uint32_t id;
uint16_t i = 0;
LED_GPIO_Config();
LED_BLUE;
/* 串口初始化 */
USART_Config();
printf("\r\n 这是一个SPI-FLASH读写测试例程 \r\n");
SPI_FLASH_Init();
id = SPI_Read_ID();
printf("\r\n id =0x%x \r\n",id);
//如果你的flash本身就是干净的,那么就没办法通过此方法校验了
SPI_Erase_Sector(0);
for(i = 0;i < 4096;i++)
{
writeBuff[i] = i;
}
//假如不擦除 直接写的话,就会把1保留 0的一些地方会改为1
SPI_Write_data(0,writeBuff,256);
SPI_Read_data(0,readBuff,4096);
for(i = 0;i < 4096 ;i++)
{
printf("0x%x ",readBuff[i]);
if(i%10 == 0)
printf("\r\n");
}
//确认测试完ID之后 再去写后面的擦除/读写FLASH 不然后面调试起来很麻烦的
while (1)
{
}
}
/*********************************************END OF FILE**********************/
初版代码就先这样啦,有需要的同学可以留言或者私信我~懒得上传工程,如果哪天上传了,我会到时候把链接附在这的~这个章节就暂时先告一段落啦~