版权声明:本文为博主原创文章,转载请附上原文出处链接。
今天介绍下STC8A8K64S4A12系列单片机外部FLASH存储器W25Q128引脚定义和硬件电路设计,掌握通过STC8A8K64S4A12系列单片机SPI外设对W25Q128的程序设计。
FLASH存储器属内存器件的一种,是一种非易失性( Non-Volatile )内存。该存储器结合了ROM和RAM的长处,不仅具备电可擦除可编程(EEPROM)的性能,还不会断电丢失数据,同时可以快速读取数据,所以现如今大多数单片机片内都集成了这种存储器。
接下来,我们主要介绍的是外部的FLASH存储器芯片,外部的FLASH存储器芯片种类和生产厂家都比较多。其中WINBOND(华邦)公司生产的W25Q系列存储器应用很广泛,是我们介绍的重点。W25Q系列存储器相比于普通的串行FLASH器件提供了更好的灵活性和性能,该系列存储器可在2.7V到3.6V的供电电压下工作(注意芯片后缀JV),工作时电流消耗最低4mA,在睡眠模式下消耗仅1uA。另外,所有的W25Q系列存储器芯片都提供节省空间的封装。
WINBOND(华邦)公司生产的W25Q系列存储器支持标准SPI接口,操作寄存器指令兼容,但不同存储空间的芯片有不同的ID,具体如下表。
序号 | 芯片型号 | 存储空间 | 供电电压 | 芯片ID | 工作温度 | 备注 |
---|---|---|---|---|---|---|
1 | W25Q16JV | 16M bit | 2.7V~3.6V | 0xEF14 | -40℃~85℃ | |
2 | W25Q32JV | 32M bit | 2.7V~3.6V | 0xEF15 | -40℃~85℃ | |
3 | W25Q64JV | 64M bit | 2.7V~3.6V | 0xEF16 | -40℃~85℃ | |
4 | W25Q128JV | 128M bit | 2.7V~3.6V | 0xEF17 | -40℃~85℃ | |
5 | W25Q256JV | 256M bit | 2.7V~3.6V | 0xEF18 | -40℃~85℃ | |
6 | W25Q512JV | 512M bit | 2.7V~3.6V | 0xEF19 | -40℃~85℃ |
☆注:芯片ID中的高8位0xEF代表的Winbond Serial Flash系列。厂家还有1G bit和2G bit的存储器芯片W25Q01JV和W25Q02JV,有需要更大存储空间可以考虑。
W25Q128JV支持标准的SPI接口,双线/四线IO模式SPI:串行时钟,数据选择,串行数据IO0(DI),IO1(DO),IO2(/WP),IO3(/HOLD)。
SPI时钟频率最高可达133MHz,因此在双IO SPI模式下等效于266MHz,在四IO SPI/QPI模式下等效于532MHz。这些传输速率比标准的异步8位或者16位并行FLASH存储器表现得更好。
STC8A8K64S4A12开发板上设计了可供用户焊接使用的W25Q系列存储器芯片的接口,其中原理图部分及硬件实物部分如下。
W25Q128JV存储芯片是由65536可编程的页组成的,每页有256个字节。一次最多可以写256个字节。可以一次擦除16页(4K字节)、128页(32K字节)、256页(64K字节)或者一整片。W25Q128JV有4096个可擦除的扇区,256个可擦除的块。4K字节的扇区对于数据和参数存储有更高的灵活性。
■ W25Q128JV内部框图
☆注:W25Q128JV存储器实际的存储空间会略大于128M bit的,因为比如4096字节都按照4K来计算的。其他W25Q系列存储器的页、扇区、块计算都和上面W25Q128JV内部框图一样,不一样的是不同的存储器块数不一样。比如,W25Q64JV存储器只有128个块,也就是8M字节存储空间(即64M bit)。
■ 使用注意事项
☆注:有些用户说我没有擦除直接写的,第一次没有问题,之后怎么出错了?想一想什么原因。(往往第一次是出厂的默认的每个空间位都是1,所以写操作没有问题)。
关于STC8A8K64S4A12系列SPI外设原理及相关寄存器的介绍请参考外接FRAM存储器实验部分,对外接FLASH存储器的SPI配置是完全一样的。
本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。
序号 | 文件名 | 后缀 | 功能描述 |
---|---|---|---|
1 | uart | .c | 包含与用户uart有关的用户自定义函数。 |
2 | W25q128 | .c | SPI通信及操作FLASH有关的用户自定义函数。 |
3 | delay | .c | 包含用户自定义延时函数。 |
■ 需要引用的头文件
#include "delay.h"
#include "uart.h"
#include " w25q128.h"
■ 需要包含的头文件路径
本例需要包含的头文件路径如下表:
序号 | 路径 | 描述 |
---|---|---|
1 | …\ Source | uart.h、w25q128.h和delay.h头文件在该路径,所以要包含。 |
2 | …\Use | STC8.h头文件在该路径,所以要包含。 |
MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。
首先,在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存储器的基本操作函数,如下表所示。
关于每个操作外部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
}
}
}
本实验需要用到的头文件以及添加头文件包含路径的方法请参考“实验2-16-1:外接FLASH存储器读写单字节实验(模拟SPI)”部分。
首先,在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表示擦除完成
}
}
}
本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。
序号 | 文件名 | 后缀 | 功能描述 |
---|---|---|---|
1 | uart | .c | 包含与用户uart有关的用户自定义函数。 |
2 | w25q128 | .c | SPI通信及操作FLASH有关的用户自定义函数。 |
3 | delay | .c | 包含用户自定义延时函数。 |
■ 需要引用的头文件
#include "delay.h"
#include "uart.h"
#include " w25q128.h"
■ 需要包含的头文件路径
本例需要包含的头文件路径如下表:
序号 | 路径 | 描述 |
---|---|---|
1 | …\ Source | uart.h、w25q128.h和delay.h头文件在该路径,所以要包含。 |
2 | …\Use | STC8.h头文件在该路径,所以要包含。 |
MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。
首先,在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存储器的基本操作函数,如下表所示。
关于每个操作外部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
}
}
}
本实验需要用到的头文件以及添加头文件包含路径的方法请参考“实验2-16-3:外接FLASH存储器读写单字节实验(硬件SPI)”部分。
首先,在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表示擦除完成
}
}
}
以上是今天要讲的内容,希望对大家有帮助,如果有啥不明白的,欢迎讨论哦!。