【STC8A8K64S4A12开发板】—片外存储器FLASH讲解

版权声明:本文为博主原创文章,转载请附上原文出处链接。


前言

今天介绍下STC8A8K64S4A12系列单片机外部FLASH存储器W25Q128引脚定义和硬件电路设计,掌握通过STC8A8K64S4A12系列单片机SPI外设对W25Q128的程序设计。


一、硬件设计

1.FRAM铁电存储器介绍

FLASH存储器属内存器件的一种,是一种非易失性( Non-Volatile )内存。该存储器结合了ROM和RAM的长处,不仅具备电可擦除可编程(EEPROM)的性能,还不会断电丢失数据,同时可以快速读取数据,所以现如今大多数单片机片内都集成了这种存储器。

接下来,我们主要介绍的是外部的FLASH存储器芯片,外部的FLASH存储器芯片种类和生产厂家都比较多。其中WINBOND(华邦)公司生产的W25Q系列存储器应用很广泛,是我们介绍的重点。W25Q系列存储器相比于普通的串行FLASH器件提供了更好的灵活性和性能,该系列存储器可在2.7V到3.6V的供电电压下工作(注意芯片后缀JV),工作时电流消耗最低4mA,在睡眠模式下消耗仅1uA。另外,所有的W25Q系列存储器芯片都提供节省空间的封装。

WINBOND(华邦)公司生产的W25Q系列存储器支持标准SPI接口,操作寄存器指令兼容,但不同存储空间的芯片有不同的ID,具体如下表。

表1:W25Q系列存储器介绍
序号芯片型号存储空间供电电压芯片ID工作温度备注
1W25Q16JV16M bit2.7V~3.6V0xEF14-40℃~85℃
2W25Q32JV32M bit2.7V~3.6V0xEF15-40℃~85℃
3W25Q64JV64M bit2.7V~3.6V0xEF16-40℃~85℃
4W25Q128JV128M bit2.7V~3.6V0xEF17-40℃~85℃
5W25Q256JV256M bit2.7V~3.6V0xEF18-40℃~85℃
6W25Q512JV512M bit2.7V~3.6V0xEF19-40℃~85℃

☆注:芯片ID中的高8位0xEF代表的Winbond Serial Flash系列。厂家还有1G bit和2G bit的存储器芯片W25Q01JV和W25Q02JV,有需要更大存储空间可以考虑。

2.W25Q128JV存储芯片介绍

2.1.芯片引脚定义

W25Q128JV支持标准的SPI接口,双线/四线IO模式SPI:串行时钟,数据选择,串行数据IO0(DI),IO1(DO),IO2(/WP),IO3(/HOLD)。

SPI时钟频率最高可达133MHz,因此在双IO SPI模式下等效于266MHz,在四IO SPI/QPI模式下等效于532MHz。这些传输速率比标准的异步8位或者16位并行FLASH存储器表现得更好。

在这里插入图片描述

图1:W25Q128JV芯片引脚

STC8A8K64S4A12开发板上设计了可供用户焊接使用的W25Q系列存储器芯片的接口,其中原理图部分及硬件实物部分如下。

在这里插入图片描述

图2:开发板外扩FLASH模块

2.2.芯片介绍及使用注意事项

W25Q128JV存储芯片是由65536可编程的页组成的,每页有256个字节。一次最多可以写256个字节。可以一次擦除16页(4K字节)、128页(32K字节)、256页(64K字节)或者一整片。W25Q128JV有4096个可擦除的扇区,256个可擦除的块。4K字节的扇区对于数据和参数存储有更高的灵活性。

■ W25Q128JV内部框图

在这里插入图片描述

图3:W25Q128JV芯片内部框图

☆注:W25Q128JV存储器实际的存储空间会略大于128M bit的,因为比如4096字节都按照4K来计算的。其他W25Q系列存储器的页、扇区、块计算都和上面W25Q128JV内部框图一样,不一样的是不同的存储器块数不一样。比如,W25Q64JV存储器只有128个块,也就是8M字节存储空间(即64M bit)。

■ 使用注意事项

  1. 由于FLASH的物理特性,决定了FLASH每一位的操作只能从1变为0(写操作)。
  2. 大多数FLASH芯片或单片机内未使用FLASH存储空间每一位出厂默认都是1。
  3. 对FLASH写操作之前必须将待操作FLASH空间数据都置为1。
  4. 对FLASH的擦除操作即是把待操作的空间的每一位都置为1。
  5. 所以FLASH写操作前需有擦除操作。
  6. W25Q128JV存储芯片的擦除比较灵活,可以按扇区、块甚至是整片擦除。(擦除是需要时间的,比如整片擦除约用时几十秒)

☆注:有些用户说我没有擦除直接写的,第一次没有问题,之后怎么出错了?想一想什么原因。(往往第一次是出厂的默认的每个空间位都是1,所以写操作没有问题)。

二、软件设计

关于STC8A8K64S4A12系列SPI外设原理及相关寄存器的介绍请参考外接FRAM存储器实验部分,对外接FLASH存储器的SPI配置是完全一样的。

1.外接FLASH存储器读写单字节实验(模拟SPI)

1.1.工程需要用到的c文件

本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。

表2:实验需要用到的c文件
序号文件名后缀功能描述
1uart.c包含与用户uart有关的用户自定义函数。
2W25q128.cSPI通信及操作FLASH有关的用户自定义函数。
3delay.c包含用户自定义延时函数。

1.2.头文件引用和路径设置

■ 需要引用的头文件

#include "delay.h"  
#include "uart.h"  
#include " w25q128.h"  

■ 需要包含的头文件路径

本例需要包含的头文件路径如下表:

表3:头文件包含路径
序号路径描述
1…\ Sourceuart.h、w25q128.h和delay.h头文件在该路径,所以要包含。
2…\UseSTC8.h头文件在该路径,所以要包含。

MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。

在这里插入图片描述

图4:添加头文件包含路径

1.3.编写代码

首先,在w25q128.c文件中编写模拟SPI方式的读写字节函数,这里的模拟SPI读字节函数与写字节函数是分开的,代码如下:

程序清单:模拟SPI写字节函数

/***************************************************************************** 
 * 描  述 : 模拟SPI写入一个字节 
 * 入  参 : uint8 date 
 * 返回值 : 无 
 ****************************************************************************/  
void SPI_WriteByte(uint8 date)  
{  
  uint8 temp,i;  
  temp = date;    
  
  for (i = 0; i < 8; i++)   
    {  
     SPI_SCK_0;  
         delay_us(10);  
     if((temp&0x80)==0x80)  
     { SPI_MOSI_1; }  
     else   
     { SPI_MOSI_0; }  
     SPI_SCK_1 ;  
         delay_us(10);  
     temp <<= 1;  
   }  
   SPI_MOSI_0;   
} 

程序清单:模拟SPI读字节函数

/*********************************************************************** 
 * 描  述 : 模拟SPI读取一个字节 
 * 入  参 : 无 
 * 返回值 : 读取uint8数据 
 ***********************************************************************/  
uint8 SPI_ReadByte(void)  
{  
  uint8 temp=0;  
  uint8 i;  
                    
  for(i = 0; i < 8; i++)  
    {  
    temp <<= 1;  
    SPI_SCK_0 ;          
    delay_us(10);  
        if(E_SPI_MISO)   
    {temp++; }  
    SPI_SCK_1 ;  
        delay_us(10);  
   }  
   return(temp);  
}

然后,编写对FLASH存储器的基本操作函数,如下表所示。

表4:FLASH相关用户函数汇集

在这里插入图片描述

关于每个操作外部FLASH相关用户函数,下面给出详细代码。

程序清单:FLASH芯片写使能函数

/*************************************************************************** 
 * 描  述 : 写禁止(将WEL清0) 
 * 入  参 : 无 
 * 返回值 : 无 
 *************************************************************************/  
void WriteDisable (void)  
{  
    SPI_CS_0;  
    SPI_WriteByte(W25X_WriteDisable);    
    SPI_CS_1;  
} 

程序清单:读FLASH芯片ID函数

/************************************************************************* 
 * 描  述 : 读取存储器芯片ID 
 * 入  参 : 无 
 * 返回值 : uint16 ID 
备注:W25Q16的ID:0XEF14  W25Q32的ID:0XEF15  W25Q64的ID:0XEF16  W25Q128的ID:0XEF17   
**************************************************************************/  
uint16 W25Q_ReadID(void)  
{  
    uint16 Temp = 0;  
  uint8 Temp1 = 0;  
    uint8   Temp2 = 0;  
    SPI_CS_0;                     
    SPI_WriteByte(0x90);        //发送读取ID命令        
    SPI_WriteByte(0x00);          
    SPI_WriteByte(0x00);          
    SPI_WriteByte(0x00);                     
    Temp1|=SPI_ReadByte();    
    Temp2|=SPI_ReadByte();    
    Temp = Temp1*256+Temp2;  
    SPI_CS_1;                 
    return Temp;  
} 

程序清单:读FLASH状态寄存器函数

/************************************************************************ 
 * 描  述 : 读取存储器芯片的状态 
 * 入  参 : 无 
 * 返回值 : uint8 状态寄存器数据字节  
备注:芯片内部状态寄存器第0位=0表示空闲,0位=1表示忙  
**************************************************************************/  
uint8 W25Q_ReadStatus(void)  
{  
    uint8 status=0;  
    SPI_CS_0;  
    SPI_WriteByte(W25X_ReadStatus);   // 0x05读取状态的命令字  
    status=SPI_ReadByte();            // 读取状态字节  
    SPI_CS_1;                         
    return status;  
} 

程序清单:写FLASH状态寄存器函数

/*********************************************************************** 
 * 描  述 : 写芯片的状态寄存器 
 * 入  参 : uint8 Status 
 * 返回值 : 无 
备注:只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!! 
***********************************************************************/  
void W25Q_WriteStatus(uint8 Status)  
{  
    SPI_CS_0;  
    SPI_WriteByte(W25X_WriteStatus);  // 0x01读取状态的命令字  
    SPI_WriteByte(Status);            // 写入一个字节  
    SPI_CS_1;                           
} 

程序清单:在一页内写入多字节数据函数

/************************************************************************ 
 * 描  述 : 在一页(0~65535)内写入少于256个字节的数据(在指定地址开始写入最大256字节的数据) 
 * 入  参 : pbuf:数据存储区  WriteAddr:开始写入的地址(24bit)  Len:要写入的字节数(最大256)  
 * 返回值 : 无 
备注:Len:要写入的字节数,该数不应该超过该页的剩余字节数!!!   
************************************************************************/  
void W25Q_Write_Page(uint8* pbuf,uint32 WriteAddr,uint16 Len)  
{  
   uint16 i;  
   while(W25Q_ReadStatus()&0x01);        //判断是否忙  
   WriteEnable();                          //写使能  
   SPI_CS_0;                               //使能器件     
   SPI_WriteByte(W25X_Writepage);          //发送写页命令  
   SPI_WriteByte((uint8)((WriteAddr)>>16));   //发送24bit地址     
   SPI_WriteByte((uint8)((WriteAddr)>>8));     
   SPI_WriteByte((uint8)WriteAddr);    
   for(i=0;i<Len;i++)                      //循环写数  
   {  
        SPI_WriteByte(*pbuf++);        
   }  
   SPI_CS_1;                               //取消片选  
   while(W25Q_ReadStatus()&0x01);        //等待写入结束     
} 

程序清单:向FLASH指定地址存入多字节数据函数

/*************************************************************************  
 * 描  述 : 在指定地址开始写入指定长度的数据 
 * 入  参 : pbuf:数据存储区  WriteAddr:开始写入的地址(24bit)  Len:要写入的字节数(最大65535) 
 * 返回值 : 无 
备注:必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!  
*************************************************************************/  
void W25Q_Write_N(uint8 * pbuf,uint32 WriteAddr,uint16 Len)  
{  
    uint16 PageLen;                  // 页内写入字节长度  
    PageLen=256-WriteAddr%256;       // 单页剩余的字节数 (单页剩余空间)  
    if(Len<=PageLen) PageLen=Len;    // 不大于256 个字节  
    while(1)  
    {  
        W25Q_Write_Page(pbuf,WriteAddr,PageLen);  
        if(PageLen==Len)break;         // 写入结束了  
        else  
        {  
            pbuf+=PageLen;  
            WriteAddr+=PageLen;  
            Len-=PageLen;              //  减去已经写入了的字节数  
            if(Len>256)PageLen=256;    // 一次可以写入256 个字节  
            else PageLen=Len;          // 不够256 个字节了  
        }  
    }  
}

程序清单:从FLASH指定地址读取多字节数据函数

/************************************************************************  
 * 描  述 : 在指定地址开始写入指定长度的数据 
 * 入  参 : pbuf:数据存储区  WriteAddr:开始写入的地址(24bit)  Len:要写入的字节数(最大65535) 
 * 返回值 : 无 
备注:必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!  
**************************************************************************/  
void W25Q_Write_N(uint8 * pbuf,uint32 WriteAddr,uint16 Len)  
{  
    uint16 PageLen;                  // 页内写入字节长度  
    PageLen=256-WriteAddr%256;       // 单页剩余的字节数 (单页剩余空间)  
    if(Len<=PageLen) PageLen=Len;    // 不大于256 个字节  
    while(1)  
    {  
        W25Q_Write_Page(pbuf,WriteAddr,PageLen);  
        if(PageLen==Len)break;         // 写入结束了  
        else  
        {  
            pbuf+=PageLen;  
            WriteAddr+=PageLen;  
            Len-=PageLen;              //  减去已经写入了的字节数  
            if(Len>256)PageLen=256;    // 一次可以写入256 个字节  
            else PageLen=Len;          // 不够256 个字节了  
        }  
    }  
}  

程序清单:按扇区擦除数据函数

/********************************************************************* 
 * 描  述 : 擦除一个扇区( 4K扇擦除) 
 * 入  参 : uint32 Addr24  扇区地址 
 * 返回值 : 无 
备注:擦除一个扇区的最少时间:150ms 
*************************************************************************/  
void W25Q_SectorErase(uint32 Addr24) //擦除资料图示的4KB空间  
{  
    unsigned char Addr1;       // 最低地址字节  
    unsigned char Addr2;       // 中间地址字节  
    unsigned char Addr3;       // 最高地址字节    
    Addr1=Addr24;  
    Addr24=Addr24>>8;  
    Addr2=Addr24;  
    Addr24=Addr24>>8;  
    Addr3=Addr24;                      // 把地址拆开来    
    while(W25Q_ReadStatus()&0x01);     // 判断是否忙     
    WriteEnable();                     // 写允许  
    SPI_CS_0;  
    SPI_WriteByte(W25X_S_Erase);       // 整扇擦除命令  
    SPI_WriteByte(Addr3);  
    SPI_WriteByte(Addr2);  
    SPI_WriteByte(Addr1);  
    SPI_CS_1;  
    while(W25Q_ReadStatus()&0x01);   // 等待擦除完成  
}  

程序清单:按块擦除数据函数

/*********************************************************************** 
 * 描  述 : 擦除一块擦除( 64K) 
 * 入  参 : uint32 Addr24  扇区地址 
 * 返回值 : 无 
*************************************************************************/  
void W25Q_BlockErase(uint32 Addr24)  //擦除资料图示的64KB空间  
{  
    uint8 Addr1;       // 最低地址字节  
    uint8 Addr2;       // 中间地址字节  
    uint8 Addr3;       // 最高地址字节    
    Addr1=Addr24;  
    Addr24=Addr24>>8;  
    Addr2=Addr24;  
    Addr24=Addr24>>8;  
    Addr3=Addr24;                      // 把地址拆开来    
    while(W25Q_ReadStatus()&0x01);     // 判断是否忙     
    WriteEnable();                     // 写允许  
    SPI_CS_0;  
    SPI_WriteByte(W25X_B_Erase);       // 整扇擦除命令  
    SPI_WriteByte(Addr3);  
    SPI_WriteByte(Addr2);  
    SPI_WriteByte(Addr1);  
    SPI_CS_1;  
    while(W25Q_ReadStatus()&0x01);   // 等待擦除完成  
}

程序清单:擦除整片芯片函数

/************************************************************************* 
 * 描  述 : 擦除整片芯片 
 * 入  参 : 无 
 * 返回值 : 无 
备注:不同型号的芯片时间不一样  
**************************************************************************/  
void W25Q_ChipErase(void)  
{  
    while(W25Q_ReadStatus()&0x01);     // 判断是否忙     
    WriteEnable();                     // 写允许  
    SPI_CS_0;  
    SPI_WriteByte(W25X_C_Erase);       // 整片擦除命令  
    SPI_CS_1;                          // 从CS=1时开始执行擦除  
    while(W25Q_ReadStatus()&0x01);     // 等待擦除完成     
}

最后,在主函数中对串口1进行初始化,并通过串口1发送不同的命令实现对单片机片外FLASH的单字节读、写及擦除等操作。

代码清单:主函数

int main()  
{  
    uint16  Temp;  
    uint8   Temp1,Temp2;  
      
    P3M1 &= 0xFE;   P3M0 &= 0xFE;                     //设置P3.0为准双向口  
    P3M1 &= 0xFD;   P3M0 |= 0x02;                     //设置P3.1为推挽输出  
      
    SPI_CS_1;                                     //SPI使能引脚初始化  
    SPI_SCK_0;                                    //SPI时钟引脚初始化  
    Uart1_Init();                                 //串口1初始化    
    EA = 1;                                       //使能总中断  
  delay_ms(10);                                 //初始化后延时  
      
    Temp=W25Q_ReadID();                           //读取外扩存储器芯片ID  
  Temp1=(uint8)(Temp/256);                      //将读取的存储器芯片ID高字节存放到Temp1中  
    Temp2=(uint8)Temp;                            //将读取的存储器芯片ID低字节存放到Temp2中  
      
  while (1)  
    {  
        if(W_ID)                           //读存储器芯片ID模式  
        {  
              W_ID=0;                            //ID标志变量清零,发送一次  
              SendDataByUart1(Temp1);            //串口1发送芯片ID高字节  
              SendDataByUart1(Temp2);            //串口1发送芯片ID低字节  
         }  
     if(WriteFLAG)                     //写模式  
         {  
              WriteFLAG=0;                      //写标志变量清零,发送一次  
              W25Q_Write_N(scan,0x00000010,1);  //在0x00000010地址开始写入1字节scan中的数据  
        SendDataByUart1(0x33);            //串口1发送数据0x33  
         }  
         if(ReadFLAG)                      //读模式  
         {  
              ReadFLAG=0;                         //读标志变量清零,发送一次  
              W25Q_Read_N(buffer,0x00000010,1);   //读0x00000010地址开始的1字节数据到buffer中  
        SendStringByUart1_n(buffer,1);      //串口1发送buffer中存的数据  
         }    
         if(ClearFLAG)                     //扇区擦除模式  
         {  
              ClearFLAG=0;                        //清除标志变量清零,发送一次  
              W25Q_SectorErase(0x00000010);       //对0x00000010地址所在的扇区进行扇区擦除  
        SendDataByUart1(0x00);              //串口1发送数据0x00  
         }     
    }  
}  

1.4.硬件连接

在这里插入图片描述

图5:开发板连接图

2.外接FLASH存储器读写多字节实验(模拟SPI)

2.1.工程需要用到的c文件

本实验需要用到的头文件以及添加头文件包含路径的方法请参考“实验2-16-1:外接FLASH存储器读写单字节实验(模拟SPI)”部分。

2.2.编写代码

首先,在w25q128.c文件中编写模拟SPI方式的读写字节函数和对FLASH存储器的基本操作函数。请参考“实验2-16-1:外接FLASH存储器读写单字节实验(模拟SPI)”部分。

然后,在主函数中对串口1进行初始化,并通过串口1发送不同的命令实现对单片机片外FLASH的多字节读、写及擦除等操作。

代码清单:主函数

int main()  
{  
    uint16  Temp;  
    uint8   Temp1,Temp2;  
      
    P3M1 &= 0xFE;   P3M0 &= 0xFE;                     //设置P3.0为准双向口  
    P3M1 &= 0xFD;   P3M0 |= 0x02;                     //设置P3.1为推挽输出  
      
    SPI_CS_1;                                     //SPI使能引脚初始化  
    SPI_SCK_0;                                    //SPI时钟引脚初始化  
    Uart1_Init();                                 //串口1初始化    
    EA = 1;                                       //使能总中断  
  delay_ms(10);                                 //初始化后延时  
      
    Temp=W25Q_ReadID();                           //读取外扩存储器芯片ID  
  Temp1=(uint8)(Temp/256);                      //将读取的存储器芯片ID高字节存放到Temp1中  
    Temp2=(uint8)Temp;                            //将读取的存储器芯片ID低字节存放到Temp2中  
      
  while (1)  
    {  
        if(W_ID)                           //读存储器芯片ID模式  
        {  
              W_ID=0;                            //ID标志变量清零,发送一次  
              SendDataByUart1(Temp1);            //串口1发送芯片ID高字节  
              SendDataByUart1(Temp2);            //串口1发送芯片ID低字节  
         }  
     if(WriteFLAG)                     //写模式  
         {  
              WriteFLAG=0;                      //写标志变量清零,发送一次  
              W25Q_SectorErase(0x00000000);       //擦除一个扇区(0x00000000在的扇区)  
              W25Q_Write_N(scan,0x00000000,10); //向FLASH地址0x00000000中写入scan数组中10个字节数据  
        SendDataByUart1(0x33);            //串口1发送数据0x33表示写操作完成  
         }  
         if(ReadFLAG)                      //读模式  
         {  
              ReadFLAG=0;                         //读标志变量清零,发送一次  
              W25Q_Read_N(buffer,0x00000000,10);      //从FLASH地址0x00000000开始读取10字节数据并存入到buffer数组中  
        SendStringByUart1_n(buffer,10);      //串口1发送数组buffer中的值(即读取的多字节数据)  
         }    
         if(ClearFLAG)                     //扇区擦除模式  
         {  
              ClearFLAG=0;                        //清除标志变量清零,发送一次  
              W25Q_SectorErase(0x00000000);       //擦除一个扇区(0x00000000在的扇区)  
        SendDataByUart1(0x00);              //串口1发送数据0x00表示擦除完成  
         }     
    }  
} 

2.3.硬件连接

在这里插入图片描述

图6:开发板连接图

3.外接FLASH存储器读写单字节实验(硬件SPI)

3.1.工程需要用到的c文件

本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。

表5:实验需要用到的c文件
序号文件名后缀功能描述
1uart.c包含与用户uart有关的用户自定义函数。
2w25q128.cSPI通信及操作FLASH有关的用户自定义函数。
3delay.c包含用户自定义延时函数。

3.2.头文件引用和路径设置

■ 需要引用的头文件

#include "delay.h"  
#include "uart.h"  
#include " w25q128.h"  

■ 需要包含的头文件路径

本例需要包含的头文件路径如下表:

表6:头文件包含路径
序号路径描述
1…\ Sourceuart.h、w25q128.h和delay.h头文件在该路径,所以要包含。
2…\UseSTC8.h头文件在该路径,所以要包含。

MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。

在这里插入图片描述

图7:添加头文件包含路径

3.3.编写代码

首先,在w25q128.c文件中对硬件SPI进行初始化,并编写硬件SPI的读写字节函数,代码如下:

程序清单:硬件SPI初始化函数

/************************************************************************************** 
 * 描  述 : 硬件SPI初始化 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************************/  
void Init_SPI(void)            
{  
     P_SW1 |=0X08;                         //将 SPI 调整到 P7.4 P7.5 P7.6  P7.7   
     P_SW1 &=0XFB;                         //将 SPI 调整到 P7.4 P7.5 P7.6  P7.7   
    SPDAT = 0;  
    SPSTAT = SPIF | WCOL;               //清除SPI状态位  
    SPCTL = SPEN | MSTR | SSIG;         //主机模式        
}  

程序清单:硬件SPI读写字节函数

/*********************************************************************** 
 * 描  述 : 硬件SPI写入一个字节,并返回一个值 
 * 入  参 : uint8 date 
 * 返回值 : 无 
 ***********************************************************************/  
uint8 SPI_SendByte(uint8 SPI_SendData)  
{  
  SPDAT = SPI_SendData;        //触发SPI发送数据  
  while (!(SPSTAT & SPIF));    //等待发送完成  
  SPSTAT = SPIF | WCOL;        //清除SPI状态位  
  return SPDAT;                //返回SPI数据  
} 

然后,编写对FLASH存储器的基本操作函数,如下表所示。

表7:FLASH相关用户函数汇集

在这里插入图片描述

关于每个操作外部FLASH相关用户函数,下面给出详细代码。

程序清单:FLASH芯片写使能函数

/*********************************************************************** 
 * 描  述 : 写使能(将WEL置位) 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************/  
void WriteEnable (void)  
{  
    SPI_CS_0;  
    SPI_SendByte(W25X_WriteEnable);    
    SPI_CS_1;  
} 

程序清单:FLASH芯片写禁止函数

/************************************************************************** 
 * 描  述 : 写禁止(将WEL清0) 
 * 入  参 : 无 
 * 返回值 : 无 
 ***********************************************************************/  
void WriteDisable (void)  
{  
    SPI_CS_0;  
    SPI_SendByte(W25X_WriteDisable);    
    SPI_CS_1;  
} 

程序清单:读FLASH芯片ID函数

/********************************************************************** 
 * 描  述 : 读取存储器芯片ID 
 * 入  参 : 无 
 * 返回值 : uint16 ID 
备注:W25Q16的ID:0XEF14  W25Q32的ID:0XEF15  W25Q64的ID:0XEF16  W25Q128的ID:0XEF17   
************************************************************************/  
uint16 W25Q_ReadID(void)  
{  
    uint16 Temp = 0;  
  uint8 Temp1 = 0;  
    uint8   Temp2 = 0;  
    SPI_CS_0;                     
    SPI_SendByte(0x90);        //发送读取ID命令         
    SPI_SendByte(0x00);           
    SPI_SendByte(0x00);           
    SPI_SendByte(0x00);                      
    Temp1|=SPI_SendByte(0xFF);    
    Temp2|=SPI_SendByte(0xFF);    
    Temp = Temp1*256+Temp2;  
    SPI_CS_1;                 
    return Temp;  
} 

程序清单:读FLASH状态寄存器函数

/********************************************************************** 
 * 描  述 : 读取存储器芯片的状态 
 * 入  参 : 无 
 * 返回值 : uint8 状态寄存器数据字节  
备注:芯片内部状态寄存器第0位=0表示空闲,0位=1表示忙  
***********************************************************************/  
uint8 W25Q_ReadStatus(void)  
{  
    uint8 status=0;  
    SPI_CS_0;  
    SPI_SendByte(W25X_ReadStatus);   // 0x05读取状态的命令字  
    status=SPI_SendByte(0xFF);            // 读取状态字节  
    SPI_CS_1;                         
    return status;  
}  

程序清单:写FLASH状态寄存器函数

/************************************************************************* 
 * 描  述 : 写芯片的状态寄存器 
 * 入  参 : uint8 Status 
 * 返回值 : 无 
备注:只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!! 
************************************************************************/  
void W25Q_WriteStatus(uint8 Status)  
{  
    SPI_CS_0;  
    SPI_SendByte(W25X_WriteStatus);  // 0x01读取状态的命令字  
    SPI_SendByte(Status);            // 写入一个字节  
    SPI_CS_1;                           
}

程序清单:在一页内写入多字节数据函数

/*********************************************************************** 
 * 描  述 : 在一页(0~65535)内写入少于256个字节的数据(在指定地址开始写入最大256字节的数据) 
 * 入  参 : pbuf:数据存储区  WriteAddr:开始写入的地址(24bit)  Len:要写入的字节数(最大256)  
 * 返回值 : 无 
备注:Len:要写入的字节数,该数不应该超过该页的剩余字节数!!!   
**********************************************************************/  
void W25Q_Write_Page(uint8* pbuf,uint32 WriteAddr,uint16 Len)  
{  
   uint16 i;  
   while(W25Q_ReadStatus()&0x01);        //判断是否忙  
   WriteEnable();                          //写使能  
   SPI_CS_0;                               //使能器件     
   SPI_SendByte(W25X_Writepage);          //发送写页命令  
   SPI_SendByte((uint8)((WriteAddr)>>16));   //发送24bit地址     
   SPI_SendByte((uint8)((WriteAddr)>>8));     
   SPI_SendByte((uint8)WriteAddr);    
   for(i=0;i<Len;i++)                      //循环写数  
   {  
        SPI_SendByte(*pbuf++);        
   }  
   SPI_CS_1;                               //取消片选  
   while(W25Q_ReadStatus()&0x01);        //等待写入结束     
}

程序清单:向FLASH指定地址存入多字节数据函数

/************************************************************************  
 * 描  述 : 在指定地址开始写入指定长度的数据 
 * 入  参 : pbuf:数据存储区  WriteAddr:开始写入的地址(24bit)  Len:要写入的字节数(最大65535) 
 * 返回值 : 无 
备注:必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!  
************************************************************************/  
void W25Q_Write_N(uint8 * pbuf,uint32 WriteAddr,uint16 Len)  
{  
    uint16 PageLen;                  // 页内写入字节长度  
    PageLen=256-WriteAddr%256;       // 单页剩余的字节数 (单页剩余空间)  
    if(Len<=PageLen) PageLen=Len;    // 不大于256 个字节  
    while(1)  
    {  
        W25Q_Write_Page(pbuf,WriteAddr,PageLen);  
        if(PageLen==Len)break;         // 写入结束了  
        else  
        {  
            pbuf+=PageLen;  
            WriteAddr+=PageLen;  
            Len-=PageLen;              //  减去已经写入了的字节数  
            if(Len>256)PageLen=256;    // 一次可以写入256 个字节  
            else PageLen=Len;          // 不够256 个字节了  
        }  
    }  
}

程序清单:从FLASH指定地址读取多字节数据函数

/*********************************************************************** 
 * 描  述 : 在指定地址开始读取指定长度的数据 
 * 入  参 : pbuf:数据存储区  ReadAddr:开始读取的地址(24bit)  Len:要读取的字节数(最大65535) 
 * 返回值 : 无 
************************************************************************/  
void W25Q_Read_N(uint8 * pbuf,uint32 ReadAddr,uint16 Len)     
{  
    uint16 i;    
    while(W25Q_ReadStatus()&0x01);        // 判断是否忙 
    SPI_CS_0;                             // 使能器件     
    SPI_SendByte(W25X_ReadDATA8);        // 发送读取命令     
    SPI_SendByte((uint8)((ReadAddr)>>16));  // 发送24bit地址     
    SPI_SendByte((uint8)((ReadAddr)>>8));     
    SPI_SendByte((uint8)ReadAddr);    
    for(i=0;i<Len;i++)  
    {  
       *pbuf++=SPI_SendByte(0xFF);            // 读一个字节     
    }  
    SPI_CS_1;                             // 取消片选              
}  

程序清单:按扇区擦除数据函数

/********************************************************************** 
 * 描  述 : 擦除一个扇区( 4K扇擦除) 
 * 入  参 : uint32 Addr24  扇区地址 
 * 返回值 : 无 
备注:擦除一个扇区的最少时间:150ms 
*************************************************************************/  
void W25Q_SectorErase(uint32 Addr24) //擦除资料图示的4KB空间  
{  
    unsigned char Addr1;       // 最低地址字节  
    unsigned char Addr2;       // 中间地址字节  
    unsigned char Addr3;       // 最高地址字节    
    Addr1=Addr24;  
    Addr24=Addr24>>8;  
    Addr2=Addr24;  
    Addr24=Addr24>>8;  
    Addr3=Addr24;                      // 把地址拆开来    
    while(W25Q_ReadStatus()&0x01);     // 判断是否忙     
    WriteEnable();                     // 写允许  
    SPI_CS_0;  
    SPI_SendByte(W25X_S_Erase);       // 整扇擦除命令  
    SPI_SendByte(Addr3);  
    SPI_SendByte(Addr2);  
    SPI_SendByte(Addr1);  
    SPI_CS_1;  
    while(W25Q_ReadStatus()&0x01);   // 等待擦除完成  
} 

程序清单:按块擦除数据函数

/************************************************************************* 
 * 描  述 : 擦除一块擦除( 64K) 
 * 入  参 : uint32 Addr24  扇区地址 
 * 返回值 : 无 
**************************************************************************/  
void W25Q_BlockErase(uint32 Addr24)  //擦除资料图示的64KB空间  
{  
    uint8 Addr1;       // 最低地址字节  
    uint8 Addr2;       // 中间地址字节  
    uint8 Addr3;       // 最高地址字节    
    Addr1=Addr24;  
    Addr24=Addr24>>8;  
    Addr2=Addr24;  
    Addr24=Addr24>>8;  
    Addr3=Addr24;                      // 把地址拆开来    
    while(W25Q_ReadStatus()&0x01);     // 判断是否忙     
    WriteEnable();                     // 写允许  
    SPI_CS_0;  
    SPI_SendByte(W25X_B_Erase);       // 整扇擦除命令  
    SPI_SendByte(Addr3);  
    SPI_SendByte(Addr2);  
    SPI_SendByte(Addr1);  
    SPI_CS_1;  
    while(W25Q_ReadStatus()&0x01);   // 等待擦除完成  
}  

程序清单:擦除整片芯片函数

/*********************************************************************** 
 * 描  述 : 擦除整片芯片 
 * 入  参 : 无 
 * 返回值 : 无 
备注:不同型号的芯片时间不一样  
*************************************************************************/  
void W25Q_ChipErase(void)  
{  
    while(W25Q_ReadStatus()&0x01);     // 判断是否忙     
    WriteEnable();                     // 写允许  
    SPI_CS_0;  
    SPI_SendByte(W25X_C_Erase);       // 整片擦除命令  
    SPI_CS_1;                          // 从CS=1时开始执行擦除  
    while(W25Q_ReadStatus()&0x01);     // 等待擦除完成     
}

最后,在主函数中对串口1、SPI外设进行初始化,并通过串口1发送不同的命令实现对单片机片外FLASH的单字节读、写及擦除等操作。

代码清单:主函数

int main()  
{  
    uint16  Temp;  
    uint8   Temp1,Temp2;  
      
    P3M1 &= 0xFE;   P3M0 &= 0xFE;                     //设置P3.0为准双向口  
    P3M1 &= 0xFD;   P3M0 |= 0x02;                     //设置P3.1为推挽输出  
      
    SPI_CS_1;                                     //SPI使能引脚初始化  
    Init_SPI();                                     //初始化SPI  
    Uart1_Init();                                 //串口1初始化    
    EA = 1;                                       //使能总中断  
  delay_ms(10);                                 //初始化后延时  
      
    Temp=W25Q_ReadID();                           //读取外扩存储器芯片ID  
  Temp1=(uint8)(Temp/256);                      //将读取的存储器芯片ID高字节存放到Temp1中  
    Temp2=(uint8)Temp;                            //将读取的存储器芯片ID低字节存放到Temp2中  
      
  while (1)  
    {  
        if(W_ID)                           //读存储器芯片ID模式  
        {  
              W_ID=0;                            //ID标志变量清零,发送一次  
              SendDataByUart1(Temp1);            //串口1发送芯片ID高字节  
              SendDataByUart1(Temp2);            //串口1发送芯片ID低字节  
         }  
     if(WriteFLAG)                     //写模式  
         {  
              WriteFLAG=0;                      //写标志变量清零,发送一次  
              W25Q_Write_N(scan,0x00000010,1);  //在0x00000010地址开始写入1字节scan中的数据  
        SendDataByUart1(0x33);            //串口1发送数据0x33  
         }  
         if(ReadFLAG)                      //读模式  
         {  
              ReadFLAG=0;                         //读标志变量清零,发送一次  
              W25Q_Read_N(buffer,0x00000010,1);   //读0x00000010地址开始的1字节数据到buffer中  
        SendStringByUart1_n(buffer,1);      //串口1发送buffer中存的数据  
         }    
         if(ClearFLAG)                     //扇区擦除模式  
         {  
              ClearFLAG=0;                        //清除标志变量清零,发送一次  
              W25Q_SectorErase(0x00000010);       //对0x00000010地址所在的扇区进行扇区擦除  
        SendDataByUart1(0x00);              //串口1发送数据0x00  
         }     
    }  
} 

3.4.硬件连接

在这里插入图片描述

图8:开发板连接图

4.外接FLASH存储器读写多字节实验(硬件SPI)

4.1.工程需要用到的c文件

本实验需要用到的头文件以及添加头文件包含路径的方法请参考“实验2-16-3:外接FLASH存储器读写单字节实验(硬件SPI)”部分。

4.2.编写代码

首先,在w25q128.c文件中编写硬件SPI方式的读写字节函数和对FLASH存储器的基本操作函数。请参考“实验2-16-3:外接FLASH存储器读写单字节实验(硬件SPI)”部分。

然后,在主函数中对串口1和SPI进行初始化,并通过串口1发送不同的命令实现对单片机片外FLASH的多字节读、写及擦除等操作。

代码清单:主函数

int main()  
{  
    uint16  Temp;  
    uint8   Temp1,Temp2;  
      
    P3M1 &= 0xFE;   P3M0 &= 0xFE;                     //设置P3.0为准双向口  
    P3M1 &= 0xFD;   P3M0 |= 0x02;                     //设置P3.1为推挽输出  
      
    SPI_CS_1;                                     //SPI使能引脚初始化  
    Init_SPI();                                     //初始化SPI  
    Uart1_Init();                                 //串口1初始化    
    EA = 1;                                       //使能总中断  
  delay_ms(10);                                 //初始化后延时  
      
    Temp=W25Q_ReadID();                           //读取外扩存储器芯片ID  
  Temp1=(uint8)(Temp/256);                      //将读取的存储器芯片ID高字节存放到Temp1中  
    Temp2=(uint8)Temp;                            //将读取的存储器芯片ID低字节存放到Temp2中  
      
  while (1)  
    {  
        if(W_ID)                           //读存储器芯片ID模式  
        {  
              W_ID=0;                            //ID标志变量清零,发送一次  
              SendDataByUart1(Temp1);            //串口1发送芯片ID高字节  
              SendDataByUart1(Temp2);            //串口1发送芯片ID低字节  
         }  
     if(WriteFLAG)                     //写模式  
         {  
              WriteFLAG=0;                      //写标志变量清零,发送一次  
              W25Q_SectorErase(0x00000000);       //擦除一个扇区(0x00000000在的扇区)  
              W25Q_Write_N(scan,0x00000000,10); //向FLASH地址0x00000000中写入scan数组中10个字节数据  
        SendDataByUart1(0x33);            //串口1发送数据0x33表示写操作完成  
         }  
         if(ReadFLAG)                      //读模式  
         {  
              ReadFLAG=0;                         //读标志变量清零,发送一次  
              W25Q_Read_N(buffer,0x00000000,10);      //从FLASH地址0x00000000开始读取10字节数据并存入到buffer数组中  
        SendStringByUart1_n(buffer,10);      //串口1发送数组buffer中的值(即读取的多字节数据)  
         }    
         if(ClearFLAG)                     //扇区擦除模式  
         {  
              ClearFLAG=0;                        //清除标志变量清零,发送一次  
              W25Q_SectorErase(0x00000000);       //擦除一个扇区(0x00000000在的扇区)  
        SendDataByUart1(0x00);              //串口1发送数据0x00表示擦除完成  
         }     
    }  
} 

4.3.硬件连接

在这里插入图片描述

图9:开发板连接图

总结

以上是今天要讲的内容,希望对大家有帮助,如果有啥不明白的,欢迎讨论哦!。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

电子友人张

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值