W25QXX是华邦公司生产的一块FLASH储存芯片
那W25Q256为例:驱动方式:单路双路四路SPI、QSPI;
擦写周期:10W次
支持电压:2.7~3.6V
频率:单路最大104Mhz、双路208Mhz、四路416Mhz
容量:容量为32M字节;
它将32M的容量分为512个块(Block),那么每个块的容量就是64K字节;
每个块又分为16个扇区(Sector),每个扇区4K个字节;
文章图片
那我们需要给W25Q256开辟一个至少4K的缓存区,这样对SRAM要求较高,芯片必须有4K以上的SRAM才能很好的操作,因为它的写操作需要先计算出它的扇区,把这个扇区的内容全部读出来,存放到芯片的SRAM里面进行修改,然后在一次性吧内容写入芯片。
引脚排列
【STM32|W25QXX使用教程】
文章图片
引脚描述
文章图片
硬件连接
文章图片
操作指令:
文章图片
w25qxx.h
#ifndef __W25QXX_H
#define __W25QXX_H
#include "sys.h"////W25X系列/Q系列芯片列表
//W25Q80ID0XEF13
//W25Q16ID0XEF14
//W25Q32ID0XEF15
//W25Q64ID0XEF16
//W25Q128 ID0XEF17
//W25Q256 ID0XEF18
#define W25Q800XEF13
#define W25Q160XEF14
#define W25Q320XEF15
#define W25Q640XEF16
#define W25Q128 0XEF17
#define W25Q256 0XEF18extern u16 W25QXX_TYPE;
//定义W25QXX芯片型号#define W25QXX_CSPFout(6)//W25QXX的片选信号//
//指令表
#define W25X_WriteEnable0x06
#define W25X_WriteDisable0x04
#define W25X_ReadStatusReg10x05
#define W25X_ReadStatusReg20x35
#define W25X_ReadStatusReg30x15
#define W25X_WriteStatusReg10x01
#define W25X_WriteStatusReg20x31
#define W25X_WriteStatusReg30x11
#define W25X_ReadData0x03
#define W25X_FastReadData0x0B
#define W25X_FastReadDual0x3B
#define W25X_PageProgram0x02
#define W25X_BlockErase0xD8
#define W25X_SectorErase0x20
#define W25X_ChipErase0xC7
#define W25X_PowerDown0xB9
#define W25X_ReleasePowerDown 0xAB
#define W25X_DeviceID0xAB
#define W25X_ManufactDeviceID 0x90
#define W25X_JedecDeviceID0x9F
#define W25X_Enable4ByteAddr0xB7
#define W25X_Exit4ByteAddr0xE9void W25QXX_Init(void);
u16W25QXX_ReadID(void);
//读取FLASH ID
u8 W25QXX_ReadSR(u8 regno);
//读取状态寄存器
void W25QXX_4ByteAddr_Enable(void);
//使能4字节地址模式
void W25QXX_Write_SR(u8 regno,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
w25qxx.c
#include "w25qxx.h"
#include "spi.h"
#include "delay.h"
#include "usart.h"
#include "stm32f4xx_hal_gpio.h"//u16 W25QXX_TYPE=W25Q256;
//默认是W25Q256//4Kbytes为一个Sector
//16个扇区为1个Block
//W25Q256
//容量为32M字节,共有512个Block,8192个Sector //初始化SPI FLASH的IO口
void W25QXX_Init(void)
{
u8 temp;
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOF_CLK_ENABLE();
//使能GPIOF时钟//PF6
GPIO_Initure.Pin=GPIO_PIN_6;
//PF6
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;
//推挽输出
GPIO_Initure.Pull=GPIO_PULLUP;
//上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST;
//快速
HAL_GPIO_Init(GPIOF,&GPIO_Initure);
//初始化 W25QXX_CS=1;
//SPI FLASH不选中
SPI5_Init();
//初始化SPI
SPI5_SetSpeed(SPI_BAUDRATEPRESCALER_2);
//设置为45M时钟,高速模式
W25QXX_TYPE=W25QXX_ReadID();
//读取FLASH ID.
if(W25QXX_TYPE==W25Q256)//SPI FLASH为W25Q256
{
temp=W25QXX_ReadSR(3);
//读取状态寄存器3,判断地址模式
if((temp&0X01)==0)//如果不是4字节地址模式,则进入4字节地址模式
{
W25QXX_CS=0;
//选中
SPI5_ReadWriteByte(W25X_Enable4ByteAddr);
//发送进入4字节地址模式指令
W25QXX_CS=1;
//取消片选
}
}
}//读取W25QXX的状态寄存器,W25QXX一共有3个状态寄存器
//状态寄存器1:
//BIT76543210
//SPRRVTB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;
0,空闲)
//默认:0x00
//状态寄存器2:
//BIT76543210
//SUSCMP LB3 LB2 LB1 (R) QESRP1
//状态寄存器3:
//BIT76543210
//HOLD/RSTDRV1 DRV0 (R) (R) WPS ADP ADS
//regno:状态寄存器号,范:1~3
//返回值:状态寄存器值
u8 W25QXX_ReadSR(u8 regno)
{
u8 byte=0,command=0;
switch(regno)
{
case 1:
command=W25X_ReadStatusReg1;
//读状态寄存器1指令
break;
case 2:
command=W25X_ReadStatusReg2;
//读状态寄存器2指令
break;
case 3:
command=W25X_ReadStatusReg3;
//读状态寄存器3指令
break;
default:
command=W25X_ReadStatusReg1;
break;
}
W25QXX_CS=0;
//使能器件
SPI5_ReadWriteByte(command);
//发送读取状态寄存器命令
byte=SPI5_ReadWriteByte(0Xff);
//读取一个字节
W25QXX_CS=1;
//取消片选
return byte;
}
//写W25QXX状态寄存器
void W25QXX_Write_SR(u8 regno,u8 sr)
{
u8 command=0;
switch(regno)
{
case 1:
command=W25X_WriteStatusReg1;
//写状态寄存器1指令
break;
case 2:
command=W25X_WriteStatusReg2;
//写状态寄存器2指令
break;
case 3:
command=W25X_WriteStatusReg3;
//写状态寄存器3指令
break;
default:
command=W25X_WriteStatusReg1;
break;
}
W25QXX_CS=0;
//使能器件
SPI5_ReadWriteByte(command);
//发送写取状态寄存器命令
SPI5_ReadWriteByte(sr);
//写入一个字节
W25QXX_CS=1;
//取消片选
}
//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{
W25QXX_CS=0;
//使能器件
SPI5_ReadWriteByte(W25X_WriteEnable);
//发送写使能
W25QXX_CS=1;
//取消片选
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{
W25QXX_CS=0;
//使能器件
SPI5_ReadWriteByte(W25X_WriteDisable);
//发送写禁止指令
W25QXX_CS=1;
//取消片选
} //读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
//0XEF18,表示芯片型号为W25Q256
u16 W25QXX_ReadID(void)
{
u16 Temp = 0;
W25QXX_CS=0;
SPI5_ReadWriteByte(0x90);
//发送读取ID命令
SPI5_ReadWriteByte(0x00);
SPI5_ReadWriteByte(0x00);
SPI5_ReadWriteByte(0x00);
Temp|=SPI5_ReadWriteByte(0xFF)<<8;
Temp|=SPI5_ReadWriteByte(0xFF);
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;
//使能器件
SPI5_ReadWriteByte(W25X_ReadData);
//发送读取命令
if(W25QXX_TYPE==W25Q256)//如果是W25Q256的话地址为4字节的,要发送最高8位
{
SPI5_ReadWriteByte((u8)((ReadAddr)>>24));
}
SPI5_ReadWriteByte((u8)((ReadAddr)>>16));
//发送24bit地址
SPI5_ReadWriteByte((u8)((ReadAddr)>>8));
SPI5_ReadWriteByte((u8)ReadAddr);
for(i=0;
i>24));
}
SPI5_ReadWriteByte((u8)((WriteAddr)>>16));
//发送24bit地址
SPI5_ReadWriteByte((u8)((WriteAddr)>>8));
SPI5_ReadWriteByte((u8)WriteAddr);
for(i=0;
ipageremain
{
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;
i++)//校验数据
{
if(W25QXX_BUF[secoff+i]!=0XFF)break;
//需要擦除
}
if(i)//需要擦除
{
W25QXX_Erase_Sector(secpos);
//擦除这个扇区
for(i=0;
i;
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;
//偏移位置为0pBuffer+=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;
//使能器件
SPI5_ReadWriteByte(W25X_ChipErase);
//发送片擦除命令
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;
//使能器件
SPI5_ReadWriteByte(W25X_SectorErase);
//发送扇区擦除指令
if(W25QXX_TYPE==W25Q256)//如果是W25Q256的话地址为4字节的,要发送最高8位
{
SPI5_ReadWriteByte((u8)((Dst_Addr)>>24));
}
SPI5_ReadWriteByte((u8)((Dst_Addr)>>16));
//发送24bit地址
SPI5_ReadWriteByte((u8)((Dst_Addr)>>8));
SPI5_ReadWriteByte((u8)Dst_Addr);
W25QXX_CS=1;
//取消片选
W25QXX_Wait_Busy();
//等待擦除完成
}
//等待空闲
void W25QXX_Wait_Busy(void)
{
while((W25QXX_ReadSR(1)&0x01)==0x01);
// 等待BUSY位清空
}
//进入掉电模式
void W25QXX_PowerDown(void)
{
W25QXX_CS=0;
//使能器件
SPI5_ReadWriteByte(W25X_PowerDown);
//发送掉电命令
W25QXX_CS=1;
//取消片选
delay_us(3);
//等待TPD
}
//唤醒
void W25QXX_WAKEUP(void)
{
W25QXX_CS=0;
//使能器件
SPI5_ReadWriteByte(W25X_ReleasePowerDown);
//send W25X_PowerDown command 0xAB
W25QXX_CS=1;
//取消片选
delay_us(3);
//等待TRES1
}
W25QXX_Write函数思路
- 每个扇区(sector)是4K,也就是4096个地址
- 在写任何一个地址之前,如果该地址的值不是0xFF,必须先擦除对应的扇区(sector),然后再写。
- 最大支持写操作的单元是page(页)一个扇区4K字节
- 根据要写的起始地址,确定要写的起始区域的扇区(Sector)号以及在起始扇区(Sector)中的偏移量。
- 根据要写的起始地址和字节数,确定要写的数据是否跨扇区(Sector)。
- 确定好要操作的扇区(Sector)以及扇区(Sector)的地址范围。
- 对每个扇区(Sector),先遍历要写的地址区域保存的数据是不是0xff,如果都是,就不用擦除。如果有不是0xff的区域,先读出里面的数据,保存在缓存W25QXX_BUFFER,然后擦除里面的内容。然后把这个sector要操作的数据,写到缓存。最后一次性吧缓存W25QXX_BUFFER的数据写到这个对应的扇区(Sector)。
//要写入到W25Q256的字符串数组
const u8 TEXT_Buffer[]={"Apollo STM32F4 SPI TEST"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{
u8 key;
u16 i=0;
u8 datatemp[SIZE];
u32 FLASH_SIZE;
Stm32_Clock_Init(360,25,2,8);
//设置时钟,180Mhz
delay_init(180);
//初始化延时函数
uart_init(90,115200);
//初始化串口波特率为115200
usmart_dev.init(90);
//初始化USMART
LED_Init();
//初始化与LED连接的硬件接口
SDRAM_Init();
//初始化SDRAM
LCD_Init();
//初始化LCD
KEY_Init();
//按键初始化
W25QXX_Init();
//W25QXX初始化
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Apollo STM32F4/F7");
LCD_ShowString(30,70,200,16,16,"SPI TEST");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2021/01/24");
LCD_ShowString(30,130,200,16,16,"KEY1:WriteKEY0:Read");
//显示提示信息
while(W25QXX_ReadID()!=W25Q256)//检测不到W25Q256
{
LCD_ShowString(30,150,200,16,16,"W25Q256 Check Failed!");
delay_ms(500);
LCD_ShowString(30,150,200,16,16,"Please Check!");
delay_ms(500);
LED0=!LED0;
//DS0闪烁
}
LCD_ShowString(30,150,200,16,16,"W25Q256 Ready!");
FLASH_SIZE=32*1024*1024;
//FLASH 大小为32M字节
POINT_COLOR=BLUE;
//设置字体为蓝色
while(1)
{
key=KEY_Scan(0);
if(key==KEY1_PRES)//KEY1按下,写入W25Q256
{
LCD_Fill(0,170,239,319,WHITE);
//清除半屏
LCD_ShowString(30,170,200,16,16,"Start Write W25Q256....");
W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE);
//从倒数第100个地址处开始,写入SIZE长度的数据
LCD_ShowString(30,170,200,16,16,"W25Q256 Write Finished!");
//提示传送完成
}
if(key==KEY0_PRES)//KEY0按下,读取字符串并显示
{
LCD_ShowString(30,170,200,16,16,"Start Read W25Q256.... ");
W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE);
//从倒数第100个地址处开始,读出SIZE个字节
LCD_ShowString(30,170,200,16,16,"The Data Readed Is:");
//提示传送完成
LCD_ShowString(30,190,200,16,16,datatemp);
//显示读到的字符串
}
i++;
delay_ms(10);
if(i==20)
{
LED0=!LED0;
//提示系统正在运行
i=0;
}
}
}
推荐阅读
- W25Qxx|W25Qxx Flash存储
- stm32|【STM32】HAL库开发教程(九)—W25qxx使用
- Winbond W25QXX SPI Flash使用笔记
- 笔记|STM32使用W25QXX flash闪存芯片基于串口自由写入或读取数据
- 台积电们在盲目扩产吗()
- 扎“芯”了——CP探针卡的国产替代道阻且长
- C语言|C语言系列(一)(C语言程序概述)
- 深度学习|【实战】K210训练与部署YOLO目标检测模型
- K210|【电赛开发】2021-F题数字识别-YOLOV2(含无脑训练教程