zoukankan      html  css  js  c++  java
  • ARM中断(二)

    本文感谢 郑星 朋友

    2440支持IRQ(普通中断)和FIQ(快速中断)。2440有60个中断源,不支持中断嵌套。

    CPU每执行一条指令都会检查CPSR寄存器,当发现I或F位被置1时,就进行中断处理。需要两次查表过程(为什么要查两次表??没有办法,ARM把所有的中断都归纳成一个IRQ中断异常和一个FIQ中断异常;第一次查表主要是查出是什么异常,可我们总要知道是这个中断异常中的什么中断呀!没办法还需要查第二次)。第一步跳入异常向量表:

    地址

    异常名称

    指令

    0x00

    复位异常

    B  RestHandler

    0x04

    未定义指令异常

    B  HandlerUndef

    0x08

    软件中断异常

    B  HandlerSWI

    0x0C

    指令预取异常

    B  HandlerPabort

    0x10

    数据预取异常

    B  HandlerDabort

    0x14

    保留

    0x18

    IRQ中断异常

    B  HandlerIRQ

    0x1C

    FIQ中断异常

    B  HandlerFIQ

    如果S3C2440刚上电或者是复位,那么pc指针被硬件强制转换到0x00地址处,那么按照2440init.s中的指令“B  ResetHandler”,pc会跳转到ResetHandler处继续执行程序。

    我们以外部中断为例,当发生外部中断后,PC指针指向0x18,执行B  HandlerIRQ 指令,接着跳转到HandlerIRQ标号处执行,S3C2440有很多的中断源,所以不可能把中断函数的地址直接赋给HandlerIRQ。这中间应该还有一个转换。就是根据不同的、具体的中断源,HandlerIRQ对应于不同的中断处理函数的地址。那么接下来看看HandlerIRQ标号后面的内容吧:

    HandlerIRQ HANDLER HandleIRQ
    很明显,这里还有一个宏定义在里面,要知道HANDLER的内容,我们可以在2440init.s中查到:

    ;下面这个宏是用于第一次查表过程的实现中断向量的重定向,如果你比较细心的话就是发现
    ;在_ISR_STARTADDRESS=0x33FF_FF00里定义的第一级中断向量表是采用型如Handle***的方式的.
    ;而在程序的ENTRY处(程序开始处)采用的是b Handler***的方式.
    ;在这里Handler***就是通过HANDLER这个宏和Handle***建立联系的.
    ;这种方式的优点就是正真定义的向量数据在内存空间里,而不是在ENTRY处的ROM(FLASH)空间里,
    ;这样,我们就可以在程序里灵活的改动向量的数据了.
    ;======================================================= 
    ;这段程序用于把中断服务程序的首地址装载到pc中,有人称之为“加载程序”。
    ;本初始化程序定义了一个数据区(在文件最后),34个字空间,存放相应中断服务程序的首地址。每个字
    ;空间都有一个标号,以Handle***命名。
    ;在向量中断模式下使用“加载程序”来执行中断服务程序。
    ;这里就必须讲一下向量中断模式和非向量中断模式的概念
    ;向量中断模式是当cpu读取位于0x18处的IRQ中断指令的时候,系统自动读取对应于该中断源确定地址上的;
    ;指令取代0x18处的指令,通过跳转指令系统就直接跳转到对应地址
    ;函数中 节省了中断处理时间提高了中断处理速度标 例如 ADC中断的向量地址为0xC0,则在0xC0处放如下
    ;代码:ldr PC,=HandlerADC 当ADC中断产生的时候系统会
    ;自动跳转到HandlerADC函数中
    ;非向量中断模式处理方式是一种传统的中断处理方法,当系统产生中断的时候,系统将interrupt
    ;pending寄存器中对应标志位置位 然后跳转到位于0x18处的统一中断
    ;函数中 该函数通过读取interrupt pending寄存器中对应标志位 来判断中断源 并根据优先级关系再跳到
    ;对应中断源的处理代码中
    
    
     MACRO  ;宏定义的开始
     $HandlerLabel HANDLER $HandleLabel
    
     $HandlerLabel     ;标号
     sub sp,sp,#4      ;(1)减少sp(用于存放转跳地址)
     stmfd sp!,{r0}    ;(2)把工作寄存器压入栈(lr does not push because it return to original address)
     ldr     r0,=$HandleLabel  ;将HandleXXX的址址放入r0
     ldr     r0,[r0]           ;把HandleXXX所指向的内容(也就是中断程序的入口)放入r0
     str     r0,[sp,#4]        ;(3)把中断服务程序(ISR)压入栈
     ldmfd   sp!,{r0,pc}       ;(4)用出栈的方式恢复r0的原值和为pc设定新值(也就完成了到ISR的转跳)
     MEND    ;宏定义的结束

    这个宏的作用其实就是在不改变任何寄存器的前提下,把pc指针指向$HandleLabel。这里是将PC指针直接由HandlerIRQ指向HandleIRQ。(在这里Handler***就是通过HANDLER这个宏和Handle***建立联系的.
    ;这种方式的优点就是正真定义的向量数据在内存空间里,而不是在ENTRY处的ROM(FLASH)空间里,
    ;这样,我们就可以在程序里灵活的改动向量的数据了.)

    下面我们就得关注一下HandleIRQ这个标号了。它在2440init.s中设这样定义的:

    ^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00
    HandleReset  #4
    HandleUndef  #4
    HandleSWI  #4
    HandlePabort #4
    HandleDabort  #4
    HandleReserved  #4
    HandleIRQ  #4
    HandleFIQ  #4
    ………

    (其中 “^”表示MAP指令,“#”表示FIELD指令)

    很简单,这里定义了一个内存地址块,首地址:_ISR_STARTADDRESS代表了地址为0x33FF_FF00的内存区域,每隔4个字节,定义一个标号。很很容易就找到了 HandleIRQ这个我们需要找的标号。那么它所代表的内存区域自然就是0x33FF_FF00+0x04*6的内存地址了(跳到此地址执行,这个过程由硬件自动完成)。那么接下来的工作就是要把真正的、具体的中断处理函数的地址赋给HandleIRQ了。这里大家先看一下下面的两端代码:

    1):

    ; 进入C语言前的最后一步了,就是把我们用说查二级向量表
    ; 的中断例程安装到一级向量表(异常向量表)里.
    
    ldr r0,=HandleIRQ ;This routine is needed
    ldr r1,=IsrIRQ    ;if there isn't 'subs pc,lr,#4' at 0x18, 0x1c
    str r1,[r0]

    (2):

    这一段程序就是用来进行第二次查表的过程了.

     ;如果说第一次查表是由硬件来完成的,那这一次查表就是由软件来实现的了.
     IsrIRQ
     sub sp,sp,#4        ;给PC寄存器保留 reserved for PC
     stmfd sp!,{r8-r9}   ;把r8-r9压入栈
    
     ldr r9,=INTOFFSET   ;把INTOFFSET的地址装入r9  INTOFFSET是一个内部的寄存器,存着中断的偏移
     ldr r9,[r9]         ;I_ISR
     ldr r8,=HandleEINT0 ;这就是我们第二个中断向量表的入口的,先装入r8
     ;===================================================================================
     ;哈哈,这查表方法够好了吧,r8(入口)+index*4(别望了一条指令是4 bytes的喔),
     ;这不就是我们要找的那一项了吗.找到了表项,下一步做什么?肯定先装入了!
     ;==================================================================================
     add r8,r8,r9,lsl #2 ;地址对齐,因为每个中断向量占4个字节,即isr = IvectTable + Offeset * 4
     ldr r8,[r8]         ;装入中断服务程序的入口
     str r8,[sp,#8]      ;把入口也入栈,准备用旧招
     ldmfd sp!,{r8-r9,pc};施招,弹出栈,哈哈,顺便把r8弹出到PC了,跳转成功!

    首先我们来看一下第(1)段程序,前面已经提到,此时的的pc已经指向了HandeIRQ所表示的内存了,但是现在该内存还是空的,pc跳转到这里也不能接着往下运行了。所以才有了第1段代码,它的作用就是给HandleIRQ安装句柄了,把IsrIRQ的入口地址填充到了HandeIRQ里面了。所以程序接着就会跳转到IsrIRQ处执行。也就是上面的第(2)段程序了。这段程序具体讲解我就不说了(2次查表),跟最上面的宏定义很类似,其实就是让PC跳转到另外一个地方(pc=HandleEINT0+INTOFFSET*4)。而那个地方正是真正的中断函数。那我们再来看看这个地址是怎么算出来的。首先HandleEINT0就是上面那个MAP定义里面的一个内存区域,有没有发现它是第一个中断源,紧接着它,就是其它各种类型的中断源了。而INTOFFSET则是S3C2440的一个特殊功能寄存器了,某个类型的中断发生了,它的值就会发生变化。而后面为什么要乘以4呢,因为文字池中定义的标号都是4字节的,其实是因为S3C2440中指针变量就是占据4个字节的,这个可以用sizeof(*p)来验证。所以此时pc指针就指向了另外一个地方,那就是刚才说的中断表了。而在c语言中,我们通常会有这样的中断函数句柄安装语句:pISR_EINTn = (unsigned int )key_interrupt;
    这里的pISR_EINTn其实是有定义的,我们以pISR_EINT0为例,宏定义如下:

    #define pISR_EINT0 (*(unsigned *)(_ISR_STARTADDRESS+0x20))

    看这个地址,其实就是上面那个中断表里面的:

    ^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00
    ……0-7
    HandleEINT0 # 4
    HandleEINT1 # 4
    HandleEINT2 # 4
    HandleEINT3 # 4
    ……

    大家发现了吧,HandleEINT0的地址是不是就是 _ISR_STARTADDRESS+0X20,所以说c语言中我们写的中断函数安装句柄,就是这个作用。

    好的,现在让我们来总结一下吧。(以外部中断0为例)
    首先是在c语言的函数中,我们已经执行了这样一条语句pISR_EINTn = (unsigned int )key_interrupt;它的作用是把中断处理函数key_interrupt的地址赋给了中断表中的HandleEINT0。
    然后当某个时刻,发生了外部中断0,那么pc指针被强制指向了0x18处,执行指令: 

    b HandlerIRQ

    跳转到HandlerIRQ之后,执行如下代码:(已经把宏定义屏蔽)

    HandlerIRQ
        sub sp,sp,#4
        stmfd sp!,{r0} 
        ldr r0,=HandleIRQ
        ldr r0,[r0] 
        str r0,[sp,#4] 
        ldmfd sp!,{r0,pc}

    然后pc之争有指向了HandleIRQ这个内存区域。而又由于HandlerIRQ已经被安装了IsrIRQ的句柄,所以紧接着pc又跳转到IsrIRQ处执行如下程序:

    IsrIRQ
        sub sp,sp,#4 ;reserved for PC
        stmfd sp!,{r8-r9}
        ldr r9,=INTOFFSET
        ldr r9,[r9]
        ldr r8,=HandleEINT0
        add r8,r8,r9,lsl #2
        ldr r8,[r8]
        str r8,[sp,#8]
        ldmfd sp!,{r8-r9,pc}

    它的作用是让pc指针根据INTOFFSET的值跳转到中断向量表中的HandleEINT0处。而在此处已经被安装了c语言中的中断处理函数的句柄,所以pc又跳到了中断处理函数中区执行中断函数了。

  • 相关阅读:
    windows 按时自动化任务
    Linux libusb 安装及简单使用
    Linux 交换eth0和eth1
    I.MX6 GPS JNI HAL register init hacking
    I.MX6 Android mmm convenient to use
    I.MX6 GPS Android HAL Framework 调试
    Android GPS GPSBasics project hacking
    Python windows serial
    【JAVA】别特注意,POI中getLastRowNum() 和getLastCellNum()的区别
    freemarker跳出循环
  • 原文地址:https://www.cnblogs.com/hicjiajia/p/2477793.html
Copyright © 2011-2022 走看看