天天看點

i.MX6ULL嵌入式Linux開發1——uboot移植初探

本系列教程以「i.MX6ULL」處理器的ARM開發闆為實驗基礎,學習記錄嵌入式Linux開發的各種知識與經驗,主要内容包括嵌入式Linux移植,嵌入式Linux驅動開發,嵌入式Linux應用開發等。

本系列教程将以野火的i.MX6ULL eMMC開發闆為硬體基礎,以「野火EBF6ULL Pro開發闆教程」和「正點原子i.MX6ULL阿爾法開發闆教程」為參考,進行學習實踐。

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開發闆

i.MX6ULL嵌入式Linux開發1——uboot移植初探

3 U-Boot簡介

uboot 的全稱是「Universal Boot Loader」,遵循 GPL 協定的開源軟體。

uboot 是一個裸機代碼,可以看作是一個裸機綜合例程。現在的 uboot 已經支援「液晶屏、網絡、USB」等進階功能。uboot 官網為 https://www.denx.de/wiki/U-Boot/

i.MX6ULL嵌入式Linux開發1——uboot移植初探

可以在uboot官網下載下傳uboot源碼,點選左側 Topics 中的“​

​Source Code​

​”,然後點選的“​

​FTP Server​

​” ,進入其 FTP 伺服器即可看到 uboot 源碼。

但我們移植uboot時一般不會直接用 uboot 官方的源碼的,官方的源碼是給半導體廠商準備的,半導體廠商會根據自家的晶片,維護自己晶片對應的uboot。

NXP(freescale)維護的的uboot位址: https://github.com/Freescale/u-boot-fslc

i.MX6ULL嵌入式Linux開發1——uboot移植初探

4 NXP uboot測試

uboot移植并不需要從零開始将 uboot 移植到我們現在所使用的開發闆上。因為半導體廠商通常都會自己做一個開發闆「原廠開發闆」,将uboot移植到他們自己的原廠開發闆上,再将這個uboot(原廠BSP 包)釋出出去。

市面上的開發闆,通常會參考原廠的開發闆做硬體,然後在原廠提供的 BSP 包上做修改,如正點原子和野火的 I.MX6ULL 開發闆參考的就是

「NXP官方的I.MX6ULL EVK開發闆」做的硬體:

i.MX6ULL嵌入式Linux開發1——uboot移植初探

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/

i.MX6ULL嵌入式Linux開發1——uboot移植初探

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       
i.MX6ULL嵌入式Linux開發1——uboot移植初探

使用交叉編譯器之前還需要「安裝其它的庫」,指令如下:

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

安裝完之後,可以「檢視交叉編譯工具的版本号」,輸入如下指令:

arm-linux-gnueabihf-gcc -v       

可以看到類似如下列印

i.MX6ULL嵌入式Linux開發1——uboot移植初探

以看出目前交叉編譯器的版本号為 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      

解壓後的源碼檔案如下:

i.MX6ULL嵌入式Linux開發1——uboot移植初探

首先看下「uboot的配置」,configs 目錄下有很多跟 I.MX6UL/6ULL 有關的配置,找到與mx6ull相同的,如下圖。

因為我這個開發闆是emmc版本的,所有就使用這個​

​mx6ull_14x14_evk_emmc_defconfig​

​。

i.MX6ULL嵌入式Linux開發1——uboot移植初探

編譯uboot使用下面3條指令:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distcleanmake ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfigmake 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- distcleanmake ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfigmake V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8      

然後進行編譯:

./build.sh      
i.MX6ULL嵌入式Linux開發1——uboot移植初探

編譯完成以後uboot 源碼多了一些檔案,其中​

​u-boot.bin​

​就是編譯出來的 uboot二進制檔案。 uboot是個裸機程式, 是以需要在其前面

加上頭部(IVT、 DCD等資料)才能在I.MX6U上執行,​

​u-boot.imx ​

​檔案就是添加頭部以後的 u-boot.bin。

u-boot.imx 就是我們最終要燒寫到開發闆中的 uboot 鏡像檔案。

i.MX6ULL嵌入式Linux開發1——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虛拟機需要先安裝「增強功能」才能使用)

i.MX6ULL嵌入式Linux開發1——uboot移植初探

然後可以使用如下指令來檢視SD卡的挂載辨別符:

ls /dev/sd*       

檢視輸出結果:

i.MX6ULL嵌入式Linux開發1——uboot移植初探

這裡的​

​/dev/sdb​

​就是我的SD卡。

注:我第一次使用SD卡燒錄時,隻多出了/dev/sdb,但不知什麼情況,用了幾次後,再插入SD卡,就會同時多出來/dev/sdb和/dev/sdb1,但實際測 試,仍然把程式燒錄到/dev/sdb也能用)。

imxdownload向SD卡燒寫led.bin檔案,指令格式如下:

./imxdownload u-boot.bin /dev/sdb       
注意不能燒寫到​

​/dev/sda​

​或​

​sda1​

​裝置裡面!那是系統磁盤。

燒寫過程會輸入如下資訊:

i.MX6ULL嵌入式Linux開發1——uboot移植初探

燒寫的最後一行會顯示燒寫大小、用時和速度,比如​

​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​

​倒計時的時候「按下鍵盤上的Enter鍵」

,預設是 3 秒倒計時,在 3 秒倒計時結束以後如果沒有按下Enter鍵的話 uboot 就會使用預設參數來啟動 Linux 核心了。

如果在 3 秒倒計時結束之前按下Enter鍵,那麼就會進入 uboot 的指令行模式:

i.MX6ULL嵌入式Linux開發1——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 秒,倒計時結束之前按下Enter鍵就會進入 Linux 指令行模式。如果在倒計時結束以後沒有按下Enter鍵,那麼 Linux 核心就會啟動,Linux 核心一旦啟動,uboot 就運作結束了。
  • 第23行是在倒計時 3 秒内按了Enter鍵,符号=>

    表示可以繼續與uboot進行「指令互動」。

看過了序列槽的uboot資訊,再來看一下闆子是實際運作情況:

i.MX6ULL嵌入式Linux開發1——uboot移植初探

由于原廠的uboot驅動的螢幕是TFT43AB (480x272),與我這裡螢幕不一樣,是以「螢幕沒有正常顯示」(現在的螢幕看起來有許多彩色的小點點),接下來,就是對uboot進行螢幕驅動的修改。

在本篇結束之前,再來研究一下uboot的序列槽指令。

4.4 uboot指令初探

上面說道,在uboot啟動的3 秒倒計時内,序列槽界面如果按下了Enter鍵,uboot就會輸出符号​

​=>​

​,則「可以繼續與uboot進行指令互動」。那可以輸入哪些指令呢?

4.4.1 help指令檢視所有指令

輸入​

​help​

​?​

​,然後按下回車即可檢視目前 uboot 所支援的指令:

=> help?       - alias for 'help'base    - print or set address offsetbdinfo  - print Board Info structurebmode   - sd1|sd2|qspi1|normal|usb|sata|ecspi1:0|ecspi1:1|ecspi1:2|ecspi1:3|esdhc1|esdhc2|esdhc3|esdhc4 [noreset]bmp     - manipulate BMP image databoot    - boot default, i.e., run 'bootcmd'bootd   - boot default, i.e., run 'bootcmd'bootelf - Boot from an ELF image in memorybootm   - boot application image from memorybootp   - boot image via network using BOOTP/TFTP protocolbootvx  - Boot vxWorks from an ELF imagebootz   - boot Linux zImage image from memoryclocks  - display clocksclrlogo - fill the boot logo area with blackcmp     - memory compareconinfo - print console devices and informationcp      - memory copycrc32   - checksum calculationdcache  - enable or disable data cachedhcp    - boot image via network using DHCP/TFTP protocoldm      - Driver model low level accessecho    - echo args to consoleeditenv - edit environment variableenv     - environment handling commandserase   - erase FLASH memoryexit    - exit scriptext2load- load binary file from a Ext2 filesystemext2ls  - list files in a directory (default /)ext4load- load binary file from a Ext4 filesystemext4ls  - 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 sizefdt     - flattened device tree utility commandsflinfo  - print FLASH memory informationfstype  - Look up a filesystem typefuse    - Fuse sub-systemgo      - start application at address 'addr'gpio    - query and control gpio pinshelp    - print command description/usagei2c     - I2C sub-systemicache  - enable or disable instruction cacheiminfo  - print header information for application imageimxtract- extract a part of a multi-imageitest   - return true/false on integer compareload    - load binary file from a filesystemloadb   - load binary file over serial line (kermit mode)loads   - load S-Record file over serial lineloadx   - load binary file over serial line (xmodem mode)loady   - load binary file over serial line (ymodem mode)loop    - infinite loop on address rangels      - list files in a directory (default /)md      - memory displaymdio    - MDIO utility commandsmii     - MII utility commandsmm      - memory modify (auto-incrementing address)mmc     - MMC sub systemmmcinfo - display MMC infomtest   - simple RAM read/write testmw      - memory write (fill)nfs     - boot image via network using NFS protocolnm      - memory modify (constant address)ping    - send ICMP ECHO_REQUEST to network hostpmic    - PMICprintenv- print environment variablesprotect - enable or disable FLASH write protectionreset   - Perform RESET of the CPUrun     - run commands in an environment variablesave    - save file to a filesystemsaveenv - save environment variables to persistent storagesetenv  - set environment variablessetexpr - set environment variable as the result of eval expressionsf      - SPI flash sub-systemshowvar - print local hushshell variablessize    - 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 bootzbootz - 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個:​

​bdinfo​

​、​

​printenv​

​和​

​version​

  • bdinfo 闆子資訊
=> bdinfoarch_number = 0x00000000boot_params = 0x80000100DRAM bank   = 0x00000000-> start    = 0x80000000-> size     = 0x20000000eth0name    = FEC1ethaddr     = (not set)current eth = FEC1ip_addr     = <NULL>baudrate    = 115200 bpsTLB addr    = 0x9FFF0000relocaddr   = 0x9FF47000reloc off   = 0x18747000irq_sp      = 0x9EF44EA0sp start    = 0x9EF44E90FB base     = 0x00000000=>      

從列印資訊可以得出DRAM的「起始位址和大小、啟動參數儲存起始位址、波特率、sp(堆棧指針)起始位址」等資訊.

  • printenv 列印環境變量
=> printenvbaudrate=115200board_name=EVKboard_rev=14X14boot_fdt=trybootcmd=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; fibootcmd_mfg=run mfgtool_args;bootz ${loadaddr} ${initrd_addr} ${fdt_addr};bootdelay=3bootscript=echo Running bootscript from mmc ...; sourceconsole=ttymxc0ethact=FEC1ethprime=FECfdt_addr=0x83000000fdt_file=undefinedfdt_high=0xfffffffffindfdt=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=zImageinitrd_addr=0x83800000initrd_high=0xffffffffip_dyn=yesloadaddr=0x80800000loadbootscript=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_unusedmmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}mmcautodetect=yesmmcboot=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=0mmcpart=1mmcroot=/dev/mmcblk0p2 rootwait rwnetargs=setenv bootargs console=${console},${baudrate} root=/dev/nfs ip=dhcp nfsroot=${serverip}:${nfsroot},v3,tcpnetboot=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=TFT43ABscript=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.4GNU ld (Linaro_Binutils-2017.01) 2.24.0.20141017 Linaro 2014_11-3-git=>      

繼續閱讀