硬件环境:ALINX 7020
ZYNQ的QSPI Flash 控制器有以下三种模式:I/O 模式、线性地址模式,以及传统 SPI 模式。
I/O模式
操作特点:在I/O模式下,软件模拟去实现 Flash 器件的通信协议。软件需要将 Flash 命令和数据写到控制器中的 TXD寄存器中,然后将接收到的数据从 RXD 寄存器中读出。软件与闪存设备协议紧密交互,命令、地址和数据都需要根据SPI Flash的数据手册规定,由用户软件组织并写入FIFO中。QSPI控制器负责将这些数据串行化后通过总线发出。
应用场景:这种模式适合对Flash操作有高级控制需求的情况,如需要优化命令序列或处理复杂的读写操作。
线性地址模式
操作特点:在线性地址模式下,QSPI控制器使用AXI接口进行数据交互,可以无需软件开销地读取Flash,且能够支持高达32MB的线性地址空间。这一模式下,数据传输更为高效,尤其在读取大量数据时。但是该模式只支持读操作,不支持写操作。
应用场景:适合于数据密集型应用,如需要快速读取大型数据文件或代码库。
传统SPI模式
操作特点:在传统SPI模式下,QSPI控制器操作如同一个标准的SPI控制器,与一个或两个闪存设备接口,支持单个从器件模式、双从器件并行模式和双从器件堆叠模式。这种模式简化了电路设计,降低了系统成本。
应用场景:适用于对传输速度要求不高,但需要简化硬件设计的场合。
由系统框图可知,flash控制器通过MIO和Flash器件相连接,可以支持单个从器件,双从器件并行和双从器件堆模式。有两种类型的接口:AXI 接口和 APB 接口。其中 AXI 接口用于线性地址模式,而 APB 接口用于 I/O 模式。
在 I/O 模式下,软件需要把命令和数据转化成 QSPI Flash 协议下的指令,转换之后的指令将被写入 Tx FIFO。然后发送逻辑将 Tx FIFO 中的内容按照 QSPI 接口规范进行并串转换,最后通过 MIO 将转换后的数据送到 Flash 存储器中。在发送逻辑将 Tx FIFO 中的数据发送出去的同时,接收逻辑会采样所发送的串行数据,进行串并转换后存储到 Rx FIFO 里面。
#include "xparameters.h" /* SDK generated parameters */
#include "xqspips.h" /* QSPI device driver */
#include "xil_printf.h"#define QSPI_DEVICE_ID XPAR_XQSPIPS_0_DEVICE_ID//发送到FLASH器件的指令
#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//FLASH BUFFER中各数据的偏移量
#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 reads#define DUMMY_SIZE 1 // Number of dummy bytes for 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#define OVERHEAD_SIZE 4 // control information: command and address#define SECTOR_SIZE 0x10000
#define NUM_SECTORS 0x100
#define NUM_PAGES 0x10000
#define PAGE_SIZE 256/* Number of flash pages to be written.*/
#define PAGE_COUNT 16/* Flash address to which data is to be written.*/
#define TEST_ADDRESS 0x00055000
#define UNIQUE_VALUE 0x05#define MAX_DATA (PAGE_COUNT * PAGE_SIZE)void 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);
void FlashQuadEnable(XQspiPs *QspiPtr);
int QspiFlashPolledExample(XQspiPs *QspiInstancePtr, u16 QspiDeviceId);static XQspiPs QspiInstance;int Test = 5;u8 ReadBuffer[MAX_DATA + DATA_OFFSET + DUMMY_SIZE];
u8 WriteBuffer[PAGE_SIZE + DATA_OFFSET];int main(void)
{int Status;xil_printf("QSPI FLASH Polled Example Test \r\n");/* Run the Qspi Interrupt example.*/Status = QspiFlashPolledExample(&QspiInstance, QSPI_DEVICE_ID);if (Status != XST_SUCCESS) {xil_printf("QSPI FLASH Polled Example Test Failed\r\n");return XST_FAILURE;}xil_printf("Successfully ran QSPI FLASH Polled Example Test\r\n");return XST_SUCCESS;
}int QspiFlashPolledExample(XQspiPs *QspiInstancePtr, u16 QspiDeviceId)
{int Status;u8 *BufferPtr;u8 UniqueValue;int Count;int Page;XQspiPs_Config *QspiConfig;//初始化QSPI驱动QspiConfig = XQspiPs_LookupConfig(QspiDeviceId);XQspiPs_CfgInitialize(QspiInstancePtr, QspiConfig, QspiConfig->BaseAddress);//初始化读写BUFFERfor (UniqueValue = UNIQUE_VALUE, Count = 0; Count < PAGE_SIZE;Count++, UniqueValue++) {WriteBuffer[DATA_OFFSET + Count] = (u8)(UniqueValue + Test);}memset(ReadBuffer, 0x00, sizeof(ReadBuffer));//设置手动启动和手动片选模式XQspiPs_SetOptions(QspiInstancePtr, XQSPIPS_MANUAL_START_OPTION |XQSPIPS_FORCE_SSELECT_OPTION |XQSPIPS_HOLD_B_DRIVE_OPTION);//设置QSPI时钟的分频系数XQspiPs_SetClkPrescaler(QspiInstancePtr, XQSPIPS_CLK_PRESCALE_8);//片选信号置为有效XQspiPs_SetSlaveSelect(QspiInstancePtr);//读FLASH IDFlashReadID();//使能FLASH Quad模式FlashQuadEnable(QspiInstancePtr);//擦除FLASHFlashErase(QspiInstancePtr, TEST_ADDRESS, MAX_DATA);//向FLASH中写入数据for (Page = 0; Page < PAGE_COUNT; Page++) {FlashWrite(QspiInstancePtr, (Page * PAGE_SIZE) + TEST_ADDRESS,PAGE_SIZE, WRITE_CMD);}//使用QUAD模式从FLASH中读出数据FlashRead(QspiInstancePtr, TEST_ADDRESS, MAX_DATA, QUAD_READ_CMD);//对比写入FLASH与从FLASH中读出的数据BufferPtr = &ReadBuffer[DATA_OFFSET + DUMMY_SIZE];for (UniqueValue = UNIQUE_VALUE, Count = 0; Count < MAX_DATA;Count++, UniqueValue++) {if (BufferPtr[Count] != (u8)(UniqueValue + Test)) {return XST_FAILURE;}}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.
*
******************************************************************************/
void 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;}/** 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;}
}/*****************************************************************************/
/**
*
* 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]);return XST_SUCCESS;
}/*****************************************************************************//**** This function enables quad mode in the serial flash connected to the* SPI interface.** @param QspiPtr is a pointer to the QSPI driver component to use.** @return None.** @note None.*******************************************************************************/
void FlashQuadEnable(XQspiPs *QspiPtr)
{u8 WriteEnableCmd = {WRITE_ENABLE_CMD};u8 ReadStatusCmd[] = {READ_STATUS_CMD, 0};u8 QuadEnableCmd[] = {WRITE_STATUS_CMD, 0};u8 FlashStatus[2];if (ReadBuffer[1] == 0x9D) {XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd,FlashStatus,sizeof(ReadStatusCmd));QuadEnableCmd[1] = FlashStatus[1] | 1 << 6;XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,sizeof(WriteEnableCmd));XQspiPs_PolledTransfer(QspiPtr, QuadEnableCmd, NULL,sizeof(QuadEnableCmd));}
}