1.NXP 官方开发板 uboot 编译测试

1.1 查找 NXP 官方的开发板默认配置文件

  • uboot 移植并不是完完全全从零开始将 uboot 移植到我们现在所使用的开发板或者开发平台上。这个对于我们来说基本是不可能的,这个工作一般是半导体厂商做的,半导体厂商负责将 uboot 移植到他们的芯片上,因此半导体厂商都会自己做一个开发板,这个开发板就叫做原厂开发板,比如大家学习 STM32 时听说过的 discover 开发板就是 ST 自己做的。半导体厂商会将 uboot 移植到他们自己的原厂开发板上,测试好以后就会将这个 uboot 发布出去,这就是大家常说的原厂 BSP 包。我们一般做产品的时候就会参考原厂的开发板做硬件,然后在原厂提供的 BSP 包上做修改,将 uboot 或者 linux kernel 移植到我们的硬件上。这个就是 uboot 移植的一般流程:

    1. 在 uboot 中找到参考的开发平台,一般是原厂的开发板。
    2. 参考原厂开发板移植 uboot 到我们所使用的开发板上。
  • 正点原子的 I.MX6ULL 开发板参考的是 NX 官方的 I.MX6ULL EVK 开发板做的硬件,因此我们在移植 uboot 时就可以以 NXP 官方的 I.MX6ULL EVK 开发板为蓝本。

  • 本章我们是将 NXP 官方的 uboot 移植到正点原子的 I.MX6ULL 开发板上在移植之前,我们先编译一下 NXP 官方 I.MX6ULL EVK 开发板对应的 uboot,首先是配置 uboot,configs 录下有很多跟 I.MX6UL/6ULL 有关的配置如下图所示:

  • 从图中可以看出有很多的默认配置文件,其中以 mx6ul 开头的是 I.MX6UL 芯片的,mx6ull 开头的是 I.MX6ULL 开发板的。I.MX6UL/6ULL 有 9x9mm 和 14x14mm 两种尺寸的,所以我们可以看到会有 mx6ull_9x9 和 mx6ull_14x14 开头的默认配置文件。我们使用的是 14x14mm 的芯片,所以关注 mx6ull_14x14 开头的默认配置文件。正点原子的 I.MX6ULL 有 EMMC 和 NAND 两个版本的,因此我们最终只需要关注 mx6ull_14x14_evk_emmc_defconfigmx6ull_14x14_evk_nand_defconfig 这两个配置文件就行了。本章我们讲解 EMMC 版本的移植(NAND 版本移植很多类似),所以使用mx6ull_14x14_evk_emmc_defconfig 作为默认配置文件。

1.2 编译 NXP 官方开发板对应的 uboot

  • 找到 NXP 官方 I.MX6ULL EVK 开发板对应的默认配置文件以后就可以编译一下,使用如下命令编译 uboot:

    1
    2
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig
    make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
  • 在编译的时候需要输入ARCHCORSS_COMPILE这两个变量的值,这样太麻烦了。我们可以直接在顶层 Makefile 中直接给 ARCHCORSS_COMPILE 赋值:

  • 250、251 行就是直接给 ARCHCROSS_COMPILE 赋值,这样我们就可以使用如下简短的命令来编译 uboot:

1
2
make mx6ull_14x14_evk_emmc_defconfig
make V=1 -j16
  • 如果既不想修改 uboot 的顶层 Makefile,又想编译的时候不用输入那么多,那么就直接创建个 shell 脚本就行了,shell 脚本名为 mx6ull_14x14_emmc.sh,然后在 shell 脚本里面输入如下内容:

    1
    2
    3
    4
    #!/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- -j16
  • 记得给mx6ull_14x14_emmc.sh 这个文件可执行权限,使用 mx6ull_14x14_emmc.sh 脚本编译 uboot 的时候每次都会清理一下工程,然后全部重新编译,编译的时候直接执行这个脚本就行了,命令如下:

    1
    ./mx6ull_14x14_evk_emmc.sh
  • 编译完成以后会生成u-boot.binu-boot.imx 等文件,但是这些文件是 NXP 官方 I.MX6ULL EVK 开发板。能不能用到正点原子的 I.MX6ULL 开发板上呢?试一下不就知道了!

1.3 烧写验证与驱动测试

  • 将 imxdownload 软件拷贝到 uboot 源码根目录下,然后使用 imxdownload 软件将 u-boot.bin烧写到 SD 卡中,烧写命令如下:

    1
    2
    chmod 777 imxdownload //给予 imxdownload 可执行权限
    ./imxdownload u-boot.bin /dev/sdd //烧写到 SD 卡中,不能烧写到/dev/sda 或 sda1 里面
  • 烧写完成以后将 SD 卡插入 I.MX6U-ALPHA 开发板的 TF 卡槽中,最后设置开发板从 SD 卡启动。打开 SecureCRT,设置好开发板所使用的串口并打开,复位开发板,SecureCRT 接收到如下图所示信息:

  • 从图可以看出,uboot 启动正常,虽然我们用的是 NXP 官方 I.MX6ULL 开发板的 uboot,但是在正点原子的 I.MX6ULL 开发板上是可以正常启动的。而且 DRAM 识别正确,为 512MB,如果用的 NAND 版本的核心版的话 uboot 启动会失败!因为 NAND 核心版用的 256MB 的 DRAM。

  1. SD 卡和 EMMC 驱动检查

    • 检查 SD 卡和 EMMC 驱动是否正常,使用命令 mmc list 列出当前的 MMC 设备,结果如图所示:

    • 从图 33.1.3.2 可以看出当前有两个 MMC 设备,检查每个 MMC 设备信息,先检查 MMC 设备 0,输入如下命令:

      1
      2
      mmc dev 0
      mmc info

    • 可以看出,mmc 设备 0 是 SD 卡,SD 卡容量为 14.8GB,这个和我所使用的 SD 卡信息相符,说明 SD 卡驱动正常。再来检查 MMC 设备 1,输入如下命令:

      1
      2
      mmc dev 1
      mmc info

    • 可以看出,mmc 设备 1 为 EMMC,容量为 7.3GB,说明 EMMC 驱动也成功,SD 卡和 EMMC 的驱动都没问题。

  2. LCD 驱动检查

    • 如果 uboot 中的 LCD 驱动正确的话,启动 uboot 以后 LCD 上应该会显示出 NXP 的 logo
  3. 网络驱动

    • uboot 启动的时候提示“Board Net Initialization Failed”和“No ethernet found.”这两行,说明网络驱动也有问题,正常情况下应该是如下图所示:

    • 现在没有图中的信息,说明当前 uboot 的网络部驱动也是有问题的,这是因为正点原子开发板的网络芯片复位引脚和 NXP 官方开发板不一样,因此需要修改驱动。

    • 总结一下 NXP 官方 I.MX6ULL EVK 开发板的 uboot 在正点原子 EMMC 版本 I.MX6ULL 开发板上的运行情况:

      1. uboot 启动正常,DRAM 识别正确,SD 卡和 EMMC 驱动正常。
      2. uboot 里面的 LCD 驱动默认是给 4.3 寸 480x272 分辨率的,如果使用的其他分辨率的屏幕需要修改驱动。
      3. 网络不能工作,识别不出来网络信息,需要修改驱动。 接下来我们要做的工作如下:
      4. 前面我们一直使用着 NXP 官方开发板的 uboot 配置,接下来需要在 uboot 中添加我们自己的开发板,也就是正点原子的 I.MX6ULL 开发板。
      5. 解决 LCD 驱动和网络驱动的问题。

2.在 U-Boot 中添加自己的开发板

  • NXP 官方 uboot 中默认都是 NXP 自己的开发板,虽说我们可以直接在官方的开发板上直接修改,使 uboot 可以完整的运行在我们的板子上。但是从学习的角度来讲,这样我们就不能了解到 uboot 是如何添加新平台的。接下来我们就参考 NXP 官方的 I.MX6ULL EVK 开发板,学习如何在 uboot 中添加我们的开发板或者开发平台。

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

  • 先在 configs 目录下创建默认配置文件,复制 mx6ull_14x14_evk_emmc_defconfig,然后重命名为 mx6ull_alientek_emmc_defconfig,命令如下:

    1
    2
    cd configs
    cp mx6ull_14x14_evk_emmc_defconfig mx6ull_alientek_emmc_defconfig
  • 然后将文件 mx6ull_alientek_emmc_defconfig 中的内容改成下面的:

    1
    2
    3
    4
    5
    CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ull_alientek_emmc/imximage.cfg,MX6ULL_EVK_EMMC_REWORK"
    CONFIG_ARM=y
    CONFIG_ARCH_MX6=y
    CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y
    CONFIG_CMD_GPIO=y
  • 可以看出mx6ull_alientek_emmc_defconfig 基本和 mx6ull_14x14_evk_emmc_defconfig 中的内容一样,只是第 1 行和第 4 行做了修改。

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

  • 在目录 include/configs 下添加 I.MX6ULL-ALPHA 开发板对应的头文件,复制include/configs/mx6ullevk.h,并重命名为 mx6ull_alientek_emmc.h,命令如下:

    1
    cp include/configs/mx6ullevk.h include/configs/mx6ull_alientek_emmc.h
  • 拷贝完成以后将:

    1
    2
    #ifndef __MX6ULLEVK_CONFIG_H
    #define __MX6ULLEVK_CONFIG_H
  • 改为:

    1
    2
    #ifndef __MX6ULL_ALIENTEK_EMMC_CONFIG_H
    #define __MX6ULL_ALIENTEK_EMMC_CONFIG_H
  • mx6ull_alientek_emmc.h 里面有很多宏定义,这些宏定义基本用于配置 uboot,也有一些 I.MX6ULL 的配置项目。如果我们自己要想使能或者禁止 uboot 的某些功能,那就在mx6ull_alientek_emmc.h 里面做修改即可。mx6ull_alientek_emmc.h 里面的内容比较多,去掉一些用不到的配置,精简后的内容如下(略):

  • 可以看出,mx6ull_alientek_emmc.h 文件中基本都是"CONFIG_"开头的宏定义,这也说明 mx6ull_alientek_emmc.h 文件的主要功能就是配置或者裁剪 uboot。如果需要某个功能的话就在里面添加这个功能对应的 CONFIG_XXX 宏即可,如果不需要某个功能的话就删除掉对应的宏即可。我们详细的看一下 mx6ull_alientek_emmc.h 中这些宏都是什么功能:

    • PHYS_SDRAM_SIZE 就是板子上 DRAM 的大小、
    • CONFIG_DISPLAY_CPUINFO,uboot 启动的时候可以输出 CPU 信息
    • CONFIG_DISPLAY_BOARDINFO,uboot 启动的时候可以输出板子信息
    • CONFIG_SYS_MALLOC_LEN 为 malloc 内存池大小,这里设置为 16MB
    • CONFIG_MXC_UART_BASE 表示串口寄存器基地址
    • CONFIG_SYS_FSL_ESDHC_ADDR 为 EMMC 所使用接口的寄存器基地址
    • 和 I2C 有关的宏定义,用于控制使能哪个 I2C,I2C 的速度为多少
    • CONFIG_MFG_ENV_SETTINGS 定义了一些环境变量,使用 MfgTool 烧写系统时候会用到这里面的环境变量
    • CONFIG_EXTRA_ENV_SETTINGS 也是设置一些环境变量
    • CONFIG_SYS_LOAD_ADDR 表示 linux kernel 在 DRAM 中的加载地址,也就是 linux kernel 在 DRAM 中的存储首地址,CONFIG_LOADADDR=0X80800000
    • CONFIG_SYS_HZ 为系统时钟频率,这里为 1000Hz
    • PHYS_SDRAM 为 I.MX6ULL 的 DRAM 控制器 MMDC0 所管辖的 DRAM 范围起始地址,也就是 0X80000000
  • 关于 mx6ull_alientek_emmc.h 就讲解到这里,其中以 CONFIG_CMD 开头的宏都是用于使能相应命令的,其他的以 CONFIG 开头的宏都是完成一些配置功能的。以后会频繁的和mx6ull_alientek_emmc.h 这个文件打交道。

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

  • uboot 中每个板子都有一个对应的文件夹来存放板级文件,比如开发板上外设驱动文件等等。NXP 的 I.MX 系列芯片的所有板级文件夹都存放在 board/freescale 目录下,在这个目录下有个名为 mx6ullevk 的文件夹,这个文件夹就是 NXP 官方 I.MX6ULL EVK 开发板的板级文件夹。复制 mx6ullevk,将其重命名为 mx6ull_alientek_emmc,命令如下:

    1
    2
    cd board/freescale/
    cp mx6ullevk/ -r mx6ull_alientek_emmc
  • 进 入 mx6ull_alientek_emmc 目 录 中 , 将 其 中 的 mx6ullevk.c 文 件 重 命 名 为mx6ull_alientek_emmc.c,命令如下:

    1
    2
    cd mx6ull_alientek_emmc
    mv mx6ullevk.c mx6ull_alientek_emmc.c
  • 我们还需要对 mx6ull_alientek_emmc 目录下的文件做一些修改:

    1. 修改mx6ull_alientek_emmc 目录下的 Makefile 文件 :

      将 mx6ull_alientek_emmc 下的 Makefile 文件内容改为如下所示:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      # (C) Copyright 2015 Freescale Semiconductor, Inc.
      #
      # SPDX-License-Identifier: GPL-2.0+
      #

      obj-y := mx6ull_alientek_emmc.o

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

      重点是第 6 行的obj-y,改为mx6ull_alientek_emmc.o,这样才会编译mx6ull_alientek_emmc.c这个文件。

    2. 修改mx6ull_alientek_emmc 目录下的 imximage.cfg 文件,将 imximage.cfg 中的下面一句:

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

      改为:

      1
      PLUGIN board/freescale/mx6ull_alientek_emmc/plugin.bin 0x00907000
    3. 修改mx6ull_alientek_emmc 目录下的 Kconfig 文件

      修改 Kconfig 文件,修改后的内容如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      if TARGET_MX6ULL_ALIENTEK_EMMC

      config SYS_BOARD
      default "mx6ull_alientek_emmc"

      config SYS_VENDOR
      default "freescale"

      config SYS_SOC
      default "mx6"

      config SYS_CONFIG_NAME
      default "mx6ull_alientek_emmc"

      endif
    4. 修改 mx6ull_alientek_emmc 目录下的 MAINTAINERS 文件

      1
      2
      3
      4
      5
      MX6ULL_ALIENTEK_EMMC BOARD
      M: Peng Fan <peng.fan@nxp.com>
      S: Maintained
      F: board/freescale/mx6ull_alientek_emmc/
      F: include/configs/mx6ull_alientek_emmc.h

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

  • uboot 是支持图形界面配置,修改文件arch/arm/cpu/armv7/mx6/Kconfig(如果用的 I.MX6UL 的话,应该修改 arch/arm/Kconfig 这个文件),在 207 行加入如下内容:

    1
    2
    3
    4
    5
    config TARGET_MX6ULL_ALIENTEK_EMMC
    bool "Support mx6ull_alientek_emmc"
    select MX6ULL
    select DM
    select DM_THERMAL
  • 在最后一行的 endif 的前一行添加如下内容:

    1
    source "board/freescale/mx6ull_alientek_emmc/Kconfig"
  • 添加完成以后的 Kconfig 文件如图所示:

  • 到此为止,I.MX6U-ALPHA 开发板就已经添加到 uboot 中了,接下来就是编译这个新添加的开发板。

2.5 使用新添加的板子配置编译 uboot

  • 在 uboot 根目录下新建一个名为 mx6ull_alientek_emmc.sh 的 shell 脚本,在这个 shell 脚本里面输入如下内容:

    1
    2
    3
    4
    #!/bin/bash
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_alientek_emmc_defconfig
    make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
  • 第 3 行我们使用的默认配置文件就是刚刚新建的mx6ull_alientek_emmc_defconfig 这个配置文件。给予 mx6ll_alientek_emmc.sh 可执行权限,然后运行脚本来完成编译,命令如下:

    1
    2
    chmod 777 mx6ull_alientek_emmc.sh //给予可执行权限,一次即可
    ./mx6ull_alientek_emmc.sh //运行脚本编译 uboot
  • 等待编译完成,编译完成以后输入如下命令,查看一下mx6ull_alientek_emmc.h 这个头文件有没有被引用:

    1
    grep -nR "mx6ull_alientek_emmc.h"
  • 如果有很多文件都引用了mx6ull_alientek_emmc.h 这个头文件,那就说明新板子添加成功:

  • 编译完成以后就使用 imxdownload 将新编译出来的 u-boot.bin 烧写到 SD 卡中测试,SecureCRT 输出结果如图所示:

  • 此时的 Board 还是“MX6ULL 14x14 EVK”,因为我们参考的 NXP 官方的 I.MX6ULL 开发板来添加自己的开发板。如果接了 LCD 屏幕的话会发现 LCD 屏幕并没有显示 NXP 的 logo,而且此时的网络同样也没识别出来。前面已经说了,默认 uboot 中的 LCD 驱动和网络驱动在正点原子的 I.MX6U-ALPHA 开发板上是有问题的,需要修改。

2.6 LCD 驱动修改

  • 一般 uboot 中修改驱动基本都是在 xxx.h 和 xxx.c 这两个文件中进行的,xxx 为板子名称,比如 mx6ull_alientek_emmc.hmx6ull_alientek_emmc.c 这两个文件。 一般修改 LCD 驱动重点注意以下几点:

    1. LCD 所使用的 GPIO,查看 uboot 中 LCD 的 IO 配置是否正确。
    2. LCD 背光引脚 GPIO 的配置。
    3. LCD 配置参数是否正确。
  • 正点原子的 I.MX6U-ALPHA 开发板 LCD 原理图和 NXP 官方 I.MX6ULL 开发板一致,也就是 LCD 的 IO 和背光 IO 都一样的,所以 IO 部分就不用修改了。需要修改的之后 LCD 参数,打开文件 mx6ull_alientek_emmc.c,找到如下所示内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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 中,定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    struct display_info_t {
    int bus;
    int addr;
    int pixfmt;
    int (*detect)(struct display_info_t const *dev);
    void (*enable)(struct display_info_t const *dev);
    struct fb_videomode mode;
    };
  • pixfmt 是像素格式,也就是一个像素点是多少位,如果是 RGB565 的话就是 16 位,如果是 888 的话就是 24 位,一般使用 RGB888。结构体 display_info_t 还有个 mode 成员变量,此成员变量也是个结构体,为fb_videomode,定义在文件 include/linux/fb.h 中,定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    struct fb_videomode {
    const char *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;
    };
  • 结构体 fb_videomode 里面的成员变量为 LCD 的参数,这些成员变量函数如下:

  • name:LCD 名字,要和环境变量中的 panel 相等。

  • xres、yres:LCD X 轴和 Y 轴像素数量。

  • pixclock:像素时钟,每个像素时钟周期的长度,单位为皮秒。

  • left_margin:HBP,水平同步后肩。

  • right_margin:HFP,水平同步前肩。

  • upper_margin:VBP,垂直同步后肩。

  • lower_margin:VFP,垂直同步前肩。

  • hsync_len:HSPW,行同步脉宽。

  • vsync_len:VSPW,垂直同步脉宽。

  • vmode:大多数使用 FB_VMODE_NONINTERLACED,也就是不使用隔行扫描。

  • 可以看出,这些参数和我们第二十四章讲解 RGB LCD 的时候参数基本一样,唯一不同的像素时钟 pixclock 的含义不同,以正点原子的 7 寸 1024*600 分辨率的屏幕(ATK7016)为例,屏幕要求的像素时钟为 51.2MHz,因此:pixclock=(1/51200000)*10^12=19531

  • 再根据其他的屏幕参数,可以得出 ATK7016 屏幕的配置参数如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    struct display_info_t const displays[] = {{
    .bus = MX6UL_LCDIF1_BASE_ADDR,
    .addr = 0,
    .pixfmt = 24,
    .detect = NULL,
    .enable = do_enable_parallel_lcd,
    .mode = {
    .name = "TFT7016",
    .xres = 1024,
    .yres = 600,
    .pixclock = 19531,
    .left_margin = 140, //HBPD
    .right_margin = 160, //HFPD
    .upper_margin = 20, //VBPD
    .lower_margin = 12, //VFBD
    .hsync_len = 20, //HSPW
    .vsync_len = 3, //VSPW
    .sync = 0,
    .vmode = FB_VMODE_NONINTERLACED
    } } };
  • 使用示例代码中的屏幕参数替换掉 mx6ull_alientek_emmc.c 中 uboot 默认的屏幕参数。

  • 打开 mx6ull_alientek_emmc.h,找到所有如下语句:

    1
    panel=TFT43AB
  • 将其改为:

    1
    panel=TFT7016
  • 也就是设置 panel 为 TFT7016,panel 的值要与示例代码中的.name 成员变量的值一致。修改完成以后重新编译一遍 uboot 并烧写到 SD 中启动。重启以后 LCD 驱动一般就会工作正常了,LCD 上回显示 NXP 的 logo。但是有可能会遇到 LCD 并没有工作,还是黑屏,这是什么原因呢?在 uboot 命令模式输入“print”来查看环境变量 panel 的值,会发现 panel 的值要是 TFT43AB(或其他的,反正不是 TFT7016),如图所示:

  • 这是因为之前有将环境变量保存到 EMMC 中,uboot 启动以后会先从 EMMC 中读取环境变量,如果 EMMC 中没有环境变量的话才会使用 mx6ull_alientek_emmc.h 中的默认环境变量。如果 EMMC 中的环境变量 panel 不等于 TFT7016,那么 LCD 显示肯定不正常,我们只需要在 uboot 中修改 panel 的值为 TFT7016 即可,在 uboot 的命令模式下输入如下命令:

    1
    2
    setenv panel TFT7016
    saveenv
  • 上述命令修改环境变量 panel 为 TFT7016,然后保存,重启 uboot,此时 LCD 驱动就工作正常了。如果 LCD 还是没有正常工作的,那就要检查自己哪里有没有改错,或者还有哪里没有修改。

2.7 底板网络驱动修改

V2.4 及以后版本的底板网络芯片更换为 SR8201F,并且网络 PHY 地址也有改变

  1. I.MX6U-ALPHA 开发板网络简介

    • I.MX6UL/ULL 内部有个以太网 MAC 外设,也就是 ENET,需要外接一个 PHY 芯片来实现网络通信功能,也就是内部 MAC+外部 PHY 芯片的方案。大家可能听过 DM9000 这个网络芯片,在一些没有内部 MAC 的 CPU 中,比如三星的 2440,4412 等,就会采用 DM9000 来实现联网功能。DM9000 提供了一个类似 SRAM 的访问接口,主控 CPU 通过这个接口即可与 DM9000 进行通信,DM9000 就是一个 MAC+PHY 芯片。这个方案就相当于外部 MAC+外部 PHY,那么 I.MX6U 这样的内部 MAC+PHY 芯片与 DM9000 方案比有什么优势吗?那优势大了去了!首先就是通信效率和速度,一般 SOC 内部的 MAC 是带有一个专用 DMA (Direct Memory Access) 的,专门用于处理网络数据包,采用 SRAM 来读写 DM9000 的速度是压根就没法和内部 MAC+外部 PHY 芯片的速度比。采用外部 DM9000 完全是无奈之举,谁让 2440,4412 这些芯片内部没有以太网外设呢,现在又想用有线网络,没有办法只能找个 DM9000 的方案。从这里也可以看出,三星的 2440、4412 这些芯片设计之初就不是给工业产品用的,他们是给消费类电子使用的,比如手机、平板等,手机或平板要上网,可以通过 WIFI 或者 4G,我是没有见过哪个手机或者平板上网是要接根网线的。正点原子的 I.MX6U-ALPHA 开发板也可以通过 WIFI 或者 4G 上网,这个是后话了。

    • I.MX6UL/ULL 有两个网络接口 ENET1 和 ENET2,正点原子的 I.MX6U-ALPHA 开发板提供了这两个网络接口,其中 ENET1 和 ENET2 都使用 SR8201F 作为 PHY 芯片。NXP 官方的 I.MX6ULL EVK 开发板使用 KSZ8081 这颗 PHY 芯片,SR8201F 相比 KSZ8081 具有体积小、外围器件少、价格便宜等优点。直接使用 KSZ8081 固然可以,但是我们在实际的产品中不一定会使用 KSZ8081,有时候为了降低成本会选择其他的 PHY 芯片,这个时候就有个问题:换了 PHY 芯片以后网络驱动怎么办?为此,正点原子的 I.MX6U-ALPHA 开发板将 ENET1 和 ENET2 的 PHY 换成了 SR8201F,这样就可以给大家讲解更换 PHY 芯片以后如何调整网络驱动,使网络工作正常

    • ENET1 的网络 PHY 芯片为 SR8201F,通过 RMII 接口与 I.MX6ULL 相连,正点原子 I.MX6U-ALPHA 开发板的 ENET1 引脚与 NXP 官方的 I.MX6ULL EVK 开发板基本一样,唯独复位引脚不同。正点原子 I.MX6U-ALPHA 开发板的 ENET1 复位引脚 ENET1_RST 接到了 I.M6ULL 的 SNVS_TAMPER7 这个引脚上。 SR8201F 内部是有寄存器的,I.MX6ULL 会读取 SR8201F 内部寄存器来判断当前的物理链接状态、连接速度(10M 还是 100M)和双工状态(半双工还是全双工)。I.MX6ULL 通过 MDIO 接口来读取 PHY 芯片的内部寄存器,MDIO 接口有两个引脚,ENET_MDC 和 ENET_MDIO, ENET_MDC 提供时钟,ENET_MDIO 进行数据传输。一个 MDIO 接口可以管理 32 个 PHY 芯片,同一个 MDIO 接口下的这些 PHY 使用不同的器件地址来做区分,MIDO 接口通过不同的器件地址即可访问到相应的 PHY 芯片。I.MX6U-ALPHA 开发板 ENET1 上连接的 SR8201F 器件地址为 0X2,所示我们要修改 ENET1 网络驱动的话重点就三点:

    1. ENET1 复位引脚初始化。
    2. SR8201F 的器件 ID。
    3. SR8201F 驱动
    • 关于 ENET2 网络驱动的修改也注意一下三点:
    1. ENET2 的复位引脚,ENET2 的复位引脚 ENET2_RST 接到了 I.MX6ULL 的 SNVS_TAMPER8 上。
    2. ENET2 所使用的 PHY 芯片器件地址,PHY 器件地址为 0X1。
    3. SR8201F 驱动,ENET1 和 ENET2 都使用的 SR8201F,所以驱动肯定是一样的。
  2. 网络 PHY 地址修改

    • 首先修改 uboot 中的 ENET1 和 ENET2 的 PHY 地址和驱动,打开 mx6ull_alientek_emmc.h 这个文件,找到如下代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      #ifdef CONFIG_CMD_NET
      #define CONFIG_CMD_PING
      #define CONFIG_CMD_DHCP
      #define CONFIG_CMD_MII
      #define CONFIG_FEC_MXC
      #define CONFIG_MII
      #define CONFIG_FEC_ENET_DEV 1

      #if (CONFIG_FEC_ENET_DEV == 0)
      #define IMX_FEC_BASE ENET_BASE_ADDR
      #define CONFIG_FEC_MXC_PHYADDR 0x2
      #define CONFIG_FEC_XCV_TYPE RMII
      #elif (CONFIG_FEC_ENET_DEV == 1)
      #define IMX_FEC_BASE ENET2_BASE_ADDR
      #define CONFIG_FEC_MXC_PHYADDR 0x1
      #define CONFIG_FEC_XCV_TYPE RMII
      #endif
      #define CONFIG_ETHPRIME "FEC"

      #define CONFIG_PHYLIB
      #define CONFIG_PHY_MICREL
      #endif
    • 第 7 行的宏 CONFIG_FEC_ENET_DEV 用于选择使用哪个网口,默认为 1,也就是选择 ENET2。第 11 行为 ENET1 的 PHY 地址,默认是 0X2,第 15 行为 ENET2 的 PHY 地址,默认为 0x1。根据前面的分析可知,正点原子的 I.MX6U-ALPHA 开发板 ENET1 的 PHY 地址刚好也为 0X2,ENET2 的 PHY 地址也是 0X1,所以可以直接使用默认配置,不需要我们修改。

    • 第 21 行定了一个宏 CONFIG_PHY_MICREL,此宏用于使能 uboot 中 Micrel 公司的 PHY 驱动,KSZ8081 这颗 PHY 芯片就是 Micrel 公司生产的,不过 Micrel 已经被 Microchip 收购了。如果要使用 SR8201F,那么就得将 CONFIG_PHY_MICREL 改为 CONFIG_PHY_REALTEK。因为 SR8201F 就是 Pin to Pin 替换 Realtek 的 RTL8201F 的,因此可以直接使用 Realtek 的相关驱动。所以示例代码理论上有三处要修改:

      1. 修改 ENET1 网络 PHY 的地址。
      2. 修改 ENET2 网络 PHY 的地址。
      3. 使能 REALTEK 公司的 PHY 驱动。
    • 但是,由于我们开发板上 SR8201F 的 PHY 地址和默认的配置一样,因此只需要是能 REALTEK 公司的 PHY 驱动,大家如果使用的其他品牌的开发板,那么就要根据实际情况来修改对应的 ENET1 和 ENET2 网络 PHY 地址。 修改后的网络 PHY 地址参数如下所示:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      #ifdef CONFIG_CMD_NET
      #define CONFIG_CMD_PING
      #define CONFIG_CMD_DHCP
      #define CONFIG_CMD_MII
      #define CONFIG_FEC_MXC
      #define CONFIG_MII
      #define CONFIG_FEC_ENET_DEV 1

      #if (CONFIG_FEC_ENET_DEV == 0)
      #define IMX_FEC_BASE ENET_BASE_ADDR
      #define CONFIG_FEC_MXC_PHYADDR 0x2
      #define CONFIG_FEC_XCV_TYPE RMII
      #elif (CONFIG_FEC_ENET_DEV == 1)
      #define IMX_FEC_BASE ENET2_BASE_ADDR
      #define CONFIG_FEC_MXC_PHYADDR 0x1
      #define CONFIG_FEC_XCV_TYPE RMII
      #endif
      #define CONFIG_ETHPRIME "FEC"

      #define CONFIG_PHYLIB
      #define CONFIG_PHY_REALTEK
      #endif
  3. 删除 uboot 中 74LV595 的驱动代码

    • uboot 中网络 PHY 芯片地址修改完成以后就是网络复位引脚的驱动修改了,打开mx6ull_alientek_emmc.c,找到如下代码:

      1
      2
      3
      4
      #define IOX_SDI IMX_GPIO_NR(5, 10)
      #define IOX_STCP IMX_GPIO_NR(5, 7)
      #define IOX_SHCP IMX_GPIO_NR(5, 11)
      #define IOX_OE IMX_GPIO_NR(5, 8)
    • 以 IOX 开头的宏定义是 74LV595 的相关 GPIO,因为 NXP 官方 I.MX6ULL EVK 开发板使用 74LV595 来扩展 IO,两个网络的复位引脚就是由 74LV595 来控制的。正点原子的 I.MX6U-ALPHA 开发板并没有使用 74LV595,因此我们将对应的代码删除掉,替换为如下所示代码:

      1
      2
      #define ENET1_RESET IMX_GPIO_NR(5, 7)
      #define ENET2_RESET IMX_GPIO_NR(5, 8)
    • ENET1 的复位引脚连接到 SNVS_TAMPER7 上,对应 GPIO5_IO07,ENET2 的复位引脚连接到 SNVS_TAMPER8 上,对应 GPIO5_IO08。继续在 mx6ull_alientek_emmc.c 中找到如下代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      static iomux_v3_cfg_t const iox_pads[] = {
      /* IOX_SDI */
      MX6_PAD_BOOT_MODE0__GPIO5_IO10 | MUX_PAD_CTRL(NO_PAD_CTRL),
      /* IOX_SHCP */
      MX6_PAD_BOOT_MODE1__GPIO5_IO11 | MUX_PAD_CTRL(NO_PAD_CTRL),
      /* IOX_STCP */
      MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL),
      /* IOX_nOE */
      MX6_PAD_SNVS_TAMPER8__GPIO5_IO08 | MUX_PAD_CTRL(NO_PAD_CTRL),
      };
    • 同理,这是 74LV595 的 IO 配置参数结构体,将其删除掉(因为其用到这两个 IO 会冲突)。继续在 mx6ull_alientek_emmc.c 中找到函数 iox74lv_init,如下所示:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      static void iox74lv_init(void)
      {
      int i;
      gpio_direction_output(IOX_OE, 0);

      for (i = 7; i >= 0; i--) {
      gpio_direction_output(IOX_SHCP, 0);
      gpio_direction_output(IOX_SDI, seq[qn_output[i]][0]);
      udelay(500);
      gpio_direction_output(IOX_SHCP, 1);
      udelay(500);
      }

      ......
      /*
      * shift register will be output to pins
      */
      gpio_direction_output(IOX_STCP, 1);
      };

      void iox74lv_set(int index)
      {
      int i;

      for (i = 7; i >= 0; i--) {
      gpio_direction_output(IOX_SHCP, 0);

      if (i == index)
      gpio_direction_output(IOX_SDI, seq[qn_output[i]][0]);
      else
      gpio_direction_output(IOX_SDI, seq[qn_output[i]][1]);
      udelay(500);
      gpio_direction_output(IOX_SHCP, 1);
      udelay(500);
      }
      ......
      /*
      * shift register will be output to pins
      */
      gpio_direction_output(IOX_STCP, 1);
      };
    • iox74lv_init 函数是 74LV595 的初始化函数,iox74lv_set 函数用于控制 74LV595 的 IO 输出电平,将这两个函数全部删除掉! 在 mx6ull_alientek_emmc.c 中找到 board_init 函数,此函数是板子初始化函数,会被 board_init_r 调用,board_init 函数内容如下:

      1
      2
      3
      4
      5
      6
      7
      8
      int board_init(void)
      {
      ......
      imx_iomux_v3_setup_multiple_pads(iox_pads, ARRAY_SIZE(iox_pads));
      iox74lv_init();
      ......
      return 0;
      }
    • board_init 会调用 imx_iomux_v3_setup_multiple_padsiox74lv_init 这两个函数来初化 74lv595 的 GPIO,将这两行删除掉。至此,mx6ull_alientek_emmc.c 中关于 74LV595 芯片的驱动代码都删除掉了,接下来就是添加 I.MX6U-ALPHA 开发板两个网络复位引脚了。

  4. 添加 I.MX6U-ALPHA 开发板网络复位引脚驱动

    • mx6ull_alientek_emmc.c 中找到如下所示代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      static iomux_v3_cfg_t const fec1_pads[] = {
      MX6_PAD_GPIO1_IO06__ENET1_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
      MX6_PAD_GPIO1_IO07__ENET1_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),
      ...
      MX6_PAD_ENET1_RX_ER__ENET1_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
      MX6_PAD_ENET1_RX_EN__ENET1_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
      };

      static iomux_v3_cfg_t const fec2_pads[] = {
      MX6_PAD_GPIO1_IO06__ENET2_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
      MX6_PAD_GPIO1_IO07__ENET2_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),
      ...
      MX6_PAD_ENET2_RX_EN__ENET2_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
      MX6_PAD_ENET2_RX_ER__ENET2_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
      };
    • 结构体数组 fec1_padsfec2_pads 是 ENET1 和 ENET2 这两个网口的 IO 配置参数,在这两个数组中添加两个网口的复位 IO 配置参数,完成以后如下所示:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      static iomux_v3_cfg_t const fec1_pads[] = {
      MX6_PAD_GPIO1_IO06__ENET1_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
      MX6_PAD_GPIO1_IO07__ENET1_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),
      ..
      MX6_PAD_ENET1_RX_ER__ENET1_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
      MX6_PAD_ENET1_RX_EN__ENET1_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
      MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL),
      };

      static iomux_v3_cfg_t const fec2_pads[] = {
      MX6_PAD_GPIO1_IO06__ENET2_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
      MX6_PAD_GPIO1_IO07__ENET2_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),
      ..
      MX6_PAD_ENET2_RX_EN__ENET2_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
      MX6_PAD_ENET2_RX_ER__ENET2_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
      MX6_PAD_SNVS_TAMPER8__GPIO5_IO08 | MUX_PAD_CTRL(NO_PAD_CTRL),
      };
    • 第 7 行和 16 行分别是 ENET1 和 ENET2 的复位 IO 配置参数。继续在文件 mx6ull_alientek_emmc.c 中找到函数 setup_iomux_fec,此函数默认代码如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      static void setup_iomux_fec(int fec_id)
      {
      if (fec_id == 0)
      imx_iomux_v3_setup_multiple_pads(fec1_pads,
      ARRAY_SIZE(fec1_pads));
      else
      imx_iomux_v3_setup_multiple_pads(fec2_pads,
      ARRAY_SIZE(fec2_pads));
      }
    • 函数 setup_iomux_fec 就是根据 fec1_padsfec2_pads 这两个网络 IO 配置数组来初始化 I.MX6ULL 的网络 IO。我们需要在其中添加网络复位 IO 的初始化代码,并且复位一下 PHY 芯片,修改后的 setup_iomux_fec 函数如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      static void setup_iomux_fec(int fec_id)
      {
      if (fec_id == 0)
      {

      imx_iomux_v3_setup_multiple_pads(fec1_pads,
      ARRAY_SIZE(fec1_pads));

      gpio_direction_output(ENET1_RESET, 1);
      gpio_set_value(ENET1_RESET, 0);
      mdelay(20);
      gpio_set_value(ENET1_RESET, 1);
      }
      else
      {
      imx_iomux_v3_setup_multiple_pads(fec2_pads,
      ARRAY_SIZE(fec2_pads));
      gpio_direction_output(ENET2_RESET, 1);
      gpio_set_value(ENET2_RESET, 0);
      mdelay(20);
      gpio_set_value(ENET2_RESET, 1);
      }
      mdelay(150); /* 复位结束后至少延时150ms才能正常使用*/
      }
    • 示例代码中第 9 行 ~ 12 行和第 18 行~ 21 行分别对应 ENET1 和 ENET2 的复位 IO 初始化,将这两个 IO 设置为输出并且硬件复位一下 LAN8720A,这个硬件复位很重要!第 22 行复位结束以后一定要至少延时 150ms 才能操作 SR8201F,这个在 SR8201F 数据手册里面有详细要求的,否则会导致 uboot 无法识别 SR8201F。

    • 至此网络的复位引脚驱动修改完成,重新编译 uboot,然后将 u-boot.bin 烧写到 SD 卡中并启动,uboot 启动信息如图所示:

    • 可以看到“Net:FEC1”这一行,提示当前使用的 FEC1 这个网口,也就是 ENET2。在 uboot 中使用网络之前要先设置几个环境变量,命令如下:

      1
      2
      3
      4
      5
      6
      setenv ipaddr 192.168.1.55 //开发板 IP 地址
      setenv ethaddr b8:ae:1d:01:00:00 //开发板网卡 MAC 地址
      setenv gatewayip 192.168.1.1 //开发板默认网关
      setenv netmask 255.255.255.0 //开发板子网掩码
      setenv serverip 192.168.1.250 //服务器地址,也就是 Ubuntu 地址
      saveenv //保存环境变量
    • 设置好环境变量以后就可以在 uboot 中使用网络了,用网线将 I.MX6U-ALPHA 上的 ENET2 与电脑或者路由器连接起来,保证开发板和电脑在同一个网段内,通过 ping 命令来测试一下网络连接,命令如下:

      1
      ping 192.168.1.250
    • 结果如图:

    • 有“host 192.168.1.250 is alive”这句,说明 ping 主机成功,说明 ENET2 网络工作正常。再来测试一下 ENET1 的网络是否正常工作,打开mx6ull_alientek_emmc.h,将 CONFIG_FEC_ENET_DEV 改为 0,然后重新编译一下 uboot 并烧写到 SD 卡中重启。重启开发板,uboot 输出信息如图 33.2.8.5 所示:

    • 有“Net:FEC0”这一行,说明当前使用的 FEC0 这个网卡,也就是 ENET1,同样的 ping 一下主机,结果如图 33.2.8.6 所示:

    • ping 主机也成功,说明 ENET1 网络也工作正常,至此,I.MX6U-ALPHA 开发板的两个网络都工作正常了,建议大家将 ENET2 设置为 uboot 的默认网卡!也就是将宏 CONFIG_FEC_ENET_DEV 设置为 1 。

    2.8 其他需要修改的地方

    • 在 uboot 启动信息中会有“Board: MX6ULL 14x14 EVK”这一句,也就是说板子名字为“MX6ULL 14x14 EVK”,要将其改为我们所使用的板子名字,比如“MX6ULL ALIENTEK EMMC”或者“MX6ULL ALIENTEK NAND”。打开文件 mx6ull_alientek_emmc.c,找到函数checkboard,将其改为如下所示内容:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      int checkboard(void)
      {
      if (is_mx6ull_9x9_evk())
      puts("Board: MX6ULL 9x9 EVK\n");
      else
      puts("Board: MX6ULL ALIENTEK EMMC\n");

      return 0;
      }
    • 修改完成以后重新编译 uboot 并烧写到 SD 卡中验证,uboot 启动信息如图所示:

    • Board 变成了“MX6ULL ALIENTEK EMMC”。至此 uboot 的驱动部分就修改完成了,uboot 移植也完成了,uboot 的最终目的就是启动 Linux 内核,所以需要通过启动 Linux 内核来判断 uboot 移植是否成功。在启动 Linux 内核之前我们先来学习两个重要的环境变量 bootcmdbootargs

3.bootcmd 和 bootargs 环境变量

  • uboot 中有两个非常重要的环境变量 bootcmdbootargs,接下来看一下这两个环境变量。bootcmdbootagrs 是采用类似 shell 脚本语言编写的,里面有很多的变量引用,这些变量其实都是环境变量,有很多是 NXP 自己定义的。文件 mx6ull_alientek_emmc.h 中的宏 CONFIG_EXTRA_ENV_SETTINGS 保存着这些环境变量的默认值,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #if defined(CONFIG_SYS_BOOT_NAND)
    #define CONFIG_EXTRA_ENV_SETTINGS \
    CONFIG_MFG_ENV_SETTINGS \
    "panel=TFT43AB\0" \
    "fdt_addr=0x83000000\0" \
    "fdt_high=0xffffffff\0" \
    ..
    "bootz ${loadaddr} - ${fdt_addr}\0"

    #else
    #define CONFIG_EXTRA_ENV_SETTINGS \
    CONFIG_MFG_ENV_SETTINGS \
    "script=boot.scr\0" \
    "image=zImage\0" \
    "console=ttymxc0\0" \
    "fdt_high=0xffffffff\0" \
    "initrd_high=0xffffffff\0" \
    ..
    "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 test $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;\0" \
  • CONFIG_EXTRA_ENV_SETTINGS 是个条件编译语句,使用 NAND 和 EMMC 的时候宏 CONFIG_EXTRA_ENV_SETTINGS 的值是不同的。

3.1 环境变量 bootcmd

  • bootcmd 在前面已经说了很多次了,bootcmd 保存着 uboot 默认命令,uboot 倒计时结束以后就会执行 bootcmd 中的命令。这些命令一般都是用来启动 Linux 内核的,比如读取 EMMC 或者 NAND Flash 中的 Linux 内核镜像文件和设备树文件到 DRAM 中,然后启动 Linux 内核。可以在 uboot 启动以后进入命令行设置 bootcmd 环境变量的值。如果 EMMC 或者 NAND 中没有保存 bootcmd 的值,那么 uboot 就会使用默认的值,板子第一次运行 uboot 的时候都会使用默认值来设置 bootcmd 环境变量。打开文件 include/env_default.h,在此文件中有如下所示内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    #ifdef DEFAULT_ENV_INSTANCE_EMBEDDED
    env_t environment __PPCENV__ = {
    ENV_CRC, /* CRC Sum */
    #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
    1, /* Flags: valid */
    #endif
    {
    #elif defined(DEFAULT_ENV_INSTANCE_STATIC)
    static char default_environment[] = {
    #else
    const uchar default_environment[] = {
    #endif
    #ifdef CONFIG_ENV_CALLBACK_LIST_DEFAULT
    ENV_CALLBACK_VAR "=" CONFIG_ENV_CALLBACK_LIST_DEFAULT "\0"
    #endif
    #ifdef CONFIG_ENV_FLAGS_LIST_DEFAULT
    ENV_FLAGS_VAR "=" CONFIG_ENV_FLAGS_LIST_DEFAULT "\0"
    #endif
    #ifdef CONFIG_BOOTARGS
    "bootargs=" CONFIG_BOOTARGS "\0"
    #endif
    #ifdef CONFIG_BOOTCOMMAND
    "bootcmd=" CONFIG_BOOTCOMMAND "\0"
    #endif
    #ifdef CONFIG_RAMBOOTCOMMAND
    "ramboot=" CONFIG_RAMBOOTCOMMAND "\0"
    #endif
    #ifdef CONFIG_NFSBOOTCOMMAND
    "nfsboot=" CONFIG_NFSBOOTCOMMAND "\0"
    #endif
    #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
    "bootdelay=" __stringify(CONFIG_BOOTDELAY) "\0"
    #endif
    #if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
    "baudrate=" __stringify(CONFIG_BAUDRATE) "\0"
    #endif
    #ifdef CONFIG_LOADS_ECHO
    "loads_echo=" __stringify(CONFIG_LOADS_ECHO) "\0"
    #endif
    #ifdef CONFIG_ETHPRIME
    "ethprime=" CONFIG_ETHPRIME "\0"
    #endif
    #ifdef CONFIG_IPADDR
    "ipaddr=" __stringify(CONFIG_IPADDR) "\0"
    #endif
    #ifdef CONFIG_SERVERIP
    "serverip=" __stringify(CONFIG_SERVERIP) "\0"
    #endif
    #ifdef CONFIG_SYS_AUTOLOAD
    "autoload=" CONFIG_SYS_AUTOLOAD "\0"
    #endif
    #ifdef CONFIG_PREBOOT
    "preboot=" CONFIG_PREBOOT "\0"
    #endif
    #ifdef CONFIG_ROOTPATH
    "rootpath=" CONFIG_ROOTPATH "\0"
    #endif
    #ifdef CONFIG_GATEWAYIP
    "gatewayip=" __stringify(CONFIG_GATEWAYIP) "\0"
    #endif
    #ifdef CONFIG_NETMASK
    "netmask=" __stringify(CONFIG_NETMASK) "\0"
    #endif
    #ifdef CONFIG_HOSTNAME
    "hostname=" __stringify(CONFIG_HOSTNAME) "\0"
    #endif
    #ifdef CONFIG_BOOTFILE
    "bootfile=" CONFIG_BOOTFILE "\0"
    #endif
    #ifdef CONFIG_LOADADDR
    "loadaddr=" __stringify(CONFIG_LOADADDR) "\0"
    #endif
    #ifdef CONFIG_CLOCKS_IN_MHZ
    "clocks_in_mhz=1\0"
    #endif
    #if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0)
    "pcidelay=" __stringify(CONFIG_PCI_BOOTDELAY)"\0"
    #endif
    #ifdef CONFIG_ENV_VARS_UBOOT_CONFIG
    "arch=" CONFIG_SYS_ARCH "\0"
    "cpu=" CONFIG_SYS_CPU "\0"
    "board=" CONFIG_SYS_BOARD "\0"
    "board_name=" CONFIG_SYS_BOARD "\0"
    #ifdef CONFIG_SYS_VENDOR
    "vendor=" CONFIG_SYS_VENDOR "\0"
    #endif
    #ifdef CONFIG_SYS_SOC
    "soc=" CONFIG_SYS_SOC "\0"
    #endif
    #endif
    #ifdef CONFIG_EXTRA_ENV_SETTINGS
    CONFIG_EXTRA_ENV_SETTINGS
    #endif
    "\0"
    #ifdef DEFAULT_ENV_INSTANCE_EMBEDDED
    }
    #endif
    };
  • 第 13~23 行 , 这 段 代 码 是 个 条 件 编 译 , 由 于 没 有 定 义DEFAULT_ENV_INSTANCE_EMBEDDEDCONFIG_SYS_REDUNDAND_ENVIRONMENT,因此 uchar default_environment[]数组保存环境变量。 在示例代码中指定了很多环境变量的默认值,比如 bootcmd 的默认值就是 CONFIG_BOOTCOMMANDbootargs 的默认值就是 CONFIG_BOOTARGS。我们可以在mx6ull_alientek_emmc.h 文件中通过设置宏 CONFIG_BOOTCOMMAND 来设置 bootcmd 的默认值,NXP 官方设置的 CONFIG_BOOTCOMMAND 值如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #define CONFIG_BOOTCOMMAND \
    "run findfdt;" \
    "mmc dev ${mmcdev};" \
    "mmc dev ${mmcdev}; if mmc rescan; then " \
    "if run loadbootscript; then " \
    "run bootscript; " \
    "else " \
    "if run loadimage; then " \
    "run mmcboot; " \
    "else run netboot; " \
    "fi; " \
    "fi; " \
    "else run netboot; fi"
  • 看起来很复杂的样子!因为 uboot 使用了类似 shell 脚本语言的方式来编写的,我们一行一行来分析。 第 2 行,run findfdt;使用的是 uboot 的 run 命令来运行 findfdtfindfdt 是 NXP 自行添加的环境变量,是用来查找开发板对应的设备树文件(.dtb)。IMX6ULL EVK 的设备树文件为 imx6ull-14x14-evk.dtb,findfdt 内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    "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 test $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;\0" \
  • findfdt 里面用到的变量有fdt_fileboard_nameboard_rev,这三个变量内容如下:

    1
    fdt_file=undefined,board_name=EVK,board_rev=14X14
  • findfdt 做的事情就是判断,fdt_file 是否为 undefined,如果 fdt_file 为 undefined 的话那要根据板子信息得出所需的.dtb 文件名。此时 fdt_file 为 undefined,所以根据 board_nameboard_rev 来判断实际所需的.dtb 文件,如果 board_name 为 EVK 并且board_rev=9x9 的话 fdt_file 就为 imx6ull-9x9-evk.dtb。如果 board_name 为 EVK 并且 board_rev=14x14 的话 fdt_file 设置为 imx6ull-14x14-evk.dtb。因此 IMX6ULL EVK 板子的设备树文件就是 imx6ull-14x14-evk.dtb,因此run findfdt 的结果就是设置 fdt_file 为 imx6ull-14x14-evk.dtb。 第 3 行,mmc dev ${mmcdev}用于切换 mmc 设备,mmcdev 为 1,因此这行代码就是:mmc dev 1,也就是切换到 EMMC 上。

  • 第 4 行,先执行 mmc dev ${mmcdev} 切换到 EMMC 上,然后使用命令 mmc rescan 扫描看有没有 SD 卡或者 EMMC 存在,如果没有的话就直接跳到 13 行,执行 run netboot,netboot 也是一个自定义的环境变量,这个变量是从网络启动 Linux 的。如果 mmc 设备存在的话就从 mmc 设备启动。 第 5 行,运行loadbootscript 环境变量,此环境变量内容如下:

  • 第 5 行,运行 loadbootscript 环境变量,此环境变量内容如下:

    1
    loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};
  • 其中 mmcdev=1,mmcpart=1,loadaddr=0x80800000,script= boot.scr,因此展开以后就是:

    1
    loadbootscript=fatload mmc 1:1 0x80800000 boot.scr;
  • loadbootscript 就是从 mmc1 的分区 1 中读取文件 boot.src 到 DRAM 的 0X80800000 处。但是 mmc1 的分区 1 中没有 boot.src 这个文件,可以使用命令“ls mmc 1:1”查看一下 mmc1 分区 1 中的所有文件,看看有没有 boot.src 这个文件。 第 6 行,如果加载 boot.src 文件成功的话就运行 bootscript 环境变量,bootscript 的内容如下:

    1
    2
    bootscript=echo Running bootscript from mmc ...;
    source
  • 因为 boot.src 文件不存在,所以 bootscript 也就不会运行。 第 8 行,如果 loadbootscript 没有找到 boot.src 的话就运行环境变量 loadimage,环境变量 loadimage 内容如下:

    1
    loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}
  • 其中 mmcdev=1,mmcpart=1,loadaddr=0x80800000,image = zImage,展开以后就是:

    1
    loadimage=fatload mmc 1:1 0x80800000 zImage
  • 可以看出 loadimage 就是从 mmc1 的分区中读取 zImage 到内存的 0X80800000 处,而 mmc1 的分区 1 中存在 zImage。 第 9 行,加载 linux 镜像文件 zImage 成功以后就运行环境变量 mmcboot,否则的话运行 netboot 环境变量。mmcboot 环境变量如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    "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;\0" \
  • 第 1 行,输出信息“Booting from mmc …”。 第 2 行,运行环境变量 mmcargsmmcargs 用来设置 bootargs,后面分析 bootargs 的时候在学习。 第 3 行,判断 boot_fdt 是否为 yes 或者 try,根据 uboot 输出的环境变量信息可知 boot_fdt=try。因此会执行 4 行的语句。 第 4 行,运行环境变量 loadfdt,环境变量 loadfdt 定义如下:

    1
    loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}
  • 展开以后就是:

    1
    loadfdt=fatload mmc 1:1 0x83000000 imx6ull-14x14-evk.dtb
  • 因此 loadfdt 的作用就是从 mmc1 的分区 1 中读取 imx6ull-14x14-evk.dtb 文件并放到 0x83000000 处。 第 8 行,如果读取.dtb 文件成功的话那就调用命令 bootz 启动 linux,调用方法如下:

    1
    bootz ${loadaddr} - ${fdt_addr};
  • 展开就是:

    1
    bootz 0x80800000 - 0x83000000 (注意‘-’前后要有空格)
  • 至此 Linux 内核启动,如此复杂的设置就是为了从 EMMC 中读取 zImage 镜像文件和设备树文件。经过分析,浓缩出来的仅仅是 4 行精华:

    1
    2
    3
    4
    mmc dev 1 //切换到 EMMC
    fatload mmc 1:1 0x80800000 zImage //读取 zImage 到 0x80800000 处
    fatload mmc 1:1 0x83000000 imx6ull-14x14-evk.dtb //读取设备树到 0x83000000 处
    bootz 0x80800000 - 0x83000000 //启动 Linux
  • NXP 官方将 CONFIG_BOOTCOMMAND 写的这么复杂只有一个目的:为了兼容多个板子,所以写了个很复杂的脚本。当我们明确知道我们所使用的板子的时候就可以大幅简化宏 CONFIG_BOOTCOMMAND 的 设 置 , 比 如 我 们 要 从 EMMC 启 动 , 那 么 宏 CONFIG_BOOTCOMMAND 就可简化为:

    1
    2
    3
    4
    5
    #define CONFIG_BOOTCOMMAND \
    "mmc dev 1;" \
    "fatload mmc 1:1 0x80800000 zImage;" \
    "fatload mmc 1:1 0x83000000 imx6ull-alientek-emmc.dtb;" \
    "bootz 0x80800000 - 0x83000000;"
  • 或者可以直接在 uboot 中设置 bootcmd 的值,这个值就是保存到 EMMC 中的,命令如下:

    1
    setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;'

3.2 环境变量 bootargs

  • bootargs 保存着 uboot 传递给 Linux 内核的参数,在上一小节讲解 bootcmd 的时候说过,bootargs 环境变量是由 mmcargs 设置的,mmcargs 环境变量如下:

    1
    mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}
  • 其中 console=ttymxc0,baudrate=115200,mmcroot=/dev/mmcblk1p2 rootwait rw,因此将 mmcargs 展开以后就是:

    1
    mmcargs=setenv bootargs console= ttymxc0, 115200 root= /dev/mmcblk1p2 rootwait rw
  • 可以看出环境变量 mmcargs 就是设置 bootargs 的值为console= ttymxc0, 115200 root= /dev/mmcblk1p2 rootwait rw,bootargs 就是设置了很多的参数的值,这些参数 Linux 内核会使用到,常用的参数有:

  1. console
    • console 用来设置 linux 终端(或者叫控制台),也就是通过什么设备来和 Linux 进行交互,是串口还是 LCD 屏幕?如果是串口的话应该是串口几等等。一般设置串口作为 Linux 终端,这样我们就可以在电脑上通过 SecureCRT 来和 linux 交互了。这里设置 console 为 ttymxc0,因为 linux 启动以后 I.MX6ULL 的串口 1 在 linux 下的设备文件就是/dev/ttymxc0,在 Linux 下,一切文件。 ttymxc0 后面有个“,115200”,这是设置串口的波特率,console=ttymxc0,115200 综合起来就是设置 ttymxc0(也就是串口 1)作为 Linux 的终端,并且串口波特率设置为 115200。
  2. root
    • root 用来设置根文件系统的位置root=/dev/mmcblk1p2 用于指明根文件系统存放在 mmcblk1 设备的分区 2 中。EMMC 版本的核心板启动 linux 以后会存在/dev/mmcblk0、/dev/mmcblk1、/dev/mmcblk0p1、/dev/mmcblk0p2、/dev/mmcblk1p1 和/dev/mmcblk1p2 这样的文件,其中/dev/mmcblkx(x=0~n)表示 mmc 设备,而/dev/mmcblkxpy(x=0~n,y=1~n)表示 mmc 设备 x 的分区 y。在 I.MX6U-ALPHA 开发板中/dev/mmcblk1 表示 EMMC,而/dev/mmcblk1p2 表示 EMMC 的分区 2。
    • root 后面有rootwait rwrootwait 表示等待 mmc 设备初始化完成以后再挂载,否则的话 mmc 设备还没初始化完成就挂载根文件系统会出错的。rw 表示根文件系统是可以读写的,不加 rw 的话可能无法在根文件系统中进行写操作,只能进行读操作。
  3. rootfstype
    • 此选项一般配置 root 一起使用,rootfstype 用于指定根文件系统类型,如果根文件系统为ext 格式的话此选项无所谓。如果根文件系统是 yaffs、jffs 或 ubifs 的话就需要设置此选项,指定根文件系统的类型。
    • bootargs 常设置的选项就这三个,后面遇到其他选项的话再讲解。

4.uboot 启动 Linux 测试

  • uboot 已经移植好了,bootcmdbootargs 这两个重要的环境变量也讲解了,接下来就要测试一下 uboot 能不能完成它的工作:启动 Linux 内核。我们测试两种启动 Linux 内核的方法,一种是直接从 EMMC 启动,一种是从网络启动

4.1 从 EMMC 启动 Linux 系统

  • 从 EMMC 启动也就是将编译出来的 Linux 镜像文件 zImage 和设备树文件保存在 EMMC 中,uboot 从 EMMC 中读取这两个文件并启动,这个是我们产品最终的启动方式。但是我们目前还没有讲解如何移植 linux 和设备树文件,以及如何将 zImage 和设备树文件保存到 EMMC 中。不过大家拿到手的 I.MX6U-ALPHA 开发板(EMMC 版本)已经将 zImage 文件和设备树文件烧写到了 EMMC 中,所以我们可以直接读取来测试。先检查一下 EMMC 的分区 1 中有没有 zImage 文件和设备树文件,输入命令ls mmc 1:1,结果如图所示:

  • 从图中可以看出,此时 EMMC 分区 1 中存在 zimageimx6ull-alientek-emmc.dtb 这两个文件,所以我们可以测试新移植的 uboot 能不能启动 linux 内核。设置 bootargsbootcmd 这两个环境变量,设置如下:

    1
    2
    3
    setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
    setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;'
    saveenv
  • 设置好以后直接输入 boot,或者 run bootcmd 即可启动 Linux 内核,如果 Linux 内核启动成功的话就会输出如图所示的启动信息:

4.2 从网络启动 Linux 系统

  • 从网络启动 linux 系统的唯一目的就是为了调试!不管是为了调试 linux 系统还是 linux 下的驱动。每次修改 linux 系统文件或者 linux 下的某个驱动以后都要将其烧写到 EMMC 中去测试,这样太麻烦了。我们可以设置 linux 从网络启动,也就是将 linux 镜像文件和根文件系统都放到 Ubuntu 下某个指定的文件夹中,这样每次重新编译 linux 内核或者某个 linux 驱动以后只需要使用 cp 命令将其拷贝到这个指定的文件夹中即可,这样就不用需要频繁的烧写 EMMC,这样就加快了开发速度。我们可以通过 nfs 或者 tftp 从 Ubuntu 中下载 zImage 和设备树文件,根文件系统的话也可以通过 nfs 挂载,不过本节我们不讲解如何通过 nfs 挂载根文件系统,这个在讲解根文件系统移植的时候再讲解。这里我们使用 tftp 从 Ubuntu 中下载 zImage 和设备树文件,前提是要将 zImage 和设备树文件放到 Ubuntu 下的 tftp 目录中。设置 bootargsbootcmd 这两个环境变量,设置如下:

    1
    2
    3
    setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
    setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'
    saveenv
  • 一开始是通过 tftp 下载 zImageimx6ull-alientek-emmc.dtb 这两个文件,过程如下图所示:

  • 下载完成以后就是启动 Linux 内核,启动过程如图 33.4.2.2 所示:

    5.总结

    • uboot 移植到此结束,简单总结一下 uboot 移植的过程:

      1. 不管是购买的开发板还是自己做的开发板,基本都是参考半导体厂商的 demo 板,而半导体厂商会在他们自己的开发板上移植好 ubootlinux kernelrootfs 等,最终制作好 BSP 包提供给用户。我们可以在官方提供的 BSP 包的基础上添加我们的板子,也就是俗称的移植。

      2. 我们购买的开发板或者自己做的板子一般都不会原封不动的照抄半导体厂商的 demo 板,都会根据实际的情况来做修改,既然有修改就必然涉及到 uboot 下驱动的移植。

      3. 一般 uboot 中需要解决串口、NAND、EMMC 或 SD 卡、网络和 LCD 驱动,因为 uboot 的主要目的就是启动 Linux 内核,所以不需要考虑太多的外设驱动。

      4. 在 uboot 中添加自己的板子信息,根据自己板子的实际情况来修改 uboot 中的驱动。