以下内容仅记录本人工作中发现的问题以及解决方法,仅供参考。

        文章记录了作者在基于zynq平台裸机开发过程中,为验证新制板卡上的flash芯片读写功能和程序的固化,遇到的软硬件问题,并在文章中详细说明了flash芯片读写功能验证的工作流程、vitis的程序固化流程、调试中的问题记录,以及完整的工程代码。

目录

一、前言

二、开发样例解析

三、flash芯片读写功能验证的工作流程

1、初始化QSPI 

2、擦flash

3、写flash

4、读flash

5、读写数据比较

四、调试中的问题记录

1、如何判断读取ID正确

2、读取ID异常问题

3、读取ID正常数据异常问题

4、程序固化异常问题

五、vitis的程序固化流程

1、生成boot.bin文件

2、boot.bin文件的烧录

六、工程源码

1、qspi.h

2、qspi.c

3、main

4、实验结果

一、前言

       区别于以前的工作环境,使用的硬件开发板外购,硬件外设接口的基本电路及功能都正常,到手进行一些简单的电源测试直接开发即可。 本人使用自研的非标准开发板,硬件接口及所有外设芯片的功能均要验证一遍,验证flash芯片的主要目的是vitis程序的固化,第一次缺乏经验未验证flash芯片功能,直接进行固化流程,不出意外的烧录失败,也因此有了这篇文章的调试踩坑记,将问题一一记录,希望可以帮助到有同样问题的开发者。

        本文验证flash芯片读写功能建立在vivadoPL端PS的IP核配置完成,串口已经打通的基础上,验证flash芯片读写功能需要使用串口打印flash芯片ID以及读出写入的数据和数据比对结果。flash芯片型号是IS25LP256D。串口相关内容可参考上一篇笔记。zynq7000 PS端串口中断开发及调试问题记录:https://blog.csdn.net/weixin_46168087/article/details/155485280?fromshare=blogdetail&sharetype=blogdetail&sharerId=155485280&sharerefer=PC&sharesource=weixin_46168087&sharefrom=from_link

二、开发样例解析

         初学者拿到一块新板卡后,除了了解硬件原理图和芯片手册,到了开发阶段,vitis软件本身提供了可参考的开发样例。

        打开参考样例,需要注意的是define下的全部内容,每个定义都有对应的英文注释,所有的内容都与芯片手册一一对应。

        下图所示为qspi的设备ID,在xparamerters.h头文件中定义,这个文件的生成方式是 Vivado 里通过 BD(Block Design)或者 IP Integrator 配置后,导出硬件(Export Hardware)时自动生成的。

        下图所示是flash的操作指令,每一项都可以在芯片手册目录中找到对应的章节。

        下图所示定义了 FlashBuffer 数据类型中每种数据的偏移量即flash操作指令、地址、数据等在读写buffer中的位置。请注意,读取数据的偏移量与写入数据的偏移量不同,因为 QSPI 驱动程序设计为允许全双工传输,因此接收到的字节数等于发送和接收的字节数。

        下图所示定义了flash的各种操作所需的字节长度,这些字节不是数据,而是控制信息,包括命令和地址。

        下图所示定义了 flash的页面大小、扇区大小以及页面和扇区的数量。页面大小指定了单次传输可以写入 flash 的最大字节数。这些数据需要根据自己使用的flash芯片手册定义。

        IS25LP256D的技术手册如图:

        因此最终的程序定义如下:

        下图所示定义测试数据的起始地址、起始值、以及占用内存的大小,用户可以根据自己的需求更改测试内存块和测试值。

        本实验测试数据部分,最终的程序定义如下:

        根据芯片手册以及代码样例,可以总结flash芯片读写功能验证的工作流程基本分为以下几个阶段:

        Step1:初始化QSPI :包括初始化QSPI、读取芯片ID,启用四线模式。

        Step2:擦flash:清空测试区

        Step3:写flash:向测试区写入测试数据

        Step4:读flash:从测试区读出测试数据

        Step5:将写入和读出的数据进行比较。

三、flash芯片读写功能验证的工作流程

1、初始化QSPI 

        通过flash芯片的唯一ID配置qspi初始化,设置qspi的工作模式、复位引脚、预分频系数等参数,清空读写操作的buffer,使能qspi片选信号,读取芯片ID,启用四线模式。

        读取ID函数内容如下:      

  

        启用四线模式函数内容如下:

2、擦flash

        清除flash操作分为全部擦除和局部擦除,通过设置擦除的起始地址和擦除的内存大小(字节数)对应不同的操作。

3、写flash

       通过设置写入数据的起始地址、写入数据的内存大小(字节数)、写数据指令对flash进行写数据操作。需要写入的数据提前存储在写buffer中。写flash函数没有返回值。

4、读flash

        通过设置读出数据的起始地址、读出数据的内存大小(字节数)、读数据指令对flash进行读数据操作。读操作函数的返回值即为读出的数据,存储在读buffer中。

5、读写数据比较

        主函数中通过串口打印写buffer的数据、读buffer的数据,将数据进行比较,比较结果打印到串口调试助手显示区。

四、调试中的问题记录

1、如何判断读取ID正确

        从开发样例可以看到读ID的指令号为0x9F,结合flash芯片数据手册可知,芯片ID三字节内容应为9D 60 19。

2、读取ID异常问题

        第一次读取ID串口调试助手全部打印出00 00 00 ,且写入的数据全部读出来00,这里串口调试助手只是打印,所以可以判断数据根本没有写入成功,通信不通。

        换了一块开发板先跑程序,第二块板ID正常,数据异常,见问题3。问题3解决后开始固化进入问题4,在问题4解决后同样按照更改方案将R369、R373换成两个阻值较小的电阻、更改了读ID异常的第一块板卡,所有功能都正常了,由此推断,芯片读取ID异常还是跟WP#IO2、HOLD#RESET#IO3这两个复用引脚有关。

3、读取ID正常数据异常问题

        下载程序后发现读取ID正常,但数据异常全为136,如下图所示:

       由于最开始的工作思路是先看硬件及芯片手册,网上搜索开发例程,用别人的代码,编译完成不报错直接跑,出现异常以后,进入断点一步步调试发现,在初始化读取ID后,配置四线模式的函数中,ID判断正确后的下一步是进行四线模式的配置,这个ID判断的首字节应该是别人使用的flash型号的首字节,由此判断数据写入不成功的原因就是四线模式根本没有配置成功,将0xEF改成0x9D即可。后面打开vitis官方自带的例程发现,例程里的ID判断直接就是0x9D,不需要更改。

        更改成自己使用的芯片对应ID后,测试正确。

4、程序固化异常问题

        flash读写功能验证完成后,烧录程序失败,vitis下方代码运行提示框提示设备处于qspi模式所以无法固化,但拨码开关跳线帽明明按照JTAG接法来接的。

        硬件原理图如图所示:

        无论在哪种启动模式下,均可在线调试,但固化程序必须在JTAG模式下固化,再断电将拨码开关拨到QSPI模式即可。

模式

MIO4

MIO5

JTAG

0

0

QSPI

0

1

SD

1

1

NAND

1

0

        换到其他模式接法继续测试固化能否成功。同时用万用表测量MIO4、MIO5工作电平。

        接SD模式:TP102=0.5V~0.1V跳,TP103=3.3V

        接QSPI模式:测量TP102=0,TP103=3.3V

        接JTAG模式:测量TP102=0,TP103=0.6V~0.1V跳

        拨码开关拨到NAND模式:测量TP102=0.6V~0.1V跳,TP103=0.6V~0.1V跳     

         由此推断之前的测试均在QSPI模式下完成,板卡从未在JTAG模式下工作过。

         为测试jtag功能,将R369、R373拆掉,焊上焊锡强行拉低到0V,再测试,能认到JTAG模式,可以完成固化程序流程。但是验证flash读写异常,如下图:

        尝试将代码中的四通道读指令屏蔽掉,换成其他三种方式测试,都正常,可以判断将R369、R373拆掉,焊上焊锡强行拉低到0V,影响了芯片的WP#IO2、HOLD#RESET#IO3复用引脚。

        最终的解决方案是R369、R373换成两个阻值较小的电阻、测试读写正常,也能认到JTAG模式,可以固化程序。

五、vitis的程序固化流程

        程序的固化流程基本分为boot.bin文件的生成和boot.bin文件的烧录。

        注:固化程序前,程序的改动上,需要将QSPI的测试函数屏蔽掉。

1、生成boot.bin文件

        方式一:如下图所示,在新建vitis工程时,勾选上Boot Components,后续开发好的程序编译完成后,会在应用工程的debug-sdcard路径下自动生成boot.bin文件。

        方式二:如下图所示,点击vitis软件左上方导航栏Xilinx-点击Create Boot Image-点击Zynq and Zynq Ultrascale。

        点击选择生成BOOT.bin文件的路径,推荐在ps工程文件夹下建立一个名称为boot的文件夹,方便后续快速查找。

        路径建立好以后,依次添加启动文件fsbl、PL端bit文件、PS端elf文件。注意:添加的顺序不能改变,启动文件fsbl路径在平台工程export文件下,PS端elf文件在应用工程debug路径下。详见截图。

        添加完三个文件,点击Create Image,生成boot.bin文件成功,打开之前建立的存放boot.bin文件的文件夹boot,可以看见生成的boot.bin文件和output.bif文件。

2、boot.bin文件的烧录

        点击vitis软件左上方导航栏Xilinx-点击Program Flash。

        操作一选择boot.bin路径。

        若boot.bin的生成由方式一完成,则路径在下图所示:

       若boot.bin的生成由方式二完成,则路径在下图所示,可以看到两种方式生成的文件大小是一样的。

        操作二选择flash芯片类型。参考硬件设计原理图,使用的时qspi四线单通道。这与BD中的PS_IP设置也是一致的。

        操作三选择启动文件fsbl路径。与方式二生成boot.bin的启动文件路径一致。

        三步操作完成后,板卡上电,拨码开关必须拨到JTAG模式,点击Program。

        烧录中:

        烧录成功:

        将拨码开关切换到qspi模式,即可正常工作。

六、工程源码

1、qspi.h

#ifndef __QSPI_H_
#define __QSPI_H_


#include "xqspips.h"
#include <stdio.h>
#include "xil_printf.h"
//QSPI器件ID
#define QSPI_DEVICE_ID		XPAR_XQSPIPS_0_DEVICE_ID
//flash操作指令
#define WRITE_STATUS_CMD	0x01	//写状态指令
#define WRITE_CMD			0x02	//写指令-页编程命令,数据字节最多128个
#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	//擦除整片flash-全部写1  只发指令
#define	SEC_ERASE_CMD		0xD8	//擦除一个64K扇区-全部写1  指令+3Byte地址
#define READ_ID				0x9F	//读取flash ID指令

//定义flash操作指令、地址、数据等在读写buffer中的位置
#define COMMAND_OFFSET		0 //flash操作指令在写buffer中的位置(第0个字节)
#define ADDRESS_1_OFFSET	1 //操作flash数据起始地址的高字节在写buffer中的位置(第1个字节)
#define ADDRESS_2_OFFSET	2 //操作flash数据起始地址的中字节在写buffer中的位置(第2个字节)
#define ADDRESS_3_OFFSET	3 //操作flash数据起始地址的低字节在写buffer中的位置(第3个字节)
#define DATA_OFFSET			4 //操作flash的数据,读取或写入的数据,在写buffer中的位置(第4个字节开始是纯数据区)
#define DUMMY_OFFSET		4 //空闲字节的位置,当使用快速、双线、四线模式读取数据时,空闲字节占读buffer的第4个字节,纯数据区从第5个字节开始

//定义各种操作所需字节长度
#define DUMMY_SIZE			1 //空闲字节大小占1个字节(当使用快速、双线、四线模式读取数据时存在dummy byte)
#define RD_ID_SIZE			4 //读取flash_ID占字节数,其中第0个字节为读取ID指令号,后3个字节为读取到的flash ID号
#define BULK_ERASE_SIZE		1 //清空整片flash指令占字节数,只需要一个清空整片flash的指令号
#define SEC_ERASE_SIZE		4 //按扇区清空flash指令占字节数,其中第0字节为按扇区清空flash的指令号,后3个字节是起始清空的flash地址
#define OVERHEAD_SIZE		4 //定义读写buffer头部数据长度,包括指令号1字节和操作地址3字节

//根据使用的flash芯片手册定义
#define SECTOR_SIZE 		0x10000	  //定义单个操作块大小64KB 可选4K扇区/32K块/64K块
#define NUM_SECTORS 		0x200	  //定义操作块数量512个 64K*512=32M
#define NUM_PAGES   		0x2000	  //定义页数量8192个  4K*8192=32M
#define PAGE_SIZE   		256		  //定义每页字节数256个字节 =4K/16bit

//定义实际读写操作的范围和数据大小
#define PAGE_COUNT 			16         //定义需要操作读写的页数
#define TEST_ADDRESS 		0x01FF0000 //定义读写操作的起始地址 最后一个64k块
#define MAX_DATA 			(PAGE_COUNT * PAGE_SIZE)	//定义读写操作的最大数据量 256字节*16页


#ifdef __cplusplus
 extern "C" {
#endif

 //初始化QSPI控制器
 int Qspi_Init(u8 QspiDeviceId,XQspiPs *QspiInstancePtr);
 //通过QSPI将数据写入flash中
 void FlashWrite(XQspiPs *QspiPtr, u32 Address, u8 *WriteBuf, u32 ByteCount, u8 Command);
 //通过QSPI读取flash中的数据
 void FlashRead(XQspiPs *QspiPtr, u32 Address, u8 *ReadBuf, u32 ByteCount, u8 Command);
 //擦除flash
 void FlashErase(XQspiPs *QspiPtr, u32 Address, u32 ByteCount);
 //读取flash ID
 int FlashReadID(void);
 //使能四线模式
 void FlashQuadEnable(XQspiPs *QspiPtr);
 //jtag模式下解除写保护
 void remove_wp(XQspiPs *QspiPtr);

#ifdef __cplusplus
}
#endif
void QSPI_Flash_Opt();

#endif

2、qspi.c

#include   "qspi.h"

//定义QSPI操作结构体对象
static XQspiPs QspiInstance;
u8 ReadBuffer[MAX_DATA + DATA_OFFSET + DUMMY_SIZE];
u8 WriteBuffer[PAGE_SIZE + DATA_OFFSET];
/* 功能:初始化QSPI控制器
 * 入参1:QSPI设备ID
 * 入参2:QSPI控制器实例化对象指针
 */
int Qspi_Init(u8 QspiDeviceId,XQspiPs *QspiInstancePtr){

	int Status;
	XQspiPs_Config *QspiConfig;

	//初始化QSPI控制器
	QspiConfig = XQspiPs_LookupConfig(QspiDeviceId);
	if (QspiConfig == NULL) {
		return XST_FAILURE;
	}

	Status = XQspiPs_CfgInitialize(QspiInstancePtr, QspiConfig,
					QspiConfig->BaseAddress);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	//QSPI控制器自检,保证初始化成功
	Status = XQspiPs_SelfTest(QspiInstancePtr);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}


	//将flash配置为手动启动、手动片选模式,将hold(reset)引脚配置为高电平,hold低电平暂停收发数据,高电平恢复收发数据
	XQspiPs_SetOptions(QspiInstancePtr, XQSPIPS_MANUAL_START_OPTION |
			XQSPIPS_FORCE_SSELECT_OPTION |
			XQSPIPS_HOLD_B_DRIVE_OPTION);

	//设置QSPI预分频系数
	XQspiPs_SetClkPrescaler(QspiInstancePtr, XQSPIPS_CLK_PRESCALE_8);

	//清空读写操作的buffer
	memset(WriteBuffer, 0x00, sizeof(WriteBuffer));
	memset(ReadBuffer, 0x00, sizeof(ReadBuffer));

	//将片选信号置为有效
	XQspiPs_SetSlaveSelect(QspiInstancePtr);

	//读取flash ID
	FlashReadID();

	//使能QSPI Quad模式
	FlashQuadEnable(QspiInstancePtr);

	return Status;
}


/**
* @brief 通过QSPI将数据写入flash中
* @param QSPI结构体指针
* @param 要写入数据的起始地址
* @param 要写入数据的数量-按字节
* @param 写数据指令
* @return 无
* @note 无
* */
void FlashWrite(XQspiPs *QspiPtr, u32 Address, u8 *WriteBuf, u32 ByteCount, u8 Command)
{
	u8 WriteEnableCmd = { WRITE_ENABLE_CMD };
	u8 ReadStatusCmd[] = { READ_STATUS_CMD, 0 };  /* must send 2 bytes */
	u8 FlashStatus[2];

	//发送写使能指令
	XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
				sizeof(WriteEnableCmd));

	//将写操作指令以及数据地址写入对应待发送buffer的前4个字节,第五个字节开始才是写入的数据
	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);

	//将要写入的纯数据,写入到flash写操作buffer中(flash写操作buffer包括写指令、地址、数据等内容)
	memcpy(WriteBuffer + 4, WriteBuf, ByteCount);

	//将写指令、写起始地址信息、写入数据内容,写到flash中
	XQspiPs_PolledTransfer(QspiPtr, WriteBuffer, NULL,
				ByteCount + OVERHEAD_SIZE);
	//等待数据写入完毕
	while (1) {

		//通过发送读状态指令,获取是否已经将数据写完,如果为可读状态,则数据写入完毕
		XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd, FlashStatus,
					sizeof(ReadStatusCmd));

		//如果读取的状态值是0xff,则证明数据还未写完
		FlashStatus[1] |= FlashStatus[0];
		if ((FlashStatus[1] & 0x01) == 0) {
			break;
		}
	}
}

/**
* @brief 通过QSPI读取flash中的数据
* @param QSPI结构体指针
* @param 读取数据的起始地址
* @param 读取数据的数量-按字节
* @param 读取数据指令-普通读取、快速、双线、四线读取等
* @return 无
* @note 无
* */
void FlashRead(XQspiPs *QspiPtr, u32 Address, u8 *ReadBuf, u32 ByteCount, u8 Command)
{
	//将读指令和读数据首地址写入到要发送的buffer中,它们占4个字节
	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);

	//如果是快速读取、双线读取和四线读取,则需要增加一个空闲字节的长度DUMMY_SIZE
	if ((Command == FAST_READ_CMD) || (Command == DUAL_READ_CMD) ||
	    (Command == QUAD_READ_CMD)) {
		ByteCount += DUMMY_SIZE;
	}

	//将读指令和读地址发送到通过QSPI发送到flash,等待数据读取至ReadBuffer中
	XQspiPs_PolledTransfer(QspiPtr, WriteBuffer, ReadBuffer,
				ByteCount + OVERHEAD_SIZE);


	//如果是快速读取、双线读取和四线读取,则需要增加一个虚拟字节的长度DUMMY_SIZE
	if ((Command == FAST_READ_CMD) || (Command == DUAL_READ_CMD) ||
	    (Command == QUAD_READ_CMD)) {
		//非普通模式读取,有空闲字节,从第5个字节开始是纯数据
		memcpy(ReadBuf, ReadBuffer + 5, ByteCount - 1);
	}else{
		//普通模式读取,无空闲字节,从第4个字节开始是纯数据
		memcpy(ReadBuf, ReadBuffer + 4, ByteCount);
	}

}

/**
* @brief 擦除flash
* @param QSPI结构体指针
* @param 擦除的起始地址
* @param 擦除数据的数量-按字节
* @return 无
* @note 无
* */
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;

	//如果是擦除整片flash,则使用整片擦除指令chip erase
	if (ByteCount == (NUM_SECTORS * SECTOR_SIZE)) {
		//发送写使能指令
		XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
				  sizeof(WriteEnableCmd));

		//将整片擦除指令写入到发送buffer的首个字节的位置
		WriteBuffer[COMMAND_OFFSET]   = BULK_ERASE_CMD;
		//将整片擦除指令发送到flash
		XQspiPs_PolledTransfer(QspiPtr, WriteBuffer, NULL,
					BULK_ERASE_SIZE);

		//等待擦除完成
		while (1) {
			//通过发送读状态指令,获取是否已经将数据写完,如果为可读状态,则数据写入完毕
			XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd,
						FlashStatus,
						sizeof(ReadStatusCmd));

			//如果读取的状态值是0xff,则证明数据还未写完
			FlashStatus[1] |= FlashStatus[0];
			if ((FlashStatus[1] & 0x01) == 0) {
				break;
			}
		}

		return;
	}

	//如果是部分擦除,则使用扇区sector擦除的指令进行擦除操作
	for (Sector = 0; Sector < ((ByteCount / SECTOR_SIZE) + 1); Sector++) {
		//发送写使能指令
		XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
					sizeof(WriteEnableCmd));

		//将扇区擦除指令,以及开始擦除首地址写入到发送buffer的前四个字节
		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);

		//将扇区擦除指令,以及开始擦除首地址发送到flash
		XQspiPs_PolledTransfer(QspiPtr, WriteBuffer, NULL,
					SEC_ERASE_SIZE);

		//等待擦除完成
		while (1) {
			//通过发送读状态指令,获取是否已经将数据写完,如果为可读状态,则数据写入完毕
			XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd,
						FlashStatus,
						sizeof(ReadStatusCmd));

			//如果读取的状态值是0xff,则证明数据还未写完
			FlashStatus[1] |= FlashStatus[0];
			if ((FlashStatus[1] & 0x01) == 0) {
				break;
			}
		}

		Address += SECTOR_SIZE;
	}
}

/**
* @brief 读取flash ID
* @param 无
* @return 无
* @note 无
* */
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;
}
/**
* @brief 使能四线模式
* @param QSPI结构体指针
* @return 无
* @note 无
* */
void FlashQuadEnable(XQspiPs *QspiPtr)
{
	u8 WriteEnableCmd = {WRITE_ENABLE_CMD};
	u8 ReadStatusCmd[] = {READ_STATUS_CMD, 0};
	u8 QuadEnableCmd[] = {WRITE_STATUS_CMD, 0};
	u8 FlashStatus[2];

	//判断读取的flash ID是否正确,不加这个判断也行
	if (ReadBuffer[1] == 0x9D) {
		//获取读状态
		XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd,
					FlashStatus,
					sizeof(ReadStatusCmd));

		QuadEnableCmd[1] = FlashStatus[1] | 1 << 6;
		//发送写使能指令
		XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,
				  sizeof(WriteEnableCmd));
		//发送Quad配置指令
		XQspiPs_PolledTransfer(QspiPtr, QuadEnableCmd, NULL,
					sizeof(QuadEnableCmd));
		while (1) {
			//获取读状态,等待指令写入完毕
			XQspiPs_PolledTransfer(QspiPtr, ReadStatusCmd, FlashStatus,
					sizeof(ReadStatusCmd));
			/*
			 * 第6it置1,第0bit置0,则Quad模式设置成功、且设备状态准备就绪
			 */
			if ((FlashStatus[0] == 0x40) && (FlashStatus[1] == 0x40)) {
				break;
			}
		}
	}
}

//qspi读写测试函数
//读写buffer数据长度
#define test_buf_len 255
//QSPI读写flash测试
void QSPI_Flash_Opt(){

	u8 nRet = XST_SUCCESS;
	u8 write_buf[test_buf_len] = {0};
	u8 read_buf[test_buf_len] = {0};

	//初始化QSPI控制器
	Qspi_Init(QSPI_DEVICE_ID,&QspiInstance);

	//write buffer填写数据
	for(int i = 0; i < test_buf_len; i++){
		write_buf[i] = i + 1;
	}

	//清除要写入的flash区域
	FlashErase(&QspiInstance, TEST_ADDRESS, test_buf_len);
	//将write buffer数据写入flash中
	FlashWrite(&QspiInstance, TEST_ADDRESS, write_buf, test_buf_len, WRITE_CMD);
	//将写入flash中的数据再进行读取
//	FlashRead(&QspiInstance, TEST_ADDRESS, read_buf, test_buf_len, READ_CMD); //常规读写指令
//	FlashRead(&QspiInstance, TEST_ADDRESS, read_buf, test_buf_len, FAST_READ_CMD);//单通道读写指令
//	FlashRead(&QspiInstance, TEST_ADDRESS, read_buf, test_buf_len, DUAL_READ_CMD);//双通道读写指令
	FlashRead(&QspiInstance, TEST_ADDRESS, read_buf, test_buf_len, QUAD_READ_CMD);//四通道读写指令

	//打印写buffer区数据
	for(int i = 0; i < test_buf_len; i++){
		printf("%d ", write_buf[i]);
		if(i == test_buf_len - 1)
			printf("\n");
	}

	//打印读buffer区数据
	for(int i = 0; i < test_buf_len; i++){
		printf("%d ", read_buf[i]);
		if(i == test_buf_len - 1)
			printf("\n");
	}

	//对比写入和读出的数据是否一致
	for(int i = 0; i < test_buf_len; i++){
		if(read_buf[i] != write_buf[i]){
			nRet = XST_FAILURE;
		}
	}

	if(nRet == XST_SUCCESS){
		printf("QSPI Operate flash successful!\n");
	}else{
		printf("QSPI Operate flash failed!\n");
	}
}

3、main

#include "platform.h"
#include "qspi.h"

int main()
{
    init_platform();
    //FLASH 芯片测试代码
    QSPI_Flash_Opt();
    cleanup_platform();
    return 0;
}

4、实验结果

        硬件连接好串口调试设备,打开vitis自带的串口调试助手,运行程序,可以看到串口调试界面依次打印flash芯片ID,写入数据缓存器write_buf的值,读出数据缓存器read_buf的值,并将两组数据进行比较,确认读写数据一致后打印QSPI Operate flash successful!。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐