i.MX6ULL开发板uboot/kernel/rootfs移植傻瓜教程【建议收藏】

一口Linux 2021-08-07 19:23

本系列教程以「i.MX6ULL」处理器的ARM开发板为实验基础,学习记录嵌入式Linux开发的各种知识与经验,主要内容包括嵌入式Linux移植,嵌入式Linux驱动开发,嵌入式Linux应用开发等。

本系列教程将以野火的i.MX6ULL eMMC开发板为硬件基础,以「野火EBF6ULL Pro开发板教程」「正点原子i.MX6ULL阿尔法开发板教程」为参考,进行学习实践。


uboot移植初探

1 嵌入式Linux移植概述

Linux 的移植主要包括3部分:

  • 移植「bootloader 代码」, Linux 系统要启动就必须需要一个 bootloader 程序,也就说芯片上电以后先运行一段bootloader程序。 这段bootloader程序会先初始化DDR等外设, 然后将Linux内核从flash(NAND,NOR FLASH,SD,MMC 等)拷贝到 DDR 中,最后启动 Linux 内核。 bootloader 有很多,常用的就是 U-Boot。

    bootloader 和 Linux 内核的关系就跟 PC 上的 BIOS 和 Windows 的关系一样,bootloader 就相当于 BIOS。

  • 移植「Linux 内核」,Linux内核由一系列程序组成,包括负责响应中断的中断服务程序、负责管理多个进程从而分享处理器时间的调度程序、负责管理地址空间的内存管理程序、网络、进程间通信的系统服务程序等。内核负责管理系统的硬件设备。

  • 移植「根文件系统(rootfs)」,Linux 中的根文件系统更像是一个文件夹或者叫做目录,在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是 Linux 运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。根文件系统里面包含了一些最常用的命令和文件。

    「U-Boot、Linux kernel和rootfs」 这三者一起构成了一个完整的Linux系统,一个可以正常使用、功能完善的Linux系统。

2 实验开发板简介

本测试使用的开发板为野火的i.MX6ULL eMMC开发板

3 U-Boot简介

uboot 的全称是「Universal Boot Loader」,遵循 GPL 协议的开源软件。

uboot 是一个裸机代码,可以看作是一个裸机综合例程。现在的 uboot 已经支持「液晶屏、网络、USB」等高级功能。uboot 官网为 https://www.denx.de/wiki/U-Boot/

可以在uboot官网下载uboot源码,点击左侧 Topics 中的“Source Code”,然后点击的“FTP Server” ,进入其 FTP 服务器即可看到 uboot 源码。

但我们移植uboot时一般不会直接用 uboot 官方的源码的,官方的源码是给半导体厂商准备的,半导 体厂商会根据自家的芯片,维护自己芯片对应的uboot。

NXP(freescale)维护的的uboot地址: https://github.com/Freescale/u-boot-fslc

4 NXP uboot测试

uboot移植并不需要从零开始将 uboot 移植到我们现在所使用的开发板上。因为半导体厂商通常都会自己做一个开发板「原厂开发板」,将uboot移植到他们自己的原厂开发板上,再将这个uboot(原厂BSP 包)发布出去。

市面上的开发板,通常会参考原厂的开发板做硬件,然后在原厂提供的 BSP 包上做修改,如正点原子和野火的 I.MX6ULL 开发板参考的就 是「NXP官方的I.MX6ULL EVK开发板」做的硬件:

4.1 编译环境搭建

4.1.1 交叉编译器下载

嵌入式Linux开发,程序编译通常在电脑端的Linux(如虚拟机中的Ubuntu)下进行编译,Ubuntu 自带gcc 编译器,但该编译器是针对 X86 架构的!而嵌入式Linux是ARM架构的, 所以需要一个在 X86 架构上可以编译 ARM 架构代码的 gcc编译器,即「交叉编译器」

交叉编译器有很多,本实验使用 Linaro 出品的交叉编译器,下载地址:

https://releases.linaro.org/components/toolchain/binaries/4.9-2017.01/arm-linux-gnueabihf/

4.1.2 交叉编译器安装

在Ubuntu中创建目录:/usr/local/arm,并将下载的gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz复制到此文件中,然后「解压」,解压命令如下:

sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz

解压完成以后会生成一个名为“gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf”的文件夹,这个文件夹里面就是我们的交叉编译工具链。

然后,需要将该目录「添加到环境变量」中。打开/etc/profile 以后,在最后面输入如下所示内容:

export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin 

使用交叉编译器之前还需要「安装其它的库」,命令如下:

sudo apt-get install lsb-core lib32stdc++6 

安装完之后,可以「查看交叉编译工具的版本号」,输入如下命令:

arm-linux-gnueabihf-gcc -v 

可以看到类似如下打印

以看出当前交叉编译器的版本号为 4.9.4,说明交叉编译工具链安装成功。

4.2 编译原厂uboot

编译前还要在Ubuntu 中「安装ncurses 库」,安装命令如下:

sudo apt-get install libncurses5-dev 

在Ubuntu中创建存放uboot的目录,如我的目录是:/home/xxpcb/myTest/imx6ull/uboot/nxp_uboot

然后,将「NXP(freescale)的uboot源码」复制进来,这里使用的是「正点原子」提供的NXP官方原版Uboot源码包( uboot-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2)

然后进行解压:

tar -vxjf uboot-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2

解压后的源码文件如下:

首先看下「uboot的配置」,configs 目录下有很多跟 I.MX6UL/6ULL 有关的配置,找到与mx6ull相同的,如下图。

因为我这个开发板是emmc版本的,所有就使用这个mx6ull_14x14_evk_emmc_defconfig

编译uboot使用下面3条指令:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8

这3条命令中 :

  • ARCH=arm 设置目标为 arm 架构

  • CROSS_COMPILE 指定所使用的交叉编译器。

  • 第1条命令相当于make distclean,目的是清除工程,一般在第一次编译的时候最好清理一下工程。

  • 第2条指令相当于make mx6ull_14x14_evk_emmc_defconfig,用于配置 uboot,配置文件为 mx6ull_14x14_evk_emmc_defconfig。

  • 第3条指令相当于make -j8,也就是使用8核来编译uboot。

为了方便的执行着3条指令,可以「将这些指令写成shell脚本」,比如在uboot源码目录下新建一个build.sh文件,写入如下内容:

#!/bin/bash

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8

然后进行编译:

./build.sh

编译完成以后uboot 源码多了一些文件,其中u-boot.bin就是编译出来的 uboot二进制文件。 uboot是个裸机程序, 因此需要在其前面

加上头部(IVT、 DCD等数据)才能在I.MX6U上执行,u-boot.imx 文件就是添加头部以后的 u-boot.bin。

u-boot.imx 就是我们最终要烧写到开发板中的 uboot 镜像文件。

4.3 烧录开发板

这是的烧录开发板,实际是要「烧录到SD卡中」,然后将SD卡插入开发板,让开发板从SD卡启动(需要在开发板上设置拨码开关来选择启动方式)。

4.3.1 烧录到SD卡

「正点原子」专门编写了一个小软件用来将编译出来的.bin 文件烧写到 SD 卡中,这个软件叫做“imxdownload

将imxdownload 复制到 Ubuntu 中的uboot源码文件夹,再使用如下指令,给予 imxdownload 可执行权限:

chmod 777 imxdownload

然后「电脑USB中插入SD卡(读卡器)」,并在虚拟机中设置usb加载(VMware或VirtualBox虚拟机需要先安装「增强功能」才能使用)

然后可以使用如下指令来查看SD卡的挂载标识符:

ls /dev/sd* 

查看输出结果:

这里的/dev/sdb就是我的SD卡。

注:我第一次使用SD卡烧录时,只多出了/dev/sdb,但不知什么情况,用了几次后,再插入SD卡,就会同时多出来/dev/sdb和/dev/sdb1,但实际测 试,仍然把程序烧录到/dev/sdb也能用)。

imxdownload向SD卡烧写led.bin文件,命令格式如下:

./imxdownload u-boot.bin /dev/sdb 

注意不能烧写到/dev/sdasda1设备里面!那是系统磁盘。

烧写过程会输入如下信息:

烧写的最后一行会显示烧写大小、用时和速度,比如u-boot.bin烧写到SD卡中的大小是 423KB,用时 1.7s,烧写速度是 236KB/s。

注意这个烧写速度,如果这个烧写速度在几百KB/s以下那么就是正常烧写。 如果这个烧写速度大于几十MB/s、甚至几百MB/s那么肯定是烧写失败了! 重新插拔/格式化SD卡或重启ubuntu再试。

烧写完成以后会在当前工程目录下生成一个load.imx的文件,这个文件就是软件 imxdownload 根据 NXP 官方启动方式介绍的内容, 在 bin 文件前面添加了一些数据头以后生成的。最终烧写到SD卡里面的就是这个imx文件。

4.3.2 启动开发板

烧录完之后,将「SD卡插入开发板启动」,使用「串口连接电脑」,查看uboot启动信息。 设置好串口参数(波特率115200)并打开,按键「复位开发板」。 当串口打印上出现Hit any key to stop autoboot倒计时的时候「按下 键盘上的回车键」,默认是 3 秒倒计时,在 3 秒倒计时结束以后如果没有按下回车键的话 uboot 就会使用默认参数来启动 Linux 内核了。

如果在 3 秒倒计时结束之前按下回车键,那么就会进入 uboot 的命令行模式:

解读一下这些信息的含义:

  • 第1行是 uboot 「版本号和编译时间」:当前的 uboot 版本号是 2016.03,编译时间是 2021/7 /11/15:22:25
  • 第3、4 行是 「CPU 信息」:当前使用的 CPU 是飞思卡尔(属于NXP)的 I.MX6ULL (频率为 792MHz),此时运行在 396MHz。这颗芯片是工业级的,结温为-40°C~105°C
  • 第 5 行是「复位原因」:I.MX6ULL 芯片上有个 POR_B 引脚,将这个引脚拉低即可复位 I.MX6ULL。
  • 第 6 行是「板子名字」,“MX6ULL 14x14 EVK”即NXP原厂开发板的名字 。
  • 第 7 行提示 「I2C 准备就绪」
  • 第 8 行提示当前板子的「DRAM(内存)」 为 512MB
  • 第 9 行提示当前有「两个MMC/SD 卡控制器」:FSL_SDHC(0)和 FSL_SDHC(1)。I.MX6ULL支持两个 MMC/SD,正点原子的 I.MX6ULL EMMC 核心板上 FSL_SDHC(0)接的 SD(TF)卡,FSL_SDHC(1)接的 EMMC。
  • 第10行是一条警告信息,先忽略。
  • 第 12、13 行是 「LCD 型号」,原厂默认的是TFT43AB (480x272)。
  • 第 14~16 是「标准输入、标准输出和标准错误」所使用的终端,这里都使用串口(serial)作为终端。
  • 第 17 、18行是「切换到emmc的第0个分区上」,因为当前的 uboot 是 emmc 版本的,也就是从 emmc 启动的。我们只是为了方便将其烧写到了 SD 卡上,但是它的“内心”还是 EMMC的。所以 uboot 启动以后会将 emmc 作为默认存储器 。
  • 第 19行是「网口信息」,提示我们当前使用的 FEC1 这个网口,I.MX6ULL 支持两个网口。
  • 第 20行提示「FEC1网卡地址没有设置」(后面我们会讲解如何在uboot 里面设置网卡地址)。
  • 第 22行提示「正常启动」, 也就是说 uboot要从emmc里面读取环境变量和参数信息启动 Linux内核了。
  • 第23行是「倒计时提示」,默认倒计时 3 秒,倒计时结束之前按下回车键就会进入 Linux 命令行模式。如果在倒计时结束以后没有按下回车键,那么 Linux 内核就会启动,Linux 内核一旦启动,uboot 就运行结束了。
  • 第23行是在倒计时 3 秒内按了回车键,符号=>表示可以继续与uboot进行「命令交互」

看过了串口的uboot信息,再来看一下板子是实际运行情况:

由于原厂的uboot驱动的屏幕是TFT43AB (480x272),与我这里屏幕不一样,所以「屏幕没有正常显示」(现在的屏幕看起来有许多彩色的小点点),接下来,就是对uboot进行屏幕驱动的修改。

在本篇结束之前,再来研究一下uboot的串口指令。

4.4 uboot命令初探

上面说道,在uboot启动的3 秒倒计时内,串口界面如果按下了回车键,uboot就会输出符号=>,则「可以继续与uboot进行命令交互」。那可以输入哪些命令呢?

4.4.1 help命令查看所有指令

输入help,然后按下回车即可查看当前 uboot 所支持的命令:

=> help
? - alias for 'help'
base - print or set address offset
bdinfo - print Board Info structure
bmode - sd1|sd2|qspi1|normal|usb|sata|ecspi1:0|ecspi1:1|ecspi1:2|ecspi1:3|esdhc1|esdhc2|esdhc3|esdhc4 [noreset]
bmp - manipulate BMP image data
boot - boot default, i.e., run 'bootcmd'
bootd - boot default, i.e., run 'bootcmd'
bootelf - Boot from an ELF image in memory
bootm - boot application image from memory
bootp - boot image via network using BOOTP/TFTP protocol
bootvx - Boot vxWorks from an ELF image
bootz - boot Linux zImage image from memory
clocks - display clocks
clrlogo - fill the boot logo area with black
cmp - memory compare
coninfo - print console devices and information
cp - memory copy
crc32 - checksum calculation
dcache - enable or disable data cache
dhcp - boot image via network using DHCP/TFTP protocol
dm - Driver model low level access
echo - echo args to console
editenv - edit environment variable
env - environment handling commands
erase - erase FLASH memory
exit - exit script
ext2load- load binary file from a Ext2 filesystem
ext2ls - list files in a directory (default /)
ext4load- load binary file from a Ext4 filesystem
ext4ls - list files in a directory (default /)
ext4size- determine a file's size
ext4write- create a file in the root directory
false - do nothing, unsuccessfully
fatinfo - print information about filesystem
fatload - load binary file from a dos filesystem
fatls - list files in a directory (default /)
fatsize - determine a file'
s size
fdt - flattened device tree utility commands
flinfo - print FLASH memory information
fstype - Look up a filesystem type
fuse - Fuse sub-system
go - start application at address 'addr'
gpio - query and control gpio pins
help - print command description/usage
i2c - I2C sub-system
icache - enable or disable instruction cache
iminfo - print header information for application image
imxtract- extract a part of a multi-image
itest - return true/false on integer compare
load - load binary file from a filesystem
loadb - load binary file over serial line (kermit mode)
loads - load S-Record file over serial line
loadx - load binary file over serial line (xmodem mode)
loady - load binary file over serial line (ymodem mode)
loop - infinite loop on address range
ls - list files in a directory (default /)
md - memory display
mdio - MDIO utility commands
mii - MII utility commands
mm - memory modify (auto-incrementing address)
mmc - MMC sub system
mmcinfo - display MMC info
mtest - simple RAM read/write test
mw - memory write (fill)
nfs - boot image via network using NFS protocol
nm - memory modify (constant address)
ping - send ICMP ECHO_REQUEST to network host
pmic - PMIC
printenv- print environment variables
protect - enable or disable FLASH write protection
reset - Perform RESET of the CPU
run - run commands in an environment variable
save - save file to a filesystem
saveenv - save environment variables to persistent storage
setenv - set environment variables
setexpr - set environment variable as the result of eval expression
sf - SPI flash sub-system
showvar - print local hushshell variables
size - determine a file's size
sleep - delay execution for some time
source - run script from memory
test - minimal test like /bin/sh
tftpboot- boot image via network using TFTP protocol
true - do nothing, successfully
usb - USB sub-system
usbboot - boot from USB device
version - print monitor, compiler and linker version
=>

4.4.2 查看指令的使用说明

命令的具体使用方法,可以输入help 命令名? 命令名查看,以“bootz”这个命令为例:

=> help bootz
bootz - boot Linux zImage image from memory

Usage:
bootz [addr [initrd[:size]] [fdt]]
- boot Linux zImage stored in memory
The argument 'initrd' is optional and specifies the address
of the initrd in memory. The optional argument ':size' allows
specifying the size of RAW initrd.
When booting a Linux kernel which requires a flat device-tree
a third argument is required which is the address of the
device-tree blob. To boot that kernel without an initrd image,
use a '-' for the second argument. If you do not pass a third
a bd_info struct will be passed instead

=>

4.4.3 信息查询命令

常用的和信息查询有关的命令有3个:bdinfoprintenvversion

  • bdinfo 板子信息
=> bdinfo
arch_number = 0x00000000
boot_params = 0x80000100
DRAM bank = 0x00000000
-> start = 0x80000000
-> size = 0x20000000
eth0name = FEC1
ethaddr = (not set)
current eth = FEC1
ip_addr = <NULL>
baudrate = 115200 bps
TLB addr = 0x9FFF0000
relocaddr = 0x9FF47000
reloc off = 0x18747000
irq_sp = 0x9EF44EA0
sp start = 0x9EF44E90
FB base = 0x00000000
=>

从打印信息可以得出DRAM的「起始地址和大小、启动参数保存起始地址、波特率、sp(堆栈指针)起始地址」等信息.

  • printenv 打印环境变量
=> printenv
baudrate=115200
board_name=EVK
board_rev=14X14
boot_fdt=try
bootcmd=run findfdt;mmc dev ${mmcdev};mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if run loadimage; thn run mmcboot; else run netboot; fi; fi; else run netboot; fi
bootcmd_mfg=run mfgtool_args;bootz ${loadaddr} ${initrd_addr} ${fdt_addr};
bootdelay=3
bootscript=echo Running bootscript from mmc ...; source
console=ttymxc0
ethact=FEC1
ethprime=FEC
fdt_addr=0x83000000
fdt_file=undefined
fdt_high=0xffffffff
findfdt=if test $fdt_file = undefined; then if test $board_name = EVK && test $board_rev = 9X9; then setenv fdt_file imx6ull-9x9-evk.dtb; fi; if tst $board_name = EVK && test $board_rev = 14X14; then setenv fdt_file imx6ull-14x14-evk.dtb; fi; if test $fdt_file = undefined; then echo WARNING:Could not determine dtb to use; fi; fi;
image=zImage
initrd_addr=0x83800000
initrd_high=0xffffffff
ip_dyn=yes
loadaddr=0x80800000
loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};
loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}
loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}
mfgtool_args=setenv bootargs console=${console},${baudrate} rdinit=/linuxrc g_mass_storage.stall=0 g_mass_storage.removable=1 g_mass_storage.file=fat g_mass_storage.ro=1 g_mass_storage.idVendor=0x066F g_mass_storage.idProduct=0x37FF g_mass_storage.iSerialNumber="" clk_ignore_unused
mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}
mmcautodetect=yes
mmcboot=echo Booting from mmc ...; run mmcargs; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if run loadfdt; then bootz ${loadaddr} -${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi;
mmcdev=0
mmcpart=1
mmcroot=/dev/mmcblk0p2 rootwait rw
netargs=setenv bootargs console=${console},${baudrate} root=/dev/nfs ip=dhcp nfsroot=${serverip}:${nfsroot},v3,tcp
netboot=echo Booting from net ...; run netargs; if test ${ip_dyn} = yes; then setenv get_cmd dhcp; else setenv get_cmd tftp; fi; ${get_cmd} ${imag}; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if ${get_cmd} ${fdt_addr} ${fdt_file}; then bootz ${loadaddr} - ${fdt_addr}; else if est ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi;
panel=TFT43AB
script=boot.scr

Environment size: 2431/8188 bytes
=>

这里有很多的环境变量, 比如「baudrate、 board_name、 board_rec、 boot_fdt、 bootcmd」等。比如bootdelay这个环境变量就表示 uboot 启动延时时间,默认 bootdelay=3,也就默认延时 3秒。前面说的 3 秒倒计时就是由 bootdelay 定义的。另外uboot中的环境变量都是字符串。

  • version 版本信息
=> version

U-Boot 2016.03 (Jul 11 2021 - 15:22:25 +0800)
arm-linux-gnueabihf-gcc (Linaro GCC 4.9-2017.01) 4.9.4
GNU ld (Linaro_Binutils-2017.01) 2.24.0.20141017 Linaro 2014_11-3-git
=>

当前uboot 版本号为 2016.03,编译日期2021/7/11,编译器为arm-linux-gnueabihf-gcc。

uboot移植实践

我们介绍了如何使用NXP原厂的uboot进行编译和烧写,将uboot运行在自己的开发板上。NXP原厂的uboot,直接烧录到我的开发板中,LCD的驱动是不正常的,需要进行修改。本篇我们就来继续研究uboot,「使得uboot能匹配我们自己的开发板」

修改uboot以匹配开发板的方式有两种,一种是在NXP原厂开发板「i.MX 6ULL EVK」的文件上进行修改,另一种仿造NXP的开发板文件,添加自己的开发板文件。

为了能更多的了解uboot,我们使用代码改动较大的第二种方式进行uboot的移植。

在修改uboot之前,先来看一下uboot的源码结构。

1 uboot源码结构分析

uboot的源码如下,这里是源码编译后的结果,包含编译后的文件。

这里文件的含义如下:

2 uboot移植实践

2.1 添加开发板配置文件

首先是「创建自己开发板的配置文件」,该文件可参考原厂开发板的配置文件,在configs文件夹下,将原来的默认配置文件mx6ull_14x14_evk_emmc_defconfig复制一份,并重命名为mx6ull_myboard_defconfig,该文件即用于作为自己开发板的配置文件。

然后进行「内容修改」,将原始内容:

CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ullevk/imximage.cfg,MX6ULL_EVK_EMMC_REWORK"
CONFIG_ARM=y
CONFIG_ARCH_MX6=y
CONFIG_TARGET_MX6ULL_14X14_EVK=y
CONFIG_CMD_GPIO=y

修改为:

2.2 添加开发板对应的头文件

在目录 include/configs 下添加自己开发板对应的头文件,复制mx6ullevk.h,并重命名为mx6ull_myboard.h,将文件中的

#ifndef __MX6ULLEVK_CONFIG_H 
#define __MX6ULLEVK_CONFIG_H

修改为:

该文件里面有很多宏定义,这些宏定义基本用于配置uboot,如果我们自己要想使能或者禁止uboot的某些功能,那就要在这里面修改。

在ubuntu中,可以安装VS Code软件来辅助查看代码,在ubuntu中安装vscode,需要先下载deb格式的安装包,然后使用类似如下的指令即可进行安装:

sudo dpkg -i code_1.58.0-1625728071_amd64.deb

安装完之后,我们可以将图标添加到ubuntu桌面上,ubuntu安装的所有软件图标都在目录/usr/share/applications中,找到 Visual Studio Code 的图标,然后点击鼠标右键,选择复制到->桌面即可。

2.3 添加开发板对应的板级文件夹

uboot中每个板子都有一个对应的文件夹来存放板级文件(如开发板上外设驱动文件等)。NXP的I.MX系列芯片的所有板级文件夹都存放在 board/freescale/目录下,在这个目录下有个名为mx6ullevk的文件夹,原厂开发板的板级文件夹。

复制 mx6ullevk,将其重命名为mx6ull_myboard,进入mx6ull_myboard目录中, 将其中的mx6ullevk.c文件重命名为mx6ull_myboard.c

2.3.1 修改Makefile文件

首先是修改 board/freescale/mx6ull_myboard 目录下的Makefile文件

将原始内容:

# (C) Copyright 2015 Freescale Semiconductor, Inc.
#
# SPDX-License-Identifier: GPL-2.0+
#

obj-y := mx6ullevk.o

extra-$(CONFIG_USE_PLUGIN) := plugin.bin
$(obj)/plugin.bin: $(obj)/plugin.o
$(OBJCOPY) -O binary --gap-fill 0xff $<$@

其中的依赖项修改为:

obj-y  := mx6ull_myboard.o

这样才会编译mx6ull_myboard.c这个文件。

2.3.2 修改imximage.cfg文件

然后修改 board/freescale/mx6ull_myboard 目录下的imximage.cfg文件

imximage.cfg中的下面一句:

PLUGIN board/freescale/mx6ullevk/plugin.bin 0x00907000

改为:

PLUGIN board/freescale/mx6ull_myboard/plugin.bin 0x00907000

2.3.3 修改Kconfig文件

接着修改 board/freescale/mx6ull_myboard 目录下的Kconfig文件

将原始内容:

if TARGET_MX6ULL_14X14_EVK || TARGET_MX6ULL_9X9_EVK

config SYS_BOARD
default "mx6ullevk"

config SYS_VENDOR
default "freescale"

config SYS_CONFIG_NAME
default "mx6ullevk"

endif

修改为:

2.3.4 修改MAINTAINERS文件

再接着修改 board/freescale/mx6ull_myboard 目录下的MAINTAINERS文件

将原始内容:

MX6ULLEVK BOARD
M: Peng Fan <peng.fan@nxp.com>
S: Maintained
F: board/freescale/mx6ullevk/
F: include/configs/mx6ullevk.h
F: configs/mx6ull_14x14_evk_defconfig
F: configs/mx6ull_9x9_evk_defconfig

修改为:

2.3.5 重命名板子的c文件

将 board/freescale/mx6ull_myboard 目录下原来的mx6ullevk.c重命名为mx6ull_myboard.c

2.4 修改U-Boot图形界面配置文件

最后修改arch/arm/cpu/armv7/mx6/目录下的Kconfig文件

注意这里的Kconfig和board/freescale/mx6ull_myboard目录下的Kconfig是不一样的。

在207行插入一些内容:

config TARGET_MX6ULL_MYBOARD
bool "Support mx6ull_myboard"
select MX6ULL
select DM
select DM_THERMAL

然后,在最后一行的endif的前一行添加如下内容:

source"board/freescale/mx6ull_myboard/Kconfig"

2.5 创建编译脚本

在uboot-imx-rel_imx_4.1.15_2.1.0_ga目录下新建一个名为build_myboard.sh的 shell 脚本,写入如下内容:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_myboard_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8

至此,以上完成的工作,相当于将NXP原厂开发板相关的配置文件,重新复制了一份,并对板子名称修改为了自己板子的名字。

此时执行./build_myboard.sh,等待编译完成后输入如下命令:

grep -nR "mx6ull_myboard.h"

如果有很多文件都引用了这个头文件, 那就说明新板子添加成功:

将uboot进行编译并运行,实际的效果应该和原厂uboot的效果一样(LCD无法显示)。

「总结一下刚才都有哪些修改」:

右端灰色的为原厂开发板的相关文件,黄色的为模仿原厂文件,新添加并修改的自己开发板的文件。

下面进行LCD驱动的修改。

3 LCD驱动的修改

一般uboot中修改驱动都是在对应板子c文件和h文件,即board/freescale/mx6ull_myboard/mx6ull_myboard.c和 include/configs/mx6ull_myboard.h这两个文件。

一般修改 LCD 驱动重点注意以下几点:

  • LCD 所使用的 GPIO,查看 uboot 中 LCD 的 IO 配置是否正确
  • LCD 背光引脚 GPIO 的配置
  • LCD 配置参数是否正确

「正点原子」以及「野火」的I.MX6ULL开发板的LCD原理图和NXP官方的开发板一致,也就是LCD的IO和背光IO都是一样的, 所以IO部分就不用修改了,只需修改之后的LCD参数。

3.1 修改c文件配置

打开文件 mx6ull_myboard.c,需要修改下面这段内容:

struct display_info_t const displays[] = {{
.bus = MX6UL_LCDIF1_BASE_ADDR,
.addr = 0,
.pixfmt = 24,
.detect = NULL,
.enable = do_enable_parallel_lcd,
.mode = {
.name = "TFT43AB",
.xres = 480,
.yres = 272,
.pixclock = 108695,
.left_margin = 8,
.right_margin = 4,
.upper_margin = 2,
.lower_margin = 4,
.hsync_len = 41,
.vsync_len = 10,
.sync = 0,
.vmode = FB_VMODE_NONINTERLACED
} } };

先来分析一下这段代码,该代码定义了一个变量displays,类型为display_info_t,这个结构体是LCD信息结构体,其中包括了LCD的分辨率,像素格式,LCD的各个参数等。

display_info_t 定义在文件  arch/arm/include/asm/imx-common/video.h  中,定义如下:

struct display_info_t {
int bus;
int addr;
int pixfmt;
int (*detect)(struct display_info_tconst *dev);
void (*enable)(struct display_info_tconst *dev);
struct fb_videomode mode;
};

这里的pixfmt是像素格式,也就是一个像素点是多少位,如果是RGB565的话就是16位,如果是RGB888的话就是24位,一般使用 RGB888。

结构体display_info_t还有个mode成员变量,此成员变量也是个结构体,为fb_videomode,定义在文件 include/linux/fb.h 中,定义如下:

struct fb_videomode {
constchar *name; /* optional */
u32 refresh; /* optional */
u32 xres;
u32 yres;
u32 pixclock;
u32 left_margin;
u32 right_margin;
u32 upper_margin;
u32 lower_margin;
u32 hsync_len;
u32 vsync_len;
u32 sync;
u32 vmode;
u32 flag;
};

结构体b_videomode里面的成员变量为LCD的参数,这些成员变量函数如下:

  • name :LCD 名字,要和环境变量中的 panel 相等
  • xres 、yres :LCD X 轴和 Y 轴像素数量
  • pixclock:像素时钟,每个像素时钟周期的长度,单位为皮秒
  • left_margin :HBP(horizontal back porch),水平同步后肩
  • right_margin :HFP(horizontal front porch),水平同步前肩
  • upper_margin:VBP(vertical back porch),垂直同步后肩
  • lower_margin:VFP(vertical front porch),垂直同步前肩
  • hsync_len :HSPW(horizontal sync pulse width),行同步脉宽
  • vsync_len:VSPW(vertical sync pulse width),垂直同步脉宽
  • vmode :大多数使用 FB_VMODE_NONINTERLACED,也就是不使用隔行扫描。

这些参数需要与实用的LCDd的参数一致。

「野火的7寸RGB屏幕」(GT911,800x480)的一些参数如下:

参数
width800
height480
HBP46
HFP22
VBP23
VFP22
HSW1
VSW1

注意像素时钟pixclock的计算方法:以野火的 7 寸RGB屏为例,屏幕要求的像素时钟为27.4MHz,因此:pixclock=(1/27400000)*10^12=36496

像素时钟就是 RGB LCD 的时钟信号,以 GT911这款屏幕为例,显示一帧图像所需要的时钟数就是:(VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP) = (1 + 23 + 480+ 22) * (1+ 46+ 800+ 22)
= 526* 869 = 457094。显示一帧图像需要457094个时钟数, 那么显示60帧就是:457094* 60 = 27425640≈27.4M,所以像素时钟就是27.4MHz

由以上的屏幕参数,可以得出GT911屏幕的配置参数如下:

struct display_info_t const displays[] = {{
.bus = MX6UL_LCDIF1_BASE_ADDR,
.addr = 0,
.pixfmt = 24,
.detect = NULL,
.enable = do_enable_parallel_lcd,
.mode = {
.name = "GT911",
.xres = 800,
.yres = 480,
.pixclock = 36496,
.left_margin = 46, //HBPD
.right_margin = 22, //HFPD
.upper_margin = 23, //VBPD
.lower_margin = 22, //VFPD
.hsync_len = 1, //HSPW
.vsync_len = 1, //VSPW
.sync = 0,
.vmode = FB_VMODE_NONINTERLACED
} } };

3.2 修改h文件配置

另外还要修改include/configs/路径下的mx6ull_myboard.h,找到所有如下语句:

panel=TFT43AB

修改为:

panel=GT911 //与mx6ull_myboard.c中修改的名称保持一致

修改完成以后重新编译一遍 uboot 并烧写到 SD 中启动。

3.3 编译测试

将修改后的uboot编译下载以后,LCD 驱动一般就会工作正常了,LCD 上会显示 NXP 的 logo。

但某些情况有可能还会遇到LCD 并没有工作,还是黑屏,这是什么原因呢?

在 uboot 命令模式输入“print”来查看环境变量 panel 的值,会发现panel的值要是TFT43AB(或其他的,反正不是GT911):

panel=TFT43AB
script=boot.scr

Environment size: 2431/8188 bytes
=>

这是因为之前有将环境变量保存到EMMC中,uboot启动以后会先从EMMC中读取环境变量,如果EMMC中没有环境变量的话才会使用 mx6ull_alientek_emmc.h 中的默认环境变量。

如果EMMC中的环境变量panel不等于GT911,那么LCD显示肯定不正常,我们只需要在uboot中修改panel的值为GT911即可,在uboot的命令模式下输入如下命令:

setenv panel GT911 
saveenv

上述命令修改环境变量panel为GT911并保存后,按下复位键重启uboot,此时 LCD 驱动就工作正常了。

4 网络测试

I.MX6ULL内部有个以太网MAC外设,也就是ENET,需要外接一个PHY芯片来实现网络通信功能,也就是「内部MAC+外部PHY芯片」的方案。I.MX6ULL有两个网络接口ENET1和ENET2,野火的开发板提供了这两个网络接口,其中ENET1和ENET2都使用是和原厂开发板一样的KSZ8081作为PHY芯片。

因此,网络驱动部分的uboot不需要修改,下面就只是来测试一下网路功能。

4.1 连接网线并查看启动情况

首先将开发板通过网线连接到局域网的路由器中(自己的电脑也要在同一个局域网,这样ubuntu虚拟机则也在同一个局域网)。

然后启动uboot,串口查看相关的打印信息,如下图,可以看到网络端口的FEC1(注意是uboot程序中默认设置的,不是因为网线插在了左边就自动识别FEC1),但是提示网络地址未设置。

4.2 设置网络参数

下面就来设置一下,首先是设置开发板的IP,在设置之前,先借助Windows电脑的cmd的ping+ip指令来测试某个IP是否被使用,如我的192.168.5.102未被使用,就可以设为开发板的IP。

除了设置开发板的IP,还要设置一些其它的网络参数,具体如下:

setenv ipaddr 192.168.5.102//开发板 IP 地址
setenv ethaddr 00:04:9f:04:d2:35//开发板网卡 MAC 地址
setenv gatewayip 192.168.5.1//开发板默认网关
setenv netmask 255.255.255.0//开发板子网掩码
setenv serverip 192.168.5.101//服务器地址,也就是 Ubuntu 地址
saveenv //保存环境变量

开发板的MAC地址是一个长度为48位(6个字节)的地址,每个字节间通过冒号间隔,理论上只要局域网内各网络设备不冲突,该地址可任意设置。

局域网的默认网关和子网掩码需要根据自己的实际情况设置(不知道是多少的,可以借助Windows电脑的cmd中的ipconfig指令来查看)

服务器的地址就是ubuntu虚拟机的地址(可以通过linux的ifconfig指令来查看)

4.3 测试另一个网口

打开 include/configs/mx6ull_alientek_emmc.h ,将CONFIG_FEC_ENET_DEV修改为 0, 重新编译uboot并烧写到SD卡中。

将网线连接到开发板右边的网口上,按照之前的测试方法再次测试:

5 uboot启动Linux内核测试

uboot的最终目的就是启动Linux内核,所以需要通过启动Linux内核来判断uboot移植是否成功。

启动Linux内核。我们测试两种启动Linux内核的方法:

  • 从EMMC启动
  • 从网络启动

「从EMMC启动」也就是将编译出来的「Linux镜像文件zImage」「设备树文件」保存在EMMC中,uboot从EMMC中读这两个文件并启动。由于我们板子的EMMC中可能还没有linux镜像文件和设备树文件,所以先不测试这种方法。

「从网络启动」,是指将linux镜像文件和根文件系统都放到Ubuntu下某个指定的文件夹中,然后通过nfs或者tftp等传输方式将系统文件(zImage和设备树文件)从Ubuntu中直接下载到开发板的内存中,EMMC中则不需要有系统文件。这种方式的作用就是方便调试,免去将代码固化到开发板的过程。当然,当开发板掉电,内存的系统文件就没了。

下面就来通过网络调试的方法来测试uboot是否能正常启动Linux内核。

「在测试之前,先来介绍一下在ubuntu虚拟机上如何搭建tftp来传输文件」

5.1 tftp服务搭建

Ubuntu上搭建TFTP服务器,需要安装tftp-hpatftpd-hpa,命令如下:

sudo apt-get install tftp-hpa tftpd-hpa 
sudo apt-get install xinetd

TFTP也需要一个文件夹来存放文件,在用户目录下新建一个目录,示例命令如下:

mkdir /home/xxpcb/myTest/tftpdir
chmod 777 /home/xxpcb/myTest/tftpdir

最后配置 tftp, 安装完成以后,新建文件/etc/xinetd.d/tftp, 如果没有/etc/xinetd.d 目录的话自行创建,然后在里面输入如下内容:

server tftp 
{
socket_type = dgram
protocol = udp
wait = yes
user = root
server = /usr/sbin/in.tftpd
server_args = -s /home/xxpcb/myTest/tftpdir/
disable = no
per_source = 11
cps = 100 2
flags = IPv4
}

完了以后启动tftp服务,命令如下:

sudo service tftpd-hpa start

打开/etc/default/tftpd-hpa文件,将其修改为如下所示内容:

# /etc/default/tftpd-hpa 

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/home/xxpcb/myTest/tftpdir"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="-l -c -s"

TFTP_DIRECTORY就是我们上面创建的tftp文件夹目录,以后我们就将所有需要通过TFTP传输的文件都放到这个文件夹里面,并且要给予这些文件相应的权限。

最后输入如下命令, 重启 tftp 服务器:

sudo service tftpd-hpa restart

至此,tftp服务器已经搭建好了,可以先来测试一下功能是否正常。

5.2 tftp文件传输测试

测试tftp功能是否正常,主要分为两步:

  • 首先是「将某个zImage镜像文件拷贝到ubuntu虚拟机的tftpboot文件夹中」,并且给予 zImage 相应777的权限。

  • 然后是「通过开发板uboot的串口交互指令将文件从ubuntu传输到开发板的内存」

uboot串口交互指令中的「tftp命令格式」如下:

tftpboot [loadAddress] [[hostIPaddr:]bootfilename]

loadAddress是文件在DRAM中的存放地址,[[hostIPaddr:]bootfilename]是要从Ubuntu中下载的文件。

tftp传输文件,不需要输入文件在Ubuntu中的完整路径,只需要输入文件名即可。

比如我们现在「将tftpboot文件夹里面的zImage文件下载到开发板DRAM的0X80800000地址处」,命令如下:

tftp 80800000 zImage

注:此次测试时,我的ubuntu虚拟机(作为tftp服务器)的IP变了,所以我又重新设置了ubuntu的IP

5.3 测试从网络启动Linux

  • 设置环境变量

    这两个环境变量的具体含义先不展开讨论。

setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-evk-emmc.dtb; bootz 80800000 - 83000000'
saveenv
  • 通过tftp将zImage和设备树下载到板子的RAM中

    就是通过网路的方式(tftp)将系统文件下载到板子的内存中,这里使用的「野火提供的yocto的zImage和dtb文件」,将两个文件辅助到ubuntu的tftp服务器目录,依次输入如下指令:

tftp 80800000 zImage
tftp 83000000 imx6ull-14x14-evk-emmc.dtb
  • 启动内核
bootz 80800000 - 83000000

可以看到「Starting kernel ...」 的字样,表示内核已经启动。

再看看下板子,已经有启动画面了:

在过一会儿,会出现系统的图形界面,只是现在还不能操作,触摸没反应。

至此,uboot的移植基本完成,可以启动Linux内核。启动内核之后,uboot的使命就完成了。


Kernel移植

1 Linux内核简介

官网:https://www.kernel.org/

NXP 会从linux内核官网下载某个版本,然后将其移植到自己的 CPU上,测试成功后就会将其开放给NXP的CPU开发者。开发者下载 NXP 提供的 Linux 内核,然后将其移植到自己的产品上。

本文我们就使用NXP提供的Linux源码,文件名为:linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2

2 Linux内核编译

编译内核之前需要先在ubuntu上安装lzop库,另外,图形化配置工具还需要ncurses库支持,安装命令为:

sudo apt-get install lzop
sudo apt-get install build-essential
sudo apt-get install libncurses5-dev

在Ubuntu中新建一个文件夹,然后将linux内核压缩包拷贝到文件夹中并解压,解压命令为:

tar -vxf linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2

解压完成后

进入该文件夹,新建一个build.sh脚本文件来编译,脚本中的内容如下:

#!/bin/sh 
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_mfg_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j8

给予该脚本可执行权限,然后运行,编译的时候会弹出Linux图形配置界面, 这里不需要做任何的配置, 直接按两下ESC键退出图形界面

之后会自动开始编译Linux内核。

编译完成以后就会在arch/arm/boot这个目录下生成一个zImage文件,该文件就是要用的Linux镜像文件。另外也会在arch/arm/boot/dts下生成很多.dtb 文件,这些.dtb 就是设备树文件。

vmlinux 、Image ,zImage 、uImage  的区别

vmlinux是ELF格式的文件,是编译出来的最原始的内核文件,编译出来 差不多有16MB,是未压缩的。在实际中我们不会使用vmlinux,而是使用zImage或uImage这样的 Linux 内核镜像文件。

Image是Linux内核镜像文件,但是Image仅包含可执行的二进制数据。Image就是使用objcopy取消掉vmlinux中的一些其他信息,比如符号表什么的。但是 Image 是没有压缩过的,Image保存在arch/arm/boot目录下,其大小大概在12MB 。

zImage是经过gzip压缩后的Image,经过压缩以后其大小大概在6MB左右。

uImage是老版本uboot专用的镜像文件,uImag是在zImage前面加了一个长度为 64字节的“头” ,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息。但是新的uboot已经支持了 zImage 启动!所以已经很少用到uImage了。

3 Linux内核源码结构

Linux内核编译过程会生成一些文件,下面来看一下编译后的内核源码结构,可以看出多出了一些编译文件

具体描述如下:

  • arch目录

    这个目录是和架构有关的目录,比如arm、arm64、avr32、x86等等架构。每种架构都对应一个目录,在这些目录中又有很多子目录,比如boot、common、configs等等。

  • block目录 block是Linux下块设备目录, 像SD卡、EMMC、NAND、硬盘等存储设备就属于块设备,block目录中存放着管理块设备的相关文件。

  • crypto目录 crypto目录里面存放着加密文件,比如常见的crc、crc32、md4、md5、hash等加密算法。

  • Documentation目录 此目录里面存放着Linux相关的文档,如果要想了解Linux某个功能模块或驱动架构的功能,就可以在Documentation目录中查找有没有对应的文档。

  • drivers目录 驱动目录文件,此目录根据驱动类型的不同,分门别类进行整理,比如drivers/i2c就是I2C相关驱动目录,drivers/gpio就是GPIO相关的驱动目录,这是我们学习的重点。

  • firmware 目录 此目录用于存放固件。

  • fs目录 此目录存放文件系统,比如fs/ext2、fs/ext4、fs/f2fs等,分别是ext2、ext4 和 f2fs等文件系统。

4 Linux内核启动测试

将编译出来的zImage和imx6ull-14x14-evk.dtb复制到Ubuntu中的tftp目录下,之后会通过uboot 的tftp命令将其下载到开发板中。

在测试之前确保uboot中的环境变量bootargs内容如下(使用print指令查看):

console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw

如果不是的话,可以使用如下指令设置一下:

setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
saveenv

然后可以测试了,启动开发板,串口中进入uboot命令行模式,然后输入如下命令将zImage和imx6ull-14x14-evk.dtb下载到开发板中并启动:

tftp 80800000 zImage   
tftp 83000000 imx6ull-14x14-evk.dtb
bootz 80800000 - 83000000

可以看到内核启动了

最后到了系统登录处,说明Linux系统正常启动了(这次LCD上没有了野火的图形界面,可能是某些固件不匹配吧,先忽略)

4.1 根文件系统缺失的错误

Linux内核启动以后是需要根文件系统的,根文件系统存在哪里是由uboot的bootargs环境变量指定, bootargs会传递给Linux内核作为命令行参数 。比如之前设置的root=/dev/mmcblk1p2,也就是说根文件系统存储在/dev/mmcblk1p2中,即EMMC的分区2中。

因为上一篇的测试时,EMMC的分区2中烧写好了根文件系统,所以设置root=/dev/mmcblk1p2,并且内核正常启动。如果我们不设置根文件系统路径,或者说根文件系统路径设置错误的话会出现什么问题?

我们将uboot中的bootargs环境变量改为“console=ttymxc0,115200” ,也就是不填写root的内容了,命令如下:

setenv bootargs 'console=ttymxc0,115200'
saveenv

修改完成以后重新从网络启动,可以看到也是先启动了内核:

但启动以后会有类似如下的错误:

最后会有下面这一行:

Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)

提示内核崩溃,因为VFS(虚拟文件系统)不能挂载根文件系统,目录不存在。即使目录存在,如果根文件系统目录里面是空的依旧会提示内核崩溃。

5 Linux中添加自己的开发板

编译NXP官方I.MX6ULL EVK开发板对应的Linux内核,发现其可以在野火的EMMC版本开发板启动。为了进一步了解Linux内核,我们可以参考官方开发板的设置,在Linux内核中添加自己的开发板。

5.1 添加开发板默认配置文件

arch/arm/configs目录下的imx_v7_mfg_defconfig重新复制一份 , 命名为自己开发板,如imx_myboard_defconfig

5.2 添加开发板对应的设备树文件

进入arch/arm/boot/dts目录中,复制一份imx6ull-14x14-evk.dts,然后将其重命名为imx6ull-myboard.dts

然后还需要修改文件arch/arm/boot/dts/Makefile,找到 dtb-$(CONFIG_SOC_IMX6ULL)配置项,在此配置项中加入“imx6ull-myboard.dtb” :

这样编译Linux的时候就可以从imx6ull-myboard.dts 编译出 imx6ull-myboard.dtb 文件了。

总结一下以上的修改主要包括:

主要就是对文件复制一份并重命名,唯一修改的是Makefile文件。

5.3 添加新的编译脚本

新建一个build_myboard.sh,写入如下内容:

#!/bin/sh 
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_myboard_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j8

编译出zImage(arch/arm/boot目录)和imx6ull-myboard.dtb (arch/arm/boot/dts目录)后再次进行Linux启动测试,可以到登录提示,说明Linux内核启动成功。

根文件系统构建

本篇进行根文件系统的构建,这是Linux移植三大组成部分的最后一步,根文件系统构建好后,就构成了一个基础的、可以运行的嵌入式Linux最小系统。

1 根文件系统简介

Linux的根文件系统一般也叫做 rootfs,Linux的根文件系统更像是一个文件夹或者叫做目录,在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是Linux运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。

根文件系统的这个“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根” ,其他的文件系统或者软件就别想工作。比如我们常用的 ls、mv、ifconfig 等命令其实就是一个个小软件,只是这些软件没有图形界面,而且需要输入命令来运行。这些小软件就保存在根文件系统中。

在构建根文件系统之前,先来看一下根文件系统里面都有些什么内容,根文件系统的目录名字为‘/’ ,就是一个斜杠:

根文件系统的各个文件夹的作用如下:

目录描述
/bin此目录下存放着系统需要的可执行文件,一般都是一些命令,比如 ls、mv 等命令
/devdev 是 device 的缩写,所以此目录下的文件都是和设备有关的。在Linux下一切皆文件,即使是硬件设备,也是以文件的形式存在的,比如/dev/ttymxc0就表示串口0
/etc此目录下存放着各种配置文件
/liblib是library的简称,也就是库的意思,因此此目录下存放着Linux所必须的库文件
/mnt临时挂载目录,一般是空目录,可以在此目录下创建空的子目录,比如/mnt/sd、/mnt/usb,这样就可以将SD卡或者U盘挂载到/mnt/sd 或者/mnt/usb 目录中
/proc此目录一般是空的,当Linux系统启动以后会将此目录作为proc文件系统的挂载点,proc是个虚拟文件系统,没有实际的存储设备。proc里面的文件都是临时存在的,一般用来存储系统运行信息文件
/usr注意,usr不是user的缩写,而是Unix Software Resource的缩写,即Unix操作系统软件资源目录。Linux 一般被称为类Unix操作系统。既然是软件资源目录,因此/usr 目录下也存放着很多软件,一般系统安装完成以后此目录占用的空间最多
/var此目录存放一些可以改变的数据
/sbin此目录页用户存放一些可执行文件, 但是此目录下的文件或者说命令只有管理员才能使用,主要用于系统管理
/sys系统启动以后此目录作为 sysfs 文件系统的挂载点,sysfs是一个类似于 proc文件系统的特殊文件系统,sysfs也是基于RAM的文件系统,也就是说它也没有实际的存储设备。此目录是系统设备管理的重要目录
/opt可选的文件、软件存放区,由用户选择将哪些文件或软件放到此目录中

2 BusyBox构建根文件系统

2.1 BusyBox简介

BusyBox是一个集成了大量的Linux命令(如ls、mv、ifconfig 等命令)和工具的软件。借助BusyBox,进行配置和编译,就可以方便的构建一个嵌入Linux平台所需要的根文件系统。

课程BusyBox官网https://busybox.net/下载源码,如下图。

左侧的“Get BusyBox”栏有一行“Download Source” ,点击“Download Source”即可打开 BusyBox 的下载页。

目前最新的 BusyBox 版本是1.33.1,但这里使用正点原子提供的1.29.0版本的BusyBox(busybox-1.29.0.tar.bz2)

2.2 搭建NFS服务

一般在Linux驱动开发的时候都是通过NFS挂载根文件系统的,当调试好之后再将根文件系统烧写到 EMMC或者NAND中,因此需要先在ubuntu虚拟机中构建NFS服务:

sudo apt-get install nfs-kernel-server rpcbind

等待安装完成,在合适的地方新建一个名为“nfs”的文件夹,供NFS服务器使用。

如我的创建目录为:/home/xxpcb/myTest/nfs

在使用NFS之前,还需要先配置NFS,修改配置文件/etc/exports,在后面添加如下所示内容:

/home/xxpcb/myTest/nfs *(rw,sync,no_root_squash)

最后重启NFS服务即可:

sudo /etc/init.d/nfs-kernel-server restart

正常情况会出现如下图,表示设置成功:

注:我第一次设置时,文件路径中的一个大小写字母搞错了,导致重启NFS时提示失败(如下图),所以在设置时要注意细节!

2.3 修改配置BusyBox

在nfs服务器目录中创建一个名为rootfs的子目录,用来存放我们的根文件系统。

busybox-1.29.0.tar.bz2发送到Ubuntu中的合适位置(我存放在 /home/xxpcb/myTest/imx6ull/dts)并解压:

tar -vxjf busybox-1.29.0.tar.bz2

解压后的文件如下:

2.3.1 修改Makefile添加编译器

注:这一步可以不修改,这里修改Makefile的目的是为了在编译时,可以不用在指定编译器的架构,从而可以缩短手动输入指令的长度。但我此次测试时,修改Makefile后,输入make指令的命令进行编译时,不指定编译器,还是会提示编译器找不到之类的问题。所以,此次的测试,我就没有修改这个Makefile。

如果坚持要修改Makefile,就是修改如下的地方,指定编译器与架构(本篇进行实验时没有修改)。

2.3.2 busybox中文字符支持

现在如果直接编译busybox的,在使用串口工具的时候是不支持中文显示的,中文字符会显示为“?” 。可以通过busybox源码,来取消 busybox对中文显示的限制。

打开文件busybox-1.29.0/libbb/printable_string.c,找到函数printable_string,吧某些程序注释掉,修改后的函数内容如下:

主要就是禁止字符大于0X7F以后 break 和输出‘?’

接着打开文件busybox-1.29.0/libbb/unicode.c,修改如下内容:

2.3.3 配置busybox

有以下几种配置选项:

  • defconfig:缺省配置,也就是默认配置选项
  • allyesconfi:全选配置,也就是选中 busybox 的所有功能
  • allnoconfig:最小配置

一般使用默认配置即可,因此使用如下命令先使用默认配置来配置一下 busybox:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig

busybox也支持图形化配置,通过图形化配置我们可以进一步选择自己想要的功能,输入如下命令打开图形化配置界面:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
  • (1) 设置Settings -> Build static binary (no shared libs)

选项“Build static binary (no shared libs)”用来决定是静态编译还是动态编译,静态编译的话就不需要库文件,但是编译出来的库会很大。动态编译的话要求根文件系统中有库文件,但是编译出来的 busybox 会小很多。这里我们不使用静态编译,所以保持默认不选即可。

  • (2) 设置Settings -> vi-style line editing commands

这个要勾选,通过按键“y”实现勾选,使得方括号内出现星号

  • (3) 配置Linux Module Utilities -> Simplified modutils

默认会选中“Simplified modutils” ,这里我们要取消勾选!使用键盘上的“n”键取消方括号中的星号。

  • (4) 配置Linux System Utilities  -> mdev (16 kb)

确保下面的全部选中,默认都是选中

  • (5) 设置Settings -> Support Unicode

要将默认没有勾选的Check $LC_ALL选中

最后按两下ESC退出设置,并选择YES保持存

2.4 编译busybox构建根文件系统

输入如下指令进行编译:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- install CONFIG_PREFIX=/home/xxpcb/myTest/nfs/rootfs

编译完成以后, busybox的所有工具和文件就会被安装到rootfs目录中,如下图:

rootfs目录下有bin、sbin和usr三个目录,以及linuxrc文件。Linux内核linit进程最后会查找用户空间的init程序,找到以后就会运行这个用户空间的init程序,从而切换到用户态。如果bootargs设置init=/linuxrc,那么linuxrc就是可以作为用户空间的init程序。

2.5 向根文件系统添加lib库

busybox编译完成后,此时的根文件系统还不能使用, 还需要一些其他的文件。

2.5.1 向rootfs/lib中添加

上面的busybox使用的是动态库编译,所以还需要向根文件系统中添加动态库

先在rootfs中创建一个名为“lib”的文件夹。lib库文件从交叉编译器中获取,之前搭建交叉编译环境的时候将交叉编译器存放到了“/usr/local/arm/”目录中,进入对应的目录:

cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib

此目录下有很多的so和.a 文件,这些就是库文件,将此目录下所有的so和.a文件都拷贝到 rootfs/lib 目录中:

cp *so* *.a /home/xxpcb/myTest/nfs/rootfs/lib/ -d

后面的“-d”表示拷贝符号链接,这里有个比较特殊的库文件:ld-linux-armhf.so.3,此库文件也是个符号链接,相当于 Windows 下的快捷方式。会链接到库 ld-2.19-2014.08-1-git.so 上,输入命令如下指令查看此文件详细信息:

ls ld-linux-armhf.so.3 -l

ld-linux-armhf.so.3 后面有个“->” ,表示其是个软连接文件,链接到文件ld-2.19-2014.08-1-git.so,因为其是一个“快捷方式” ,因此大小只有 24B。但是,ld-linux-armhf.so.3不能作为符号链接,否则的话在根文件系统中执行程序无法执行!所以我们需要重新复制ld-linux- armhf.so.3,替换掉这个软链接。

先删除这个软连接文件:

rm ld-linux-armhf.so.3

然后重新进入到 /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm- linux-gnueabihf/libc/lib 目录中,重新拷贝ld-linux-armhf.so.3,命令如下:

cp ld-linux-armhf.so.3 /home/xxpcb/myTest/nfs/rootfs/lib/

拷贝完成以后再到 rootfs/lib 目录下查看ld-linux-armhf.so.3文件详细信息,此时ld-linux-armhf.so.3 已经不是软连接了,而是实实在在的一个库文件,而且文件大小为 724392B。

继续进入如下目录中:

cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/lib

此目录下也有很多的的so和.a 库文件,我们将其也拷贝到 rootfs/lib 目录中,命令如下:

cp *so* *.a /home/xxpcb/myTest/nfs/rootfs/lib/ -d

rootfs/lib 目录的库文件就这些了,完成以后的rootfs/lib目录如图:

2.5.2 向rootfs/usr/lib中添加

rootfs/usr目录下创建一个名为lib的目录, 将如下目录中的库文件拷贝到rootfs/usr/lib目录下:

/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib

将此目录下的so和.a 库文件都拷贝到rootfs/usr/lib目录中:

cp *so* *.a /home/xxpcb/myTest/nfs/rootfs/usr/lib/ -d

完成以后的rootfs/usr/lib目录为:

至此,根文件系统的库文件就全部添加好了,可以在rootfs目录下使用“du”命令来查看一下/lib和/usr/lib 这两个目录的大小:

du ./lib ./usr/lib/ -sh

2.6 创建其他文件夹

在根文件系统中创建其他文件夹,如 dev、proc、mnt、sys、tmp 和 root 等,创建完后的效果:

3 根文件系统初步测试

3.1 bootargs环境变量设置

使用NFS挂载的方式来测试上面创建好的根文件系统rootfs。

uboot里面的bootargs环境变量会设置root的值,需要将root的值改为NFS挂载,设置格式如为:

root=/dev/nfs nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>
  • <server-ip>:服务器IP,存放根文件系统的Ubuntu的IP地址,比如我的192.168.5.105。
  • <root-dir> :根文件系统的存放路径,比如我的就是/home/xxpcb/myTest/nfs/rootfs。
  • <nfs-options>:NFS 的其他可选选项,一般不设置。
  • <client-ip> :客户端IP ,开发板的IP地址,Linux内核启动以后就会使用此IP地址来配置开发板。我的为92.168.5.102。
  • <gw-ip> :网关地址,我的就是 192.168.5.1。
  • <netmask>:子网掩码,我的就是 255.255.255.0。
  • <hostname>:客户机的名字,一般不设置,此值可以空着。
  • <device> :设备名,也就是网卡名,一般是 eth0,eth1….,正点原子与野火的开发板均为ENET2为eth0,ENET1为eth1。这里我们使用ENET2,所以网卡名就是 eth0。
  • <autoconf> :自动配置,一般不使用,所以设置为 off。
  • <dns0-ip> :DNS0 服务器 IP 地址,不使用。
  • <dns1-ip> :DNS1 服务器 IP 地址,不使用。

根据上面的格式bootargs环境变量的root值如下:

root=/dev/nfs nfsroot=192.168.5.105:/home/xxpcb/myTest/nfs/rootfs,proto=tcp rw ip=192.168.5.102:192.168.5.105:192.168.5.1:255.255.255.0::eth1:off

启动开发板,串口连接开发板,进入uboot命令行模式,然后设置bootargs环境变量,命令如下:

setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.5.105:/home/xxpcb/myTest/nfs/rootfs,proto=tcp rw ip=192.168.5.102:192.168.5.105:192.168.5.1:255.255.255.0::eth1:off'
saveenv

设置好以后使用“boot”命令启动Linux内核

Linux内核的启动还是按照上一篇介绍的,使用tftp将zImage和设备树传输到开发板中运行

这里注意一下,因为此次测试,我将zImage和dtb文件移入了tftp目录中的nxp文件夹中,所以传输指令需要修改一下:

setenv bootcmd 'tftp 80800000 nxp/zImage; tftp 83000000 nxp/imx6ull-myboard.dtb; bootz 80800000 - 83000000'
saveenv

然后就可以使用boot命令来进行tftp传输了。

3.2 NFS挂载错误与解决方法

3.2.1 错误提示

在使用boot命令来进行tftp传输了,启动内核时,出现了NFS根文件系统不能挂载的错误:

VFS: Unable to mount root fs  via NFS, trying floppy.

VFS: Cannot open root device "nfs" or unknown-block(2,0): error -6

3.2.2 无效的解决方法

先是尝试了多种方法,都不能解决问题,这些无效的方法包括:

  • 尝试修改配置,将Linux System Utilities  ->Support mounting NFS file选中(无效)
  • 尝试将nfs目录的下的rootfs文件夹赋予777的权限(无效)
  • 尝试换用其它的串口软件(SecureCRT)来操作(无效)

3.2.3 有效的解决方法

最后,参考这篇博文:https://blog.csdn.net/InFoport/article/details/90317697

通过在bootargs添加中添加nfsvers=4,这个选项,就可以正常挂载nfs的文件系统了:

setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.5.105:/home/xxpcb/myTest/nfs/rootfs,proto=tcp,nfsvers=4 rw ip=192.168.5.102:192.168.5.105:192.168.5.1:255.255.255.0::eth1:off'

注:无效方法中的Linux System Utilities  ->Support mounting NFS file,因开启后也不起作用,后续测试就将其改为默认的不勾选。

3.3 文件系统使用测试

按下回车键,就进入了文件系统,使用ls命令就可以看到了系统文件。

再使用touch命令来新建一个中文名称的文件,也是OK的。

4 总结

本篇使用BusyBox来构建根文件系统,并通过NFS网络调试的方式实现根文件系统挂载测试,实测时解决了NFS根文件系统不能挂载的问题,最终根文件系统基本功能测试正常。

关注,回复【1024】海量Linux资料赠送
 精彩文章合集
linux入门
C语言
Linux驱动
ARM
计算机网络
粉丝问答
所有原创
一口Linux 写点代码,写点人生!
评论
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 120浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 87浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 101浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 102浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 119浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 68浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 58浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 111浏览
  • 艾迈斯欧司朗全新“样片申请”小程序,逾160种LED、传感器、多芯片组合等产品样片一触即达。轻松3步完成申请,境内免费包邮到家!本期热荐性能显著提升的OSLON® Optimal,GF CSSRML.24ams OSRAM 基于最新芯片技术推出全新LED产品OSLON® Optimal系列,实现了显著的性能升级。该系列提供五种不同颜色的光源选项,包括Hyper Red(660 nm,PDN)、Red(640 nm)、Deep Blue(450 nm,PDN)、Far Red(730 nm)及Ho
    艾迈斯欧司朗 2024-11-29 16:55 175浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 125浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 111浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 78浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 98浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 73浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦