ESP32-SPI-Arduino/src/W25QXXArduino/w25qxx.cpp

440 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "w25qxx.h"
uint16_t W25QXX_TYPE = 0XFFFF; //默认是W25Q128
//4Kbytes为一个Sector
//16个扇区为1个Block
//W25Q128
//容量为16M字节,共有256个Block,4096个Sector
static void delayus(uint32_t nus){
volatile uint32_t i;
while (nus-- > 0){
i = 26;
while (i-- > 0);
}
}
/**
* @brief 初始化SPI,ESP32一共用4个SPI能用的有两个.
*
*/
void SPI1S_Init(void){
// gpio init
// pinMode(NORFLASH_HOLD_PIN, OUTPUT);
// pinMode(NORFLASH_WP_PIN, OUTPUT);
// digitalWrite(NORFLASH_HOLD_PIN, HIGH); //保持
// digitalWrite(NORFLASH_WP_PIN, HIGH); //写保护
#ifdef HARDWARE_SPI
//使用硬件SPI
SPI.begin(NORFLASH_CLK_PIN, NORFLASH_MISO_PIN, NORFLASH_MOSI_PIN, NORFLASH_CS_PIN);
SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST);
SPI.setFrequency(SPI_FREQUENCY);
// SPI.setFrequency(1000000*20);
pinMode(NORFLASH_CS_PIN, OUTPUT);
#endif
#ifdef SOFTWARE_SPI
//使用软件模拟SPI
pinMode(NORFLASH_CLK_PIN, OUTPUT);
pinMode(NORFLASH_MOSI_PIN, OUTPUT);
pinMode(NORFLASH_MISO_PIN, INPUT);
pinMode(NORFLASH_CS_PIN, OUTPUT);
digitalWrite(NORFLASH_CS_PIN,HIGH);
digitalWrite(NORFLASH_CLK_PIN,LOW);
delay(1);
#endif
#ifdef FLASH_TEST_ENABLE
/* readwrite test */
int g = 0;
uint8_t str[1280];
memset(str, 0, sizeof(str));
unsigned int j = 0;
for(int k=0; k < 5; k++)
{
for(int i = 0; i < 256; i++)
{
str[j] = i;
j++;
}
}
Serial.println("");
Serial.println("-----write data-------");
W25QXX_CS0;
W25QXX_Erase_Sector(0x00);
W25QXX_Write(str,0x10,256);
memset(str, 0, sizeof(str));
W25QXX_Read(str,0x00,512);
W25QXX_CS1;
Serial.println("str:");
for(int k = 0; k < 512; k++) {
if(g == 16){
Serial.println("|");
if(k % 256 == 0) Serial.println("---------------");
{
g = 1;
}
}
else {
g++;
}
Serial.printf("%02X ", str[k]);
}
#endif
}
/**
* @brief 读写一个字节
*
* @param TxData 要写入的字节
* @return uint8_t 读取到的字节
*/
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{
uint8_t Rxdata;
#ifdef HARDWARE_SPI
Rxdata = SPI.transfer(TxData);
return Rxdata;
#endif
#ifdef SOFTWARE_SPI
uint8_t i = 0, Rxdata = 0;
for(i = 0; i < 8; i++)
{
digitalWrite(NORFLASH_CLK_PIN, HIGH);
digitalWrite(NORFLASH_CLK_PIN, LOW);
if(digitalRead(NORFLASH_MISO_PIN))
{
Rxdata |= 0x80 >> i;
}
}
return Rxdata;
#endif
return Rxdata; //返回收到的数据
}
/**
* @brief 初始化SPI FLASH的IO口
*
*/
void W25QXX_Init(void)
{
SPI1S_Init(); //初始化SPI
W25QXX_TYPE = W25QXX_ReadID(); //读取FLASH ID.
}
//读取W25QXX的状态寄存器W25QXX一共有3个状态寄存器
//状态寄存器1
//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
//状态寄存器2
//BIT7 6 5 4 3 2 1 0
//SUS CMP LB3 LB2 LB1 (R) QE SRP1
//状态寄存器3
//BIT7 6 5 4 3 2 1 0
//HOLD/RST DRV1 DRV0 (R) (R) WPS ADP ADS
//regno:状态寄存器号,范:1~3
//返回值:状态寄存器值
/**
* @brief 读取W25QXX的状态寄存器
*
* @param regno 状态寄存器号,范:1~3
* @return uint8_t 状态寄存器值
*/
uint8_t W25QXX_ReadSR(uint8_t regno)
{
uint8_t 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_CS0; //使能器件
SPI1_ReadWriteByte(command); //发送读取状态寄存器命令
byte=SPI1_ReadWriteByte(0Xff); //读取一个字节
W25QXX_CS1; //取消片选
return byte;
}
//写W25QXX状态寄存器
void W25QXX_Write_SR(uint8_t regno,uint8_t sr)
{
uint8_t 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_CS0; //使能器件
SPI1_ReadWriteByte(command); //发送写取状态寄存器命令
SPI1_ReadWriteByte(sr); //写入一个字节
W25QXX_CS1; //取消片选
}
//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{
W25QXX_CS0; //使能器件
SPI1_ReadWriteByte(W25X_WriteEnable); //发送写使能
W25QXX_CS1; //取消片选
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{
W25QXX_CS0; //使能器件
SPI1_ReadWriteByte(W25X_WriteDisable); //发送写禁止指令
W25QXX_CS1; //取消片选
}
//读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
//0XEF18,表示芯片型号为W25Q256
uint16_t W25QXX_ReadID(void)
{
uint16_t Temp = 0;
W25QXX_CS0;
SPI1_ReadWriteByte(0x90);//发送读取ID命令
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(0x00);
Temp|=SPI1_ReadWriteByte(0xFF)<<8;
Temp|=SPI1_ReadWriteByte(0xFF);
W25QXX_CS1;
return Temp;
}
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)
{
uint16_t i;
W25QXX_CS0; //使能器件
SPI1_ReadWriteByte(W25X_ReadData); //发送读取命令
if(W25QXX_TYPE==W25Q256) //如果是W25Q256的话地址为4字节的要发送最高8位
{
SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>24));
}
SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>16)); //发送24bit地址
SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>8));
SPI1_ReadWriteByte((uint8_t)ReadAddr);
for(i=0; i<NumByteToRead; i++)
{
pBuffer[i]=SPI1_ReadWriteByte(0XFF); //循环读数
}
W25QXX_CS1;
}
//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
uint16_t i;
W25QXX_Write_Enable(); //SET WEL
W25QXX_CS0; //使能器件
SPI1_ReadWriteByte(W25X_PageProgram); //发送写页命令
if(W25QXX_TYPE==W25Q256) //如果是W25Q256的话地址为4字节的要发送最高8位
{
SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>24));
}
SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>16)); //发送24bit地址
SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>8));
SPI1_ReadWriteByte((uint8_t)WriteAddr);
for(i=0; i<NumByteToWrite; i++)SPI1_ReadWriteByte(pBuffer[i]); //循环写数
W25QXX_CS1; //取消片选
W25QXX_Wait_Busy(); //等待写入结束
}
/**
* @brief 只能写入一页的数据刚好256字节.
*
* @param pBuffer 待写入的数据256字节
* @param WriteAddr flash的首地址确保是页的开头地址。
* @return uint8_t 错误返回1完成返回0
*/
uint8_t W25QXX_Write_Page_Only(uint8_t* pBuffer,uint16_t WriteAddr){
uint16_t i;
if ((WriteAddr%256)!=0){
return 1; //要写入的地址不是页首地址就直接返回
}
W25QXX_Write_Enable(); //SET WEL
W25QXX_CS0; //使能器件
SPI1_ReadWriteByte(W25X_PageProgram); //发送写页命令
if(W25QXX_TYPE==W25Q256) //如果是W25Q256的话地址为4字节的要发送最高8位
{
SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>24));
}
SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>16)); //发送24bit地址
SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>8));
SPI1_ReadWriteByte((uint8_t)WriteAddr);
SPI.writeBytes(pBuffer,256); //写256个数据
W25QXX_CS1; //取消片选
W25QXX_Wait_Busy(); //等待写入结束
return 0;
}
/**
* @brief 无检验写SPI FLASH必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!具有自动换页功能
* 在指定地址开始写入指定长度的数据,但是要确保地址不越界!
*
* @param pBuffer 数据存储区
* @param WriteAddr 开始写入的地址(24bit)
* @param NumByteToWrite 要写入的字节数(最大65535)
*/
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
uint16_t 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个字节了
}
};
}
uint8_t W25QXX_BUFFER[4096];
/**
* @brief 写SPI FLASH在指定地址开始写入指定长度的数据.该函数带擦除操作!此函数全局定义了一数组uint8_t W25QXX_BUFFER[4096];
*
* @param pBuffer 存储区
* @param WriteAddr 数据开始写入的地址(24bit)
* @param NumByteToWrite 要写入的字节数(最大65535)
*/
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint32_t NumByteToWrite)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t * 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; //不大于扇区剩余空间大小
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_CS0; //使能器件
SPI1_ReadWriteByte(W25X_ChipErase); //发送片擦除命令
W25QXX_CS1; //取消片选
W25QXX_Wait_Busy(); //等待芯片擦除结束
}
/**
* @brief 擦除一个扇区,擦除一个扇区的最少时间:150ms
*
* @param Dst_Addr 扇区地址 根据实际容量设置
*/
void W25QXX_Erase_Sector(uint32_t Dst_Addr)
{
//监视falsh擦除情况,测试用
//printf("fe:%x\r\n",Dst_Addr);
Dst_Addr*=4096;
W25QXX_Write_Enable(); //SET WEL
W25QXX_Wait_Busy();
W25QXX_CS0; //使能器件
SPI1_ReadWriteByte(W25X_SectorErase); //发送扇区擦除指令
if(W25QXX_TYPE==W25Q256) //如果是W25Q256的话地址为4字节的要发送最高8位
{
SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>24));
}
SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>16)); //发送24bit地址
SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>8));
SPI1_ReadWriteByte((uint8_t)Dst_Addr);
W25QXX_CS1; //取消片选
W25QXX_Wait_Busy(); //等待擦除完成
}
//等待空闲
void W25QXX_Wait_Busy(void)
{
while((W25QXX_ReadSR(1)&0x01)==0x01); // 等待BUSY位清空
}
//进入掉电模式
void W25QXX_PowerDown(void)
{
W25QXX_CS0; //使能器件
SPI1_ReadWriteByte(W25X_PowerDown); //发送掉电命令
W25QXX_CS1; //取消片选
delayus(3); //等待TPD
}
//唤醒
void W25QXX_WAKEUP(void)
{
W25QXX_CS0; //使能器件
SPI1_ReadWriteByte(W25X_ReleasePowerDown); // send W25X_PowerDown command 0xAB
W25QXX_CS1; //取消片选
delayus(3); //等待TRES1
}