美文网首页
ZYNQ EBAZ4205+HDMI扩展板 显示Linux桌面播

ZYNQ EBAZ4205+HDMI扩展板 显示Linux桌面播

作者: kuree | 来源:发表于2022-02-15 16:42 被阅读0次

    之前的文章介绍了如何生成EBAZ4205矿板的u-boot和Linux内核,使用的硬件只有改完SD卡启动的EBAZ4205裸板,只能通过串口与板子通信。这次尝试启动桌面并播放视频(由于EBAZ4205没有引出USB,仍然通过串口控制)。原板子是没有显示接口的,需要自己做一块HDMI扩展板。

    ZYNQ的FPGA部分使用的是Artix-7,网上有不少Artix-7输出HDMI的教程,大多使用Digilent DVI IP,FPGA部分可以通过这个IP直接输出TMDS编码的数据,这样就不用专用的RGB2HDMI的IC了。Digilent DVI IP将RGB888 1080P时序的并口数据编码成10bit一组的串行数据输出到pin上,1080P的像素时钟是148.5Mhz,FPGA内部最高时钟是148.5的5倍,采用双边延输出。Digilent DVI IP不支持将音频编码进TMDS流,想要输出音频必须在FPGA上搭建I2S IP,或者使用PDM的方式输出。

    EBAZEXT-V2扩展板

    EBAZEXT-V2 KiCad 6.0工程托管在 GitHub,Elrori/EBAZ4205遵守GPL。

    古早之前就画过一板VGA的扩展板,后来没测过。VGA接口已过时,因此画了一板HDMI。板子中DDC/I2C部分不使用,可能有设计上的bug。没有DDC接口,Linux将获取不到显示器分辨率,可以设置设备树让VDMA强制输出1920x1080P60的画面。
    接口资源:

    • USB-C供电,EBAZ4205不用再接电源
    • CMOS/GPIO 接ov摄像头或通用IO
    • 1.3寸LCD/GPIO 接SPI屏幕(1.3寸 240x240 SPI st7789方案)
    • USB转串口
    • HDMI-A 1080P60
    • PDM音频输出
    • 按键x2、LEDx2

    这次测试只用到了HDMI接口。


    在这里插入图片描述 在这里插入图片描述

    VIVADO 2019.1硬件搭建

    为了输出画面,FPGA部分要将图像数据从DDR搬运到DVI IP,该工作由XILINX VDMA完成。XILINX VDMA输入AXI4-MM,输出AXI4-Stream接口数据,需要XILINX AXI4S-VID-OUT IP转换为视频时序。时序由XILINX VTC产生。整个硬件架构参考原子《领航者 ZYNQ 之嵌入式 SDK开发指南 V1.3》中的第20章,硬件搭完后必须先裸机测试下SD卡的图片能不能显示。可以直接复制原子的设计,但要修改内存大小、增加concat_2和中断、增加MII接口和4bit concat。硬件架构下图。图中以太网使用的是GMII接口,125MHz x 8bit。4205板子使用MII的PHY芯片,GMII兼容MII,当GMII工作在25MHz x 4bit时就是MII(高4位不用)。PHY芯片自动侦测链路速率,给FPGA 25MHz或2.5MHz时钟。25MHz x 4bit=100Mbps,考虑到96clk帧间隔和包头,实际速率大约90几。

    在这里插入图片描述

    图中的中断concat_2必须接上,否则petalinux报错。综合编译导出硬件设计后产生以下文件备用:

    to_pl.bit
    fsbl.elf 
    xxx.hdf (实际上xxx.hdf文件就足以可以产生fsbl.elf 和to_pl.bit了,petalinux-build之后就有了)
    

    在ubuntu16.04环境编译 u-boot & Linux

    以下过程参考自CSDN文章

    全部采用18.3版本。也可以用最新版本,但版本必须一致。如果选择petalinux19.1之后版本,建议直接在ubuntu安装 Vitis

    下载:

    去XILINX官网下载 petalinux-v2018.3-final-installer.run
    https://github.com/Xilinx/linux-xlnx/releases/tag/xlnx_rebase_v4.14_2018.3
    https://github.com/Xilinx/u-boot-xlnx/releases/tag/xilinx-v2018.3
    https://github.com/Digilent/linux-digilent/blob/master/drivers/clk/clk-dglnt-dynclk.c
    https://github.com/Digilent/linux-digilent/blob/master/drivers/gpu/drm/xilinx/digilent_encoder.c
    

    执行过程:

    # 安装petalinux
    petalinux-v2018.3-final-installer.run ./petalinux
    unzip u-boot和linux压缩包到当前目录
    # 载入petalinux环境
    source ./petalinux/settings.sh
    # 建立petalinux工程
    petalinux-create --type project --template zynq --name proj
    # 拷贝vivado export hardware 中生成的 hdf文件到当前目录
    cp x.hdf ./
    # 配置hdf到petalinux工程
    cd proj
    petalinux-config --get-hw-description=../
    # 出现选择框(我们选择外部linux源码编译工程,因为我们需要在Linux中添加内核模块,两个.c文件)
    1. Image Packaging Configuration -> Root filesystem type 选项中选择 SD card
    2. Linux Components Selection -> linux-kernel选择ext-local-src。新增的External linux-kernel local source settings选项内填入你的刚解压linux源码完整路径 保存退出。
    # 进入之前解压后的xilinx linux目录
    cd ../linux-xlnx-xlnx_rebase_v4.14_2018.3
    #------------------------------------------------------------------------------------------
    # 增加内核驱动
    # 打开
    vi ./drivers/clk/Kconfig
    # 加入以下内容:
    config COMMON_CLK_DGLNT_DYNCLK
        tristate "Digilent axi_dynclk Driver"
        depends on ARCH_ZYNQ || MICROBLAZE
        help
        ---help---
          Support for the Digilent AXI Dynamic Clock core for Xilinx
          FPGAs.
    # 打开
    vi ./drivers/clk/Makefile
    # 加入以下内容:
    obj-$(CONFIG_COMMON_CLK_DGLNT_DYNCLK)   += clk-dglnt-dynclk.o
    # 打开
    vi ./drivers/gpu/drm/xilinx/Kconfig
    # 加入以下内容:
    config DRM_DIGILENT_ENCODER
        tristate "Digilent VGA/HDMI DRM Encoder Driver"
        depends on DRM_XILINX
        help
          DRM slave encoder for Video-out on Digilent boards.
    # 打开
    vi ./drivers/gpu/drm/xilinx/Makefile
    # 加入以下内容:
    obj-$(CONFIG_DRM_DIGILENT_ENCODER) += digilent_encoder.o
    
    # 加入刚下载的.c文件
    cp clk-dglnt-dynclk.c ./drivers/clk/
    cp digilent_encoder.c ./drivers/gpu/drm/xilinx/
    
    cd ../proj
    # 配置linux内核
    petalinux-config -c kernel
    1. 在 Device Drivers -> Graphics support,选择 Digilent VGA/HDMI DRM Encoder Driver 按 y
    2. 在 Device Drivers -> Common Clock Framework 选项中选择 Digilent axi_dynclk Driver 按 y
    3. 保存退出,要修改默认的保存文件名
    #------------------------------------------------------------------------------------------
    

    以上过程就是在Linux中增加新内核模块/驱动的方法,这个是编译进内核而不是编译成.ko驱动文件。可以参考原子《领航者 ZYNQ 之嵌入式Linux 开发指南 V1.5.2》第四篇驱动开发,讲的很详细。

    • 在对应目录加入.c文件
    • 修改对应目录内的Makefile、Kconfig。使之出现在make menuconfig菜单中

    # 进入petalinux工程编辑设备树
    vi project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
    

    【EBAZ4205+EBAZEXTV2】的设备树内容如下(EBAZEXTV2的LED/KEY外设未添加)。这东西在build之前看不到system-conf.dtsi的内容,否则就可以不依赖petalinux了。
    笔者不清楚pinctrl0部分的作用,一般而言,在fsbl配置好FPGA后,MIO的多路选择器已经选到正确的引脚,自带的pinctrl驱动看似是多余的。ebaz-4205设备树已经出现在最新Linux内核中,该设备树可能是假定你没有使用fsbl时编写的,比较完备。

    以下内容部分参考Linux内核中的设备树。与显示相关的是最后三个端点。

    /include/ "system-conf.dtsi"
    / {
        model = "EBAZ4205 board";
        compatible = "xlnx,zynq-EBAZ4205", "xlnx,zynq-7000";
    
        aliases {
            ethernet0 = &gem0;
            serial0 = &uart1;
            mmc0 = &sdhci0;
        };
    
        memory@0 {
            device_type = "memory";
            reg = <0x0 0x10000000>;
        };
    
        chosen {
            bootargs = "";
            stdout-path = "serial0:115200n8";
        };
    
        ebaz-keys {
         compatible = "gpio-keys";
         autorepeat;
         s2 {
               label = "s2";
               gpios = <&gpio0 20 0>;
               /*KEY_POWER*/
               linux,code = <116>;
               wakeup-source;
               autorepeat;
         };
    
         s3 {
               label = "s3";
               gpios = <&gpio0 32 0>;
               /*KEY_HOME*/
               linux,code = <102>;
               wakeup-source;
               autorepeat;
         };
        };
        ebaz-leds {
            compatible = "gpio-leds";
    
            led-green {
                label = "green";
                gpios = <&gpio0 54 1>;
                default-state = "on";
            };
                led-red {
                label = "red";
                gpios = <&gpio0 55 1>;
                default-state = "on";
            };
        };
    };
    
    &clkc {
        ps-clk-frequency = <33333333>;
    };
    
    &gem0 {
       status = "okay";
       phy-mode = "mii";
       phy-handle = <&phy>;
    
       /* PHY clock */
       assigned-clocks = <&clkc 18>;
       assigned-clock-rates = <25000000>;
    
       phy: ethernet-phy@0 {
          reg = <0>;
       };
    };
    
    &smcc {
        status = "okay";
    };
    /**/
    &nand0 {
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_nand0_default>;
    
        partition@0 {
            label = "nand-fsbl-uboot";
            reg = <0x0 0x8000000>;
        };
    };
    
    &pinctrl0 {
        pinctrl_nand0_default: nand0-default {
            mux {
                groups = "smc0_nand8_grp";
                function = "smc0_nand";
            };
    
            conf {
                groups = "smc0_nand8_grp";
                bias-pull-up;
            };
        };
    
        pinctrl_uart1_default: uart1-default {
            mux {
                groups = "uart1_4_grp";
                function = "uart1";
            };
    
            conf {
                groups = "uart1_4_grp";
                slew-rate = <0>;
                io-standard = <3>;
            };
        };
    };
    
    &sdhci0 {
        u-boot,dm-pre-reloc;
        status = "okay";
    };
    
    &uart1 {
        u-boot,dm-pre-reloc;
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_uart1_default>;
    };
    
    
    &amba_pl {
     
        hdmi_encoder_0:hdmi_encoder {
            compatible = "digilent,drm-encoder";
            //digilent,edid-i2c = <&i2c0>;//我没有使用DDC/I2C接口因此把这段注释,强制使用以下分辨率
            digilent,hpref = <1920>;
            digilent,vpref = <1080>;
        };
     
        xilinx_drm {
            compatible = "xlnx,drm";
            xlnx,vtc = <&v_tc_0>;
            xlnx,connector-type = "HDMIA";
            xlnx,encoder-slave = <&hdmi_encoder_0>;
            clocks = <&axi_dynclk_0>;
            dglnt,edid-i2c = <&i2c0>;
            planes {
                xlnx,pixel-format = "rgb888";
                plane0 {
                    dmas = <&axi_vdma_0 0>;
                    dma-names = "dma";
                };
            };
        };
    };
    &axi_dynclk_0 {
        compatible = "digilent,axi-dynclk";
        #clock-cells = <0>;
        clocks = <&clkc 15>;
    };
    &v_tc_0 {
        compatible = "xlnx,v-tc-5.01.a";
    };
    
    

    执行:

    petalinux-build
    # 由zimage产生uImage:
    petalinux-package --image -c kernel --format uImage
    # 将设备树名字改为devicetree.dtb
    mv xxxx.dtb devicetree.dtb
    # 如果更改了设备树dts,不用再petalinux-build ,编译设备树:
    petalinux-build -b device-tree
    

    产生uboot、内核(uImage)、根文件系统、设备树(devicetree.dtb)、zynq_fsbl.elf、xx.bit。这里只用设备树(devicetree.dtb)内核(uImage)

    uImage
    devicetree.dtb
    

    petalinux-build默认会产生image.ub和zImage镜像。image.ub是由zImage .dtb .cpio.gz 构成。.cpio.gz 是内存盘。我习惯用uboot的bootm启动uImage和加载设备树,所以将zImage转成uImage。用bootz启动zImage也可以。
    内存中的存放地址如下:

    • 0x0 devicetree_image
    • 0x8000 kernel_image
    • 0x1000000 ramdisk_image 如果使用内存盘就放到这个位置,本文使用SD卡上的ext4根目录,所以没用到

    进入u-boot源码。参考之前文章(EBAZ4205 ZYNQ 7Z010 u-boot & Linux 生成方法记录)的4、5、6、7、8、9、10步生成 BOOT.bindevicetree.dtb,注意第10步使用的是新的硬件bit和fsbl.elf(这两个文件可以从vivado&XILINX SDK生成也可以直接用上面petalinux-build产生的)。注意这里u-boot产生的devicetree.dtb是不需要的,用petalinux-build产生的。

    参照之前文章第12步编写uEnv.txt。

    BOOT.bin
    uEnv.txt
    

    BOOT.bin uEnv.txt uImage devicetree.dtb放入SD卡fat32分区,分区大小设置为100M左右。大部分进入U-BOOT但启动不了Linux的原因是U-BOOT的bootcmd变量设置不对。在运行bootcmd变量之前,U-BOOT会载入uEnv.txt内的环境变量,可以在环境变量内做一些操作跳过自动bootcmd。在uEnv.txt内修改bootcmd或者加入类似于uenvcmd=run bootm...的命令来指定启动过程。


    根文件系统

    可以直接使用的根文件系统:linaro-precise-ubuntu-desktop-20120723305.tar.gz(https://releases.linaro.org/archive/12.07/ubuntu/precise-images/ubuntu-desktop/)

    建议在命令行解压到SD卡ext4分区:

    sudo tar --strip-components=3 -C /media/xxx/rootfs -xzpf linaro-precise-ubuntu-desktop-20120723-305.tar.gz binary/boot/filesystem.dir
    

    分区工具用ubuntu自带的图形disk工具或者用 DiskGenius


    启动

    启动后在ebaz4205的自带串口登录,执行一些必要的修改:

    vi /etc/apt/sources.list
    # 源改为以下内容
    deb http://mirrors.ustc.edu.cn/ubuntu-old-releases/ubuntu/ precise main universe
    deb-src http://mirrors.ustc.edu.cn/ubuntu-old-releases/ubuntu/ precise main universe
    deb http://mirrors.ustc.edu.cn/ubuntu-old-releases/ubuntu/ precise-security main universe
    deb-src http://mirrors.ustc.edu.cn/ubuntu-old-releases/ubuntu/ precise-security main universe
    deb http://mirrors.ustc.edu.cn/ubuntu-old-releases/ubuntu/ precise-updates main universe
    deb-src http://mirrors.ustc.edu.cn/ubuntu-old-releases/ubuntu/ precise-updates main universe
    # 安装
    apt-get update
    apt-get install openssh-server mplayer fbi jfbterm
    

    该Linux内核已经支持HDMI上的 FrameBuffer 所有对显存的操作都是针对/dev/fb0文件的。FrameBuffer学习参考

    http://bbs.chinaunix.net/thread-1932291-1-1.html

    https://zhouyuqian.com/2020/05/11/ZYNQ-%E7%A7%BB%E6%A4%8D-Linux-Frame-Buffer/

    https://blog.yangl1996.com/post/ba-tu-pian-zhi-jie-xie-ru-framebuffer/
    framebuffer的c语言操作一般是这样的:

        // 完整的程序参见附录,该程序引用自原子《领航者 ZYNQ 之嵌入式Linux 开发指南 V1.5.2》。
        fd = open("/dev/fb0", O_RDWR);
        /* 获取framebuffer设备的参数信息 */
        ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
        ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
        screensize = fb_var.yres * fb_fix.line_length;
        // 用户内存到内核地址的映射
        // 如果不做映射,就相当于读写文件,读写文件函数会进入内核态运行,将数据拷贝到用户内存。多一次拷贝。
        // mmap在八股文中经常出现,总算是碰到实际应用了
        base = (unsigned char *)mmap(NULL, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        memset(base, 0x00, screensize);         // 显存清零
    

    打开桌面:

    # lightdm上电初期存在顺序bug,显示不正常,需要先关闭lightdm上电启动。
    echo  "manual" | sudo tee -a /etc/init/lightdm.override
    kill -9 <lightdm的pid>
    # 重新启动lightdm桌面
    startx &
    

    尝试播放视频

    一、图片文件与pdf文件浏览:

    fbi -T 1 1.JPG
    fbgs -c 1.pdf
    
    在这里插入图片描述

    二、视频播放,可以用mplayer:

    mplayer -lavdopts threads=2 -vo fbdev:/dev/fb0 -nosound /root/Videos/1080p1.mp4
    

    -vo fbdev:/dev/fb0 指定framebuffer文件。
    当采用两个线程时,帧数少许提高,cpu占用率为90%。帧数仍然很低。但是,能放1080P视频已经是意料之外了。


    ebaz1-4.gif

    三、中文显示(HDMI屏幕显示终端,串口输入按键):

    jfbterm
    
    在这里插入图片描述

    以上备忘
    有些地方仍有不足,实际复现时可能会遇到其他困难,如有疑问欢迎讨论学习

    附录

    /***************************************************************
     Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
     文件名    : lcdTest.c
     作者      : 邓涛
     版本      : V1.0
     描述      : LCD应用层测试程序
     其他      : 无
     论坛      : www.openedv.com
     日志      : 初版V1.0 2020/7/23 邓涛创建
     ***************************************************************/
    
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    #include <linux/fb.h>
    #include <sys/mman.h>
    #include <stdio.h>
    #include <string.h>
    
    static void display_demo_1 (unsigned char *frame, unsigned int width, unsigned int height, unsigned int stride)
    {
            unsigned int xcoi, ycoi;
            unsigned char wRed, wBlue, wGreen;
            unsigned int iPixelAddr = 0;
    
            for(ycoi = 0; ycoi < height; ycoi++) {
    
                    for(xcoi = 0; xcoi < (width * 3); xcoi += 3) {
    
                            if (((xcoi / 4) & 0x20) ^ (ycoi & 0x20)) {
                                    wRed = 255;
                                    wGreen = 255;
                                    wBlue = 255;
                            } else {
                                    wRed = 0;
                                    wGreen = 0;
                                    wBlue = 0;
                            }
    
                            frame[xcoi + iPixelAddr + 0] = wRed;
                            frame[xcoi + iPixelAddr + 1] = wGreen;
                            frame[xcoi + iPixelAddr + 2] = wBlue;
                    }
    
                    iPixelAddr += stride;
            }
    }
    
    static void display_demo_2 (unsigned char *frame, unsigned int width, unsigned int height, unsigned int stride)
    {
            unsigned int xcoi, ycoi;
            unsigned int iPixelAddr = 0;
            unsigned char wRed, wBlue, wGreen;
            unsigned int xInt;
    
            xInt = width * 3 / 8;
            for(ycoi = 0; ycoi < height; ycoi++) {
    
                    for(xcoi = 0; xcoi < (width*3); xcoi+=3) {
    
                            if (xcoi < xInt) {                                   //White color
                                    wRed = 255;
                                    wGreen = 255;
                                    wBlue = 255;
                            }
                            else if ((xcoi >= xInt) && (xcoi < xInt*2)) {         //YELLOW color
                                    wRed = 255;
                                    wGreen = 255;
                                    wBlue = 0;
                            }
                            else if ((xcoi >= xInt * 2) && (xcoi < xInt * 3)) {        //CYAN color
                                    wRed = 0;
                                    wGreen = 255;
                                    wBlue = 255;
                            }
                            else if ((xcoi >= xInt * 3) && (xcoi < xInt * 4)) {        //GREEN color
                                    wRed = 0;
                                    wGreen = 255;
                                    wBlue = 0;
                            }
                            else if ((xcoi >= xInt * 4) && (xcoi < xInt * 5)) {        //MAGENTA color
                                    wRed = 255;
                                    wGreen = 0;
                                    wBlue = 255;
                            }
                            else if ((xcoi >= xInt * 5) && (xcoi < xInt * 6)) {        //RED color
                                    wRed = 255;
                                    wGreen = 0;
                                    wBlue = 0;
                            }
                            else if ((xcoi >= xInt * 6) && (xcoi < xInt * 7)) {        //BLUE color
                                    wRed = 0;
                                    wGreen = 0;
                                    wBlue = 255;
                            }
                            else {                                                //BLACK color
                                    wRed = 0;
                                    wGreen = 0;
                                    wBlue = 0;
                            }
    
                            frame[xcoi+iPixelAddr + 0] = wRed;
                            frame[xcoi+iPixelAddr + 1] = wGreen;
                            frame[xcoi+iPixelAddr + 2] = wBlue;
                    }
    
                    iPixelAddr += stride;
            }
    }
    
    int main (int argc, char **argv)
    {
            struct fb_var_screeninfo fb_var = {0};
            struct fb_fix_screeninfo fb_fix = {0};
            unsigned int screensize;
            unsigned char *base;
            int fd;
    
            /* 打开LCD */
            fd = open("/dev/fb0", O_RDWR);
    
            if (fd < 0) {
                    printf("Error: Failed to open /dev/fb0 device.\n");
                    return fd;
            }
    
            /* 获取framebuffer设备的参数信息 */
            ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
            ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
    
            /* mmap映射 */
            screensize = fb_var.yres * fb_fix.line_length;
            base = (unsigned char *)mmap(NULL, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
            if ((unsigned char *)-1 == base) {
                    close(fd);
                    return -1;
            }
    
            memset(base, 0x00, screensize);         // 显存清零
    
            /* 循环显示不同颜色 */
            for ( ; ; ) {
    
                    display_demo_1(base, fb_var.xres, fb_var.yres, fb_fix.line_length);
                    sleep(2);
    
                    display_demo_2(base, fb_var.xres, fb_var.yres, fb_fix.line_length);
                    sleep(2);
            }
    
            /* 关闭设备 释放内存 */
            memset(base, 0x00, screensize);
            munmap(base, screensize);
            close(fd);
            return 0;
    }
    

    相关文章

      网友评论

          本文标题:ZYNQ EBAZ4205+HDMI扩展板 显示Linux桌面播

          本文链接:https://www.haomeiwen.com/subject/pbrllrtx.html