在MPSOC开发板上搭建MPSOC嵌入式最小系统,并使用串口打印“Hello World” 信息。通过本次实验我们将了解 MPSOC 嵌入式系统的开发流程,熟悉 MPSOC 嵌入式最小系统的搭建。
如上图所示,开发流程大体可以分为 6 步。其中 step1 至 step4 为硬件设计部分,在 Vivado 软件中实现; step5 为软件设计部分,在 Vitis 软件中实现;step6 为功能的验证。复杂的程序还涉及 Debug,这个也是在 Vitis 软件中实施。
嵌入式最小系统的概念包括以下两个方面:一、它是使系统正常工作的最小条件;二、它是其他系统建立的基础。
以 ARM Cortex 为核心、DDR 为内存,加上传输信息使用的 UART 串口就构成了 MPSOC 嵌入式最小系统。可以看到,这个最小系统只包括了 MPSOC 中的 PS 部分。
注意project type界面选择rtl project并且“Do not specify sources at this time”;器件型号选择“xczu2eg-sfvc784-2-i”;
在左侧导航栏(Flow Navigator)中,单击 IP Integrator 下的 Create Block Design;接下来在 Diagram 窗口中给设计添加 IP。点击上图中箭头所指示的加号(两个任选一个)“+”,会 打开 IP 目录(IP Catalog)。也可以通过快捷键 Ctrl + I,或者右键点击 Diagram 工作区中的空白位置,然后选择“ADD IP”;打开 IP 目录后,在搜索栏中键入“ZYNQ”,找到并双击“Zynq UltraScale+MPSOC”,将 Zynq UltraScale+MPSOC IP 添加到设计中。
双击所添加的 Zynq UltraScale+MPSOC 模块,进入处理系统的配置界面。界面左侧为页面导航面板, 右侧为配置信息面板。
下面我们简要地介绍一下页面导航面板中各个页面的作用:
PS UltraScale+ Block Design 页面显示了 zynq 硬核的整体架构图,其中绿色部分是可配置模块,可以点 击进入相应的编辑界面进行配置,当然也可以在左侧导航栏选择相应的编辑界面。
I/O Configuration 页面可以选择不同的 I/O 外设并进行相应的配置。
Clock Configuration 页面分为 Input Clocks 和 Output Clocks 两个标签页,用来配置 PS 输入时钟、外设 时钟,以及 DDR 和 CPU 时钟等。
DDR Configuration 页面用于设置 DDR 控制器配置信息。
PS-PL Configuration 用于 PS 和 PL 交互的相关配置,包括常用的中断、复位信号和数据接口。
点击导航面板中 I/O Configuration,PS 和外部设备之间的连接主要是通过复用的输入/输出(Multiplexed Input/Output,MIO)来实现的。BANK501 中的 MIO42 和 MIO43 被用作 UART 串口通信的引脚,并最终与开发板上的 USB 转串口芯片 CH340 连接。因此,为了实现串口 通信的功能,我们需要在 PS 中将 MIO42 和 MIO43 配置成 UART0 模块的接口引脚。
点击左侧 Clock Configuration 打开时钟配置页面,该界面主要是配置 MPSOC PS 中的时钟频率。比如 输入时钟默认是 33.33333Mhz,这与我们开发板上的 PS 端输入时钟频率相同。对于 CPU 的时钟、DDR 的 时钟以及其它外设的时钟,我们直接保持默认设置即可。
因为本实验是搭建 MPSOC 的嵌入式最小系统,只需要使用 MPSOC 中的 PS 端。因此我们将 PS 中与 PL 端交互的接口信号移除。
返回到 Vivado 界面后,在 Diagram 中可以看到 ZYNQ UltraScale+ MPSOC IP 模块变化,该模块少了四组接口,这正是因为我们在配置该 IP 核的过程中移除了与 PL 相关的接口信号。
点击下图中箭头所指示的按钮“validate design”,对我们配置的 IP 核进行验证
在对话框中,Synthesis Options 选择 Out of context per IP,这里我们保持默认;Run Setings 用于设置生 成过程中要使用的处理器的线程数,进行多线程处理,保持默认或设置为个人电脑处理器最大可使用线程 数都可以,一般选择最大可使用线程数。然后点击“Generate”来生成设计的综合、实现和仿真文件。
在“Generate”过程中会为设计生成所有需要的输出结果。比如 Vivado 工具会自动生成处理系统的 XDC 约束文件,因此我们不需要手动对 MPSOC PS 引出的接口(DDR 和 FIXED_IO)进行管脚分配。
Generate 完成后,在弹出的对话框中点击“OK”。
在 Sources 窗口中,点击“IP Source”标签页,可以看到 Generate 过程生成的输出结果。
在“Hierarchy”标签页再次右键点击 system.bd,然后选择“Create HDL Wrapper”。
在弹出的对话框中确认勾选“Let Vivado manage wrapper and auto-update”,然后点击“OK”。
design_1_wrapper.v 为创建的 Verilog 文件,箭头所指的“品”字形图标指示当前模块为顶层模块。该模 块使用 Verilog HDL 对设计进行封装,主要完成了对 block design 的例化,大家也可以双击打开该文件查看 其中的内容。
另外我们勾选了“Let Vivado manage wrapper and auto-update”,这样我们在修改了 Block Design 之后就不需要再重新生成顶层模块,Vivado 工具会自动更新该文件。
如果设计中使用了 PL 的资源,则需要添加引脚约束并对该设计进行综合、实现并生成 Bitstream 文件。 本次实验未用到 PL 部分,所以无需生成 Bitstream 文件,只需将硬件导出。
在弹出的对话框中,因为没有生成 bitstream 文件,所以无需勾选“Include bitstream”,直接点击“OK” 按钮。
上图中,XSA file name 一栏是产生的硬件信息文件的文件名,这里我们保持默认。Export to 后面的路 径是生成的包含硬件信息文件的路径,生成的文件如下所示:
新建 vitis 文件夹,将xsa文件移入备用,后续作为 vitis 软件开发存储路径。
在菜单栏中选择 Tools > Launch Vitis,启动 Vitis 开发环境。如下图所示:
在弹出的对话框中,我们将工程路径指定到新建的 vitis 文件夹下,如下图所示:
到这里,我们已经完成了 MPSOC 嵌入式系统的硬件设计部分。接下来需要到 Vitis 软件中进行应用程 序开发,也就是软件设计部分。
在菜单栏选择 File > New > Application Project, 新建一个 vitis 应用工程
在弹出的对话框中,输入工程名“hello_world”,其它选项保持默认,点击“Next”,如下图 所示:
打开 Create a new platform from hardware(XSA)标签页,点击“+”添加 xsa 文件,如下图所示:
添加之前生成的xca文件,添加 xsa 文件后后点击 next。
在弹出的页面中有一个 Generate boot components 选项,如果勾选,软件会自动生成 fsbl 工程,这 里我们选择默认勾选,然后点击 next,如下图所示:
在弹出的工程模板选择页面里,我们选择已有的 Hello World 模板,然后点击 Finish
工程建立完成后的页面如下图所示,我看可以看到生成了两个工程,一个是硬件平台工程,即 platform 工程,一个是应用工程。
双击打开 hello_world/src 工程目录下 helloworld.c 文件,可以看到源代码。
可以看到程序中主函数调用了 3 个函数,分别是 init_platform()、cleanup_platform()和 print()函数。我们将鼠标停留在各个函数名上,vitis 就会显示该函数的声明。如果想查看函数的定义,可以按住 Ctrl 键不放, 用鼠标点击相应的函数,就会跳转到其定义的地方。
可以看到 init_platform 函数的作用是使能 caches 和初始化 uart;cleanup_platform 函数的作用是取消使 能 caches。实际上这两个函数在该工程中并没有启动任何作用,因为这两个函数是针对于特定平台如 Microblaze 的,对于我们使用的 MPSOC 平台而言是不起作用的,所以 main 函数中只需包含第 9 行的 print 语句就可以了,出于平台的通用性和可移植性,此处我们保留这两个函数。
另外需要注意程序中打印字符串“Hello World”使用的是 print()函数,而不是 C 语言里的 printf()函数。 print()函数是 Xilinx 定义的一个用于打印字符串的函数,调用该函数需要包含头文件“xil_printf.h”。
选中应用工程,右键 Build Project 对工程进行编译。
编译进度可以在工具下方的控制台面板(Console)中进行查看,编译完成后显示“Finished building: hello_world.elf”,如下图所示:
首先我们将下载器与 MPSOC 开发板上的 JTAG 接口连接,下载器另外一端与电脑连接。使用 USB 连接线将开发板 USB_UART (PS_PORT)接口与电脑连接,用于串口通信。接下来将开发板上四个启动模式开关均置为 ON,即设置为 JTAG 模式。最后连接开发板电源给开发板上电。
注意第一次连接开发板 USB_UART 接口时,需要安装 CH340 驱动(USB 串口驱动)。
在 Vitis 软件的下方,找到 Terminal 窗口。如果界面中没有找到该窗口,或者操作过程中把该窗口 给关闭了,则可以通过在菜单栏中选择 Window > Show View > Other,在 Show View窗口中搜索添加 Terminal。
添加 Terminal 后如下图所示,点击箭头处的图标对串口进行设置。
在弹出的窗口中,Choose terminal 一栏中,下拉选择 Serial Terminal 串口终端,选择串口终端后,接下来需要对串口设置。这里设置波特率为 “115200”,数据位为 8 位,停止位为 1 位,然后点击 OK,如下图所示:
配置完成,连接成功后如下图所示:
右键 hello_world 工程,选择“Run As”,选择“Run Confagurations…”, 下载程序:
在打开的下载页面中,没有出现下载选项,这时需要双击左侧列表中 Single Application Debug 一项,双击后,该项下面出现新的项 Debugger_hello_world-default,同时在右侧出现的页面中选择 Target Setup 标签 页,勾选复位,然后点击 run 下载程序,如下图所示:
下载完成后,应用程序会将字符串“Hello World”通过 MPSOC PS 端的串口模块发送出去。在 Terminal 窗口可以看到上位机接收到的字符串,如下图所示:
GPIO 可以通过 MIO 连接到 PS 端的引脚,也可以通过 EMIO 连接到 PL。本章将介绍如何使用 GPIO 外设通过 MIO 控制 PS 端的 LED。
MPSOC 分为 PS 和 PL 两部分,那么器件的引脚(Pin)资源同样也分成了两部分。MPSOC PS 中的外设可以通过 MIO(Multiuse I/O,多用输入/输出)模块连接到 PS 端的引脚上,也可以通过 EMIO 连接到 PL 端的引脚。MPSOC 系列芯片一般有 78 个 MIO。
图是 GPIO 的框图,从中我们可以看到 GPIO 分为 6 个 Bank,其中 Bank0、Bank1 和 Bank2 连接到 MIO;而 Bank3、Bank4 和 Bank5 连接到 EMIO。Bank0、Bank1 和 Bank2 分别有 26bit,总共 78bit,也就是说有 78 个 MIO。Bank3、Bank4 和 Bank5 分 别有 32bit,也就是说 PS 端可以使用 96 个 EMIO。
PS 所有的外设都可以通过 MIO 访问,这些外设也是与 MIO 进行连接,每个 MIO 虽然可以独立控制, 以及独立驱动单个引脚的外设,但对于 QSPI、USB、以太网等这些外设,对于 MIO 的连接有着特殊的要求, 如图 2.1.2 所示,对于以太网而言,要与 MIO26~37、MIO38~49、MIO52~63 和 MIO64~75 引脚连接,而且 以太网与 MIO26 连接的引脚只能作为以太网的 tx_clk 使用,可见当其作为以太网的接口引脚时,相应的 MIO 的功能就已经确定下来了。从图中 MIO 一览表中我们可以看到 MIO 一但选定,引脚位置就已经确定下来了,不需要添加引脚约束。
MIO 与 PS 是如何连接的? 图展示 PS 的 IO 外设。PS 外设的大多数 I/O 信号可以通过 MIO 路由到 PS 引脚,或通过 EMIO 路由到 PL 引脚。
这里我们重点介绍外设系统图中箭头所指的部分。PS 通过 APB 总线对控制、状态寄存器的读写实现对 GPIO 的驱动,具体可以参见下图。
左边的一列是寄存器,上半部分是关于中断的,这部分我们在涉及到中断的时候会讲解,这里 我们重点介绍下红色框圈出的下半部分。
DATA_RO 是数据只读寄存器,通过该寄存器能够观察器件引脚上的值。如果 GPIO 信号配置为输出, 则通常会反映输出上驱动的值,写入此寄存器将被忽略。
DATA 是数据寄存器,该寄存器控制 GPIO 信号配置为输出时要输出的值。该寄存器的所有 32 位都是一次写入的。读取该寄存器返回写入 DATA 或 MASK_DATA_ {LSW,MSW}的先前值,它不会返回器件引脚上的当前值。
MASK_DATA_LSW 和 MASK_DATA_MSW 是数据掩码寄存器,该寄存器使软件能够有选择地一次更改所需的输出值。可以写入最多 16 位的任意组合,MASK_DATA_LSW 控制 Bank 的低 16 位, MASK_DATA_MSW 控制高 16 位。未写入的那些位保持不变并保持其先前的值。读取该寄存器返回写入 DATA 或 MASK_DATA_ {LSW,MSW}的先前值;它不会返回器件引脚上的当前值。该寄存器避免了对未更改位的读-修改-写序列的需要。 DIRM 是方向模式寄存器,用于控制 I/O 引脚是用作输入还是输出。当 DIRM [x] == 0 时,输出驱动器被禁用,该引脚作为输入引脚使用。 OEN 是使能输出寄存器。将 I/O 配置为输出时,该寄存器控制是否启用输出。禁用输出时,引脚为 3 态。当 OEN [x] == 0 时,输出被禁用。
从这些寄存器中我们可以看到,如果配置引脚为输出,不仅需要设置方向,还要使能输出。关于这些寄存器的具体介绍,可参考 ug1085 手册。需要说明的是我们在程序中操作 MIO 时直接调用 Xilinx 官方提供的函数即可,无需直接操作这些寄存器。
另外需要说明的是 MIO 信号对 PL 部分是不可用的,所以对 MIO 的操作是纯 PS 的操作,且每个 GPIO 都可独立动态编程为输入、输出或中断检测。
从上面看出,Zynq 的 PS 部分就像 stm32 一样,本质都是配置外设的寄存器,利用开发商或者其他提供的友好接口的库函数对寄存器进行配置开发。 stm32 使用 ARM公司提供的cortex-M3内核,通过下载器将机器码下载至存储器代码段。
此时再回过头来看 stm32 的 SoC 架构和总线系统相比 Zynq 的也容易看了许多,之后再看stm32 的数据手册也多了一些理解。还有之后的 stm32 的学习,可以使用 keil5 的调试功能,检查各个寄存器的值和执行的汇编语言,从而更深层面了解微机原理和 CPU 的运作。
本章的实验任务是使用 GPIO 通过 MIO 控制 PS 端 LED 的亮灭,实现 LED 闪烁的效果。
从实验任务我们可以画出如下的系统框图,DDR4 中存放和运行程序、UART 打印信息、MIO 驱动 LED 外设。虽然本实验可以不需要 UART,不过为了方便打印一些信息,此处我们加上 UART。
注意养成绘制系统框图的习惯,帮助理解系统架构
此处介绍如何在先前工程的基础上继续实验而不破坏先前的工程。
先打开《第一章 Hello World》实验的 Vivado 工程,打开后选择菜单栏的 File-> Project->Save As...。在弹出的另存为界面中可以输入新的工程名或更改保存位置,此处我们输入新的工程名“gpio_mio”, 工程位置保持默认即可,然后取消勾选 Include run results,最后点击“OK”。(注意:文件所在路径不能过长,最多280个字符,如果保存失败修改存储路径;另外之前的“Creat_Block_Design”之所以选择默认的“design_1”而不是特定的“hello_world”是因为这个名字设定之后便不可修改,另存之后仍是之前的名字)
在 Flow Navigator 中,点击 IP INTEGRATOR 下的 Open Block Design,在打开的下图 Diagram 窗口,双击打开 Zynq UltraScale+ MPSOC 重定义窗口。
在下图所示的重定义窗口,点击左侧的 I/O Configuration,在右侧的界面中展开 Low Speed,展开 I/O Peripherals,展开 GPIO,然后勾选 GPIO0 MIO 和 GPIO1 MIO。另外开发板上的 Bank0 即原理图中的 BANK500 为 1.8V,所以我们将 5 处的 Bank0、6 处的 bank2 电压设置为 LVCOMS 1.8V,最后点击 OK。
实际用到的 GPIO_MIO 与原理图相关。为了方便大家的查找和使用,MPSOC PS 端 IO 引脚分配我们都列在了资料盘开发板原理图文件夹下的 IO 引脚分配总表中,我们摘录部分如下图
按 Ctrl+S 快捷键保存 Diagram。
在弹出的下图中,Synthesis Options 选择 Global,Run Setings 保持默认选择,然后点击 Generate。
注意与实验一区别,实验一是“Out of context per IP”,这里是“Global”。
创建顶层 HDL Wrapper 因为我们在创建 Hello World 实验时创建顶层 HDL Wrapper 使用的是下图所示的 Let Vivado manage wrapper and auto-update选项,所以此处无需再创建顶层HDL Wrapper,Vivado会自动更新顶层HDL Wrapper。 此时第三步完成。
由于本实验未用到 PL 部分,所以无需生成 Bitstream 文件,只需导出 hardware 即可。步骤和实验一相同。
基本与实验一相同。工程名改为“gpio_mio”;选择工程模版“Empty Application”,本章将自行创建工程文件,故选择空模板,然后点击“Finish” 按钮,如下图所示:
双击硬件平台目录下 platform.spr 文件,找到点击板级支持包“Board_Support_Package”,点击展开 “Peripheral Drivers”,右侧有相关文档和示例。找到 GPIO,如下图所示:
点击 Documentation 将在浏览器窗口打开 GPIO 的 API 文档,里面有关于 GPIO 的详细信息,想了解 GPIO 的,可以仔细浏览其中的信息。
导入示例。如果我们点击 Import Examples(Documentation 旁边的按钮),会弹出下图所示的导入示例界面,关于 GPIO 有两个示 例,如下图所示:
这两个示例的介绍可以在刚才打开的 API 文档中看到。在 API 文档中点击左侧的 Examples,右侧出现 这两个示例的介绍信息,如图所示: xgpiops_intr_example.c 包含有关如何直接使用 XGpiops 驱动程序的示例。此示例显示了中断模式下驱动程序的用法,并使用 GPIO 的中断功能检测按钮事件,根据输入控制 LED 输出。xgpiops_polled_example.c 同样包含有关如何直接使用 XGpiops 驱动程序的示例。此示例提供了用于读取/写入各个引脚的 API 的用法。
们因为本实验暂未使用到中断,所以应该选择 xgpiops_polled_example 示例。选择 好示例后,点击“OK”按钮。
在 Explorer 中,新增了 xgpiops_polled_example_1 目录,我们打开其 src 目录下的 xgpiops_polled_example.c 文件。
显示行数。此处我们说一下如何显示代码的行数,在下图所示的 1 处箭头所指的上或下方点击鼠标右键,在弹出的菜单中选择 2 处的 Show Line Numbers,就会显示代码的行数。
xgpiops_polled_example.c 文件有四个函数,其中 GpioInputExample 函数由于我们本实验只用 MIO 输出所以未用到。该文件代码虽然是为特定开发板使用的,不过我们稍作修改也可以拿来使用。有两个 LED 分别接到 PS 的 MIO38 和 MIO39,这里我们使用 PS_LED1,即连接 MIO38。 我们修改该文件第 193 行的 Output_Pin 为 38,保存该文件,然后编译,编译完成后下载到开发板会看到板 上的 LED1 灯闪烁,闪烁时间约为 2 秒,随后 LED 灯熄灭。
现在我们自己动手写一个驱动 MIO 的代码。
新建源文件。首先我们在 gpio_mio/src 目录上右键,选择 New-> File,如下图所示:
在添加源文件界面中,File name 一栏我们输入文件名“main.c”,然后点击“Finish”按钮。输入源代码。我们在新建的 main.c 文件中输入以下代码:
#include "xparameters.h" //器件参数信息
#include "xstatus.h" //包含 XST_FAILURE 和 XST_SUCCESS 的宏定义
#include "xil_printf.h" //包含 print()函数
#include "xgpiops.h" //包含 PS GPIO 的函数
#include "sleep.h" //包含 sleep()函数
//宏定义 GPIO_DEVICE_ID
#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
//连接到 MIO 的 LED
#define MIOLED0 38 //连接到 MIO38
#define MIOLED1 39 //连接到 MIO39
XGpioPs Gpio; // GPIO 设备的驱动程序实例
int main()
{
int Status;
XGpioPs_Config *ConfigPtr;
print("MIO Test! \n\r");
ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
Status = XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr);
if (Status != XST_SUCCESS){
return XST_FAILURE;
}
//设置指定引脚的方向:0 输入,1 输出
XGpioPs_SetDirectionPin(&Gpio, MIOLED0, 1);
XGpioPs_SetDirectionPin(&Gpio, MIOLED1, 1);
//使能指定引脚输出:0 禁止输出使能,1 使能输出
XGpioPs_SetOutputEnablePin(&Gpio, MIOLED0, 1);
XGpioPs_SetOutputEnablePin(&Gpio, MIOLED1, 1);
while (1) {
XGpioPs_WritePin(&Gpio, MIOLED0, 0x0); //向指定引脚写入数据:0 或 1
XGpioPs_WritePin(&Gpio, MIOLED1, 0x0);
sleep(1); //延时 1 秒
XGpioPs_WritePin(&Gpio, MIOLED0, 0x1);
XGpioPs_WritePin(&Gpio, MIOLED1, 0x1);
sleep(1);
}
return XST_SUCCESS;
}
该代码实现了 LED 灯每隔 1 秒闪一次的功能。
代码第 8 行我们宏定义了 GPIO_DEVICE_ID,使其为 XPAR_XGPIOPS_0_DEVICE_ID,如果在 Vitis 软件中,按住 Ctrl 键不放,将鼠标移动到 XPAR_XGPIOPS_0_DEVICE_ID 上,当鼠标变成手指状时,单击 鼠标左键,会自动跳转到 xparameters.h 文件中,该文件定义了各个外设的基地址、器件 ID、中断等,我们 这里重新宏定义 XPAR_XGPIOPS_0_DEVICE_ID 是为了以后方便修改。
代码第 10 行宏定义了 MIOLED0,其值为 38,因为其连接到 PS 的 MIO38 引脚。一般对于这种 MIO 的 使用,驱动某一引脚,在代码中使用该引脚对应的 MIO 数字标号即可。
代码第 21 行至 26 行是获取 GPIO 的 ID 和基址信息并初始化其配置,以及判断是否初始化成功。代码第 28 行的 XGpioPs_SetDirectionPin 和 31 行 XGpioPs_SetOutputEnablePin 函数分别是设置 GPIO 的方向(输 入还是输出)函数和使能输出函数,代码第 35 行的 XGpioPs_WritePin 是向指定 GPIO 引脚写入数据的函数, 关于这三个函数的具体使用可以查看其定义。查看其定义的简便方法是在 VITIS 软件中,按住 Ctrl 键不放, 将鼠标移动到想查看定义的函数名上,当鼠标变成手指状时,单击鼠标左键,即可跳转到定义或声明的地方。
代码第 37 和第 40 行的 sleep 函数为秒延时函数,延时 m 秒就使用 sleep(m)语句。还有一个微秒延时函数 usleep(m),延时 m 微秒。
编译工程。保存 main.c 文件,右键点击应用工程 gpio_mio,在弹出的菜单中选择 Build Project, 如下图所示:
编译完成后,生成 elf 文件,
PS 和外部设备之间的通信主要是通过复用的输入/输出(Multiplexed Input/Output,MIO)实现的。除此之外,PS 还可以通过扩展的 MIO(Extended MIO,EMIO)来实现与外部设备的连接。EMIO 使用了 PL 的 I/O 资源,当 PS 需要扩展超过 78 个引脚的时候可以用 EMIO,也可以用它来连接 PL 中实现的 IP 模块。
在大多数情况下,PS 端经由 EMIO 引出的接口会直接连接到 PL 端的器件引脚上,通过 IO 管脚约束来指定所连接 PL 引脚的位置。通过这种方式,EMIO 可以为 PS 端实现额外的 96 个输入引脚或 96 个带有输出使能的输出引脚。EMIO 还有一种使用方式,就是用于连接 PL 内实现的功能模块(IP 核),此时 PL 端 的 IP 作为 PS 端的一个外部设备。
本章的实验任务是使用 MPSOC 开发板上的两个 PS 端按键控制 PL 端 LED 亮灭,两个 PL 端按键去控 制 PS 端 LED 的亮灭。
在配置界面中,点击左侧的 I/O Configuration。然后在右侧展开 GPIO 一栏,勾选 GPIO EMIO,并设置位宽为 4。该设置将通过 EMIO 扩展一个 4 位的 GPIO 接口信号,此信号将用于连接 PL 端的引脚。注意这里 GPIO0 和 GPIO1 已经勾选。
完成配置后,点击右下角的“OK”按钮。然后在 Diagram 窗口中可以看到 Zynq UltraScale+ MPSoC 多 了一个 GPIO_0 端口,如下图所示:
将光标移动到上图中箭头所指示的位置,会发现光标变成了铅笔的样式。点击选中该端口,然后点击 鼠标右键,在弹出的列表中选择“Make External”。点击选中该接口,在左侧 External Interface Properties 一栏中将该接口的名称修改为 GPIO_EMIO。如下 图所示:
在 Sources 窗口中展开 Design Sources,然后右键点击 design_1_wrapper 下的 design_1.bd,在弹出 的菜单中选择 Generate Output Products(在之前的实验中,我们创建顶层模块时选择了“Let Vivado manage wrapper and auto-update”选项,所以 此处无需再创建顶层 HDL Wrapper,Vivado 会自动更新顶层 HDL Wrapper。)
在左侧 Flow Navigator 导航栏中找到 RTL ANALYSIS,点击该选项中的“Open Elaborated Design”。
注意:如果出现闪退Vivado RTL 闪退问题的解决办法
在 ELABORATED DESIGN 界面下方找到 I/O Ports 窗口。如果没有找到 I/O Ports 一栏则通过在菜单栏 中点击 Layout,然后在下拉列表中选择 I/O Planning。我们将在 I/O Ports 窗口中对 PL 部分的接口进行管 脚分配。PS 端的管脚约束文件,在图 3.3.12 中选择“Generate Output Products”之后,Vivado 工具会自动创建。
在本次实验中,EMIO 扩展了四个 GPIO 的接口信号,即上图中的 GPIO_EMIO_tri_io[0], GPIO_EMIO_tri_io[1],GPIO_EMIO_tri_io[2],GPIO_EMIO_tri_io[3]。这里,我们将 GPIO_EMIO_tri_io[0] 接到 PL_KEY1 引脚,GPIO_EMIO_tri_io[1]接到 PL_KEY2 引脚,GPIO_EMIO_tri_io[2]接到 PL_LED1 引脚, GPIO_EMIO_tri_io[3]接到 PL_LED2 引脚。 查看原理图可知,这四个引脚的管脚约束分别是,PL_KEY1 为 AD11,PL_KEY2 为 AD10,PL_LED1 为 AE10,PL_LED2 为 AF10,且都在 BANK44 上,该 BANK 电压为 3.3V。接下来在软件中进行管脚分配, I/O Std 一列对应的电平也需要修改。
设置完成后按快捷 Ctrl+S 保存管脚约束,在弹出的对话框输入文件名“pin”,最后点击“OK”。
在左侧Flow Navigator 导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”, 然后在连续弹出的对话框中依次点击“YES”、“OK”。此时,Vivado 工具开始对设计进行综合、实现、并生 成 Bitstream 文件。生成 Bitstream 完成后,在弹出的对话框中选择“Open Implemented Design”。点击“OK”,如果弹出对话框提示关闭 Elaborated Design,则点击“YES”。 在 IMPLEMENTED DESIGN 界面我们可以查看设计对 PL 资源的使用情况。在左侧 Flow Navigator 导 航栏中找到 IMPLEMENTATION,点击该选项中的“Report Utilization”,然后在弹出的对话框中点击“OK”。
在界面下方的 Utilization 标签页中,选择左侧的 Summary,然后在右侧会以表格和柱状图两种方式显 示当前 PL 资源的使用情况。在我们本次实验中,只消耗了 PL 端 4 个 LUT 和 4 个 IO 资源,这个 IO 就是 PS 通过 EMIO 扩展 GPIO 接口信号时所使用的 PL 引脚。如下图所示:
在菜单栏中选择 File > Export > Export hardware。 在弹出的对话框中,勾选“Include bitstream”,然后点击“OK”按钮。在此处需要注意,如果我们的设计使用了 PL 的资源,比如使用了 PL 的引脚,或者在 PL 内实现了部 分功能模块,那么我们就需要生成 Bitstream 文件,并在导出硬件的时候包含该文件。
#include "stdio.h"
#include "xparameters.h"
#include "xgpiops.h"
#define GPIOPS_ID XPAR_XGPIOPS_0_DEVICE_ID //PS 端 GPIO 器件 ID
#define MIO_LED1 38 //PS_LED1 连接到 MIO38
#define MIO_LED2 39 //PS_LED2 连接到 MIO39
#define MIO_KEY1 40 //PS_KEY1 连接到 MIO40
#define MIO_KEY2 41 //PS_LEY2 连接到 MIO41
#define EMIO_KEY1 78 //PL_KEY1 连接到 EMIO0
#define EMIO_KEY2 79 //PL_KEY2 连接到 EMIO1
#define EMIO_LED1 80 //PL_LED1 连接到 EMIO2
#define EMIO_LED2 81 //PL_LED2 连接到 EMIO3
int main()
{
printf("EMIO TEST!\n");
XGpioPs gpiops_inst; //PS 端 GPIO 驱动实例
XGpioPs_Config *gpiops_cfg_ptr; //PS 端 GPIO 配置信息
//根据器件 ID 查找配置信息
gpiops_cfg_ptr = XGpioPs_LookupConfig(GPIOPS_ID);
//初始化器件驱动
XGpioPs_CfgInitialize(&gpiops_inst,gpiops_cfg_ptr,gpiops_cfg_ptr->BaseAddr);
//设置 LED 为输出
XGpioPs_SetDirectionPin(&gpiops_inst, MIO_LED1, 1);
XGpioPs_SetDirectionPin(&gpiops_inst, MIO_LED2, 1);
XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_LED1, 1);
XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_LED2, 1);
//使能 LED 输出
XGpioPs_SetOutputEnablePin(&gpiops_inst, MIO_LED1, 1);
XGpioPs_SetOutputEnablePin(&gpiops_inst, MIO_LED2, 1);
XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_LED1, 1);
XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_LED2, 1);
//设置 KEY 为输入
XGpioPs_SetDirectionPin(&gpiops_inst, MIO_KEY1, 0);
XGpioPs_SetDirectionPin(&gpiops_inst, MIO_KEY2, 0);
XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_KEY1, 0);
XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_KEY2, 0);
//读取按键状态,控制 LED 亮灭
while(1){
XGpioPs_WritePin(&gpiops_inst, MIO_LED1,
~XGpioPs_ReadPin(&gpiops_inst, EMIO_KEY1));
XGpioPs_WritePin(&gpiops_inst, MIO_LED2,
~XGpioPs_ReadPin(&gpiops_inst, EMIO_KEY2));
XGpioPs_WritePin(&gpiops_inst, EMIO_LED1,
~XGpioPs_ReadPin(&gpiops_inst, MIO_KEY1));
XGpioPs_WritePin(&gpiops_inst, EMIO_LED2,
~XGpioPs_ReadPin(&gpiops_inst, MIO_KEY2));
}
return 0;
}
在代码的第 7 至 10 行,我们指定了 PS 端输出 LED 和输入 KEY 的 MIO 编号,这些编号可以从 MPSOC 开 发板的原理图中查到。在代码的 11 至 14 行指定了 PL 端 LED 和按键 KEY 的 EMIO 编号。
在本章的简介部分我们提到过,MPSOC 的 GPIO 被分成了 6 组,其中通过 EMIO 扩展的 GPIO 接口位于 BANK3 至 BANK5 中。在本次实验中我们通过 EMIO 扩展了 4 个 GPIO 信号,即 BANK3 的 EMIO0,EMIO1,EMIO2,EMIO3,由于 GPIO 的 BANK0,BANK1,BANK2 分别有 26 个信号,即 MIO 共有 78 个信号,所以 BANK3 的 EMIO0 是第 79 个信号,编号为 78(从 0 开始编号)。
我们按住 Ctrl 键,然后点击代码开头处所引用的头文件“xgpiops.h”以打开该文件。在 xgpiops.h 文件第 162 行给出了 MPSOC 器件 GPIO 最大的引脚数目,共 174 个,分别位于 6 个 Bank 中。在下面的注释中则分别列出了各 Bank 的引脚编号范围,同样可以看到 Bank3 的第一个引脚编号为 78。