W25Q128FV串行Flash存储器由65536页组成,每一页256字节,总共128Mbit,相当于16M字节的存储空间。一次写入可操作高达256字节,擦除可以按16个页擦除(即一个Sector),128个页擦除(八个Sector),256个页擦除(16个Sector),或者整片擦除。标准SPI通信支持时钟频率高达104MHz,Dual SPI通信支持时钟频率高达208MHz,QSPI通信支持时钟频率高达416MHz。
本例使用NXP公司提供的LPC21XX平台的SPI控制器与串行Flash存储器W25Q128FV进行通信,实现对体量较大的数据进行存储。
概念说明
- bit/Byte/Page/Sector:电脑是以二进制存储以及发送接收数据的。二进制的一位,就叫做 1 bit。也就是说 bit 的含义就是二进制数中的一个数位,即 “0” 或者 "1"。Byte 是字节的英文写法。它的简写为大写字母 “B",1 Byte = 8 bit。在本例中Page(页)是256个字节,Sector(扇区)是16个页。
- SPI是一个同步的数据总线,也就是说它是用单独的数据线和一个单独的时钟信号来保证发送端和接收端的完美同步。产生时钟的一侧称为主机,另一侧称为从机。总是只有一个主机(一般来说可以是微控制器/MCU),但是可以有多个从机,W25Q128FV在本实现中即为从机。具体的通信时序如下:
- 芯片命名规则:华邦的 SPI flash 型号(丝印)一般格式为(只看当前最新的 W25Q 系列):{[W25Q] [xxx] [y] [z] [XX] [Y] [Z]},
- xxx表示容量(单位 bit),比如 16表示 16Mbit(2Mbyte),256表示 256Mbit (32Mbyte)。
- y 表示第几代,按字母表顺序排列,比如 J 比 F的肯定要新,一般情况下,越新的支持的速度越高
- z表示器件的电压范围:
-
L:2.3V - 3.6V
-
V:2,7V - 3.6V
-
W:1.65/1,7V - 1.95V
-
-
XX表示 封装类型:
-
SS = 8-pin SOIC 208-mil
-
ZP = WSON8 6x5-mm
-
XG = XSON 4x4x0.45-mm
-
SF = 16-pin SOIC 300-mil
-
TB = TFBGA 8x6-mm (5x5 ball array)
-
TC = TFBGA 8x6-mm (6x4 ball array)
-
-
Y 表示温度范围:
-
I = Industrial (-40°C to +85°C)
-
J = Industrial Plus (-40°C to +105°C)
-
实现原理
嵌入式代码运行在LPC21xx平台上,使用SPI控制器与Flash存储器进行通信,未使用芯片提供的写保护功能,原理示意图如下:
嵌入式代码
嵌入式代码由两部分构成:
- LPC21XX平台SPI控制器初始化
- W25Q128FV芯片Flash操作接口封装
如上所述,我们第一步的工作需要将SPI控制器按照正确的通信速率进行初始化,并为后边Flash的读写操作提供SPI收发接口,代码如下:
#include <lpc213x.h>
#include "spi.h"
#include "serial.h"
#define SPI_DEBUG (1)
#if 0
void spi0_isr(void) __irq
{
unsigned char status;
unsigned char recv_byte;
if ((VICIRQStatus & 0x400) && (S0SPINT & 0x1))
{
/* Interrupt Occurs */
status = S0SPSR;
if (status & 0x78)
{
/* ABRT/MODF/ROVR/WCOL exceptions */
sendstr("spi bus 0 exception, status=");
sendhex(status);
sendstr("\n");
}
else if (status == 0x80)
{
/* SPIF indicates SPI Transfer Completed,
** read or write SPI Data Reg clear SPIF
*/
recv_byte = S0SPDR;
sendstr("spi bus 0 recv ");
sendhex(recv_byte);
sendstr("\n");
}
/* Clear Interrupt */
S0SPINT = 0x1;
}
}
#endif
void spi_init(void)
{
/* Configure P0.4~7 as SPI bus 0 */
PINSEL0 &= ~0x0000FF00;
PINSEL0 |= 0x00005500;
/* PCLK is 30MHz, SPI Clock Frequency is 5MHz */
S0SPCCR = 0x6;
/* Master mode, 8 bits per transfer(MSB first),
** disable interrupt, CPOL=0, CPHA=0
*/
S0SPCR = 0x20;
#if 0
/* SPI Bus 0 ISR */
VICVectCntl2 = 0x20 | 10;
VICVectAddr2 = (unsigned int)spi0_isr;
VICIntEnable = 1 << 10;
#endif
}
int spi_write_byte(unsigned char byte)
{
unsigned char recv_byte;
if (SPI_DEBUG)
{
sendstr("spi bus 0 send ");
sendhex(byte);
sendstr("\n");
}
S0SPDR = byte;
/* Check SPIF bit, Wait for Data Transfer Complete */
while (!(S0SPSR & 0x80));
recv_byte = S0SPDR;
if (SPI_DEBUG)
{
sendstr("spi bus 0 recv ");
sendhex(recv_byte);
sendstr("\n");
}
return recv_byte;
}
int spi_read_byte(unsigned char *byte)
{
if (0 == byte)
{
sendstr("spi_read_byte byte is NULL\n");
return -1;
}
/* Only Write S0SPDR generate SPI Clock for receive data */
*byte = spi_write_byte(0xFF);
return 0;
}
int spi_write(unsigned char *data, int len)
{
int i = 0;
if (0 == data)
{
sendstr("spi_write data is NULL\n");
return -1;
}
for (i = 0; i < len; i++)
{
spi_write_byte(data[i]);
}
return 0;
}
int spi_read(unsigned char *data, int len)
{
int i = 0;
if (0 == data)
{
sendstr("spi_read data is NULL\n");
return -1;
}
for (i = 0; i < len; i++)
{
spi_read_byte(&data[len - 1 -i]);
}
return 0;
}
我们第二步需要将对外扩的Flash芯片读写等操作接口封装好供上层应用使用,下面是它的.c以及对应的.h文件,代码中由相关注释帮助理解。
- W25Q12xx.c:
#include "w25qxx.h" #include "spi.h" #include "delay.h" #include "usart.h" u16 W25QXX_TYPE=W25Q128; //默认是W25Q128 //4Kbytes为一个Sector //16个扇区为1个Block //W25Q128 //容量为16M字节,共有128个Block,4096个Sector //初始化SPI FLASH的IO口 void W25QXX_Init(void) { spi_init(); //初始化SPI W25QXX_TYPE=W25QXX_ReadID(); //读取FLASH ID. } //读取W25QXX的状态寄存器 //BIT7 6 5 4 3 2 1 0 //SPR RV TB BP2 BP1 BP0 WEL BUSY //SPR:默认0,状态寄存器保护位,配合WP使用 //TB,BP2,BP1,BP0:FLASH区域写保护设置 //WEL:写使能锁定 //BUSY:忙标记位(1,忙;0,空闲) //默认:0x00 u8 W25QXX_ReadSR(void) { u8 byte=0; W25QXX_CS=0; //使能器件 spi_write(W25X_ReadStatusReg,1); //发送读取状态寄存器命令 spi_read(&byte,1); //读取一个字节 W25QXX_CS=1; //取消片选 return byte; } //写W25QXX状态寄存器 //只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!! void W25QXX_Write_SR(u8 sr) { W25QXX_CS=0; //使能器件 spi_write(W25X_WriteStatusReg,1); //发送写取状态寄存器命令 spi_write(sr,1); //写入一个字节 W25QXX_CS=1; //取消片选 } //W25QXX写使能 //将WEL置位 void W25QXX_Write_Enable(void) { W25QXX_CS=0; //使能器件 spi_write(W25X_WriteEnable,1); //发送写使能 W25QXX_CS=1; //取消片选 } //W25QXX写禁止 //将WEL清零 void W25QXX_Write_Disable(void) { W25QXX_CS=0; //使能器件 spi_write(W25X_WriteDisable,1); //发送写禁止指令 W25QXX_CS=1; //取消片选 } //读取芯片ID //返回值如下: //0XEF13,表示芯片型号为W25Q80 //0XEF14,表示芯片型号为W25Q16 //0XEF15,表示芯片型号为W25Q32 //0XEF16,表示芯片型号为W25Q64 //0XEF17,表示芯片型号为W25Q128 u16 W25QXX_ReadID(void) { u16 Temp = 0; W25QXX_CS=0; spi_write(0x90,1);//发送读取ID命令 spi_write(0x00,1); spi_write(0x00,1); spi_write(0x00,1); spi_read(&Temp,2); W25QXX_CS=1; return Temp; } //读取SPI FLASH //在指定地址开始读取指定长度的数据 //pBuffer:数据存储区 //ReadAddr:开始读取的地址(24bit) //NumByteToRead:要读取的字节数(最大65535) void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead) { u16 i; W25QXX_CS=0; //使能器件 spi_write(W25X_ReadData,1); //发送读取命令 spi_write((u8)((ReadAddr)>>16),1); //发送24bit地址 spi_write((u8)((ReadAddr)>>8),1); spi_write((u8)ReadAddr,1); spi_read(pBuffer,NumByteToRead); W25QXX_CS=1; } //SPI在一页(0~65535)内写入少于256个字节的数据 //在指定地址开始写入最大256字节的数据 //pBuffer:数据存储区 //WriteAddr:开始写入的地址(24bit) //NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!! void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) { u16 i; W25QXX_Write_Enable(); //SET WEL W25QXX_CS=0; //使能器件 spi_write(W25X_PageProgram,1); //发送写页命令 spi_write((u8)((WriteAddr)>>16),1); //发送24bit地址 spi_write((u8)((WriteAddr)>>8),1); spi_write((u8)WriteAddr,1); spi_write(pBuffer,NumByteToWrite); W25QXX_CS=1; //取消片选 W25QXX_Wait_Busy(); //等待写入结束 } //无检验写SPI FLASH //必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败! //具有自动换页功能 //在指定地址开始写入指定长度的数据,但是要确保地址不越界! //pBuffer:数据存储区 //WriteAddr:开始写入的地址(24bit) //NumByteToWrite:要写入的字节数(最大65535) //CHECK OK void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) { u16 pageremain; pageremain=256-WriteAddr%256; //单页剩余的字节数 if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节 while(1) { W25QXX_Write_Page(pBuffer,WriteAddr,pageremain); if(NumByteToWrite==pageremain)break;//写入结束了 else //NumByteToWrite>pageremain { pBuffer+=pageremain; WriteAddr+=pageremain; NumByteToWrite-=pageremain; //减去已经写入了的字节数 if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节 else pageremain=NumByteToWrite; //不够256个字节了 } }; } //写SPI FLASH //在指定地址开始写入指定长度的数据 //该函数带擦除操作! //pBuffer:数据存储区 //WriteAddr:开始写入的地址(24bit) //NumByteToWrite:要写入的字节数(最大65535) u8 W25QXX_BUFFER[4096]; void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) { u32 secpos; u16 secoff; u16 secremain; u16 i; u8 * W25QXX_BUF; W25QXX_BUF=W25QXX_BUFFER; secpos=WriteAddr/4096;//扇区地址 secoff=WriteAddr%4096;//在扇区内的偏移 secremain=4096-secoff;//扇区剩余空间大小 //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用 if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节 while(1) { W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容 for(i=0;i<secremain;i++)//校验数据 { if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除 } if(i<secremain)//需要擦除 { W25QXX_Erase_Sector(secpos);//擦除这个扇区 for(i=0;i<secremain;i++) //复制 { W25QXX_BUF[i+secoff]=pBuffer[i]; } W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区 }else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间. if(NumByteToWrite==secremain)break;//写入结束了 else//写入未结束 { secpos++;//扇区地址增1 secoff=0;//偏移位置为0 pBuffer+=secremain; //指针偏移 WriteAddr+=secremain;//写地址偏移 NumByteToWrite-=secremain; //字节数递减 if(NumByteToWrite>4096)secremain=4096; //下一个扇区还是写不完 else secremain=NumByteToWrite; //下一个扇区可以写完了 } }; } //擦除整个芯片 //等待时间超长... void W25QXX_Erase_Chip(void) { W25QXX_Write_Enable(); //SET WEL W25QXX_Wait_Busy(); W25QXX_CS=0; //使能器件 spi_write(W25X_ChipErase,1); //发送片擦除命令 W25QXX_CS=1; //取消片选 W25QXX_Wait_Busy(); //等待芯片擦除结束 } //擦除一个扇区 //Dst_Addr:扇区地址 根据实际容量设置 //擦除一个山区的最少时间:150ms void W25QXX_Erase_Sector(u32 Dst_Addr) { //监视falsh擦除情况,测试用 printf("fe:%x\r\n",Dst_Addr); Dst_Addr*=4096; W25QXX_Write_Enable(); //SET WEL W25QXX_Wait_Busy(); W25QXX_CS=0; //使能器件 spi_write(W25X_SectorErase,1); //发送扇区擦除指令 spi_write((u8)((Dst_Addr)>>16),1); //发送24bit地址 spi_write((u8)((Dst_Addr)>>8),1); spi_write((u8)Dst_Addr); W25QXX_CS=1; //取消片选 W25QXX_Wait_Busy(); //等待擦除完成 } //等待空闲 void W25QXX_Wait_Busy(void) { while((W25QXX_ReadSR()&0x01)==0x01); // 等待BUSY位清空 } //进入掉电模式 void W25QXX_PowerDown(void) { W25QXX_CS=0; //使能器件 spi_write(W25X_PowerDown,1); //发送掉电命令 W25QXX_CS=1; //取消片选 delay_us(3); //等待TPD } //唤醒 void W25QXX_WAKEUP(void) { W25QXX_CS=0; //使能器件 spi_write(W25X_ReleasePowerDown,1); // send W25X_PowerDown command 0xAB W25QXX_CS=1; //取消片选 delay_us(3); //等待TRES1 }
- W25Q12xx.h:
W25Q12xx.h: #ifndef __W25QXX_H #define __W25QXX_H #include "sys.h" //W25X系列/Q系列芯片列表 //W25Q80 ID 0XEF13 //W25Q16 ID 0XEF14 //W25Q32 ID 0XEF15 //W25Q64 ID 0XEF16 //W25Q128 ID 0XEF17 #define W25Q80 0XEF13 #define W25Q16 0XEF14 #define W25Q32 0XEF15 #define W25Q64 0XEF16 #define W25Q128 0XEF17 extern u16 W25QXX_TYPE; //定义W25QXX芯片型号 #define W25QXX_CS PBout(14) //W25QXX的片选信号 // //指令表 #define W25X_WriteEnable 0x06 #define W25X_WriteDisable 0x04 #define W25X_ReadStatusReg 0x05 #define W25X_WriteStatusReg 0x01 #define W25X_ReadData 0x03 #define W25X_FastReadData 0x0B #define W25X_FastReadDual 0x3B #define W25X_PageProgram 0x02 #define W25X_BlockErase 0xD8 #define W25X_SectorErase 0x20 #define W25X_ChipErase 0xC7 #define W25X_PowerDown 0xB9 #define W25X_ReleasePowerDown 0xAB #define W25X_DeviceID 0xAB #define W25X_ManufactDeviceID 0x90 #define W25X_JedecDeviceID 0x9F void W25QXX_Init(void); u16 W25QXX_ReadID(void); //读取FLASH ID u8 W25QXX_ReadSR(void); //读取状态寄存器 void W25QXX_Write_SR(u8 sr); //写状态寄存器 void W25QXX_Write_Enable(void); //写使能 void W25QXX_Write_Disable(void); //写保护 void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite); void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead); //读取flash void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//写入flash void W25QXX_Erase_Chip(void); //整片擦除 void W25QXX_Erase_Sector(u32 Dst_Addr); //扇区擦除 void W25QXX_Wait_Busy(void); //等待空闲 void W25QXX_PowerDown(void); //进入掉电模式 void W25QXX_WAKEUP(void); //唤醒 #endif
十六宿舍 原创作品,转载必须标注原文链接。
©2023 Yang Li. All rights reserved.
欢迎关注 『十六宿舍』,大家喜欢的话,给个👍,更多关于嵌入式相关技术的内容持续更新中。