zoukankan      html  css  js  c++  java
  • 周立功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
     
     
  • 相关阅读:
    使用JDBC连接MySql时出现:The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration
    Mysql Lost connection to MySQL server at ‘reading initial communication packet', system error: 0
    mysql-基本命令
    C# 监听值的变化
    DataGrid样式
    C# 获取当前日期时间
    C# 中生成随机数
    递归和迭代
    PHP 时间转几分几秒
    PHP 根据整数ID,生成唯一字符串
  • 原文地址:https://www.cnblogs.com/xinjie/p/1546382.html
Copyright © 2011-2022 走看看