QSPI(Quad Serial Peripheral Interface) 是一种高速串行通信接口,在标准SPI(Serial Peripheral Interface)的基础上扩展至4条数据线(Quad Mode),显著提升数据传输速率。它广泛应用于Flash存储器、传感器和微控制器之间的通信。AS32系列MCU芯片开发板集成了一块S25FL512SAGMFVG13型号的QSPI FLASH,本文我们将介绍该系列芯片使用简洁单线模式操作QSPI FLASH。

代码详解:
1、扇区擦除函数

根据指导手册我们知道,在进行扇区擦除命令之前必须先给一个WREN 命令,该命令会将状态寄存器中的写使能锁存器(WEL)置位,以允许任何写入操作。此外,本文用例使用3字节地址,根据时序图可知,应先发送指令(长度可配置)接着发送一个24位地址。
我们开始讲解扇区擦除函数,代码如下:
#define WRITE_ENABLE_INST 0x06
#define WRITE_DISBALE_INST 0x04
#define ERASE_SECTOR_INST 0xD8
#define READ_RIGISTER1_INST 0x05
#define PAGE_PROGRAME_INST 0x02
#define READ_FLASH_INST 0x03
void QSPI_FlashErase(uint32_t block_start, uint32_t sector_num){
uint32_t dst;
uint32_t SR_Status;
uint8_t erase_status = 1;
dst = block_start;
uint32_t num = sector_num-1;
if(dst + num * 0x3FFFF> 0xFFFFFF)
{
Printf("error :Exceeds the erase range.\r\n");
}
QSPI_Reset();
QSPI_Deinit();
QSPI_StructInitenable();
QSPI_WriteEnable();
while( (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) == 1));
for(uint32_t i = 0; i < num; i++)
{
QSPI_SectorErase();
while( (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) == 1));
QSPI_SetAddr(dst + num*0x3FFFF);
while( (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) == 1));
}
while(erase_status)
{
QSPI_SetDataLength(0);
QSPI_ReadStatus_Rigister1();
delay_ms(10);
erase_status = QSPI_ReadData();
}
QSPI_ReadFlash(dst, PROGRAME_SIZE, QSPI_BUFFER );
}
首先看第16行,对QSPI结构体初始化,分别配置片选高电平时间,QSPI时钟信号在指令之间的电平,QSPI时钟分频。

接着第17行,发送写使能命令。该函数主要配置QSPI->CCR寄存器,主要配置指令写使能指令和简介写模式,其余配置读者可查阅AS32X601设计手册的QSPI章节,这里不做过多赘述。

第22行,进行扇区擦除,和写使能函数一致,区别在于配置不同的指令,第24行设置需要擦除区域的首地址。

第30行,配置QSPI数据长度寄存器(DLR)为0,我们需要读取1字节,即SR1寄存器8位。第31行,我们发送读取QSPI FLASH的SR1寄存器指令。第33行,读取QSPI FLASH返回的8位数据,直到erase_status = 0时,我们擦除操作完成。这里是间接读取模式,还可使用轮询模式,减少CPU等待时间,完全交给QSPI FLASH来处理数据。

以下是读取SR1寄存器的相关配置,和擦除函数一致,区别在于指令,当然读者可以自行配置QSPI 为2线或4线。

2、读QSPI FLASH
接上节,我们在最后调用了QSPI_ReadFlash函数,本章节详细讲解该函数,先看FLASH手册,

该指令允许从存储器阵列的任意字节地址开始读取。每输出一个字节数据后,地址会自动递增至下一个更高地址(按顺序连续递增)。因此,只需提供起始地址000000h并发送一次读指令,即可连续读取整个存储器的内容。当到达最高地址后,地址计数器将自动回绕至000000h,从而支持无限循环的连续读取操作。
函数如下:
void QSPI_ReadFlash(uint32_t block_start, uint32_t byte_num, uint32_t buffer[])
{
uint32_t FIFO_LEVEL = 0;
QSPI_WriteEnable();
QSPI_SetDataLength(byte_num);
QSPI_Read_Flash();
QSPI_SetAddr(block_start);
delay_ms(10);
FIFO_LEVEL = QSPI_GetFIFOLevel();
while(FIFO_LEVEL != 0)
{
for(uint32_t i=0;i<FIFO_LEVEL;i++)
{
buffer[i] = QSPI_ReadData();
Printf("%x\t",buffer[i]);
}
FIFO_LEVEL = QSPI_GetFIFOLevel();
}
QSPI_WriteDisable();
}
同样在读数据之前需要解锁QSPI FLASH,并在操作完成后上锁。第5行,设置读取需要读取的字节数,第6行,调用QSPI_READ_FLASH(),函数如下,第9行,获取FIFO中有效字节数,当FIFO非空时,读QSPI->DR寄存器获取数据,直到FIFO为空。

3、下板验证
程序初始化后,调用QSPI_FlashErase(0x00, 1),从0地址开始,擦除1个扇区,观察打印信息,并截取了逻辑分析仪片段。


4、页编程函数

主要内容:若地址的最低9位(A8-A0)不全为0,则超出当前页末尾的传输数据将从同一页的起始地址(即最低9位全为0的地址)开始编程,即地址会在页对齐的边界内回绕。这是因为用户只需输入单个页地址即可覆盖整个页边界。
如果发送给设备的数据不足一页,这些数据字节将从所提供地址开始按顺序编程,而不会影响同一页内的其他字节。
为优化时序,使用页编程(PP)命令在页边界内加载整个页大小的编程缓冲器,相比于加载不足一页的数据到编程缓冲器,将节省总体编程时间。
int QSPI_FlashWrite(uint32_t block_start,uint32_t offset_into_block, uint32_t count, uint32_t buffer[])
{
uint32_t dst;
uint32_t ii;
uint32_t num;
dst = block_start + offset_into_block;
num = count;
if(dst%4 != 0)
{
Printf( "the addr must 4-byte alignment\r\n");
return 0;
}
QSPI_Deinit();
if((dst%256) == 0)
{ int loop = 0;
Printf( "the first packge is 256 bytes\r\n");
while(num > 256)
{
QSPI_StructInitenable();
QSPI_SetDataLength(0xff);
QSPI_WriteEnable();
QSPI_PageProgram();
QSPI_SetAddr(dst);
for(ii = loop*256/4; ii < loop*256/4+64; ii++)
{
QSPI->DR = buffer[ii];
}
while( (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) == 1));
dst += 256;
num -= 256;
delay_ms(20);
loop++;
}
QSPI_StructInitenable();
QSPI_SetDataLength(block_start + offset_into_block + count - dst -1);
QSPI_WriteEnable();
QSPI_PageProgram();
QSPI_SetAddr(dst);
for(ii = 256*loop/4; ii < (256*loop+block_start + offset_into_block + count - dst)/4; ii++)
{
QSPI->DR = buffer[ii];
}
while( (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) == 1));
dst += block_start + offset_into_block + count -- dst;
}
else
{
QSPI_StructInitenable();
QSPI_WriteEnable();
QSPI_PageProgram();
QSPI_SetAddr(dst);
QSPI_SetDataLength(256-(dst%256));
for(ii = 0; ii < (256-(dst%256))/4; ii++)
{
QSPI->DR = buffer[ii];
}
while( (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) == 1));
dst += 256-(dst%256);
while(num > 256)
{
QSPI_StructInitenable();
QSPI_WriteEnable();
QSPI_PageProgram();
QSPI_SetAddr(dst);
QSPI_SetDataLength(256-1);
for(ii = 0; ii < 64; ii++)
{
QSPI->DR = buffer[ii];
}
while( (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) == 1));
dst += 256;
num -= 256;
}
QSPI_StructInitenable();
QSPI_WriteEnable();
QSPI_PageProgram();
QSPI_SetAddr(dst);
QSPI_SetDataLength(block_start + offset_into_block + count - dst -1);
for(ii = 0; ii < (block_start + offset_into_block + count - dst)/4; ii++)
{
QSPI->DR = buffer[ii];
}
while( (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) == 1));
dst += block_start + offset_into_block + count - dst;
}
QSPI_WriteDisable();
while( (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) == 1));
QSPI_ReadFlash(block_start,PROGRAME_SIZE,QSPI_BUFFER);
return 0; //RESULT_OK
}
第1至13行定义了函数参数并计算目标地址,同时检查地址是否4字节对齐,若未对齐则报错返回。第14行对QSPI控制器进行复位。第15至48行处理目标地址256字节对齐的情况:第16至35行循环写入完整的256字节数据页,每次写入前初始化硬件、设置数据长度、使能写入、发送页编程命令并设置地址,随后通过循环写入64个32位数据,等待操作完成后更新地址和剩余计数;第36至47行处理剩余不足256字节的数据尾部,采用类似的流程但数据长度根据剩余量动态计算。第49至88行处理目标地址未256字节对齐的情况:第51至60行先写入从起始地址到下一个256字节边界的首部数据片段;第61至75行循环写入后续完整的256字节页;第76至87行处理最后的尾部数据。
5、下板验证
准备写入1024个数,0-0x3FF,共4K,从0地址开始,观察打印信息


