跳至主要内容

【转】基于ARM的嵌入式系统Bootloader启动流程分析

基于ARM的嵌入式系统Bootloader启动流程分析

减小字体 增大字体 作者:未知  来源:本站整理  发布时间:2008-12-13

  摘要:讲述了基于 ARM处理器的嵌入式系统在上电启动后应用程序或操作系统运行前,对处理器及其内部功能模块进行初始化的过程,并结合经过实际验证的代码详细的分析了S3C44B0 Bootloader的运行过程。

  关键字:ARM 嵌入式系统 Bootloader

  一. 引言:

  对于 PC 机,其开机后的初始化处理器配置、硬件初始化等操作是由 BIOS(Basic Input /Output System)完成的,但对于嵌入式系统来说,出于经济性、价格方面的考虑一般不配置 BIOS,因此我们必须自行编写完成这些工作的程序,这就是所需要的开机程序。而在嵌入式系统中,通常并没有像 BIOS 那样的固件程序,启动时用于完成初始化操作的这段代码被称为 Bootloader 程序,因此整个系统的加载启动任务就完全由 Bootloader 来完成。简单地说,通过这段程序,可以初始化硬件设备、建立内存空间的映射图(有的 CPU 没有内存映射功能如 S3C44B0),从而将系统的软硬件环境设定在一个合适的状态,以便为最终调用操作系统内核、运行用户应用程序准备好正确的环境。Bootloader依赖于实际的硬件和应用环境,因此要为嵌入式系统建立一个通用、标准的 Bootloader 是非常困难的。Bootloader也依赖于具体的嵌入式板级设备的配置,这也就是说,对于两块不同的嵌入式主板而言,即使它们是基于同一 CPU 而构建,要想让运行在一块板子上的 Bootloader 程序也能运行在另一块板子上,通常都需要修改 Bootloader 的源程序。

  二. 启动流程

  系统加电复位后,几乎所有的 CPU都从由复位地址上取指令。比如,基于 ARM7TDMI内核的 CPU 在复位时通常都从地址 0x00000000 处取它的第一条指令。而以微处理器为核心的嵌入式系统通常都有某种类型的固态存储设备(比如 EEPROM、FLASH等)被映射到这个预先设置好的地址上。因此在系统加电复位后,处理器将首先执行存放在复位地址处的程序。通过集成开发环境可以将 Bootloader 定位在复位地址开始的存储空间内,因此Bootloader是系统加电后、操作系统内核或用户应用程序运行之前,首先必须运行的一段程序代码。对于嵌入式系统来说,有的使用操作系统,也有的不使用操作系统,比如功能简单仅包括应用程序的系统,但在系统启动时都必须执行 Bootloader,为系统运行准备好软硬件运行环境。

  系统的启动通常有两种方式,一种是可以直接从 Flash 启动,另一种是可以将压缩的内存映像文件从 Flash(为节省 Flash 资源、提高速度)中复制、解压到 RAM,再从 RAM 启动。当电源打开时,一般的系统会去执行 ROM(应用较多的是 Flash)里面的启动代码。这些代码是用汇编语言编写的,其主要作用在于初始化 CPU 和板上的必备硬件如内存、中断控制器等。有时候用户还必须根据自己板子的硬件资源情况做适当的调整与修改。

  系统启动代码完成基本软硬件环境初始化后,对于有操作系统的情况下,启动操作系统、启动内存管理、任务调度、加载驱动程序等,最后执行应用程序或等待用户命令;对于没有操作系统的系统直接执行应用程序或等待用户命令。

  

  启动代码是用来初始化电路以及用来为高级语言写的软件做好运行前准备的一小段汇编语言,在商业实时操作系统中,启动代码部分一般被称为板级支持包,英文缩写为 BSP。它的主要功能就是:电路初始化和为高级语言编写的软件运行做准备。系统启动流程如图 1所示,主要的过程如下:

  1. 启动代码的第一步是设置中断和异常向量。

  2. 完成系统启动所必须的最小配置,某些处理器芯片包含一个或几个全局寄存器,这些寄存器必须在系统启动的最初进行配置。

  3. 设置看门狗,用户设计的部分外围电路如果必须在系统启动时初始化,就可以放在这一步。

  4. 配置系统所使用的存储器,包括 Flash,SRAM 和DRAM 等,并为他们分配地址空间。如果系统使用了 DRAM 或其它外设,就需要设置相关的寄存器,以确定其刷新频率,数据总线宽度等信息,初始化存储器系统。有些芯片可通过寄存器编程初始化存储器系统,而对于较复杂系统通常集成有 MMU 来管理内存空间。

  5. 为处理器的每个工作模式设置栈指针,ARM 处理器有多种工作模式,每种工作模式都需要设置单独的栈空间。

  6. 变量初始化,这里的变量指的是在软件中定义的已经赋好初值的全局变量,启动过程中需要将这部分变量从只读区域,也就是 Flash拷贝到读写区域中,因为这部分变量的值在软件运行时有可能重新赋值。还有一种变量不需要处理,就是已经赋好初值的静态全局变量,这部分变量在软件运行过程中不会改变,因此可以直接固化在只读的 Flash或 EEPROM中。

  7. 数据区准备,对于软件中所有未赋初值的全局变量,启动过程中需要将这部分变量所在区域全部清零。

  8. 最后一步是调用高级语言入口函数,比如 main函数等。

  三. 程序分析

  下面根据实际经过测试的代码详细讲述系统的启动过程。

  .text /*将此操作符开始的代码编译到代码段或代码段子段中*/

  /* 集成开发环境(IDE)可以通过链接脚本文件将下面的语句定位在零起始地址,系统上电后 CPU从此处开始执行*/

  ENTRY:

  b ResetHandler /*跳至 ResetHandler,此句被定位在零起始地址*/

  /*除用户模式外的其他 6种模式称为特权模式。特权操作模式主要处理异常和监控调用(有时称为软件中断),它们可以自由的访问系统资源和改变模式。特权模式中除系统模式以外的 5 种模式又称为异常模式,下面的代码用于出现异常时 CPU 就会根据以下的语句自动跳转到对应的异常处理程序处*/

  b HandlerUndef /* handlerUndef */

  b HandlerSWI /* SWI interrupt handler */

  b HandlerPabort /* handlerPAbort */

  b HandlerDabort /* handlerDAbort */

  b . /* handlerReserved */

  b HandlerIRQ

  b HandlerFIQ

  … ...

  … ...

  ResetHandler: /*上电后跳转到此处开始执行*/

  Ldr r0,=WTCON /*禁止看门狗*/

  ldr r1,=0x0

  str r1,[r0]

  ldr r0,=INTMSK /*屏蔽所有中断请求 */

  ldr r1,=0x07ffffff

  str r1,[r0]

  /*设置时钟控制寄存器*/

  ldr r0,=LOCKTIME

  ldr r1,=0xfff

  str r1,[r0]

  .if PLLONSTART

  ldr r0,=PLLCON /* 设置 PLL */

  ldr r1,=((M_DIV<<12)+(P_DIV<<4)+S_DIV) /*Fin=8MHz,Fout=64MHz*/

  str r1,[r0]

  .endif

  ldr r0,=CLKCON

  ldr r1,=0x7ff8 /*所有单元时钟允许*/

  str r1,[r0]

  /*为 BDMA设置复位值*/

  ldr r0,=BDIDES0

  ldr r1,=0x40000000 /* BDIDESn 复位值应为 0x40000000 */

  str r1,[r0]

  ldr r0,=BDIDES1

  ldr r1,=0x40000000 /* BDIDESn 复位值应为 0x40000000 */

  str r1,[r0]

  /*设置存储器控制寄存器,存储器的配置数据都存储在 SMRDATA为起始地址的数据表中,下面的代码可以一次将预先配置好的初始化数据存入与存储器控制器相关的 13 个寄存器,这些寄存器则是以 0x01c80000 为起始地址的 13 个连续的 32 位寄存器*/

  ldr r0,=SMRDATA

  ldmia r0,{r1-r13}

  ldr r0,=0x01c80000 /* BWSCON存储控制寄存器地址 */

  stmia r0,{r1-r13}

  /*初始化堆栈*/

  /* CPU复位后是处于管理模式下的,所以首先要初始化管理模式下的堆栈寄存器*/

  ldr sp, =SVCStack

  /*由于处理器的每种运行模式都要有自己独立的物理堆栈寄存器 R13,在用户应用程序的初始化部分,一般都要初始化每种模式下的 R13,使其指向该运行模式的栈空间,这样,当程序的运行进入异常模式时,可以将需要保护的寄存器放入 R13 所指向的堆栈,而当程序从异常模式返回时,则从对应的堆栈中恢复,采用这种方式可以保证异常发生后程序的正常执行*/

  bl InitStacks /*跳转至其它堆栈初始化程序并返回*/

  /*设置 IRQ中断处理*/

  /*44B0 有两种中断模式:一种是没有中断向量表;一种是使用了中断向量表,使用中断向量表只能是 IRQ方式。当使用中断向量表的时候,中断发生时由 S3C44B0 的中断控制器根据中断向量表,利用硬件方式自动跳转到相应的中断处理服务程序所在的位置;不使用中断向量表时按下面的代码,利用软件方式跳转而进行中断处理,因为 S3C44B0 有30 个中断源,所以需要程序判断以确定调用那个中断服务程序*/

  ldr r0,=HandleIRQ /*如果在0x18 和0x1c 地址处无"subs pc,lr,#4"*/

  ldr r1,=IsrIRQ /*为了中断正常返回这些语句是必须的 */

  str r1,[r0]

  /*拷贝读写区域数据/数据区准备,将系统需要读写的数据和变量从 ROM拷贝到 RAM里。Image_RO_Limit、Image_RW_Base、Image_ZI_Base等这些符号还会在另外的链接脚本文件中出现,这些符号是用来定位程序各个段的参考信息。集成开发环境在编译链接的时候会根据我们编写的程序,把它们转换成用来对各个段定位的地址信息*/

  LDR r0, =Image_RO_Limit /*取只读数据区域地址指针*/

  LDR r1, =Image_RW_Base /*准备执行拷贝操作*/

  LDR r3, =Image_ZI_Base

  CMP r0, r1 /*检查是否相同*/

  BEQ F1 /*相同则跳过拷贝操作*/

  F0:

  CMP r1, r3 /*执行拷贝操作*/

  LDRCC r2, [r0], #4

  STRCC r2, [r1], #4

  BCC F0

  F1:

  LDR r1, =Image_ZI_Base /*零数据准备区起始地址*/

  MOV r2, #0

  F2:

  CMP r3, r1 /*执行数据区清零*/

  STRCC r2, [r3], #4

  BCC F2

  MRS r0, CPSR

  BIC r0, r0, #NOINT /*中断请求允许*/

  MSR CPSR_cxsf, r0

  /* 跳转到 C入口程序 */

  BL Main

  B.

  四. 总结:

  启动过程中的初始化程序就是初始化 CPU 内部各个关键的寄存器、配置外围硬件电路相关寄存器、建立中断向量表等,然后跳转到一般由高级语言编写的主函数的应用程序代码去执行,这样就可以利用高级语言来编写完成系统设计所要求的各种功能。初始化的过程对大多数初学者来说,比较难理解的是中断的处理和一些少见的操作符号,这些符号多是一些宏定义或系统用于在内存空间中对各个段的定位标识符号。掌握了 S3C44B0 的启动代码之后,对系统功能程序设计会起到很大的帮助,是进行下一步程序设计的基础。

  五. 参考文献:

  1. 田泽.嵌入式系统开发与应用.北京.北京航空航天大学出版社.2005

  2. 田泽.嵌入式系统开发与应用实验教程.北京.北京航空航天大学出版社.2004

  3. 深圳英蓓特信息技术有限公司.Embest ARM 实验教学系统用户手册.Version 2.01.2003

  4. SAMSUNG 公司.S3C44B0_datasheet.pdf.

  cnidz.com 编辑 2008年

评论

此博客中的热门博文

【转】AMBA、AHB、APB总线简介

AMBA 简介 随着深亚微米工艺技术日益成熟,集成电路芯片的规模越来越大。数字IC从基于时序驱动的设计方法,发展到基于IP复用的设计方法,并在SOC设计中得到了广泛应用。在基于IP复用的SoC设计中,片上总线设计是最关键的问题。为此,业界出现了很多片上总线标准。其中,由ARM公司推出的AMBA片上总线受到了广大IP开发商和SoC系统集成者的青睐,已成为一种流行的工业标准片上结构。AMBA规范主要包括了AHB(Advanced High performance Bus)系统总线和APB(Advanced Peripheral Bus)外围总线。   AMBA 片上总线        AMBA 2.0 规范包括四个部分:AHB、ASB、APB和Test Methodology。AHB的相互连接采用了传统的带有主模块和从模块的共享总线,接口与互连功能分离,这对芯片上模块之间的互连具有重要意义。AMBA已不仅是一种总线,更是一种带有接口模块的互连体系。下面将简要介绍比较重要的AHB和APB总线。 基于 AMBA 的片上系统        一个典型的基于AMBA总线的系统框图如图3所示。        大多数挂在总线上的模块(包括处理器)只是单一属性的功能模块:主模块或者从模块。主模块是向从模块发出读写操作的模块,如CPU,DSP等;从模块是接受命令并做出反应的模块,如片上的RAM,AHB/APB 桥等。另外,还有一些模块同时具有两种属性,例如直接存储器存取(DMA)在被编程时是从模块,但在系统读传输数据时必须是主模块。如果总线上存在多个主模块,就需要仲裁器来决定如何控制各种主模块对总线的访问。虽然仲裁规范是AMBA总线规范中的一部分,但具体使用的算法由RTL设计工程师决定,其中两个最常用的算法是固定优先级算法和循环制算法。AHB总线上最多可以有16个主模块和任意多个从模块,如果主模块数目大于16,则需再加一层结构(具体参阅ARM公司推出的Multi-layer AHB规范)。APB 桥既是APB总线上唯一的主模块,也是AHB系统总线上的从模块。其主要功能是锁存来自AHB系统总...

【转】vector成员函数

函数 表述 c.assign(beg,end) c.assign(n,elem) 将 [beg; end) 区间中的数据赋值给 c 。 将 n 个 elem 的拷贝赋值给 c 。 c.at (idx) 传回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range 。 c.back() 传回最后一个数据,不检查这个数据是否存在。 c.begin() 传回迭代器重的可一个数据。 c.capacity() 返回容器中数据个数。 c.clear() 移除容器中所有数据。 c.empty() 判断容器是否为空。 c.end() 指向迭代器中的最后一个数据地址。 c.erase(pos) c.erase(beg,end) 删除 pos 位置的数据,传回下一个数据的位置。 删除 [beg,end) 区间的数据,传回下一个数据的位置 。 c.front() 传回地一个数据。 get_allocator 使用构造函数返回一个拷贝。 c.insert(pos,elem) c.insert(pos,n,elem) c.insert(pos,beg,end) 在 pos 位置插入一个 elem 拷贝,传回新数据位置。 在 pos 位置插入 n 个 elem 数据。无返回值。 在 pos 位置插入在 [beg,end) 区间的数据。无返回值。 c.max_size() 返回容器中最大数据的数量。 c.pop_back() 删除最后一个数据。 c.push_back(elem) 在尾部加入一个数据。 c.rbegin() 传回一个逆向队列的第一个数据。 c.rend() 传回一个逆向队列的最后一个数据的下一个位置。 c.resize(num) 重新指定队列的长度。 c.reserve() 保留适当的容量。 c.size() 返回容器中实际数据的个数。 c1.swap(c2) swap(c1,c2) 将 c1 和 c2 元素互换。 ...

【转】VxWorks套接口

int m_socket;   // Open a socket        m_socket = socket(AF_INET, SOCK_STREAM, 0);   第一个参数 domain 说明我们网络程序所在的主机采用的通讯协族 (AF_UNIX 和 AF_INET 等 ). AF_UNIX 只能够用于单一的 Unix 系统进程间通信 , 而 AF_INET 是针对 Internet 的 , 因而可以允许在远程主机之间通信 . VxWorks 套接字仅支持 Internet 域地址族 , 不支持 UNIX 域地址族 . 因此在需要 domain 参数的函数中 , 使用 AF_INET 作为函数参数值 . 第二个参数 type 说明我们网络程序所采用的通讯协议 ( SOCK_STREAM , SOCK_DGRAM 等 ). SOCK_STREAM 表明我们用的是 TCP 协议 , 这样会提供按顺序的 , 可靠 , 双向 , 面向连接的比特流 . SOCK_DGRAM  表明我们用的是 UDP 协议 , 这样只会提供定长的 , 不可靠 , 无连接的通信 . 此外,还有 SOCK_RAW 代表是原始协议套接字 . 第三个参数 protocol, 由于我们指定了 type, 所以这个地方我们一般只要用 0 来代替就可以了 . socket 为网络通讯做基本的准备 , 成功打开则返回一个套接字描述符 , 如果失败则返回 ERROR. 套接字描述符是一个标准的 I/O 系统文件描述符 (fd, file descriptor), 可以被 close(), read(), write() 和 ioctl() 函数使用 .   // Make the socket sending alive messages when connected int flag = 1; setsockopt(m_socket, SOL_SOCKET, SO_KEEPALIVE, (char*)&flag, sizeof(flag));   // increase receive buffer siz...