前言
目前正在学习STM32单片机的基础知识,通过库函数实现想要的一些功能。这篇文章主要介绍的是片外的Flash的操作,Flash的型号是W25Q16DV(芯片介绍在后面),通信的方式是SPI通信。通过ST提供的库函数,实现对SPI通信的简单封装。实现Flash的驱动程序,包括驱动的初始化、写使能、读操作、写操作、擦除操作等功能。在做比如FLASH、SPI等介绍时,都是只讲一点,这篇博文主要是代码的实现。相关知识建议自行搜索学习,我就不班门弄斧,我只做抛砖引玉。
SPI简介
SPI的控制模式
首先要牢记,在使用SPI的时候,至少需要一个主机和一个从机。但是也可以一个主机多个从机,但是不可以多主机多从机或者多主机一从机。这里说的从机和主机,其实在实际应用中多表现为MCU是主机,其他的芯片,例如ADC转换芯片、FLASH芯片、EEPROM芯片为从机。
接着需要知道的是,SPI在通信的时候,因为是同步通信,所以需要一个时钟信号,这个时钟信号是由主机提供的。
然后需要知道从机其实在通信的时候是处于被动的模式,当主机需要通信时会通过CS(片选信号线)选择一个芯片(唯一),然后由主机产生时钟信号。
最后需要知道SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。
SPI的传输模式
SPI存在四种传输模式,这四种传输模式是由SPI的时钟极性和时钟相位组成的,简单的理解就是:
时钟极性(CKP)是时钟信号线在空闲时处于什么电平。
时钟相位(CKE)是一个时钟周期会有2个跳变沿,而相位直接决定SPI总线从那个跳变沿开始采集数据。
SPI的信号线
在SPI通信的过程中,使用到了4根线,分别是CS、SCLK、MOSI、MISO。
CS:片选信号线,在主机需要和那个从机进行通信时,会把相应从机的CS片选信号线发送高电平或者低电平。这个需要看具体的芯片而定,一般情况下是发送低电平选择从机。
SCLK:时钟信号线,时钟信号线非常重要,在整个通信过程中都会由主机产生时钟信号。
MOSI:主设备数据输出信号线,在通信时,主设备会通过这条信号线持续的发送信号,具体发送的信号内容需要根据芯片手册而定。
MISO:主设备数据输入信号线,(假设我们在初始化SPI的时候选择的时全双工的工作模式)那么每一个时钟周期,主机在发送数据的同时,也会由这个输入信号线接收到一个BIT的数据。
SPI工作模式【参考博文:SPI几种工作模式简介】
双线双向全双工模式
双线单向只收模式
单线双向半双工发送模式
单线双向半双工接收模式
这里SPI只是做了很基础的介绍,具体详细的内容,比如四种模式的波形、通信的过程等,还需要读者自行查阅。我觉的学习的过程就是你不断地自行找资料、一点点学习的过程。望海涵。
Flash简介
了解Flash之前可以先大致看看经常伴随Flash出现的另一种存储器EEPROM。
EEPROM的全称是“电可擦除可编程只读存储器,即Electrically Erasable Programmable Read-Only Memory。是相对于紫外擦除的ROM来讲的。在一开始,ROM是不能编程的,出厂什么内容就永远什么内容,不灵活。后来呢,出现了PROM,可以自己写入一次,但要是写错了,只能换一片。可是人类的科技是一直都在进步的,终于出现了可多次擦除写入的EPROM,每次擦除要把芯片拿到紫外线上照一下。可是依旧不灵活方便,想一下你往单片机上下了一个程序之后发现有个地方需要加一句话,为此你要把单片机放紫外灯下照半小时,然后才能再下一次,这么折腾一天也改不了几次。最后伟大的发明,EEPROM出现了,程序猿终于可以随意的修改ROM里面的内容了。
总结:EEPROM可以随机访问和修改任何一个字节,可以往每个bit中写入0或者1。掉电后数据不丢失,可以保存100年,可以擦写100w次。具有较高的可靠性,但是电路复杂/成本也高。因此目前的EEPROM都是几十千字节到几百千字节的,绝少有超过512K的。
大概知道了一下EEPROM的发展史,我们来看看今天的主角FLASH
FLASH属于广义上的EEPROM,因为它也是可带电擦除的ROM。但是为了区别于一般的按字节为单位的擦写的EEPROM,我们都叫它FLASH。FLASH做的改进就是擦除时不再以字节为单位,而是以块为单位,一次简化了电路,数据密度更高,降低了成本。上M的ROM一般都是FLASH。FLASH分为NOR FLASH和NAND FLASH。NOR FLASH数据线和地址线分开,可以实现RAM一样的随机寻址功能,可以读取任何一个字节。但是擦除仍要按块来擦。NAND FLASH同样是按块擦除,但是数据线和地址线复用,不能利用地址线随机寻址。读取只能按页来读取。(NAND FLASH按块来擦除,按页来读,NOR FLASH没有页)
总结:
在读写速度上,由于NAND FLASH引脚上复用,因此读取速度比NOR FLASH慢一点,但是擦除和写入速度比NOR FLASH快很多。
在电路设计上,NAND FLASH内部电路更简单,因此数据密度大,体积小,成本也低。因此大容量的FLASH都是NAND型的。小容量的2~12M的FLASH多是NOR型的。
在使用寿命上,NAND FLASH的擦除次数是NOR的数倍。而且NAND FLASH可以标记坏块,从而使软件跳过坏块。NOR FLASH 一旦损坏便无法再用。
在实际应用上, 因为NOR FLASH可以进行字节寻址,所以程序可以在NOR FLASH中运行。嵌入式系统多用一个小容量的NOR FLASH存储引导代码,用一个大容量的NAND FLASH存放文件系统和内核。
参考博文:yuanlulu-EEPROM和flash的区别
W25Q16DV简介
W25Q16DV是一个FLASH的芯片,其实我也是看芯片手册才知道,这个16代表的意思是16MBit。因为芯片手册篇幅很大,还是英文,在这里我只介绍很小一部分的东西,具体的内容,希望读者自行下载该芯片手册查阅。(插个题外话,大家可以下载一个叫半导小芯的APP,里面可以查到很多芯片,也可以下载相应的数据手册)。
该芯片是一个16Mbit容量的芯片,换算就是16MBit / 8Bit = 2M。1M = 1024K。1K = 1024Byte。1Byte = 8Bit,所以他的容量就是2M * 1024 * 1024 = 2,097,152Byte,换算成16进制就是0X1FFFFF,所以在操作内存的时候,地址不能超过这个。
该芯片由页、扇区、块组成。由芯片手册可以知道,它的一页是256个Byte,也就是在写入的时候,一次最多可以写入256个字节的数据,超过了需要自行在代码中处理。而扇区和块,目前主要我是在擦除的时候用到了,代码实现是按一个扇区擦除的。该芯片规定一个扇区是4K,经过换算一个扇区有16个页;一个块由64K,经过换算一共由32个块,而一个块有16个扇区。
关于读写和擦除的介绍,首先是读(0X03),当主机发送一个读内存的命令之后,芯片就会根据读的起始地址,然后只要产生时钟信号,就会一直往下读,理论上如果产生足够多的时钟信号,一次可以把整个芯片所有的内容读完;接着是擦除(0X20),因为FLASH规定的特性,芯片存储的电平只能从高电平转成低电平,所以每次需要写的时候都需要对相应写的空间地址进行擦除,可以是按扇区或者按块进行擦除(目前实现的是按扇区进行擦除),擦除完之后才可以进行写入;然后是写(0X02),写就是在相应的地址中进行数据的写入,需要注意的是写之前一定对写入的扇区或者块进行擦除;最后需要注意的是,在写,无论是写数据进内存还是写状态寄存器,都需要开启写使能(0X06)。
下面放两张数据手册截的图,可进行参考。(具体细节希望读者可以下载数据手册看)
![466c9c978550427b857de8f60458e286.png](https://img-blog.csdnimg.cn/466c9c978550427b857de8f60458e286.png)
![49c54dd269e444c68c5f47cb778e9b9d.png](https://img-blog.csdnimg.cn/49c54dd269e444c68c5f47cb778e9b9d.png)
程序代码
使用过程中用到的代码
//Std_Types.h
#ifndef STD_TYPES_H
#define STD_RTPES_H
#define E_OK 0x00u
#define E_NOT_OK 0x01u
typedef uint8_t Std_ReturnType;
#endif
SPI相关代码
//SPI_Types.h
#ifndef SPI_TYPES_H
#define SPI_TYPES_H
typedef SPI_TypeDef* Spi_ChannelType;
typedef uint8_t Spi_DataBufferType;
typedef uint16_t Spi_NumberOfDataType;
typedef uint32_t SPI_TimeOutCount;
#endif
//SPI.h
#ifndef SPI_H
#define SPI_H
#include "stm32f10x.h"
#include "stm32f10x_spi.h"
#include "SPI_Types.h"
#include "Std_Types.h"
#define FLASH_CS_CHOOSE_DISABLE() GPIO_SetBits(FLASH_SPI_GPIO_PORT,FLASH_SPI_CS_PIN)
#define FLASH_CS_CHOOSE_ENABLE() GPIO_ResetBits(FLASH_SPI_GPIO_PORT,FLASH_SPI_CS_PIN)
#define SPI_FLAG_TIMEOUT ((uint32_t)0x1000)
#define SPI_FLAG_TXE SPI_I2S_FLAG_TXE //发送标志位
#define SPI_FLAG_RXNE SPI_I2S_FLAG_RXNE //接收标志位
#define FLASH_SPI SPI2
#define FLASH_SPI_CLK RCC_APB1Periph_SPI2
#define FLASH_SPI_GPIO_CLK RCC_APB2Periph_GPIOB
#define FLASH_SPI_GPIO_PORT GPIOB
#define FLASH_SPI_GPIO_SPEED GPIO_Speed_50MHz
#define FLASH_SPI_GPIO_MODE GPIO_Mode_AF_PP
#define FLASH_SPI_GPIO_CS_MODE GPIO_Mode_Out_PP
#define FLASH_SPI_CS_PIN GPIO_Pin_12
#define FLASH_SPI_SCK_PIN GPIO_Pin_13
#define FLASH_SPI_MISO_PIN GPIO_Pin_14
#define FLASH_SPI_MOSI_PIN GPIO_Pin_15
/*
SPI初始化
*/
void spiInit();
/*
先发送再接收才会产生时序,一定要注意!!
否则STM32不会产生时序,产生一下后就停止,只接受到了地址的数据
*/
uint8_t readWriteByte(uint8_t data);
/*
发送和接收数据
*/
Std_ReturnType spiSetupSimple(Spi_ChannelType Channel , const Spi_DataBufferType *SrcDataBufferPtr , Spi_DataBufferType *DesDataBufferPtr , Spi_NumberOfDataType Length);
/*
设置波特率
*/
void setSpiSpeed(uint8_t SPI_BaudRatePrescaler);
#endif
//SPI.c
#include "SPI.h"
void spiInit(){
//使能SPI2和外设连接的GPIO口的时钟
RCC_APB1PeriphClockCmd(FLASH_SPI_CLK , ENABLE);
RCC_APB2PeriphClockCmd(FLASH_SPI_GPIO_CLK , ENABLE);
//初始化GPIOB四个引脚,注意,CS初始化模式是推挽输出
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN | FLASH_SPI_MISO_PIN | FLASH_SPI_MOSI_PIN;
GPIO_InitStructure.GPIO_Speed = FLASH_SPI_GPIO_SPEED;
GPIO_InitStructure.GPIO_Mode = FLASH_SPI_GPIO_MODE;
GPIO_Init(FLASH_SPI_GPIO_PORT , &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN ;
GPIO_InitStructure.GPIO_Mode = FLASH_SPI_GPIO_CS_MODE;
GPIO_Init(FLASH_SPI_GPIO_PORT , &GPIO_InitStructure);
GPIO_SetBits(FLASH_SPI_GPIO_PORT , FLASH_SPI_SCK_PIN | FLASH_SPI_MISO_PIN | FLASH_SPI_MOSI_PIN);
//cs不选
FLASH_CS_CHOOSE_DISABLE();
//初始化SPI2
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//工作模式是(双向还是单向)全双工还是半双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//SPI的模式,主机模式还是从机模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//SPI一次传输数据的大小,8为还是16位
//mode0(0,0) or mode3(1,1) be supported
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//时钟极性,也就是时钟信号在空闲状态的时候是高还是低
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//时钟相位,数据的采集实在第二个跳变沿
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//片选引脚是使用软件还是硬件控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//时钟波特率的设置。目前SPI的是时钟频率是36MHZ,做256预分频
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//MSB or LSB,传输的时候,是高位在前还是低位在前
SPI_InitStructure.SPI_CRCPolynomial = 0;//CRC校验
SPI_Init(FLASH_SPI , &SPI_InitStructure);
//使能SPI2
SPI_Cmd(FLASH_SPI , ENABLE);
//readWriteByte(0xff);//启动传输
}
void setSpiSpeed(uint8_t SPI_BaudRatePrescaler){
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
FLASH_SPI->CR1&=0XFFC7;
FLASH_SPI->CR1|=SPI_BaudRatePrescaler; //设置SPI2速度
SPI_Cmd(FLASH_SPI,ENABLE);
}
Std_ReturnType spiSetupSimple(Spi_ChannelType Channel , const Spi_DataBufferType *SrcDataBufferPtr , Spi_DataBufferType *DesDataBufferPtr , Spi_NumberOfDataType Length){
SPI_TimeOutCount timeOut = 0;
for(Spi_NumberOfDataType index = 0 ; index < Length ; ++index){
timeOut = SPI_FLAG_TIMEOUT;
//首先检查是否发送缓冲是否为空
while(SPI_I2S_GetFlagStatus(Channel , SPI_FLAG_TXE ) == RESET){
if((--timeOut) == 0){
//发送超时处理,暂时不实现
return E_NOT_OK;
}
}
//发送缓冲区为空,那么可以发送数据
SPI_I2S_SendData(Channel , *(SrcDataBufferPtr + index));
while(SPI_I2S_GetFlagStatus(Channel , SPI_FLAG_RXNE ) == RESET){
if((--timeOut) == 0){
//接收超时处理,暂时不实现
return E_NOT_OK;
}
}
*(DesDataBufferPtr+index) = SPI_I2S_ReceiveData(Channel);
}
return E_OK;
}
W25Q16DV相关代码
//CDD_W25Q16_Types.h
#ifndef CDD_W25Q16_TYPES_H
#define CDD_W25Q16_TYPES_H
typedef uint8_t Fls_DataType;
typedef uint16_t Fls_DataType16;
typedef uint8_t Fls_DataBufferType;
typedef uint8_t Fls_NumberOfBufferType;
typedef uint32_t Fls_DataAddr;
#endif
//CDD_W25Q16_Cfg.h
#ifndef CDD_W25Q16_CFG_H
#define CDD_W25Q16_CFG_H
/* 配置参数 */
#define FLASH_BUSY_TIME_OUT 10000
/* 操作指令 */
#define INS_CODE_WRSR 0x01 //写状态寄存器
#define INS_CODE_WRFL 0x02 //写内存数据
#define INS_CODE_RDFL 0x03 //读内存数据
#define INS_CODE_WRDI 0x04 //写禁止
#define INS_CODE_RDSR1 0x05 //读状态寄存器1
#define INS_CODE_RDSR2 0X35 //读状态寄存器2
#define INS_CODE_WREN 0X06 //写使能
#define INS_CODE_ERASE_SECTOR_4 0x20 //按扇区擦除
#define INS_CODE_ERASE_BLOCK_32 0x52 //按块32KB擦除
#define INS_CODE_ERASE_BLOCK_64 0xD8 //按块64KB擦除
#define INS_CODE_DEID 0X9F //获取设备的ID
#define INS_CODE_DEFA 0XFF //默认数据
#endif
//CDD_W25Q16.h
#ifndef CDD_W25Q16_H
#define CDD_W25Q16_H
#include "stm32f10x.h"
#include "SPI.h"
#include "CDD_W25Q16_Cfg.h"
#include "CDD_W25Q16_Types.h"
/*
8bit(位)=1Byte(字节)
1024Byte(字节)=1KB
1024KB=1MB
*/
/****************************************************************************************
* 类型定义
***************************************************************************************/
typedef enum{
fFls_Idle = 0, /* 空闲状态 */
fFls_WRData = 1, /* 正在写数据 */
fFls_WRSR = 2, /* 正在写状态寄存器 */
fFls_RData = 3, /* 正在读数据 */
fFls_RDSR = 4, /* 正在读状态寄存器*/
}f_FlsOpState;
/****************************************************************************************
* 参数定义
***************************************************************************************/
/****************************************************************************************
* 函数声明
***************************************************************************************/
extern void Fls_W25Q16_Init();
extern void Fls_W25Q16_Device_Id();
extern Std_ReturnType Fls_W25Q16_Write_Enable();
extern Std_ReturnType Fls_W25Q16_Write_Disable();
extern Std_ReturnType Fls_W25Q16_Read_SR1(Fls_DataType* pState);
extern Std_ReturnType Fls_W25Q16_Read_SR2(Fls_DataType* pState);
extern Std_ReturnType Fls_W25Q16_Write_SR(const Fls_DataType stateReg1 , const Fls_DataType stateReg2);
extern Std_ReturnType Fls_W25Q16_Write(Fls_DataAddr addr, Fls_NumberOfBufferType len, Fls_DataBufferType *buf);
extern Std_ReturnType Fls_W25Q16_Read(Fls_DataAddr addr, Fls_NumberOfBufferType len , Fls_DataBufferType *buf);
extern Std_ReturnType Fls_W25Q16_Erase_Sector_4(Fls_DataAddr addr, Fls_NumberOfBufferType len);
extern Std_ReturnType Fls_W25Q16_Erase_Block_32(Fls_DataAddr addr, Fls_NumberOfBufferType len);
extern Std_ReturnType Fls_W25Q16_Erase_Block_64(Fls_DataAddr addr, Fls_NumberOfBufferType len);
extern Std_ReturnType Fls_W25Q16_Protect();
extern Std_ReturnType Fls_W25Q16_DisProtect();
extern Std_ReturnType Fls_W25Q16_Wait_Busy();
extern Std_ReturnType Fls_W25Q16_Wait_Busy_Time_Out();
#endif
//CDD_W25Q16.c
#include "CDD_W25Q16.h"
/* 一次只能读取一个扇区的数据 */
Spi_DataBufferType gFlashTxCmd[4100] = {0};
Spi_DataBufferType gFlashRxData[4100] = {0};
f_FlsOpState gFlsWrState = fFls_Idle;//FLASH的状态
/**
* @brief W25Q16 Flash初始化
* @param None
* @retval None
*/
void Fls_W25Q16_Init(){
setSpiSpeed(SPI_BaudRatePrescaler_2);//设置2分频
Fls_W25Q16_Write_SR(0 ,0);
}
/**
* @brief W25Q16 Flash 获取芯片的ID
* @param None
* @retval None
*/
void Fls_W25Q16_Device_Id(){
Std_ReturnType result;
//发送的命令
gFlashTxCmd[0] = INS_CODE_DEID;
gFlashTxCmd[1] = INS_CODE_DEFA;
gFlashTxCmd[2] = INS_CODE_DEFA;
gFlashTxCmd[3] = INS_CODE_DEFA;
FLASH_CS_CHOOSE_ENABLE(); /* 使能设备 */
result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 4);
FLASH_CS_CHOOSE_DISABLE(); /* 失能设备 */
if(result == E_OK){
//成功处理
}else{
//失败处理
}
}
/**
* @brief W25Q16 Flash 芯片写使能
* @param None
* @retval None
*/
Std_ReturnType Fls_W25Q16_Write_Enable(){
Std_ReturnType result = E_OK;
/* 地址和命令 */
gFlashTxCmd[0] = INS_CODE_WREN;
FLASH_CS_CHOOSE_ENABLE();
result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 1);
FLASH_CS_CHOOSE_DISABLE();
return result;
}
/**
* @brief W25Q16 Flash 芯片写禁止
* @param None
* @retval None
*/
Std_ReturnType Fls_W25Q16_Write_Disable(){
Std_ReturnType result = E_OK;
/* 地址和命令 */
gFlashTxCmd[0] = INS_CODE_WRDI;
FLASH_CS_CHOOSE_ENABLE();
result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 1);
FLASH_CS_CHOOSE_DISABLE();
return result;
}
/**
* @brief W25Q16 Flash 读取状态寄存器1的数据
* @param None
* @retval None
*/
Std_ReturnType Fls_W25Q16_Read_SR1(Fls_DataType* pState){
Std_ReturnType result = E_OK;
FLASH_CS_CHOOSE_ENABLE();
/* 地址和命令 */
gFlashTxCmd[0] = INS_CODE_RDSR1;
gFlashTxCmd[1] = INS_CODE_DEFA;
result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 2);
if(!result){
*pState = gFlashRxData[1];
}else{
*pState = 0xFF;
}
FLASH_CS_CHOOSE_DISABLE();
return result;
}
/**
* @brief W25Q16 Flash 读取状态寄存器2的数据
* @param None
* @retval None
*/
Std_ReturnType Fls_W25Q16_Read_SR2(Fls_DataType* pState){
Std_ReturnType result = E_OK;
FLASH_CS_CHOOSE_ENABLE();
/* 地址和命令 */
gFlashTxCmd[0] = INS_CODE_RDSR2;
gFlashTxCmd[1] = INS_CODE_DEFA;
result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 2);
if(!result){
*pState = gFlashRxData[1];
}else{
*pState = 0xFF;
}
FLASH_CS_CHOOSE_DISABLE();
return result;
}
/**
* @brief W25Q16 Flash 写状态寄存器1和2的数据
* @param stateReg1:状态寄存器1的写入值
* @param stateReg2:状态寄存器2的写入值
* @retval None
*/
Std_ReturnType Fls_W25Q16_Write_SR(const Fls_DataType stateReg1 , const Fls_DataType stateReg2){
Std_ReturnType result = E_OK;
/* 地址和命令 */
gFlashTxCmd[0] = INS_CODE_WRSR;
gFlashTxCmd[1] = stateReg1;
gFlashTxCmd[2] = stateReg2;
Fls_W25Q16_Write_Enable();
FLASH_CS_CHOOSE_ENABLE();
result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 3);
FLASH_CS_CHOOSE_DISABLE();
return result;
}
/**
* @brief 往W25Q16芯片内写数据,以页为单位,每一页的大小是256Byte
* @param a ddr:Flash地址
* @param len:数据长度
* @param buf:数据地址
* @retval None
*/
Std_ReturnType Fls_W25Q16_Write(Fls_DataAddr addr, Fls_NumberOfBufferType len, Fls_DataBufferType *buf){
Std_ReturnType result = E_NOT_OK;
Fls_DataAddr pageId = addr & 0x0000FF;
/* 判断写入的地址和长度是否合法 , 每一页只能写入256字节 */
if((len == 0) || (len > 256) || addr > 0x1FFFFF || (addr + len) > 0x200000 || (pageId + len) > 256 || gFlsWrState != fFls_Idle){
return result ;
}
Fls_W25Q16_Write_Enable();//写使能
/* 检查寄存器是否处于忙的状态 */
if(Fls_W25Q16_Wait_Busy_Time_Out() != E_OK){
return E_NOT_OK;
}
/* 组装命令和需要写入的数据 */
gFlashTxCmd[0] = INS_CODE_WRFL;
gFlashTxCmd[1] = (uint8_t)(addr>>16);
gFlashTxCmd[2] = (uint8_t)(addr>>8);
gFlashTxCmd[3] = (uint8_t)(addr);
for( Fls_NumberOfBufferType index = 1 ; index 4096) || addr > 0x1FFFFF || (addr + len) > 0x200000 || gFlsWrState != fFls_Idle){
return result;
}
/* 地址和命令 */
gFlashTxCmd[0] = INS_CODE_RDFL;
gFlashTxCmd[1] = (uint8_t)(addr>>16);
gFlashTxCmd[2] = (uint8_t)(addr>>8);
gFlashTxCmd[3] = (uint8_t)(addr);
Fls_NumberOfBufferType index = 1;
for(; index 4096 || addr > 0x1FFFFF || (addr + len) > 0x200000 || gFlsWrState != fFls_Idle){
return result;
}
/*
不能跨扇区操作 ,
这个的解释是,一个扇区是4KB,
那么假设它的起始地址是0x000000,结束的地址就是0x000FFF。
当需要擦除的地址和0x000FFF与的时候,得出的地址都是一个扇区内的某个地址,
这个地址再加上操作的长度,理论上不能大于4KB(也就是4096)
*/
/*sectorId = addr & 0x000FFF;
if((sectorId + len) > 4096){
return result;
}*/
//对地址进行扇区起始地址设置
addr &= 0XFFF000;
gFlsWrState = fFls_WRData;
if(Fls_W25Q16_Wait_Busy_Time_Out() != E_OK){
//检查寄存器是否处于忙的状态
return E_NOT_OK;
}
Fls_W25Q16_Write_Enable();//写使能
FLASH_CS_CHOOSE_ENABLE();//选择CS
gFlashTxCmd[0] = INS_CODE_ERASE_SECTOR_4;
gFlashTxCmd[1] = (uint8_t)(addr>>16);
gFlashTxCmd[2] = (uint8_t)(addr>>8);
gFlashTxCmd[3] = (uint8_t)(addr);
result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 4);
FLASH_CS_CHOOSE_DISABLE();
gFlsWrState = fFls_Idle;
//Fls_W25Q16_Wait_Busy();
if(Fls_W25Q16_Wait_Busy_Time_Out() != E_OK){
//检查寄存器是否处于忙的状态
return E_NOT_OK;
}
return E_OK;
}
/*
Std_ReturnType Fls_W25Q16_Erase_Block_32(Fls_DataAddr addr, Fls_NumberOfBufferType len){
}
Std_ReturnType Fls_W25Q16_Erase_Block_64(Fls_DataAddr addr, Fls_NumberOfBufferType len){
}
*/
/**
* @brief 查看寄存器1的读写状态
* @param None
* @retval None
*/
Std_ReturnType Fls_W25Q16_Wait_Busy(){
//首先获取到状态寄存器1的
Fls_DataType stateReg1 = 0;
if(!Fls_W25Q16_Read_SR1(&stateReg1)){
while((stateReg1 & 0x01));
return E_OK;
}
return E_NOT_OK;
}
/**
* @brief 查看寄存器1的读写状态,同时定时FLASH_BUSY_TIME_OUT
* @param None
* @retval None
*/
Std_ReturnType Fls_W25Q16_Wait_Busy_Time_Out(){
//首先获取到状态寄存器1的
Fls_DataType stateReg1 = 0;
uint16_t timeOut = 0;
if(!Fls_W25Q16_Read_SR1(&stateReg1)){
while((stateReg1 & 0x01) && timeOut != FLASH_BUSY_TIME_OUT){
++timeOut;
}
return stateReg1 && timeOut;
}
return E_NOT_OK;
}
总结
由于刚开始接触这个,所以很多东西介绍的不是很清楚,希望读者见谅。其次希望读者在查看本篇博文的时候,要去思考,多看芯片手册,这样才能有些许收获,而不是直接拿到代码编译,然后大致浏览就算过了。接着我也知道本片博文还有很多不完善的地方,无论是排版还是内容,希望大家可以留言一起讨论学习,我也会根据大家的建议继续发布自己的一些学习心得。在代码实现上,有些东西做的还不是很完善,比如写入超过一页的数据该怎么处理,我是直接就返回E_NOT_OK,其实可以做一下分页写入操作;亦或者在擦除的时候我只实现了按扇区擦除,大家也可以根据现有代码实现按块擦除等。
|