一、W25QXX芯片介绍
本次以W25Q32为主介绍,其他如W25Q64,W25Q16等只是在存储大小上有区别。
W25Q32有16384个可编程页,每页有256个字节;在页擦除时可以按照16页为一组擦除(即4KB扇区擦除)、128页为一组擦除(即32KB块擦除)、256页为一组擦除(即64KB块擦除)或者整个芯片擦除。
块、扇区、页的大小:
块(block) | 64KB |
扇区(sector) | 4KB |
页(page) | 256byte |
W25Q32有64block = 64*16sector = 64*16*(4*1024/256)page = 16384page
二、状态寄存器
BUSY(忙位):BUSY位是一个只读位,位于状态寄存器中的S0,当器件在执行“页编程”、“扇区擦除”、“跨擦除”、“芯片擦除”、“写状态寄存器或擦除/程序安全寄存器”指令时,该位自动置为1,这是除了“读状态寄存器”指令,其他指令都忽略。当指令执行完毕后,该位自动置为0,表示芯片接收其他指令。
WEL位:写使能锁存(WEL)是状态寄存器(S1)中的一个只读位,在执行写使能指令后被设置为1。当设备禁止写时,WEL状态位清零。写禁用状态发生在上电或以下任何指令之后:写禁用、页程序、四页程序、扇区擦除、块擦除、芯片擦除、写状态寄存器、擦除安全寄存器和程序安全寄存器。
三、指令介绍
3.1 Manufacturer/Device ID
读取制造厂商/设备ID。不同的芯片制造厂商/设备ID是不同的,具体是多少要查看对应的手册。
指令最后得到的是MF7-MF0 ID7-ID0,所以得到的是EF15
代码编写过程:
1、先将CS拉低
2、发送8位指令
3、发送24位的地址
4、接收8位的制造商ID和8位的设备ID
5、CS拉高
uint16_t readID()
{
unsigned char command = W25X_ManufactDeviceID;
W25QXX_CS_L;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
unsigned char data = 0x00;
HAL_SPI_Transmit(&hspi1,&data,1,0xff);
HAL_SPI_Transmit(&hspi1,&data,1,0xff);
HAL_SPI_Transmit(&hspi1,&data,1,0xff);
uint16_t ID = 0x00;
uint8_t temp = 0xff;
HAL_SPI_Receive(&hspi1,&temp,1,0xff);
ID = temp;
// printf("0x%2x",temp);
ID = ID<<8;
HAL_SPI_Receive(&hspi1,&temp,1,0xff);
ID = ID|temp;
//printf("0x%2x",temp);
W25QXX_CS_H;
return ID;
}
3.2 JEDEC ID
返回的正确结果是EF4016
//读取JEDEC ID =0xEF4016
uint32_t JEDEC_ID()
{
unsigned char command = W25X_JedecDeviceID;
W25QXX_CS_L;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
uint32_t JEDEC_ID = 0x00;
uint8_t temp1 = 0xff;
uint8_t temp2 = 0xff;
uint8_t temp3 = 0xff;
HAL_SPI_Receive(&hspi1,&temp1,1,0xff);
HAL_SPI_Receive(&hspi1,&temp2,1,0xff);
HAL_SPI_Receive(&hspi1,&temp3,1,0xff);
JEDEC_ID = temp1<<16 | temp2<<8 | temp3;
W25QXX_CS_H;
return JEDEC_ID;
}
3.3 Read Status Register-1
返回S7-S0
//查看寄存器1,查看最低位是否为0,0:表示空闲,1:表示在将进行写,在写之前要判断是否空闲
void readStatueRegister1()
{
W25QXX_CS_L;
uint8_t command = W25X_ReadStatusReg1;
uint8_t statue = 0xff;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
do{
HAL_SPI_Receive(&hspi1,&statue,1,0xff);
//printf("0x%02X\r\n",statue);
HAL_Delay(1000);
}while(statue & 0x01);
W25QXX_CS_H;
}
3.4 Write Enable
void WriteEnable()
{
uint8_t command = W25X_WriteEnable;
W25QXX_CS_L;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
W25QXX_CS_H;
}
3.5 Sector Erase (4KB)
void EraseSector(uint32_t SectorAddr)
{
uint8_t command = W25X_SectorErase;
//等待写结束
readStatueRegister1();
//写使能
WriteEnable();
W25QXX_CS_L;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
//分三次发送地址
uint8_t addr = 0x00;
addr = ( SectorAddr& 0xFF0000) >>16;
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
addr = ( SectorAddr & 0xFF00) >>8;
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
addr = ( SectorAddr& 0xFF);
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
W25QXX_CS_H;
readStatueRegister1();
}
3.6 Page Program
在页面成开始之前。需要进行如下操作:
1、擦除页所在的扇区。因为在写入时只能将内存中的1变为0,而不能将0变为1,而擦除指令则可以将0置为1。
所以页编程:1---置为0
扇区擦除:0----置为1
2、等待写结束。如果上次的写操作还没完成,则不能进行本次页编程。
3、写使能。
4、执行页编程指令。
//页编程
//参数:数组,地址,字节
void PagePrograe(uint8_t* pBuffer,uint32_t SectorAddr, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
printf("从%06Xh地址开始写入%d个数据\r\n",WriteAddr,NumByteToWrite);
uint8_t command = W25X_PageProgram;
//先擦除扇区
EraseSector(SectorAddr);
//等待写结束
readStatueRegister1();
//写使能
WriteEnable();
W25QXX_CS_L;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
//分三次发送地址
uint8_t addr = 0x00;
addr = (WriteAddr & 0xFF0000) >>16;
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
addr = (WriteAddr & 0xFF00) >>8;
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
addr = (WriteAddr & 0xFF);
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
//写
while(NumByteToWrite--)
{
HAL_SPI_Transmit(&hspi1,pBuffer,1,0xff);
printf("0X%02X ",*pBuffer);
pBuffer++;
}
W25QXX_CS_H;
}
3.7 Read Data
void ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
printf("从%06Xh地址开始读%d个数据\r\n",ReadAddr,NumByteToRead);
uint8_t command= W25X_ReadData;
//等待空闲
readStatueRegister1();
W25QXX_CS_L;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
//分三次发送地址
uint8_t addr = 0x00;
addr = (ReadAddr & 0xFF0000) >>16;
// printf("%0x ",addr);
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
addr = (ReadAddr & 0xFF00) >>8;
//printf("%0x ",addr);
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
addr = (ReadAddr & 0xFF);
//printf("%0x ",addr);
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
//读
while(NumByteToRead--)
{
HAL_SPI_Receive(&hspi1,pBuffer,1,0xff);
// printf("0X%02X ",*pBuffer);
pBuffer++;
}
// for(int i =0;i < NumByteToRead;i++)
// printf("0X%02X ",*pBuffer);
W25QXX_CS_H;
}
四、配置
芯片:STM32F103CBT6
这里我用了其他引脚作为片选信号。
五、添加W25QXX相关文件
w25qxx.c
#include "w25qxx.h"
#include "w25qxx_config.h"
#include "main.h"
#include "stdio.h"
#define W25QXX_CS_H HAL_GPIO_WritePin(_W25QXX_CS_GPIO, _W25QXX_CS_PIN, GPIO_PIN_SET);
#define W25QXX_CS_L HAL_GPIO_WritePin(_W25QXX_CS_GPIO, _W25QXX_CS_PIN, GPIO_PIN_RESET);
w25qxx_t w25qxx;
#if (_W25QXX_USE_FREERTOS == 1)
#define W25qxx_Delay(delay) osDelay(delay)
#include "cmsis_os.h"
#else
#define W25qxx_Delay(delay) for(uint8_t i=0;i<255;i++);;
#endif
//##############################################################################################################
//EF15
uint16_t readID()
{
/*
1、片选拉低
2、发送指令
3、发送三个8位数据
4、接收数据8位
5、接收数据8位
6、片选拉高
*/
unsigned char command = W25X_ManufactDeviceID;
W25QXX_CS_L;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
unsigned char data = 0x00;
HAL_SPI_Transmit(&hspi1,&data,1,0xff);
HAL_SPI_Transmit(&hspi1,&data,1,0xff);
HAL_SPI_Transmit(&hspi1,&data,1,0xff);
uint16_t ID = 0x00;
uint8_t temp = 0xff;
HAL_SPI_Receive(&hspi1,&temp,1,0xff);
ID = temp;
// printf("0x%2x",temp);
ID = ID<<8;
HAL_SPI_Receive(&hspi1,&temp,1,0xff);
ID = ID|temp;
//printf("0x%2x",temp);
W25QXX_CS_H;
return ID;
}
//##############################################################################################################
//读取JEDEC ID =0xEF4016
uint32_t JEDEC_ID()
{
unsigned char command = W25X_JedecDeviceID;
W25QXX_CS_L;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
uint32_t JEDEC_ID = 0x00;
uint8_t temp1 = 0xff;
uint8_t temp2 = 0xff;
uint8_t temp3 = 0xff;
HAL_SPI_Receive(&hspi1,&temp1,1,0xff);
HAL_SPI_Receive(&hspi1,&temp2,1,0xff);
HAL_SPI_Receive(&hspi1,&temp3,1,0xff);
JEDEC_ID = temp1<<16 | temp2<<8 | temp3;
W25QXX_CS_H;
return JEDEC_ID;
}
//##############################################################################################################
//查看寄存器1,查看最低位是否为0,0:表示空闲,1:表示在将进行写,在写之前要判断是否空闲
void readStatueRegister1()
{
W25QXX_CS_L;
uint8_t command = W25X_ReadStatusReg1;
uint8_t statue = 0xff;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
do{
HAL_SPI_Receive(&hspi1,&statue,1,0xff);
//printf("0x%02X\r\n",statue);
HAL_Delay(1000);
}while(statue & 0x01);
W25QXX_CS_H;
}
//###########################################################################################################
//写使能
void WriteEnable()
{
uint8_t command = W25X_WriteEnable;
W25QXX_CS_L;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
W25QXX_CS_H;
}
//###########################################################################################################
void EraseSector(uint32_t SectorAddr)
{
uint8_t command = W25X_SectorErase;
//等待写结束
readStatueRegister1();
//写使能
WriteEnable();
W25QXX_CS_L;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
//分三次发送地址
uint8_t addr = 0x00;
addr = ( SectorAddr& 0xFF0000) >>16;
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
addr = ( SectorAddr & 0xFF00) >>8;
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
addr = ( SectorAddr& 0xFF);
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
W25QXX_CS_H;
readStatueRegister1();
}
//###########################################################################################################
//页编程
//参数:数组,地址,字节
void PagePrograe(uint8_t* pBuffer,uint32_t SectorAddr, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
printf("从%06Xh地址开始写入%d个数据\r\n",WriteAddr,NumByteToWrite);
uint8_t command = W25X_PageProgram;
//先擦除扇区
EraseSector(SectorAddr);
//等待写结束
readStatueRegister1();
//写使能
WriteEnable();
W25QXX_CS_L;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
//分三次发送地址
uint8_t addr = 0x00;
addr = (WriteAddr & 0xFF0000) >>16;
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
addr = (WriteAddr & 0xFF00) >>8;
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
addr = (WriteAddr & 0xFF);
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
//写
while(NumByteToWrite--)
{
HAL_SPI_Transmit(&hspi1,pBuffer,1,0xff);
printf("0X%02X ",*pBuffer);
pBuffer++;
}
W25QXX_CS_H;
}
//###########################################################################################################
void ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
printf("从%06Xh地址开始读%d个数据\r\n",ReadAddr,NumByteToRead);
uint8_t command= W25X_ReadData;
//等待空闲
readStatueRegister1();
W25QXX_CS_L;
HAL_SPI_Transmit(&hspi1,&command,1,0xff);
//分三次发送地址
uint8_t addr = 0x00;
addr = (ReadAddr & 0xFF0000) >>16;
// printf("%0x ",addr);
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
addr = (ReadAddr & 0xFF00) >>8;
//printf("%0x ",addr);
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
addr = (ReadAddr & 0xFF);
//printf("%0x ",addr);
HAL_SPI_Transmit(&hspi1,&addr,1,0xff);
//读
while(NumByteToRead--)
{
HAL_SPI_Receive(&hspi1,pBuffer,1,0xff);
// printf("0X%02X ",*pBuffer);
pBuffer++;
}
// for(int i =0;i < NumByteToRead;i++)
// printf("0X%02X ",*pBuffer);
W25QXX_CS_H;
}
w25qxx_config.h
这里根据个人引脚定义需要修改_W25QXX_CS_GPIO 和_W25QXX_CS_PIN
#ifndef __W25QXX_CONFIG_H__
#define __W25QXX_CONFIG_H__
#define _W25QXX_SPI hspi1
#define _W25QXX_CS_GPIO GPIOB
#define _W25QXX_CS_PIN GPIO_PIN_0
#define _W25QXX_USE_FREERTOS 0
#define _W25QXX_DEBUG 1
#endif
w25qxx.h
#ifndef __W25QXX_H__
#define __W25QXX_H__
#include <stdbool.h>
#include "spi.h"
#define SPI_FLASH_PageSize 256
#define SPI_FLASH_PerWritePageSize 256
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
#define W25X_ReadStatusReg1 0x05
#define W25X_WriteStatusReg1 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
#define WIP_Flag 0x01 /* Write In Progress (WIP) flag */
#define W25QXX_DUMMY_BYTE 0xFF
#pragma pack(1)
typedef enum
{
W25Q10=1,
W25Q20,
W25Q40,
W25Q80,
W25Q16,
W25Q32,
W25Q64,
W25Q128,
W25Q256,
W25Q512,
}W25QXX_ID_t;
typedef struct
{
W25QXX_ID_t ID;
uint8_t UniqID[8];
uint16_t PageSize;
uint32_t PageCount;
uint32_t SectorSize;
uint32_t SectorCount;
uint32_t BlockSize;
uint32_t BlockCount;
uint32_t CapacityInKiloByte;
uint8_t StatusRegister1;
uint8_t StatusRegister2;
uint8_t StatusRegister3;
uint8_t Lock;
}w25qxx_t;
#pragma pack()
extern w25qxx_t w25qxx;
/************************************************用户API*******************************************/
uint16_t readID();
uint32_t JEDEC_ID();
void readStatueRegister1();
void PagePrograe(uint8_t* pBuffer,uint32_t SectorAddr, uint32_t WriteAddr, uint16_t NumByteToWrite);
void ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
void EraseSector(uint32_t SectorAddr);
#endif
main.c中添加
/* USER CODE BEGIN 2 */
printf("串口测试成功\r\n");
uint16_t ID = 0x00;
ID = readID();
printf("---------------------------------------\r\n");
printf("Manufacturer/Device ID =0X%X\r\n",ID);
printf("---------------------------------------\r\n");
uint32_t JEDECID = JEDEC_ID();
printf("JEDEC ID=0X%6X\r\n",JEDECID);
printf("---------------------------------------\r\n");
unsigned char buff[256]={0};
for(int i =0;i<256;i++)
{
buff[i]=i;
}
//write
PagePrograe(buff,0x00, 0x100, 256);
HAL_Delay(2000);
printf("---------------------------------------\r\n");
// //read
unsigned char read[256]={0};
ReadBuffer(read, 0x100, 256);
for(int i=0;i<256;i++)
{
printf("0X%02X ", read[i]);
}