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又跳到了中断处理函数中区执行中断函数了。

  • 相关阅读:
    OC3(字符串,值类)
    OC2(初始化方法)
    OC1(类和对象)
    postgresql 时间戳格式为5分钟、15分钟
    centos添加ftp用户并禁止外切目录
    postgresql 判断日期是否合法
    tigerVNC的简单使用教程(CentOS的远程桌面连接)
    linux awk解析csv文件
    由windows向linux上传下载文件方法
    codeblocks linux编译告警乱码解决办法
  • 原文地址:https://www.cnblogs.com/hicjiajia/p/2477793.html
Copyright © 2011-2022 走看看