STM32学习心得:SPI-Flash-W25Q16DV

前言

  1. 目前正在学习STM32单片机的基础知识,通过库函数实现想要的一些功能。这篇文章主要介绍的是片外的Flash的操作,Flash的型号是W25Q16DV(芯片介绍在后面),通信的方式是SPI通信。
  2. 通过ST提供的库函数,实现对SPI通信的简单封装。
  3. 实现Flash的驱动程序,包括驱动的初始化、写使能、读操作、写操作、擦除操作等功能。
  4. 在做比如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只是做了很基础的介绍,具体详细的内容,比如四种模式的波形、通信的过程等,还需要读者自行查阅。我觉的学习的过程就是你不断地自行找资料、一点点学习的过程。望海涵。

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

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 <= len ; ++index){
        gFlashTxCmd[3+index] = *(buf + index - 1);
    }
    FLASH_CS_CHOOSE_ENABLE();//片选拉低
    gFlsWrState = fFls_WRData;
    result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 4 + len);//调用SPI开始发送数据
    FLASH_CS_CHOOSE_DISABLE(); //片选拉高
    gFlsWrState = fFls_Idle;
    if(Fls_W25Q16_Wait_Busy_Time_Out() != E_OK){
        return E_NOT_OK;
    }
    return E_OK;
}

/**
  * @brief  擦除W25Q16数据,按扇区(4KB)擦除,所以len最大不能超过,起始擦除也是一个写的过程
  * @param  	addr:读取数据的开始地址
  * @param  	len:读取数据的长度
  * @param  	buf:接收缓存
  * @retval None
  */
Std_ReturnType Fls_W25Q16_Read(Fls_DataAddr addr, Fls_NumberOfBufferType len , Fls_DataBufferType *buf){
    Std_ReturnType result;
    /* 判断读取的地址是否合法,在这里还需要 */
    if((len == 0) || (len > 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 <= len ; ++index){
        gFlashTxCmd[3 + index] = INS_CODE_DEFA;
    }
    FLASH_CS_CHOOSE_ENABLE();
    gFlsWrState = fFls_RData;
    result = spiSetupSimple(FLASH_SPI , gFlashTxCmd , gFlashRxData , 4 + len);
    FLASH_CS_CHOOSE_DISABLE();
    gFlsWrState = fFls_Idle;
    if(!result){
        //读取成功
        for(index = 0 ; index < len ; ++index){
            *(buf + index) = gFlashRxData[4 + index];
        }
    }else{
        //读取失败
        for(index = 0 ; index < len ; ++index){
            *(buf + index) = 0;
        }
    }
    return result;
}

/**
  * @brief  擦除W25Q16数据,按扇区(4KB)擦除,所以len最大不能超过,起始擦除也是一个写的过程
  * @param  	addr:擦除扇区的开始地址
  * @param  	len:擦除的长度,因为是按扇区擦除,所以字节的长度是4096
  * @retval None
  */
Std_ReturnType Fls_W25Q16_Erase_Sector_4(Fls_DataAddr addr, Fls_NumberOfBufferType len){
    Std_ReturnType result = E_NOT_OK;
    //uint32_t sectorId = 0;
    /* 判断擦除的地址是否合法,在这里还需要 */
    if((len == 0) || len > 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;
}

总结

  1. 由于刚开始接触这个,所以很多东西介绍的不是很清楚,希望读者见谅。
  2. 其次希望读者在查看本篇博文的时候,要去思考,多看芯片手册,这样才能有些许收获,而不是直接拿到代码编译,然后大致浏览就算过了。
  3. 接着我也知道本片博文还有很多不完善的地方,无论是排版还是内容,希望大家可以留言一起讨论学习,我也会根据大家的建议继续发布自己的一些学习心得。
  4. 在代码实现上,有些东西做的还不是很完善,比如写入超过一页的数据该怎么处理,我是直接就返回E_NOT_OK,其实可以做一下分页写入操作;亦或者在擦除的时候我只实现了按扇区擦除,大家也可以根据现有代码实现按块擦除等。

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值