<acronym id="xonnx"></acronym>
      <td id="xonnx"></td>
    1. <pre id="xonnx"></pre>

      1. 專注電子技術學習與研究
        當前位置:單片機教程網 >> MCU設計實例 >> 瀏覽文章

        S3C2440中斷代碼的深層次分析

        作者:佚名   來源:本站原創   點擊數:  更新時間:2014年03月12日   【字體:

        在前一段時間分析了ARM異常處理機制的處理方式,分析了在異常產生以后CPU自動完成的相關處理以及程序員應該完成的基本操作。著重分析了異常代碼的返回地址分析已經采用通用代碼處理各種異常的可能性。

        異常處理的基本過程如下:異常產生(在指令的臨界中檢測CPU的狀態,一般實質在這條指令被執行完成,但是還沒有執行下一條指令之前檢測)——>保存狀態寄存器,切換狀態寄存器,保存LR=PC-4,強制PC跳轉到對應異常向量(以上的過程都是CPU自動完成)——》調整返回地址,在棧中保存寄存器,便于恢復寄存器的值——》異常處理函數——》退出異常。

        中斷處理機制的兩種形式:

        1、  采用在中斷向量中存儲簡單的跳轉指令,跳轉到異常處理函數中,但是這種方式存在的缺點就是跳轉指令的范圍是有局限性的。

        2、  采用更新PC值的方法進行,具體的實現形式是在另一個固定地址處(handle_addr)保存對應異常處理函數的地址,然后采用LDR PC [PC, offset],其中offset = handle_addr – vect – 0x08;這種機制只要保證選擇的地址恰當就能實現不同距離的跳轉。

        以上的分析和處理在上一次中已經分析,這次分析中斷的處理過程,中斷只是異常的一種特殊情況,對異常的處理得到了好的理解,那么對中斷的處理也就比較方便了。

        ARM內核中只支持IRQIFQ兩種類型的中斷,但是不同的廠商提供不同類型的中斷控制器實現對中斷的擴展,使得實際的芯片更加適合我們的使用。但是中斷控制器的差別也使得不同廠商的中斷處理也有差別,但是基本的思想是一致的。

         


         

        S3C2440的中斷控制器一個支持60種中斷源,基本的實現如上圖所示;镜募拇嫫靼SRCPND、INTPND(有且僅有1bit會被置位,可以通過這個寄存器判斷中斷源,找出那個IRQ源發生中斷)、INTMOD、INTMSK、PRIORITY(用來改變中斷的優先級順序,但是其中還是存在一些固有的順序,具體的參看手冊)、INTOFFSET(用來表示IRQINTPND的那個bit被置位,這樣每一類的中斷源都存在一個固定的偏移量,這個寄存器可以用來用來計算偏移量以及通過這個偏移量找到對應的中斷處理函數地址存儲位置等),當然也存在一些關于多個中斷源構成的子中斷寄存器,SUBSRCPND、INTSUBMSK。

         

        S3C2440的啟動代碼中描述了關于中斷處理過程的基本過程和原理。

        首先需要搞清楚下面的一個宏定義:

               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

         

        1、搞清楚ARM中的MACRO偽指令,這個偽指令就是我們在匯編中的宏定義,我們都知道宏的實現能夠避免代碼的重復型以及代碼的可修復性。關于ARM匯編中的宏定義基本的形式如下:

               MACRO

                      {$label} macroname {$parameter} {$parameter}…

                      Code

               MEND

               其中$label 宏指令被展開時,label可被替換為相應的符號,一般為一個標號

               macroname 所定義的宏的名稱

               $parameter 宏指令的參數,當宏指令被展開時被替換成對應的值。

        2、依據上面的定義我們可以知道當前這段代碼定義了一個宏指令,HANDLER,其中標號為$HandlerLabel,參數為$HandleLabel

        基本的實現代碼分析如下:

        sub  sp,sp,#4;               在棧中預留一個區域,用來保存PC的值

        stmfd      sp!,{r0}  ;      由于r0還需要被使用,因此需要被壓棧

        ldr     r0,=$HandleLabel ;這里的ldr是一個偽指令,主要是將標號$HandleLabel的地址加載到r0中,這也是壓棧r0的原因。

        ldr     r0,[r0]            ;這是ARMldr指令,主要是將$HandleLabel對應地址中的內容加載到r0中。如果在$HandleLabel中保存的是一個中斷處理函數的地址,那么只需要將這個值加載到PC即可實現了中斷任務跳轉,實際上這個過程就是采用了異常處理的第二種方式:

        即加載PC的方式,而不是簡單的跳轉方式。

         

        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)

        這兩句代碼正是這段代碼的精髓;拘问饺缦拢

        str     r0,[sp,#4],是指將r0的內容,也就是異常處理函數的地址保存到棧中的SP-4位置處,這個位置也恰好是之前sub       sp,sp,#4; 用來預留給保存PC值的位置,這時將異常處理函數的地址保存在這個地址處,接下來的ldmfd   sp!,{r0,pc}剛好就是將棧中的內容加載到R0PC中,這樣也就實現了將異常處理函數地址加載到PC.實現了跳轉過程。

         

         

        高地址

        SP_0/SP_3

        SP_1

        Handle_addr

        SP_2

        R0

         

        低地址

        從上面的分析可以知道這種中斷處理的方式,并不是中斷處理中的簡單跳轉方式(因為跳轉范圍的局限性)而是采用更新PC值的形式實現的。

         

        接下來分析IRQ,這種在我們實際開發中使用比較多的中斷形式進行分析。

        首先可以發現存在:

        1、b       HandlerIRQ  ;handler for IRQ interrupt

        這種情況下發生在中斷產生過程中,是在IRQ向量中執行的,也就是在0x18處執行,其中HandlerIRQ實質上是一個標號,對應一個具體的地址。其中保存的內容就是對應IRQ處理函數的地址。但是在代碼中只有一個HandlerIRQ,形式如下

        HandlerIRQ  HANDLER HandleIRQ

         

        2、HandlerIRQ    HANDLER HandleIRQ

        根據上面的宏定義,可以將這句代碼進行擴展,得到如下的形式:

        HandlerIRQ

              sub  sp,sp,#4 

               stmfd     sp!,{r0}

        ldr     r0,= HandleIRQ

               ldr     r0,[r0]

               str     r0,[sp,#4]

               ldmfd   sp!,{r0,pc}

         

        3、關于HandleIRQ其中存放的內容可以從下面的代碼中得到。

               ; Setup IRQ handler

               ldr   r0,=HandleIRQ    ;This routine is needed

               ldr   r1,=IsrIRQ    ;if there is not 'subs pc,lr,#4' at 0x18, 0x1c

               str   r1,[r0]

        其中可以看到,在HandleIRQ中保存的內容是IsrIRQ的地址,而IsrIRQ我們可以知道是一個中斷服務函數,因為在寫代碼的過程中進程會遇到這個特殊字符__ISR,這段代碼是在啟動代碼中執行的。

        4、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}

        還是一句一句的分析:

               sub  sp,sp,#4        ;為保存PC值預留一個棧區域,這個區域與上面的處理過程是異曲同工的。

               stmfd      sp!,{r8-r9}     ;保存r8,r9中的值,因為接下來將使用這兩個寄存器

        ldr   r9,=INTOFFSET; 這是一個偽指令操作,實質上是將寄存器INTOFFSET的地址加載到r9中。

        ldr   r9,[r9];得到寄存器中的值,這個寄存器中的值恰好保存了當前最高優先級中斷的中斷號(優先級是可以調節的,而中斷號是一個固定值,因此選擇中斷號比較恰當),這樣也就知道了具體是那個中斷源產生了中斷。

         

        ldr   r8,=HandleEINT0;這句的ldr是偽指令,意思是將標號的地址加載到r8

        add  r8,r8,r9,lsl #2;從指令的意義分析:r8 = r8 + r9>>2 = r8+r9*4;

        其實這兩句結合一下S3C2440的中斷資料就不難分析得出,因為HandleEINT0實質上是指存儲外部中斷0處理函數地址的地方,那么我們可以將這一塊內存地址看做是一個IRQISR中斷向量表,而EINT0恰好是中斷優先級最高的中斷,那么可以將這個地址HandleEINT0作為IRQ中斷向量表的入口地址,其他中斷號的地址,只需要通過偏移地址就能得到,由于指針的大小恰好為4個字節,因此得到的相應中斷號的入口地址是

        HandleEINT0 = HandleEINT0 + INTOFFSET*4,

        這些地址中都保存了對應中斷處理函數的函數地址。

               ldr   r8,[r8]是指將r8的內容加載到r8中,也就是將對應中斷處理函數的地址加載到r8中。

               str   r8,[sp,#8];這句代碼的作用實質上就是和上面的分析一樣,也就是將r8的值保存到之前為PC預留的區域中。

        ldmfd      sp!,{r8-r9,pc};這句也恰好驗證了上面的分析,PC中的值恰好就是之前的sp+8處的內容,這樣中斷處理函數的地址就到了PC中。

        小結:

        我們可以將ARM中采用2級向量表的形式實現異常的中斷處理,其中第一級是CPU中定義好的向量表,也就是異常向量表。在這一級的向量表中,實現跳轉到對應的異常公共處理函數,另外每一種異常問題都存在自己的子問題,這時候采用第二級的向量表就可以解決各種子問題。第一級的向量表一般來說都是CPU定義好的,而第二級向量表則是我們在程序設計中人工實現的。

         

        5、那么又是如何得到C語言中的函數呢,實質上已經很簡單了,具體的分析如下:

        //S3c2440init.s

               ^   _ISR_STARTADDRESS             ; _ISR_STARTADDRESS=0x33FF_FF00

        HandleReset   #   4

        HandleUndef #   4

        HandleSWI            #   4

        HandlePabort    #   4

        HandleDabort    #   4

        HandleReserved  #   4

        HandleIRQ             #   4

        HandleFIQ             #   4

         

        這邊就可以看做第二級中斷向量表

               ;@0x33FF_FF20

        HandleEINT0         #   4

        HandleEINT1         #   4

        HandleUART0       #   4

        ….

        HandleSPI1           #   4

        HandleRTC           #   4

        HandleADC           #   4

               _ISR_STARTADDRESSs3c2440中是一個具體的地址值,這個地址值可以在option.h中找到。因此依據這個值我們就可以知道我們的二級向量表的實際位置,這種處理的方式存在一定的巧妙性,同時中斷地址的選擇也需要我們恰當的設置。這里的“^ 其實就是 MAP ,這段程序的意思是,從 _ISR_STARTADDRESS 開始,預留一個變量,每個變量一個標號,預留的空間為 4個字節,也就是 32BIT,其實這里放的是真正的C寫的處理函數的地址,說白了,就是函數指針,這樣做就很靈活了。

        //option.h

        #define _ISR_STARTADDRESS               0x33ffff00

        同時在s3c2440addr.h中又可以找到下面的定義:

        //s3c2440addr.h

        // Exception vector(異常向量,不是CPU的異常向量)

        #define pISR_RESET           (*(unsigned *)(_ISR_STARTADDRESS+0x0))

        #define pISR_UNDEF           (*(unsigned *)(_ISR_STARTADDRESS+0x4))

        #define pISR_SWI        (*(unsigned *)(_ISR_STARTADDRESS+0x8))

        #define pISR_PABORT         (*(unsigned *)(_ISR_STARTADDRESS+0xc))

        #define pISR_DABORT        (*(unsigned *)(_ISR_STARTADDRESS+0x10))

        #define pISR_RESERVED     (*(unsigned *)(_ISR_STARTADDRESS+0x14))

        #define pISR_IRQ         (*(unsigned *)(_ISR_STARTADDRESS+0x18))

        #define pISR_FIQ         (*(unsigned *)(_ISR_STARTADDRESS+0x1c))

        // Interrupt vector(中斷向量)

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

        #define pISR_EINT1            (*(unsigned *)(_ISR_STARTADDRESS+0x24))

        #define pISR_EINT2            (*(unsigned *)(_ISR_STARTADDRESS+0x28))

        #define pISR_EINT3            (*(unsigned *)(_ISR_STARTADDRESS+0x2c))

        #define pISR_EINT4_7  (*(unsigned *)(_ISR_STARTADDRESS+0x30))

        #define pISR_SPI1        (*(unsigned *)(_ISR_STARTADDRESS+0x94))

        #define pISR_RTC        (*(unsigned *)(_ISR_STARTADDRESS+0x98))

        #define pISR_ADC        (*(unsigned *)(_ISR_STARTADDRESS+0x9c))

        從上面的代碼中我們可以知道pISR_EINT0之類的實質上就是一個地址,如果我們在這個地址中填充處理函數的地址值也就形成了函數指針,實際上只需要將函數名賦值給對應的中斷向量即可。這樣也就找到了適當的處理方式.基本的形式如下所示:

               Void main()

        {

               

        pISR_EINT0 = (U32)Button_ISR;

        While(1)

        {

        }

        }

        /*中斷服務函數*/

        static void _irq Button_ISR(void)

        {

              

        }

        幾個分析的比較清晰的網址可以去看看:

        http://blog.sina.com.cn/s/blog_8c134b590100yke9.html

        http://hi.baidu.com/%C9%B3%BC%D3%BB%C6%BD%F0%CA%A5%B6%B7%CA%BF/blog/item/fbc56f137f475a085baf5334.html

         

        總結:

         


        問題?在其中的代碼中,我并沒有看到返回地址的操作問題,我找了很多的代碼,但是好像都不是特別的準確。也就是沒有找到對應的
         subs pc, lr, =0x04操作。

         

        代碼中經典的片段就是如何實現了代碼的跳轉問題:

        sub sp,sp, #0x04;為保存PC值預留空間

        stmfd     sp!,{r0}; 保存需要使用到的寄存器值,需要使用多少,就壓多少的堆棧

        …//使用r0進行相關的操作

        ldr r0,[r0];

        str r0, [sp,#0x04]; //這個操作類似于函數調用中的問題

        ldmfd sp!,{r0, pc};//出棧操作,實現了對PC值的賦值

        關閉窗口

        相關文章

        欧美性色欧美精品视频,99热这里只有精品mp4,日韩高清亚洲日韩精品一区二区,2020国自产拍精品高潮