当前位置: 首页 > web >正文

ZYNQ芯片,SPI驱动开发自学全解析个人笔记【FPGA】【赛灵思

参考文献:

ZYNQ-Vitis(SDK)裸机开发之(八)PS端QSPI读写flash操作(包括SPI、Dual SPI、Qual SPI的配置使用)_sdk vivado 测试flash selftest-CSDN博客文章浏览阅读5.4k次,点赞50次,收藏81次。本文围绕ZYNQ QSPI Flash开发展开,介绍了Flash和SPI知识,包括Flash存储特性、区域划分,SPI引脚、协议等。详细说明了Vivado工程搭建,以及Vitis程序编写,涵盖操作格式、头文件和源文件内容,还编写了读写测试函数,最后给出实测结果。 https://blog.csdn.net/qq_38584212/article/details/137965232?fromshare=blogdetail&sharetype=blogdetail&sharerId=137965232&sharerefer=PC&sharesource=dsafefvf&sharefrom=from_link


ZYNQ-Linux开发之(七)国产化复旦微电子FMQL平台uboot和kernel无法识别国产SPI Flash 芯片以及分区的问题_复旦微fmql 社区-CSDN博客文章浏览阅读5.1k次,点赞8次,收藏61次。本文详细指导如何确保UBOOT和KERNEL识别新型Flash芯片SM25QH256MX,包括在UBOOT中添加驱动支持,修改源码添加芯片ID,以及在KERNEL中配置和设备树中修改相应节点。 https://blog.csdn.net/qq_38584212/article/details/132041125?fromshare=blogdetail&sharetype=blogdetail&sharerId=132041125&sharerefer=PC&sharesource=dsafefvf&sharefrom=from_link
ZYNQ中的SPI控制器使用_zynq spi-CSDN博客文章浏览阅读5.1k次,点赞43次,收藏72次。本文详细介绍了SPI控制器的工作原理,特别是Motorola提出的SPI接口,以及在XilinxZYNQ平台上的SPI0和SPI1控制器的使用方法,包括Vivado中的配置步骤、管脚映射和Vitis中的编程示例,涵盖了SPI的配置选项和轮询模式操作。 https://blog.csdn.net/2301_79521562/article/details/137013718?fromshare=blogdetail&sharetype=blogdetail&sharerId=137013718&sharerefer=PC&sharesource=dsafefvf&sharefrom=from_link

一、SPI控制器简介
SPI 接口是 Motorola 首先提出的全双工三线同步串行外围接口, 采用主从模式(MasterSlave) 架构; 支持多 slave模式应用, 一般仅支持单 Master。 时钟由 Master 控制, 在时钟移位脉冲下, 数据按位传输, 高位在前, 低位在后(MSBfirst) ; SPI 接口有 2 根单向数据线, 为全双工通信。


1.SPI 接口
共有 4 根信号线, 分别是: 设备选择线、 时钟线、 串行输出数据线、 串行输入数据线。

(1) MOSI: 主器件数据输出, 从器件数据输入

(2) MISO: 主器件数据输入, 从器件数据输出

(3) SCLK: 时钟信号, 由主器件产生

(4) /SS: 从器件使能信号, 由主器件控制

2.时钟极性和时钟相位
时钟的极性(CPOL)。决定空闲时时钟时低电平还是高电平。当时钟极性为 0 时(CPOL=0) , SCK 信号线在空闲时为低电平; 当时钟极性为 1 时(CPOL=1) , SCK 信号线在空闲时为高电平;
时钟的相位(CPHA)。当时钟相位为 1 时(CPHA=1) , 在 SCK 信号线的第二个跳变沿进行采样。为0的时候是在第一个跳变沿采样

3. ZYNQ的SPI控制器


        ZYNQ中有两个SPI控制器(SPI0和SPI1),每个控制器有三个片选信号,也可3-8译码获得八个片选信号。既可以做从机又可以做主机。他的引脚:SS/SCLK/MOSI和MISO,可以映射到MIO引脚上,也可以映射到EMIO引脚上(就可以根据需要调试)

二、在ZYNQ中使用SPI控制器的方法
1.在vivado中配置
        在vivado中程配置IP核时,勾选SPI0。同时可以选择映射到MIO还是EMIO上。这里选择EMIO,就可以把SPI控制器的线引到特定的管脚,方便调试。

        设置完IP核之后要连线,检查是否有错误,然后保存——生成OUTPUT文件——生成HDL文件。映射到EMIO的时候,要进行管脚约束,确定映射到哪一个EMIO上。设置管脚约束的方法可以在正点原子的嵌入式开发指南中的EMIO教程视频当中看到。

设置完以后要生成比特流文件。然后导出XSA文件。注意要记录下来SPI控制器的哪个脚,引射到了哪个EMIO上面。当使用了更多EMIO的时候,也要记录下来EMIO[0]对应了哪个引脚,EMIO[1]对应了哪个引脚等等……方便后面编程的时候能正确对应起来

2.在vitis中编程
将刚才生成的xsa文件导入,在vitis中编写程序

(1)添加spi控制器的相关代码
介绍轮询方式:

首先添加文件命名为spi_ps.c文件

#include "spi_ps.h"XSpiPs SpiInstance;/* 发送和接收的缓冲区 */
u8 *ReadBuf = (void*) 0x08000000 ;
u8 *SendBuf = (void*) 0x08100000 ;/* -----------------spi初始化函数--------------------------- */int SpiPs_Init(XSpiPs *SpiInstancePtr,u16 SpiDeviceId )
{int Status;XSpiPs_Config *SpiConfig;//根据器件ID查找配置信息SpiConfig = XSpiPs_LookupConfig(SpiDeviceId);if (NULL == SpiConfig) {return XST_FAILURE;}/* 初始化函数 */Status = XSpiPs_CfgInitialize((SpiInstancePtr), SpiConfig,SpiConfig->BaseAddress);if (Status != XST_SUCCESS) {return XST_FAILURE;}/* 配置SPI的工作模式* 第一个参数是XSpiPs 实例* 第二个参数设置为主机模式, */Status = XSpiPs_SetOptions(SpiInstancePtr, XSPIPS_MASTER_OPTION | XSPIPS_FORCE_SSELECT_OPTION );/* 配置SPI时钟的分频参数 */Status = XSpiPs_SetClkPrescaler(SpiInstancePtr, XSPIPS_CLK_PRESCALE_256);/* 配置被选择的从设备 */XSpiPs_SetSlaveSelect(SpiInstancePtr, 0x00);return XST_SUCCESS;
}/* -------------------SPI的发送函数------------- */
void SpiPs_Send(XSpiPs *SpiInstancePtr,u8 *SendBuffer, int ByteCount)
{/* 原函数形式* s32 XSpiPs_PolledTransfer(XSpiPs *InstancePtr, u8 *SendBufPtr,u8 *RecvBufPtr, u32 ByteCount)* 其中 第二个参数 SendBufPtr 是发送的数据的指针,指向要发送数据的地址了* 第三个参数 RecvBufPtr 是接收的数据,* ByteCount 是之要发送的字节数* 这个属于轮询模式    */XSpiPs_PolledTransfer(SpiInstancePtr, SendBuffer, ReadBuf,ByteCount);}/* -------------------SPI的读取函数------------- */
void SpiPs_Read(XSpiPs *SpiInstancePtr,u8 *ReadBuffer,int ByteCount)
{XSpiPs_PolledTransfer(SpiInstancePtr, SendBuf, ReadBuffer,ByteCount);
}

其中,这一句配置SPI的工作模式,包括主机模式、手动片选还是自动片选、手动开始传输还是自动开始传输、时钟相位和时钟极性。要配置哪一个,就把对应的写到第二个参数的位置上。

Status = XSpiPs_SetOptions(SpiInstancePtr, XSPIPS_MASTER_OPTION | 
XSPIPS_FORCE_SSELECT_OPTION );

 可以跳转进这个函数,能看到这个配置表,需要加哪个就把哪个复制进去。

typedef struct {u32 Option;u32 Mask;
} OptionsMap;static OptionsMap OptionsTable[] = {{XSPIPS_MASTER_OPTION, XSPIPS_CR_MSTREN_MASK},{XSPIPS_CLK_ACTIVE_LOW_OPTION, XSPIPS_CR_CPOL_MASK},{XSPIPS_CLK_PHASE_1_OPTION, XSPIPS_CR_CPHA_MASK},{XSPIPS_DECODE_SSELECT_OPTION, XSPIPS_CR_SSDECEN_MASK},{XSPIPS_FORCE_SSELECT_OPTION, XSPIPS_CR_SSFORCE_MASK},{XSPIPS_MANUAL_START_OPTION, XSPIPS_CR_MANSTRTEN_MASK}
};

比如,要想设置时钟极性为1,就把这个函数改写成:

Status = XSpiPs_SetOptions(SpiInstancePtr, XSPIPS_MASTER_OPTION | 
XSPIPS_FORCE_SSELECT_OPTION | XSPIPS_CLK_ACTIVE_LOW_OPTION);

第二个分频参数:SPI的时钟是166.66MHz,分频后的时钟就是SPI控制器做主机时的时钟频率。比如最大256分频,这时候时钟频率就是大概是651KHz左右。

然后设置片选的从设备。这个功能我没有使用,但是应该是用哪个片选就写哪个,从0到2编号

然后后面的是轮询模式下发送和接收的函数。可以直接调用。

然后再添加spi_ps.h的文件:

 
#ifndef SRC_SPIPS_H_
#define SRC_SPIPS_H_#include "xparameters.h"    /* EDK generated parameters */
#include "xspips.h"        /* SPI device driver */
#include "xscugic.h"        /* Interrupt controller device driver */
#include "xil_exception.h"
#include "xil_printf.h"void SpiPs_Read(XSpiPs *SpiInstancePtr,u8 *ReadBuffer,int ByteCount);void SpiPs_Send(XSpiPs *SpiInstancePtr,u8 *SendBuffer, int ByteCount);int SpiPs_Init(XSpiPs *SpiInstancePtr,u16 SpiDeviceId );#define SPI_DEVICE_ID        XPAR_XSPIPS_0_DEVICE_ID
#define MAX_DATA        100#endif /* SRC_SPIPS_H_ */

具体要用SPI发送数据的时候,第一步先把要发的数据写到sendbuf缓冲区里面,然后调用send函数即可。比如这样:

SendBuf[0] = RegAddr;
for(t=0;t<Len;t++){SendBuf[t+1] = Data[t];}
SpiPs_Send(&SpiInstance,SendBuf,Len+1)

1. ZYNQ 7010平台介绍:
Xilinx的ZYNQ 7000系列处理器是集成了ARM处理器的FPGA(现场可编程门阵列)解决方案。ZYNQ 7010是这个系列中的一个型号,具有一个双核心的ARM Cortex-A9处理器和一定的FPGA逻辑资源。它为开发者提供了一个将处理器软件可编程性和FPGA硬件可编程性结合的独特机会。

2. SPI(Serial Peripheral Interface)通信协议:
SPI是一种常用的串行通信协议,用于微处理器和各种外围设备之间的数据交换。它采用主从模式,通常包括一个主设备和一个或多个从设备。SPI通信包含四根线:SCLK(时钟线)、MOSI(主设备数据输出,从设备数据输入)、MISO(主设备数据输入,从设备数据输出)和CS(片选信号)。它以其高速率和全双工通信能力而广泛应用于传感器、SD卡等设备。

3. 驱动程序的作用:
驱动程序是软件组件,它允许操作系统和计算机硬件之间的通信。在本例中,驱动程序是实现ZYNQ 7010与SPI设备通信的软件部分。驱动程序将负责初始化SPI通信,以及向SPI设备发送和接收数据。

4. SDK(Software Development Kit)驱动库:
SDK驱动库是指为了简化开发工作,将一些通用的操作封装成函数或者类库,供开发者在编写应用程序时调用。这些库通常包括了初始化硬件、配置硬件、数据传输等常用功能。使用SDK驱动库可以大大减少开发时间和复杂性。

5. 代码直接编译运行:
描述中提到的“项目代码可直接编译运行”,意味着提供的ZIP压缩文件中包含了所有必要的源代码、库文件和配置文件,且无需复杂的设置或修改即可在支持ZYNQ 7010的开发环境中编译成可执行文件。这说明了该驱动程序具有良好的兼容性和即插即用的特性。

详细知识点:

- ZYNQ 7010的编程和配置:
开发者在使用ZYNQ 7010实现SPI驱动时,需要了解如何编程和配置FPGA部分以及ARM处理器部分。这涉及到使用Xilinx提供的开发工具,如Vivado来配置FPGA,并使用SDK进行软件开发。

- SPI协议的实现细节:
在编写SPI驱动时,需要根据SPI协议规范来设计时序和通信逻辑。例如,需要编写代码来控制SCLK的频率,精确地在时钟边沿上切换MOSI和MISO的信号状态,以及确保数据的正确同步和时序控制。

- 驱动程序的架构和设计:
SPI驱动程序通常包含初始化代码、数据读写函数、中断处理等模块。驱动程序的架构应该清晰、高效,能够处理SPI通信中可能出现的各种情况,如超时、错误检测等。

- SDK的使用:
对于ZYNQ 7010这样的处理器,Xilinx提供了一套完整的软件开发工具包SDK,包括编译器、链接器、调试器等工具。开发者需要熟悉SDK的使用,才能有效地开发和调试基于ZYNQ 7010的SPI驱动程序。

- 跨平台的代码移植和兼容性:
由于ZYNQ 7010可能应用于不同的系统和设备中,因此在编写SPI驱动程序时,应考虑到代码的可移植性和兼容性。这意味着代码应尽可能地与操作系统无关,以及能够在不同硬件配置下正常工作。

- 性能优化:
SPI通信的性能优化是提高系统整体性能的关键。开发者需要在保证数据传输稳定性的前提下,优化时序、减少通信延迟、提高数据吞吐率等。

- 测试和验证:
驱动程序在开发完成后需要经过严格的测试和验证。这包括单元测试、集成测试和系统测试,确保在各种正常和异常情况下驱动程序都能正确工作。

- 文档和支持:
提供完善的开发文档和支持也是驱动开发过程中不可或缺的一部分。文档应该详细记录驱动程序的功能、API接口说明、使用示例以及常见问题解答。

通过以上知识点的详细阐述,我们可以看到开发ZYNQ 7010的SPI驱动程序是一个复杂且需要多方面考虑的工作。涉及到硬件和软件的结合、软件工程的最佳实践以及对特定硬件平台的深入理解。

项目介绍
在嵌入式系统设计中,SPI(Serial Peripheral Interface)接口是一种广泛使用的通信协议,尤其在需要高速数据传输的场景中表现出色。Zynq SoC(System on Chip)和Zynq UltraScale+ MPSoC(Multi-Processor System on Chip)作为Xilinx公司的高性能嵌入式处理器,提供了灵活且强大的SPI接口实现方案。

本项目提供了一份详细的指南——pg153-axi-quad-spi.pdf,帮助开发者在使用Zynq SoC或Zynq UltraScale+ MPSoC时,选择并实现合适的SPI接口。指南中详细介绍了两种主要的SPI实现方法:使用PS(Processing System)端的SPI控制器和在PL(Programmable Logic)端使用AXI Quad SPI(QSPI)IP模块。

项目技术分析
PS端SPI控制器
位置:位于PS端,即处理系统部分。
控制器数量:提供两个SPI控制器。
配置复杂度:较低,适合快速原型设计和简单应用。
性能:中等,适用于大多数常规应用。
资源占用:较低,对系统资源的影响较小。
灵活性:中等,适合标准SPI通信需求。
PL端AXI Quad SPI
位置:位于PL端,即可编程逻辑部分。
控制器数量:提供一个AXI Quad SPI IP模块。
配置复杂度:较高,需要更多的配置和调试工作。
性能:较高,适用于需要高性能数据传输的场景。
资源占用:较高,对系统资源的需求较大。
灵活性:较高,适合需要高度定制化的应用。
项目及技术应用场景
应用场景
工业自动化:在工业控制系统中,SPI接口常用于传感器和执行器的通信,Zynq SoC/MPSoC的高性能SPI接口可以确保数据的实时性和可靠性。
消费电子:在智能家居、可穿戴设备等消费电子产品中,SPI接口用于与各种外设(如显示屏、传感器等)的通信。
汽车电子:在汽车电子系统中,SPI接口用于与各种传感器和控制器的通信,确保系统的稳定性和安全性。
技术选择
快速原型设计:如果项目需要快速原型设计,且对性能要求不高,可以选择PS端的SPI控制器。
高性能需求:如果项目需要高性能的数据传输,且对系统资源有较高的容忍度,可以选择PL端的AXI Quad SPI IP模块。
项目特点
灵活性:项目提供了两种SPI实现方式,开发者可以根据具体需求选择最合适的方案。
高性能:PL端的AXI Quad SPI IP模块提供了更高的性能,适合需要高速数据传输的应用。
资源优化:PS端的SPI控制器占用资源较少,适合资源受限的应用场景。
详细指南:项目提供的pg153-axi-quad-spi.pdf文件详细介绍了两种SPI实现方式的配置和使用方法,帮助开发者快速上手。
通过本项目,开发者可以充分利用Zynq SoC/MPSoC的强大功能,实现高效、可靠的SPI通信,满足各种复杂应用的需求。无论是快速原型设计还是高性能数据传输,本项目都能为您提供最佳的解决方案。

例程开发环境:

SOC芯片:ZYNQ7020

开发环境:Vivado2020.2,Vitis2020.2

       Flash芯片:W25Q256,即256Mb,32MB

一、Flash知识简介
Flash存储器也叫闪存,是一种非易失性存储器,说白了就是数据掉电不丢失,所以一般用来存放运行程序或者需要掉电保存的数据,并且flash具有操作方便、读写速度快的优点。

Flash存储数据时,只能将1写为0,不能将0写为1,因此对flash进行擦除操作时,就是将flash对应区域全部置1,写数据时,就将对应bit置0即可。

       Flash内部区域划分:级别从大到小一般是:整片(chip)>块(block、bulk、bank)>扇区(sector)>页(page)

其中页为最小划分的区域单位,其内部一般包含若干字节,例如一页包含256字节等,但是目前大部分厂商常用的最小单位划分基本都是扇区,具体是啥还是要看芯片手册来确定;

       下面是我是用的W25Q256 flash芯片的区域划分情况

最小单位为扇区,每个扇区容量大小4KB,即4096字节
每个块包含16个扇区,所以每个块大小64KB
整片flash共有512个块,共32MB存储空间


二、SPI知识简介
1.SPI引脚介绍
/CS:片选引脚,低电平有效,拉低时,表明使能该芯片的操作

VCC:电源引脚

GND:接地引脚

CLK:输入时钟引脚,用于SPI通信同步

IO0(MISO):数据输入引脚

IO1(MOSI)数据输出引脚

IO2(WP):写保护引脚,低电平时,flash无法被写入数据,在Quad SPI模式下复用为数据引脚

IO3(HOLD):暂停通讯引脚,拉低时,DO为高阻态,flash暂停其余操作保持现有状态,等待HOLD拉高,再恢复之前的通讯,在Quad SPI模式下复用为数据引脚

2.SPI协议介绍
SPI为标准通信协议,不仅可以操作flash,还可以与其他类型器件进行通信,但是由于SPI标准通信协议为全双工,且速度较慢,因此实际读写flash时,一般都使用其扩展协议,即Dual flash和Quad flash

SPI接口协议:使用IO0(MISO)和IO1(MOSI)这两个进行读写,IO0输入使用,IO1输出使用,可同时进行读取操作,因此为全双工通信,但是一般读写flash时很少使用全双工模式,所以操作flash时,标准SPI协议用的很少
Dual SPI接口协议:同样使用IO0和IO1这两个进行读写,IO0和IO1只能同时向一个方向发送数据或同时读取数据,因此Dual SPI属于半双工通信,但是同一时刻可以读取2bit或写入2bit数据,是标准SPI协议速度的两倍
Quad SPI接口协议:增加两个读写flash的IO(WP和HOLD复用为数据IO),同时使用IO0- IO3四线进行读或写,同样为半双工通信,同一时刻可以传输4bit数据,是标准SPI通信速度的4倍,常用于操作flash的读写
注意:Dual SPI和Quad SPI一般只用于读写flash使用,不控其他类型器件

3.ZYNQ Quad SPI说明
       ZYNQ QSPI Flash控制器通过MIO与外部 Flash 器件连接,支持三种模式:单个从

器件模式、双从器件并行模式和双从器件堆模式:

(1)单个从器件模式:即外接单个 flash,通过 4bit I/O(即 quad 、dual 或单线)与 flash 进行通信。

(2)双从器件并行模式:把每个 flash 的 IO 进行了单独的连接,扩展成 8bit 用于同时访问两块 flash,实现扩展 QSPI Flash 容量。

(3)双从器件堆叠模式:使用片选 SS 信号进行区分 flash的使能。对 flash 仍然是 4bit,即同一时间只能操作一块 flash。通过使用双从器件模式可以扩展 QSPI Flash

的存储容量

下图是ZYNQ QSPI Flash三种使用模式的框图以及block design中的配置使用方法

三、Vivado工程搭建
ZYNQ QSPI Flash为PS核内置功能,属于硬核,直接对PS端进行配置即可使用,不需要增加PL端的任何IP核;因此本项目工程是在ZYNQ-Vitis(SDK)裸机开发之(一)串口实验工程基础上开发的,一些block design的设计方法,Vitis工程的建立方法等,均在该篇文章中进行了详细的讲解,大家可以去参考:

ZYNQ-Vitis(SDK)裸机开发之(一)串口收发使用:PS串口+PL串口、多个串口使用方法

       PS核需要勾选上QSPI,我的只有一片flash,因此选的Signal SS 4-bit IO选项,具体引脚约束根据自己项目原理图确定

四、编写Vitis程序


1.ZYNQ QSPI Flash操作的格式:


(1)在向flash中写数据时,传入的buffer中结构应按照如下放置数据:

第0个字节:放要下发的指令号

第1-3个字节:放要操作数据的起始地址,当然如果某些指令不需要操作数据,例如读取flash ID,这种的话1-3字节就不需要填写数据,后者随便填就行,作为空闲字节使用

从第4个字节开始:为纯数据区,即需要写入flash内部的数据

(2)在从flash中读数据时,读取的buffer中的数据结构如下所示:

使用普通读指令READ_CMD读取数据时,返回的数据结构与写入时的数据结构一致,提取数据时从第4个字节开始提取,可见下图所示:      

       使用Fast、Dual、Quad这三种模式读取的时候,读取回来的数据结构中,多出一个空闲字节Dummy,在数据区的前面,因此此时提取数据时,应该从第5个字节开始提取

2.头文件:qspi_hdl.h


(1)定义QSPI器件ID号

(2)定义flash芯片操作指令,这个需要根据自己使用芯片的手册进行修改

(3)定义flash操作指令、起始地址、空闲字节、数据等的偏移地址

(4)定义空闲字节Dummy、读ID、擦除指令、buffer头部所占字节数量

(5)定义flash芯片的容量参数,包括页数、页字节、扇区数、扇区字节等

(6)定义要操作的flash部分区域,包括读写起始地址,读写范围、读写的字节数量等等

(7)声明QSPI Flash操作相关的函数,QSPI初始化、读写操作、擦除操作、读flash ID、使能Quad SPI模式等

/*!\file    qspi_hdl.h\brief   firmware functions to manage qspi\version 2024-04-15, V1.0.0\author  tbj
*/#ifndef QSPI_HDL_H
#define QSPI_HDL_H#include "xqspips.h"//QSPI器件ID
#define QSPI_DEVICE_ID        XPAR_XQSPIPS_0_DEVICE_ID//flash操作指令
#define READ_CMD            0x03    //读指令
#define WRITE_CMD            0x02    //写指令
#define READ_STATUS_CMD        0x05    //读状态指令
#define WRITE_STATUS_CMD    0x01    //写状态指令
#define WRITE_ENABLE_CMD    0x06    //写使能指令
#define WRITE_DISABLE_CMD    0x04    //禁止写使能指令
#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    //擦除一个扇区-全部写1
#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(根据自己flash芯片手册确定)
#define NUM_SECTORS 0x200      //定义扇区数量-256个(根据自己flash芯片手册确定)
#define NUM_PAGES 0x20000      //定义页数量-65536个(根据自己flash芯片手册确定)
#define PAGE_SIZE 256          //定义每页字节数-256个字节(根据自己flash芯片手册确定)//定义实际读写操作的范围和数据大小
#define PAGE_COUNT 16         //定义需要操作读写的页数
#define TEST_ADDRESS 0x01FF0000//0x00055000        //定义读写操作的起始地址
#define UNIQUE_VALUE 0x05            //定义读写操作的起始值
#define MAX_DATA (PAGE_COUNT * PAGE_SIZE)    //定义读写操作的最大数据量//定义QSPI操作结构体对象
XQspiPs QspiInstance;#ifdef __cplusplusextern "C" {
#endif//初始化QSPI控制器int Qspi_Init(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);//擦除flashvoid FlashErase(XQspiPs *QspiPtr, u32 Address, u32 ByteCount);//读取flash IDint FlashReadID(void);//使能四线模式void FlashQuadEnable(XQspiPs *QspiPtr);#ifdef __cplusplus
}
#endif#endif /* QSPI_HDL_H */



3.源文件:qspi_hdl.c


(1)对头文件总QSPI初始化、读写操作、擦除操作、读flash ID、使能Quad SPI模式等函数进行实现

/*!\file    qspi_hdl.c\brief   firmware functions to manage qspi\version 2024-04-15, V1.0.0\author  tbj
*/#include "qspi_hdl.h"//QSPI读写flash使用的buffer,内部使用
static u8 FlashReadBuffer[MAX_DATA + DATA_OFFSET + DUMMY_SIZE];
static u8 FlashWriteBuffer[PAGE_SIZE + DATA_OFFSET];/* 功能:初始化QSPI控制器* 入参1:QSPI控制器实例化对象指针*/
int Qspi_Init(XQspiPs *QspiInstancePtr){int Status;XQspiPs_Config *QspiConfig;//初始化QSPI控制器QspiConfig = XQspiPs_LookupConfig(QSPI_DEVICE_ID);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;}//清空读写操作的buffermemset(FlashWriteBuffer, 0x00, sizeof(FlashWriteBuffer));memset(FlashReadBuffer, 0x00, sizeof(FlashReadBuffer));//将flash配置为手动启动、手动片选模式,将hold(reset)引脚配置为高电平,hold低电平,暂停收发数据,高电平恢复收发数据Status |= XQspiPs_SetOptions(QspiInstancePtr, XQSPIPS_MANUAL_START_OPTION |XQSPIPS_FORCE_SSELECT_OPTION |XQSPIPS_HOLD_B_DRIVE_OPTION);//设置QSPI预分频系数Status |= XQspiPs_SetClkPrescaler(QspiInstancePtr, XQSPIPS_CLK_PRESCALE_8);//将片选信号置为有效Status |= XQspiPs_SetSlaveSelect(QspiInstancePtr);//读取flash IDStatus |= 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个字节,第五个字节开始才是写入的数据FlashWriteBuffer[COMMAND_OFFSET]   = Command;FlashWriteBuffer[ADDRESS_1_OFFSET] = (u8)((Address & 0xFF0000) >> 16);FlashWriteBuffer[ADDRESS_2_OFFSET] = (u8)((Address & 0xFF00) >> 8);FlashWriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);//将要写入的纯数据,写入到flash写操作buffer中(flash写操作buffer包括写指令、地址、数据等内容)memcpy(FlashWriteBuffer + 4, WriteBuf, ByteCount);//将写指令、写起始地址信息、写入数据内容,写到flash中XQspiPs_PolledTransfer(QspiPtr, FlashWriteBuffer, 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个字节FlashWriteBuffer[COMMAND_OFFSET]   = Command;FlashWriteBuffer[ADDRESS_1_OFFSET] = (u8)((Address & 0xFF0000) >> 16);FlashWriteBuffer[ADDRESS_2_OFFSET] = (u8)((Address & 0xFF00) >> 8);FlashWriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);//如果是快速读取、双线读取和四线读取,则需要增加一个空闲字节的长度DUMMY_SIZEif ((Command == FAST_READ_CMD) || (Command == DUAL_READ_CMD) ||(Command == QUAD_READ_CMD)) {ByteCount += DUMMY_SIZE;}//将读指令和读地址发送到通过QSPI发送到flash,等待数据读取至ReadBuffer中XQspiPs_PolledTransfer(QspiPtr, FlashWriteBuffer, FlashReadBuffer,ByteCount + OVERHEAD_SIZE);//如果是快速读取、双线读取和四线读取,则需要增加一个虚拟字节的长度DUMMY_SIZEif ((Command == FAST_READ_CMD) || (Command == DUAL_READ_CMD) ||(Command == QUAD_READ_CMD)) {//非普通模式读取,有空闲字节,从第5个字节开始是纯数据memcpy(ReadBuf, FlashReadBuffer + 5, ByteCount - 1);}else{//普通模式读取,无空闲字节,从第4个字节开始是纯数据memcpy(ReadBuf, FlashReadBuffer + 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 eraseif (ByteCount == (NUM_SECTORS * SECTOR_SIZE)) {//发送写使能指令XQspiPs_PolledTransfer(QspiPtr, &WriteEnableCmd, NULL,sizeof(WriteEnableCmd));//将整片擦除指令写入到发送buffer的首个字节的位置FlashWriteBuffer[COMMAND_OFFSET]   = BULK_ERASE_CMD;//将整片擦除指令发送到flashXQspiPs_PolledTransfer(QspiPtr, FlashWriteBuffer, 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的前四个字节FlashWriteBuffer[COMMAND_OFFSET]   = SEC_ERASE_CMD;FlashWriteBuffer[ADDRESS_1_OFFSET] = (u8)(Address >> 16);FlashWriteBuffer[ADDRESS_2_OFFSET] = (u8)(Address >> 8);FlashWriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);//将扇区擦除指令,以及开始擦除首地址发送到flashXQspiPs_PolledTransfer(QspiPtr, FlashWriteBuffer, 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;//读取ID指令,后三个字节是空闲字节,填不填都行,填什么也无所谓FlashWriteBuffer[COMMAND_OFFSET]   = READ_ID;FlashWriteBuffer[ADDRESS_1_OFFSET] = 0x23;FlashWriteBuffer[ADDRESS_2_OFFSET] = 0x08;FlashWriteBuffer[ADDRESS_3_OFFSET] = 0x09;//将读ID指令发送到flashStatus = XQspiPs_PolledTransfer(&QspiInstance, FlashWriteBuffer, FlashReadBuffer,RD_ID_SIZE);if (Status != XST_SUCCESS) {return XST_FAILURE;}xil_printf("FlashID=0x%x 0x%x 0x%x\n\r", FlashReadBuffer[1], FlashReadBuffer[2],FlashReadBuffer[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 (FlashReadBuffer[1] == 0xEF) {//获取读状态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;}}}
}


4.编写QSPI Flash读写测试函数

//读写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(&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");}
}

5.main函数调用

五、实测结果

http://www.xdnf.cn/news/16404.html

相关文章:

  • 同声传译新突破!字节跳动发布 Seed LiveInterpret 2.0
  • Win11批量部署神器winget
  • 滚珠导轨:手术机器人与影像设备的精密支撑
  • 升级目标API级别到35,以Android15为目标平台(三 View绑定篇)
  • 上位机程序开发基础介绍
  • Round-Robin仲裁器
  • 深入理解 BIO、NIO、AIO
  • RocketMQ学习系列之——客户端消息确认机制
  • jwt 在net9.0中做身份认证
  • [2025CVPR-图象分类方向]CATANet:用于轻量级图像超分辨率的高效内容感知标记聚合
  • C# WPF 实现读取文件夹中的PDF并显示其页数
  • 案例分享|告别传统PDA+便携打印机模式,快速实现高效率贴标
  • Class18卷积层的填充和步幅
  • uniapp之微信小程序标题对其右上角按钮胶囊
  • 测试ppyoloe的小样本few-shot能力,10张图片精度达到69.8%
  • Allegro软件光绘文件Artwork到底如何配置?
  • Python柱状图
  • Lakehouse x AI ,打造智能 BI 新体验
  • 戴尔电脑 Linux 安装与配置指南_导入mysql共享文件夹
  • 关于网络模型
  • FreeRTOS—优先级翻转问题
  • vue项目入门
  • 【C++避坑指南】vector迭代器失效的八大场景与解决方案
  • haproxy七层代理(原理)
  • 从0开始学习R语言--Day57--SCAD模型
  • 深入浅出设计模式——创建型模式之简单工厂模式
  • Hive【Hive架构及工作原理】
  • 如何高效通过3GPP官网查找资料
  • JAVA + 海康威视SDK + FFmpeg+ SRS 实现海康威视摄像头二次开发
  • 服务器托管:网站经常被攻击该怎么办?