Flash存储器又称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还可以快速读取数据(NVRAM的优势),使数据不会因为断电而丢失。
FLASH闪存是属于内存器件的一种,“Flash”。闪存则是一种非易失性( Non-Volatile )内存,在没有电流供应的条件下也能够长久地保持数据,其存储特性相当于硬盘,这项特性正是闪存得以成为各类便携型数字设备的存储介质的基础。
闪存是一种特殊的、以宏块抹写的EPROM。早期的闪存进行一次抹除,就会清除掉整颗芯片上的数据。
各类 DDR 、 SDRAM 或者 RDRAM 都属于挥发性内存,只要停止电流供应内存中的数据便无法保持,因此每次电脑开机都需要把数据重新载入内存。而闪存是一种非易失性存储器,即断电数据也不会丢失。因为闪存不像RAM(随机存取存储器)一样以字节为单位改写数据,因此不能取代RAM。——百度百科
在1984年,东芝公司的发明人舛冈富士雄首先提出了快速闪存存储器(此处简称闪存)的概念。与传统电脑内存不同,闪存的特点是非易失性(也就是所存储的数据在主机掉电后不会丢失),其记录速度也非常快。
Intel是世界上第一个生产闪存并将其投放市场的公司。1988年,公司推出了一款256K bit闪存芯片。它如同鞋盒一样大小,并被内嵌于一个录音机里。後来,Intel发明的这类闪存被统称为NOR闪存。它结合EPROM(可擦除可编程只读存储器)和EEPROM(电可擦除可编程只读存储器)两项技术,并拥有一个SRAM接口。
第二种闪存称为NAND闪存。它由日立公司于1989年研制,并被认为是NOR闪存的理想替代者。NAND闪存的写周期比NOR闪存短90%,它的保存与删除处理的速度也相对较快。NAND的存储单元只有NOR的一半,在更小的存储空间中NAND获得了更好的性能。鉴于NAND出色的表现,它常常被应用于诸如CompactFlash、SmartMedia、 SD、 MMC、 xD、 and PC cards、USB sticks等存储卡上。——百度百科
1、IIC EEPROM------容量小,采用的是IIC通信协议;用于在掉电时,存系统配置参数,比如屏幕亮度等。常用芯片型号有 AT24C02、FM24C02、CAT24C02等,其常见的封装多为DIP8,SOP8,TSSOP8等;
2、SPI NorFlash------容量略大,采用的是SPI 通信协议;用于存放程序和数据。程序和数据可存放在同一芯片上,拥有独立的数据总线和地址总线,能快速随机读取,允许系统直接从Flash中读取代码执行;可以单字节或单字编程,但不能单字节擦除,必须以Sector为单位或对整片执行擦除操作。常见到的S25FL128、MX25L1605、W25Q64等型号都是SPI NorFlash。
3、SPI NandFlash------采用了SPI NorFlash一样的SPI的通信协议,用于存储数据;在读写的速度上没什么区别,但在存储结构上却采用了与Parallel NandFlash相同的结构,所以SPI nand相对于SPI norFlash具有擦写的次数多,擦写速度快的优势。
4、eMMC Flash------eMMC采用统一的MMC标准接口,eMMC相当于NandFlash+主控IC;自身集成MMC Controller,存储单元与NandFlash相同。常见到的KLMAG8DEDD、THGBMAG8B4JBAIM、EMMC04G-S100等型号都是eMMC Flash。
5、SD卡------它在MMC的基础上发展而来,有两个可选的通信协议:SD模式和SPI模式。——https://www.cnblogs.com/leo0621/p/8204852.html
NOR和NAND是市场上两种主要的非易失闪存技术,当选择存储解决方案时,设计师必须权衡以下的各项因素:
本文将以W25Q64为例,介绍Flash的基本知识和使用方法。
W25Q64是华邦公司推出的大容量SPI FLASH产品,其容量为64Mb(8M Bytes)。该25Q系列的器件在灵活性和性能方面远远超过普通的串行闪存器件。W25Q64将8M字节的容量分为128个块,每个块大小为64K字节,每个块又分为16个扇区,每个扇区4K个字节。W25Q64的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。所以,这需要给W25Q64开辟一个至少4K的缓存区,这样必须要求芯片有4K以上的SRAM才能有很好的操作。
W25Q64的擦写周期多达10W次,可将数据保存达20年之久,支持2.7~3.6V的电压,支持标准的SPI,还支持双输出/四输出的SPI,最大SPI时钟可达80Mhz。——https://www.cnblogs.com/gongchuangsu/p/4850223.html
管脚功能对照表
功能说明
注:本文不研究双倍和四倍SPI
片选端(/CS)
SPI 片选(/CS)引脚使能和禁止芯片操作。当CS̅̅̅为高电平时,芯片未被选择,串行数据输出(DO、IO0、IO1、IO2 和 IO3)引脚为高阻态。未被选择时,芯片处于待机状态下的低功耗,除非芯片内部在擦除、编程。当/CS 变成低电平,芯片功耗将增长到正常工作,能够从芯片读写数据。上电后,在接收新的指令前,/CS 必须由高变为低电平。上电后,/CS 必须上升到 VCC,在/CS 接上拉电阻可以完成这个。
串行数据输入、输出和 IOs(DI、DO 和 IO0、IO1、IO2、IO3)
W25Q64、W25Q16 和 W25Q32 支持标准 SPI、双倍 SPI 和四倍 SPI。标准的 SPI 传输用单向的 DI(输入)引脚连续的写命令、地址或者数据在串行时钟(CLK)的上升沿时写入到芯片内。标准的SPI 用单向的 DO(输出)在 CLK 的下降沿从芯片内读出数据或状态。
双倍和四倍 SPI 指令用双向的 IO 引脚在 CLK 的上升沿来连续的写指令、地址或者数据到芯片内,在 CLK 的下降沿从芯片内读出数据或者状态。四倍 SPI 指令操作时要求在状态寄存器 2 中的四倍使能位(QE)一直是置位状态。当 QE=1 时/WP 引脚变为 IO2,/HOLD 引脚变为 IO3。
写保护(/WP)
写保护引脚(/WP)用来保护状态寄存器。和状态寄存器的块保护位(SEC、TB、BP2、BP1 和BP0)和状态寄存器保护位(SRP)对存储器进行一部分或者全部的硬件保护。/WP 引脚低电平有效。当状态寄存器 2 的 QE 位被置位了,/WP 引脚(硬件写保护)的功能不可用,被用作了 IO2。
保持端(/HOLD)
当/HOLD 引脚是有效时,允许芯片暂停工作。在/CS 为低电平时,当/HOLD 变为低电平,DO 引脚将变为高阻态,在 DI 和 CLK 引脚上的信号将无效。当/HOLD 变为高电平,芯片恢复工作。/HOLD功能用在当有多个设备共享同一 SPI 总线。/HOLD 引脚低电平有效。当状态寄存器 2 的 QE 位被置位了,/ HOLD 引脚的功能不可用,被用作了 IO3。
串行时钟(CLK)
串行时钟输入引脚为串行输入和输出操作提供时序。(见 SPI 操作),设备数据传输是从高位开 始,数据传输的格式为 8bit,数据采样从第二个时间边沿开始,空闲状态时,时钟线 clk 为高电平。
——W25Q64中文手册
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议。
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是MISO(主设备数据输入)、MOSI(主设备数据输出)、SCLK(时钟)、CS(片选)。
(1)MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;
(2)MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;
(3)SCLK – Serial Clock,时钟信号,由主设备产生;
(4)CS – Chip Select,从设备使能信号,由主设备控制。
其中,CS是从芯片是否被主芯片选中的控制信号,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),主芯片对此从芯片的操作才有效。这就使在同一条总线上连接多个SPI设备成为可能。
接下来就负责通讯的3根线了。通讯是通过数据交换完成的,这里先要知道SPI是串行通讯协议,也就是说数据是一位一位的传输的。这就是SCLK时钟线存在的原因,由SCLK提供时钟脉冲,SDI,SDO则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。因此,至少需要8次时钟信号的改变(上沿和下沿为一次),才能完成8位数据的传输。
时钟信号线SCLK只能由主设备控制,从设备不能控制。同样,在一个基于SPI的设备中,至少有一个主设备。这样的传输方式有一个优点,在数据位的传输过程中可以暂停,也就是时钟的周期可以为不等宽,因为时钟线由主设备控制,当没有时钟跳变时,从设备不采集或传送数据。SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。芯片集成的SPI串行同步时钟极性和相位可以通过寄存器配置,IO模拟的SPI串行同步时钟需要根据从设备支持的时钟极性和相位来通讯。
最后,SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。——百度百科
SPI总线传输一共有4种模式,这4种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义,其中CPOL参数规定了SCK时钟信号空闲状态的电平,CPHA规定了数据是在SCK时钟的上升沿被采样还是下降沿被采样。
CPOL= 0,CPHA=0
SCK串行时钟线空闲是为低电平,数据在SCK时钟的前边沿(上升沿)被采样,在SCK时钟的后边沿(下降沿)切换电平状态;CPOL= 0,CPHA=1
SCK串行时钟线空闲是为低电平,数据在SCK时钟的后边沿(下降沿)被采样,在SCK时钟的前边沿(上升沿)切换电平状态;CPOL= 1,CPHA=0
SCK串行时钟线空闲是为高电平,数据在SCK时钟的前边沿(下降沿)被采样,在SCK时钟的后边沿(上升沿)切换电平状态;CPOL= 1,CPHA=1
SCK串行时钟线空闲是为高电平,数据在SCK时钟的后边沿(上升沿)被采样,在SCK时钟的前边沿(下降沿)切换电平状态。
根据前面SPI模式的内容,可以看出上图为模式1(CPOL=0, CPHA=1)
标号 1 处,NSS(CS)信号线由高变低,是SPI通讯的起始信号。NSS是每个从机各自独占的信号线,当从机检在自己的NSS线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。
在图中的标号 6 处,NSS信号由低变高,是SPI通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。
SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同时进行的。——野火电子PPT
支持 SPI 总线的工作模式 0(0,0)和 3(1,1)。模式 0 和模式 3 的主要区别在于常态时的 CLK信号,当 SPI 主机已准备好数据还没传输到串行 Flash 中,对于模式 0 CLK 信号常态为低。
设备数据传输是从高位开始,数据传输的格式为8bit
,数据采样从第二个时间边沿开始,空闲状态时,时钟线 CLK 为高电平。——W25Q64中文手册
指令表1:
忙是只读的状态寄存器(S0)被设置为1状态时,表示设备正在执行程序(可能是在擦除芯片)或写状态寄存器指令。这个时候设备将忽略传来的指令, 除了读状态寄存器和擦除暂停指令,写指令或写状态指令无效,当 S0 为 0 状态时,指示设备已经执行完毕,可以进行下一步操作。
——W25Q64中文手册
读取状态寄存器的指令是 8 位的指令。发送指令之前,先将/CS 拉低,再发送指令码“05h”或者“35h”。设备收到读取状态寄存器的指令后,将状态信息(高位)依次移位发送出去,如上图所示。
读出的状态信息,最低位为 1 代表忙,最低位为 0 代表可以操作,状态信息读取完毕,将片选线拉高。
读状态寄存器指令可以使用在任何时候,即使程序在擦除的过程中或者写状态寄存器周期正在进行中。这可以检测忙碌状态来确定周期是否完成,以确定设备是否可以接受另一个指令。——W25Q64中文手册
读取数据指令允许按顺序读取一个字节的内存数据。当片选 CS/拉低之后,紧随其后是一个 24 位的地址(A23-A0)(需要发送 3 次,每次 8 个字节,先发高位)。芯片收到地址后,将要读的数据按字节大小转移出去,数据是先转移高位,对于单片机,时钟下降沿发送数据,上升沿接收数据。读数据时,地址会自动增加,允许连续的读取数据。这意味着读取整个内存的数据,只要用一个指令就可以读完。数据读取完成之后,片选信号/ CS 拉高。
读取数据的指令序列,如上图所示。如果一个读数据指令而发出的时候,设备正在擦除扇区,或者(BUSY = 1),该读指令将被忽略,也不会对当前周期有什么影响。——W25Q64中文手册
页编程指令允许从一个字节到 256 字节的数据编程(一页)(编程之前必须保证内存空间是 0XFF)。允许写入指令之前,必须先发送设备写使能指令。写使能开启后,设备才能接收编程指令。开启页编程先拉底/ CS,然后发送指令代码“02h”,接着发送一个 24 位地址(A23-A0)(发送 3 次,每次 8 位) 和至少一个数据字节(数据字节不能超过 256字节)。数据字节发送完毕,需要拉高片选线 /CS,并判断状态位,等待写入结束。
进行页编程时,如果数据字节数超过了 256 字节,地址将自动回到页的起始地址,覆盖掉之前的数据。在某些情况下,数据字节小于 256 字节(同一页内),也可以正常对其他字节存放,不会有任何影响。如果存放超过 256 字节的数据,需要分次编程存放。——W25Q64中文手册
扇区擦除指令可以擦除指定一个扇区(4 k 字节)内所有数据,将内存空间恢复到 0xFF 状态。写入扇区擦除指令之前必须执行设备写使能(发送设备写使能指令 0x06),并判断状态寄存器(状态寄存器位最低位必须等于 0 才能操作)。发送的扇区擦除指令前,先拉低/ CS,接着发送扇区擦除指令码”20h”,和 24 位地址(A23-A0),地址发送完毕后,拉高片选线 /CS,并判断状态位,等待擦除结束。擦除一个扇区的最少需要 150ms 时间。
——W25Q64中文手册
全芯片擦除指令,可以将整个芯片的所有内存数据擦除,恢复到 0XFF 状态。写入全芯片擦除指令之前必须执行设备写使能(发送设备写使能指令 0x06),并判断状态寄存器(状态寄存器位最低位必须等于 0 才能操作)。发送全芯片擦除指令前,先拉低/ CS,接着发送擦除指令码”C7h”或者是”60h”, 指令码发送完毕后,拉高片选线 /CS,并判断状态位,等待擦除结束。全片擦除指令尽量少用,擦除会缩短设备的寿命。
——W25Q64中文手册
读取制造商/设备 ID 指令可以读取制造商 ID 和特定的设备 ID。读取之间,拉低 CS 片选信号,接着发送指令代码“90h” ,紧随其后的是一个 24 位地址(A23-A0)000000h。 之后,设备发出,华邦电子制造商 ID(EFh)和设备ID(w25q64 为 16h)。如果 24 位地址设置为 000001h 的设备 ID 会先发出,然后跟着制造商 ID。制造商和设备 ID 可以连续读取。完成指令后,片选信号/CS 拉高。
——W25Q64中文手册
以下代码仅供参考,属于我练手的代码,并不能保证没有Bug
大部分51单片机没有自带的SPI接口,所以这里使用纯软件代码实现SPI协议。
51单片机型号——AT89C52RC
由于我没有现成的W25Q64芯片,所以只能用Proteus进行仿真,但是Proteus里只有AT25FX型号的SPI Flash,好在它们的读写操作指令基本相同,只有部分功能(读ID、擦写等)存在差异。
SPI读写字节
关键函数,同时进行SPI的写和读,采用SPI模式3(CPOL=1,CPHA=1)。
/******************************************************************************
* @ 函数名 : Spi_Write_Read_One_Byte
* @ 功 能 : SPI读写一个字节
* @ 参 数 : tx_data 要写入的字节
* @ 返回值 : 读出的字节
* @ 备 注 : 核心函数
******************************************************************************/
static unsigned char Spi_Write_Read_One_Byte(unsigned char tx_data)
{
unsigned char i = 0;
unsigned char rx_data = 0;
for(i = 0; i < 8; i++)
{
SCK = 0; // 时钟电平转换
if(tx_data & 0x80)
SO = 1; // 输出高电平
else
SO = 0;
rx_data <<= 1;
SCK = 1; // 时钟电平转换
if(SI)
rx_data++; // 最低位设为1
tx_data <<= 1;
}
return rx_data; // 返回读到字节
}
写使能
/******************************************************************************
* @ 函数名 : Spi_Write_Enable
* @ 功 能 : 写使能
* @ 参 数 : 无
* @ 返回值 : 无
******************************************************************************/
static void Spi_Write_Enable(void)
{
CS = 0; //选中芯片
Spi_Write_Read_One_Byte(WRITE_ENABLE); //写使能
CS = 1; //释放芯片
}
等待Flash写入或擦除完成
/******************************************************************************
* @ 函数名 : Flash_Wait_For_Written
* @ 功 能 : 等待FLASH写完
* @ 参 数 : 无
* @ 返回值 : 无
******************************************************************************/
static void Flash_Wait_For_Written(void)
{
unsigned char rx_data = 0;
CS = 0; //选中芯片
Spi_Write_Read_One_Byte(READ_STATUS_REG); //读取状态寄存器
do
{
rx_data = Spi_Write_Read_One_Byte(0);
}while((rx_data & 0x01) == 1);
CS = 1; //释放芯片
}
Flash读数据
/******************************************************************************
* @ 函数名 : Flash_Read_Data
* @ 功 能 : 读取存储器数据
* @ 参 数 :
buff 读出的数据
addr 开始读取的地址
len 要读取的长度
* @ 返回值 : 无
******************************************************************************/
void Flash_Read_Data(unsigned char *buff, unsigned long addr, unsigned int len)
{
CS = 0; //选中芯片
Spi_Write_Read_One_Byte(READ_DATA); //发送读取命令
Spi_Write_Read_One_Byte((addr >> 16) & 0xff);
Spi_Write_Read_One_Byte((addr >> 8) & 0xff);
Spi_Write_Read_One_Byte(addr & 0xff);
while(len--)
{
*buff++ = Spi_Write_Read_One_Byte(0);
}
CS = 1; //释放芯片
}
Flash页写
/******************************************************************************
* @ 函数名 : Flash_Write_Data
* @ 功 能 : 向存储器写入数据(最大写一页)
* @ 参 数 :
buff 读出的数据
addr 开始读取的地址
len 要读取的长度
* @ 返回值 : 无
******************************************************************************/
void Flash_Write_Data(unsigned char *buff, unsigned long addr, unsigned int len)
{
Spi_Write_Enable(); //写使能
CS = 0; //选中芯片
Spi_Write_Read_One_Byte(WRITE_DATA); //发送页写命令
Spi_Write_Read_One_Byte((addr >> 16) & 0xff);
Spi_Write_Read_One_Byte((addr >> 8) & 0xff);
Spi_Write_Read_One_Byte(addr & 0xff);
while(len--)
{
Spi_Write_Read_One_Byte(*buff++);
}
CS = 1; //释放芯片
Flash_Wait_For_Written(); //等待写完成
}
扇区擦除
只有Flash存储空间数据为0xFF时才能修改Flash的数据,所以每次写入前需要进行擦除。
/******************************************************************************
* @ 函数名 : Flash_Erase_Sector
* @ 功 能 : 擦除FLASH指定扇区
* @ 参 数 : addr 开始读取的地址
* @ 返回值 : 无
******************************************************************************/
void Flash_Erase_Sector(unsigned long addr)
{
Spi_Write_Enable(); //写使能
CS = 0; //选中芯片
Spi_Write_Read_One_Byte(ERASE_SECTOR); //发送扇区擦除命令
Spi_Write_Read_One_Byte((addr >> 16) & 0xff);
Spi_Write_Read_One_Byte((addr >> 8) & 0xff);
Spi_Write_Read_One_Byte(addr & 0xff);
CS = 1; //释放芯片
Flash_Wait_For_Written(); //等待擦写完成
}
Flash还有其他高级操作,比如跨页写,这里就不进行讲述,只实现简单的读写功能。
这里用上面的代码仿真一个小实验,完整工程代码见文末
实现功能:先将 AT25F1024(1Mb)的第一个扇区进行擦除,再向 0 号地址空间写入 8 个16进制数据,最后从 AT25F1024 的 0 号地址空间写读出 8 个字节数据,同时显示在数码管上。
Proteus仿真
第一步擦除实际用时40多秒,但Proteus中的时序分析显示用时1s,可能是给虚拟机的内存不够,导致运行速度受限。
实验现象:
完整代码
本人水平有限,仅供参考。
为方便展示,所有代码写在同一个文件
#include //此文件中定义了单片机的一些特殊功能寄存器
unsigned char code coding[16]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//显示0-F的值
#define LED_TUBE P3 //P3的8个IO端口对应数码管的8个信号引脚
#define WRITE_ENABLE 0x06 //写芯片使能,在指令或数据前先要使能
#define WRITE_DISABLE 0x04 //禁止写芯片
#define READ_STATUS_REG 0x05 //读状态寄存器
#define READ_DATA 0x03 //读取存储器数据
#define WRITE_DATA 0x02 //页编程
#define ERASE_SECTOR 0x52 //扇区擦除 w25qxx为0x20
#define CHIP_ERASE 0x62 //全片擦除 w25qxx为0xc7/0x60
#define READ_ID 0x15 //读取芯片ID w25qxx为0x90
sbit SCK = P1^0; //将SCK位定义为P1.0引脚
sbit SO = P1^1; //将SI位定义为P1.1引脚
sbit SI = P1^2; //将SO位定义为P1.2引脚
sbit CS = P1^3; //将CS定义为P1.3引脚
/******************************************************************************
* @ 函数名 : Spi_Write_Read_One_Byte
* @ 功 能 : SPI读写一个字节
* @ 参 数 : tx_data 要写入的字节
* @ 返回值 : 读出的字节
* @ 备 注 : 核心函数
******************************************************************************/
static unsigned char Spi_Write_Read_One_Byte(unsigned char tx_data)
{
unsigned char i = 0;
unsigned char rx_data = 0;
for(i = 0; i < 8; i++)
{
SCK = 0; // 时钟电平转换
if(tx_data & 0x80)
SO = 1; // 输出高电平
else
SO = 0;
rx_data <<= 1;
SCK = 1; // 时钟电平转换
if(SI)
rx_data++; // 最低位设为1
tx_data <<= 1;
}
return rx_data; // 返回读到字节
}
/******************************************************************************
* @ 函数名 : Spi_Write_Enable
* @ 功 能 : 写使能
* @ 参 数 : 无
* @ 返回值 : 无
******************************************************************************/
static void Spi_Write_Enable(void)
{
CS = 0; //选中芯片
Spi_Write_Read_One_Byte(WRITE_ENABLE); //写使能
CS = 1; //释放芯片
}
/******************************************************************************
* @ 函数名 : Flash_Wait_For_Written
* @ 功 能 : 等待FLASH写完
* @ 参 数 : 无
* @ 返回值 : 无
******************************************************************************/
static void Flash_Wait_For_Written(void)
{
unsigned char rx_data = 0;
CS = 0; //选中芯片
Spi_Write_Read_One_Byte(READ_STATUS_REG); //读取状态寄存器
do
{
rx_data = Spi_Write_Read_One_Byte(0);
}while((rx_data & 0x01) == 1);
CS = 1; //释放芯片
}
/******************************************************************************
* @ 函数名 : Flash_Read_Data
* @ 功 能 : 读取存储器数据
* @ 参 数 :
buff 读出的数据
addr 开始读取的地址
len 要读取的长度
* @ 返回值 : 无
******************************************************************************/
void Flash_Read_Data(unsigned char *buff, unsigned long addr, unsigned int len)
{
CS = 0; //选中芯片
Spi_Write_Read_One_Byte(READ_DATA); //发送读取命令
Spi_Write_Read_One_Byte((addr >> 16) & 0xff);
Spi_Write_Read_One_Byte((addr >> 8) & 0xff);
Spi_Write_Read_One_Byte(addr & 0xff);
while(len--)
{
*buff++ = Spi_Write_Read_One_Byte(0);
}
CS = 1; //释放芯片
}
/******************************************************************************
* @ 函数名 : Flash_Write_Data
* @ 功 能 : 向存储器写入数据(最大写一页)
* @ 参 数 :
buff 读出的数据
addr 开始读取的地址
len 要读取的长度
* @ 返回值 : 无
******************************************************************************/
void Flash_Write_Data(unsigned char *buff, unsigned long addr, unsigned int len)
{
Spi_Write_Enable(); //写使能
CS = 0; //选中芯片
Spi_Write_Read_One_Byte(WRITE_DATA); //发送页写命令
Spi_Write_Read_One_Byte((addr >> 16) & 0xff);
Spi_Write_Read_One_Byte((addr >> 8) & 0xff);
Spi_Write_Read_One_Byte(addr & 0xff);
while(len--)
{
Spi_Write_Read_One_Byte(*buff++);
}
CS = 1; //释放芯片
Flash_Wait_For_Written(); //等待写完成
}
/******************************************************************************
* @ 函数名 : Flash_Erase_Sector
* @ 功 能 : 擦除FLASH指定扇区
* @ 参 数 : addr 开始读取的地址
* @ 返回值 : 无
******************************************************************************/
void Flash_Erase_Sector(unsigned long addr)
{
Spi_Write_Enable(); //写使能
CS = 0; //选中芯片
Spi_Write_Read_One_Byte(ERASE_SECTOR); //发送扇区擦除命令
Spi_Write_Read_One_Byte((addr >> 16) & 0xff);
Spi_Write_Read_One_Byte((addr >> 8) & 0xff);
Spi_Write_Read_One_Byte(addr & 0xff);
CS = 1; //释放芯片
Flash_Wait_For_Written(); //等待擦写完成
}
/******************************************************************************
* @ 函数名 : Delay_10us
* @ 功 能 : 10us粗略延时
* @ 参 数 : 延时时间--单位10us
* @ 返回值 : 无
******************************************************************************/
void Delay_10us(unsigned int time)
{
while(time--);
}
/******************************************************************************
* @ 函数名 : main
* @ 功 能 : 主函数
* @ 参 数 : 无
* @ 返回值 : 无
******************************************************************************/
int main()
{
unsigned char i = 0;
unsigned char tx_buff[20] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
unsigned char rx_buff[20] = {0};
SCK = 1; //时钟空闲电平为高
CS = 1; //释放芯片
Flash_Erase_Sector(0); //擦除第一个扇区
Flash_Write_Data(tx_buff, 0, 8); //向0号地址写入8个字节
Flash_Read_Data(rx_buff, 0, 8); //从0号地址读出8个字节
while(1)
{
for(i = 0; i < 8; i++)
{
//将从Flash读出的数据显示在数码管上
LED_TUBE = coding[rx_buff[i]];
//粗略延时500ms
Delay_10us(50000);
}
}
}