摘要
在 Zynq 平台的裸机开发中,软件程序并不是凭空运行起来的,而是依赖一个已经配置好的硬件平台。对于初学者来说,真正进入 ARM 裸机实验的第一步,并不是马上写 C 程序,而是先在 Vivado 中创建包含 Zynq PS 的硬件系统,再导出硬件平台到 SDK 中编写和下载程序。本文结合 GPIO 点亮 LED 的入门实验,介绍使用 Vivado 创建 PS 应用系统的基本流程,以及在 SDK 中创建裸机工程并编写 LED 控制程序的方法,帮助初学者建立对 Zynq 裸机开发完整流程的初步认识。
关键词:Zynq;Vivado;SDK;ARM裸机;GPIO;LED;PS系统
文章目录
- 摘要
-
- 一、前言
- 二、实验目的与课程定位
- 三、实验内容概述
- [四、为什么在写程序之前要先创建 PS 应用系统](#四、为什么在写程序之前要先创建 PS 应用系统)
- [六、创建包含 Zynq PS 的系统](#六、创建包含 Zynq PS 的系统)
- [七、PS 系统中与本实验相关的关键配置](#七、PS 系统中与本实验相关的关键配置)
- [十、导出硬件平台并启动 SDK](#十、导出硬件平台并启动 SDK)
- [十一、SDK 是做什么的](#十一、SDK 是做什么的)
- 十二、创建裸机应用工程
- [十三、添加 main.c 并编写 LED 控制程序](#十三、添加 main.c 并编写 LED 控制程序)
- 十四、程序运行与调试
- 常见问题分析
一、前言
在嵌入式学习中,点亮一个 LED 灯几乎是所有入门实验的"第一课"。
它就像编程语言中的 Hello World 一样,虽然功能简单,但却能完整体现一个系统从创建工程、配置硬件、编写程序到下载调试的基本开发流程。
对于 Zynq 平台而言,这个实验看起来只是"让 LED 闪烁",但背后实际上涉及两部分内容:
一部分是硬件平台的建立,也就是在 Vivado 中创建一个包含 Zynq PS 的系统;
另一部分是软件程序的编写与运行,也就是在 SDK 中创建裸机应用工程,通过 GPIO 控制 LED 的亮灭。
因此,这个实验的重点并不只是"点亮灯"本身,而是帮助学习者理解:
- Zynq 裸机开发的基本步骤是什么;
- Vivado 和 SDK 在整个开发流程中分别承担什么作用;
- 为什么软件开发之前,必须先完成硬件平台配置;
- ARM 程序是如何通过 GPIO 控制开发板上的 LED 的。
本文就以"使用 GPIO 点亮 LED"为例,介绍 Zynq 裸机开发实验中的第一个完整实践流程。
二、实验目的与课程定位
本实验是嵌入式裸机程序设计开发实验系列中的第一个实验,属于典型的入门级内容。
从表面上看,它只是实现一个非常简单的现象:
让开发板上的 LED 灯周期性亮灭。
但从教学意义上看,这个实验承担着非常重要的作用:
- 帮助初学者建立对 Zynq 开发流程的整体认识;
- 让学习者第一次接触 Vivado 工程创建与 PS 系统配置;
- 让学习者理解 SDK 中裸机应用工程的创建方式;
- 初步认识 GPIO 寄存器操作和硬件控制的基本思想。
也就是说,这个实验的重点不是深入讲解 GPIO 的全部原理,而是通过一个最简单的目标,把**"硬件平台 + 软件程序 + 板级运行"**这一整套流程串起来。
所以可以把它理解为:
bash
这不是一个"难点实验",而是一个"开门实验"。
三、实验内容概述
本实验主要包括四个阶段:
- 使用 Vivado 创建工程
在 Vivado 中新建工程,选择目标芯片,并进入 IP Integrator 设计环境。
- 创建包含 Zynq PS 的系统
在 Block Design 中加入 Zynq PS 硬核,对 MIO、GPIO、DDR 等参数进行配置,生成系统设计文件。
- 导出硬件平台并启动 SDK
将 Vivado 中生成的硬件设计导出为 HDF 文件,并在 SDK 中基于该硬件平台创建裸机应用工程。
- 编写 C 程序控制 GPIO 点亮 LED
通过 C 语言编写 PS 端 GPIO 控制程序,配置 MIO7 引脚输出高低电平,实现 LED 灯闪烁。
整个流程看起来步骤较多,但本质上非常清晰:
bash
Vivado 负责"搭平台",SDK 负责"写程序"。
四、为什么在写程序之前要先创建 PS 应用系统
很多初学者一开始会疑惑:
"我只是想写一个 C 程序点亮 LED,为什么还要先在 Vivado 里配置这么多内容?"
原因在于,Zynq 不是普通单片机,它是一个 SoC FPGA 平台。
其中的 ARM 处理器系统虽然是芯片内部自带的,但并不意味着通电后就已经自动具备了所有可用配置。
例如,在本实验中,ARM 想控制板上的 LED,就必须先明确以下内容:
- 哪个 MIO 引脚连接到了 LED;
- GPIO 功能是否已经使能;
- DDR 是否已经正确配置,供程序运行使用;
- PS 的固定引脚接口是否已经导出;
- 硬件系统信息是否已经生成给软件工程使用。
这些都属于硬件平台层面的准备工作。
如果这些配置没有完成,那么即使 C 程序写出来了,也无法正确运行。
所以在 Zynq 裸机开发中,常见流程一定是:
bash
先搭硬件平台,再写软件程序。
五、使用 Vivado 创建工程
- 启动 Vivado
本实验使用的软件版本为 Vivado 2018.3。
启动软件后,会进入主界面,在 Quick Start 区域通常可以看到几个常用选项:
- Create Project
- Open Project
- Open Example Project
这里选择 Create Project,开始创建一个新工程。
- 设置工程路径
在创建工程时,需要指定工程存放路径。这里有一个非常重要但经常被忽略的细节:
bash
工程路径中不要包含中文,也不要包含空格。
这是因为某些开发工具、脚本或编译流程对路径格式比较敏感,一旦路径中出现中文或空格,后续有可能出现莫名其妙的问题。
因此建议提前建立一个专门的工程目录,例如:
c
F:\zynq_projects\LED
这样的路径通常更稳妥。
- 设置工程名称
工程名称可以根据实验内容命名,例如:
c
LED
同时建议勾选创建工程子目录的选项,这样每个工程都有独立文件夹,方便后续管理。
对于初学者来说,这一步虽然看似只是命名,但其实养成良好的工程组织习惯非常重要。后面实验越来越多时,独立目录结构会让工程管理轻松很多。
- 选择工程类型
在工程类型选择界面,通常直接选择默认的 RTL Project。
由于当前是新工程,不需要导入已有源文件,因此可以勾选暂时不添加源文件。
这里需要注意的是:
虽然我们后面主要是做 Zynq PS 系统配置,但在 Vivado 中创建工程仍然遵循通用的工程创建流程,因此先建立一个基础工程框架是必要的。
- 选择目标芯片
这是整个工程创建过程中最关键的一步之一。
以 ACZ702 开发板为例,可能会遇到两种芯片版本:
bash
XC7Z010
XC7Z020
二者都属于 Zynq 系列,ARM 部分基本一致,主要差别在于 PL 逻辑资源规模不同。
例如,XC7Z020 的逻辑资源比 XC7Z010 更丰富。
如果实验所使用的开发板是基于 XC7Z020,那么在器件搜索框中输入对应型号进行选择即可。一般型号中还会带有封装和速度等级,例如:
bash
xc7z020clg400-2
其中:
- xc7z020 表示芯片型号;
- clg400 表示封装形式;
- -2 表示速度等级。
选择器件时,一定要与实际开发板型号保持一致,否则后续生成的硬件配置就会与实际硬件不匹配。
六、创建包含 Zynq PS 的系统
工程创建完成后,接下来就进入本实验最核心的部分:
在 IP Integrator 中创建 Block Design,并加入 Zynq PS 系统。
- 创建 Block Design
在 Vivado 左侧的 Project Manager 中,进入 IP Integrator,点击 Create Block Design。
Block Design 的名字可以自定义,一般习惯命名为:
bash
system
创建完成后,会出现一个空白的设计画布。此时系统中还没有任何模块,需要手动添加。
- 添加 Zynq PS 硬核
在空白区域添加 IP,选择 ZYNQ7 Processing System。
这里一定要理解一个关键点:
bash
添加 Zynq PS,并不是"新加了一个 ARM",而是在当前设计中"启用并配置芯片内部已经存在的 ARM 系统"。
也就是说,PS 本来就在芯片内部,Vivado 这里做的是对它进行图形化配置。
- 运行自动配置
加入 Zynq PS 之后,通常可以点击 Run Block Automation。
Vivado 会根据当前器件和板级信息,自动完成一部分基础配置,例如固定 I/O 和 DDR 接口的导出。
对于初学者来说,这一步非常有帮助,因为它能自动帮我们完成大量基础设置,减少手工连线和参数配置带来的出错概率。
七、PS 系统中与本实验相关的关键配置
在本实验中,最重要的不是把所有 PS 参数都弄懂,而是先抓住和 LED 实验直接相关的几个配置项。
- GPIO 配置
本实验的目标是控制 PS 端的 LED,因此首先要确保 GPIO 功能已经使能。
在 ACZ702 开发板上,PS 端 LED 通常连接到 MIO7 引脚。
也就是说,ARM 通过控制 MIO7 的输出电平,就能控制这颗 LED 的亮灭。
因此在 PS 配置界面中,需要确认:
- GPIO 功能已开启;
- MIO7 可作为 GPIO 使用。
- DDR 配置
虽然这个实验只是点灯,但程序本身依然需要在存储器中运行。
在 Zynq 系统中,裸机程序通常会放到 DDR 中执行,因此 DDR 参数必须配置正确。
这一点对初学者来说很容易忽略。
很多人会觉得"LED 实验那么简单,应该不需要管 DDR 吧",但实际上:
bash
程序能否正常下载和运行,和 DDR 配置是否匹配密切相关。
如果开发板使用的是特定型号的 DDR 芯片,就要在 PS 配置中选择对应的 DDR 参数。
有时开发板上只会印一个缩写代号,而不是完整型号,这时往往需要结合板卡资料或芯片标识来确认。
- FIXED_IO 导出
Zynq PS 会涉及一组固定引脚接口,通常显示为 FIXED_IO。
这些接口承载了 PS 的固定功能连接,因此在系统中需要一起导出。
简单来说,如果只把 PS 模块放进去,却没有把相关固定接口导出,那这个 PS 系统在工程中就不算完整。
八、生成输出文件并导出顶层设计
当 PS 的配置完成后,还需要执行几步非常关键的操作,才能让硬件平台真正可用。
- 验证设计
在 Block Design 中完成配置后,可以执行 Validate Design 来检查当前设计是否存在错误或严重警告。
如果验证通过,说明当前设计从结构上是合理的,可以继续后续流程。
- 生成 Output Products
右键 Block Design,选择 Generate Output Products。
这一步的作用是让 Vivado 根据当前设计生成对应的中间文件和配置输出,为后续综合、实现和导出硬件平台做准备。
- 创建 HDL Wrapper
接着需要为 Block Design 创建 HDL 顶层文件,也就是常说的 Create HDL Wrapper。
生成 Wrapper 后,Vivado 会自动创建一个顶层 HDL 文件,用来实例化整个 Block Design。
从工程结构上看,这一步意味着你的系统设计已经有了可综合的顶层入口。
可以把它理解为:
bash
Block Design 是图形化系统原型,HDL Wrapper 是它对应的顶层封装。
九、为什么还要生成 Bitstream
很多初学者会问:
bash
"这个实验只用到了 PS 端 LED,又没有写任何 PL 逻辑,为什么还要生成 Bitstream?"
这个问题很典型。
从严格意义上说,本实验主要在使用 PS 功能,PL 部分几乎没有参与具体逻辑设计。
但在 Vivado → SDK 的常规开发流程中,生成 Bitstream 并导出带 Bitstream 的硬件平台,是一种非常标准也非常稳妥的做法。
这样做的好处有两个:
- 保证整个硬件设计状态是完整一致的;
- 后续在 SDK 中运行程序时,可以顺带完成 FPGA 配置和系统初始化,避免某些环境不一致问题。
因此,即使当前实验的核心是 PS,也通常建议按照完整流程执行:
- Generate Bitstream
- Export Hardware(包含 bitstream)
- Launch SDK
这是一个非常典型的 Zynq 开发流程。
十、导出硬件平台并启动 SDK
当 Bitstream 生成完成后,就可以将当前硬件设计导出给 SDK 使用。
在旧版本工具链中,通常会导出为 HDF 文件。
这个文件非常重要,因为它记录了当前硬件平台的关键信息,例如:
- PS 系统配置;
- 地址映射信息;
- 外设资源信息;
- 时钟与接口配置;
- 与软件工程相关的硬件描述内容。
可以把 HDF 简单理解为:
bash
Vivado 生成给 SDK 使用的"硬件说明书"。
没有这个文件,SDK 就不知道当前硬件平台长什么样,自然也就无法正确创建软件工程。
导出完成后,启动 SDK,开发环境通常会自动加载该硬件平台,并生成一个类似 hw_platform 的硬件工程。
十一、SDK 是做什么的
进入 SDK 之后,就正式进入软件开发阶段了。
如果说 Vivado 负责的是"搭硬件平台",那么 SDK 负责的就是:
- 创建软件工程
- 生成 BSP
- 编写裸机 C 程序
- 编译生成 ELF 文件
- 下载程序到开发板运行
在 Zynq 裸机开发中,SDK 本质上是一个嵌入式软件开发环境。
它和普通 PC 上写 C 程序的 IDE 不同,它是围绕硬件平台来组织工程的。
因此,SDK 打开后你会发现它不只是一个代码编辑器,还会自动带出和硬件平台相关的一些工程与头文件。
十二、创建裸机应用工程
在 SDK 中,新建应用工程时,一般选择:
bash
File → New → Application Project
然后填写工程名,例如:
bash
LED
此时 SDK 会让你选择:
- 硬件平台
- 处理器
- BSP
- 应用模板
- 为什么选择 Empty Application
在很多入门实验中,教材会建议选择 Empty Application,也就是空工程。
原因很简单:
虽然 SDK 也提供了 Hello World 等模板,但有些模板依赖串口输出。如果当前硬件系统没有专门配置串口,或者暂时不想引入多余内容,那么直接用空工程最合适。
这样做的好处是:
- 工程结构更干净;
- 代码全部自己写,更利于理解;
- 避免模板自动生成内容干扰初学者判断。
- BSP 的作用
创建应用工程时,SDK 会同时生成一个 BSP(Board Support Package)。
BSP 可以理解为板级支持包,它里面通常包含:
- 硬件参数头文件
- 驱动库
- 基础系统支持
- 中断、异常、启动相关支持
- standalone 运行环境
对于裸机程序来说,BSP 是非常重要的基础支撑。
它相当于帮你把"硬件平台"和"软件代码"连接起来了。
十三、添加 main.c 并编写 LED 控制程序
创建空工程之后,需要手动添加源文件,例如:
bash
main.c
这一点看起来很简单,但很多初学者第一次操作时会忽略文件后缀,导致文件没有被当成 C 源文件处理。所以这里一定要确保文件名带有 .c 后缀。
本实验的目标是控制 PS 端 MIO7 引脚输出高低电平,从而控制 LED 亮灭。
- LED 控制的基本思路
在开发板上,PS 端 LED 连接到 MIO7。
如果该 LED 是高电平点亮、低电平熄灭,那么程序逻辑就很简单:
- 把对应 GPIO 配置为输出;
- 输出高电平,LED 点亮;
- 延时一段时间;
- 输出低电平,LED 熄灭;
- 再延时一段时间;
- 无限循环执行。
- 裸机程序的本质
这个实验看起来是在"控制灯",本质上其实是在做下面三件事:
- 配置 GPIO 方向寄存器;
- 配置 GPIO 输出使能寄存器;
- 对数据寄存器进行读写。
也就是说,裸机程序的核心思想依然是:
bash
通过寄存器操作硬件。
只不过在 Xilinx 平台中,通常会借助一些驱动头文件和 API,例如:
- xparameters.h
- xgpiops.h
- xil_io.h
- unistd.h
这些头文件分别承担硬件参数、GPIO 驱动、底层寄存器读写和延时函数声明等作用。
- 代码示例
下面给出一个适合入门理解的 LED 闪烁程序示例:
c
#include "xparameters.h"
#include "xil_io.h"
#include "sleep.h"
#define GPIO_BASEADDR XPAR_XGPIOPS_0_BASEADDR
#define GPIO_DIRM_0 0x204
#define GPIO_OUTEN_0 0x208
#define GPIO_DATA_0 0x40
#define LED_PIN 7
int main()
{
u32 reg;
/* 设置MIO7为输出方向 */
reg = Xil_In32(GPIO_BASEADDR + GPIO_DIRM_0);
reg |= (1 << LED_PIN);
Xil_Out32(GPIO_BASEADDR + GPIO_DIRM_0, reg);
/* 使能MIO7输出 */
reg = Xil_In32(GPIO_BASEADDR + GPIO_OUTEN_0);
reg |= (1 << LED_PIN);
Xil_Out32(GPIO_BASEADDR + GPIO_OUTEN_0, reg);
while (1)
{
/* 输出高电平,点亮LED */
reg = Xil_In32(GPIO_BASEADDR + GPIO_DATA_0);
reg |= (1 << LED_PIN);
Xil_Out32(GPIO_BASEADDR + GPIO_DATA_0, reg);
usleep(500000);
/* 输出低电平,熄灭LED */
reg = Xil_In32(GPIO_BASEADDR + GPIO_DATA_0);
reg &= ~(1 << LED_PIN);
Xil_Out32(GPIO_BASEADDR + GPIO_DATA_0, reg);
usleep(500000);
}
return 0;
}
这段程序的重点不在于一定要记住每一个寄存器偏移量,而在于理解其整体逻辑:
- 先设置方向;
- 再使能输出;
- 最后不断切换输出电平。
十四、程序运行与调试
编译完成后,会生成对应的 .elf 文件。
这个文件就是最终要下载到开发板上运行的 ARM 可执行程序。
连接开发板后,在 SDK 中运行程序时,通常要关注以下几个选项:
- 是否选择了正确的处理器,例如 ps7_cortexa9_0
- 是否勾选了 Reset entire system
- 是否勾选了 Program FPGA
- 是否指定了正确的 ELF 文件
虽然当前实验重点在 PS,但很多情况下依然建议勾选 Program FPGA,以保证整个系统状态正确初始化。
运行成功后,如果程序逻辑和硬件连接都正确,就可以观察到 LED 周期性闪烁。
常见问题分析
- 为什么我已经写了 main.c,却没有生成 .elf 文件?
通常有几种可能:
- 工程没有成功编译;
- 代码中存在语法错误;
- 源文件没有正确加入工程;
- 文件并不是 .c 类型;
- BSP 或硬件平台存在问题。
因此,遇到没有 .elf 文件时,不要只盯着代码内容,还要检查工程结构和编译日志。
- 为什么 LED 没有亮?
可能原因包括:
- MIO 引脚配置不对;
- GPIO 功能没有在 PS 中开启;
- LED 实际连接引脚不是 MIO7;
- 输出高低电平逻辑与板卡电路不一致;
- 程序没有真正运行到板子上;
- DDR 或硬件平台配置存在问题。
- 为什么要导出 HDF 文件?
因为 SDK 需要依赖 HDF 获取硬件平台信息。
没有 HDF,SDK 就不知道有哪些外设、寄存器基地址是多少、当前处理器配置如何。
- 为什么明明是 PS 实验,还要经过 Vivado?
因为 Zynq 的 PS 系统并不是一个完全脱离硬件设计环境独立存在的"黑盒"。
在实际开发中,PS 的很多配置仍然通过 Vivado 来完成,所以 Vivado 依然是开发入口之一。
下篇预告
《基于ARM的裸机程序设计和开发(三):C编程基础和硬件编程原理》