硬件描述
硬件需求
- ZYNQ7020 系列
- LCD 显示屏 800*600
- rtsp 网口摄像头
接线方式
- USB_UART 通过 type-c 线连接电脑,电脑需要安装 CH340 的串口驱动
- ps 端网口和摄像头的网口连接
- 开发板和摄像头的电源线连接
- LCD 显示屏的排线连接 pl 端排线接口
- SD 卡包含镜像等必须文件
开发环境需求
参考文档 3_【正点原子】启明星 ZYNQ 之嵌入式 Linux 开发指南_V3.2
参考文献 https://wdjxrrf0as.feishu.cn/docx/DNAUd22pWoPSrbxUbTMcwKk6n9e?from=from_copylink
- Ubuntu18 的虚拟机,见文档第一章
- 第二章是 linux 基本操作,可以了解
- MobaXterm,tftp,ssh,见第四章
- Petalinux2020.2,见第五章
- 5.5 的 vitis 非必须
- Petalinux 离线编译依赖,见 6.3
- Vivado2020.2
参考资料
链接:https://pan.baidu.com/s/1Tj_UvPEQUGVyvZW5Sf7CiA?pwd=xoec
提取码:xoec
参考依赖库
-
交叉编译完毕的 ffmpeg 和 opencv 库
-
使用时需要将 ffmpeg 中的 tmp/lib 下的文件放到开发板的 linux 系统的/usr/local/lib 和/usr/lib 下
-
使用时需要将 opencv 中的 install_arm/lib 下的文件放到开发板的 linux 系统的/usr/local/lib 和/usr/lib 下
-
上述操作使用 scp 命令,可以参考下面的
scp lib/* root@192.168.110.50:/usr/lib/ scp lib/* root@192.168.110.50:/usr/local/lib/
-
系统制作
目标实现 PS 端读取 RTSP 摄像头,发送到 PL 端的 LCD 显示屏
Vivado 配置
参考例程
- 参照例程《 3_【正点原子】启明星 ZYNQ 之嵌入式 Linux 开发指南_V3.2 》的 LCD 驱动实验,使用其 Vivado 工程进行配置
- 该例程配置了 LCD 的显示驱动,配置 AXI-Stream 相关 IP 核等,但是没有配置网口相关内容
- 参照例程《 2_【正点原子】启明星 ZYNQ 之嵌入式 Vitis 开发指南 v1_2 》的 lwip 的 echo server 实验,参考其配置网口相关芯片
具体流程
- 复制并打开 LCD 的 Vivado 工程,将其放在英文路径,项目路径在
【新资料-Vivado_2020.2-持续更新中】正点原子启明星(V2)ZYNQ开发板资料盘(A盘)\4_Source_Code\3_Embedded_Linux\Linux驱动例程\25_lcd_disp\vivado_prj\Navigator_7020
,将其另存为一个新的项目
- 在 Flow Navigator 中,点击 IP INTEGRATOR 下的 Open Block Design,双击 ZYNQ 的组件
- 配置网口引脚,并点击 OK,随后按 Crtl+S 保存修改,可以看到 Console 输出提示
- 右击 Source 下的后缀为 bd 的文件,生成 HDL 文件
所有的选项都是默认的,直接点下一步 - 生成 bitstream,会提示没有 Synthesis,直接下一步,会自动执行 Run Synthesis 和 Run Implementation
- 导出硬件描述文件
导出的 xsa 硬件描述文件,大小应该差不多是这个大小,如果小于他可能是没有包含 bit 流文件,会导致后续生成的镜像少文件,附上 xsa 文件
Petalinux 镜像制作
参考例程
- 参照例程《 3_【正点原子】启明星 ZYNQ 之嵌入式 Linux 开发指南_V3.2 》的 LCD 驱动实验,Linux 网络设备驱动实验和搭建驱动开发使用的 ZYNQ 镜像
编译 Petalinux
-
参考 20.1,创建 Petalinux 工程,创建完毕之后先不执行 20.1.1,先根据 6.3 配置 petalinux 离线编译的 yocto 等,随后根据教程一直执行到 20.2,20.3 暂时不执行
-
创建工程,导入硬件描述文件
petalinux-create -t project --template zynq -n ALIENTEK-ZYNQ-camera cd ALIENTEK-ZYNQ-camera/ petalinux-config --get-hw-description ../xsa_7020/camera/
-
其中,我的硬件描述文件和工程目录的相对位置为
- 执行 petalinux-config 时是在 ALIENTEK-ZYNQ-camera 下,
../
的意思是当前目录的上级目录,上级目录中有 xsa_7020,在../xsa_7020/camera/
下存放了 xsa 后缀的文件
- 执行 petalinux-config 时是在 ALIENTEK-ZYNQ-camera 下,
-
-
配置串口和 sd 卡启动
- 进入“Subsystem AUTO Hardware Settings ---> Serial Settings”选项,将“FSBL”和“DTG”两项使用的串口改成“uart_0”
- 进入“Subsystem AUTO Hardware Settings ---> Serial Settings”选项,将“FSBL”和“DTG”两项使用的串口改成“uart_0”
-
按四次“ESC”键返回顶层配置界面,进入“Image Packaging Configuration---> Root filesystem type (INITRD)”配置选项,将其更改为“EXT4(SD/eMMC/SATA/USB)”
-
配置“Yocto Settings --->Local sstate feeds settings--->local sstate feeds url”,取消“Enable Network sstate feeds”使能,此处参考 6.3,自行解压文件,配置路径
-
配置“Yocto Settings --->Add pre-mirror url”,删除原来的内容,添加 downloads 包文件路径,添加格式为“file://<路径>”,使能“Enable BB NO NETWORK”,此处参考 6.3,自行解压文件,配置路径
-
进入 Petalinux 工程,编辑工程下的 project-spec/meta-user/conf/petalinuxbsp.conf 文件,在文件末尾添加如下内容
PREMIRRORS_prepend = " \git://.*/.* file:///opt/tmp/downloads \n \ gitsm://.*/.* file:///opt/tmp/downloads \n \ ftp://.*/.* file:///opt/tmp/downloads \n \ http://.*/.* file:///opt/tmp/downloads \n \ https://.*/.* file:///opt/tmp/downloads \n"
file:///opt/tmp/downloads 要和上一步配的一致
-
生成 BOOT.BIN,在项目根目录下执行
petalinux-build -c bootloader
petalinux-build -c u-boot
petalinux-package --boot --fsbl --u-boot --dtb no --force
-
生成 boot.scr,位置在 images/linux
petalinux-build
- 参照 20.1.2,修改 boot.scr:
# Generate boot.scr: # mkimage -c none -A arm -T script -d boot.cmd.default boot.scr # ################for boot_target in ${boot_targets}; doif test "${boot_target}" = "jtag" ; thenbootm 0x00200000 0x04000000 0x00100000exit;fiif test "${boot_target}" = "mmc0" || test "${boot_target}" = "mmc1" ; thenif test -e ${devtype} ${devnum}:${distro_bootpart} /image.ub; thenfatload ${devtype} ${devnum}:${distro_bootpart} 0x10000000 image.ub;bootm 0x10000000;exit;fiif test -e ${devtype} ${devnum}:${distro_bootpart} /zImage; thenfatload ${devtype} ${devnum}:${distro_bootpart} 0x00200000 zImage;;fiif test -e ${devtype} ${devnum}:${distro_bootpart} /system.dtb; thenfatload ${devtype} ${devnum}:${distro_bootpart} 0x00100000 system.dtb;fiif test -e ${devtype} ${devnum}:${distro_bootpart} /rootfs.cpio.gz.u-boot; thenfatload ${devtype} ${devnum}:${distro_bootpart} 0x04000000 rootfs.cpio.gz.u-boot;bootm 0x00200000 0x04000000 0x00100000exit;fiif test -e ${devtype} ${devnum}:${distro_bootpart} /system.bit; then fatload ${devtype} ${devnum}:${distro_bootpart} 0x00800000 system.bit; fpga loadb 0 ${fileaddr} ${filesize} fibootz 0x00200000 - 0x00100000exit;fiif test "${boot_target}" = "xspi0" || test "${boot_target}" = "qspi" || test "${boot_target}" = "qspi0"; thensf probe 0 0 0;if test "image.ub" = "image.ub"; thensf read 0x10000000 0x1000000 0xF00000;bootm 0x10000000;exit;fiif test "image.ub" = "uImage"; thensf read 0x00200000 0x1000000 0x500000;sf read 0x04000000 0x1580000 0xA00000bootm 0x00200000 0x04000000 0x00100000exit;fiexit;fiif test "${boot_target}" = "nand" || test "${boot_target}" = "nand0"; thennand infoif test "image.ub" = "image.ub"; thennand read 0x10000000 0x1000000 0x6400000;bootm 0x10000000;exit;fiif test "image.ub" = "uImage"; thennand read 0x00200000 0x1000000 0x3200000;nand read 0x04000000 0x4600000 0x3200000;bootm 0x00200000 0x04000000 0x00100000exit;fifi done
-
使用 mkimage 生成 boot.scr,但是会提示没有这个工具
mkimage -c none -A arm -T script -d boot.cmd.default boot.scr
-
mkimage 可以先全局搜索,这个会在编译过程中生成,如下所示,找到和该路径相似的路径,将内容拷贝到/usr/bin 下,回到之前的目录再执行即可
sudo find / -name "*mkimage*"
cd /home/workspace/ALIENTEK-ZYNQ-camera/build/tmp/work/zynq_generic-xilinx-linux-gnueabi/linux-xlnx/5.4+gitAUTOINC+62ea514294-r0/recipe-sysroot-native/usr/bin/sudo cp mkimage uboot-mkimage /usr/bin/cd /home/workspace/ALIENTEK-ZYNQ-camera/images/linux/mkimage -c none -A arm -T script -d boot.cmd.default boot.scr
-
编译完成后会在 components/plnx_workspace/device-tree/device-tree/ 下生成设备树文件,设备树文件会参与接下来的内核编译,设备树文件都是根据 xsa 硬件描述文件生成的,我们只需要进行修改即可
-
system-conf.dtsi:这是一个包含特定于系统配置的定义和指令的设备树包含文件(. dtsi) 。它通常由 PetaLinux 工具根据项目配置自动生成。这个文件可能包括对其他
.dtsi
文件的引用,系统级别的配置,以及特定于板卡的设置。 -
zynq-7000.dtsi:这个文件定义了 Zynq-7000 SoC 的核心硬件资源和配置。它是一个通用的设备树包含文件,用于描述 Zynq-7000 SoC 的基本硬件结构,包括处理器、内存、中断控制器等。这个文件通常是不需要修改的,因为它描述的是 SoC 的固定硬件特性。
-
system-top.dts:这是主设备树源文件,它包括(通过
#include
指令)system-conf.dtsi
和其他必要的.dtsi
文件。system-top.dts
是描述整个系统硬件配置的顶层文件,它最终被编译成.dtb
文件,操作系统在启动时使用这个.dtb
文件来了解硬件配置。可以看到是 dts 引入了其他的设备树文件才能正确编译出 dtb 文件/** CAUTION: This file is automatically generated by Xilinx.* Version: * Today is: Wed Jun 12 03:11:33 2024*//dts-v1/; #include "zynq-7000.dtsi" #include "pl.dtsi" #include "pcw.dtsi" / {chosen {bootargs = "earlycon";stdout-path = "serial0:115200n8";};aliases {ethernet0 = &gem0;i2c0 = &i2c1;serial0 = &uart0;spi0 = &qspi;};memory {device_type = "memory";reg = <0x0 0x40000000>;}; }; #include "system-user.dtsi"
-
pl.dtsi:这个文件包含了与 PL(Programmable Logic,可编程逻辑)部分相关的设备树定义。在使用 Zynq-7000 SoC 的系统中,PL 部分是 FPGA,
pl.dtsi
通常描述了在 FPGA 中实现的自定义硬件模块(例如,自定义 IP 核)的配置和接口。 -
pcw.dtsi:这个文件包含了处理器配置向导(Processor Configuration Wizard,PCW)生成的配置信息。它描述了处理器子系统(PS)的配置,包括时钟、外设和接口的设置。这个文件是由 Xilinx 工具根据你的硬件设计自动生成的。
-
编译根文件系统
-
回到项目根目录下,配置根文件系统,这里不做修改直接退出
petalinux-config -c rootfs
-
编译根文件系统,生成 rootfs.tar.gz
petalinux-build -c rootfs
内核编译
参考例程
- 参照例程《 3_【正点原子】启明星 ZYNQ 之嵌入式 Linux 开发指南_V3.2 》的 LCD 驱动实验,Linux 网络设备驱动实验和搭建驱动开发使用的 ZYNQ 镜像
- 使用镜像为官方的 2020.2 的内核源码开发板资料盘(A 盘)\4_SourceCode\3_Embedded_Linux\资源文件\Kernel\ linux-xlnx-xlnx_rebase_v5.4_2020.2.tar.gz
添加设备树文件
-
参照 20.3.1 将设备树文件复制到指定目录
cd /home/workspace/ALIENTEK-ZYNQ-camera/components/plnx_workspace/device-tree/device-tree/ cp pcw.dtsi pl.dtsi zynq-7000.dtsi system-conf.dtsi system-top.dts /home/workspace/kernel/kernel-ethnet-camera/arch/arm/boot/dts/ cd /home/workspace/ALIENTEK-ZYNQ-camera/project-spec/meta-user/recipes-bsp/device-tree/files/ cp system-user.dtsi /home/workspace/kernel/kernel-ethnet-camera/arch/arm/boot/dts/
-
修改 system-user.dtsi,修改 system-conf.dtsi
-
system-user.dtsi 如下所示
///include/ "system-conf.dtsi" #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/input/input.h> #include <dt-bindings/media/xilinx-vip.h> #include <dt-bindings/phy/phy.h> / { model = "Alientek Navigator Zynq Development Board"; compatible = "xlnx,zynq-zc702", "xlnx,zynq-7000"; chosen { bootargs = "console=ttyPS0,115200 earlycon root=/dev/mmcblk0p2 rw rootwait"; stdout-path = "serial0:115200n8"; }; }; &uart0 { u-boot,dm-pre-reloc; status = "okay"; }; &sdhci0 { u-boot,dm-pre-reloc;status = "okay"; }; &gem0 { local-mac-address = [00 0a 35 00 8b 87]; phy-handle = <ðernet_phy>; ethernet_phy: ethernet-phy@7 { /* yt8521 */ reg = <0x7>; device_type = "ethernet-phy"; }; };
-
虽然这里注释掉了 system-conf.dtsi,理论上不会引用这个文件,但是保险起见还是修改一下,主要修改网口的内容,这个文件里的 gem0 的值是错的,参考上述的内容
-
设备树的文件节点可以理解为追加和覆盖,在 pl.dtsi 和 pcw.dtsi 以及 zynq-7000.dtsi 中都定义了一些设备树的节点,以网口为例,后追加的信息,会覆盖前面已经有的信息,比如 gem0 的 local-mac-address 属性,后写入的覆盖之前的
-
-
修改 arch/arm/boot/dts 目录下 Makefile 文件,将添加后的设备树顶层文件放到 Makefile 中
- vim 编辑器支持查找功能便于快速定位这一行
添加 LCD 显示驱动
-
参照 49.5.1 创建 LCD 节点的第五点,前面的可以不用看,修改内核目录下的 arch/arm/boot/dts/system-user.dtsi,这里直接给完整的内容,内容包含前置的修改的 system-user.dtsi
#include <dt-bindings/gpio/gpio.h> #include <dt-bindings/input/input.h> #include <dt-bindings/media/xilinx-vip.h> #include <dt-bindings/phy/phy.h>/ {model = "Alientek Zynq Development Board";compatible = "xlnx,zynq-zc702", "xlnx,zynq-7000"; chosen {bootargs = "console=ttyPS0,115200 earlycon root=/dev/mmcblk0p2 rw rootwait";stdout-path = "serial0:115200n8";};#if 0backlight_lcd: backlight {compatible = "pwm-backlight";pwms = <&pwm_0 0 5000000>;brightness-levels = <0 10 20 30 40 50 60 70 80 90 100>;default-brightness-level = <10>;}; #endif };&uart0 {u-boot,dm-pre-reloc;status = "okay"; };&sdhci0 {u-boot,dm-pre-reloc;status = "okay"; };&gem0 {local-mac-address = [00 0a 35 00 8b 87];phy-handle = <ðernet_phy>;ethernet_phy: ethernet-phy@7 { /* yt8521 */reg = <0x7>;device_type = "ethernet-phy";}; };&qspi {u-boot,dm-pre-reloc;flash@0 { /* 16 MB */compatible = "w25q256", "jedec,spi-nor";reg = <0x0>;spi-max-frequency = <50000000>;#address-cells = <1>;#size-cells = <1>;partition@0x000000 {label = "boot";reg = <0x000000 0x600000>;};partition@0x600000 {label = "bootenv";reg = <0x600000 0x020000>;};partition@0x620000 {label = "image.ub";reg = <0x620000 0x700000>;};partition@0xD20000 {label = "boot.scr";reg = <0xD20000 0x020000>;};partition@0xD40000 {label = "space";reg = <0xD40000 0x000000>;};}; };&pwm_0 {compatible = "digilent,axi-pwm";#pwm-cells = <2>;clock-names = "pwm";npwm = <1>; };/* RGB LCD */ &lcd_vtc {compatible = "xlnx,v-tc-5.01.a"; };&lcd_vdma {dma-ranges = <0x00000000 0x00000000 0x40000000>; // 1GB };&amba_pl {xlnx_vdmafb_lcd {compatible = "xilinx,vdmafb";status = "okay";vtc = <&lcd_vtc>;clocks = <&clk_wiz_0 0>;clock-names = "lcd_pclk";dmas = <&lcd_vdma 0>;dma-names = "lcd_vdma";pwms = <&pwm_0 0 5000000>;reset-gpio = <&gpio0 54 GPIO_ACTIVE_LOW>;lcdID-gpio = <&gpio1 0 0 GPIO_ACTIVE_LOW &gpio1 1 0 GPIO_ACTIVE_LOW &gpio1 2 0 GPIO_ACTIVE_LOW>;display-timings {timing_4342: timing0 {clock-frequency = <9000000>;hactive = <480>;vactive = <272>;hback-porch = <40>;hfront-porch = <5>;hsync-len = <1>;vback-porch = <8>;vfront-porch = <8>;vsync-len = <1>;hsync-active = <0>;vsync-active = <0>;de-active = <1>;pixelclk-active = <0>;};timing_4384: timing1 {clock-frequency = <31000000>;hactive = <800>;vactive = <480>;hback-porch = <88>;hfront-porch = <40>;hsync-len = <48>;vback-porch = <32>;vfront-porch = <13>;vsync-len = <3>;hsync-active = <0>;vsync-active = <0>;de-active = <1>;pixelclk-active = <0>;};timing_7084: timing2 {clock-frequency = <40330000>;hactive = <800>;vactive = <480>;hback-porch = <88>;hfront-porch = <40>;hsync-len = <128>;vback-porch = <33>;vfront-porch = <10>;vsync-len = <2>;hsync-active = <0>;vsync-active = <0>;de-active = <1>;pixelclk-active = <0>;};timing_7016: timing3 {clock-frequency = <51200000>;hactive = <1024>;vactive = <600>;hback-porch = <140>;hfront-porch = <160>;hsync-len = <20>;vback-porch = <20>;vfront-porch = <12>;vsync-len = <3>;hsync-active = <0>;vsync-active = <0>;de-active = <1>;pixelclk-active = <0>;};timing_1018: timing4 {clock-frequency = <71100000>;hactive = <1280>;vactive = <800>;hback-porch = <80>;hfront-porch = <70>;hsync-len = <10>;vback-porch = <10>;vfront-porch = <10>;vsync-len = <3>;hsync-active = <0>;vsync-active = <0>;de-active = <1>;pixelclk-active = <0>;};};};};
-
参照 49.6.1 添加 LCD 驱动程序到内核,上传 atk_vdmafb.c,xilinx_drm_drv.h、xilinx_vtc.c 和 xilinx_vtc.h 到 drivers/gpu/drm/xlnx
cd drivers/gpu/drm/xlnx/
-
打开 drivers/gpu/drm/xlnx 目录下的 Makefile 文件,在文件末尾添加如下内容:
obj-$(CONFIG_FB_XILINX_VDMA) += xilinx_vtc.o obj-$(CONFIG_FB_XILINX_VDMA) += atk_vdmafb.o
-
修改 Kconfig 文件,打开 drivers/gpu/drm/xlnx 目录下的 Kconfig 文件,在文件末尾添加如下内容:
config FB_XILINX_VDMA tristate "Xilinx LCD Framebuffer driver support By Alientek" depends on FB && ARCH_ZYNQ && XILINX_DMA select FB_SYS_FILLRECT select FB_SYS_COPYAREA select FB_SYS_IMAGEBLIT select FB_MODE_HELPERS select VIDEOMODE_HELPERS help LCD framebuffer driver based on vdma,vtc,dyclk IP core.
添加 PWM 驱动
-
将 pwm 的驱动文件拷贝到 drivers/pwm
-
打开 drivers/pwm 目录下的 Makefile 文件,在文件末尾添加如下内容
obj-$(CONFIG_PWM_DGLNT) += pwm-dglnt.o
-
打开 drivers/pwm 目录下的 Kconfig 文件,在文件末尾前添加如下内容,注意相对位置
config PWM_DGLNT tristate "Digilent AXI PWM driver support" depends on ARCH_ZYNQ || ARCH_ZYNQMP help Simple Driver for Digilent AXI_PWM IP Core. To compile this driver as a module, choose M here: the module will be called pwm-dglnt.c
开始编译
-
配置 defconf,可能会出错
make xilinx_zynq_defconfig
-
参考第十章,10.6 及以前的配置,配置好交叉编译环境,其中后面的路径是你交叉编译环境的,配置完成后需要重启
echo "alias esdk='source /opt/pkg/petalinux/2020.2/environment-setup-cortexa9t2hf-neon-xilinx-linux-gnueabi'" >> ~/.bashrc
-
先运行 esdk,然后在执行就可以了
-
-
配置内核,先参照 49.6.3
make menuconfig
- Device Drivers ---> Graphics support ---> <*> Xilinx LCD Framebuffer driver support By Alientek
- Device Drivers ---> Common Clock Framework ---> <*> Xilinx Clocking Wizard
- Device Drivers --->[] Pulse-Width Modulation (PWM) Support ---> <> Digilent AXI PWM driver support
-
再参照 54.4.1 打开 Device Drivers ---> [ * ]Network device support ---> -- PHY Device support and infrastructure ---> <> Xilinx GMII2RGMII converter driver
-
至此配置完成,按 esc 退出,退出时记得选 yes 保存配置
-
启动编译
make -j8 make dtbs
-
内核编译完成后会在 arch/arm/boot 目录下生成内核镜像文件 zImage,在 arch/arm/boot/dts 目录下生成设备树镜像文件 system-top.dtb
-
命令“make dtbs”将。dts 编译成。dtb,编译完成后就可以使用新的设备树镜像文件,放到 sd 卡时需要将 system-top.dtb 修改为 system.dtb
-
制作启动 SD 卡
参考例程
- 参照例程《 3_【正点原子】启明星 ZYNQ 之嵌入式 Linux 开发指南_V3.2 》的 Petalinux 设计流程实战,其中 6.2.10 制作 SD 启动卡
SD 卡分区配置
-
将 SD 卡插入 Ubuntu 系统,查看设备上挂载的存储设备和分区信息
sudo fdisk -l
- 可以根据 sd 卡的容量判断设备 sda1 就是刚刚插入 ubuntu 系统的 sd 卡
-
查看当前文件目录的挂载情况
df -h
- 现在 SD 卡还没有分区还没有挂载所以看不见
-
使用
fdisk
工具来对指定的设备进行分区操作(如果你前面显示的 sd 卡是 sda1,下面试试使用 sda,不然可能会报错)sudo fdisk /dev/sda
-
先将之前的分区删除,输入 d
-
按 n 新建分区,输入 p 创建主分区并回车,输入 1 使用分区号 1 并回车,回车使用第一个扇区 2048,输入 +100 使第一个分区大小 100M
-
设置分区类型,先按 t 再按 c
-
设置为引导分区,输入 a 回车
-
按 n 创建第二个分区,一路回车即可
-
输入 p 检查分区情况,输入 w 写入 sd 卡
-
格式化 sd 卡的分区
sudo mkfs.vfat -F 32 -n boot /dev/sda1 sudo mkfs.ext4 -L rootfs /dev/sda2
-
制作 SD 卡
-
我们需要的文件包含启动文件 BOOT.BIN,boot.scr,system.bit,zImage,system.dtb,需要放在第一个分区,rootfs.tar.gz 是根文件系统放在第二个分区
-
挂载刚刚创建的 sd 卡分区
-
先在/mnt 创建文件夹用于挂载分区,然后再使用 mount 挂载
sudo mkdir -p /mnt/sda1 sudo mkdir -p /mnt/sda2
sudo mount /dev/sda1 /mnt/sda1 sudo mount /dev/sda2 /mnt/sda2
-
使用 df 命令可以查看分区是否挂载成功
-
-
拷贝文件到 SD 卡的分区中,system.bit、BOOT.BIN 和 boot.scr 在上文 4.2.2 中编译的 Petalinux 的 image/linux 中;zImage 和 system-top.dtb 在上文 4.3.5 内核编译的内核源码路径,arch/arm/boot 目录下生成内核镜像文件 zImage,在 arch/arm/boot/dts 目录下生成设备树镜像文件 system-top.dtb
cd /home/workspace/ALIENTEK-ZYNQ-camera/images/linux/ sudo cp BOOT.BIN boot.scr system.bit /mnt/sda1/
cd /home/workspace/kernel/kernel-ethnet-camera/arch/arm/boot/ sudo cp zImage dts/system-top.dtb /mnt/sda1/
sudo mv /mnt/sda1/system-top.dtb /mnt/sda1/system.dtb
-
将根文件系统解压到 SD 卡的第二个分区,rootfs.tar.gz 也在项目目录下的
cd /home/workspace/ALIENTEK-ZYNQ-camera/images/linux/ sudo tar -xzf rootfs.tar.gz -C /mnt/sda2/
-
至此,sd 卡制作成功,卸载 SD 卡准备启动
sudo umount /dev/sda*
验证是否成功
- 开发板的拨码开关选为 OFF,为 SD 卡启动模式,然后按照前文 1.2 连接线,LCD 显示了字符,进入之后网口设置 ip 和网段可以 ping 视为成功
- 此处附本次实验的编译成功的文件
PS 端程序编写
开发环境配置
-
基本的 x86 架构的 C++编译环境配置:ubuntu 下安装 C/C++ 开发环境_ubuntu 安装 c++开发环境-CSDN 博客
-
配置 arm 的编译环境
sudo apt-get update sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
-
和博客中相比只需要修改一下 gcc 和 g++的路径即可,默认安装都是在/usr/bin 下
-
注意每一个 toolchain 都要有一个对应的 cmake 配置项,切换编译环境时要将对应的项上移
-
编译成功会提示你的编译结果在哪里
-
-
本项目的代码依赖于 opencv 模块,需要有交叉编译版本的 opencv
-
OpenCv 简易编译,官网下载 SourceCode,在目标目录下解压,进入目录后创建 build 和 install
cd /home/workspace/opencv-4.6.0 mkdir install_arm mkdir build_arm && cd build_arm
-
使用
cmake
并指定工具链文件来配置 OpenCVcmake -DCMAKE_TOOLCHAIN_FILE=../platforms/linux/arm-gnueabi.toolchain.cmake \-DCMAKE_INSTALL_PREFIX=/home/workspace/opencv-4.6.0/install_arm \-D WITH_FFMPEG=ON \-D FFMPEG_INCLUDE_DIR=/home/workspace/ffmpeg-4.4.4/tmp/include \-D FFMPEG_LIB_DIR=/home/workspace/ffmpeg-4.4.4/tmp/lib \-D CMAKE_BUILD_TYPE=RELEASE \-D BUILD_EXAMPLES=OFF \..
-
编译 opencv
make -j4 && make install
-
CMakeList 配置
cmake_minimum_required(VERSION 3.3) project(Camera)set(CMAKE_CXX_STANDARD 14) set(OpenCV_DIR "/home/workspace/opencv-4.6.0/install_arm/lib/cmake/opencv4") find_package(OpenCV REQUIRED) # 添加 OpenCV 的头文件路径 include_directories(${OpenCV_INCLUDE_DIRS})# 添加库文件搜索路径 link_directories(${OpenCV_LIBRARY_DIRS})add_executable(Camera main.cpp)# 链接OpenCV库到您的项目 target_link_libraries(Camera ${OpenCV_LIBS})
-
-
上述的交叉编译能够编译出二进制文件,但是无法在 petalinux 运行,原因是缺少 FFMPEG 库,可以参考OpenCv+FFmpeg交叉编译,编译出 3.3 的参考依赖库然后移植
摄像头源码
#include <opencv2/opencv.hpp>
#include <iostream>
#include <unistd.h>
#include <termios.h>
#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 colorwRed = 255;wGreen = 255;wBlue = 255;}else if ((xcoi >= xInt) && (xcoi < xInt*2)) { //YELLOW colorwRed = 255;wGreen = 255;wBlue = 0;}else if ((xcoi >= xInt * 2) && (xcoi < xInt * 3)) { //CYAN colorwRed = 0;wGreen = 255;wBlue = 255;}else if ((xcoi >= xInt * 3) && (xcoi < xInt * 4)) { //GREEN colorwRed = 0;wGreen = 255;wBlue = 0;}else if ((xcoi >= xInt * 4) && (xcoi < xInt * 5)) { //MAGENTA colorwRed = 255;wGreen = 0;wBlue = 255;}else if ((xcoi >= xInt * 5) && (xcoi < xInt * 6)) { //RED colorwRed = 255;wGreen = 0;wBlue = 0;}else if ((xcoi >= xInt * 6) && (xcoi < xInt * 7)) { //BLUE colorwRed = 0;wGreen = 0;wBlue = 255;}else { //BLACK colorwRed = 0;wGreen = 0;wBlue = 0;}frame[xcoi+iPixelAddr + 0] = wRed;frame[xcoi+iPixelAddr + 1] = wGreen;frame[xcoi+iPixelAddr + 2] = wBlue;}iPixelAddr += stride;}
}
static void write_frame_to_lcd(unsigned char* frameBuffer, const cv::Mat& frame, unsigned int lcdWidth, unsigned int lcdHeight, unsigned int lcdStride) {// 缩放图像以适应 LCD 尺寸cv::Mat resizedFrame;cv::resize(frame, resizedFrame, cv::Size(lcdWidth, lcdHeight));unsigned int xcoi, ycoi;unsigned int iPixelAddr = 0;for (ycoi = 0; ycoi < lcdHeight; ycoi++) {for (xcoi = 0; xcoi < lcdWidth; xcoi++) {// 获取缩放后的图像中的像素值cv::Vec3b pixel = resizedFrame.at<cv::Vec3b>(ycoi, xcoi);// RGB 值unsigned char wRed = pixel[2];unsigned char wGreen = pixel[1];unsigned char wBlue = pixel[0];// 写入 LCD 帧缓冲区frameBuffer[iPixelAddr + (xcoi * 3) + 0] = wRed;frameBuffer[iPixelAddr + (xcoi * 3) + 1] = wGreen;frameBuffer[iPixelAddr + (xcoi * 3) + 2] = wBlue;}iPixelAddr += lcdStride;}
}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); // 显存清零cv::VideoCapture capture("rtsp://192.168.110.13:554/bk_livestream/1");if (!capture.isOpened()) {std::cerr << "ERROR! Unable to open URL\n";return -1;}cv::Mat frame;cv::Mat frameRGB;cv::VideoWriter writer;int frame_width = static_cast<int>(capture.get(cv::CAP_PROP_FRAME_WIDTH));int frame_height = static_cast<int>(capture.get(cv::CAP_PROP_FRAME_HEIGHT));//下面的代码用于写入视频文件//writer.open("/home/root/output.avi", cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), 10,cv::Size(frame_width, frame_height));/*if (!writer.isOpened()) {std::cerr << "Could not open the output video file for write\n";return -1;}*/int frameNumber = 0;while (true) {//LCD的显示例程//display_demo_1(base, fb_var.xres, fb_var.yres, fb_fix.line_length);if (!capture.read(frame)) {std::cerr << "No frame\n";break;}if (!frame.empty()) {std::cout << "Frame " << ++frameNumber;std::cout << " - Size: " << frame.size();std::cout << " - Type: " << frame.type();std::cout << std::endl;//写入视频文件的代码//writer.write(frame); // Write the frame without checking return value} else {std::cerr << "Empty frame\n";}if (frameNumber == 100*3){break;}cv::cvtColor(frame, frameRGB, cv::COLOR_YUV2RGB);// 再次检查转换后的图像是否为空if (frameRGB.empty()) {std::cout << "Conversion failed: Resulting frame is empty." << std::endl;return -1;}write_frame_to_lcd(base, frameRGB, 800, 480, 800 * 3);//LCD的显示例程//display_demo_2(base, fb_var.xres, fb_var.yres, fb_fix.line_length);}capture.release();//writer.release();std::cout << "Video capture and write ended." << std::endl;/* 关闭设备 释放内存 */memset(base, 0x00, screensize);munmap(base, screensize);close(fd);return 0;
}
启动程序
-
RTSP 视频依赖于以太网,因此开发板启动后要配置好 IP,开发板的 IP 要和摄像头在同一个网段
-
务必保证 3.3 的依赖库按照要求移植到开发板
-
使用 scp 命令将编译好的二进制程序放到开发板
scp Camera root@192.168.110.50:/home/root/Camera
-
然后在开发板的/home/root 目录下启动程序,启动之前修改一下程序权限
chmod 777 Camera ./Camera