周立功lpc21xx/lpc22xx系列ARM7启动代码分析

只是有点经验, 仅是一家之言

 

<span style="color:#000000">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
NoInt       EQU 0x80

USR32Mode   EQU 0x10
SVC32Mode   EQU 0x13
SYS32Mode   EQU 0x1f
IRQ32Mode   EQU 0x12
FIQ32Mode   EQU 0x11</span>


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

 

<span style="color:#000000">IMPORT __use_no_semihosting_swi</span>

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

 

<span style="color:#000000">IMPORT  FIQ_Exception                   
IMPORT  __main                             
IMPORT  TargetResetInit</span>

是把要引入的外部标号声明一下,以便下面使用.

 

<span style="color:#000000">EXPORT  bottom_of_heap
EXPORT  StackUsr
EXPORT  Reset
EXPORT __user_initial_stackheap</span>

把要给其它文件使用的标号声明

 

<span style="color:#000000">AREA    vectors,CODE,READONLY

        ENTRY</span>

声明汇编文件的入口, 整个文件是从这里开始执行的.

 

<span style="color:#000000">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</span>


上面几行是配置中断向量表. 中断向量表的顺序是不能变的,这是ARM规定的,可以参考相关书籍. 有几点要说明一下:

第一, 关于DCD     0xb9205f80, 按照ARM7的中断向量表分布图, 这个位置是个保留位. 但是究竟为什么要用0xb9205f80这个数值呢?

 

根据周立功的说法, nxp系列的lpc21xx,lpc22xx片子要求"中断向量表中所有数据32位累加和为0,否则程序不能脱机运行", 我在AXD反汇编了一下(如下图),把中断向量表中的8个机器码累加了一下:0xe59ff018*6+0xe51ffff0+0xb9205f80,没错, 结果是零. 但是我遇到一个问题, 就是我把0xb9205f80这个数值改成任何值,程序运行都没问题. 头大了, 这个问题待解决中……(希望高手看到了可以指点一二).

 

第二, 关于LDR     PC, [PC, #-0xff0]. 这里本应该放IRQ中断的, 为什么是这么一句话. 其实在我blog的其中一篇文章里有提到过这一点:

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

 

<span style="color:#000000">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</span>


这几行是为上面中断向量表中的中断标号分配内存空间, 也就是它们的执行地址. 一开始我有个疑问, 为什么不直接用LDR     PC, ResetInit,还要用DCD中转一下, 后来上网查了一下,才恍然大悟, ldr指令中的地址必须为当前指令地址是4KB范围内, 用DCD中转一下就可以在整个程序空间寻址。

 

<span style="color:#000000">Undefined

        B       Undefined
SoftwareInterrupt                     
        B       SoftwareInterrupt  

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</span>

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

 

<span style="color:#000000">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</span>


上面是一个子函数, 函数名为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,注意下面几句

<span style="color:#000000">StackSvc           DCD     SvcStackSpace + (SVC_STACK_LEGTH - 1)* 4
StackIrq           DCD     IrqStackSpace + (IRQ_STACK_LEGTH - 1)* 4
StackFiq           DCD     FiqStackSpace + (FIQ_STACK_LEGTH - 1)* 4
StackAbt           DCD     AbtStackSpace + (ABT_STACK_LEGTH - 1)* 4
StackUnd           DCD     UndtStackSpace + (UND_STACK_LEGTH - 1)* 4</span>


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

<span style="color:#000000">//startup.s文件

AREA    Stacks, DATA, NOINIT
StackUsr

//分散加载文件

STACKS 0x40002000 UNINIT
{
        Startup.o (Stacks)
}</span>


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

 

ResetInit

        BL      InitStack
        BL      TargetResetInit
        B       __main

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

 

<span style="color:#000000">__user_initial_stackheap   

    LDR   r0,=bottom_of_heap
    LDR   r1,=StackUsr
     MOV   pc,lr</span>

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

 

 

<span style="color:#000000">StackSvc           DCD     SvcStackSpace + (SVC_STACK_LEGTH - 1)* 4
StackIrq           DCD     IrqStackSpace + (IRQ_STACK_LEGTH - 1)* 4
StackFiq           DCD     FiqStackSpace + (FIQ_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</span>

上面几行代码是为各个模式下的堆栈分配空间. 其中MyStacksA的位置会在分散加载文件中指定.

 

<span style="color:#000000">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</span>


上面这几行其实是加密芯片用的, 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时,芯片即被加密.

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值