软件环境:vivado 2017.4 硬件平台:XC7Z020
在实际项目中,写好的ZYNQ工程在debug测试完毕之后,固化到FLASH往往是最后一步,然而,在固化的过程中,往往并不都是一次就能成功的,而固化不成功的原因也很多,最直接的排查手段就是可以先做一下FLASH的读写测试,看下FLASH的访问是否正常。
这里以镁光N25Q128为例子,说下常规FLASH的操作和测试过程,工程这边没什么特别需要注意的,用的是之前测试uartlite的工程,接口是QSPI Flash的。
首先打开flash手册,可以看到有这么一段比较重要的说明:
可以看出,flash是从1写到0,也就是说,擦除后的flash应该是全FF,然后写数据时候,往对应bit写0即可; 总字节数16MB,包含256个sectors(每sector 64K),相当于4096个subsectors(每个subsectors 4K),或65536个page(每个page 256B)。以框图描述如下:
主要还是要记住sector有256个,包括65536个page,每个page 256B,每个地址代表1B。
搞清楚存储的结构之后,下一个要关心的就是读写等操作指令了,下面只是列举了其中一部分,不同厂商,不同容量的话,需要注意有些指令是有可能不一样的:
另外,在使用fast read时候,需要注意,上来的数据中,首先是dummy bytes:
写的话就直接按照指令+地址+数据的格式就好:
先上代码:
#include "xparameters.h" /* SDK generated parameters */
#include "xqspips.h" /* QSPI device driver */
#include "xil_printf.h"
#include "sleep.h"
/************************** Constant Definitions *****************************/
/*
* The following constants map to the XPAR parameters created in the
* xparameters.h file. They are defined here such that a user can easily
* change all the needed parameters in one place.
*/
#define QSPI_DEVICE_ID XPAR_XQSPIPS_0_DEVICE_ID
/*
* The following constants define the commands which may be sent to the FLASH
* device.
*/
#define WRITE_STATUS_CMD 0x01
#define WRITE_CMD 0x02
#define READ_CMD 0x03
#define WRITE_DISABLE_CMD 0x04
#define READ_STATUS_CMD 0x05
#define WRITE_ENABLE_CMD 0x06
#define FAST_READ_CMD 0x0B
#define DUAL_READ_CMD 0x3B
#define QUAD_READ_CMD 0x6B
#define BULK_ERASE_CMD 0xC7
#define SEC_ERASE_CMD 0xD8
#define READ_ID 0x9F
/*
* The following constants define the offsets within a FlashBuffer data
* type for each kind of data. Note that the read data offset is not the
* same as the write data because the QSPI driver is designed to allow full
* duplex transfers such that the number of bytes received is the number
* sent and received.
*/
#define COMMAND_OFFSET 0 /* FLASH instruction */
#define ADDRESS_1_OFFSET 1 /* MSB byte of address to read or write */
#define ADDRESS_2_OFFSET 2 /* Middle byte of address to read or write */
#define ADDRESS_3_OFFSET 3 /* LSB byte of address to read or write */
#define DATA_OFFSET 4 /* Start of Data for Read/Write */
#define DUMMY_OFFSET 4 /* Dummy byte offset for fast, dual and quad reads */
#define DUMMY_SIZE 1 /* Number of dummy bytes for fast, dual and quad reads */
#define RD_ID_SIZE 4 /* Read ID command + 3 bytes ID response */
#define BULK_ERASE_SIZE 1 /* Bulk Erase command size */
#define SEC_ERASE_SIZE 4 /* Sector Erase command + Sector address */
/*
* The following constants specify the extra bytes which are sent to the
* FLASH on the QSPI interface, that are not data, but control information
* which includes the command and address
*/
#define OVERHEAD_SIZE 4
/*
* The following constants specify the page size, sector size, and number of
* pages and sectors for the FLASH. The page size specifies a max number of
* bytes that can be written to the FLASH with a single transfer.
*/
#define SECTOR_SIZE 0x10000//65536
#define NUM_SECTORS 0x100//256
#define NUM_PAGES 0x10000//65536
#define PAGE_SIZE 256
/*
* Number of flash pages to be written.
*/
#define PAGE_COUNT 65536
/*
* Flash address to which data is ot be written.
*/
#define TEST_ADDRESS 0x00000000//0x00055000
#define UNIQUE_VALUE 0x05
/*
* The following constants specify the max amount of data and the size of the
* the buffer required to hold the data and overhead to transfer the data to
* and from the FLASH.
*/
#define MAX_DATA PAGE_COUNT * PAGE_SIZE
/**************************** Type Definitions *******************************/
/***************** Macros (Inline Functions) Definitions *********************/
/************************** Function Prototypes ******************************/
int FlashErase(XQspiPs *QspiPtr, u32 Address, u32 ByteCount);
void FlashWrite(XQspiPs *QspiPtr, u32 Address, u32 ByteCount, u8 Command);
void FlashRead(XQspiPs *QspiPtr, u32 Address, u32 ByteCount, u8 Command);
int FlashReadID(void);
int QspiFlashPolledExample(XQspiPs *QspiInstancePtr, u16 QspiDeviceId);
/************************** Variable Definitions *****************************/
/*
* The instances to support the device drivers are global such that they
* are initialized to zero each time the program runs. They could be local
* but should at least be static so they are zeroed.
*/
static XQspiPs QspiInstance;
/*
* The following variable allows a test value to be added to the values that
* are written to the FLASH such that unique values can be generated to
* guarantee the writes to the FLASH were successful
*/
int Test = 5;
/*
* The following variables are used to read and write to the flash and they
* are global to avoid having large buffers on the stack
*/
u8 ReadBuffer[MAX_DATA + DATA_OFFSET + DUMMY_SIZE];
u8 WriteBuffer[PAGE_SIZE + DATA_OFFSET];
/*****************************************************************************/
/**
*
* Main function to call the QSPI Flash example.
*
* @param None
*
* @return XST_SUCCESS if successful, otherwise XST_FAILURE.
*
* @note None
*
******************************************************************************/
#define FLASH_TEST_ADDRESS0 0x00000000
int System_init_startup (void);
int qspi_flash_id(void);
int qspi_ease_entire_flash(void);
int Spi_Blank_Check(u32 StartAddr, u32 NoByteToRead);
int qspi_flash_write(void);
int qspi_flash_read(void);
static int qspi_init_flag=0;
static u32 Address = FLASH_TEST_ADDRESS0;
int main(void)
{
int Status;
int Cnt;
int choice, exit_flag = 0;
u32 SectCount=1, StartAddr, NoByteToRead;
if (qspi_init_flag ==0)
{
Status = System_init_startup();
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
else
qspi_init_flag=1;
}
StartAddr = FLASH_TEST_ADDRESS0;
NoByteToRead = 512;
SectCount = 512;
while(exit_flag != 1)
{
print("\n\r*******************************************************************\n\r");
print("\n\rChoose from options below: \r\n");
print("1: Read Quad SPI flash ID\r\n");
print("2: Erase Quad SPI flash\r\n");
print("3: Blank Check Quad SPI flash\r\n");
print("4: Write Quad SPI flash\r\n");
print("5: Read Quad SPI flash\r\n");
print("\n\r*******************************************************************\n\r");
choice = inbyte();
if (isalpha(choice)) {
choice = toupper(choice);
}
switch(choice)
{
case '1':
{
print("\n\r\t Read Quad SPI flash ID\t\r\n");
qspi_flash_id();
}
break;
case '2':
{
print("Entire flash erase (Bulk Erase)\r\n");
FlashErase(&QspiInstance, TEST_ADDRESS, MAX_DATA);
print("Entire flash erase finish!\r\n");
}
break;
case '3':
{
print ("\n\rQuad SPI flash Blank Check:\n\r");
xil_printf("\r\n\r\nStart Address \t= 0x%08x\n\rEnd Address \t= 0x%08x\n\r", StartAddr,(TEST_ADDRESS + MAX_DATA));
print ("\n\rPerforming Blank Check operation...\n\r");
Status = Spi_Blank_Check(TEST_ADDRESS, MAX_DATA);
if (Status != XST_SUCCESS) {
print("\n\r\n\r\t\tBlank Check Operation Fail!.\r\n");
}else
{
print("\n\r\n\rBlank Check Operation Completed without error.\r\n");
}
}
break;
case '4':
{
print ("\n\rWrite Quad SPI flash:\n\r");
qspi_flash_write();
}
break;
case '5':
{
print ("\n\rRead Quad SPI flash:\n\r");
qspi_flash_read();
}
break;
default:break;
}
}
}
int System_init_startup (void)
{
int Status;
XQspiPs_Config *QspiConfig;
QspiConfig = XQspiPs_LookupConfig(QSPI_DEVICE_ID);
if (NULL == QspiConfig) {
return XST_FAILURE;
}
Status = XQspiPs_CfgInitialize(&QspiInstance, QspiConfig,
QspiConfig->BaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
XQspiPs_SetOptions(&QspiInstance, XQSPIPS_MANUAL_START_OPTION |
XQSPIPS_FORCE_SSELECT_OPTION |
XQSPIPS_HOLD_B_DRIVE_OPTION);
XQspiPs_SetClkPrescaler(&QspiInstance, XQSPIPS_CLK_PRESCALE_8);
XQspiPs_SetSlaveSelect(&QspiInstance);
return XST_SUCCESS;
}
int qspi_flash_id(void)
{
int Status;
if (qspi_init_flag ==0)
{
Status = System_init_startup ();
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
else
qspi_init_flag=1;
}
Status = FlashReadID();
if(Status != XST_SUCCESS) {
return XST_FAILURE;
}
return XST_SUCCESS;
}
int Spi_Blank_Check(u32 StartAddr, u32 NoByteToRead)
{
int Status;
int remaind_int, NoOfPage;
u32 Index,StartOffsetAddr;
StartOffsetAddr = StartAddr;
if (qspi_init_flag ==0)
{
Status = System_init_startup ();
if (Status != XST_SUCCESS) {
} else qspi_init_flag=1;
}
NoOfPage = (NoByteToRead/PAGE_SIZE);
remaind_int = (NoByteToRead - ( NoOfPage * PAGE_SIZE));
if (remaind_int != 0) {
NoOfPage = (NoOfPage+1);
}
xil_printf("\n\rPerforming Blank Check Operation...\n\rStart Address\t= 0x%08x\n\rEnd Address\t= 0x%08x\n\rNumber Of Pages\t= 0x%08x\n\r", StartOffsetAddr, NoByteToRead, NoOfPage);
while (NoOfPage !=0)
{
memset(ReadBuffer, 0x00, sizeof(ReadBuffer));
FlashRead(&QspiInstance, StartOffsetAddr, PAGE_SIZE, FAST_READ_CMD);
xil_printf("\nStartOffsetAddr %x:",StartOffsetAddr);
for(Index = 0; Index < PAGE_SIZE; Index++)
{
if(ReadBuffer[Index + DATA_OFFSET + DUMMY_SIZE] != 0xFF)
{
xil_printf("Blank Check Fail at Address:0x%x = 0x%x\r\n", (TEST_ADDRESS + Index), (ReadBuffer[Index + DATA_OFFSET + DUMMY_SIZE]));
return XST_FAILURE;
} else
{
xil_printf(".");
}
}
NoOfPage--;
StartOffsetAddr = (StartOffsetAddr + PAGE_SIZE);
}
return XST_SUCCESS;
}
int qspi_flash_write(void)
{
int Page;
int Count;
u8 UniqueValue;
print ("\n\rWriteBuffer:");
for (UniqueValue = UNIQUE_VALUE, Count = 0; Count < PAGE_SIZE;
Count++, UniqueValue++) {
WriteBuffer[DATA_OFFSET + Count] = (u8)(UniqueValue);
xil_printf (" 0x%x ",WriteBuffer[DATA_OFFSET + Count]);
}
for (Page = 0; Page < 5; Page++) {
FlashWrite(&QspiInstance, (Page * PAGE_SIZE) + TEST_ADDRESS,PAGE_SIZE, WRITE_CMD);
usleep(500000);
}
return XST_SUCCESS;
}
int qspi_flash_read(void)
{
int Cnt;
memset(ReadBuffer, 0x00, sizeof(ReadBuffer));
FlashRead(&QspiInstance, TEST_ADDRESS, (5 * PAGE_SIZE) , FAST_READ_CMD);
for(Cnt = 0; Cnt < (5 * PAGE_SIZE); Cnt++)
{
xil_printf(" 0x%x ", (ReadBuffer[Cnt + DATA_OFFSET + DUMMY_SIZE]));
}
return XST_SUCCESS;
}
/*****************************************************************************/
/**
*
* This function writes to the serial FLASH connected to the QSPI interface.
* All the data put into the buffer must be in the same page of the device with
* page boundaries being on 256 byte boundaries.
*
* @param QspiPtr is a pointer to the QSPI driver component to use.
* @param Address contains the address to write data to in the FLASH.
* @param ByteCount contains the number of bytes to write.
* @param Command is the command used to write data to the flash. QSPI
* device supports only Page Program command to write data to the
* flash.
*
* @return None.
*
* @note None.
*
******************************************************************************/
void FlashWrite(XQspiPs *QspiPtr, u32 Address, u32 ByteCount, u8 Command)
{
u8 WriteEnableCmd = { WRITE_ENABLE_CMD };
u8 ReadStatusCmd[] = { READ_STATUS_CMD, 0 }; /* must send 2 bytes */
u8 FlashStatus[2];
/*
* Send the write enable command to the FLASH so that it can be
* written to, this needs to be sent as a seperate transfer before
* the write
*/
XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
sizeof(WriteEnableCmd));
/*
* Setup the write command with the specified address and data for the
* FLASH
*/
WriteBuffer[COMMAND_OFFSET] = Command;
WriteBuffer[ADDRESS_1_OFFSET] = (u8)((Address & 0xFF0000) >> 16);
WriteBuffer[ADDRESS_2_OFFSET] = (u8)((Address & 0xFF00) >> 8);
WriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);
/*
* Send the write command, address, and data to the FLASH to be
* written, no receive buffer is specified since there is nothing to
* receive
*/
XQspiPs_PolledTransfer(QspiPtr, WriteBuffer, NULL,
ByteCount + OVERHEAD_SIZE);
/*
* Wait for the write command to the FLASH to be completed, it takes
* some time for the data to be written
*/
while (1) {
/*
* Poll the status register of the FLASH to determine when it
* completes, by sending a read status command and receiving the
* status byte
*/
XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd, FlashStatus,
sizeof(ReadStatusCmd));
/*
* If the status indicates the write is done, then stop waiting,
* if a value of 0xFF in the status byte is read from the
* device and this loop never exits, the device slave select is
* possibly incorrect such that the device status is not being
* read
*/
if ((FlashStatus[1] & 0x01) == 0) {
break;
}
}
}
/*****************************************************************************/
/**
*
* This function reads from the serial FLASH connected to the
* QSPI interface.
*
* @param QspiPtr is a pointer to the QSPI driver component to use.
* @param Address contains the address to read data from in the FLASH.
* @param ByteCount contains the number of bytes to read.
* @param Command is the command used to read data from the flash. QSPI
* device supports one of the Read, Fast Read, Dual Read and Fast
* Read commands to read data from the flash.
*
* @return None.
*
* @note None.
*
******************************************************************************/
void FlashRead(XQspiPs *QspiPtr, u32 Address, u32 ByteCount, u8 Command)
{
/*
* Setup the write command with the specified address and data for the
* FLASH
*/
WriteBuffer[COMMAND_OFFSET] = Command;
WriteBuffer[ADDRESS_1_OFFSET] = (u8)((Address & 0xFF0000) >> 16);
WriteBuffer[ADDRESS_2_OFFSET] = (u8)((Address & 0xFF00) >> 8);
WriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);
if ((Command == FAST_READ_CMD) || (Command == DUAL_READ_CMD) ||
(Command == QUAD_READ_CMD)) {
ByteCount += DUMMY_SIZE;
}
/*
* Send the read command to the FLASH to read the specified number
* of bytes from the FLASH, send the read command and address and
* receive the specified number of bytes of data in the data buffer
*/
XQspiPs_PolledTransfer(QspiPtr, WriteBuffer, ReadBuffer,
ByteCount + OVERHEAD_SIZE);
}
/*****************************************************************************/
/**
*
* This function erases the sectors in the serial FLASH connected to the
* QSPI interface.
*
* @param QspiPtr is a pointer to the QSPI driver component to use.
* @param Address contains the address of the first sector which needs to
* be erased.
* @param ByteCount contains the total size to be erased.
*
* @return None.
*
* @note None.
*
******************************************************************************/
int FlashErase(XQspiPs *QspiPtr, u32 Address, u32 ByteCount)
{
u8 WriteEnableCmd = { WRITE_ENABLE_CMD };
u8 ReadStatusCmd[] = { READ_STATUS_CMD, 0 }; /* must send 2 bytes */
u8 FlashStatus[2];
int Sector;
/*
* If erase size is same as the total size of the flash, use bulk erase
* command
*/
if (ByteCount == (NUM_SECTORS * SECTOR_SIZE)) {
/*
* Send the write enable command to the FLASH so that it can be
* written to, this needs to be sent as a seperate transfer
* before the erase
*/
XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
sizeof(WriteEnableCmd));
/*
* Setup the bulk erase command
*/
WriteBuffer[COMMAND_OFFSET] = BULK_ERASE_CMD;
/*
* Send the bulk erase command; no receive buffer is specified
* since there is nothing to receive
*/
XQspiPs_PolledTransfer(QspiPtr, WriteBuffer, NULL,
BULK_ERASE_SIZE);
/*
* Wait for the erase command to the FLASH to be completed
*/
while (1) {
/*
* Poll the status register of the device to determine
* when it completes, by sending a read status command
* and receiving the status byte
*/
XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd,
FlashStatus,
sizeof(ReadStatusCmd));
/*
* If the status indicates the write is done, then stop
* waiting; if a value of 0xFF in the status byte is
* read from the device and this loop never exits, the
* device slave select is possibly incorrect such that
* the device status is not being read
*/
if ((FlashStatus[1] & 0x01) == 0) {
break;
}
}
return XST_SUCCESS;
}
/*
* If the erase size is less than the total size of the flash, use
* sector erase command
*/
for (Sector = 0; Sector < ((ByteCount / SECTOR_SIZE) + 1); Sector++) {
/*
* Send the write enable command to the SEEPOM so that it can be
* written to, this needs to be sent as a seperate transfer
* before the write
*/
XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
sizeof(WriteEnableCmd));
/*
* Setup the write command with the specified address and data
* for the FLASH
*/
WriteBuffer[COMMAND_OFFSET] = SEC_ERASE_CMD;
WriteBuffer[ADDRESS_1_OFFSET] = (u8)(Address >> 16);
WriteBuffer[ADDRESS_2_OFFSET] = (u8)(Address >> 8);
WriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);
/*
* Send the sector erase command and address; no receive buffer
* is specified since there is nothing to receive
*/
XQspiPs_PolledTransfer(QspiPtr, WriteBuffer, NULL,
SEC_ERASE_SIZE);
/*
* Wait for the sector erse command to the FLASH to be completed
*/
while (1) {
/*
* Poll the status register of the device to determine
* when it completes, by sending a read status command
* and receiving the status byte
*/
XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd,
FlashStatus,
sizeof(ReadStatusCmd));
/*
* If the status indicates the write is done, then stop
* waiting, if a value of 0xFF in the status byte is
* read from the device and this loop never exits, the
* device slave select is possibly incorrect such that
* the device status is not being read
*/
if ((FlashStatus[1] & 0x01) == 0) {
break;
}
}
Address += SECTOR_SIZE;
}
return XST_SUCCESS;
}
/*****************************************************************************/
/**
*
* This function reads serial FLASH ID connected to the SPI interface.
*
* @param None.
*
* @return XST_SUCCESS if read id, otherwise XST_FAILURE.
*
* @note None.
*
******************************************************************************/
int FlashReadID(void)
{
int Status;
/*
* Read ID in Auto mode.
*/
WriteBuffer[COMMAND_OFFSET] = READ_ID;
WriteBuffer[ADDRESS_1_OFFSET] = 0x23; /* 3 dummy bytes */
WriteBuffer[ADDRESS_2_OFFSET] = 0x08;
WriteBuffer[ADDRESS_3_OFFSET] = 0x09;
Status = XQspiPs_PolledTransfer(&QspiInstance, WriteBuffer, ReadBuffer,
RD_ID_SIZE);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
xil_printf("FlashID=0x%x 0x%x 0x%x\n\r", ReadBuffer[1], ReadBuffer[2],
ReadBuffer[3]);
if ( (ReadBuffer[1] == 0x20))
{
xil_printf("\n\rManufacturer ID:\t0x%x\t:= MICRON\n\r", ReadBuffer[1]);
if ( (ReadBuffer[2] == 0xBA))
{
xil_printf("Memory Type:\t\t0x%x\t:= N25Q 3V0\n\r", ReadBuffer[2]);
}
else
{
if ((ReadBuffer[2] == 0xBB))
{
xil_printf("Memory Type:\t\t0x%x\t:= N25Q 1V8\n\r", ReadBuffer[2]);
} else xil_printf("Memory Type:\t\t0x%x\t:= QSPI Data\n\r", ReadBuffer[2]);
}
if ((ReadBuffer[3] == 0x18))
{
xil_printf("Memory Capacity:\t0x%x\t:= 128Mbit\n\r", ReadBuffer[3]);
}
else if ( (ReadBuffer[3] == 0x19))
{
xil_printf("Memory Capacity:\t0x%x\t:= 256Mbit\n\r", ReadBuffer[3]);
}
else if ((ReadBuffer[3] == 0x20))
{
xil_printf("Memory Capacity:\t0x%x\t:= 512Mbit\n\r", ReadBuffer[3]);
}
else if ((ReadBuffer[3] == 0x21))
{
xil_printf("Memory Capacity:\t0x%x\t:= 1024Mbit\n\r", ReadBuffer[3]);
}
}
else if ((ReadBuffer[1] == 0x01))
{
xil_printf("\n\rManufacturer ID: \tSPANSION\n\r");
if ((ReadBuffer[3] == 0x18))
{
xil_printf("Memory Capacity\t=\t256Mbit\n\r");
}
else if ((ReadBuffer[3] == 0x19))
{
xil_printf("Memory Capacity\t=\t512Mbit\n\r");
}
else if ((ReadBuffer[3] == 0x20))
{
xil_printf("Memory Capacity\t=\t1024Mbit\n\r");
}
}
else if ((ReadBuffer[1] == 0xEF))
{
xil_printf("\n\rManufacturer ID\t=\tWINBOND\n\r");
if ((ReadBuffer[3] == 0x18))
{
xil_printf("Memory Capacity\t=\t128Mbit\n\r");
}
}
return XST_SUCCESS;
}
下面对代码进行简要的说明:
1.代码总共包含5个功能,读ID、整片擦除、空白检测、写入数据、读取数据,通过串口进行交互
2.代码最开始#define _CMD相关的指令和OFFSET、SIZE配置等参数,需要根据实际使用的片子来进行调整,保证跟手册一致,否则有可能无法正常进行测试
3.读、写测试均测试5个page,起始地址由代码中的TEST_ADDRESS来指定
4.在System_init_startup()中完成了QSPI接口的初始化,通过XQspiPs_SetOptions()来设置参数,通过XQspiPs_SetClkPrescaler()来设置时钟的分频
5.如果待测的是1Gb那种大容量flash,一定注意要把FlashWrite()和FlashRead()函数接口中的地址长度改掉,当前使用的是3字节地址
6.不同厂商不同芯片不同读取指令,dummy bytes个数可能会不同,如果懒得查手册,最好实际读取时候打断点,来确定最开始的dummy bytes个数
7.在写flash时,注意要有小的间隔,否则有可能写入的数据完全不正确
接下来展示实测结果:
读ID:
整片擦除:
此时回读前5个page:
进行空白检测:
然后再向前5个page循环写入从5开始的递增数:
此时回读新写入后的5个page,可见: