嵌入式启动代码的分析

嵌入式启动代码的分析
嵌入式启动代码的分析

LPC21xx启动代码的分析

注:下文中红色标记的部分是源码,黑色部分是解释文字

让我们从头开始………………

以下定义了几个常数,这些常数定义了五种模式下堆栈的大小以及各种模式对应的状态字,所谓的状态字是CPSR寄存器的低5位,处理器根据该值确定状态或者切换状态。

每种模式都对应独立的堆栈

SVC_STACK_LEGTH EQU 0

FIQ_STACK_LEGTH EQU 0

IRQ_STACK_LEGTH EQU 256

ABT_STACK_LEGTH EQU 0

UND_STACK_LEGTH EQU 0

定义向量中断IRQ以及快速中断掩码

CPSR寄存器结构:N Z C V…………I F T M4 M3 M2 M1 M0

NoInt EQU 0x80 X X X X…………1 0 X X X X X X

NoFIQ EQU 0x40 X X X X…………0 1 X X X X X X

定义各种工作模式的状态字

CPSR寄存器结构:N Z C V…………I F T M4 M3 M2 M1 M0

USR32Mode EQU 0x10 X X X X…………X X X 1 0 0 0 0

SVC32Mode EQU 0x13 X X X X…………X X X 1 0 0 1 1

SYS32Mode EQU 0x1f X X X X…………X X X 1 1 1 1 1

IRQ32Mode EQU 0x12

FIQ32Mode EQU 0x11

上面几行代码,不用过多分析, 定义几个符号而已, 把EQU想像成C中的#define就可以了. 具体定义的数值,下面的代码用到我再解释.

IMPORT __use_no_semihosting_swi

上面这一句的作用是在代码中禁用semihosting 机制. 到底什么是semihostiong这里不多说, 网上有很多. 这里只说明Semihosting主要用来调试, 在release版本的代码中一般是要禁用的.

IMPORT FIQ_Exception

IMPORT __main

IMPORT TargetResetInit

上面三行是把要引入的外部标号声明一下,以便下面使用,这些标号可以是其他C文件里的,也可以是其他的汇编文件里的。

EXPORT bottom_of_heap

EXPORT StackUsr

EXPORT Reset

EXPORT __user_initial_stackheap

上面四行是把本文件里面的函数或者变量输出为外部标号,给其他文件里程序使用。

AREA vectors,CODE,READONLY

ENTRY

上面这一行声明汇编文件的入口, 整个文件是从这里开始执行的,一个文件里可以有多个ENTRY,该标号只是表示这是该程序段的入口,至于哪个ENTRY作为整个程序的入口,应该在程序编译的时候指定。每个入口都有一个名字,改名字可以是任意合法的字符串。

Reset

LDR PC, ResetAddr

LDR PC, UndefinedAddr

LDR PC, SWI_Addr

LDR PC, PrefetchAddr

LDR PC, DataAbortAddr

DCD 0xb9205f80 该处放置确定数字,不必改动

LDR PC, [PC, #-0xff0]

LDR PC, FIQ_Addr

上面几行是配置中断向量表. 中断向量表的顺序是不能变的,因为这是ARM7规定的,可以参考相关书籍. 这里有几个问题要说明一下.

第一, 关于DCD 0xb9205f80, 按照ARM7的中断向量表分布图, 这个位置是个保留位. nxp系列的lpc21xx,lpc22xx片子要求"中断向量表中所有数据32位累加和为0,否则程序不能脱机运行"。

第二, 关于LDR PC, [PC, #-0xff0],这里本应该放IRQ中断的,该指令向量中断地址寄存器的地址(这个问题可以放在中断部分解决)。

注:ARM7的三级流水线结构导致了PC指向的是当前指令的后8个字节. 本来IRQ是应该放在0x00000018处的. LDR PC, [PC, #-0xff0]这条语句执行后, PC的当前值就是0x00000018+8-0xff0. 很容易计算出它的结果是0xfffff030. 看一下lpc22xx的手册就知道. 这个地址就是VICVectAddr. 也就是说本来这个地址是应该放IRQ服务程序的入口地址的,但是这个地址被放在了VICVectAddr 这个寄存器里. 英文手册里有一段对VICVectAddr 描述. 看了之后就容易明白是怎么回事了: Vector Address Register. When an IRQ interrupt occurs, the IRQ service routine can read this register and jump to the value read

ResetAddr DCD ResetInit

UndefinedAddr DCD Undefined

SWI_Addr DCD SoftwareInterrupt

PrefetchAddr DCD PrefetchAbort

DataAbortAddr DCD DataAbort

Nouse DCD 0

IRQ_Addr DCD 0

FIQ_Addr DCD FIQ_Handler

这几行是为上面中断向量表中的中断标号分配内存空间, 也就是它们的执行地址. 为什么不直接用LDR PC, ResetInit,还要用DCD中转一下?

因为ldr指令中的地址必须为当前指令地址是4KB范围内(一条32bit的指令既要表示操作码,还有状态码,剩下的用来表示数字或者地址的bit有限,只能够表示4k空间), 用DCD中转一下就可以在整个程序空间寻址。因为DCD定义的是一个纯32bit数据或者地址,可以表示4个G的空间大小,也就意味着中断处理程序可以放在存储器中的任何一个位置。

Undefined

B Undefined

SoftwareInterrupt ;该部分以后在软件中断部分单独讲解

CMP R0, #4 ;散转向参数存放在R0中,判断是否超过4,散转表只有四项LDRLO PC, [PC, R0, LSL #2] ;每项为4个字节

MOVS PC, LR ;跳转

SwiFunction 散转表

DCD IRQDisable0 ;0

DCD IRQEnable0 ;1

DCD FIQDisable0 ;2

DCD FIQEnable0 ;3

SwiFunction的标号下面完成的程序散转,你要在SoftwareInterrupt 入口处检查swi的中断号,并将SwiFunction的地址赋值给R0。

相关宏定义位置请参考target.h里面的宏定义,target文件里的IRQEnable等函数名称与此处的IRQEnable标号无直接关系,可以不相同。

IRQDisable0

;关IRQ中断

MRS R0, SPSR

ORR R0, R0, #NoInt

MSR SPSR_c, R0

MOVS PC, LR

IRQEnable0

;开IRQ中断

MRS R0, SPSR

BIC R0, R0, #NoInt

MSR SPSR_c, R0

MOVS PC, LR

FIQDisable0

;关FIQ中断

MRS R0, SPSR

ORR R0, R0, #NoFIQ

MSR SPSR_c, R0

MOVS PC, LR

FIQEnable0

;开FIQ中断

MRS R0, SPSR

BIC R0, R0, #NoFIQ

MSR SPSR_c, R0

MOVS PC, LR

PrefetchAbort

B PrefetchAbort ;空调转DataAbort

B DataAbort

FIQ_Handler

STMFD SP!, {R0-R3, LR}

BL FIQ_Exception

LDMFD SP!, {R0-R3, LR}

SUBS PC, LR, #4

这几行不用过多解释, 只是说明上面几个异常如何执行. InitStack

MOV R0, LR

;设置管理模式堆栈

MSR CPSR_c, #0xd3

LDR SP, StackSvc

;设置中断模式堆栈

MSR CPSR_c, #0xd2

LDR SP, StackIrq

;设置快速中断模式堆栈

MSR CPSR_c, #0xd1

LDR SP, StackFiq

;设置中止模式堆栈

MSR CPSR_c, #0xd7

LDR SP, StackAbt

;设置未定义模式堆栈

MSR CPSR_c, #0xdb

LDR SP, StackUnd

;设置系统模式堆栈

MSR CPSR_c, #0xdf

LDR SP, =StackUsr

MOV PC, R0

上面是一个子函数, 函数名为InitStack. 顾名思意, 这个函数设置ARM七种工作模式下的堆栈. 关于这一段代码有三点要说.

第一, MSR CPSR_c, #0xdf, 这一句把ARM的工作模式设置为系统模式,或者也可以说是用户模式, 因为系统模式与用户模式是共享相同的寄存器组. 用0xdf对CPSR寄存器赋值,就把IRQ中断关闭了(可以查一下CRSR的详细说明), 代码正常执行时处理器是处在用户模式的,所以IRQ中断是不会执行的. 所以,如果用周立功的这个启动代码,当你的程序中需要中断时,要把0xdf改成0x5f. 之前看到很多人在网上说用周立功的ADS工程模板,进不了中断,很多情况下是这个原因.

第二, 并不是每一种模式下的堆栈都用设置的, 比如说如果你的程序中不会用到FIQ,就可以不用设置快速中断下的堆栈.

第三, 注意LDR SP, =StackUsr这个语句, 其它都是没有=号的, 为什么这个要用等号呢? 这就是LDR伪指令与LDR指令的区别了, LDR SP, =StackUsr是把StackUsr表示的地址装载到sp, LDR SP, StackUnd是把StackUnd表示地址的内容装载到sp,注意下面几句

StackSvc DCD SvcStackSpace + (SVC_STACK_LEGTH - 1)* 4

StackIrq DCD IrqStackSpace + (IRQ_STACK_LEGTH - 1)* 4

StackAbt DCD AbtStackSpace + (ABT_STACK_LEGTH - 1)* 4

StackUnd DCD UndtStackSpace + (UND_STACK_LEGTH - 1)* 4

可以看到,没有”=”的标号都已经用DCD初始化了, 而StackUsr到底是什么呢, 它是由下面的语句决定的

(startup.s文件)

AREA Stacks, DATA, NOINIT

StackUsr

这样就明白了, StackUsr肯定是0x40000000~0x400020000之间的某个数. 用户模式下的堆栈空间就是它了.

ResetInit

BL InitStack

BL TargetResetInit

B __main

处理器上电复位后通过中断向量表进入该函数,__main函数主要工作是初始化C的库函数, 并由它进入C的main函数.

__user_initial_stackheap

LDR r0,=bottom_of_heap

LDR r1,=StackUsr

MOV pc,lr

__user_initial_stackheap函数是ADS的一个库函数, 如果程序中用到的分散加载文件, 这个函数必须要被实现. 应用程序的栈和heap是在C库函数初始化过程中建立起来的。可以通过重定向对应的子程序来改变堆栈和heap的位置. 堆栈的地址在分散加载文件里已经指定好,本函数不应该修改它们的值. 用r0,r1分别返回heap和stack的基址. 关于ADS的存储器机制大家可以去网上查更详细的资料.

StackSvc DCD SvcStackSpace + (SVC_STACK_LEGTH - 1)* 4

StackIrq DCD IrqStackSpace + (IRQ_STACK_LEGTH - 1)* 4

StackAbt DCD AbtStackSpace + (ABT_STACK_LEGTH - 1)* 4

StackUnd DCD UndtStackSpace + (UND_STACK_LEGTH - 1)* 4

AREA MyStacks, DATA, NOINIT, ALIGN=2

SvcStackSpace SPACE SVC_STACK_LEGTH * 4

;Stack spaces for Administration Mode

IrqStackSpace SPACE IRQ_STACK_LEGTH * 4

;Stack spaces for Interrupt ReQuest Mode

FiqStackSpace SPACE FIQ_STACK_LEGTH * 4

;Stack spaces for Fast Interrupt reQuest Mode

AbtStackSpace SPACE ABT_STACK_LEGTH * 4

;Stack spaces for Suspend Mode

UndtStackSpace SPACE UND_STACK_LEGTH * 4

;Stack spaces for Undefined Mode

上面几行代码是为各个模式下的堆栈分配空间.

IF :DEF: EN_CRP

IF . >= 0x1fc

INFO 1,"\nThe data at 0x000001fc must be 0x87654321.\nPlease delete some source before this line."

ENDIF

CrpData

WHILE . < 0x1fc

NOP

WEND

CrpData1

DCD 0x87654321 ;/*When the Data is 为0x87654321,user code be protected. 当此数为0x87654321时,用户程序被保护*/

ENDIF

上面这几行其实是加密芯片用的, lpc21xx和lpc22xx系列的ARM7,当你的工程选择RelInFlash时, 代码写进flash,芯片也同时被加密, 加密状态下JTAG也读不到芯片, 也不能单步调试, 要解密的话必须要用ISP完全擦除一下. 上面的代码的意思就是在地址0x1fc处放数据0x87654321, 从而实现加密的功能, 但前提是IF :DEF: EN_CRP, 也就是定义了EN_CPP这个宏. 而这个宏是在当选择了RelInFlash时ADS自动定义的. 然后,再说一下0x87654321的问题. LPC2100 系列ARM7微控制器是世界首款可加密的ARM芯片,对其加密的方法是通过用户程序在指定地址上设置规定的数据。PHILIPS公司规定,对于LPC2100芯片(除LPC2106/2105/2104外),当片内FLASH地址0x000001FC处的数据为0x87654321时,芯片即被加密. 所以问题搞定.

AREA Heap, DA TA, NOINIT

bottom_of_heap SPACE 1

AREA Stacks, DA TA, NOINIT

StackUsr

END

启动代码部分的结束。

相关主题
相关文档
最新文档