ZYNQ嵌入式Linux学习——linux启动文件制作

一、基础知识

在zynq上成功运行linux需要制作的启动文件包括:U-Bootlinux内核设备树根文件系统。接下来对这四者进行简单介绍,以明白其在linux系统中的作用。

本文使用的ZYNQ型号是Zynq-7000系列的xc7z100ffg900,使用的vivado版本是2021.2

需要安装的软件:

  • Vivado 2021.2
  • Vitis 2021.2
  • VMware

1. U-Boot

对于计算机系统而言,从开机上电到操作系统启动需要一个引导过程,这个引导过程由引导程序指定。 引导程序是系统上电启动运行的第一段软件代码。引导程序的主要运行任务就是将内核映像从硬盘读到 RAM 中,然后跳转到内核的入口点去运行,即开始启动操作系统。 嵌入式 Linux 系统同样离不开引导程序,这个引导程序一般我们叫作启动加载程序( Bootloader)。

Bootloader 是在操作系统运行之前执行的一段小程序。通过这段小程序,可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备。也就是说芯片上电以后先运行一段 bootloader 程序。这段 bootloader 程序会先初始化 DDR 等外设,然后将 Linux 内核从 flash(NAND, NOR FLASH, SD, MMC 等)拷贝到 DDR中,最后启动 Linux 内核。当然了, bootloader 的实际工作要复杂的多,但是它最主要的工作就是启动 Linux 内核

Zynq-7000 嵌入式软件栈如下图:

image-20250605101801033

U-Boot,通用引导加载程序的缩写,是一种开源的主引导加载程序,用于嵌入式设备,以引导 Linux 社区中经常使用的设备操作系统内核。 Xilinx 在 Zynq-7000 设备中使用 U-Boot 作为第二阶段引导加载程序。

Zynq-7000 的第一阶段引导加载程序( FSBL)使用硬件比特流(如果存在)配置 FPGA,并 将 操 作 系 统 ( OS ) 映 像 或 第 二 阶 段 引 导 加 载 器 映 像 从 非 易 失 性 存 储 器( NAND/SD/eMC/QSPI)加载到存储器( DDR/OCM)。

2. linux内核

img

  • 从技术层面讲,内核是硬件与软件之间的一个中间层。作用是将应用层序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址。

  • 从应用程序的层面讲,应用程序与硬件没有联系,只与内核有联系,内核是应用程序知道的层次中的最底层。在实际工作中内核抽象了相关细节。

  • 内核是一个资源管理程序。负责将可用的共享资源(CPU时间、磁盘空间、网络连接等)分配得到各个系统进程。

  • 内核就像一个库,提供了一组面向系统的命令。系统调用对于应用程序来说,就像调用普通函数一样。

3. 设备树

设备树的概念从Linux内核V2.6版本开始引入, 用于描述一个硬件平台的硬件资源信息,这些信息包括:CPU的数量和类别、内存基地址和大小、总线和桥、外设连接、中断控制器和中断使用情况、GPIO控制器和GPIO使用情况、Clock控制器和Clock使用情况等等。

设备树的特点:

  • 实现驱动代码与设备硬件信息相分离
  • 通过被bootloader(uboot)Linux传递到内核, 内核可以从设备树中获取对应的硬件信息。
  • 对于同一SOC的不同主板,只需更换设备树文件即可实现不同主板的无差异支持,而无需更换内核文件,实现了内核和不同板级硬件数据的拆分

整个设备树包含DTC(device tree compiler)DTS(device tree source)DTB(device tree blob)

img

  • DTS(device tree source)

DTS是一种ASCII文本格式的设备树描述,在ARM Linux中,一个dts文件对应一个ARM的设备

Dtsi:由于一个SoC可能对应多个设备(一个SoC可以对应多个产品和电路板),这些.dts文件势必须包含许多共同的部分,Linux内核为了简化,把SoC公用的部分或者多个设备共同的部分一般提炼为.dtsi,类似于C语言的头文件。其他的设备对应的.dts就包括这个.dtsi

  • DTC(device tree compiler)

DTC是将.dts编译为.dtb的工具,相当于gcc。该工具一般在编译内核的时候,默认会自动执行编译操作。

  • DTB(device tree blob)

dtb文件是.dts 被 DTC 编译后的二进制格式的设备树文件,它由Linux内核解析,也可以被bootloader进行解析。

image-20220802091438853.png

4. 根文件系统

根文件系统首先是内核启动时所mount的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。

根文件系统首先是一种文件系统,该文件系统不仅具有普通文件系统的存储数据文件的功能,但相对于普通的文件系统,它是内核启动时挂载(mount)的第一个文件系统,内核代码的映像文件保存在根文件系统中,系统引导启动程序会在根文件系统挂载之后从中把一些初始化脚本(如rcS,inittab)和服务加载到内存中去运行。 文件系统和内核是完全独立的两个部分。在嵌入式中移植的内核下载到开发板上,是没有办法真正的启动Linux操作系统的,会出现无法加载文件系统的错误。

image-20250613105838421

二、Petalinux制作启动文件

由第一章可知,需要制作的启动文件包括U-Bootlinux内核设备树根文件系统。本章使用petalinux制作的文件包括:

  • BOOT.BIN

包含了fsbl文件、bitstream文件、U-Boot文件

  • image.ub

linux内核文件,其中包含了linux内核和设备树

  • boot.scr

其核心作用是为U-Boot提供自动执行的命令序列,从而完成从硬件初始化到Linux内核加载的完整启动流程

  • rootfs.tar.gz

根文件系统的压缩包,解压就可以得到根文件系统

1. Petalinux简介

Petalinux 工具是 Xilinx 公司推出的嵌入式 Linux 开发套件,包括了 u-boot、 Linux Kernel、device-tree、 rootfs 等源码和库,以及 Yocto recipes,可以让客户很方便的生成、配置、编译及自定义 Linux 系统。 Petalinux 支持 Versal 、 Zynq UltraScale+ MPSoC、 Zynq-7000 SoC 以及MicroBlaze,可与 Xilinx 硬件设计工具 Vivado 协同工作,大大简化了 Linux 系统的开发工作。具体的介绍可访问 Petalinux 工具网站: https://china.xilinx.com/products/design-tools/embeddedsoftware/petalinux-sdk.html

2. Ubuntu安装

2.1 Ubuntu镜像下载

image-20241008181159661

  • 根据需要安装的Petalinux版本找到对应的Ubuntu版本要求即可。本篇使用Vivado2021.2和Petalinux2021.2(Vivado版本和Petalinux版本要保持一致),选择的Ubuntu版本是20.04.1,在Ubuntu官网下载历史版本https://old-releases.ubuntu.com/releases/20.04.1/

    往下翻找到Ubuntu 20.04.1的ISO文件,点击下载

    image-20241008205038265

2.2 创建虚拟机

image-20241008182009461

  • 选择自定义

image-20241008182036558

  • 镜像选择刚刚下载的20.04.1

image-20241008203713831

  • 把磁盘大小设置为80GB,默认的20GB是不够的

image-20250623232643909

  • 没提到的默认下一步就可以了。

  • 注意:千万不要点系统内提示的任何一个更新提示,否则会给更新到20.04.6

    可以使用

    1
    lsb_release -a

    命令查看当前发行版本

image-20241008204858298

2.3 更新apt软件包

后续需要使用apt下载一些软件包,一般来说安装完Ubuntu系统后需要更新一下apt软件包。

1
2
sudo apt update
sudo apt upgrade -y

等待更新完毕

3. Petalinux安装

3.1 下载安装包

在官网https://china.xilinx.com/support/download/index.html/content/xilinx/zh/downloadNav/embedded-design-tools/archive.html
下载相应的版本

image-20241008195232352

下载需要注册账号、填写信息。需要注意的是国内的账号会出现无法下载的情况。建议科学上网另辟蹊径或者直接找现成的(

3.2 安装依赖库以及软件

在桌面上右键打开终端

image-20241008200352962

一般情况下是可以使用ctrl+shift+v直接复制进终端的,如果不能复制可以尝试安装下面两个包

1
sudo apt install open-vm-tools open-vm-tools-desktop -y

使用下面的命令安装依赖

1
sudo apt install iproute2 gawk python3 python build-essential gcc git make net-tools libncurses5-dev tftpd zlib1g-dev libssl-dev flex bison libselinux1 gnupg wget git-core diffstat chrpath socat xterm autoconf libtool tar unzip texinfo zlib1g-dev gcc-multilib automake zlib1g:i386 screen pax gzip cpio python3-pip python3-pexpect xz-utils debianutils iputils-ping python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev pylint3 libtinfo5 -y

3.3 修改bash

Petalinux 工具需要主机系统的/bin/sh 是 bash,而 Ubuntu 默认的/bin/sh 是 dash,所以这里需要进行更改。

在终端里运行

1
sudo dpkg-reconfigure dash
  • 选择**<否>**然后按回车

image-20241008201750045

3.4 安装Petalinux

把下载的petalinux安装包拖入任意一个文件夹内(可以直接从主机拖进虚拟机)

在这个文件夹下打开终端

image-20241008201351688

运行下面的代码,创建文件夹,并且把/opt目录的属主和属组更改为当前用户名

1
2
sudo chown -R $USER:$USER /opt
mkdir -p /opt/pkg/petalinux/2021.2

把Petalinux安装到刚刚创建的文件夹中

1
./petalinux-v2021.2-final-installer.run -d /opt/pkg/petalinux/2021.2

然后会有读软件许可协议的提示,回车,然后按Q退出

image-20241008210452613

然后输入y同意协议

image-20241008210558941

同意所有的用户许可协议,等待下载完成

image-20241008211113764

下载完成后,可以在下载目录中看到下载的文件

image-20241008211249705

3.5 设置Petalinux环境变量

在正式使用 petalinux 工具之前,需要先运行 petalinux 安装目录下的 settings.sh 脚本文件设置 petalinux 工作环境, settings.sh 脚本用于 bash,还有一个 settings.csh 用于 C shell。一般默认情况下,我们使用 bash 作为登录 shell,所以 source 脚本文件 settings.sh 对petalinux 所需的运行环境进行配置,命令如下:

1
source /opt/pkg/petalinux/2021.2/settings.sh

需要注意的是该命令只对当前终端有效,重新打开终端后需要重新执行这一步。执行结果如下图所示:

image-20241008211731367

我们来验证下工作环境是否已设置,在终端输入如下命令:

1
echo $PETALINUX

image-20241008211831915

打印出了Petalinux的安装路径,表示环境变量设置成功。

鉴于每次打开终端使用 Petalinux 都需要设置相应的环境变量,我们为了方便,将设置Petalinux 环境变量的命令设置成别名,这样我们使用起来就方便些。设置别名方法的很简单,在终端输入如下命令:

1
echo "alias sptl='source $PETALINUX/settings.sh'" >> ~/.bashrc

设置完成后,每次打开新的终端,只需要输入

1
sptl

就可以设置环境变量。sptl的意思是source petalinux

4. 创建vivado硬件工程

本章将创建一个制作启动文件需要的ZYNQ硬件平台,包括配置PS端外设,添加PL端IP核等。linux操作系统通过内核与硬件底层沟通,所以通过vivado设计的底层硬件决定了我们制作的linux系统具有哪些功能。

本次计划实现的功能包括:

  • 实现linux的基本功能,能够串口打印信息

  • 实现以zynq为时钟源1s闪烁一次的PL端的LED灯

  • 实现可以在linux系统内使用指令控制开关的PL端LED灯

4.1 PS端配置

  • 打开vivado并新建工程

image-20250620113331555

  • 选择项目位置

image-20250620113449051

  • 选择RTL Project

image-20250620113554040

  • 搜索你的zynq芯片型号

image-20250620113748750

  • 点击Create Block Design

image-20250620114129199

image-20250620114248050

  • 点击“+”添加IP核

image-20250620114315402

  • 搜索ZYNQ7 Processing System,双击添加IP核。这个IP核就是PS端的处理器。

image-20250620114421684

image-20250620114537093

双击ZYNQ7 Processing SystemIP核进入PS配置界面

image-20250620141819822

需要配置的外设包括:

  • UART 0:用于linux打印系统信息

image-20250621112823540

根据原理图来选择引脚:

image-20250621113554636

image-20250621113533048

在PS-PL Configuration中的General里可以修改UART0的波特率。一般保持默认的115200就好。

image-20250621120148001

  • SD 0:zynq通过SD卡启动系统,所以需要配置SD卡

image-20250621113809763

根据原理图来选择引脚:

image-20250621115319638

image-20250621115434582

  • ENET 0:以太网配置,调试ZYNQ经常要用到ssh远程连接,以远程连接zynq,并且还能远程传输文件。

image-20250621132823305

同样的,根据原理图选择引脚。

image-20250621132925868

  • GPIO:使能所有PS端的GPIO,同时打开以太网的Reset

image-20250621145049845

image-20250621145129966

  • QUAD SPI:ZYNQ选择启动方式需要使能QSPI引脚

image-20250622221421362

image-20250622221557846

image-20250622221615749

BANK 1的I/O电平改成LVCMOS 1.8V

image-20250621134340332

配置完外设后,配置PS端的时钟。这里一般使用100MHz

image-20250621133714630

最后是最重要的DDR配置,如果DDR型号选错了的话是无法启动Linux系统的。系统最先执行的步骤是把U-BOOT拷贝到DDR上,基本上所有程序都运行在DDR上。一定要按照原理图来正确配置DDR型号

image-20250621133936961

image-20250621134557474

4.2 Block Design设计

image-20250620114537093

  • 右键FCLK_CLK0FCLK_RESET0_N,选择Make External

FCLK_CLK0是PS内部时钟子系统(由PS-PLL生成)输出的时钟。我们把这根线拉出来,当做时钟源给PL端的LED,以实现闪烁的功能。

FCLK_RESET0_N是PS通过其内部时钟子系统生成的复位信号,与FCLK_CLK0(时钟信号)同步。它提供了一种受控的、同步的复位机制,确保PL侧逻辑与PS的时钟域同步复位。我们把这根线拉出来给PL端的LED用做复位信号。

image-20250620130730446

image-20250620131044456

  • 点击左边的M_AXI_GP0_ACLK,把它与FCLK_CLK0连接到一起

M_AXI_GP0_ACLK是PS主通用端口Master AXI GP0的时钟输入,用于同步PS通过AXI接口与PL或外部设备的通信。

image-20250620131344421

  • 添加IP核Processor System Reset

Processor System Reset(PSR)IP核是一个专门用于生成和管理复位信号的模块,其核心作用是为系统提供可靠、同步的复位逻辑,尤其适用于处理多时钟域、异步复位同步释放等复杂场景。

image-20250620132520963

  • 添加IP核AXI GPIO

AXI GPIO(AXI General-Purpose Input/Output)是Xilinx提供的一个基于AXI4-Lite接口的通用输入/输出IP核,主要用于通过AXI总线控制或读取外部GPIO信号。它适用于Zynq SoC或FPGA设计,允许PS(处理器系统)通过AXI接口与PL(可编程逻辑)中的GPIO交互,或者直接连接外部设备(如LED、按键、传感器等)。

image-20250620132818296

双击IP核AXI GPIO配置IP核,把GPIO Width改成1

image-20250621151530310

  • 点击Run Connection Automation

image-20250620133103283

image-20250620133143063

  • 点击Run Block Automation

image-20250620133324661

image-20250620133343384

  • 最后的Block Design如下图所示:

image-20250620133725959

做完所有配置后,右键design_1 Generate Output Products

image-20250621134744029

image-20250621134837015

生成完成后,再右键design_1,选择Create HDL Wrapper,为Block Design创建一个硬件描述文件

image-20250621140100641

image-20250621140153040

创建完成后是这样的:

image-20250621140249468

这样就可以把我们创建的Block Design当成一个模块写verilog代码了。

4.3 PL端verilog代码设计

本章将把章节4.2中设计的Block Design当成模块,提供时钟源和复位信号

  • 创建新的硬件描述文件

image-20250621140855253

image-20250621140907158

image-20250621140922640

image-20250621141004435

image-20250621141023922

image-20250621141048592

image-20250621141118876

然后双击刚刚创建的文件,编写led.v

image-20250621141201576

  • 编写闪烁LED灯代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
module led(
led_out,
gpio_rtl_0_tri_io
);

output reg led_out;
inout [0:0]gpio_rtl_0_tri_io;
wire FCLK_CLK0_0;
wire FCLK_RESET0_N_0;

reg [31:0]count;

always @(posedge FCLK_CLK0_0 or negedge FCLK_RESET0_N_0) begin
if (~FCLK_RESET0_N_0) begin
count <= 0;
led_out <= 0;
end else if (count < 50_000_000 - 1) begin
count <= count + 1;
end else begin
count <= 0;
led_out <= ~led_out;
end
end

design_1_wrapper u_design_1_wrapper(
.DDR_addr (DDR_addr ),
.DDR_ba (DDR_ba ),
.DDR_cas_n (DDR_cas_n ),
.DDR_ck_n (DDR_ck_n ),
.DDR_ck_p (DDR_ck_p ),
.DDR_cke (DDR_cke ),
.DDR_cs_n (DDR_cs_n ),
.DDR_dm (DDR_dm ),
.DDR_dq (DDR_dq ),
.DDR_dqs_n (DDR_dqs_n ),
.DDR_dqs_p (DDR_dqs_p ),
.DDR_odt (DDR_odt ),
.DDR_ras_n (DDR_ras_n ),
.DDR_reset_n (DDR_reset_n ),
.DDR_we_n (DDR_we_n ),
.FCLK_CLK0_0 (FCLK_CLK0_0 ),
.FCLK_RESET0_N_0 (FCLK_RESET0_N_0 ),
.FIXED_IO_ddr_vrn (FIXED_IO_ddr_vrn ),
.FIXED_IO_ddr_vrp (FIXED_IO_ddr_vrp ),
.FIXED_IO_mio (FIXED_IO_mio ),
.FIXED_IO_ps_clk (FIXED_IO_ps_clk ),
.FIXED_IO_ps_porb (FIXED_IO_ps_porb ),
.FIXED_IO_ps_srstb (FIXED_IO_ps_srstb ),
.gpio_rtl_0_tri_io (gpio_rtl_0_tri_io )
);

endmodule

编写保存后,Sources中的文件如图所示

image-20250621142651909

led被自动设置成了top文件。如果led.v没有设置成top文件就右键led.v然后Set as Top

  • 添加约束文件

点击加号添加文件

image-20250621143234241

创建约束文件

image-20250621143306389

image-20250621143321982

image-20250621143343877

image-20250621143357361

双击led.xdc编写约束文件

image-20250621143429859

随便在原理图上找一个PL端的LED引脚:

image-20250621143628322

image-20250621143706136

我们这次要点亮两个LED,一个是1s闪烁一次的LED灯,另一个是可以通过linux命令控制开关的LED灯

我这里选择T_LED2对应的引脚W29和T_LED3对应的引脚T29

  • 编写led.xdc:
1
2
3
4
5
6
7
8
# 绑定led_out到引脚W29
set_property PACKAGE_PIN W29 [get_ports led_out]
# 设置引脚电平标准为LVCMOS33
set_property IOSTANDARD LVCMOS33 [get_ports led_out]
# 绑定gpio_rtl_0_tri_io到引脚
set_property PACKAGE_PIN T29 [get_ports {gpio_rtl_0_tri_io[0]}]
# 设置引脚电平标准为LVCMOS33
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_rtl_0_tri_io[0]}]

点击左侧的Open Elaborated Design

image-20250621142810265

点击Schematic可以看见我们编写的模块的RTL电路

image-20250621142932900

PS端为整个电路提供了时钟和复位,并且提供了一个GPIO输出

image-20250621152514332

然后点击Generate Bitstream就可以生成bit流文件了(你也可以按部就班地Run Synthesis然后Run Implementation,最后再生成比特流,但我习惯直接点生成比特流,其实一样的)

image-20250621144234998

生成完成:

image-20250621152443692

  • 导出硬件文件

linux启动文件的制作需要依赖于vivado的硬件,我们在这里导出一个xsa文件,这个xsa文件包含了所有这个工程的硬件信息。

image-20250621152734357

image-20250621152908151

然后一路Next就可以了

项目文件目录下面生成了项目的xsa文件

image-20250621152939324

5. 使用Petalinux定制Linux系统

Petalinux一般的设计流程如下。需要说明的是,不是每一步都需要执行一遍,可以根据使用场景有选择的执行。

  1. 通过 Vivado 创建硬件平台,得到 xsa 文件

  2. 运行 source <petalinux 安装路径>/settings.sh,设置 Petalinux 运行环境

  3. 通过 petalinux-create -t project 创建 petalinux 工程

  4. 使用 petalinux-config –get-hw-description, 将 xsa 文件导入到 petalinux 工程当中并配置petalinux 工程

  5. 使用 petalinux-config -c kernel 配置 Linux 内核

  6. 使用 petalinux-config -c rootfs 配置 Linux 根文件系统

  7. 配置设备树文件

  8. 使用 petalinux-build 编译整个工程

  9. 使用 petalinux-package –boot 制作 BOOT.BIN 启动文件

  10. 制作 SD 启动卡,将 BOOT.BIN 和 image.ub 以及根文件系统部署到 SD 卡中

  11. 将 SD 卡插入开发板,并将开发板启动模式设置为从 SD 卡启动

  12. 开发板连接串口线并上电启动,串口上位机打印启动信息, 登录进入 Linux 系统

5.1 创建Petalinux工程

  • 设置Petalinux环境变量

我们在安装Petalinux的时候就介绍过这个步骤

1
2
3
source /opt/pkg/petalinux/2021.2/settings.sh
# 或者使用之前设置过的简便写法
sptl
  • 创建一个用于放工程的文件夹,并在文件夹内打开终端

image-20250622110805899

  • 创建petalinux工程

现在我们在该目录中创建一个名为“ ZYNQ_BASE”的Petalinux工程,在终端中输入如下命令

1
petalinux-create -t project --template zynq -n ZYNQ_BASE

-t 是“ –type”的简写。 template 参数表明创建的 petalinux 工程使用的平台模板,此处的zynq 表明使用的是 zynq 平台模板的 petalinux 工程,用于 zynq-7000 系列的芯片。 name 参数(此处简写为“ -n”)后接的是 petalinux 工程名

image-20250622111142117

image-20250622111219387

5.2 配置Petalinux工程

  • 把之前生成的led.xsa拖进文件夹里

image-20250622111549715

首次配置Petalinux工程是将xsa文件导入到Petalinux工程中,Petalinux工具会解析xsa文件并弹出配置窗口。在终端中输入如下命令配置Petalinux工程

1
2
3
4
# 进入项目的目录
cd ZYNQ_BASE
# 使用xsa文件配置工程
petalinux-config --get-hw-description ../led.xsa

如果xsa文件有更改,只需要执行petalinux-config --get-hw-description ../led.xsa命令重新加载文件

执行成功后,会弹出配置窗口

image-20250622112127482

这个窗口不能通过鼠标操作,只能通过键盘操作。

通过键盘上的“↑”和“↓”键来选择要配置的菜单

按下“ Enter”键进入子菜单。菜单中蓝色高亮的首字母就是此菜单的热键,在键盘上按下此高亮字母对应的键可以快速选中对应的菜单。

选中子菜单以后按下“ Y”键就会将相应的配置选项写入配置文件中,菜单前面变为“ < * >”。

按下“ N”键不编译相应的代码。

按下“ M”键就会将相应的代码编译为模块,菜单前面变为“ < M >”。

按两下“ Esc”键退出,也就是返回到上一级,按下“ ?”键查看此菜单的帮助信息,按下“ /”键打开搜索框,可以在搜索框输入要搜索的内容。

在配置界面下方会有五个按钮,这五个按钮的功能如下:

Select:选中按钮,和“ Enter”键的功能相同,负责选中并进入某个菜单。

Exit:退出按钮,和按两下“ Esc”键功能相同,退出当前菜单,返回到上一级。

Help:帮助按钮,查看选中菜单的帮助信息。

Save:保存按钮,保存修改后的配置文件。

Load:加载按钮,加载指定的配置文件。

本次不需要对工程配置进行修改,按键盘上的”→”键,选择Save,按回车

image-20250622113529242

选择配置文件(config)的存放位置

image-20250622113550707

最后按两次键盘上的“ Esc”退出配置窗口,Petalinux工具开始自动配置工程。

image-20250622113835605

如果后面想重新配置,只需输入petalinux-config命令即可重新配置。

5.3 配置Linux内核

输入下面的命令配置内核:

1
petalinux-config -c kernel

配置页面:

image-20250622115707986

也没有什么需要修改的,同样Save之后再退出就可以了

结算界面

image-20250622120003003

5.4 配置Linux根文件系统

在终端输入下面的命令可配置根文件系统

1
petalinux-config -c rootfs

image-20250622121252399

默认配置可满足一般使用,也可以根据需求来定制根文件系统,这里保持默认配置。需要说明的是“ PetaLinux RootFS Settings”可以用来设置root用户的密码,默认为“root”。后面登录的时候会用到。

保存配置并退出。

image-20250622121437848

5.5 编译Petalinux工程

输入下面的命令编译Petalinux工程

1
petalinux-build

编译完成结算画面:

image-20250622141856754

编译生成的镜像文件在项目目录下的images文件夹里

image-20250622142649380

5.6 制作BOOT.BIN启动文件

Petalinux提供了petalinux-package命令将PetaLinux项目打包为适合部署的格式,其中“petalinux-package –boot”命令生成可引导映像,该映像可直接与Zynq系列设备(包括Zynq-7000和Zynq UltraScale + MPSoC)或基于MicroBlaze的FPGA设计一起使用。对于Zynq系列设备,可引导格式为 BOOT.BIN,可以从SD卡引导启动。

ZYNQ的启动文件BOOT.BIN一般包含fsbl文件、bitstream文件和uboot文件。使用下面的命令可生成BOOT.BIN文件

1
petalinux-package --boot --fsbl --fpga --u-boot --force

选项“–fsbl”用于指定fsbl文件所在位置,后面接文件对应的路径信息,如果不指定文件位置,默认对应的是images/linux/zynq_fsbl.elf

选项“–fpga”用于指定bitstream文件所在位置,后面接该文件对应的路径信息,默认对应的是 images/linux/system.bit,实际可能有区别

选项“–u-boot”用于指定U-Boot文件所在位置,后面接该文件所在路径信息,默认为images/linux/u-boot.elf

执行结果如下图所示:

image-20250622144829405

我们需要的文件是下面几个

image-20250622145210170

5.7 制作SD启动卡

如果使用SD卡引导linux系统启动,一般需要在SD卡上有2个分区。一个分区使用FAT32文件系统,用于放置启动镜像文件(如BOOT.BIN,boot.scr文件和linux镜像文件等),另一分区使用EXT4文件系统,用于存放根文件系统(rootfs)

在将SD卡插入电脑之前,先配置虚拟机支持USB 3.0

左上角点虚拟机,进入设置

image-20250622162433043

点击USB控制器,USB兼容性选择USB 3.1

image-20250622162512163

插入USB之后,一般Ubuntu系统会自动识别U盘。如果没有,可以看右下角,右键箭头所指的图标

image-20250622195559309

image-20250622195655212

使用df -h命令查看当前系统挂载的硬盘设备

image-20250622201444007

U盘一般挂载在/media下面,我这里是已经分过区的SD卡,所以我会先删除所有分区,再进行分区操作,以演示整个SD卡制作流程。

可以看到红框里,我的SD卡的设备节点是/dev/sdc(节点不一定是/dev/sdc,要具体检查自己的SD卡节点的名字),执行下面的操作:

1
2
umount /dev/sdc*
sudo fdisk /dev/sdc

再输入”p”查看分区

image-20250622203915061

可以看到这个SD有两个分区,键入”d”,再键入1删除分区1,再次键入d删除第二个分区。如果只有1个分区的话只需要输入一次d就可以了。

当输入d弹出“还没有定义分区!”时,表明已经没有分区。

image-20250622204243011

接下来新建分区。首先键入”n”创建一个分区,再输入”p”使其为主分区。使用默认分区号1和第一个扇区2048。设置最后一个扇区,也就是设置第一个分区的大小,一般设置500M足够了,通过输入“+500M”,为该分区预留500MB,如果提示分区包含vfat签名并询问是否移除该签名,则输入“y”。

image-20250622204812304

然后设置分区类型,输入”t”,再输入”c”,设置为”W95 FAT32(LBA)”,再输入”a”设置为引导分区

image-20250622213200766

第一个分区就创建完成了,接下来创建第二个分区

键入”n”新建分区,后面一路默认就可以了

image-20250622213622977

此时再输入”p”可以看到创建的两个分区,最后输入”w”保存到SD卡

image-20250622213914935

创建完分区之后,就可以格式化分区了

1
2
sudo mkfs.vfat -F 32 -n boot /dev/sdc1
sudo mkfs.ext4 -L rootfs /dev/sdc2

image-20250622214325167

此时再重新插拔SD卡就可以看到我们刚刚制作的SD卡了

image-20250622214740234

然后我们把之前制作的启动文件放到boot分区里,包括BOOT.BIN、boot.scr、image.ub

image-20250622215018902

然后我们需要把根文件系统解压到rootfs分区里

在生成rootfs.tar.gz的文件夹内打开终端,输入命令(-C后面的路径需要根据实际情况来)

1
sudo tar -zxf ./rootfs.tar.gz -C /media/kank/rootfs

image-20250622215314541

image-20250622215555684

5.8 启动开发板

  • 根据原理图,将ZYNQ的启动模式设置为SD卡启动

image-20250623110223734

image-20250623110814815

  • 打开串口上位机,进入Linux系统

上电一开始,按回车可以进入boot界面(如果超过2秒不进行操作boot会自行往下进行)

image-20250623110927089

此时boot已经启动成功,bitstream也应该成功下载到板子上,此时能看见其中一个LED灯在闪烁。

在命令里输入boot,启动内核

image-20250623111348151

此时,linux系统已启动成功。接下来尝试用命令行点亮第二个led

首先,进入/sys/class/gpio,查看这个目录下的内容

1
2
cd /sys/class/gpio
ls

image-20250623111653344

关注其中gpiochip1023,这意味着GPIO编号从1023开始

输入下面的命令创建GPIO并设置为输出模式

1
2
echo 1023 > export
echo out > gpio1023/direction

然后使用下面的命令就可以控制led灯的开关了

1
2
3
4
# 把GPIO拉低,点亮LED
echo 0 > gpio1023/value
# 把GPIO拉高,关掉LED
echo 1 > gpio1023/value

至此,使用Petalinux定制Linux系统已经走完全部流程,并且实现了当初设想的功能,控制了两个LED

三、源码编译启动文件并移植

上一章介绍了使用Petalinux制作启动文件,Petalinux虽然比较方便,将所有操作都集成到一起,但是Petalinux很占空间(40GB都不够),而且每次编译都比较慢,所以我们这里将尝试使用Xilinx官方提供的源码自己编译,速度会更快,也更加灵活。但是这种方式初次上手的时候可能会理不清关系,我在这里做一个关系梳理,展示所有需要的文件的合成路径:

启动文件合成路径

可以参考上面的流程图,跟着下面的步骤,理清制作流程

  1. 使用vivado工程生成的xsa文件在vitis里创建设备树工程,编译生成system-top.dts
  2. system-top.dts添加到u-boot源码目录里,使用交叉编译工具链编译u-boot源码,得到u-boot.elfsystem-top.dts也被编译成我们最终需要的设备树文件system-top.dtb
  3. 使用xsa文件在vitis内创建fsbl工程,编译后产生文件fsbl.elfbit文件
  4. 在vitis内把fsbl.elfbit文件u-boot.elf三个文件合成为引导文件BOOT.bin
  5. system-top.dts添加到内核源码目录里,使用交叉编译工具链编译内核源码,得到我们最终需要的内核文件zImage

1. 构建交叉编译工具链

交叉编译是编译技术发展过程中的一个重要分支。通俗的说,交叉编译就是在一个平台上生成另一个平台上的可执行代码。这里提到的平台有两方面的含义:处理器的体系结构和所运行的操作系统。

通常,程序是在一台计算机上编译,然后再分布到将要使用的其它计算机上。当主机系统(运行编译器的系统)和目标系统(产生的程序将在其上运行的系统)不兼容时,该过程就叫做交叉编译。嵌入式系统应用的日益广泛和需求推动了交叉编译技术的发展。非X86的嵌入式系统开发,一般都是基于X86的PC平台进行,这种开发方式就是交叉编译。嵌入式系统中的应用程序都是通过交叉编译得到的。

Petalinux在构建linux系统的过程中会编译生成Linux的交叉编译工具链,然后使用工具链构建linux系统。不过这种交叉编译工具链可能不方便实用,我们的目的是得到实用的linux编译工具链,这样就可以摆脱Petalinux编译linux系统了。

获取linux交叉编译工具链需要用Petalinux构建 SDK,然后安装SDK。所谓的SDK,也就是软件开发工具集,与Petalinux构建的根文件系统息息相关,里面不仅包含有Petalinux构建的根文件系统、各种库和头文件,还包含linux交叉编译工具链,用来编译linux内核及linux应用以使其能在ZYNQ开发板上运行。

1.1 定制根文件系统rootfs

前面介绍了SDK与Petalinux构建的根文件系统息息相关,这是因为SDK中包含了根文件系统,而根文件系统中又包含了我们添加的各种各样的软件库,特别是链接库,我们在电脑主机中开发编译Qt或OpenCV时就需要这些链接库。下面我们定制本章的根文件系统

在上个章节创建的petalinux的工程目录下

配置petalinux环境变量

1
sptl

配置工程

1
petalinux-config

配置”Image Packaging Configuration —> Root filesystem type”类型为”EXT4“,将根文件系统放到SD卡第二个分区

image-20241012200239178

保存设置并退出

输入如下命令配置根文件系统

1
petalinux-config -c rootfs

image-20241012200505693

  • 1.1 添加OpenCV

退到最开始的界面

Petalinux Package Groups —>

​ packagegroup-petalinux-opencv —>

​ packagegroup-petalinux-opencv(Y)

image-20241012201154044

  • 添加Python

Petalinux Package Groups —>

​ packagegroup-petalinux-python-modules —>

​ packagegroup-petalinux-python-modules(Y)

image-20241012201306299

  • 添加gcc编译工具链(可选)

Petalinux Package Groups —>

​ packagegroup-petalinux-self-hosted —>

​ packagegroup-petalinux-self-hosted(Y)

image-20241012201502973

  • 添加网络工具(可选)

Petalinux Package Groups —>

​ packagegroup-petalinux-networking-stack —>

​ packagegroup-petalinux-networking-stack(Y)

image-20241012201639027

1.2 编译根文件系统并构建SDK

使用如下命令编译刚才定制的根文件系统:(下面两个命令会超级慢,而且真的很占磁盘空间)

1
petalinux-build -c rootfs

image-20250623182420048

生成的根文件在images/linux/目录下。接下来构建SDK,输入命令:

1
petalinux-build --sdk

image-20250623193549887

完成后,在images/linux/目录下生成了sdk.sh文件,我们到该目录下查看

1
2
cd images/linux/
ls

image-20250623194202383

执行sdk.sh

1
./sdk.sh

默认路径是/opt/petalinux/2021.2,我这里保持默认,直接按回车就可以,然后输入y同意。

image-20250623195001276

每次打开新的终端后,如果需要使用SDK,需要在使用前执行

1
. /opt/petalinux/2021.2/environment-setup-cortexa9t2hf-neon-xilinx-linux-gnueabi

执行后,输入arm再按两下Tab,可以看见交叉编译相关的命令,代表环境配置成功

image-20250623195413306

此时,我们已摆脱petalinux的限制,可以直接使用交叉编译工具链编译BOOT,内核等

如果不想每次都手动配置SDK环境变量,执行下面的命令,每次有新终端打开时都会自动设置SDK环境变量

1
echo '. /opt/petalinux/2021.2/environment-setup-cortexa9t2hf-neon-xilinx-linux-gnueabi' | tee -a ~/.bashrc

2. 生成设备树

首先在Github找到Xilinx发布的设备树源码,找到v2021.2的版本下载

https://github.com/Xilinx/u-boot-xlnx/tags

image-20250623201203481

下载后放到合适的位置解压

打开Vitis新建工程

image-20250623201407508

上方选项点击Xilinx,选择Software Repositories

image-20250623201621523

点击New新建,添加刚刚下载的设备树文件所在的文件夹,并应用

image-20250623201757787

新建Platform Project

image-20250623201942155

填写项目名称

image-20250623202031327

选择xsa文件,根据xsa文件建立设备树项目

image-20250623202135952

Operating system选择device_tree

image-20250623202250208

右键Build Project

image-20250623202713701

编译完成后在vitis工作目录下/zynq_device_tree(你项目的名字)/ps7_cortexa9_0/device_tree_domain/bsp下面生成了所需要的设备树文件

包括四个文件pcw.dtsipl.dtsisystem-top.dtszynq-7000.dtsi

image-20250623203054074

如果硬件有更新,只需要右键项目然后Update Hardware Specification,再重新编译即可

image-20250623203414486

3. 配置并编译u-boot

首先在Github上找到Xilinx发布的u-boot源码,选择v2021.2版本

https://github.com/Xilinx/u-boot-xlnx/tags

我们这里下载tar.gz的文件

image-20250623205321189

把下载好的文件放进虚拟机里并解压

1
tar -zxf ./u-boot-xlnx-xilinx-v2021.2.tar.gz
  • 在u-boot源码目录下的configs文件夹下找到文件xilinx_zynq_virt_defconfig(可以ctrl+F搜索)

image-20250623210122005

搜索CONFIG_DEFAULT_DEVICE_TREE,把对应的值改成system-top

image-20250623210750516

搜索CONFIG_OF_LIST,把system-top添加到list中

image-20250623210838514

保存并退出

  • 在include/configs目录下搜索zynq-common.h

image-20250623211208937

文件里搜索default environment

image-20250623211404583

这里搜到的是u-boot的环境变量,在u-boot启动阶段会用到这些变量。

我们把环境变量改成下面的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/* Default environment */
#ifndef CONFIG_EXTRA_ENV_SETTINGS
#define CONFIG_EXTRA_ENV_SETTINGS \
"fdt_high=0x20000000\0" \
"initrd_high=0x20000000\0" \
"scriptaddr=0x20000\0" \
"script_size_f=0x40000\0" \
"fdt_addr_r=0x1f00000\0" \
"pxefile_addr_r=0x2000000\0" \
"kernel_addr_r=0x2000000\0" \
"scriptaddr=0x3000000\0" \
"ramdisk_addr_r=0x3100000\0" \
"ethaddr=00:0a:36:00:00:00\0" \
"kernel_image=zImage\0" \
"kernel_load_address=0x2080000\0" \
"ramdisk_image=uramdisk.image.gz\0" \
"ramdisk_load_address=0x4000000\0" \
"devicetree_image=system-top.dtb\0" \
"devicetree_load_address=0x2000000\0" \
"bitstream_image=system.bit.bin\0" \
"boot_image=BOOT.bin\0" \
"loadbit_addr=0x100000\0" \
"loadbootenv_addr=0x2000000\0" \
"kernel_size=0x500000\0" \
"devicetree_size=0x20000\0" \
"ramdisk_size=0x5E0000\0" \
"boot_size=0xF00000\0" \
"fdt_high=0x20000000\0" \
"initrd_high=0x20000000\0" \
"bootenv=uEnv.txt\0" \
"loadbootenv=load mmc 0 ${loadbootenv_addr} ${bootenv}\0" \
"importbootenv=echo Importing environment from SD ...; " \
"env import -t ${loadbootenv_addr} $filesize\0" \
"sd_uEnvtxt_existence_test=test -e mmc 0 /uEnv.txt\0" \
"preboot=if test $modeboot = sdboot && env run sd_uEnvtxt_existence_test; " \
"then if env run loadbootenv; " \
"then env run importbootenv; " \
"fi; " \
"fi; \0" \
"mmc_loadbit=echo Loading bitstream from SD/MMC/eMMC to RAM.. && " \
"mmcinfo && " \
"load mmc 0 ${loadbit_addr} ${bitstream_image} && " \
"fpga load 0 ${loadbit_addr} ${filesize}\0" \
"uenvboot=" \
"if run loadbootenv; then " \
"echo Loaded environment from ${bootenv}; " \
"run importbootenv; " \
"fi; " \
"if test -n $uenvcmd; then " \
"echo Running uenvcmd ...; " \
"run uenvcmd; " \
"fi\0" \
"sdboot=if mmcinfo; then " \
"run uenvboot; " \
"echo Copying Linux from SD to RAM... && " \
"load mmc 0 ${kernel_load_address} ${kernel_image} && " \
"load mmc 0 ${devicetree_load_address} ${devicetree_image} && " \
"bootz ${kernel_load_address} - ${devicetree_load_address}; " \
"fi\0" \
"default_bootcmd=run sdboot;\0" \
"bootargs=console=ttyPS0,115200 root=/dev/mmcblk0p2 rootwait rw\0" \
BOOTENV
#endif

保存并退出

  • 进入arch/arm/dts目录,把之前生成的设备树文件拖进这个文件夹内

image-20250623215615241

  • 搜索文件Makefile

image-20250623212204877

这里可能要用到vim编辑文件(或者用其他文本编辑器也可以)

双击打开后发现是vim打开的,vim中按ctrl+B向前翻页,按ctrl+F向后翻页。我们往下翻翻到dtb-$(CONFIG_ARCH_ZYNQ)(大概在260行,右下角能看到是多少行)

image-20250623212832278

在这一项的最后一行添加system-top.dtb,如图所示。按”i”进入编辑模式,更改完毕后按esc退出编辑模式,再输入:wq保存文件并退出vim

image-20250623213130711

  • 在u-boot源码的根目录下(一定要在源码根目录下)执行下面的命令,以xilinx_zynq_virt_defconfig的内容进行项目配置
1
make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- xilinx_zynq_virt_defconfig

image-20250623213512928

  • 同样在u-boot源码的根目录下执行下面的命令,使用menuconfig进行图形化配置
1
make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- menuconfig

首先进入> Boot options > Autoboot options中,把自动boot前的延迟时间改成5秒,这样在进入boot界面时会倒计时5秒再往下进行

image-20250623213815375

进入> Boot options,把bootcmd value改成run default_bootcmd。这里对应之前在zynq-common.h中改过的环境变量,其中有一个变量名称是default_bootcmd,这里把bootcmd value改成default_bootcmd后,就把default_bootcmd设置成了boot执行的第一个脚本

image-20250623214128976

进入> Environment,把环境变量保存到FAT分区下

image-20250623215116157

进入> Device Drivers > Serial drivers,配置串口号

image-20250623215002338

保存配置并退出

  • 在u-boot源码根目录下执行下面的命令来编译
1
make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- -j20

在源码根目录下生成目标文件u-boot.elf

image-20250623220103964

arch/arm/dts目录下也生成了编译后的设备树文件system-top.dtb

image-20250623220152280

4. 制作BOOT.bin

Vitis中创建Application Platform

image-20250623220831918

选择根据xsa文件创建硬件平台

image-20250623220940678

输入项目名称,这里是用来做fsbl的项目,所以命名为zynq_base_fsbl

image-20250623221009436

选择Zynq FSBL

image-20250623221123603

编辑fsbl_debug.h

image-20250623221252721

添加一行#define FSBL_DEBUG_INFO,这样在fsbl执行时会打印信息,从而在启动失败时判断错误原因

image-20250623221634927

编译fsbl项目

image-20250623221843981

右键fsbl项目Create Boot Image

image-20250623222400248

如图是制作BOOT.bin需要的三个文件

image-20250623222649896

他们的顺序一定是fsbl.elf > bit > u-boot.elf

fsbl.elf是执行的第一段引导程序,bit是硬件比特流,u-boot.elf是刚刚编译u-boot源码产生的文件,前两个不需要更改,只需要把最后一个文件改成u-boot.elf即可。

点击Create Image后生成Boot.bin,如图,直接右键复制就可以复制到文件夹里。

image-20250623223010599

如果xsa文件有更新,右键fsbl的硬件平台,点击Update Hardware Specification,再重新编译fsbl项目即可。

image-20250623223137566

至此,BOOT.bin已制作完成。

5. 配置并编译内核

首先在Github官网上找到Xilinx发布的内核源码,下载v2021.2版本

https://github.com/Xilinx/linux-xlnx/tags

image-20250623223650903

拖到虚拟机里并解压

1
tar -zxf ./linux-xlnx-xlnx_rebase_v5.10_2021.2.tar.gz

进入arch/arm/boot/dts目录,把设备树文件拖进文件夹里

image-20250623224148286

在此目录下搜索文件Makefile

image-20250623224309158

使用同样的方法在dtb-$(CONFIG_ARCH_ZYNQ)的最后一行添加system-top.dtb(大概在1293行)

image-20250623224446426

到内核源码的根目录下,执行下面的命令使用xilinx_zynq_defconfig文件的内容做项目配置

1
make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- xilinx_zynq_defconfig

image-20250623224928829

一般我们不对内核进行修改,如果有需求再使用menuconfig进行配置即可。

在内核源码的根目录下运行下面的命令编译内核

1
make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- -j20

最后会在arch/arm/boot目录下面生成目标文件zImage

image-20250623230507436

6. 启动开发板

我们已经得到了启动Linux需要的启动文件BOOT.binzImagesystem-top.dtb

把这三个文件放到SD卡的第一个分区即可,根文件系统的移植操作和Petalinux章节的相同,根文件系统直接使用Petalinux编译出来的那个就可以了

image-20250623230731083

在上电最开始5秒内按下回车进入uboot界面

image-20250623230925057

此时应该能观察到其中一个LED闪烁,代表着bit文件已经下载到zynq里。

输入boot启动内核

image-20250623231059069

启动成功,进入Linux系统。此处仿照Petalinux章节中最后的内容,对GPIO进行操作

1
2
cd /sys/class/gpio
ls

image-20250623231218241

1
2
echo 1023 > export
echo out > gpio1023/direction

使用下面的命令控制另一个LED

1
2
3
4
# 把GPIO拉低,点亮LED
echo 0 > gpio1023/value
# 把GPIO拉高,关掉LED
echo 1 > gpio1023/value

至此,Linux移植部分的内容已全部介绍完毕。

参考文章

3_【正点原子】领航者ZYNQ之嵌入式Linux开发指南_V3.1

深度:一文看懂Linux内核!Linux内核架构和工作原理详解

【一文秒懂】Linux设备树详解

Linux根文件系统(rootfs原理详解)

【ZYNQ】移植Linux系统,配置与驱动相对应的设备树

Ubuntu Linux虚拟机不识别U盘问题解决