S3C2440的Keil启动文件S3C2440.s添加中断配置

参考文档

S3C2440之MMU驱动代码模板
keil下基于arm9的TX2440开发板的外部中断编写
S3C2440中断过程详解(ADS,TQ2440)
mini2440—–keil for ARM之中断一

前言

本文主要描述如何在Keil自带的S3C2440.s文件中添加中断配置及中断地址映射,从而可以在中断产生时跳转到用户代码中的中断服务函数。目前在TQ2440开发板是实测可用。

工程源代码下载

本文所有代码都是截取代码,“…”代表其还有上下文。可根据代码中上下文的残缺部分找到该代码添加的位置。

一,添加中断相关寄存器地址符号映射

因为原始S3C2440.s文件中不含中断相关寄存器的地址,所以需要在开头添加:

...

; *  RAM_INTVEC: when set the startup code copies exception vectors 
; *  from execution address to on-chip RAM.
; */


;=================
; INTERRUPT
;=================
SRCPND      EQU  0x4a000000    ;Interrupt request status
INTMOD      EQU  0x4a000004    ;Interrupt mode control
INTMSK      EQU  0x4a000008    ;Interrupt mask control
PRIORITY    EQU  0x4a00000c    ;IRQ priority control           <-- May 06, 2002 SOP
INTPND      EQU  0x4a000010    ;Interrupt request status
INTOFFSET   EQU  0x4a000014    ;Interruot request source offset
SUSSRCPND   EQU  0x4a000018    ;Sub source pending
INTSUBMSK   EQU  0x4a00001c    ;Interrupt sub mask
_ISR_STARTADDRESS   EQU 0x33ffff00

; Standard definitions of Mode bits and Interrupt (I & F) flags in PSRs

Mode_USR        EQU     0x10
Mode_FIQ        EQU     0x11
Mode_IRQ        EQU     0x12

...

二,添加中断向量偏移表

在文件末尾添加中断向量表,对应2440addr.h文件中相应的中断向量地址,起始地址参见_ISR_STARTADDRESS

...

                LDR     R2, = (Heap_Mem +      Heap_Size)
                LDR     R3, = Stack_Mem
                BX      LR
                ENDIF

ALIGN

    AREA RamData, DATA, READWRITE

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

;Do not use the label 'IntVectorTable',
;The value of IntVectorTable is different with the address you think it may be.
;IntVectorTable
;@0x33FF_FF20
HandleEINT0     #   4
HandleEINT1     #   4
HandleEINT2     #   4
HandleEINT3     #   4
HandleEINT4_7   #   4
HandleEINT8_23  #   4
HandleCAM       #   4       ; Added for 2440.
HandleBATFLT    #   4
HandleTICK      #   4
HandleWDT       #   4
HandleTIMER0    #   4
HandleTIMER1    #   4
HandleTIMER2    #   4
HandleTIMER3    #   4
HandleTIMER4    #   4
HandleUART2     #   4
;@0x33FF_FF60
HandleLCD       #   4
HandleDMA0      #   4
HandleDMA1      #   4
HandleDMA2      #   4
HandleDMA3      #   4
HandleMMC       #   4
HandleSPI0      #   4
HandleUART1     #   4
HandleNFCON     #   4       ; Added for 2440.
HandleUSBD      #   4
HandleUSBH      #   4
HandleIIC       #   4
HandleUART0     #   4
HandleSPI1      #   4
HandleRTC       #   4
HandleADC       #   4
;@0x33FF_FFA0
    END

三,添加程序__ENTRY入口点

定义程序入口点的位置,在ram调试状态下,就是0x30000000对应的Reset Handler的跳转代码。

...


;  Mapped to Address 0.
;  Absolute addressing mode must be used.
;  Dummy Handlers are implemented as infinite loops which can be modified.

                EXPORT __ENTRY
__ENTRY

Vectors         LDR     PC, Reset_Addr         
                LDR     PC, Undef_Addr
                LDR     PC, SWI_Addr
                LDR     PC, PAbt_Addr

...

四,修改添加中断现场保护及跳转代码

除了IRQ中断以外的所有中断的现场保护及跳转代码,因为这些中断在向量表都只有一种状态,所以不需要偏移。这里是宏定义的实际代码,宏定义的调用见五,修改添加IRQ中断现场保护及跳转代码中的代码。

...

GPJCON_Val      EQU     0x00000000
GPJUP_Val       EQU     0x00000000

;//  I/O Setup‘

        MACRO
$HandlerLabel HANDLER $HandleLabel

$HandlerLabel
    sub sp,sp,#4    ;decrement sp(to store jump address)
    stmfd   sp!,{r0}    ;PUSH the work register to stack(lr does not push because it return to original address)
    ldr     r0,=$HandleLabel;load the address of HandleXXX to r0
    ldr     r0,[r0]  ;load the contents(service routine start address) of HandleXXX
    str     r0,[sp,#4]      ;store the contents(ISR) of HandleXXX to stack
    ldmfd   sp!,{r0,pc}     ;POP the work register and pc(jump to ISR)
    MEND

;----------------------- CODE --------------------------------------------------

                PRESERVE8


; Area Definition and Entry Point
;  Startup Code must be linked first at Address at which it expects to run.

...

五,修改添加IRQ中断现场保护及跳转代码

IRQ中断的现场保护及跳转代码。IRQ中断有很多可能,比如按键的外部中断,串口中断,IIC中断。。。等,需要根据实际中断标志指示跳转到对应的服务函数,所以需要一套独立的中断处理代码。此处代码来自《mini2440—–keil for ARM之中断一》。

...

                ELSE
IRQ_Addr        DCD     IRQ_Handler
                ENDIF
FIQ_Addr        DCD     FIQ_Handler

;Undef_Handler   B       Undef_Handler
                ;IF      :DEF:__RTX
                ;ELSE
;SWI_Handler     B       SWI_Handler
                ;ENDIF
;PAbt_Handler    B       PAbt_Handler
;DAbt_Handler    B       DAbt_Handler
;IRQ_Handler     PROC
                ;EXPORT  IRQ_Handler               [WEAK]
                ;B       .
                ;ENDP
;FIQ_Handler     B       FIQ_Handler

FIQ_Handler     HANDLER HandleFIQ
HandlerIRQ      HANDLER HandleIRQ
Undef_Handler   HANDLER HandleUndef
SWI_Handler     HANDLER HandleSWI
DAbt_Handler    HANDLER HandleDabort
PAbt_Handler    HANDLER HandlePabort

;呵呵,来了来了.好戏来了,这一段程序就是用来进行第二次查表的过程了.
;如果说第一次查表是由硬件来完成的,那这一次查表就是由软件来实现的了.
;为什么要查两次表??
;没有办法,ARM把所有的中断都归纳成一个IRQ中断异常和一个FIRQ中断异常
;第一次查表主要是查出是什么异常,可我们总要知道是这个中断异常中的什么中断呀!
;没办法了,再查一次表呗!
;===================================================================================
;外部中断号判断,通过中断服务程序入口地址存储器的地址偏移确定
;PC=[HandleEINT0+[INTOFFSET]]              
;IsrIRQ
IRQ_Handler
    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了,跳转成功!


; Reset Handler

                EXPORT  Reset_Handler


...

六,屏蔽USER模式,开启SVC模式,打开IRQ中断

因为在ram调试时,code地址是从0x30000000开始,而IRQ中断跳转会直接调到0x00000018,然而我们期望跳到0x30000018(这个才是真正的IRQ入口),所以需要用mmu做一个0x000000180x30000018的映射(这个在TQ2440工程中的mmu初始化代码中完成)。但是mmu调用cp15协处理器的汇编代码在USER模式下会出问题?!目前只能先用SVC模式。此外,Keil自带的S3C2440.s文件默认是关闭SVC模式的IRQ中断,需要修改代码开启。

...

             SUB     R0, R0, #FIQ_Stack_Size

;  Enter IRQ Mode and set its Stack Pointer
                MSR     CPSR_c, #Mode_IRQ:OR:I_Bit:OR:F_Bit
                MOV     SP, R0
                SUB     R0, R0, #IRQ_Stack_Size

;  Enter Supervisor Mode and set its Stack Pointer
                ;MSR     CPSR_c, #Mode_SVC:OR:I_Bit:OR:F_Bit
                MSR     CPSR_c, #Mode_SVC:OR:F_Bit
                MOV     SP, R0
                SUB     R0, R0, #SVC_Stack_Size

;  Enter User Mode and set its Stack Pointer
                ;MSR     CPSR_c, #Mode_USR
                ;MSR     CPSR_c, #Mode_USR:OR:I_Bit:OR:F_Bit
                ;MOV     SP, R0
                ;SUB     SL, SP, #USR_Stack_Size

;  Enter User Mode and set its Stack Pointer
                ;MSR     CPSR_c, #Mode_USR
                IF      :DEF:__MICROLIB

                EXPORT __initial_sp

...

补充一下

再去看了一下Keil自带的S3C2440的Blinky例程,发现其实里面是有中断配置的,而且工作的很正常。其使用的是用户模式并开启了IRQ和FIQ,详见如下代码:

...

;  Enter Supervisor Mode and set its Stack Pointer
                MSR     CPSR_c, #Mode_SVC:OR:I_Bit:OR:F_Bit
                MOV     SP, R0
                SUB     R0, R0, #SVC_Stack_Size

;  Enter User Mode and set its Stack Pointer
                MSR     CPSR_c, #Mode_USR
                MOV     SP, R0
                SUB     SL, SP, #USR_Stack_Size

;  Enter User Mode and set its Stack Pointer
                MSR     CPSR_c, #Mode_USR
                IF      :DEF:__MICROLIB

...

而最关键的中断入口函数,直接是用C写的:

...

/* Common interrupt handler, handles all interrupts                           */
__irq void IRQ_Handler (void)
{

  unsigned int msk = (1 << INTOFFSET);

  switch (msk) {
    case BIT_TIMER0:                    /* Timer 0 interrupt mask             */
      if (ticks++ >= ((Speed << 3)+4)) {/* Set Clock to 1 periodically        */
        ticks   = 0;
        Clock   = 1;
      }
      break;
  }

  SRCPND = msk;                         /* Clear Interrupt                   */
  INTPND = msk;
}

...

这个方式也很是高明,有点像PIC的中断处理方式,优先级完全由代码决定。一开始先入为主,网上说没有也就这么认为,现在想来,三星/ARM的工程师怎么可能这么水?总之此处中断服务函数可以处理所有的IRQ中断,不过要写其他的中断,比如FIQ,SWI等等,还是要动启动文件吧。。。

你可能感兴趣的:(ARM裸板跑起)