欢迎光临
我们一直在努力

保姆级教程六:告别死等!ZYNQ Linux下PL如何主动向PS发中断?(UIO驱动小白必看)

保姆级教程六:告别死等!ZYNQ Linux下PL如何主动向PS发中断?(UIO驱动小白必看)

文章目录

  • 保姆级教程六:告别死等!ZYNQ Linux下PL如何主动向PS发中断?(UIO驱动小白必看)
    • 🔔 核心概念:Linux 下的“智能门铃” —— UIO 驱动框架
    • 🛠️ 第一步:Vivado 硬件设计(给 FPGA 拉一根门铃线)
    • 🚀 第二步:PetaLinux 配置(告诉 Linux 门铃装在哪)
      • 1. 开启内核的 UIO 驱动支持
      • 2. 修改设备树(最关键的一步!)
      • 3. 重新编译打包
    • 🔍 第三步:开机验证“门铃”是否安好
    • 💻 第四步:C语言实战(躺平等待 FPGA 的呼唤)
      • 编译运行:
    • ❓ 进阶答疑 (FAQ)

在前面的教程中,我们学会了用
/dev/mem +
mmap 的方法,让 PS端(ARM核)去读写 PL端(FPGA)的物理地址。

很多小白学到这里觉得大功告成了,但如果你去公司里写出这样的代码,大概率会被老员工骂:“你这样写,CPU 会被活活累死的!”

为什么?因为 mmap 只能让老板(PS端)主动去问员工(PL端)。假设让 FPGA 去处理一张高清图片,CPU 为了知道它有没有处理完,只能写个 while(1) 死循环,每隔1毫秒去读一次状态寄存器。这叫轮询(Polling),极度浪费系统资源!

今天,我们就来学习更高级、更优雅的玩法:中断(Interrupt)。让 FPGA 干完活后,主动给 CPU “按门铃”!


🔔 核心概念:Linux 下的“智能门铃” —— UIO 驱动框架

在裸机(无系统)状态下,写中断很简单,配几个寄存器就行。但在 Linux 系统里,中断是内核的“绝对禁脔”,普通的应用层 C 语言根本摸不到中断线。

如果要按正规流程,你需要写一套完整的 Linux 字符设备内核驱动。这对小白来说,简直是地狱难度。

好在,Linux 内核的大佬们给咱们留了一道极其好用的“合法后门”——UIO (Userspace I/O) 框架。

💡 小白秒懂 UIO: 如果说上一篇的 /dev/mem 是直接把整个物理内存扒光了给你看(不安全、不支持门铃)。 那么 UIO 就是系统专门为你这个 FPGA 模块建的一个“带门铃的独立办公室”。 你不仅能安全地读写里面的寄存器,当 FPGA 发送中断时,你的 C 语言程序还能听到“叮咚”声!


🛠️ 第一步:Vivado 硬件设计(给 FPGA 拉一根门铃线)

要让 PL 端能发中断,硬件连线必须要有。

  • 打开你的 Vivado Block Design。
  • 双击 ZYNQ7 Processing System 的大方块,找到 Interrupts 选项卡。
  • 勾选 Fabric Interrupts -> PL-PS Interrupt Ports -> IRQ_F2P(FPGA to PS 的缩写)。
  • 此时 ZYNQ 方块上会多出一个叫 IRQ_F2P 的引脚。
  • 把你 PL 端 IP 核的 interrupt 引脚连到这个 IRQ_F2P 上。(如果你是用 AXI GPIO,记得在配置里勾选 Enable Interrupt)。
  • 老规矩:重新生成 Bitstream,导出 .xsa 文件!

  • 🚀 第二步:PetaLinux 配置(告诉 Linux 门铃装在哪)

    拿着新的 .xsa 图纸,回到 Ubuntu 里的 PetaLinux 工程。

    1. 开启内核的 UIO 驱动支持

    输入命令打开内核配置:

    petalinux-config -c kernel

    进入菜单,按以下路径勾选(按 Y 变成 [*] 或 <*>): Device Drivers -> Userspace I/O drivers -> 勾选 Userspace I/O platform driver with generic IRQ handling。 保存并退出。

    2. 修改设备树(最关键的一步!)

    Linux 开机时需要查户口,我们需要在设备树里声明:这个硬件,请用 UIO 驱动来接管!

    打开 PetaLinux 工程里的设备树追加文件:

    vim project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi

    在里面添加以下代码(假设你的 AXI IP 核名字叫 my_ip_0):

    /include/ "system-conf.dtsi"
    / {
    };

    /* 通过符号引用,覆写 PL 端的设备节点属性 */
    &my_ip_0 {
    compatible = "generic-uio"; /* 这句是灵魂!告诉系统用 UIO 驱动它 */
    };

    3. 重新编译打包

    petalinux-build
    petalinux-package –boot –fsbl images/linux/zynq_fsbl.elf –fpga images/linux/system.bit –u-boot –force

    把新的 BOOT.BIN 和 image.ub 拷入 SD 卡,插板开机!


    🔍 第三步:开机验证“门铃”是否安好

    开机登录 ZYNQ 开发板的终端,输入:

    ls /dev/uio*

    如果你看到屏幕上输出了 /dev/uio0,恭喜你!这说明 Linux 已经成功把你的 FPGA 模块包装成了一个 UIO 设备!


    💻 第四步:C语言实战(躺平等待 FPGA 的呼唤)

    以前用 /dev/mem 我们是死循环读,现在用 /dev/uio0,我们可以让程序“睡大觉”,直到中断来了才醒。

    新建 uio_test.c:

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

    #define UIO_DEV "/dev/uio0"
    #define MAP_SIZE 4096 // 映射大小

    int main() {
    int fd;
    void *map_addr;
    unsigned int irq_count = 0; // 记录中断发生的次数

    // 1. 打开 UIO 设备文件
    fd = open(UIO_DEV, O_RDWR);
    if (fd < 0) {
    printf("打开 %s 失败!\\n", UIO_DEV);
    return 1;
    }

    // 2. 映射物理地址 (UIO的映射比 /dev/mem 更安全,不需要填绝对物理地址)
    map_addr = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (map_addr == MAP_FAILED) {
    printf("内存映射失败!\\n");
    close(fd);
    return 1;
    }

    volatile unsigned int *pl_reg = (volatile unsigned int *)map_addr;

    printf("UIO 初始化成功!程序进入休眠,等待 PL 端发送中断…\\n");

    // ==========================================
    // 3. 核心机制:阻塞等待中断
    // ==========================================

    // 开启 UIO 中断:必须往 /dev/uio0 写入一个值为 1 的整数
    unsigned int irq_on = 1;
    write(fd, &irq_on, sizeof(irq_on));

    // read() 会让程序阻塞(死等睡觉),直到 PL 端发来中断,程序才会往下执行!
    // 读出的数据是中断发生的总次数
    read(fd, &irq_count, sizeof(irq_count));

    printf("==================================\\n");
    printf("叮咚!收到 PL 端发来的中断信号!\\n");
    printf("这是第 %d 次中断。\\n", irq_count);

    // 中断发生后,去读取 PL 计算好的数据寄存器 (假设偏移量为 4)
    // printf("PL端处理结果为: 0x%X\\n", *(pl_reg + 1));
    printf("==================================\\n");

    // ==========================================

    // 4. 清理现场
    munmap(map_addr, MAP_SIZE);
    close(fd);

    return 0;
    }

    编译运行:

    arm-linux-gnueabihf-gcc uio_test.c -o uio_test
    chmod +x uio_test
    ./uio_test

    运行后,程序会停在 等待 PL 端发送中断… 这里,此时 CPU 占用率为 0%! 一旦你的 FPGA 硬件条件满足(比如按键按下、或者定时器到时间),触发了 IRQ_F2P,屏幕上就会瞬间打印出:“叮咚!收到 PL 端发来的中断信号!”


    ❓ 进阶答疑 (FAQ)

    Q1:既然 UIO 这么好用,那上一篇的 /dev/mem 是不是白学了?

    • 不白学! UIO 的本质依然是物理内存映射,但它是更高级、更安全的封装。/dev/mem 适合快速调试、直接强改寄存器;而 UIO 适合做正式项目的应用层驱动。公司里绝大多数不需要海量数据传输的自研 IP 核,都是用 UIO 控制的!

    Q2:如果我还要传大量图像数据怎么办?UIO 够用吗?

    • UIO 只能传控制指令和小数据(寄存器级别)。 如果你要从 PS 搬运 1080P 视频到 PL 进行图像处理,单靠 UIO 会卡死。这就需要祭出 ZYNQ 的终极大杀器:AXI DMA (直接内存访问)。这不仅需要 UIO,还需要分配连续物理内存。

    结语: 从“死循环轮询”到“UIO 阻塞中断”,你的 ZYNQ Linux 开发水平已经完成了从“初学者”到“入门工程师”的蜕变!掌握了 UIO,你就能驾驭 80% 的日常软硬协同开发任务。

    赞(0)
    未经允许不得转载:171主机测评 » 保姆级教程六:告别死等!ZYNQ Linux下PL如何主动向PS发中断?(UIO驱动小白必看)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址