ZYNQ的ARM侧基础学习_Part1
1 初始Vitis
前面应该学习过ZYNQ的PL端的开发,还不涉及PS端。ZYNQ实际上是以PS端为核心,PL端作为它的外设,来去比方说做一个硬件加速了或者是对PS端做一个外设的扩展,那么要学习ZYNQ,一定是要学习PS端开发的。
前面所学的FPGA开发是通过Verilog语言,是偏底层硬件的开发和设计。而这里所学习的一个Vitis软件是开发它的PS端,使用C或者是C++。
另外需要说明的是,ZYNQ是PS+PL,而这个MPSoC也是PS+PL,他们只不过是ARM处理器以及PL端的一些资源等等有些差异,但他们的开发很类似,所以说下面的教程以ZYNQ为主要目标来去讲解,那么针对它们的差异地方也会去做一些说明。
1.1 Vitis是什么
AMD Vitis软件平台是一款开发环境,主要用于开发包括FPGA架构,ARM处理器子系统和AI引擎在内的设计。Vitis 工具与Vivado设计套件相结合,可以为开发提供更高层次的抽象设计。
说起这个Vitis,如果之前看过比较旧一点的资料的话,会发现它的一个嵌入式裸机开发工具是SDK,那么这个软件在升级之后就叫Vitis了。其实他俩开发方式有很多类似的地方,像这个SDK呢,它原来是集成在Vivado软件当中的,而后面这个SDK独立出来了,那么也就叫Vitis。
1.2 为什么学习Vitis
目的:为了发挥出ZYNQ/MPSoC软硬件协同设计的优势。
因为ZYNQ包含PS和PL的,并且以PS为核心,它俩是通过AXI接口进行互联的。在很多的使用场景中,单独用FPGA不好实现,单独用纯软件也不好实现,所以就需要它俩结合到一起。
那么在学习Vitis的时候,一方面学习它的PS开发,另一方面也是学习PS跟PL它俩是怎么协同设计的。
1.3 嵌入式系统的开发流程
上图是嵌入式部分的流程图。首先第一步就是创建Vivado工程,这个过程其实就跟我们前面做FPGA开发是一样的,就是通过Vivado软件创建一个工程。在创建好之后,就需要使用IP的集成器来创建一个PS系统,这个IP集成器是塞灵斯提供了的一个工具,在里面可以调用赛灵斯的官方IP核,然后像搭积木一样的方式去搭建整个系统.
接下来是生成这个顶层的HDL,就是说通过这个软件去创建一个顶层的模块,对前面所搭建的系统进行例化。那么接下来就是生成比特流然后把硬件导出,可以发现这个生成比特流打了个括号,意思是它是一个可选的,如果这个系统只有PS端,那么是没有必要生成这个比特流(也生成不了,因为里面是没有包含PL端的,因为比特流实际上是PL端的一个配置文件,如果说这个系统里面包含了PL,那么你在后面导出硬件之前就要生成比特流文件)。
接下来就进入Vitis的一个开发了,它是基于前面所搭建的硬件平台进行一个开发,那么最后就是进行板级验证。
1.4 ZYNQ/MPSoC最小系统
下面再来看一下ZYNQ的一个最小系统,下图是给出了ZYNQ的一个最小系统(ZYNQ的最小系统只包含了PS,不包含PL):
最小系统这里给出了两个条件:① 它是系统正常工作的最小条件;② 它是其他系统建立的基础。
最小系统中,至少包含一个ARM A9处理器,然后它还有一个DDR3存储控制器,与外部的DDR3存储芯片连接,程序是要运行在这个DDDR3上面的,另外有一些数据也可以存储在里面。接着还需要一个UART控制器,它通过MIO外设接到了USB_UART。那么为什么需要这个串口呢?其实大家如果说按照我们的文档操作的话呢,应该是所有历程都会包含这个UART,这个UART在我们后面开发过程当中会比较频繁的用到,可以通过它打印一些信息。
另外还有一点需要跟大家强调一下,这个DDR3控制器它并不是必须的,因为也可以把程序运行在这个OCM(片上存储器)里面,只不过这种程序运行在OCM上不典型,那么为什么不典型呢?主要是他的这个存储容量比较小,跟外置的DDR3肯定没法比。
1.5 参考资料
- 正点原子文档:
- 嵌入式Vitis开发指南
- Xiinx官方手册:
- Zynq-7000 SoC技术参考手册(UG585)
- Zynq UIltraScale+技术参考手册(UG1085)
2 HelloWorld-ZYNQ最小系统搭建
2.1 创建Vivado工程
然后,会让你选择工程所在的路径。然后起一个工程名,这里就叫Hello_world。注意要选择Creat project subdirectory,让Vivado帮助我们管理工程目录。
然后选择创建RTL工程,注意选择Do not specify at this time,也就是我们在创建工程过程中不指定RTL文件。
然后点击下一步,选择开发板上的芯片型号。点击完成。
2.2 使用IP Integrator创建PS
双击ZYNQ打开PS端的配置:
首先是DDR配置:
接着是串口UART配置:
方法1,如下图所示:
方法2:如下图所示
配置串口的参数:
好到这里,DDR3和UART的引角包括配置就完成了。
在上讲解了怎么在Diagram的画布里面添加这个ZYNQ的IP核,添加IP核实际上就是对PS做一个配置,而并不是通过FPGA去搭建一个软核。
下面取消一些多余的接口,可以发现图中有个M_AXI_GP0_ACLK、M_AXI_GP0,它两前面的名称是一样的,那么它们就属于同一组类型的接口信号,那么这个M_AXI_GP0就是PS跟PL交互的一个GP0的接口,而M_AXI_GP0_ACLK就是对应接口的时钟,本次实验不包含PL端,那么肯定这个GP接口也不需要,所以可以把这个接口给它取消。步骤是:双击打开ZYNQ,按照图中操作
下图可以发现,这两接口就消失了。
图中还有个FCLK_CLK0是PS提供给PL端的一个时钟,同样双击打开ZYNQ,按照图中操作就可以去掉这个时钟了:
另外针对clock的配置,再展开说一下,其中最上面的Processor/Memory Clocks是处理器/memory的时钟,点开后可以发现CPU的时钟是666.666MHz,这个时钟来自ARM PLL,DDR的时钟频率是533.333MHz,这个时钟来自DDR PLL。
它们这些不同的时钟频率的最原始的时钟都是来自于图中上面的Input Frequency(MHz)输入时钟33.333MHz,也就是PS端的晶振。
图中还有个FCLK_RESET0_N是PS提供给PL端的一个复位信号,,同样双击打开ZYNQ,按照图中操作就可以去掉这个复位了:
到这里,不需要的接口就都取消了,变成如下的情况:
下面需要把有用的接口引出来。不需要我们手动引出,上图中,左上角有关Run Block Automation,点击之后,在弹出的页面点击OK,即可得到下面引出引脚的图:
到这里,这个block design就搭建好了。如何确认搭建的对不对呢?可按照上图中红色字提醒的操作进行验证。
下面就来让它产生输出的结果或者是输出的设计,操作如下图所示:
2.3 生成顶层HDL
如何生成顶层HDL呢?如下图所示:
点击OK之后,软件会自动生成一个Verilog代码,可以打开看一下:
另外前面在产生输出的结果(Generate Output Products)的时候,它实际上会产生一些网表文件、一些综合文件,包括一些IO约束,也就是在下图IP source里:
2.4 生成Bit流并导出Hardware
针对本次实验是不包含PL端的,所以说不需要生成Bit流,下面就直接导出硬件Hardware。如下图:
点击下一步之后,会继续弹出一个框,用于选择导出的这个Hardware文件的路径和名称,之后点击下一步,点击Finish导出即可。
导出之后,会产生一个.xsa文件,这个文件全称为Xilinx Support Archiver可以理解为是一个硬件平台文件(软件的设计会基于这个硬件平台文件来开发),后面在做Vitis开发的时候,或者是Linux的时候都会用到这个文件。它里面包含了一些硬件信息,比方说包含了处理器配置信息,还有一些外设的连接信息和器件初始化代码等等。
其实
.xsa文件就是个压缩包,可以用压缩软件打开它,可以发现它里面包含很多文件。其实我们在Vitis软件当中基于这个.xsa文件去创建的时候,它会把这些文件都解压出来。正是因为有了这些文件,我们的软件程序才能够在ZYNQ处理器上面运行。
下面就可以打开Vitis软件进行嵌入式软件开发了。
2.5 在Vitis中进行软件设计
点击下一步之后,出现下面的界面:
之后出现如下界面:
点击下一步后,会出现一个“域”的设置界面:
点击下一步后,出现如下界面:
在src文件夹中,找到源文件helloworld.c,如下图所示:
编译工程:
编译完成后,会生成一个.elf文件,这个文件就是ARM处理器所运行的文件,就像前面PL侧的比特流文件。
接着就可以上板验证了,将板子硬件连接好之后就可以烧录程序了:
程序烧录到板子之后,可以打开串口调试助手,就可以接收到板子发送的代码中的两行字符串了。printf()本质底层是调用了UART串口打印的。
注意:
需要说明的是,可以发现这里其实直接就通过函数发送出去了,并没有发现对UART进行初始化,其实这个初始化以及包括整个DDR的初始化,都会有其他的函数去执行的,这些函数是早于这个
main()函数去执行的,那么对UART、DDR等初始化完成之后,才会跳转到main()函数去执行,这些启动的细节有机会再来做一个详细的介绍。
2.6 小结
3 MIO-GPIO学习
3.1 MIO-GPIO简介
MIO:Multiplexed I/O,多路复用IO,MIO将来自PS外设和静态存储器接口的访问多路复用到PS引脚上(功能太多,引脚不够用)。
GPIO: General Purpose I/O,GPIO是ZYNQ的PS端的一个IO外设,用于观测(Input)和控制(Output)器件引脚的状态。
3.2 MIO与EMIO
MIO:Multiplexed I/O,多路复用IO,EMIO:ExtendedMIO,扩展的MIO。
- 当PS的引脚不够用的时候,可以使用EMIO进行扩展,从而使用PL侧的IO;
- 当某个设备硬件已经连接到PL端,但是通过Verilog代码实现较为复杂的时候,可以使用EMIO让PS来驱动。
图中,中间IOP是一个IO外设,其左侧是跟互联接口相连接的,IOP可以通过MIO连接到PS的引脚,当然很多引脚都是双向的。然后也可以通过EMIO连接到PL端,可以连接到一些用户逻辑或者是一些模块上面,也可以连接到PL端的IO引脚上。
3.3 Bank分组
3.3.1 PS的IO口Bank划分
物理电气分组 (Bank 500, 501, 502, 13, 33, 34, 35..):这源自FPGA的物理设计。为了管理不同的IO电气标准(如LVCMOS、LVDS、HSTL等)和电压(如1.8V,2.5V,3.3V),FPGA的脚被分成了多个物理Bank。
- 每个物理Bank有独立的VCCIO供电引脚。同一个物理Bank内的所有引脚必须使用相同的电压标准。
- 这套编号系统是所有Xiinx7系列FPGA(包括Artix, Kintex,Virtex, Zynq)通用的。
- 对于Zyng-7020芯片,其物理Bank编号通常包括:
- Bank 500,501,502:这三个Bank对应的是PS部分的MIO引脚。
- Bank 13,14,33,34,35:这些是PL部分的通用Bank。你的Zynq-7020具体有多少个PL Bank取决于封装型号(如CLG400,CLG484等)。
下面以CLG400封装和XC7Z020为例来看一下。
bank502专门用于连接DDR3,Bank500和Bank501是MIO引脚(数一下会发现总共58个,要除去其中的4个引脚,这4个引脚是连接时钟、复位、电源等,所以实际可用的MIO数量为54)
3.3.2 GPIO口Bank的划分
注意,3.3.1小节的Bank划分和3.3.2小节的Bank划分不是一个概念。
3.4 GPIO的特性
- 软件通过GPIO外设可以观测和控制54个MIO和64个EMIO(Zynq-7000)
- 每个GPIO可以独立且动态的编程,作为输入/输出以及中断模式
- GPIO被分成了四个BANK,即四个寄存器组(Zyng-7000)
- 软件通过一组存储器映射(memory-mapped)的寄存器来控制GPIO
补充:存储器映射(memory-mapped)
对于所有外部设备而言,CPU一律把它们看作地址空间,如果这些地址空间挂载的是外设(比如GPIO、UART、SPI等),那么CPU的寻址就映射成寄存器;如果挂载的是存储器(比如说DDR3、DDR4),那么CPU的寻址就映射为存储器的地址。
GPIO控制和状态寄存器的存储器映射基地址为
0xE000_A000(Zynq-7000系列)
3.5 GPIO寄存器
前面提到了存储器映射的概念,也就是说当对某个外设进行操作的时候,需要提供一个地址才能对它进行读写操作,那么针对这个GPIO外设进行操作的时候,同样也需要提供一个地址,这个地址其实就是一个寄存器的地址。对GPIO所有的配置、操作都是通过操作它的寄存器来去实现的。
本节先不看中断相关的寄存器,先看GPIO读写操作相关的寄存器。
- 图中可以发现寄存器主要分为四大类:
- 首先是
DATA_RO,这是一个数据读寄存器,简单说就是读取GPIO引脚的电平。 - 接着是数据寄存器,简单说就是控制GPIO引脚的电平
DATA:是数据写入寄存器(32位)MASK_DATA_LSW:是数据写入寄存器(带屏蔽功能,低16位)MASK_DATA_MSW:是数据写入寄存器(带屏蔽功能,高16位)
- 然后
DIRM是方向模式寄存器,控制GPIO方向是输入还是输出。- 要注意,输入其实是一直有效的,即使你把GPIO设置为输出模式,也可以读取引脚电平
- 最后
OEN是输出使能寄存器。
- 首先是
3.6 GPIO官方数据手册阅读
…… …… ……
3.7 GPIO的LED实验
3.7.1 实验任务
本节的实验任务是使用PS端的MIO控制两个LED,实现LED闪烁的效果,闪烁间隔为1s。硬件连接如下图所示,这里只用到了下面的PS端的2个LED灯。
3.7.2 系统框图
本次系统框图其实就是在最小系统的基础上,添加一个GPIO外设。总之,本次实验包括A9处理器、DDR3控制器(它连接到外部DDR3存储器芯片,作为程序的运行空间),还需要UART打印信息,这是最小系统,在此基础上添加了GPIO的一个控制器,然后把GPIO连到PS上的两个引脚,来控制两个灯的亮灭。
在了解了这个系统框图之后,下面就可以搭建这个系统了。在此之前,可以先做一个思路的整理:
- 硬件设计部分
- 在最小系统的基础上,添加一个GPIO外设就可以了;
- 软件设计部分
- 思路1:基于直接操作寄存器的方式
- 思路2:基于库函数的方式
3.7.3 工程搭建
- 创建Vivado工程
- 打开Vivado软件,创建工程,参考上面的2.1小节。
- 使用IP Integrator创建PS
- 点击Create Block Design,填写模块名称;
- 添加ZYNQ的PS的IP核;
- 配置DDR3、UART;
- 删除不需要的信号;
- 在MIO Configuration中配置GPIO外设;
- 点击Run Block Automation,自动连接端口;
- 验证设计是否正确。
- 产生输出的结果(Generate Output Products)
- 创建顶层HDL
- 生成Bit流并导出Hardware
- 打开Vitis开始软件设计
3.7.4 程序编写
(一)思路一:寄存器方式
请参考第3.5讲视频讲解
(二)思路二:库函数方式
1 |
|
XGpioPs_LookupConfig 的作用是:软件拿着一个“身份证号码”(ID),去系统数据库里查出这个设备的“家庭住址”(寄存器基地址)。
为了彻底搞清楚,我们需要区分两个概念:“控制器(管理者)的ID” 和 “引脚(具体员工)的编号”。MIO7 和 MIO8 的“身份证号码(ID)”是一样的,但它们的“工号(引脚号)”不同。
- “身份证号码”到底是谁的?
首先必须纠正一个非常容易产生的误解:
- 这个 ID 不是给 MIO7 的。
- 这个 ID 也不是给 MIO8 的。
- 这个 ID 是给“GPIO 控制器”这个硬件模块的。
想象一家公司:
- GPIO 控制器 = 部门经理(他负责管理手下的一堆引脚)。
- MIO7, MIO8 = 部门里的员工。
当你调用
XGpioPs_LookupConfig(GPIO_DEVICE_ID)时,你实际上是在找“部门经理”。
- 你告诉软件:“我要找 GPIO 0 号部门的经理。”
- 软件就把“0 号部门经理办公室的地址”(基地址)给你。
无论你想操作员工 MIO7 还是员工 MIO8,因为他们都属于同一个部门(都在 GPIO 0 控制器里),所以你找的“部门经理 ID”是同一个。
- 编程时如何写这个 ID?是背下来还是查?
绝对不是背下来,是查出来的! 而且通常不需要你去手动查数字,而是直接复制宏定义名字。
- 步骤一:找到“花名册”(xparameters.h)
如前所述,这个文件在工程目录的
bsp文件夹里。打开它,你会看到类似这样的内容:
1
2 /* 定义 GPIO 控制器的 ID */
- 步骤二:在代码中“抄写”
你不需要写
0(虽然写0也能跑,但不规范)。你需要把这个名字XPAR_XGPIOPS_0_DEVICE_ID抄到你的代码里。
4 EMIO-GPIO学习
上面是ZYNQ框图,可以发现IO外设(IO Peripherals)既可以通过MIO来连接到PS的IO,也可以连接到EMIO,从而连接到PL侧的IO。同时,从图中可以看到存储器接口(Memory Interface)只能连接到MIO,不能连接到EMIO。
注意:
其实这个IO外设通过EMIO连接到PL的话呢,它除了能够连接到PL端的引脚,它也支持连接到PL端内部的一个逻辑或者是模块。当然连接到PL端的IO,它的使啊更为更为广泛。
另外可以发现这个框图里面,有些部分是有方框的,比方说MIO或者是XADC,而有些是没有这个方框,比方说这个EMIO和GP(General-Purpose Ports)。带方框表示它是一个功能模块,而没有方框表示它是一个接口,像这个EMIO、GP等,都是属于接口。
上图是展示了PS的IO外设既可以连接到PS的引脚,也可以连接到PL端的引脚。PS I/O外设连接到PS端的引脚是通过MIO的一个多路复用器,而连接到PL端的引脚是通过EMIO的接口。当然了,它除了可以连接到PL端的引脚之外,也可以直接连接到PL端的内部逻辑或者PL端的一些模块上。
4.1 EMIO基础概念
EMIO:ExtendedMIO,扩展的MIO。
EMIO是PS和PL之间的接口,主要用于当PS的引脚不够用的时候,可以使用EMIO来进行扩展,从而使用PL的IO。
下面的表格,给出了针对这个IO外设,它连接到MIO或者是连接到EMIO的差异。
TTC是一个定时器,那么针对每个定时器,它可以引出的端口或者是信号的个数不一样,上面表格中可以看出,针对MIO它是一对,而针对EMIO它是三对。
然后下面是SWDT,它是一个看门狗的定时器,上面表格中可以看出,这个对MIO和EMIO是一样的。
然后下面是SMC(静态存储的控制器),针对MIO这个静态存储控制器是可以连接到SRAM或者NAND Flash,但是它不支持连接到EMIO,所以说在最初设计基于ZYNQ原理图的时候,这些是需要了解的。
还有Quad-SPI是Flash控制器,它支持一个单线双线和四线。但针对EMIO是不支持的。
再往下是一个SDIO的控制器,针对PS端的SDIO控制器有两个,主要是用来连接比方说SD卡或者是EMMC,针对MIO或者是EMO,区别在于它所支持的频率不一样。
然后是本节所需要学习的一个重点,就是针对这个GPIO外设,它连接到MIO是支持到54个IO(使用的GPIO Bank 0和Bank 1),而针对EMIO是支持64个引脚的,然后它俩最重要的差异在于是否支持输入输出,对MIO来说,它的引脚支持输入和输出,就是单根线支持输入和输出,而EMIO它是控制三个引脚,分别是输入、输出和一个三态引脚,也就是说这个GPIO外设是直接引出三根线连接到PL端。
接着是一个USB,MIO支持Host、device、OTG等几种模式,而EMIO不支持USB。
然后再往下面是一个以太网的控制器Ethernet[0,1],因为有了这个以太网的控制器,程序里面对PS网口的控制会变得比较简单。这个以太网的控制器有两个(0、1),MIO支持的这个接口是RGMII,EMIO支持的接口类型是MII或者是GMII,RGMII相比于GMII,它的引脚更少。
这里再来展开给大家讲一下,领航者开发版它是有两个网口,一个是PS的网口,一个是PL端的一个网口,板载的这两个网口的一个芯片的接口类型都是RGMII,包括领航者出厂测试程序,它是支持对这两个网口的控制,它都是使用的这个以太网的控制器。它是有两个嘛,刚好一个接PS,一个接PL,但是板子的这两个网口的以太网接口类型都是RGMII。
这就有问题了,上面图中可以看到,通过EMIO连接到PL端那个网口,它只支持GMII(GMII不支持RGMII),那该怎么处理呢?
其实也比较简单,相当于我这个以太网的控制器Ethernet再接一个FPGA模块,这个模块实现的功能就是GMII到RGMII接口的一个转换。然后这个模块再引到PL端的引脚,那么PL端就是一个RGMII的接口了。
那么大家发现没有,其实上面这种解决方式就是前面提到的:IO外设接到FPGA的一个内部的模块,然后转换之后再通过这个IP输出端口连接到PL端引脚。所以说这种方式相当于是EMIO连接到FPGA的一个功能模块上面去的。
后面还有一些什么SPI、CAN、UART等,可以通过看UG58手册去查询它的一个使用和差异点在哪里。后面在使用的过程当中呢,也会去详细的介绍它这个差异。
那针对GPIO的话,主要是知道它所使用的这个GPIO Bank是不一样的。
4.2 GPIO连接MIO和EMIO差异
- 对于MIO来说,当输出除能时,输出端口处于三态(3-state)
- 输出除能:也就是说你是取消对这个输出引脚的控制,那么它的整个引脚的状态就是由输入所决定的
- 三态:指三种状态,分别是高电平、低电平还有高阻态
- 对于EMIO来说,输出端口不支持三态,因此不受OEN寄存器的影响;
EMIOGPIOTN[x] = DIRM[x] & OEN[x],这个EMIOGPIOTN三态控制引脚等于DIRM和OEM两个寄存器想与,实现输出的控制。
上图中针对MIO,最右侧是PS的一个IO,下面是一个三态控制引脚,左侧是输出引脚,上面是输入引脚。当这个三态打开的时候,就表示输出是一个高阻状态,所以说此时输出引脚就是断开的。此时就可以把这个IO作为输入去采集IO引脚的电平。而当这个三态关闭的时候,输出引脚就可以控制最右侧IO引脚的状态输出高低电平。
对于EMIO,其输出端口不支持三态,并且不受OEN寄存器的影响。注意这里只是说这个输出端口它不支持3态,其实EMIO它是这三个端口(参考3.3.2小节的图),最终体现在PL端的一个IO是一根线,这根线既支持输入又支持输出。它是怎么做到的呢?这个就用FPGA的一个内部逻辑实现,也就是它有一个专用的I/O Buffer,图中三根引脚连接到I/O Buffer,然后连接到PL端的一个引脚,它就是单根线,是可以支持输入和输出的。一般在Vivado软件里面操作的时候,其实我们只需要把EMIO引到端口,然后工具它会自动添加一个I/O Buffer的。所以说一般也不需要我们程序再去做一个特殊的处理。
4.3 实验任务实现
4.3.1 实验任务
本次的实验任务:使用两个用户按键分别控制PS端的两个灯的一个亮灭。这两个灯都是MIO的一个端口,这两个按键一个是通过MIO连接(相当于是PS端的一个按键),另外一个是通过EMIO连接(相当于PL端的一个按键)。
4.3.2 硬件设计
上图是本次实验的硬件设计部分,从这个原理图中可以发现,无论是PL端的按键还是PS端的按键,在按键没有被按下的时候是高电平,按键按下之后,连接到GND,所以说按下之后就是低电平。
4.3.3 系统框图
上图是本实验的系统框图,从这个框图中可以发现:首先处理器(arm Cortex-A9)、UART、DDR3是一个最小系统所需要包含的。图中两个按键分别是PS端和PL端的,一个连接到MIO,一个连接到EMIO。
注意:本实验用到了PL侧的EMIO,所以需要生成比特流文件。
4.3.4 编程实现任务
思路:首先先在3.7小节的基础上,实现PS端按键控制灯亮灭的任务,然后再更改Vivado工程,把PL端按键加入进来。
前面的工程创建部分,请参考视频:https://www.bilibili.com/video/BV1Et421H75m?t=98.6&p=20
工程搭建好之后,首先实现PS端的按键控制灯亮灭的程序:
1 |
|
下面,需要通过EMIO扩展出一个PL端的引脚,此引脚接一个按键实现对PS端灯的控制,所以说下面就返回到Vivado工程,打开框图设计:
双击打开ZYNQ的IP核,对GPIO进行配置:
如图修改完之后,点击OK,在系统框图上就出现了GPIO的一个接口:
展开GPIO_0之后,可以发现有三个端口,GPIO_I表示一个输入端口,GPIO_O表示一个输出端口,GPIO_T表示是一个三态控制(用于控制引脚方向的)。要把它引到PL端的一个端口上面去,方法如下图所示:
图中端口名称GPIO_EMO_KEY是可以在左侧栏进行修改的。
上图中,选择generate output products后,就耐心等待编译完成。编译成功之后,就看一下其顶层模块有没有把这个端口加进来:
这个时候可以把顶层模块这个文件给先关掉,然后把这个顶层模块移除,然后重新去创建这个HDR的一个顶层模块:
现在重新生成了顶层模块,双击打开看一下:
可以发现这里多了一个端口,就是我们刚才那所添加的一个端口——GPIO_EMIO_KEY_tri_io(软件这里重新命名了这个端口)。
然后继续往后面翻的话,如下图:
可以发现他这里加了一个I/O Buffer,它将PS端所输出的三个引脚_T、_I和_O经过这个IO Buffer之后,它变成了一个双向的IO引脚连接到了这个端口。
下面对这个IO端口进行约束,因为他是要连接到PL端的引脚,所以说下面打开原理图:
根据图中进行设置后,右键RTL ANALYSIS把这个界面关闭掉,然后它会生成一个.xdc文件:
比特流文件编译好了之后呢,我们就把它导出到硬件(选择File -> Export -> Export Hardware)
导出过程,会有一些选项,注意下图中,要选择比特流文件:
接着,回到Vitis软件,我们前面已经编写好了一部分代码,当Vivado软件更新了硬件之后,那么在Vitis软件中怎么把更新后的硬件加进来呢?如下图所示:
点击OK后,提示硬件更新完成。
然后找到板级支持包,对它进行复位:
注意,FSBL也给它复位一下(上图中的蓝色字),这个是跟固化相关的。
复位完成之后,重新去编译一下这个平台工程:
- PL侧按键控制程序编写
其实EMIO引脚的控制跟MIO是非常类似的,只不过它属于不同的bank,所以说这里的引脚编号有差异。那么这个针对EMIO的编号应该是多少呢?我们可以看一下配置函数XGpioPs_CfgInitialize()里面。如下图,对于ZYNQ的MPSOC(性能强于ZYNQ 7000系列)
那么针对ZYNQ 7000系列呢,如下图:
EMIO是从Bank2开始的,所以说它的标号就是从54开始的,因此这里来定义一个EMIO_KEY0,是PL端的,它的编号就应该是54
1 |
|
5 GPIO之MIO按键中断
5.1 中断简介
5.1.1 中断概念
中断(Interrupt):打断CPU执行正常的程序,转而处理紧急程序,然后返回原程序暂停的地方继续运行,就叫中断。
5.1.2 如何实现中断
在ZYNQ里面有一个专业名词叫“GIC”,也就是通用中断控制器。这个中断控制器它就可以监测不同的事件。
针对GPIO的中断,它是怎么实现这个中断功能的呢?
上面这个图在3.5小节展示过,当时主要是学习的下面这部分普通GPIO功能。这个图的上半部分主要是跟中断有关的,那么从上半部分左边的寄存器可以发现INT就是interrupt的缩写。
图中可以发现,input线除了连接到DATA_RO,还向上连接到Interrupt Detecion Logic(中断检测逻辑)。这个中断检测逻辑的功能就是对一个输入信号进行监测,比方说监测一个上升沿或者是下降沿等等,具体是通过INT_TYPE、INT_POLARITY、INT_ANY这三个寄存器来配置的。
INT_TYPE控制是边沿触发还是电平触发中断INT_POLARITY配置中断的极性(正、负),这个极性就是进一步去配置中断检测逻辑,个比方说配置了电平触发,电平触发也分为两种,可以配置为低电平触发还是高电平触发。INT_ANY该寄存器仅在INT_TYPE配置为边沿触发的时候有效,该寄存器就是配置是否是双边沿都触发中断。
图中可以发现,当检测到一个中断的信号之后,会连接到一个DQ触发器,表示中断状态。可以发现INT_STAT中断状态寄存器有一根线Write-1-to-clear指向DQ触发器,如果向INT_STAT中断状态寄存器中写入1,则就可以清除这个中断。
另外,也可以发现DQ触发器的Q端输出的信号也连接到INT_STAT中断状态寄存器,则可以通过读取中断状态的寄存器查看有没有产生中断。
接着,DQ触发器的Q端输出之后,进入一个与门,与门的另一个输入连接左上角的三个寄存器,这三个寄存器是干嘛的呢:
INT_MASK:是中断屏蔽寄存器,通过读取这个寄存器判断哪些中断位被开启了,哪些位被屏蔽了。INT_DIS:中断关闭寄存器INT_EN:中断使能寄存器
所以说,如果想要屏蔽掉某个引脚的中断,就设置
INT_DIS给它禁止掉,如果想打开某个引脚的中断,就设置INT_EN。
如果说某个引脚使能中断,那么检测到中断之后,就会产生一个IRQ(中断请求),这个中断请求给到GIC(通用中断控制器)。那么这个GIC是在哪里呢,如下图:
上图中黑色框内就是GIC,GIC的左侧有一根IRQ(中断请求)线连接过来,这个中断请求可以来自于IO外设,不同的IO外设都可以产生中断。
另外,在前面一张图中,可以发现IRQ #52 to GIC这里有一个#52,这个52是什么意思呢?简单说它是一个中断ID,中断ID的作用是:从图中可以发现,不同的IO外设都可以产生中断信号,这个中断ID就是用来标识是哪一个外设产生的中断。比方说中断ID=52,那么中断就来自于GPIO,如果中断ID等于其他值,那么就来自于其他外设(针对每个IO外设的中断ID是多少,同样也是查看手册)。
此外,不仅仅是IO外设可以产生中断,PL端也可以产生中断,甚至APU内部的CPU也可以产生中断。
以ZYNQ来说,它是54个MIO和64个EMIO,这118个是共享了一个中断ID,也就是#52。那么不同的引脚产生的中断请求的ID都是52,那如何确定这个中断来自于哪个引脚呢?这就要结合INT_MASK和INT_STAT这两个寄存器进行判断——首先INT_MASK可以判断哪些中断被屏蔽了,其次可以通过读取INT_STAT寄存器去判断这个中断是来自于哪一个引脚。
5.1.3 实验任务
使用GPIO的MIO中断功能,实现按键控制LED灯的亮灭。
5.1.4 系统框图
5.2 实现实验
5.2.1 搭建工程
打开前面的MIO控制LED灯实验创建的Vivado工程,打开后把这个工程另存为本次实验的工程。本次实验用不到PL侧,所以导出的时候不需要比特流文件。
5.2.2 编写程序
导出完成后,打开Vitis软件,快速创建一个空的工程。然后添加一个main.c文件。
1 |
|
6 AXI-GPIO
6.1 AXI GPIO与AXI接口
6.1.1 AXI GPIO是什么
AXI GPIO是ZYNQ PL部分的一个IP软核,实现了通用输入输出接口的功能,并通过AXI协议实现了与处理系统(PS)的通信,使得开发者可以方便地控制和扩展GPIO接口。
上图是AXI GPIO的IP核(从Vivado的软件中截取的一个界面)。
首先,S_AXI接口可以展开,展开后会有很多个端口信号,简单说这就是一个AXI接口,这个S表示从机的接口。然后s_axi_aclk是S_AXI接口的时钟。s_axi_aresetn是一个复位信号。右侧是GPIO的端口,可以实现普通GPIO接口的功能,包括控制引脚,它是支持输入和输出的,还有它也可以直接跟的FPGA的逻辑进行连接。
总之,这个AXI GPIO的IP软核,是使用FPGA可编程的逻辑资源来实现的一个GPIO的功能
6.1.2 GPIO与AXI GPIO的对比
| 对比项 | GPIO | AXI GPIO |
|---|---|---|
| 资源类型 | 硬核(存在实际的物理电路) | 软核(使用PL端资源实现) |
| 连接I/O | 可连接到PS和PL端I/O | 不能连接PS端I/O,只能连接PL端I/O |
| 连接PL逻辑 | 支持EMIO | 支持 |
| 灵活性/拓展性 | 较低 | 较高 |
注意:AXI GPIO是软核,也就是说不存在硬件电路,是官方或者工程师自己通过FPGA程序,利用可编程逻辑资源来去搭建的一个AXI GPIO功能。当然了这个可编程的逻辑,也可以用于实现一个I2C、UART、SPI、傅里叶变换等等都可以。
6.1.3 AXI接口介绍
从上面第4节EMIO-GPIO学习的那个复杂的图中,其实AXI接口就是指GP接口(General-Purpose Ports)和HP接口(High-Perormance Ports),外加一个ACP接口,AXI主要就是指这三个接口。下面是一个简化的示意图:
左边是PS右边是PL。从这个图中能够看到PS和PL之间的AXI接口。
首先第一个是GP接口,它是一个通用用途接口。可以发现图中上下有两个GP接口,一个是M_AXI_GP,另一个是S_AXI_GP,其中M表示Master,S表示Slave,这里的M和S它是相对于PS来说的,所以说上面的这个Master它就是表示PS作为主机PL作为从机,下面的Slave它是表示PS作为一个从机PL作为一个主机。我们知道一般一个通信接口作为主机和作为从机的区别是什么——主机发起和控制通信总线而从机机呢一般就是被动地响应。总共有4个GP接口。GP接口的数据总线是32位的,一般的应用场景包括中低速的一些场景上,比如说PS对PL做一些配置等。
当我有一个大量数据搬移的情况时,该使用什么接口。这里提供了一个更高性能的接口,就是下面的HP接口,可以看到它是个S表示一个从机,HP接口总共有四个,这四个HP接口都是PS作为从机,PL作为主机。这个HP接口跟上面两个GB接口最大的一个差异在哪里呀——是不是可以发现有个FIFO,用来缓存数据,所以说PS、PL之间在高速传输数据的时候会经过FIFO进行一个缓存,实现一个大量数据的传输。
比方说我们举个例子,有个摄像头位于PL端,我们知道不同的摄像头,它输出的帧率不一样、分辨率不一样,一般摄像头它是每秒钟源源不断地输出大量的数据,而我们要通过LCD屏或者是HTMI屏显示这个摄像头数据的话呢,我们一般是需要对这个数据做一个缓存,也就是说把这个图像数据缓存到内存当中也就是DDR3当中,那对于我们所使用的ZYNQ来说呢,它的这个DDR3是位于PS端的,所以说是需要把这个图像数据搬移到这个PS内的DDR3当中,那么由于摄像头的数据量很大,所以说我们就可以通过这个HP接口进行一个传输。
若传输少量数据,可以通过GP接口进行传输。
这里还有一个接口,就是这个ACP接口,那这个S也是表示PS作为从机PL作为主机。可以明显发现这个ACP接口跟GP和HP最大的区别是什么——可以发现GP、HP其实都是连接到了一个互联Interconnect模块上面去,我们知道这个互联它其实是相当于一个开关,来去控制/管理这个接口上面的数据传输。而ACP接口它是直接连接到APU里面的SCU上,这个SCU是一致性控制单元,可以作为这个ARM处理器和二级缓存以及OCM的一个桥梁,所以说SCU呢它是比较方便的去访问这个缓存,也就意味着PL端可以直接去访问这个APU里面的缓存。所以说当我们的这个PS端他有一个CPU计算了结果之后呢它可以快速的将这个结果通过ACP接口送到PL端,而同时当PL端做一个硬件加速的时候,ACP可以把他的这个加速的结果快速地发送到这个ARM处理器。
下面给出了一个总结的表格:
AXI它究竟是什么呢?下面就来介绍一下这个接口。这个AXI它首先是这个AMBA这个接口总线的一部分
AMBA它最早是由ARM开发的,主要是用在这个单片机上面,后面对这个总线进行了扩展和修订,那么现在一般就认为在这个片上系统也就是SOC,就是以这个ZYNQ为代表的SCO上面。除此之外,它其实也应用在SOPC也就是纯FPGA的一个芯片上面。
这个AMBA包含多个接口总线,包括APB、AHB、AXI、ATB。AMBA不断升级,现在是到了4.0版本。那AXI也升级到了AXI4。AXI是一个高级可扩展接口或者说是总线。
AXI4-Full表示一个全功能的接口,它比较适合用在一个中高速的一个通信,支持突发传输,比方说主机在发起这个传输的时候,只需要指定一个起始地址,在指定好起始地址之后,后面的数据就可以源源不断的传输,从机它会自动对这个数据放到相应的地址进行累加,所以说它支持的一个传输速度就比较快。
AXI4-Light表示一个轻量级的接口,那它支持的传输速率就比较低了,不支持突发传输,但占用的资源相对较小。
这里所说的这两个接口AXI4-Full和AXI4-Light它其实都是属于存储器映射接口(什么叫存储器映射接口呢,就说你在发起传输的时候,你需要指定地址)。
AXI4-Stream是一个流的传输,它是不要求指定地址的,所以说是比较适合大量数据的一个高速传输。
6.2 AXI GPIO简介
这一节教学视频中,主要是正点原子讲师带着看了一下Vivado软件中官方IP软核的介绍文档,还是讲了不少的,我这里偷懒了,感觉笔记不太好做,请直接参考原视频吧:AXI GPIO简介 - 正点原子 - Bilibili
6.3 实验任务
通过调用 AXI GPIO IP核,使用中断机制,实现开发板PL 端按键控制PS端LED灯亮灭的功能。
下面是系统框图:
图中,AXI Interconnect也是一个IP核,用于将一个(或多个)AXI存储器映射(memory-mapped)的主器件连接到一个(或多个)存储器映射的从器件。
概念1:互联(Interconnect)—— 互联实际上是一个开关,它管理并指挥所连接的AXI接口之间的通信。
概念2:存储器映射,就是把外设(如按键控制器、LED控制器)的寄存器,安排在内存的地址空间里。CPU 访问外设,就像访问普通内存一样,只需要知道“地址”,然后进行“读”或“写”。
我们可以这样对应理解:
1
2
3
4
5
6
7 主器件(Master): 图中的 ARM Cortex-A9。它是发号施令的人,它发起读写请求。
从器件(Slave): 图中的 AXI GPIO。它是听话的执行者,它等待被访问。
存储器映射的含义:
ARM 想要读取 PL_KEY0(按键)的状态,它不需要发明一种特殊的“读按键指令”。
ARM 只需要执行一条普通的“读内存指令”,目标地址设为 AXI GPIO 的地址(例如 0x41200000)。
AXI Interconnect(交通枢纽) 看到这个地址,就知道:“哦,这个地址不是给 DDR 内存的,是给右边 AXI GPIO 的。” 于是把请求转给 AXI GPIO。
AXI GPIO 收到请求,把按键的状态(0 或 1)作为数据返回给 ARM。你可能会问,为什么要这么麻烦把外设映射成内存?
指令简单: CPU 不需要专门学习一套控制外设的指令(如
IN,OUT)。它只需要会用LOAD(读)和STORE(写)这两招,就能控制天下所有的外设。编程方便:对于写 C 语言程序的程序员来说,控制外设就像操作数组一样简单。比如:
int key_status = *(int *)0x41200000;(这就相当于去那个“映射”的地址读了一下按键状态)。
6.4 硬件设计
首先,在Vivado工程中,添加ZYNQ的PS端硬核。
双击打开对其进行配置——① 配置DDR,② 在MIO Configure中配置UART、GPIO MIO(注意电源也要修改)。
其次,添加AXI GPIO软核:
然后,配置完成两个IP核之后,就可以开始连线了。其实这个Vivado软件是支持自动连线的,只有少部分端口需要我们主动去连接:
下图是连接完成后的图:
复位IP核可以简单看一下。首先它有一个同步时钟slowest_sync_clk,然后下面有一些复位的输入信号,比方说一个外部复位输入ext_reset_in、还有一个辅助复位输入aux_reset_in、还有一个debug系统复位mb_debug_sys_rst,还有一个DCM的一个lock的信号dcm_locked。所以说它可以将不同的复位输入同步到这个同步时钟slowest_sync_clk上面,然后再去输出一个针对外设的复位。总之,这个复位IP核主要是用来同步这个复位信号。然后它同步之后的这个复位信号peripheral_aresetn连接到各个不同的功能模块,比方说这里的AXI GPIO以及AXI Interconnection IP核。
AXI Interconnection IP核的功能就是:一个主接口的输入M_AXI_GP0连接到一个从接口S_AXI。
最后,会发现AXI GPIO核的中断引脚ip2intc_irpt还没有连线,这个主要是还没有配置ZYNQ硬核的中断:
之后可手动连线:
这就完成了硬件部分的工程搭建,然后先验证一下这个设计是否正确,验证无误后就可以生成输出文件了:
完成之后,就可以产生顶层文件。
然后,还需要做管脚约束 —— 通过PCB原理图,可以发现PL_KEY0对应的是L14:
最后,生成比特流文件,然后导出硬件。
6.5 软件设计
本次实验的功能跟前面所学习的GPIO MIO按键中断的功能是类似的,所以说可以参考前面所学习的MIO按键中断实验。这里先来打开前面学习的MIO按键中断实验,看一下它的C代码,尝试借鉴修改。
首先,打开Vitis软件,创建一个新的工程。创建一个main.c文件
1 |
|
7 ZYNQ启动流程和程序固化
7.1 启动流程
前面学习了不少历程了,都是通过Jtag的方式下载程序的(也就是使用下载器连接板卡的Jtag接口,然后把程序在线烧录到板卡当中)。通过这种方式比较方便的对代码进行验证,主要是调试功能。其缺点是是当板卡断电之后程序就丢失了。
想让程序断电之后再上电仍然能够运行,那么就需要把程序固化到板卡的片外非易失性存储器当中,比方说像什么Flash等等。这一小节就来教大家怎么把程序固化到板卡当中。
7.1.1 ZYNQ的启动方式
上图总结了ZYNQ系列的启动方式,主要是分为两大类——一个是主模式,另一个是从模式。当然前面通过JTAG的下载方式其实就是从模式。下面重点看一下主模式。
ZYNQ芯片的主模式支持四种:
- NOR Flash
- NAND Flash
- QSPI Flash
- SD卡
7.1.2 ZYNQ启动方式配置
那么ZYNQ的不同启动方式是怎么配置的呢?板卡上面有一个拨码开关BOOT_CFG,这个拨码开关就是控制它启动方式的。上图是拨码开关的电路图,可以发现,拨码开关BOOT_CFG能够控制BOOT_MODE0和BOOT_MODE1的电平高低,根据图中的表格,对应分析就得到了拨码开关BOOT_CFG开关状态对应的启动模式。
7.1.3 ZYNQ的启动过程
下图给出了ZYNQ的一个启动过程。
首先是power on reset,这是一个上电复位,就说这个板卡上电之后,它会一直处于一个复位状态,当上电完成之后,它的这个复位引脚才会被拉高。
在复位完之后,它就进入了一个Stage0:BootROM Executes。这个BootROM是啥呢?可以把它理解成是固化在ZYNQ芯片里面的一段代码(Boot是跟启动相关的,ROM是只读存储器),简单说BootROM是跟启动相关的一段代码,用户不可以对它进行修改。它除了做一些初始化,还会引导Stage1:FSBL的启动。
Stage1:FSBL会对一些关键的外设进行配置,包括DDR、一些MIO引脚等等。
然后才会去引导裸机程序User Application的运行。
- Power On Reset
一开始是POR(Power On Reset),就说板卡断电然后上电之后呢,先是板子的供电电源有一个启动顺序,然后会拉高这个复位信号,相当于结束复位。结束复位之后,所有的寄存器都恢复原来的状态。然后硬件采集这个模式的引脚(Hardware Samples Mode_Pins),这个模式的引脚就是前面所看到的MIO[2]到MIO[8]这几个与启动相关的引脚(参考视频:https://www.bilibili.com/video/BV1Et421H75m?t=525.2&p=34)。然后JTAG、IO外设、DDR等这些外设都会被Disabled除能。
然后它会做一个选择——就是PLL是否使能(前面不是讲了启动的时候有一个引脚单独控制PLL),那么它会根据这个PLL的配置来去选择是否使能PLL。那么最后就是BootROM的一个执行了。这个是从上电启动到这个BootROM执行的过程啊(上图左半侧部分)
需要说明的是,除了通过断电上电进行复位,还有一个硬复位(PS_POR_B)和软复位(PS_SRST_B)。硬复位(PS_POR_B)和断电上电的复位过程类似,软复位(PS_SRST_B)则有点不同。软复位(PS_SRST_B)是复位大部分的寄存器,但是有部分寄存器是没有复位的,然后它有一个安全模式和非安全模式,这个主要与程序有没有做加密有关,我们学习的时候一般是没有做加密的,可以先不用管它,最后进入到BootROM。
- Stage0:BootROM
BootROM是固化在ZYNQ芯片内部的一个程序,是APU运行的第一个程序。BootROM的主要任务是配置系统,将FSBL代码从
- Stage1:FSBL
下面就进入到一个FSBL的一个一个阶段了,这个FSBL它是可以由我们程序员创建的,但Xilinx官方提供了一个标志的FSBL程序的模板,大部分情况下可以不用对它做任何修改。那么它所实现的功能包括:
- User Application
最后一个阶段就是用户的应用程序了。
注意:
这里所说的启动过程主要是针对裸机程序的,那如果是Linux的一个启动过程,肯定是有差异的。
7.2 程序固化实验
7.2.1 实验任务
本章的实验任务是在“AXIGPIO按键控制LED实验”的基础上实现程序上电自启动(程序固化),包括从SD卡启动和从QSPIFlash启动两种方式。
7.2.2 固化实验实操
请直接参考视频吧:实操:ZYNQ开发板固化程序 - 正点原子 - Bilibili。
毕竟只是一种固化程序的操作。
参考链接1:ZYNQ学习笔记 程序固化
参考链接2:ZYNQ启动过程
参考链接3:详解zynq的启动和配置过程





















































































