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


    要是早看了下面的两篇文章,呵呵,学习进度应该能快很多吧,文章分享了,大家自己看啊。
    周立功lpc21xx/lpc22xx系列ARM7启动代码分析  摘自搜狐博客技术为王博友
    网上已经有人做了一个周立功lpc2000(ARM7TDMI)启动代码分析的文章, 我本来想做一个s3c2410(ARM920T)的启动代码分析的, 但是看来了一下2410的启动代码,发现有些东西还不是理解的很清楚, 我ARM9的经验比较少.

    所以还是做一个ARM7的启动代码分析吧, 网上那一份相比,我这个主要关注startup.s文件.网上那个startup.s几乎是一笔带过的.

    红色标记的是源码.

    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

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

    IMPORT __use_no_semihosting_swi

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

    IMPORT  FIQ_Exception                   

    IMPORT  __main                             

    IMPORT  TargetResetInit

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

    EXPORT  bottom_of_heap

    EXPORT  StackUsr

    EXPORT  Reset

    EXPORT __user_initial_stackheap

    上面四行是把要给其它文件使用的标号声明

    AREA    vectors,CODE,READONLY

            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的中断向量表分布图, 这个位置是个保留位. 但是究竟为什么要用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

    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范围内, 用DCD中转一下就可以在整个程序空间寻址.

    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

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

    接上一篇

    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

    StackFiq           DCD     FiqStackSpace + (FIQ_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

    (分散加载文件)

    STACKS 0x40002000 UNINIT

     {

            Startup.o (Stacks)

     }

    这样就明白了, 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

    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

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

    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时,芯片即被加密. 所以问题搞定.

    LPC2200开发板启动代码分析

    文件: 周立功LPC2200开发板启动代码分析.pdf
    大小: 634KB
    下载: 下载

     

     

    再转一篇

    ARM周立功模板启动代码中断处理文件IRQ.C的中文解释

    NoInt        EQU  0x80           //禁止IRQ中断
    USR32Mode  EQU  0x10           //用户模式
    SVC32Mode  EQU  0x13           //管理模式
    SYS32Mode  EQU   0x1f          //系统模式
    IRQ32Mode  EQU   0x12          //中断模式
    FIQ32Mode   EQU  0x11          //快速中断模式
    ;引入的外部标号在这声明
    //IMPORT表示引用外部的信息
    IMPORT OSIntCtxSw              ;任务切换函数//引用外部的函数
    IMPORT OSIntExit                ;中断退出函数
    IMPORT OSTCBCur               ;UC/OS II正在运行的任务指针
    IMPORT OSTCBHighRdy          ; UC/OS II任务就绪表中级别最高的优先级
    IMPORT OSIntNesting            ;中断嵌套计数器
    IMPORT StackUsr                ;用户模式堆栈
    IMPORT OsEnterSum             ;开关中断的次数

    CODE32
    AREA IRQ,CODE,READONLY
    MACRO
    $IRQ_Label HANDLER $IRQ_Exception_Function
    EXPORT $IRQ_Label                          ; 输出的标号
    IMPORT $IRQ_Exception_Function               ; 引用的外部标号
    $IRQ_Label
    SUB LR, LR, #4                               ; 计算返回地址
    //进入中断后,它的返回地址该怎么计算呢,可以这样来理解,因为它的指令流水线是3级的,即执行进入中断函数时,PC已经指向欲取值的指令即当前执行的地址+8;当已进入中断时,LR里面装的是PC,所以要想中断返回到正确的地址处,就必须把LR-4。
    STMFD SP!, {R0-R3, R12, LR}                 ; 保存任务环境
    //这里面为什么只把R0-R3,R12,LR保存呢,其它不用吗,是这样的,我们可以从你装的ADS1.2目录下的PDF文件夹里面的ADS_DeveloperGuide_D.PDF文件的2.2就可以发现r4-r11装的是局部变量,在进行函数跳转时,编译器它会自动保护它们的。
    MRS R3, SPSR ; 保存状态
    STMFD SP, {R3, SP, LR}^ ; 保存用户状态的R3,SP,LR,注意不能回写,前面一个SP是IRQ模式的,后面一个SP是用户模式的,为什么不能回写呢,如果你回写的话,那么它保存的是用户的SP,显然是不行的。不知这样理解对不对。这里保存SP和LR的目的是为了嵌套,
    ; 正是因为没有回写,所以后面调整了SP ,调整指令是 SUB SP, SP, #4*3
    LDR R2, =OSIntNesting           ; OSIntNesting++ 中断嵌套数+1 
    ;(相当于调用了一次中断进入函数OSIntEnter(),与后面的BL OSIntExit 形成呼应)
    LDRB R1, [R2]
    ADD R1, R1, #1
    STRB R1, [R2]
    SUB SP, SP, #4*3  ;由于前面SP没有回写,保存了3个32位的寄存器,这里调整指针
                     ;做好弹出这三个数据的准备
    MSR CPSR_c, #(NoInt | SYS32Mode) ; 切换到系统模式。只有切换到系统模式,让后面的服务程序在系统模式下运行,才能实现嵌套。
    CMP R1, #1                       ;判断是否是只有第一次进入中断,还是有嵌套
    LDREQ SP, =StackUsr              ;如果是第一次中断则设定系统模式的堆栈指针

    BL $IRQ_Exception_Function        ; 调用c语言的中断处理程序
    MSR CPSR_c, #(NoInt | SYS32Mode) ; 切换到系统模式。做好中断退出的准备

    LDR R2, =OsEnterSum          ; OsEnterSum,使OSIntExit退出时中断关闭
    MOV R1, #1                   ;相当于调用了OS_ENTER_CRITICAL();
    STR R1, [R2]
                                  
    BL OSIntExit                  ;调用UC/OS的中断退出函数  OSIntNesting--
    ; 如果中断嵌套数不等于0 则不进行任务调度
    LDR R2, =OsEnterSum          ; 因为中断服务程序要退出,所以OsEnterSum=0
    MOV R1, #0                   ; 相当于调用了OS_EXIT_CRITICAL()
    STR R1, [R2]

    MSR CPSR_c, #(NoInt | IRQ32Mode) ; 切换回irq模式
    LDMFD SP, {R3, SP, LR}^ ; 恢复用户状态的R3,SP,LR, //前面一个SP是IRQ模式的,后面一个SP是用户模式的,为什么不能回写呢,如果你回写的话,那么它保存的是用户的SP,显然是不行的。不知这样理解对不对。
    ; 正是因为没有回写,所以后面调整了SP ,调整指令是 ADD SP, SP, #4*3 ;
    LDR R0, =OSTCBHighRdy    ;读出就绪表中任务最高优先级,判断是否需要任务切换
    LDR R0, [R0]
    LDR R1, =OSTCBCur
    LDR R1, [R1]
    CMP R0, R1                   ;//判断被挂起的任务是不是具有最高优先级 
    ADD SP, SP, #4*3 ;              ;如果不是则进行任务切换
    MSR SPSR_cxsf, R3
    LDMEQFD SP!, {R0-R3, R12, PC}^         ; 不进行任务切换
    LDR PC, =OSIntCtxSw                    ; 进行任务切换
    MEND
    END
     
     
  • 相关阅读:
    老齐python-基础7(文件操作、迭代)
    老齐python-基础6(循环 if while for)
    老齐python-基础5(运算符、语句)
    老齐python-基础4(元祖、字典、集合)
    老齐python-基础3(列表)
    老齐python-基础2(字符串)
    Jupyter notebook 的安装、入门
    pycharm 激活码
    VMware无法连接 MKS:套接字连接尝试次数太多正在放弃
    Numpy基本用法简介
  • 原文地址:https://www.cnblogs.com/xinjie/p/1546382.html
Copyright © 2020-2023  润新知