摆脱PetaLinux繁琐配置:在ZYNQ上快速部署Ubuntu系统

 为大家提供了已移植适配的根文件系统,下载后直接解压至SD卡的EXT4分区即可使用。目前提供Ubuntu 14.04、20.04、22.04多个版本,并将持续更新更多新版Ubuntu系统。大幅节省开发时间,让您能专注于软件开发,成倍提升效率

制作不易,记得三连哦,给我动力,持续更新!!!

完整工程文件下载:https://pan.baidu.com/s/1XlvV7nQyRVnfCevlxCKPMA?pwd=9X5B 


前言    

        在 ZYNQ-7000 平台上进行嵌入式 Linux 开发时,根文件系统的构建与配置至关重要。然而,Xilinx 原生的 PetaLinux 根文件系统配置繁琐复杂,而网络上常见的 Ubuntu 移植教程又往往版本过旧漏洞百出,使移植过程困难重重。

        为此,我们推出了专为 Zynq 定制的一站式解决方案:一个开箱即用的通用 Ubuntu 根文件系统,经深度优化与预配置,可帮助开发者快速部署,大幅节省开发时间。我们不仅分享在 Ubuntu 根文件系统制作、优化与部署中的实践经验与踩坑总结,助您少走弯路,还承诺实时更新,随时提供最新版本的 Ubuntu 系统,确保您的开发始终基于最稳定、最先进的环境。

本文将指导您快速完成以下核心步骤:

  1. 使用 Vivado 生成硬件描述文件

  2. 通过 Petalinux 编译生成 BOOT.bin 和内核

  3. 制作 Ubuntu 根文件系统

  4. 移植 ZYNQ 硬件配置至 Ubuntu

  5. 制作 SD 卡启动盘

  6. 进行程序的测试

我们将基于一个简单的 PL 与 PS 交互程序,从 Vitis 逻辑验证 到 Ubuntu 系统功能验证(如网络、APT),确保系统完全可用。

一、准备环境

  • 开发板:ZYNQ-7000 系列

  • vivado、petalinx版本:2019.2

  • Host 环境:Ubuntu 20.04/22.04

  • 工具:chrootqemu-user-static、交叉编译工具链

二、✅功能展示

在文章开始之前,首先来看看本篇文章具体做了什么

❶ 搭建一个PL和PS交互的程序

本次设计通过 Zynq 的 EMIO 接口,实现对 PL 端 LED 状态读取与开关控制,并实时监测 SW 开关状态

image

❷ 通过vitis进行功能验证

编写一个逻辑程序,通过SW去控制LED灯状态

image

❸  制作ubuntu系统,并对功能进行验证

通过串口进入ubuntu系统

image

SSH连接ZYNQ ubuntu系统

image

通过读取SW和控制LED灯程序进行验证

1. 通过命令行打印SW、LED状态

image

2. 开发板硬件状态

image

 

三、程序设计

        在构建系统之前,首先需要准备一个基础的 Zynq 硬件设计程序,作为系统搭建与验证的硬件平台。该设计不仅为后续的 Ubuntu 系统提供底层支持,同时也用于验证系统与开发板的兼容性及基本功能是否正确运行。        

        首先,需创建一个 Vivado 工程,添加并配置 Zynq 处理系统模块,重点启用并扩展 EMIO 接口,同时集成 AXI GPIO 控制器等外设模块。

        整体的Block Design设计以及地址分配如下图所示:大家可以根据自己的开发板进行适当更改,这里需要大家有点zynq基础。

 

 

 程序搭建好之后,就要用petalinx进行编译,具体设计步骤与方法,可参考我之前的详细教程:

zynq使用petalinux进行点灯实验(AXI_GPIO) - CSDN博客

通过本教程,您将获得嵌入式系统启动所需的三个核心文件

  • BOOT.bin(内含 FSBL、比特流与 U-Boot 的启动文件

  • image.ub(包含 Linux 内核设备树的镜像文件)

  • rootfs.tar.gzPetaLinux 根文件系统压缩包)

这些文件是后续 Zynq 系统设计与 Ubuntu 移植的必备基础,在接下来的步骤中均会发挥关键作用。

四、制作ubuntu根文件系统

        本章是本系列最核心的内容:详解如何为 Zynq 制作 Ubuntu 根文件系统。本次实践将采用 Ubuntu 22.04 作为基础版本,全程聚焦于实际构建步骤。以下为具体操作流程:

1. 首先在PC主机Ubuntu系统中安装qemu模拟器:

sudo apt-get install qemu-user-static

2. 下载ubuntu22.04 base 包

下载链接:Ubuntu Base 22.04.5 LTS (Jammy Jellyfish)

需要下载 -armhf 结尾的,是适用于zynq系列的,arm64 结尾的是适用于zynqMP的。

3. 在PC主机Ubuntu系统挂载此根文件系统

# 解压缩base包
sudo tar -xvf ubuntu-base-22.04.4-base-armhf.tar.gz
# 配置网络,复制本机 resolv.conf 文件
sudo cp /etc/resolv.conf ./ubuntu/etc/resolv.conf
# 换源
sudo vim ./ubuntu/etc/apt/sources.list
# 拷贝 qemu-arm-static 到 ubuntu_rootfs/usr/bin/ 目录下。
sudo cp $(which qemu-arm-static) ./ubuntu/usr/bin

ubuntu22.04国内源获取链接:ubuntu-ports | 清华大学开源软件镜像站

编写挂载脚本 mount.sh,用于挂载根文件系统运行所需要的设备和目录

查看代码
#!/bin/bash

SCRIPT_NAME=$(basename "$0")
MOUNT_POINTS=("proc" "sys" "dev" "dev/pts")

show_help() {
    cat << EOF
Usage: $SCRIPT_NAME [-m <path>] [-u <path>] 

Mount or unmount directories for chroot environment.

Options:
  -m, --mount <path>    Mount proc, sys, dev and dev/pts to the specified path and chroot
  -u, --umount <path>   Unmount previously mounted directories from the specified path
  -h, --help           Show this help message

Examples:
  $SCRIPT_NAME -m /media/sdcard/
  $SCRIPT_NAME -u /media/sdcard/
EOF
}

mount_directories() {
    local target_path="$1"
    echo "Mounting directories to $target_path"
    
    for mount_point in "${MOUNT_POINTS[@]}"; do
        local source_path="/$mount_point"
        local target_full_path="${target_path%/}/$mount_point"
        
        # 创建目标目录(如果不存在)
        sudo mkdir -p "$target_full_path" 2>/dev/null
        
        if mountpoint -q "$target_full_path"; then
            echo "  ⚠ Already mounted: $target_full_path"
            continue
        fi
        
        case $mount_point in
            "proc")
                sudo mount -t proc "$source_path" "$target_full_path" && \
                echo "  ✓ Mounted proc → $target_full_path" || \
                echo "  ❌ Failed to mount proc"
                ;;
            "sys")
                sudo mount -t sysfs "$source_path" "$target_full_path" && \
                echo "  ✓ Mounted sys → $target_full_path" || \
                echo "  ❌ Failed to mount sys"
                ;;
            *)
                sudo mount -o bind "$source_path" "$target_full_path" && \
                echo "  ✓ Mounted $mount_point → $target_full_path" || \
                echo "  ❌ Failed to mount $mount_point"
                ;;
        esac
    done
}

unmount_directories() {
    local target_path="$1"
    echo "Unmounting directories from $target_path"
    
    # Unmount in reverse order to handle dependencies
    for ((i=${#MOUNT_POINTS[@]}-1; i>=0; i--)); do
        local mount_point="${MOUNT_POINTS[$i]}"
        local target_full_path="${target_path%/}/$mount_point"
        
        if mountpoint -q "$target_full_path"; then
            sudo umount "$target_full_path" 2>/dev/null && \
                echo "  ✓ Unmounted $target_full_path" || \
                echo "  ⚠ Failed to unmount $target_full_path (may be busy)"
        else
            echo "  ℹ Not mounted: $target_full_path"
        fi
    done
}

check_path() {
    local path="$1"
    local operation="$2"
    
    if [[ -z "$path" ]]; then
        echo "Error: No path specified for $operation"
        return 1
    fi
    
    # 确保路径以斜杠结尾
    if [[ "$path" != */ ]]; then
        path="${path}/"
    fi
    
    if [[ ! -d "$path" ]]; then
        echo "Error: Target path '$path' does not exist or is not a directory"
        return 1
    fi
    
    echo "$path"
    return 0
}

main() {
    local target_path=""
    local operation=""
    
    # Show help if no arguments provided
    if [[ $# -eq 0 ]]; then
        show_help
        exit 1
    fi

    while [[ $# -gt 0 ]]; do
        case $1 in
            -m|--mount)
                operation="mount"
                if [[ -z "$2" || "$2" == -* ]]; then
                    echo "Error: No path specified for mount"
                    show_help
                    exit 1
                fi
                target_path=$(check_path "$2" "mount") || exit 1
                shift 2
                ;;
            -u|--umount)
                operation="umount"
                if [[ -z "$2" || "$2" == -* ]]; then
                    echo "Error: No path specified for umount"
                    show_help
                    exit 1
                fi
                target_path=$(check_path "$2" "umount") || exit 1
                shift 2
                ;;
            -h|--help)
                show_help
                exit 0
                ;;
            *)
                echo "Error: Unknown option '$1'"
                show_help
                exit 1
                ;;
        esac
    done

    case $operation in
        "mount")
            if mount_directories "$target_path"; then
                echo -e "\n✅ Mount completed successfully!"
                echo -e "You can now run: sudo chroot $target_path"
                echo -e "Or press Enter to chroot now (Ctrl+D to exit)..."
                read -p "Press Enter to continue or Ctrl+C to cancel..."
                sudo chroot "$target_path"
                echo -e "\nExited chroot environment"
            else
                echo -e "\n❌ Mount failed with errors"
                exit 1
            fi
            ;;
        "umount")
            unmount_directories "$target_path"
            echo -e "\n✅ Unmount completed!"
            ;;
    esac
}

# 检查是否是sudo运行
if [[ $EUID -ne 0 ]]; then
    echo "This script must be run as root or with sudo"
    exit 1
fi

main "$@"

保存退出后,给脚本增加执行权限,并挂载。

# 增加脚本执行权限
sudo chmod +x mount.sh
# 挂载文件系统
sudo ./mount.sh -m ./ubuntu/
# 进入根文件系统
sudo chroot ./ubuntu/
# 退出根文件系统
exit
# 卸载文件系统
sudo ./mount.sh -u ./ubuntu/

挂载成功如下图所示

image

4. 配置根文件系统

安装必须软件包

apt-get update
apt-get install sudo ssh net-tools ethtool vim openssh-server tzdata iputils-ping ifupdown iproute2 netplan.io udev -y

用户设置

# 添加user用户
adduser user

# 将用户添加到sudo组
usermod -aG sudo user

# 设置主机名称
echo "ubuntu-zynq">/etc/hostname

# 设置本机入口ip
echo "127.0.0.1 localhost">>/etc/hosts
echo "127.0.0.1 ubuntu-zynq">>/etc/hosts
echo "127.0.0.1 localhost ubuntu-zynq" >> /etc/hosts

配置串口调试服务

## 如果没有/etc/init/ttyPS0.conf,则自行创建
mkdir /etc/init
vim /etc/init/ttyPS0.conf

[文件内容]
start on stoppedrc or RUNLEVEL=[12345]
stop on runlevel[!12345]
respawn
exec /sbin/getty -L 115200 ttyPS0 vt102

## 建立一个软连接
ln -s /lib/systemd/system/getty@.service /etc/systemd/system/getty.target.wants/getty@ttyPS0.service

配置网络

## 创建并打开`/etc/network/interfaces`文件   (必须安装ethtool)
vim /etc/network/interfaces

# 本地回环
auto lo 
iface lo inet loopback 

# 两种方法任选一个

# 1、获取动态配置: 
auto eth0 
iface eth0 inet dhcp 

# 2、获取静态配置: 
auto eth0 
iface eth0 inet static 
address 192.168.3.124 
netmask 255.255.255.0 
gateway 192.168.3.1 
hwaddress ether 00:11:22:33:44:55  # 设置新的 MAC 地址(不需要改 可以删除这一行)

系统的文件系统表

# 修改/etc/fstab
# <file system>   <dir>         <type>    <options>                          <dump> <pass>
/dev/mmcblk1p2    /ROOT             ext4      defaults,noatime,errors=remount-ro   0      1
/dev/mmcblk1p1    /BOOT             vfat      defaults,noatime                     0      0

清除缓存

## 清理APT缓存
apt-get clean

## 自动清理无用的依赖项
apt-get autoremove

以上操作全部完成后,输入 exit 退出文件系统,并解除挂载。

5. 压缩为 ubuntu_rootf.tar.gz

sudo tar -zcvf ubuntu.tar.gz -C ./ubuntu/ .

五、制作SD启动盘

1. SD卡分区

首先,将 SD 卡划分为两个分区:

  • FAT 格式的 BOOT 分区:用于存放启动文件 BOOT.bin 和内核镜像,建议分配 256MB 空间。

  • EXT4 格式的 ROOT 分区:用于存放根文件系统,占用 SD 卡剩余全部空间。

推荐选用容量大于 16GB 的 SD 卡,以确保充足的系统存储空间。

2. 导入PL端驱动

  • 解压 Ubuntu 根文件系统至 ROOT 分区

sudo tar -zxvf ubuntu.tar.gz -C /media/linuxusb/ROOT/
  • 解压 PetaLinux 生成的 rootfs.tar.gz 至临时目录,并复制库文件

# 解压至临时目录
sudo tar -zxvf rootfs.tar.gz -C /tmp/temp_root/
# 复制 lib 内容至 ROOT 分区
sudo cp -r /tmp/temp_root/lib/* /media/linuxusb/ROOT/lib/
  • 执行同步操作,确保数据完整写入 SD 卡

sync

执行完毕后,即成功制作好 SD 卡启动盘

六、程序的测试

操作步骤如下:

  1. 将 ZYNQ 开发板启动模式设置为 SD 卡启动

  2. 给开发板上电,等待系统启动

  3. 登录系统(输入用户名和密码)

  4. 编写并运行 SW 状态查询与 LED 控制程序

完成以上步骤后,即可实现对 PL 端外设的读写控制。

测试程序代码如下:主要功能是通过SW去控制LED状态

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdint.h>

#define SW_BASE   0x41200000   // SW 基地址
#define LED_BASE  0x41210000   // LED 基地址
#define MAP_SIZE  0x10000      // 映射大小(64KB)

int main() {
    int fd;
    void *sw_base, *led_base;
    volatile uint32_t *sw_reg, *led_reg;

    // 打开 /dev/mem
    if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
        perror("open /dev/mem");
        return -1;
    }

    // 映射 SW
    sw_base = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, SW_BASE);
    if (sw_base == MAP_FAILED) {
        perror("mmap sw");
        close(fd);
        return -1;
    }
    sw_reg = (volatile uint32_t *)sw_base;

    // 映射 LED
    led_base = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, LED_BASE);
    if (led_base == MAP_FAILED) {
        perror("mmap led");
        munmap(sw_base, MAP_SIZE);
        close(fd);
        return -1;
    }
    led_reg = (volatile uint32_t *)led_base;

    printf("按 Ctrl+C 退出程序\n");
    while (1) {
        uint32_t sw_val = *sw_reg;   // 读取 SW 状态
        *led_reg = sw_val;          // 把 SW 状态直接显示到 LED
        printf("SW = 0x%02X -> LED = 0x%02X\n", sw_val & 0xFF, sw_val & 0xFF);
        usleep(500000); // 500ms
    }

    munmap(sw_base, MAP_SIZE);
    munmap(led_base, MAP_SIZE);
    close(fd);

    return 0;
}

首先通过apt-get安装gcc编译器

sudo apt-get install gcc -y

然后编译sw_led.c

gcc sw_led.c -o sw_led

测试现象 

1. 通过命令行打印SW、LED状态

image

2. 开发板硬件状态

image

 

上述测试充分证明了 Ubuntu 系统在 ZYNQ 平台上完全可用且适配良好,接下来将开展更复杂的 PL-PS 交互实验敬请关注后续更新!

七、ubuntu根文件系统获取

        我们为大家提供了已移植适配的根文件系统,下载后直接解压至SD卡的EXT4分区即可使用。目前提供Ubuntu 14.04、20.04、22.04多个版本,并将持续更新更多新版Ubuntu系统。大幅节省开发时间,让您能专注于软件开发,成倍提升效率

完整工程文件下载:https://pan.baidu.com/s/1XlvV7nQyRVnfCevlxCKPMA?pwd=9X5B 

下一期将进行ZYNQ上实现ubuntu桌面显示系统设计,大家记得关注我哦😀。

制作不易,记得三连哦,给我动力,持续更新!!!

谢谢大家!!!

posted @ 2025-09-01 09:33  FPGAmaster  阅读(1103)  评论(0)    收藏  举报