IMX6ULL芯片内部有一个boot ROM,上电后boot ROM上的程序就会运行。它会根据BOOT_MODE[1:0]的值,以及eFUSE或GPIO的值决定后续的启动流程。
注:eFUSE即熔丝,只能烧写一次,一般正式发布产品时烧写最终值;平时调试时通过GPIO来设置开发板的启动方式。
boot ROM上的程序功能强大,可以从USB口或串口下载程序并把它烧写到Flash等设备上,也可以从SD卡或EMMC、Flash等设备上读出程序、运行程序。
问题来了:
① boot ROM是从USB口下载、运行程序,还是从SD卡等设备上读出、运行程序,谁决定?
BOOT_MODE[1:0]的值来自于2个引脚BOOT_MODE1、BOOT_MODE0。这2个引脚在上电时是输入引脚,芯片启动后采集这2个引脚的值,存入BOOT_MODE寄存器。以后这2个引脚就可以用于其他功能,不会影响到BOOT_MODE寄存器。
BOOT_MODE[1:0]的值确定了4种启动模式,如下图:
00模式在我们的开发过程中很少用到,简单介绍一下:在这种模式下,GPIO的值被忽略。Boot ROM会根据eFUSE的值来选择启动设备、设置启动设备。但是,对于刚出厂的芯片eFUSE值可能是错乱的、不适合你的设备的,怎么办?eFUSE中有一个值BT_FUSE_SEL,它的出厂值是0,表示eFUSE未被烧写。boot ROM程序发现BT_FUSE_SEL为0时,它会通过USB或串口来下载程序;发现BT_FUSE_SEL为1时,才会根据eFUSE的值选择启动设备,读出、运行该设备上的程序。
01模式,boot ROM程序通过USB或串口下载、运行程序,这个模式可以用来烧写EMMC等设备。我们的开发板出厂时,就是通过这个模式下载、烧写出厂程序的。
10模式,称之为内部模式,简单地说就是从SD卡、EMMC等设备启动程序。这就引入下面第2个问题。
② 如何选择启动设备?
00模式下是通过eFUSE的值选择启动设备,我们不关心。
10模式下既可以通过eFUSE的值也可以通过GPIO的值来选择启动设备,但是到底通过谁来决定?eFUSE中有一个值BT_FUSE_SEL,对,又是它。它的初始值为0,表示eFUSE未被烧写。在10模式下,当BT_FUSE_SEL为0时就会通过GPIO来选择启动设备;当BT_FUSE_SEL为1时就会通过eFUSE来选择启动设备。
在开发阶段,我们使用GPIO来选择设备,这就引入下面第3个问题。
③ 如何通过eFUSE或GPIO选择、设置启动设备?
通过eFUSE或GPIO不仅能选择启动设备,还可以设置启动设备。
为什么还需要设置?比如Nand Flash参数各有不同,有些的页大小是2048,有些是4096。这些参数不同,boot ROM程序读Nand Flash的方法就不同,我们必须把这些参数告诉boot ROM:通过eFUSE或GPIO来标明这些参数。
首先看看要设置哪些eFUSE或GPIO来选择不同的启动设备。
从上图可知,既可以使用eFUSE也可以使用GPIO来选择启动设备,换句话说GPIO可以覆盖eFUSE的值。哪些GPIO覆盖哪些eFUSE?这可以查看IMX6ULL芯片手册《Chapter 8: System Boot》里的《GPIO boot overrides》,我们把它摘出来放在3.1.3小节里。
选择启动设备后,还需要标明一些参数。
比如选择EMMC启动时,EMMC接在哪一个接口,eSDHC1还是eSDHC2?它的速度如何?
比如选择TF卡启动时,TF卡接在哪一个接口,eSDHC1还是eSDHC2?它的速度如何?
假设使用EMMC启动,或是TF卡启动,怎么设置eFUSE或GPIO?这些信息可以查询IMX6ULL芯片手册《Chapter 5: Fusemap》,摘录如下。
当BOOT_MODE设置为0b00时,通过eFUSE选择启动设备,通过eFUSE获得设备的参数。
当BOOT_MODE设置为0b10时,通过eFUSE或GPIO来选择启动设备,获得设备的参数;使用eFUSE还是GPIO由eFUSE中的BT_FUSE_SEL决定,它默认是0,表示使用GPIO。
以BOOT_MODE为0b10为例,解析一下上图。
要设置为SD卡、TF卡启动,有2个设置方法:
a. 设置eFUSE的BOOT_CFG1[7:5]为0b010,或
b. 查看《3.1.3 GPIO boot overrides》确定BOOT_CFG1[7:5]对应的GPIO为LCD1_DATA07~05,把这3个引脚设置为0b010。
根据SD卡、TF卡的性能,可以设置eFUSE或GPIO来表示它能否提供更高的速度:
a. 设置eFUSE的BOOT_CFG1[4:0],或
b. 查看《3.1.3 GPIO boot overrides》确定BOOT_CFG1[4:0]对应的GPIO为LCD1_DATA04~00,设置这些引脚。
IMX6ULL有两个SD卡、TF卡接口,使用哪一个接口?请看下表:
a. 设置eFUSE的BOOT_CFG2[4:3]可以确定使用eSDHC1或eSDHC2,或
b. 查看《3.1.3 GPIO boot overrides》确定BOOT_CFG2[4:3]对应的GPIO为LCD1_DATA12~11,设置这些引脚
通过eFUSE或GPIO,还可以标明启动设备的更多参数,具体细节可以参考芯片手册《Chapter 5: Fusemap》,作为硬件开发人员需要去细细研究;作为软件开发人员,实际上只需要看开发板手册知道怎么设置启动开关即可。
100ASK_IMX6ULL开发板上的红色拨码开关用来设置启动方式、选择启动设备,支持这3种方式:EMMC启动、SD卡启动、USB烧写。
板子背后画有一个表格,表示这3种方式如何设置。
表格如下:
BOOT CFG | ||||
BOOT | SW1(LCD_DATA5) | SW2(LCD_DATA11) | SW3(BOOT_MODE0) | SW4(BOOT_MODE1) |
EMMC | OFF | OFF | ON | OFF |
SD | ON | ON | ON | OFF |
USB | X | X | OFF | ON |
IMX6ULL上有2个EMMC Flash接口,也复用为2个SD/TF卡接口,通过LCD1_DATA12~11来选择接口。0b00对应eSDHC1接口,0b01对应eSDHC2接口。LCD1_DATA12的值在核心板上已经通过电阻设置好。LCD1_DATA11的值通过拨码开关SW2来设置:ON表示0,对应eSDHC1接口,100ASK_IMX6ULL的TF卡接口使用了eSDHC1接口;OFF表示1,对应eSDHC2接口,100ASK_IMX6ULL的EMMC接口使用了eSDHC2接口。
这3种启动方式的设置示意图如下:
要注意的是,设置为USB启动时,不能插上SD卡、TF卡。
刚出厂的板子在EMMC上烧写了系统,你可以设置为EMMC启动方式。
IMX6ULL中既可以通过eFUSE也可以通过GPIO来选择、设置启动设备,在手册里大部分场合只列出了eFUSE,对应的GPIO需要查表:IMX6ULL芯片手册《Chapter 8: System Boot》里的《GPIO boot overrides》。
我们把它摘录出来。
这个启动流程可以猜测出来,假设板子设置为SD/TF卡启动,boot ROM程序会做什么?把程序从SD/TF卡读出来,运行。
从哪里读?从SD/TF卡读,这需要先初始化SD/TF卡:根据eFUSE或GPIO的设置初始化SD/TF卡。
读到哪里去?读到内存即DDR去,这需要先初始化DDR。
除了初始化启动设备、初始化DDR,还需要初始化什么?也许要初始化时钟,让CPU跑得更快一点。
总结起来就是:初始化CPU、时钟等,初始化内存,初始化启动设备,从启动设备上把程序读入内存,运行内存的程序。
官方的启动流程如下,这个流程图比较粗糙,总结起来就是:
a. 检查CPU ID
b. 检查Reset Type,冷启动、唤醒的启动过程是不一样的
c. 检查启动模式BOOT_MODE,检查eFUSE或GPIO
d. 根据上述检查从USB口、UART口或是某个启动设备下载boot image
e. 认证image
f. 启动
对于具体的启动设备,IMX6ULL芯片手册《Chapter 8: System Boot》中有对应章节描述更为细致的启动流程。基本上就是对这些启动设备根据eFUSE或GPIO的设置进行初始化,尝试更高的工作频率等。
在往后的学习中,如果涉及这些细节,我们再描述。
假设使用SD/TF卡启动,卡上的程序有多大?它应该被复制到DDR哪里去?这些问题,请看《3.3 IMX6ULL映像文件制作与使用》。
如果您有S3C2440或其他单片机的学习经验,可以知道程序的二进制版本,比如lcd.bin可以直接烧写到Flash上。它们是自启动的,什么意思?比如一上电,运行的是lcd.bin前面的代码,它会初始化内存,把自己从Flash上复制到内存里去执行。请记住:自己把自己复制到内存。
但是对于IMX6ULL,烧写在EMMC、SD/TF卡上的程序,并不能“自己复制自己”。一上电首先运行的是boot ROM上的程序,它从EMMC、SD/TF卡上把程序复制进内存里。
所以:boot ROM程序需要知道从启动设备哪个位置读程序,读多大的程序,复制到哪里去。
所以:启动设备上,不能仅仅烧写bin文件,需要在添加额外的信息。
还有一个问题,IMX6ULL的boot ROM程序可以把程序读到DDR里,那需要先初始化DDR。每种板子接的DDR可能不一样,boot ROM程序需要初始化这些不同的DDR。boot ROM从哪里得到这些不同的参数?
还有,IMX6ULL支持各种启动设备,比如各种Nor Flash。为了通用,boot ROM程序将会使用最保守的参数,也就是最慢的时序来访问Nor Flash。为加快启动程序,boot ROM程序可以根据我们提供的信息初始化硬件,让它以更优的参数运行。
这些参数信息,被称为“Device Configuration Data”,设备配置数据(DCD),这些DCD将会跟bin文件一起打包烧写在启动设备上。boot ROM程序会从启动设备上读出DCD数据,根据DCD来写对应的寄存器以便初始化芯片。DCD中列出的是对某些寄存器的读写操作,我们可以在DCD中设置DDR控制器的寄存器值,可以在DCD中使用更优的参数设置必需的硬件。这样boot ROM程序就会帮我们初始化DDR和其他硬件,然后才可以把bin程序读到DDR中并运行。
总结起来,烧写在EMMC、SD卡或是TF卡上的,除了程序本身,还有位置信息、DCD信息,这些内容合并成一个映像文件,如下图:
这4部分内容合并成为一个映像文件,烧写在EMMC、SD卡或TF卡等启动设备的某个固定地址,boot ROM程序去这个固定地址读出映像文件。启动设备不同,固定地址不同,如下图:
先贴出一张图,然后再细细讲解:
下面的讲解图中,列有C语言格式的结构体,这些结构体来源于U-boot的tools目录下的imximage.h。对于程序员,有时候看结构体可以更快地理解映像文件的格式。
(1). Image Vector Table(IVT):
IVT会被放在固定的地址,IVT中是一系列的地址,boot ROM程序会根据这些地址来确定映像文件中其他部分在哪里。
IVT格式如下:
要注意的是上图中这4项:
a. header:
里面有3项:tag、length、version。length表示IVT的大小,它是32字节。要注意是的,它是大字节序的。
b. entry:
用户程序运行时第1条指令的地址,就是程序的链接地址、程序被复制到内存哪里
c. dcd:
映像被复制到内存后,其中的DCD数据的地址。
d. boot data:
映像被复制到内存后,其中的boot data的地址。
e. self:
映像被复制到内存后,IVT自己所在的地址。
(2). Boot data:
映像被复制到内存后,IVT自己所在的地址。
a. start:
这是映像文件在内存中的地址,注意,它不等于IVT在内存中的地址。
什么意思?假设IVT被保存在启动设备TF卡1024偏移地址处,IVT被复制到内存地址0x87000000,那么start=0x87000000-1024。
所以start表示的是启动设备开头的数据,被复制到内存哪里去。
从它的含义也可以推理出:boot ROM程序会把启动设备开头的数据,复制到内存;而不仅仅是从IVT开始复制。
b. length:
保存在启动设备上的整个映像文件的长度,从0地址开始(不是从IVT开始)。
c. plugin:
这是一个标记位,当它为1时表示这个映像文件是“plugin”,即插件。
boot ROM程序可以支持有限的启动设备,如果你想双持更多的启动设备比如网络启动、CDROM启动,就需要提供对应的驱动。这些驱动就是“plugin”,我们的教程不涉及,该标记位为0。
Boot data就是用来表示映像文件应该被复制到哪里去,以前它的大小。boot ROM程序就是根据它来把整个映像文件复制到内存去的。
(3). DCD:
DCD的作用在前面讲解过,简单地说就是设备的配置信息,里面保存有一些寄存器值。
实际上DCD还可以更复杂,它支持多种命令:write data、check data、nop、unlock。我们可以通过write data命令写寄存器,通过check data命令等待寄存器就绪。
DCD格式如下:
DCD以Header开始,里面的TAG为0xD2表示它是DCD,里面还标明了DCD的大小、版本。
接下来就是各个“CMD”,你可以在一个“CMD”里操作多个寄存器,比如在一个“write data command”中,写多个寄存器。
以“write data command”为例简单介绍一下,它的格式为:
上图中,TAG为0xCC表示这是“write data command”;Length表示命令的大小;Parameter的作用稍后再说。
既然是写命令,那自然就有“地址、值”,上图中就是多个“Address、Value/Mask”。为何还有
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LkxPWvUB-1642062603303)(https://cdn.jsdelivr.net/gh/DongshanPI/HomeSite-Photos@main/IMX6ULL-BareMetal/IMX6ULL_Start_Up_image015.png#pic_center)]
Parameter中b[2:0]用来表示写操作的字节数,是以字节、半字(2 byte),还是字(4 byte)来操作。
而b[4]、b[3]决定了是写值(write value),清位(clear bitmask),还是设位(set bitmask)。
对于其他命令,共格式可以参考IMX6UL的芯片手册,这里就不再介绍了。
(4). User code and data:
就是用户程序或数据,原原本本地添加到映像文件里就可以。
https://github.com/NXPmicro/mfgtools
我们制作映像文件的目的什么?把我们自己的程序烧写到启动设备,让boot ROM程序启动它。
所以制作映像文件的起点是:我们编写的程序。制作过程中各填值的计算方法如下图所示。
上图中各步骤细说如下:
① 确定入口地址entry:
我们的程序运行时要放在内存中哪一个位置,这是我们决定的。它被称为入口地址、链接地址。
② 确定映像文件在内存中的地址start:
boot ROM程序启动时,会把“initial load region”读出来,“initial load region”里含有IVT、Boot data、DCD。boot ROM根据DCD初始化设备后,再把整个映像文件读到内存。
在启动设备上,“initial load region”之后紧跟着我们的程序,反过来说就是我们程序的前面,放着“initial load region”。假设“initial load region”的大小为load_size,那么在内存中“initial load region”的位置start = entry – load_size。
注意:“initial load region”位于启动设备0位置,它的头部并不是IVT,而是一些无用的数据(或是分区信息)。
③ 确定IVT在内存中的地址self:
我们知道IVT在启动设备上某个固定的位置:ivt_offset。那么在内存中它的位置可以如下计算:
self = start + ivt_offset = entry – load_size + ivt_offset
④ 确定Boot data在内存中的地址boot_data:
IVT的大小是32字节,IVT之后就是Boot data,而IVT中的boot_data值表示Boot data在内存中的位置,计算如下:
boot_data = self + 32 = entry – load_size + ivt_offset + 32
⑤ 确定DCD在内存中的地址dcd:
Boot data的大小是12字节,Boot data之后就是DCD,而IVT中的dcd值表示DCD在内存中的位置,计算如下:
dcd = boot_data + 12 = entry – load_size + ivt_offset + 44
⑥ 写入DCD的数据:
DCD是用初始化硬件的,特别是初始化DDR。而DDR的初始化非常的复杂、专业,我们一般是使用硬件厂家提供的代码。
在后面的程序中你可以看到,我们是使用类似下面的指令来制作映象文件:
./tools/mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d led.bin led.imx
上述命令中的imximage.cfg.cfgtmp就是厂家提供的,内部截取部分贴出来:
从上图也可以看到imximage.cfg.cfgtmp文件中基本是对寄存器的写操作。
mkimage程序来自u-boot,它会把imximage.cfg.cfgtmp中的内容转换为DCD数据。我们并不打算讲解DCD的内容,只需要了解它的大概作用:
a. 设置时钟:DDR也需要时钟,这很好理解
b. 设置引脚:DDR需要很多引脚
c. 设置DDR控制器:Multi-mode DDR controller (MMDC)
⑦ 写入用户程序
⑧ 经过上述7个步骤,整个映像文件就构造出来了,可以把它烧入启动设备。
我们提供的示例程序001_led中有一个文件:led.img,它就是映象文件,可以直接烧入TF卡。用软件Hex Editor Neo打开led.img,选择doble word方式显示,可以看到如下内容,你可以自行验证一下映像文件中各个值。
我们编译出来的映像文件有2类后缀:imx、img。imx文件开头就是IVT,可以把它烧写到TF卡1024偏移位置处;img文件开头是1024字节的0值数据,后面才是IVT等,它可以通过win32diskimger等工具直接烧写到TF卡0偏移位置处。
另外,我们还可以通过USB把imx文件直接下载到板子上,并运行。
注意:通过USB下载方式,可以烧写程序到EMMC、TF卡上,但是并非“直接烧写”。它的过程如下:
a. 通过USB下载u-boot到内存,
b. 通过USB下载用户程序到内存,
c. 通过USB发送命令运行u-boot,
d. 用u-boot烧写把内存中的用户程序烧写到EMMC、TF卡上
最新版本的IMX6ULL烧写工具名为uuu:Universal Update Utility,又名mfgtools 3.0。
源码地址为:
https://github.com/NXPmicro/mfgtools
编译好的可执行程序下载地址:
https://github.com/NXPmicro/mfgtools/releases
使用USB来下载、运行裸机程序,是最简单的方法,不需要烧写。
步骤如下:
① 开发板设置为USB启动,或称为USB下载模式:
对于100ASK_IMX6ULL开发板,启动开关设置为下图所示的样子:
② 使用USB线连接电脑和开发板的OTG口:
对于100ASK_IMX6ULL开发板,接线如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nTV6P4Up-1642062807801)(https://cdn.jsdelivr.net/gh/DongshanPI/HomeSite-Photos@main/IMX6ULL-BareMetal/IMX6ULL_Start_Up_image020.png)]
接好线后上电。
③ 使uuu命令下载、运行IMX文件:
我们提供的程序中在tools目录下预先放置了uuu.exe(windows用)、uuu(Linux用)。
以Windows为例,如下操作:
a. 进入程序目录,打开命令行:
b. 执行命令:
在命令行中执行:tools\uuu.exe led.imx ,如下图所示。如果一切正常,可以看到开发板上的绿灯开始闪烁。
如果不成功,请确认:
a. 开发板的启动开关是否设置为USB模式
b. 开发板不要插上TF卡
c. 开发板复位一下,再执行uuu命令
d. 要下载运行的是imx文件,不是bin文件,也不是img文件
这需要借助读卡器,在电脑上烧写TF卡。步骤如下:
a. 烧写TF卡:
把TF卡通过读卡器接到电脑上,使用win32diskimager把img文件烧写到SD卡上,如下图所示操作,烧写成功后会有提示:
b. 启动开发板:
把烧写好的TF卡插到开发板,开发板设置为SD/TF启动模式(如下),上电即可:
注意:使用win32diskimager烧写时,一定要选择img文件,不能选择imx文件。
USB只有下载、运行的功能,烧写要借助u-boot。所以我们要下载2个文件:u-boot-dtb.imx、我们的程序比如led.imx,然后运行u-boot,执行u-boot命令来烧写。
步骤如下:
① 开发板设置为USB启动,或称为USB下载模式:
对于100ASK_IMX6ULL开发板,启动开关设置为下图所示的样子:
② 使用USB线连接电脑和开发板的OTG口:
对于100ASK_IMX6ULL开发板,接线如下所示:
接好线后上电。
③ 使uuu命令下载、运行IMX文件:
我们提供的程序中在tools目录下预先放置了uuu.exe(windows用)、uuu(Linux用)。
以Windows为例,如下操作:
a. 进入程序目录,打开命令行:
b. 执行命令:
在命令行中执行:tools\uuu.exe -b emmc tools\u-boot-dtb.imx led.imx ,如下图所示。
命令解析:“-b emmc”表示要烧写emmc,需要借助于tools\u-boot-dtb.imx,要烧写的文件是led.imx。
如果一切正常,把开发板设置为EMMC启动后重新上电,可以看到绿灯闪烁。
如果烧写不成功,请确认:
a. 开发板的启动开关是否设置为USB模式
b. 开发板不要插上TF卡
c. 开发板复位一下,再执行uuu命令
d. 烧写成功后,开发板断电,设置为EMMC启动,再重新上电观察效果
windows用)、uuu(Linux用)。
以Windows为例,如下操作:
a. 进入程序目录,打开命令行:
[外链图片转存中…(img-8ROk4sov-1642061926253)]
b. 执行命令:
在命令行中执行:tools\uuu.exe -b emmc tools\u-boot-dtb.imx led.imx ,如下图所示。
命令解析:“-b emmc”表示要烧写emmc,需要借助于tools\u-boot-dtb.imx,要烧写的文件是led.imx。
如果一切正常,把开发板设置为EMMC启动后重新上电,可以看到绿灯闪烁。
[外链图片转存中…(img-Yd5pOChR-1642061926253)]
如果烧写不成功,请确认:
a. 开发板的启动开关是否设置为USB模式
b. 开发板不要插上TF卡
c. 开发板复位一下,再执行uuu命令
d. 烧写成功后,开发板断电,设置为EMMC启动,再重新上电观察效果