基于ARM的裸机程序设计和开发(二):使用Vivado创建PS应用系统

摘要

在 Zynq 平台的裸机开发中,软件程序并不是凭空运行起来的,而是依赖一个已经配置好的硬件平台。对于初学者来说,真正进入 ARM 裸机实验的第一步,并不是马上写 C 程序,而是先在 Vivado 中创建包含 Zynq PS 的硬件系统,再导出硬件平台到 SDK 中编写和下载程序。本文结合 GPIO 点亮 LED 的入门实验,介绍使用 Vivado 创建 PS 应用系统的基本流程,以及在 SDK 中创建裸机工程并编写 LED 控制程序的方法,帮助初学者建立对 Zynq 裸机开发完整流程的初步认识。

关键词:Zynq;Vivado;SDK;ARM裸机;GPIO;LED;PS系统

文章目录


一、前言

在嵌入式学习中,点亮一个 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 复制代码
这不是一个"难点实验",而是一个"开门实验"。

三、实验内容概述

本实验主要包括四个阶段:

  1. 使用 Vivado 创建工程

在 Vivado 中新建工程,选择目标芯片,并进入 IP Integrator 设计环境。

  1. 创建包含 Zynq PS 的系统

在 Block Design 中加入 Zynq PS 硬核,对 MIO、GPIO、DDR 等参数进行配置,生成系统设计文件。

  1. 导出硬件平台并启动 SDK

将 Vivado 中生成的硬件设计导出为 HDF 文件,并在 SDK 中基于该硬件平台创建裸机应用工程。

  1. 编写 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 创建工程

  1. 启动 Vivado

本实验使用的软件版本为 Vivado 2018.3。

启动软件后,会进入主界面,在 Quick Start 区域通常可以看到几个常用选项:

  • Create Project
  • Open Project
  • Open Example Project

这里选择 Create Project,开始创建一个新工程。

  1. 设置工程路径

在创建工程时,需要指定工程存放路径。这里有一个非常重要但经常被忽略的细节:

bash 复制代码
工程路径中不要包含中文,也不要包含空格。

这是因为某些开发工具、脚本或编译流程对路径格式比较敏感,一旦路径中出现中文或空格,后续有可能出现莫名其妙的问题。

因此建议提前建立一个专门的工程目录,例如:

c 复制代码
F:\zynq_projects\LED

这样的路径通常更稳妥。

  1. 设置工程名称

工程名称可以根据实验内容命名,例如:

c 复制代码
LED

同时建议勾选创建工程子目录的选项,这样每个工程都有独立文件夹,方便后续管理。

对于初学者来说,这一步虽然看似只是命名,但其实养成良好的工程组织习惯非常重要。后面实验越来越多时,独立目录结构会让工程管理轻松很多。

  1. 选择工程类型

在工程类型选择界面,通常直接选择默认的 RTL Project。

由于当前是新工程,不需要导入已有源文件,因此可以勾选暂时不添加源文件。

这里需要注意的是:

虽然我们后面主要是做 Zynq PS 系统配置,但在 Vivado 中创建工程仍然遵循通用的工程创建流程,因此先建立一个基础工程框架是必要的。

  1. 选择目标芯片

这是整个工程创建过程中最关键的一步之一。

以 ACZ702 开发板为例,可能会遇到两种芯片版本:

bash 复制代码
XC7Z010

XC7Z020

二者都属于 Zynq 系列,ARM 部分基本一致,主要差别在于 PL 逻辑资源规模不同。

例如,XC7Z020 的逻辑资源比 XC7Z010 更丰富。

如果实验所使用的开发板是基于 XC7Z020,那么在器件搜索框中输入对应型号进行选择即可。一般型号中还会带有封装和速度等级,例如:

bash 复制代码
xc7z020clg400-2

其中:

  • xc7z020 表示芯片型号;
  • clg400 表示封装形式;
  • -2 表示速度等级。

选择器件时,一定要与实际开发板型号保持一致,否则后续生成的硬件配置就会与实际硬件不匹配。

六、创建包含 Zynq PS 的系统

工程创建完成后,接下来就进入本实验最核心的部分:

在 IP Integrator 中创建 Block Design,并加入 Zynq PS 系统。

  1. 创建 Block Design

在 Vivado 左侧的 Project Manager 中,进入 IP Integrator,点击 Create Block Design。

Block Design 的名字可以自定义,一般习惯命名为:

bash 复制代码
system

创建完成后,会出现一个空白的设计画布。此时系统中还没有任何模块,需要手动添加。

  1. 添加 Zynq PS 硬核

在空白区域添加 IP,选择 ZYNQ7 Processing System。

这里一定要理解一个关键点:

bash 复制代码
添加 Zynq PS,并不是"新加了一个 ARM",而是在当前设计中"启用并配置芯片内部已经存在的 ARM 系统"。

也就是说,PS 本来就在芯片内部,Vivado 这里做的是对它进行图形化配置。

  1. 运行自动配置

加入 Zynq PS 之后,通常可以点击 Run Block Automation。

Vivado 会根据当前器件和板级信息,自动完成一部分基础配置,例如固定 I/O 和 DDR 接口的导出。

对于初学者来说,这一步非常有帮助,因为它能自动帮我们完成大量基础设置,减少手工连线和参数配置带来的出错概率。

七、PS 系统中与本实验相关的关键配置

在本实验中,最重要的不是把所有 PS 参数都弄懂,而是先抓住和 LED 实验直接相关的几个配置项。

  1. GPIO 配置

本实验的目标是控制 PS 端的 LED,因此首先要确保 GPIO 功能已经使能。

在 ACZ702 开发板上,PS 端 LED 通常连接到 MIO7 引脚。

也就是说,ARM 通过控制 MIO7 的输出电平,就能控制这颗 LED 的亮灭。

因此在 PS 配置界面中,需要确认:

  • GPIO 功能已开启;
  • MIO7 可作为 GPIO 使用。
  1. DDR 配置

虽然这个实验只是点灯,但程序本身依然需要在存储器中运行。

在 Zynq 系统中,裸机程序通常会放到 DDR 中执行,因此 DDR 参数必须配置正确。

这一点对初学者来说很容易忽略。

很多人会觉得"LED 实验那么简单,应该不需要管 DDR 吧",但实际上:

bash 复制代码
程序能否正常下载和运行,和 DDR 配置是否匹配密切相关。

如果开发板使用的是特定型号的 DDR 芯片,就要在 PS 配置中选择对应的 DDR 参数。

有时开发板上只会印一个缩写代号,而不是完整型号,这时往往需要结合板卡资料或芯片标识来确认。

  1. FIXED_IO 导出

Zynq PS 会涉及一组固定引脚接口,通常显示为 FIXED_IO。

这些接口承载了 PS 的固定功能连接,因此在系统中需要一起导出。

简单来说,如果只把 PS 模块放进去,却没有把相关固定接口导出,那这个 PS 系统在工程中就不算完整。

八、生成输出文件并导出顶层设计

当 PS 的配置完成后,还需要执行几步非常关键的操作,才能让硬件平台真正可用。

  1. 验证设计

在 Block Design 中完成配置后,可以执行 Validate Design 来检查当前设计是否存在错误或严重警告。

如果验证通过,说明当前设计从结构上是合理的,可以继续后续流程。

  1. 生成 Output Products

右键 Block Design,选择 Generate Output Products。

这一步的作用是让 Vivado 根据当前设计生成对应的中间文件和配置输出,为后续综合、实现和导出硬件平台做准备。

  1. 创建 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
  • 应用模板
  1. 为什么选择 Empty Application

在很多入门实验中,教材会建议选择 Empty Application,也就是空工程。

原因很简单:

虽然 SDK 也提供了 Hello World 等模板,但有些模板依赖串口输出。如果当前硬件系统没有专门配置串口,或者暂时不想引入多余内容,那么直接用空工程最合适。

这样做的好处是:

  • 工程结构更干净;
  • 代码全部自己写,更利于理解;
  • 避免模板自动生成内容干扰初学者判断。
  1. BSP 的作用

创建应用工程时,SDK 会同时生成一个 BSP(Board Support Package)。

BSP 可以理解为板级支持包,它里面通常包含:

  • 硬件参数头文件
  • 驱动库
  • 基础系统支持
  • 中断、异常、启动相关支持
  • standalone 运行环境

对于裸机程序来说,BSP 是非常重要的基础支撑。

它相当于帮你把"硬件平台"和"软件代码"连接起来了。

十三、添加 main.c 并编写 LED 控制程序

创建空工程之后,需要手动添加源文件,例如:

bash 复制代码
main.c

这一点看起来很简单,但很多初学者第一次操作时会忽略文件后缀,导致文件没有被当成 C 源文件处理。所以这里一定要确保文件名带有 .c 后缀。

本实验的目标是控制 PS 端 MIO7 引脚输出高低电平,从而控制 LED 亮灭。

  1. LED 控制的基本思路

在开发板上,PS 端 LED 连接到 MIO7。

如果该 LED 是高电平点亮、低电平熄灭,那么程序逻辑就很简单:

  • 把对应 GPIO 配置为输出;
  • 输出高电平,LED 点亮;
  • 延时一段时间;
  • 输出低电平,LED 熄灭;
  • 再延时一段时间;
  • 无限循环执行。
  1. 裸机程序的本质

这个实验看起来是在"控制灯",本质上其实是在做下面三件事:

  • 配置 GPIO 方向寄存器;
  • 配置 GPIO 输出使能寄存器;
  • 对数据寄存器进行读写。

也就是说,裸机程序的核心思想依然是:

bash 复制代码
通过寄存器操作硬件。

只不过在 Xilinx 平台中,通常会借助一些驱动头文件和 API,例如:

  • xparameters.h
  • xgpiops.h
  • xil_io.h
  • unistd.h

这些头文件分别承担硬件参数、GPIO 驱动、底层寄存器读写和延时函数声明等作用。

  1. 代码示例

下面给出一个适合入门理解的 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 周期性闪烁。

常见问题分析

  1. 为什么我已经写了 main.c,却没有生成 .elf 文件?

通常有几种可能:

  • 工程没有成功编译;
  • 代码中存在语法错误;
  • 源文件没有正确加入工程;
  • 文件并不是 .c 类型;
  • BSP 或硬件平台存在问题。

因此,遇到没有 .elf 文件时,不要只盯着代码内容,还要检查工程结构和编译日志。

  1. 为什么 LED 没有亮?

可能原因包括:

  • MIO 引脚配置不对;
  • GPIO 功能没有在 PS 中开启;
  • LED 实际连接引脚不是 MIO7;
  • 输出高低电平逻辑与板卡电路不一致;
  • 程序没有真正运行到板子上;
  • DDR 或硬件平台配置存在问题。
  1. 为什么要导出 HDF 文件?

因为 SDK 需要依赖 HDF 获取硬件平台信息。

没有 HDF,SDK 就不知道有哪些外设、寄存器基地址是多少、当前处理器配置如何。

  1. 为什么明明是 PS 实验,还要经过 Vivado?

因为 Zynq 的 PS 系统并不是一个完全脱离硬件设计环境独立存在的"黑盒"。

在实际开发中,PS 的很多配置仍然通过 Vivado 来完成,所以 Vivado 依然是开发入口之一。

下篇预告

《基于ARM的裸机程序设计和开发(三):C编程基础和硬件编程原理》

相关推荐
EVERSPIN3 小时前
BLE蓝牙水表蓝牙芯片方案
arm开发·蓝牙芯片·蓝牙芯片方案
银河麒麟操作系统5 小时前
银河麒麟桌面操作系统V10SP1(全X86/ARM架构)【进程资源限制与性能优化实践】技术文章
arm开发·性能优化·架构
左手の明天12 小时前
Linux内核裁剪深入浅出:从原理到实操,打造轻量化嵌入式内核
linux·arm开发·c++
路溪非溪14 小时前
wpa_supplicant核心操作总结
linux·网络·arm开发·驱动开发
忆和熙14 小时前
ARM处理器指令系统——ARM指令的寻址方式(上,数据处理指令的寻址方式、Load/Store 寻址方式总览)
arm开发·arm指令寻址方式
忆和熙14 小时前
ARM处理器指令系统——ARM指令的寻址方式(下,Load/Store寻址方式、AArch32/64中Load/Store寻址方式的区别)
arm开发·arm指令寻址方式
猫猫的小茶馆1 天前
【Linux 驱动开发】Linux 内核启动过程详解
linux·c语言·arm开发·驱动开发·stm32·单片机·mcu
田里的水稻1 天前
ARM_运行openClaw
arm开发·人工智能·算法·机器人
Flamingˢ1 天前
基于ARM的裸机程序设计和开发(一):Zynq SoC FPGA的诞生
arm开发·fpga开发