新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 2440啟動代碼和中斷處理過程

        2440啟動代碼和中斷處理過程

        作者: 時間:2016-11-09 來源:網絡 收藏
        啟動代碼與Bootloader不同,主要是指進入C語言之前的匯編代碼,網上都稱為是bootloader的stage1,一般通用的內容包括:
        (1) 定義程序入口點
        (2)設置異常向量表
        (3)初始化存儲系統
        (4)初始化用戶程序的執行環境
        (5)初始化堆棧指針寄存器,改變處理器的模式
        (6)設置FIQ/IRQ中斷處理程序入口
        (7)進入C程序
        1、編譯器選擇
        GBLL THUMBCODE
        [ {CONFIG} = 16
        THUMBCODE SETL {TRUE}
        CODE32
        |
        THUMBCODE SETL {FALSE}
        ]
        因為處理器分為16位/32位兩種工作狀態,程序的編譯器也是分16位和32兩種編譯方式,所以這段程序用于根據處理器工作狀態確定編譯器編譯方式,程序不難理解,主要解釋一下符號“[ | ]”的意思,上面的程序是指:
        if({CONFIG} = 16 )
        { THUMBCODE SETL {TRUE}
        CODE32 }
        else
        THUMBCODE SETL {FALSE}
        2、宏定義
        MACRO
        $HandlerLabelHANDLER$HandleLabel
        $HandlerLabel
        sub sp,sp,#4
        stmfd sp!,{r0}
        ldrr0,=$HandleLabel
        ldrr0,[r0]
        strr0,[sp,#4]
        ldmfdsp!,{r0,pc}
        MEND
        $HandlerLabel是宏的地址標號,HANDLER是宏名,$HandleLabel是宏的參數,$標號在宏指令展開時,標號會替換為用戶定義的符號。在此后,所有遇到$HandlerLabel HANDLER $HandleLabel這種形式的表達式都會被展開成$HandlerLabel到MEND之間的函數。
        例如:ADC_IRQ HANDLER HandleADC即代表如下函數,語句ldr pc,=ADC_IRQ的作用也就是跳轉到這個函數:
        ADC_IRQ
        sub sp,sp,#4
        stmfd sp!,{r0}
        ldrr0,=HandleADC
        ldrr0,[r0]
        strr0,[sp,#4]
        ldmfdsp!,{r0,pc}
        這段程序用于把ADC中斷服務程序的首地址裝載到pc中,跳轉到中斷處理函數,稱之為“加載程序”。HandleADC是一個地址標號,它的內容就是ADC中斷服務程序的地址標號,即文件最后的那個表HandleADC # 4所示,將HandleADC # 4中的4換成中斷服務程序的地址標號即是,程序在這里定義了一個數據區,存放各種中斷服務程序的首地址。每個字空間都有一個標號,以Handle***命名。
        3、寄存器及堆棧設置
        按照上面的順序,可以比較容易讀懂啟動代碼的作用,主要就是通過設置特殊功能寄存器來達到對系統參數的設定。依次禁止看門狗,中斷,設定時鐘控制寄存器,存儲器控制寄存器等等。
        由于各個工作模式下的堆棧指針是相互獨立的,所以要分別進入各個模式下設置其堆棧指針,基本上都差不多,比如未定義指令模式下的設置:
        mrs r0,cpsr
        bic r0,r0,#MODEMASK
        orr r1,r0,#UNDEFMODE|NOINT
        msr cpsr_cxsf,r1
        ldr sp,=UndefStack
        UnderStack是在程序后面用UnderStack # 256建立的一個堆棧空間的首地址,這部分空間建立在RAM中,256字節空間的堆棧大小。
        4、初始化用戶程序的執行環境
        之前在44B0里的啟動代碼里還有包括把ROM里的程序拷貝到RAM中并跳轉到RAM運行程序,也就是把加載狀態下的程序按照編譯連接時的設置重新排布成運行時的程序狀態,以達到符號能夠正確連接的目的,這里是涉及到了所謂的映像文件,但是2410這里沒有這一段,即程序的加載態就是它的運行態,所以要求燒寫程序時必須要把它燒寫在設置的RO地址上,否則程序將不能正確執行。下面這段程序實現RW數據初始化,只是把數據段復制到高地址,如果沒有設置RW的話這段代碼也不會執行。
        ;Copy and paste RW data/zero initialized data
        ldr r0, =|Image$$RO$$Limit| ; Get pointer to ROM data
        ldr r1, =|Image$$RW$$Base| ; and RAM copy
        ldr r3, =|Image$$ZI$$Base|

        ;Zero init base => top of initialised data
        cmp r0, r1 ; Check that they are different
        beq %F2
        1
        cmp r1, r3
        ldrcc r2, [r0], #4
        strcc r2, [r1], #4
        bcc %B1
        2
        ldr r1, =|Image$$ZI$$Limit|
        mov r2, #0
        3
        cmp r3, r1 ; Zero init
        strcc r2, [r3], #4
        bcc %B3
        b %F1(B1)的意思是在臨近的地址標號跳轉,F是向后尋找,B是向前尋找。
        5、說說映象文件
        用ADS編譯產生的映像文件有.axf、.bin、.hex等等格式,就拿要直接燒進Flash里的.bin文件來說,在其他書上看到有這么句話“映像文件一般由域組成,域由最多三個輸出段(RO,RW,ZI)組成,輸出段又由輸入段組成。”
        對于這段話,前兩句是比較好理解的,域就是整個映像文件,對于大部分程序來說就只有一個域,也就是燒進Flash里的那部分東東,稱作加載域;輸出段就是用AREA定義的RO,Rw,一般就這兩個,拿前面的bootloader來說,整體框架是這樣的:
        AREA Init,CODE,READONLY ;<--RO段
        ENTRY
        Entry;<--CODE部分
        … …
        SMRDATA DATA ;<--DATA部分
        … …
        AREARamData, DATA, READWRITE ;<--RW段
        … …

        然而對于輸出段又由輸入段組成卻著實糊涂了好一陣,輸入段是指源程序的代碼(CODE)部分和數據(DATA)部分。上面這個框架中,在RO輸出段的Entry下開始一系列的匯編指令操作,這個應該是CODE輸入段,而SMRDATA DATA引領DCD用于開辟一片數據存儲空間,這部分應該是DATA輸入段,它與RW里的數據不同之處在于這部分數據不能被修改。
        在ADS編譯前在ARM-Linker里的Ro_Base和Rw_Base兩個地址值,就是指兩個輸出段的起始地址,即程序是按照你設置的這種方式排布在內存中的,各個地址標號根據這兩個值確定。然而,用Ultraedit打開bin文件卻發現其實Rw是跟在Ro后面的,也就是說,這兩個段并沒有按照你設置的地址起始,由此引出映像文件的加載域和運行時域兩個概念。
        加載域就是用Ultraedit打開看到的程序最原始的狀態,而運行時域則是程序在執行時按照你設定的方式排布的狀態,顯然,上面設置的兩個地址是針對運行時域來設置的,程序要滿足上面的設置才能正確連接。也就是程序開始階段(加載域狀態)是不能正確連接的,不過開始時不需要用到Rw里的數據,程序是可以運行的,因此必須在需要用到Rw數據之前把它拷貝到上面設置的位置上,這就是bootloader里初始化用戶程序的執行環境部分的作用,把數據移動到正確的位置!
        拷貝完Rw里的數據之后,所有的符號都可以正確連接,這時跳轉到main函數里去執行程序就可以了。2410的這段啟動代碼沒有進行Ro的拷貝,所以如果你把程序燒在0x0地址,那么Ro就必須設置成0x0,如果你設置成0x30000000,那么Ro就必須設置成0x30000000,如果Rw不設置,它將默認跟在Ro后面,否則就執行上面的搬遷代碼,挪到正確的位置上。由于本系統是采用NandFlash啟動的,最初的啟動代碼必須要在0x0處的SRAM里執行,所以,如果要把這段啟動代碼當作NandFlash的啟動代碼的話,Ro就必須設成0x0。
        6、中斷處理過程
        要使用中斷,首先需要清掉程序狀態寄存器CPSR里的IRQ位,這個很容易被忽略了。再之后才是考慮與中斷有關的相應寄存器.
        這個幾個寄存器比較容易弄混了:
        SRCPND/SUBSRCPND:只要中斷產生的條件滿足,例如外部電平,定時溢出等等,SRCPND/SUBSRCPND的相應位就會被置位,它不管其他地方的設置如何,所以某一時刻可能有幾個位同時被置位了(幾個中斷同時產生)。
        INTMSK/INTSUBMSK:這個是中斷屏蔽位,清零表示允許中斷請求,默認是禁止了所有的中斷請求。
        INTPND:它表示處理器接下來就要去處理的那個中斷,某一時刻只可能有一個位被置位。這個寄存器置位的必要條件是SRCPND/SUBSRCPND已經是1,而且INTMSK/INTSUBMSK相應位已經清零。
        SRCPND/SUBSRCPND和INTPND都不會自動清零,要程序向相應的位寫1才能清零。

        2410不支持中斷嵌套,中斷產生后處理器進入到IRQ模式,只有在等到這個中斷處理完之后才能響應下一次中斷。
        如果同時產生多個中斷,就涉及到了中斷優先級的問題。SRCPND寄存器對應的32個中斷源總共被分為6個組,每個組由一個ARBITER(0~5)對其進行管理。中斷必須先由所屬組的ARBITER(0~5)進行第一次優先級判斷然后再到ARBITER6進行第二次判斷。可以更改的只是組里的優先級順序。
        PRIORITY的各個位被分為兩種類型,一種是ARB_MODE,另一種為ARB_SEL,拿ARBITER0來說,這個組一共包含了四種中斷源:EINT0~EINT3,分別對應Req0~Req3,很明顯ARB_SEL0就是決定了這四種中斷的優先順序,如果這個組里的兩個中斷同時產生,將會把排在前面的先傳遞給ARBITER06進行第二次判斷。ARB_MODE0置1代表開啟優先級次序旋轉,當該位置為1之后,ARB_SEL0的值會在每處理完一次中斷后順次改變。

        中斷處理流程
        啟動代碼開始是一個異常向量表,這個向量表是固定的,由處理器決定,必須要放在0x0地址那個地方,這個跟51單片機的中斷向量表相類似。
        b ResetHandler
        b HandlerUndef ;handler for Undefined mode
        b HandlerSWI ;handler for SWI interrupt
        b HandlerPabort ;handler for PAbort
        b HandlerDabort ;handler for DAbort
        b . ;reserved
        b HandlerIRQ ;handler for IRQ interrupt
        b HandlerFIQ ;handler for FIQ interrupt
        當產生IRQ中斷時,PC首先無條件地來到0x18這個地址處,這個0x18就是處理器決定的IRQ中斷的入口地址,所以要在這個地址處放一條跳轉指令b HandlerIRQ,PC接著跳轉到HandlerIRQ地址標號處,這里存放著一個宏語句:
        HandlerIRQ HANDLER HandleIRQ
        按照上面說的宏展開,其實是執行這么一段語句:
        sub sp,sp,#4 ;留下堆棧的第一個位置
        stmfd sp!,{r0} ;保護R0因為后面要用R0傳遞值
        ldrr0,=HandleIRQ ;將HandleIRQ這個地址標號的值傳如R0
        ldrr0,[r0];取存放在HandleIRQ里的那個值
        strr0,[sp,#4] ;把取到的值壓入棧
        ldmfdsp!,{r0,pc};恢復R0并把PC指向HandleIRQ里存放的地址值
        HandleIRQ里存放是什么值呢?代碼最后有個這樣的表,這個表就是在SDRAM里的另外一張異常向量表,這張表可以根據需要修改_ISR_STARTADDRESS的值來隨意更改它的位置。
        ^_ISR_STARTADDRESS
        HandleReset#4
        HandleUndef#4
        HandleSWI#4
        HandlePabort #4
        HandleDabort #4
        HandleReserved #4
        HandleIRQ#4
        HandleFIQ#4
        這里實現結構化一片地址空間的目的,可見在HandleIRQ這里預留了4個字節的空間,但是這個空間里現在放的是什么東西呢?
        在代碼的初始化過程中有這么一段代碼:
        ldr r0,=HandleIRQ ; Setup IRQ handler
        ldr r1,=IsrIRQ
        str r1,[r0]
        原來是把IsrIRQ所在的地址值放到這個地方,那就是宏實現了把PC指向IsrIRQ的目的。程序來到IsrIRQ:
        IsrIRQ
        sub sp,sp,#4;預留堆棧
        stmfd sp!,{r8-r9} ;保護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指向相應的中斷處理地址
        假如產生了EINT0中斷來到了這里,那么PC將會跳轉到HandleEINT0里存放的地址值,與上面的相同,程序里有這個表:
        HandleEINT0#4
        HandleEINT1#4
        HandleEINT2#4
        HandleEINT3#4
        HandleEINT4_7 #4
        .
        .
        .
        這個表在2410addr.h頭文件里也有對應的定義,指向的是同樣的一塊地方:
        .
        .
        .
        #define pISR_EINT0(*(unsigned *)(_ISR_STARTADDRESS+0x20))
        #define pISR_EINT1(*(unsigned *)(_ISR_STARTADDRESS+0x24))
        #define pISR_EINT2(*(unsigned *)(_ISR_STARTADDRESS+0x28))
        .
        .
        .
        問題是HandleEINT0存放的又是什么值呢?這就需要在初始化EINT0的時候寫上這么一句:
        pISR_EINT0 = (unsigned )_IsrEINT0Service;
        也就是把EINT0的中斷處理函數的地址寫到HandleEINT0地址處存放,那么到此PC就可以跳轉到_IsrEINT0Service里去了,這里完成你所需要的中斷處理過程。


        評論


        技術專區

        關閉
        主站蜘蛛池模板: 黑水县| 永靖县| 东源县| 秀山| 阿拉善左旗| 高阳县| 商南县| 武宁县| 华池县| 化德县| 长宁区| 双辽市| 东港市| 安多县| 隆子县| 云林县| 鞍山市| 金山区| 娱乐| 晋宁县| 当阳市| 汉沽区| 花莲县| 蓝山县| 化州市| 黔西| 沈丘县| 金平| 江陵县| 靖边县| 绥化市| 茂名市| 石林| 鄂州市| 江城| 山阴县| 安达市| 蚌埠市| 宣城市| 沈丘县| 湖南省|