Bootload U-Boot分析

news2024/11/26 8:58:18

Bootloader是在操作系统运行之前执行的一段小程序。通过这段小程序可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备。

对于嵌入式系统,Bootloader是基于特定硬件平台来实现的。因此,几乎不可能为所有的嵌入式系统建立一个通用的Bootloader,不同的处理器架构都有不同的Bootloader。Bootloader不但依赖于CPU的体系结构,而且依赖于嵌入式系统板级设备的配置。对于2块不同的嵌入式板而言,即使它们使用同一种处理器,要想让运行在一块板子上的Bootloader程序也能运行在另一块板子上,一般也都需要修改Bootloader的源程序。

反过来,大部分Bootloader仍然具有很多共性,某些Bootloader也能够支持多种体系结构的嵌入式系统。例如,U-Boot就同时支持PowerPC、ARM、MIPS和X86等体系结构,支持的板子有上百种。通常,它们都能够自动从存储介质上启动,都能够引导操作系统,并且大部分都可以支持串口和以太网接口。

1 Bootloader的引导方式

Linux系统是通过Bootloader引导启动的。加电后,就要执行Bootloader来初始化系统。

系统加电或复位后,所有CPU都会从某个地址开始执行,这是由处理器设计决定的。例如,X86的复位向量在高地址端,ARM处理器在复位时从地址0x00000000取第一条指令。嵌入式系统的开发板都要把板上ROM或Flash映射到这个地址。因此,必须把Bootloader程序存储在相应的Flash位置。系统加电后,CPU将首先执行它。

主机和目标机之间一般有串口可以连接,Bootloader软件通常会通过串口来输入输出。例如,输出出错或者执行结果信息到串口终端,从串口终端读取用户控制命令等。Bootloader启动过程通常是多阶段的,这样既能提供复杂的功能,又有很好的可移植性。例如,从Flash启动的Bootloader多数是两阶段的启动过程。从后面U-Boot的内容可以详细分析这个特性。

大多数Bootloader都包含2种不同的操作模式:本地加载模式和远程下载模式。这两种操作模式的区别仅对于开发人员才有意义,也就是不同启动方式的使用。从最终用户的角度看,Bootloader的作用就是用来加载操作系统,并不存在所谓的本地加载模式与远程下载模式的区别。

因为Bootloader的主要功能是引导操作系统启动,所以我们详细讨论一下各种启动方式的特点。

(1)网络启动方式

这种方式开发板不需要配置较大的存储介质,与无盘工作站有点类似。但是使用这种启动方式之前,需要把Bootloader安装到板上的EPROM或者Flash中。Bootloader通过以太网接口远程下载Linux内核映像或者文件系统。

使用这种方式也有前提条件,就是目标板需有串口、以太网接口或者其他连接方式。串口一般可以作为控制台,同时可以用来下载内核映像和RAMDISK文件系统。串口通信传输速率过低,不适合用来挂接NFS文件系统。所以以太网接口成为通用的互联设备,一般的开发板都可以配置10M以太网接口。

对于PDA等手持设备来说,以太网的RJ-45接口显得大了些,而USB接口,特别是USB的迷你接口,尺寸非常小。对于开发的嵌入式系统,可以把USB接口虚拟成以太网接口来通信。这种方式在开发主机和开发板两端都需要驱动程序。

另外,还要在服务器上配置启动相关网络服务。Bootloader下载文件一般都使用TFTP网络协议,还可以通过DHCP的方式动态配置IP地址。DHCP/BOOTP服务为Bootloader分配IP地址,配置网络参数,然后才能够支持网络传输功能。如果Bootloader可以直接设置网络参数,就可以不使用DHCP。TFTP服务为Bootloader客户端提供文件下载功能,把内核映像和其他文件放在/tftpboot目录下。这样Bootloader可以通过简单的TFTP协议远程下载内核映像到内存。大部分引导程序都能够支持网络启动方式。例如,BIOS的PXE(Preboot Execution Environment)功能就是网络启动方式;U-Boot也支持网络启动功能。

(2)磁盘启动方式

传统的Linux系统运行在台式机或者服务器上,这些计算机一般都使用BIOS引导,并且使用磁盘作为存储介质。如果进入BIOS设置菜单,可以探测处理器、内存、硬盘等设备,可以设置BIOS从软盘、光盘或者某块硬盘启动。也就是说,BIOS并不直接引导操作系统。那么在硬盘的主引导区,还需要一个Bootloader。这个Bootloader可以从磁盘文件系统中把操作系统引导起来。Linux传统上是通过LILO(LInux LOader)引导的,后来又出现了GNU的软件GRUB(GRand Unified Bootloader)。这2种Bootloader广泛应用在x86的Linux系统上。你的开发主机可能就使用了其中一种,熟悉它们有助于配置多种系统引导功能。

LILO软件工程是由Werner Almesberger创建,专门为引导Linux开发的。现在LILO的维护者是John Coffman(最新版本下载站点:http://lilo.go.dyndns.org)。LILO有详细的文档,如LILO套件中附带使用手册和参考手册。此外,还可以在LDP的“LILO mini-HOWTO”中找到LILO的使用指南。GRUB是GNU计划的主要Bootloader。GRUB最初是由Erich Boleyn为GNU Mach操作系统撰写的引导程序。后来由Gordon Matzigkeit和Okuji Yoshinori接替了Erich的工作,继续维护和开发GRUB。GRUB的网站(http://www.gnu.org/software/grub/)上有对套件使用的说明文件,叫做《GRUB manual》。GRUB能够使用TFTP和BOOTP或者DHCP通过网络启动,这种功能对于系统开发过程很有用。除了传统的Linux系统上的引导程序以外,还有其他一些引导程序,也可以支持磁盘引导启动。例如,LoadLin可以从DOS下启动Linux;还有ROLO、LinuxBIOS、U-Boot也支持这种功能。

(3)Flash启动方式

大多数嵌入式系统上都使用Flash存储介质。Flash有很多类型,包括NOR Flash、NAND Flash和其他半导体盘。其中,NOR Flash(也就是线性Flash)使用最为普遍。

NOR Flash可以支持随机访问,所以代码是可以直接在Flash上执行的。Bootloader一般是存储在Flash芯片上的。另外,Linux内核映像和RAMDISK也可以存储在Flash上。通常需要把Flash分区使用,每个区的大小应该是Flash擦除块大小的整数倍。Bootloader一般放在Flash的底端或者顶端,这要根据处理器的复位向量设置。首先,要使Bootloader的入口位于处理器加电执行第一条指令的位置。其次,分配参数区,这里可以作为Bootloader的参数保存区域。再次,内核映像区,Bootloader引导Linux内核,就是要从这个地方把内核映像解压到RAM中去,然后跳转到内核映像入口执行。最后,文件系统区,如果使用Ramdisk文件系统,则需要Bootloader把它解压到RAM中。如果使用JFFS2文件系统,将直接挂接为根文件系统。还可以分出一些数据区,这要根据实际需要和Flash大小来考虑了。这些分区是开发者定义的,Bootloader一般直接读写对应的偏移地址。到了Linux内核空间,可以配置成MTD设备来访问Flash分区。但是,有的Bootloader也支持分区的功能,例如,Redboot可以创建Flash分区表,并且内核MTD驱动可以解析出redboot的分区表。除了NOR Flash外,还有NAND Flash、Compact Flash、DiskOnChip等。这些Flash具有芯片价格低,存储容量大的特点。但是这些芯片一般通过专用控制器的I/O方式来访问,不能随机访问,因此引导方式跟NOR Flash也不同。在这些芯片上,需要配置专用的引导程序。通常,这种引导程序起始的一段代码就把整个引导程序复制到RAM中运行,从而实现自行启动,这与从磁盘上启动有些相似。

2 Bootloader的种类

嵌入式系统世界已经有各种各样的Bootloader,种类划分也有多种方式。除了按照处理器体系结构不同划分以外,还有功能复杂程度的不同。

首先区分一下“Bootloader”和“Monitor”的概念。严格来说,“Bootloader”只是引导设备并且执行主程序的固件;而“Monitor”还提供了更多的命令行接口,可以进行调试、读写内存、烧写Flash、配置环境变量等。“Monitor”在嵌入式系统开发过程中可以提供很好的调试功能,开发完成以后,就完全设置成了一个“Bootloader”。所以,习惯上大家把它们统称为Bootloader。

下列出了Linux的开放源码引导程序及其支持的体系结构。表中给出了x86 ARM PowerPC体系结构的常用引导程序,并且注明了每一种引导程序是不是“Monitor”。

Linux的开放源码引导程序及其支持的体系结构

对于每种体系结构,都有一系列开放源码Bootloader可以选用。 

(1)x86

x86的工作站和服务器上一般使用LILO和GRUB。LILO是Linux发行版主流的Bootloader。不过Redhat Linux发行版已经使用了GRUB,GRUB比LILO有更友好的显示界面,使用配置也更加灵活方便。

在某些x86嵌入式单板机或者特殊设备上,会采用其他Bootloader,如ROLO。这些Bootloader可以取代BIOS的功能,能够从Flash中直接引导Linux启动。现在ROLO支持的开发板已经并入U-Boot,所以U-Boot也可以支持x86平台。

(2)ARM

ARM处理器的芯片商很多,所以每种芯片的开发板都有自己的Bootloader。结果ARM bootloader也变得多种多样。最早有为ARM720处理器的开发板的固件,又有了armboot和StrongARM平台的BLOB,还有S3C2410处理器开发板上的vivi等。现在armboot已经并入了U-Boot,所以U-Boot也支持ARM/XSCALE平台。U-Boot已经成为ARM平台事实上的标准Bootloader。

(3)PowerPC

PowerPC平台的处理器有标准的Bootloader,就是ppcboot。ppcboot在合并armboot等之后,创建了U-Boot,成为各种体系结构开发板的通用引导程序。U-Boot仍然是PowerPC平台的主要Bootloader。

(4)MIPS

MIPS公司开发的YAMON是标准的Bootloader,也有许多MIPS芯片商为自己的开发板写了Bootloader。现在,U-Boot也已经支持MIPS平台。

(5)SH

SH平台的标准Bootloader是sh-boot。Redboot在这种平台上也很好用。

(6)M68K

M68K平台没有标准的Bootloader。Redboot能够支持M68K系列的系统。

值得说明的是Redboot,它几乎能够支持所有的体系结构,包括MIPS、SH、M68K等体系结构。Redboot是以eCos为基础,采用GPL许可的开源软件工程。现在由core eCos的开发人员维护(源码下载网站:http://www.ecoscentric.com/snapshots)。Redboot的文档也相当完善,有详细的使用手册《RedBoot User’s Guide》。

3 U-Boot源码结构

最早,DENX软件工程中心的Wolfgang Denk基于8xxrom的源码创建了ppcboot工程,并且不断添加处理器的支持。后来,Sysgo Gmbh把ppcboot移植到ARM平台上,创建了armboot工程。然后以ppcboot工程和armboot工程为基础,创建了U-Boot工程。

现在U-Boot已经能够支持PowerPC、ARM、x86、MIPS体系结构的上百种开发板,已经成为功能最多、灵活性最强并且开发最积极的开放源码Bootloader。目前仍然由DENX的Wolfgang Denk维护(U-Boot软件包下载网站:http://sourceforge.net/project/u-boot。还可以订阅该网站活跃的U-Boot Users邮件论坛,这个邮件论坛对于U-Boot的开发和使用都很有帮助)。

从网站上下载得到U-Boot源码包,如U-Boot-1.1.6.tar.bz2。解压就可以得到全部U-Boot源程序。在顶层目录下有18个子目录,分别存放和管理不同的源程序。这些目录中所要存放的文件有一定规则,可以分为3类。

(1)第1类目录与处理器体系结构或者开发板硬件直接相关。

(2)第2类目录是一些通用的函数或者驱动程序。

(3)第3类目录是U-Boot的应用程序、工具或者文档。

U-Boot的源码顶层目录下各级目录存放原则

 U-Boot的源代码包含对几十种处理器、数百种开发板的支持。可是对于特定的开发板,配置编译过程只需要其中部分程序。这里具体以S3C2440 ARM920t处理器为例,具体分析S3C2440处理器和开发板所依赖的程序,以及U-Boot的通用函数和工具。

4 U-Boot的编译

U-Boot的源码是通过GCC和Makefile组织编译的。顶层目录下的Makefile首先可以设置开发板的定义,然后递归地调用各级子目录下的Makefile,最后把编译过的程序链接成U-Boot映像。

(1)顶层目录下的Makefile

它负责U-Boot整体配置编译。按照配置的顺序阅读其中关键的几行。

每一种开发板在Makefile都需要有板子配置的定义。例如,SMDK2440开发板的定义如下:

smdk2440_config   :      unconfig
   @$(MKCONFIG)$(@:_config=)arm s3c24xx smdk2440 samsung  s3c2440

执行配置U-Boot的命令make smdk2440_config,通过./mkconfig脚本生成include/config. mk的配置文件。文件内容正是根据Makefile对开发板的配置生成的。

          ARCH  =arm
          CPU  =s3c24xx
          BOARD =smdk2440
          VENDOR= sumsang
          SOC  =s3c2440

上面的include/config.mk文件定义了ARCH、CPU、BOARD、SOC这些变量。这样硬件平台依赖的目录文件可以根据这些定义来确定。SMDK2440平台相关目录如下。

          board/samsung/smdk2440/
          cpu/arm920t/
          cpu/arm920t/s3c24x0/
          lib_arm/
          include/asm-arm/
          include/configs/smdk2440.h

再回到顶层目录的Makefile文件开始的部分,其中下列几行包含了这些变量的定义。

          # load ARCH, BOARD, and CPU configuration
          include include/config.mk
          export    ARCH CPU BOARD VENDOR SOC

Makefile的编译选项和规则在顶层目录的config.mk文件中定义。各种体系结构通用的规则直接在这个文件中定义。通过ARCH、CPU、BOARD、SOC等变量为不同硬件平台定义不同选项。不同体系结构的规则分别包含在ppc_config.mk、arm_config.mk、mips_config.mk等文件中。

顶层目录的Makefile中还要定义交叉编译器,以及编译U-Boot所依赖的目标文件。

ifeq ($(ARCH),arm)
CROSS_COMPILE=arm-linux-         //交叉编译器的前缀
#endif
export  CROSS_COMPILE
…
# U-Boot objects....order is important (i.e. start must be first)
OBJS    =cpu/$(CPU)/start.o                //处理器相关的目标文件
…
LIBS  =lib_generic/libgeneric.a          //定义依赖的目录,每个目录下先把目标
										 文件连接成*.a文件。
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
endif
LIBS += lib_$(ARCH)/lib$(ARCH).a
…

然后还有U-Boot映像编译的依赖关系。

ALL = u-boot.srec u-boot.bin System.map
all:       $(ALL)
u-boot.srec:    u-boot
		$(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
u-boot.bin: u-boot
		$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
…
u-boot:        depend$(SUBDIRS)$(OBJS)$(LIBS)$(LDSCRIPT)
		UNDEF_SYM='$(OBJDUMP) -x $(LIBS) /
		|sed -n –e 's/.*/(__u_boot_cmd_.*/)/-u/1/p'|sort|
uniq`;/$(LD) $(LDFLAGS) $$UNDEF_SYM $(OBJS) /
		--start-group $(LIBS) $(PLATFORM_LIBS) --end-group /
		-Map u-boot.map -o u-boot

Makefile默认的编译目标为all,包括u-boot.srec、u-boot.bin、System.map。u-boot.srec和u-boot.bin又依赖于U-Boot。U-Boot就是通过ld命令按照u-boot.map地址表把目标文件组装成U-Boot。

其他Makefile内容就不再详细分析了,上述代码分析应该可以为阅读代码提供了一个线索。

(2)开发板配置头文件

除了编译过程Makefile以外,还要在程序中为开发板定义配置选项或者参数。这个头文件是include/configs/<board_name>.h。<board_name>用相应的BOARD定义代替。

这个头文件中主要定义了两类变量。

一类是选项,前缀是CONFIG_,用来选择处理器、设备接口、命令、属性等。例如:

ifndef __CONFIG_H
#define __CONFIG_H
/*
*  *High Level Configuration Options
*   *(easy to change)
*    */
#define CONFIG_ARM920T     1      /*This is an ARM920EJS Core    */
#define CONFIG_S3C2440     1      /*in a SAMSUNG S3C24A0 SoC     */
#define CONFIG_S3C24XX     1      /*in a SAMSUNG S3C24XX Family  */
#define CONFIG_SMDK2440    1      /*on a SAMSUNG SMDK2440 Board         */
/* input clock of PLL */
#ifdef KINGFISH
#define CONFIG_SYS_CLK_FREQ 12000000
#else
#define CONFIG_SYS_CLK_FREQ 16934400
#endif
另一类是参数,前缀是CFG_,用来定义总线频率、串口波特率、Flash地址等参数。例如:
#define CFG_LONGHELP       /*undef to save memory    */
#define CFG_PROMPT   "SMDK2440#"   /*Monitor Command Prompt    */
#define CFG_CBSIZE     256    /*Console I/O Buffer Size  */
#define CFG_PBSIZE (CFG_CBSIZE+sizeof(CFG_PROMPT)+16) /* Print Buffer Size */
#define CFG_MAXARGS    16     /*max number of command args   */
#define CFG_BARGSIZE       CFG_CBSIZE   /*Boot Argument Buffer Size    */
#define CFG_MEMTEST_START   0x10000000  /*memtest works on*/
#define CFG_MEMTEST_END    0x13e00000  /*63 MB in DRAM    */#endif

(3)编译结果

根据对Makefile的分析,编译分为两步。第1步配置,如make smdk2440_config;第2步编译,执行make就可以了。

编译完成后,可以得到U-Boot各种格式的映像文件和符号表

 U-Boot的3种映像格式都可以烧写到Flash中,但需要看加载器能否识别这些格式。一般u-boot.bin最为常用,直接按照二进制格式下载,并且按照绝对地址烧写到Flash中就可以了。u-boot和u-boot.srec格式映像都自带定位信息。

(4)U-Boot工具

在tools目录下还有些U-Boot的工具,这些工具有的也经常用到,表6.4说明了几种工具的用途。

 这些工具都有源代码,可以参考改写其他工具。其中mkimage是很常用的一个工具,Linux内核映像和ramdisk文件系统映像都可以转换成U-Boot的格式。

5 U-Boot的移植

U-Boot能够支持多种体系结构的处理器,支持的开发板也越来越多。因为Bootloader是完全依赖硬件平台的,所以在新电路板上需要移植U-Boot程序。开始移植U-Boot之前,先要熟悉硬件电路板和处理器。确认U-Boot是否已经支持新开发板的处理器和I/O设备。假如U-Boot已经支持一块非常相似的电路板,那么移植的过程将非常简单。移植U-Boot工作就是添加开发板硬件相关的文件、配置选项,然后配置编译。

开始移植之前,需要先分析一下U-Boot已经支持的开发板,比较出硬件配置最接近的开发板。选择的原则是:首先处理器相同,其次处理器体系结构相同,然后是以太网接口等外围接口。还要验证一下这个参考开发板的U-Boot,至少能够配置编译通过。

以S3C2440处理器的开发板为例,U-Boot-1.1.6版本已经支持SMDK2440开发板。我们可以基于SMDK2440移植,那么先把SMDK2440编译通过。以S3C2440开发板etc2410为例说明。移植的过程参考SMDK2440开发板,SMDK2440在U-Boot-1.1.6中已经支持。

移植U-Boot的基本步骤如下。

(1)在顶层Makefile中为开发板添加新的配置选项,使用已有的配置项目为例。

smdk2440_config  :    unconfig
  @$(MKCONFIG)$(@:_config=)arm s3c24xx smdk2440 samsung  s3c2440

参考上面两行,添加下面两行。

etc2440_config  :    unconfig
    @$(MKCONFIG)$(@:_config=)arm s3c24xx smdk2440 samsung  s3c2440

(2)创建一个新目录存放开发板相关的代码,并且添加文件。

board/etc2440/config.mk
board/etc2440/flash.c
board/etc2440/fs2410.c
board/etc2440/Makefile
board/etc2440/memsetup.S
board/etc2440/u-boot.lds

(3)为开发板添加新的配置文件

可以先复制参考开发板的配置文件再修改。例如:

$cp include/configs/smdk2440.h include/configs/etc2440.h

如果是为一个新的CPU移植,还要创建一个新的目录存放CPU相关的代码。

(4)配置开发板

$ make etc2410_config

(5)编译U-Boot

执行make命令,编译成功可以得到U-Boot映像。有些错误是与配置选项有关系的,通常打开某些功能选项会带来一些错误,一开始可以尽量参考板配置相同。

(6)添加驱动或者功能选项

在能够编译通过的基础上,还要实现U-Boot的以太网接口、Flash擦写等功能。

对于etc2410开发板的以太网驱动和smdk2440完全相同,所以可以直接使用。CS8900驱动程序文件如下。

drivers/cs8900.c
drivers/cs8900.h

对于Flash的选择就麻烦多了,Flash芯片价格或者采购方面的因素都有影响。多数开发板大小、型号都不相同。所以还需要移植Flash的驱动。每种开发板目录下一般都有flash.c文件,需要根据具体的Flash类型修改。例如:

board/etc2440/flash.c

(7)调试U-Boot源代码,直到U-Boot在开发板上能够正常启动。

调试的过程可能是很艰难的,需要借助工具,并且有些问题可能会困扰很长时间。

6 添加U-Boot命令

U-Boot的命令为用户提供了交互功能,并且已经实现了几十个常用的命令。如果开发板需要很特殊的操作,可以添加新的U-Boot命令。

U-Boot的每一个命令都是通过U_Boot_CMD宏定义的。这个宏在include/command.h头文件中定义,每一个命令定义一个cmd_tbl_t结构体。

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) /
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

这样每一个U-Boot命令用一个结构体来描述。结构体包含的成员变量:命令名称、最大参数个数、重复数、命令执行函数、用法、帮助。从控制台输入的命令是由common/command.c中的程序解释执行的。find_cmd()负责匹配输入的命令,从列表中找出对应的命令结构体。

基于U-Boot命令的基本框架,来分析一下简单的icache操作命令,就可以知道添加新命令的方法。

(1)定义CACHE命令。

在include/cmd_confdefs.h中定义了所有U-Boot命令的标志位。

#define CFG_CMD_CACHE    0x00000010ULL  /*icache,dcache    */

如果有更多的命令,也要在这里添加定义。

(2)实现CACHE命令的操作函数。

下面是common/cmd_cache.c文件中icache命令部分的代码。

#if (CONFIG_COMMANDS & CFG_CMD_CACHE)
static int on_off (const char *s)
{      //这个函数解析参数,判断是打开cache,还是关闭cache
	 if(strcmp(s,"on")==0){  //参数为“on”
		  return (1);
	 }else if(strcmp(s,"off")==0){  //参数为“off”
		  return (0);
  }
  return (-1);
}
int do_icache ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{    //对指令cache的操作函数
   switch(argc){
   case 2:             /* 参数个数为1,则执行打开或者关闭指令cache操作 */
			switch(on_off(argv[1])){
			case 0:     icache_disable();       //打开指令cache
			  break;
			case 1:     icache_enable();        //关闭指令cache
			  break;
			}
		   /*FALL TROUGH*/
   case 1:          /* 参数个数为0,则获取指令cache状态*/
		   printf("Instruction Cache is %s/n",
			  icache_status() ? "ON":"OFF");
		   return 0;
   default:  //其他默认情况下,打印命令使用说明
		   printf("Usage:/n%s/n",cmdtp->usage);
		   return 1;
   }
   return 0;
}
……
U_Boot_CMD( //通过宏定义命令
  icache,   2,   1,    do_icache,  //命令为icache,命令执行函数为do_icache()
  "icache  -enable or disable instruction cache/n",   //帮助信息
  "[on, off]/n"
  "    -enable or disable instruction cache/n"
);
…
#endif

U-Boot的命令都是通过结构体__U_Boot_cmd_##name来描述的。根据U_Boot_CMD在include/command.h中的两行定义可以明白。

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) /
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

还有,不要忘了在common/Makefile中添加编译的目标文件。

(3)打开CONFIG_COMMANDS选项的命令标志位。

这个程序文件开头有#if语句需要预处理是否包含这个命令函数。CONFIG_COMMANDS选项在开发板的配置文件中定义。例如,SMDK2440平台在include/configs/smdk2440.h中有如下定义。

          /***********************************************************
          * Command definition
          ***********************************************************/
          #define CONFIG_COMMANDS /
                        (CONFIG_CMD_DFL  |/
                        CFG_CMD_CACHE    |/
                        CFG_CMD_REGINFO   |/
                        CFG_CMD_DATE     |/
                        CFG_CMD_ELF)

按照上面3个步骤,就可以添加新的U-Boot命令。

7 U-Boot的调试

新移植的U-Boot如果不能正常工作,这时就需要调试了。调试U-Boot离不开工具,只有理解U-Boot启动过程,才能正确地调试U-Boot源代码。

1.硬件调试器

硬件电路板制作完成以后,这时上面还没有任何程序,称为裸板。首要的工作是把程序或者固件加载到裸板上,这就要通过硬件工具来完成。习惯上把这种硬件工具称为仿真器。

仿真器可以通过处理器的JTAG等接口控制板子,直接把程序下载到目标板内存中,或者进行Flash编程。如果板上的Flash是可以插拔的,就可以通过专用的Flash烧写器来完成。其中JTAG等接口就是专门用来连接仿真器的。

仿真器还有一个重要的功能就是在线调试程序,这对于调试Bootloader和硬件测试程序很有用。从最简单的JTAG电缆,到ICE仿真器,再到可以调试Linux内核的仿真器。复杂的仿真器可以支持与计算机间的以太网或者USB接口通信,下载U-Boot到目标板内存中。通过BDI2000的下载命令LOAD,把程序加载到目标板内存中;然后跳转到U-Boot入口。

2.U-Boot启动过程

尽管有了调试跟踪手段,甚至也可以通过串口打印信息了,但是不一定能够准确判断出错原因。如果能够充分理解代码的启动流程,那么对准确地解决和分析问题很有帮助。

开发板加电后,执行U-Boot的第一条指令,然后按顺序执行U-Boot启动函数。看一下board/smsk2410/u-boot.lds这个链接脚本,可以知道目标程序的各部分链接顺序。第一个要链接的是cpu/arm920t/start.o,那么U-Boot的入口指令一定位于这个程序中。下面详细分析一下程序跳转和函数的调用关系,以及函数实现。

(1)cpu/arm920t/start.S

这个汇编程序是U-Boot的入口程序,开头就是复位向量的代码。

_start:b      reset       //复位向量
	ldr   pc,_undefined_instruction
	ldr   pc,_software_interrupt
	ldr   pc,_prefetch_abort
	ldr   pc,_data_abort
	ldr   pc,_not_used
	ldr   pc,_irq      //中断向量
	ldr   pc,_fiq      //中断向量
…
/*the actual reset code  */
reset:         //复位启动子程序
	/* 设置CPU为SVC32模式 */
	mrs   r0,cpsr
	bic   r0,r0,#0x1f
	orr   r0,r0,#0xd3
	msr     cpsr,r0
/* 关闭看门狗 */
/* 这些初始化代码在系统重新启动时执行,运行时热复位从RAM中启动不执行 */
#ifdef CONFIG_INIT_CRITICAL
	bl    cpu_init_crit
#endif
relocate:                     /* 把U-Boot重新定位到RAM*/
	adr   r0,_start         /*r0是代码的当前位置 */
	ldr   r1,_TEXT_BASE     /* 测试判断是从Flash启动,还是从RAM启动*/
	cmp    r0,r1            /* 比较r0和r1,调试的时候不要执行重定位 */
	beq    stack_setup   /* 如果r0等于r1,跳过重定位代码 */
	/* 准备重新定位代码 */
	ldr   r2,_armboot_start
	ldr   r3,_bss_start
	sub   r2,r3,r2          /*r2 得到armboot的大小   */
	add   r2,r0,r2          /*r2 得到要复制代码的末尾地址 */
copy_loop:/* 重新定位代码 */
	ldmia r0!,{r3-r10}   /*从源地址[r0]复制 */
	stmia r1!,{r3-r10}   /* 复制到目的地址[r1]*/
	cmp     r0,r2         /* 复制数据块直到源数据末尾地址[r2]*/
	ble   copy_loop
	/* 初始化堆栈等     */
stack_setup:
	ldr   r0,_TEXT_BASE            /* 上面是128 KB重定位的u-boot*/
	sub   r0,r0,#CFG_MALLOC_LEN    /* 向下是内存分配空间 */
	sub   r0,r0,#CFG_GBL_DATA_SIZE/* 然后是bdinfo结构体地址空间  */
#ifdef CONFIG_USE_IRQ
	sub   r0,r0,#(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
	sub   sp,r0,#12    /* 为abort-stack预留3个字 */
clear_bss:
	ldr   r0,_bss_start     /* 找到bss段起始地址 */
	ldr   r1,_bss_end       /*     bss段末尾地址   */
	mov     r2,#0x00000000    /* 清零 */
clbss_l:str r2,[r0]       /*bss段地址空间清零循环...  */
	add   r0,r0,#4
	cmp     r0,r1
	bne   clbss_l
	/* 跳转到start_armboot函数入口,_start_armboot字保存函数入口指针 */
	ldr   pc,_start_armboot
_start_armboot:.word start_armboot    //start_armboot函数在lib_arm/board.c中实现
/* 关键的初始化子程序 */
cpu_init_crit:
……  //初始化CACHE,关闭MMU等操作指令
	/* 初始化RAM时钟。
	* 因为内存时钟是依赖开发板硬件的,所以在board的相应目录下可以找到
memsetup.S文件。
	*/
	mov     ip,lr
	bl      memsetup   //memsetup子程序在board/smdk2410/memsetup.S中实现
	mov     lr,ip
	mov     pc,lr

(2)lib_arm/board.c

start_armboot是U-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。

void start_armboot (void)
{
	DECLARE_GLOBAL_DATA_PTR;
	ulong size;
	init_fnc_t **init_fnc_ptr;
	char *s;
	/* Pointer is writable since we allocated a register for it */
	gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
	/* compiler optimization barrier needed for GCC >= 3.4 */
	__asm__ __volatile__("": : :"memory");
	memset ((void*)gd, 0, sizeof (gd_t));
	gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
	memset (gd->bd, 0, sizeof (bd_t));
	monitor_flash_len = _bss_start - _armboot_start;
	/* 顺序执行init_sequence数组中的初始化函数 */
	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		  if ((*init_fnc_ptr)() != 0) {
				hang ();
		  }
	}
	/*配置可用的Flash */
	size = flash_init ();
	display_flash_config (size);
	/*_armboot_start在u-boot.lds链接脚本中定义 */
	mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
	/* 配置环境变量,重新定位 */
	env_relocate ();
	/* 从环境变量中获取IP地址 */
	gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
	/* 以太网接口MAC地址 */
	……
	devices_init();     /* 获取列表中的设备 */
	jumptable_init ();
	console_init_r();   /* 完整地初始化控制台设备 */
	enable_interrupts();/* 使能例外处理 */
	/* 通过环境变量初始化 */
	if ((s = getenv ("loadaddr")) != NULL) {
		  load_addr = simple_strtoul (s, NULL, 16);
	}
	/*main_loop()总是试图自动启动,循环不断执行 */
	for (;;) {
		  main_loop();   /* 主循环函数处理执行用户命令 --common/main.c*/
	}
	/* NOTREACHED - no way out of command loop except booting */
}

(3)init_sequence[]

init_sequence[]数组保存着基本的初始化函数指针。这些函数名称和实现的程序文件在下列注释中。

init_fnc_t *init_sequence[] = {
	cpu_init,   /* 基本的处理器相关配置 --cpu/arm920t/cpu.c*/
	board_init,   /* 基本的板级相关配置--board/smdk2410/smdk2410.c*/
	interrupt_init,       /* 初始化例外处理--cpu/arm920t/s3c24x0/
					  interrupt.c */
	env_init,           /* 初始化环境变量 --common/cmd_flash.c*/
	init_baudrate,        /* 初始化波特率设置 --lib_arm/board.c*/
	serial_init,  /* 串口通信设置 --cpu/arm920t/s3c24x0/serial.c*/
	console_init_f,   /* 控制台初始化阶段1--common/console.c*/
	display_banner,      /* 打印u-boot信息 --lib_arm/board.c*/
	dram_init,    /* 配置可用的RAM--board/smdk2410/smdk2410.c*/
	display_dram_config,/* 显示RAM的配置大小 --lib_arm/board.c*/
	NULL;
};

8 U-Boot与内核的关系

U-Boot作为Bootloader,具备多种引导内核启动的方式。常用的go和bootm命令可以直接引导内核映像启动。U-Boot与内核的关系主要是内核启动过程中参数的传递。

(1)go命令的实现

/*common/cmd_boot.c  */
int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	ulong addr, rc;
	int    rcode=0;
	if (argc < 2) {
		   printf("Usage:/n%s/n",cmdtp->usage);
		   return 1;
	}
	addr = simple_strtoul(argv[1], NULL, 16);
	printf ("## Starting application at 0x%08lX .../n", addr);
	/*
	* pass address parameter as argv[0] (aka command name),
	* and all remaining args
	*/
	rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);
	if (rc != 0) rcode = 1;
	printf ("## Application terminated, rc = 0x%lX/n", rc);
	return rcode;
};

go命令调用do_go()函数,跳转到某个地址执行的。如果在这个地址准备好了自引导的内核映像,就可以启动了。尽管go命令可以带变参,但实际使用时一般不用来传递参数。

(2)bootm命令的实现

	/* common/cmd_bootm.c */
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	ulong iflag;
	ulong addr;
	ulong data, len, checksum;
	ulong  *len_ptr;
	uint  unc_len=0x400000;
	int   i,verify;
	char  *name,*s;
	int   (*appl)(int,char*[]);
	image_header_t *hdr = &header;
	s = getenv ("verify");
	verify=(s&&(*s=='n')) ? 0:1;
	if (argc < 2) {
		   addr=load_addr;
	} else {
		   addr=simple_strtoul(argv[1],NULL,16);
	}
	SHOW_BOOT_PROGRESS (1);
	printf ("## Booting image at %08lx .../n", addr);
	/* Copy header so we can blank CRC field for re-calculation */
	memmove (&header, (char *)addr, sizeof(image_header_t));
	if (ntohl(hdr->ih_magic) != IH_MAGIC)
	{
		   puts("Bad Magic Number/n");
		   SHOW_BOOT_PROGRESS(-1);
		   return 1;
	}
	SHOW_BOOT_PROGRESS (2);
	data = (ulong)&header;
	len  =sizeof(image_header_t);
	checksum = ntohl(hdr->ih_hcrc);
	hdr->ih_hcrc = 0;
	if(crc32 (0, (char *)data, len) != checksum) {
		   puts("Bad Header Checksum/n");
		   SHOW_BOOT_PROGRESS(-2);
		   return 1;
	}
	SHOW_BOOT_PROGRESS (3);
	/* for multi-file images we need the data part, too */
	print_image_hdr ((image_header_t *)addr);
	data = addr + sizeof(image_header_t);
	len  =ntohl(hdr->ih_size);
	if(verify) {
		   puts("   Verifying Checksum...");
		   if(crc32(0,(char*)data,len)!=ntohl(hdr->ih_dcrc)){
				printf ("Bad Data CRC/n");
				SHOW_BOOT_PROGRESS (-3);
				return 1;
		   }
		   puts("OK/n");
	}
	SHOW_BOOT_PROGRESS (4);
	len_ptr = (ulong *)data;
	…
	switch (hdr->ih_os) {
	default:              /*handled by(original)Linux case*/
	case IH_OS_LINUX:
		do_bootm_linux  (cmdtp,flag,argc,argv,
					addr, len_ptr, verify);
		break;
	…
}

bootm命令调用do_bootm函数,这个函数专门用来引导各种操作系统映像,可以支持引导Linux、vxWorks、QNX等操作系统。引导Linux的时候,调用do_bootm_linux()函数。

(3)do_bootm_linux()函数的实现

/* lib_arm/armlinux.c */
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
			  ulong addr, ulong *len_ptr, int verify)
{
	DECLARE_GLOBAL_DATA_PTR;
	ulong len = 0, checksum;
	ulong initrd_start, initrd_end;
	ulong data;
	void (*theKernel)(int zero, int arch, uint params);
	image_header_t *hdr = &header;
…
}

do_bootm_linux()函数是专门引导Linux映像的函数,它还可以处理ramdisk文件系统的映像。这里引导的内核映像和ramdisk映像,必须是U-Boot格式的。U-Boot格式的映像可以通过mkimage工具来转换,其中包含了U-Boot可以识别的符号。

9 U-Boot的常用命令

U-Boot加电启动后,按任意键可以退出自动启动状态,进入命令行。

U-Boot 1.1.6 (Apr 262010-12:27:13)
U-Boot code:11080000->1109614C  BSS:->1109A91C
RAM Configuration:
Bank #0: 10000000 32 MB
Micron StrataFlash MT28F128J3 device initialized
Flash: 32 MB
In:    serial
Out:   serial
Err:   serial
Hit any key to stop autoboot:  0
U-Boot>

在命令行提示符下,可以输入U-Boot的命令并执行。U-Boot可以支持几十个常用命令,通过这些命令,可以对开发板进行调试,可以引导Linux内核,还可以擦写Flash完成系统部署等功能。掌握这些命令的使用,才能够顺利地进行嵌入式系统的开发。

输入help命令,可以得到当前U-Boot的所有命令列表。每一条命令后面是简单的命令说明。

=> help
?      -alias for'help'
autoscr - run script from memory
base    -print or set address offset
bdinfo  -print Board Info structure
boot    -boot default,i.e.,run'bootcmd'
bootd   -boot default,i.e.,run'bootcmd'
bootm   -boot application image from memory
…

U-Boot还提供了更加详细的命令帮助,通过help命令还可以查看每个命令的参数说明。由于开发过程的需要,有必要先把U-Boot命令的用法弄清楚。接下来,根据每一条命令的帮助信息,解释一下这些命令的功能和参数。

=> help bootm
bootm [addr [arg ...]]
  - boot application image stored in memory
	passing arguments 'arg ...'; when booting a Linux kernel,
	'arg' can be the address of an initrd image

bootm命令可以引导启动存储在内存中的程序映像。这些内存包括RAM和可以永久保存的Flash。第1个参数addr是程序映像的地址,这个程序映像必须转换成U-Boot的格式。

第2个参数对于引导Linux内核有用,通常作为U-Boot格式的RAMDISK映像存储地址;也可以是传递给Linux内核的参数(默认情况下传递bootargs环境变量给内核)。

          => help bootp
          bootp [loadAddress] [bootfilename]

bootp命令通过bootp请求,要求DHCP服务器分配IP地址,然后通过TFTP协议下载指定的文件到内存。

第1个参数是下载文件存放的内存地址。

第2个参数是要下载的文件名称,这个文件应该在开发主机上准备好。

          => help cp
          cp [.b, .w, .l] source target count
                - copy memory

cp命令可以在内存中复制数据块,包括对Flash的读写操作。

第1个参数source是要复制的数据块起始地址。

第2个参数target是数据块要复制到的地址。这个地址如果在Flash中,那么会直接调用写Flash的函数操作,所以U-Boot写Flash就使用这个命令,当然需要先把对应Flash区域内容清除。

第3个参数count是要复制的数目,根据cp.b、cp.w、cp.l分别以字节、字、长字为单位。

          => help echo
          echo [args..]
              - echo args to console; /c suppresses newline

echo命令回显参数。

          => help erase
          erase start end
              - erase FLASH from addr 'start' to addr 'end'
          erase N:SF[-SL]
              - erase sectors SF-SL in FLASH bank # N
          erase bank N
              - erase FLASH bank # N
          erase all
              - erase all FLASH banks

erase命令可以擦除Flash。

参数必须指定Flash擦除的范围,按照起始地址和结束地址,start必须是擦除块的起始地址;end必须是擦除末尾块的结束地址。这种方式最常用。例如,擦除0x20000 – 0x3ffff区域命令为erase 20000 3ffff。按照组和扇区,N表示Flash的组号,SF表示擦除起始扇区号,SL表示擦除结束扇区号。另外,还可以擦除整个组,擦除组号为N的整个Flash组。擦除全部Flash只要给出一个all的参数即可。

          => help go
          go addr [arg ...]
              - start application at address 'addr'
                passing 'arg' as arguments

go命令可以执行应用程序。

第1个参数是要执行程序的入口地址。

第2个可选参数是传递给程序的参数,可以不用。

          => help iminfo
          iminfo addr [addr ...]
              - print header information for application image starting at
                  address 'addr' in memory; this includes verification of the
                  image contents (magic number, header and payload checksums)

iminfo可以打印程序映像的开头信息,包含了映像内容的校验(序列号、头和校验和)。

第1个参数指定映像的起始地址。

第2个可选的参数是指定更多的映像地址。

          => help loadb
          loadb [ off ] [ baud ]
              - load binary file over serial line with offset 'off' and baudrate 'baud'

loadb命令可以通过串口线下载二进制格式文件。

          => help loads
          loads [ off ]
              - load S-Record file over serial line with offset 'off'

nfs命令可以使用NFS网络协议通过网络启动映像。

          => help printenv
          printenv
              - print values of all environment variables
          printenv name ...
              - print value of environment variable 'name'

printenv命令打印环境变量。

可以打印全部环境变量,也可以只打印参数中列出的环境变量。

          => help run
          run var [...]
              - run the commands in the environment variable(s) 'var'

run命令可以执行环境变量中的命令,后面参数可以跟几个环境变量名。

          => help setenv
          setenv name value ...
              - set environment variable 'name' to 'value ...'
          setenv name
              - delete environment variable 'name'

setenv命令可以设置环境变量。

第1个参数是环境变量的名称。

第2个参数是要设置的值,如果没有第2个参数,表示删除这个环境变量。

          => help sleep
          sleep N
              - delay execution for N seconds (N is _decimal_ !!!)

sleep命令可以延迟N秒钟执行,N为十进制数。

          => help tftpboot
          tftpboot [loadAddress] [bootfilename]

tftpboot命令可以使用TFTP协议通过网络下载文件。按照二进制文件格式下载。另外使用这个命令,必须配置好相关的环境变量,如serverip和ipaddr。

第1个参数loadAddress是下载到的内存地址。

第2个参数是要下载的文件名称,必须放在TFTP服务器相应的目录下。

这些U-Boot命令为嵌入式系统提供了丰富的开发和调试功能。在Linux内核启动和调试过程中,都可以用到U-Boot的命令。但是一般情况下,不需要使用全部命令。例如,已经支持以太网接口,可以通过tftpboot命令来下载文件,那么还有必要使用串口下载的loadb吗?反过来,如果开发板需要特殊的调试功能,也可以添加新的命令。

10 U-Boot的环境变量

与Shell类似,U-Boot也使用环境变量。可以通过printenv命令查看环境变量的设置。

U-Boot> printenv
bootdelay=3
baudrate=115200
netmask=255.255.0.0
ethaddr=12:34:56:78:90:ab
bootfile=uImage
bootargs=console=ttyS0,115200 root=/dev/ram rw initrd=0x30800000,8M
bootcmd=tftp 0x30008000 zImage;go 0x30008000
serverip=192.168.1.1
ipaddr=192.168.1.100
stdin=serial
stdout=serial
stderr=serial
Environment size: 337/131068 bytes
U-Boot>

U-Boot环境变量的解释说明

 U-Boot的环境变量都可以有默认值,也可以修改并且保存在参数区。U-Boot的参数区一般有EEPROM和Flash两种设备。

环境变量的设置命令为setenv,在此以NFS和Nandflash两种启动的方式来说明一下。

(1)在启动内核之前需要设置启动参数,可以使用setenv bootargs命令完成。例如,下面的启动参数是使用nfs方式挂载文件系统的内核启动参数:

# setenv bootargs noinitrd root=/dev/nfs console=ttySAC0 init=/linuxrc
nfsroot=192.168.1.10:/nfsboot
ip=192.168.1.20:192.168.1.10:192.168.1.1:255.255.255.0::eth0:on

这是以nfs作为根文件系统的启动参数,其中192.168.1.20是板子IP;192.168.1.10是服务器IP,192.168.1.1是网关IP,255.255.255.0是掩码。可以根据自己网络的实际情况更换合适的IP和nfs目录。使用nfs作为根文件系统对于程序的调试是非常有意义的,这样板子的根文件系统可以放在开发主机上(Red Hat或Ubuntu),不必每次文件系统更新后都要重新烧写Flash。

(2)如果程序调试已经结束,需要把文件系统烧写到板子的Nandflash中,并让内核到Nandflash中挂载文件系统,那么需要使用下面的启动参数:

#setenv bootargs noinitrd root=/dev/mtdblock2 console=ttySAC0 init=/linuxrc

这是以nand中的mtdblock2分区作为根文件系统的启动参数,这里默认情况下使用的是Yaffs2格式的文件系统。

上面定义的环境变量有serverip ipaddr rootpath bootargs kernel_addr。环境变量bootargs中还使用了环境变量,bootargs定义命令行参数,通过bootm命令传递给内核。环境变量nfscmd中也使用了环境变量,功能是把uImage下载到指定的地址并且引导起来。可以通过run命令执行nfscmd脚本。

(3)U-Boot自支运行命令

U-Boot在板子复位后,如果用户没有在命令界面中按下任意键,那么U-Boot会自动运行某些命令。比如下图中,如果用户在倒计时结束之前没有按下按键那么U-Boot自动从Nandflash中复制数据到内存,并跳转到内存中启动内核。

对于这个复位后自动运行的动作实际上是可以自定义的。比如说,要实现这样的动作:让U-Boot在复位后自动通过TFTP从主机下载内核文件zImage到内存中并启动内核的,那么可以这样设置:

# setenv bootcmd "tftp c0008000 zImage;bootm c0008000"

个人比较喜欢这样设置,因为这样设置在内核改动后不需要重新烧写到Nandflash中,直接放到Red Hat或Ubuntu的tftp共享目录下,由uboot下载到内存中直接启动,不需要经过nand烧写。设置完毕后输入saveenv保存一下,重新启动板子后设置生效。

当内核调试完毕,内核需要烧写到Nandflash中,这时U-Boot的自动运行动作就变成了从Nandflash中读取内核到内存,并跳转到内存启动内核。这样就需要如下设置:

# setenv bootcmd "nand read c000800040000 3c0000;bootm c0008000"

11 使用U-Boot

U-Boot是“Monitor”。除了Bootloader的系统引导功能,它还有用户命令接口,提供了一些复杂的调试、读写内存、烧写Flash、配置环境变量等功能。掌握U-Boot的使用,将极大地方便嵌入式系统的开发。

新开发的电路板没有任何程序可以执行,也就不能启动,需要先将U-Boot烧写到Flash中。

如果主板上的EPROM或者Flash能够取下来,就可以通过编程器烧写。例如,计算机BIOS就存储在一块256KB的Flash上,通过插座与主板连接。但是多数嵌入式单板使用贴片的Flash,不能取下来烧写。这种情况可以通过处理器的调试接口,直接对板上的Flash编程。

处理器调试接口是为处理器芯片设计的标准调试接口,包含BDM、JTAG和EJTAG 3种接口标准。JTAG接口在第4章已经介绍过;BDM(Background Debug Mode)主要应用在PowerPC8xx系列处理器上;EJTAG主要应用在MIPS处理器上。这3种硬件接口标准定义有所不同,但是功能基本相同,下面都统称为JTAG接口。

JTAG接口需要专用的硬件工具来连接,无论从功能、性能角度,还是从价格角度,这些工具都有很大差异。最简单方式就是通过JTAG电缆,转接到计算机并口连接。这需要在主机端开发烧写程序,还需要有并口设备驱动程序。开发板加电或者复位的时候,烧写程序探测到处理器并且开始通信,然后把Bootloader下载并烧写到Flash中。这种方式速率很慢,可是价格非常便宜。一般来说,平均每秒钟可以烧写100~200个字节。烧写完成后,复位实验板,串口终端应该显示U-Boot的启动信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/844753.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Hystrix技术指南】(5)Command创建和执行实现

创建流程 构建HystrixCommand或者HystrixObservableCommand对象 *使用Hystrix的第一步是创建一个HystrixCommand或者HystrixObservableCommand对象来表示你需要发给依赖服务的请求。 若只期望依赖服务每次返回单一的回应&#xff0c;按如下方式构造一个HystrixCommand即可&a…

FreeRTOS(vTaskList与vTaskGetRunTimeStats)

目录 1、Cube配置 ①配置SYS ②配置TIM3 ③配置USART2 ④配置FreeRTOS ⑤配置中断优先级 2、代码添加改动 ①在main函数合适位置开启TIM3中断 ②修改HAL_TIM_PeriodElapsedCallback函数 ③完善两个相关函数 ④vTaskList与vTaskGetRunTimeStats的使用 vTaskList&#xff…

C# 有效的字母异位词

242 有效的字母异位词 给定两个字符串 和 &#xff0c;编写一个函数来判断 是否是 的字母异位词。stts 注意&#xff1a;若 和 中每个字符出现的次数都相同&#xff0c;则称 和 互为字母异位词。stst 示例 1: 输入: s “anagram”, t “nagaram” 输出: true 示例 2: 输…

探索国产嵌入式Python解决方案的方法(开源)

大家好&#xff0c;今天我们要介绍一款适用于单片机的嵌入式Python开源项目 -- PikaPython。 第一&#xff1a;嵌入式Python的发展趋势 在嵌入式领域软硬件的发展趋势中&#xff0c;硬件的成本日益降低&#xff0c;性能逐渐提升。这种趋势使得Python在芯片上的运行难度已经大大…

学python的心得体会1000字,学python的心得体会2000字

这篇文章主要介绍了学python的心得体会2000字&#xff0c;具有一定借鉴价值&#xff0c;需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获&#xff0c;下面让小编带着大家一起了解一下。 1. 初学者应该从简单的练习开始&#xff0c;先掌握基本的语法和概念&#xff0c;…

Three.js光源

目录 Three.js入门 Three.js光源 本文我们将研究three.js中的灯光类型和JavaScript中的光源&#xff0c;探索不同的光源设置。我们的目标是全面理解光源设置和类型&#xff0c;比如环境光、半球光、矩形光、方向光、点光源和聚光灯。我们将逐个介绍它们的属性参数和使用方法…

深度相机常见技术:深度相机的相位求解

1.1 深度相机概述 深度相机是近几年兴起的新技术&#xff0c;相比较传统的相机&#xff0c;深度相机在功能上添加了一个深度测量&#xff0c;从而更方便准确的感知周围的环境及变化。深度相机有很多的应用场景&#xff0c;在我们日常生活中有很多深度相机的影子。那它可以用来…

在线对对联

对对联的起源可以追溯到中国古代,它与中国文化有着密切的关系。 1. 最早的对对联出现在汉朝,当时称为“对句”。它起源于民间,后来逐渐成为文人雅士的精神寄托。 2. 唐代时,对对联的格式更加规范,并被称为“春联”。它成为春节张贴的主要内容,寓意吉祥。 3. 宋代以后,对对联…

小程序商品如何开启返佣

​越来越多的商家开始意识到小程序平台的潜力和价值。开启分销返佣机制是一种有效的方式&#xff0c;可以吸引更多的用户和合作伙伴&#xff0c;提高销售额和品牌知名度。下面将介绍商城小程序商品如何开启返佣。 1. 开启系统分销设置。在管理员后台->营销设置 点击分销功能…

MYSQL --Austindatabases 历年文章合集

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

高速过孔同进同出后续来了!影响大不大由你们自己说

高速先生成员---黄刚 话说Chris在上篇文章的结尾留下的悬念&#xff0c;其实在上周的答题里&#xff0c;也有不少粉丝猜到了接下来要验证的内容。我们知道&#xff0c;任何两个结构如果距离变近了&#xff0c;容性就会增加&#xff0c;无论是孔和孔&#xff0c;线和线&#xf…

JMeter命令行执行+生成HTML报告

1、为什么用命令行模式 使用GUI方式启动jmeter&#xff0c;运行线程较多的测试时&#xff0c;会造成内存和CPU的大量消耗&#xff0c;导致客户机卡死&#xff1b; 所以一般采用的方式是在GUI模式下调整测试脚本&#xff0c;再用命令行模式执行&#xff1b; 命令行方式支持在…

MySQL(1)

MySQL创建数据库和创建数据表 创建数据库 1. 连接 MySQL mysql -u root -p 2. 查看当前的数据库 show databases; 3. 创建数据库 create database 数据库名; 创建数据库 4. 创建数据库时设置字符编码 create database 数据库名 character set utf8; 5. 查看和显示…

redis入门2-命令

Redis的基本数据类型 redis的基本数据类型&#xff08;value&#xff09;: string,普通字符串 hash&#xff08;哈希&#xff09;,适合存储对象 list(列表),按照插入顺序排序&#xff0c;可以由重复的元素 set(无序集合)&#xff0c;没有重复的元素 sorted set(有序集合)&…

Spring Boot + Vue3前后端分离实战wiki知识库系统十一--文档管理功能开发三

文档内容的显示&#xff1a; 在上一次https://www.cnblogs.com/webor2006/p/17510360.html文档管理模块还差文档的显示木有完成&#xff0c;所以接下来先将这块模块给收尾了。 增加单独获取内容的接口&#xff1a; 概述&#xff1a; 在前端页面文档查询时&#xff0c;只查询了文…

【开源项目--稻草】Day05

【开源项目--稻草】Day05 1. 显示问题列表1.1 显示问题持续时间 2. 显示问题的标签列表2.1 实现过程 3.显示问题的图片4. 实现分页功能4.1 为什么需要分页(翻页)4.2 实现分页的sql语句4.3 PageHelper的使用控制器的调用和VUE代码的重构分页导航条的配置 1. 显示问题列表 上次课…

LISA:通过大语言模型进行推理分割

论文&#xff1a;https://arxiv.org/pdf/2308.00692 代码&#xff1a;GitHub - dvlab-research/LISA 摘要 尽管感知系统近年来取得了显著的进步&#xff0c;但在执行视觉识别任务之前&#xff0c;它们仍然依赖于明确的人类指令来识别目标物体或类别。这样的系统缺乏主动推理…

csdn崩溃了?每次都卡

反馈给了官方客服也没有响应&#xff0c;最近几周都是这样的高频率的转圈圈&#xff01;这个入口不受重视&#xff1f;这个对于csdn用户来说&#xff0c;是最最基本的入口 如果CSDN&#xff08;CSDN.net&#xff09;崩溃了&#xff0c;可能会对以下方面产生影响&#xff1a; 开…

【perl】报错合集

perl报错合集 &#xff08;注&#xff1a;可能会不定时更新&#xff09; 1.Name “main::x” used only once: possible typo at … 1.Name "main::x" used only once: possible typo at ...给某个变量赋值但是从来没有用它&#xff0c;或者变量之只用一次但没有…

前沿分享-100 μAh 微型电池

这是SMD 组件形状的固态锂离子微型电池&#xff0c;容量高达 100Ah&#xff0c;在22年的慕尼黑电子展上出现过。 因为是可重复使用的&#xff0c;未来该产品甚至有机会取代容量更高&#xff08;例如100 Ah 时&#xff09;的不可充电硬币电池。 一般应用于超低功率的传感器&…