單片機
返回首頁

s3c2440學習之路-012-1 Undefined未定義中斷

2021-09-09 來源:eefocus

1 未定義中斷的原理

1.1 ARM的指令組成

ARM的指令是由32位組成的,是有一定的組成格式的,如果不符合組成格式的話,那就這條指令就無法被識別,就是未定義指令了。指令的[31]~[28] 是條件位,當條件位為1110B時,就表明該指令一定背執行。這里特別指出[31] ~[28]是因為后面的例子種將會使用到。

在這里插入圖片描述
在這里插入圖片描述

1.2 執行未定中斷的過程

當發現未定義指令時ARM會做什么呢,如同s3c2440學習之路-012-0 異常中斷基礎知識 的1.4 小節所說的, 程序會自動跳到0x4的位置去執行代碼。

具體的執行過程如下:


執行某條命令,發現不符合ARM的命令格式,產生未定義異常


發生異常時硬件的處理,即進入異常

2.1將返回地址保存在LR(R14)寄存器

2.2將CPSR復制到SPSR(SPSR 就是專門弄來備份CPSR的)

2.3設置CPSR的模式位(設置CPSR的bit0~bit4)

2.4設置PC值為0x4(對應未定義異常向量的地址)


發生異常時軟件的處理,即處理異常然后回到異常發生時的位置繼續處理

3.1 設置SP(因為需要通過壓棧來保存寄存器和代碼跳轉)

3.2 保存現場(將通用的寄存器保存起來)

3.3 將SPSR賦值給CPSR

3.4 清除中斷標志位(未定義異常無中斷標志可清除)

3.5 恢復現場(將通用的寄存器恢復回去)

3.6 將LR直接賦值給PC(這里不需要做差值)


我們把重點放在軟件的處理上,接下來通過源碼來分析,這樣可以更好的體會到未定義中斷的處理過程。

在這里插入圖片描述
在這里插入圖片描述

2 源碼分析

2.1 進入未定義中斷

start.s


   ldr pc, =sdram_addr


sdram_addr:


    bl uart0_init

    bl print_hello


    .word 0xdeadc0de


    bl print_hello


    ldr pc, =main /* abs jump to main */


loop:

    b loop


ldr pc, =sdram_addr 是為了讓代碼跳到SDRAM上去執行,如果不清楚請看s3c2440學習之路-011代碼重定位

接下來就是初始化串口,然后執行print_hello函數,這個函數非常簡單,就是打印"hello"。


void print_hello(void)

{

    printf("n");

    printf("hellon");

}


接下來就是重頭戲,.word 0xdeadc0de, 是一條未定指令(0xdeadc0de,C零DE, 不是C歐DE, 有點像Dead Code 死代碼),它的組成不符合ARM指令格式,因此會產生未定義中斷。回顧前面所說的,最終硬件會把PC賦值成0x4, 程序會跑到0x4的地址去執行。


2.2 未定義中斷的軟件處理

start.s


.text

.global _start

_start:


    b _reset

    ldr pc, _undef_addr


_undef_addr: .word _undef


_undef:


    /*set bank sp, sdram end address is 0x34000000

    because sdram size is 64M,

    and sdram start address is 0x30000000 */

    ldr sp, =0x34000000


      /* store register */

    stmdb sp!, {r0-r12, lr}


    mrs r0, cpsr

    ldr r1, =undef_string

    bl printf_undef


    mov r0, #4

    bl led_off


    /* resume register */

    ldmia sp!, {r0-r12, pc}^ /*^ means copy spsr to cpsr */


undef_string:

    .string "Undefinf Excption!"

    .align 4


_reset:

    /* stop watch dog */

    ldr r0, =0x53000000

    mov r1, #0

    str r1, [r0]


程序開始會執行2條指令,第一條是b reset, 所處的地址是0x0, 也就是上電后自動執行的第一條指令。第二條指令是ldr pc, _undef_addr, 所處的地址是0x4, 也就是發生未定義中斷時硬件會自動跳到這里的地址。要弄懂這條命令的意思,就需要弄懂ldr 匯編指令的2種用法。


ldr r0, =0xA

ldr r0, 0xB


上面2條ldr指令,一條是帶"=",一條不帶"="。 第1條指令的意思是r0=0xA, 是直接賦值的意思,而第2條指令是獲取0xB地址上的值,再賦值給r0。如果把0xB當做指針的話,第2條指令就等價于r0=*0xB。一個是直接賦值,一個是取它地址里面的值,在賦值。


在回到ldr pc, _undef_addr, 先獲取_undef_addr地址處的數值,在賦值給pc。通過反匯編查看可知,_undef_addr的值為0x30000008, 而0x30000008地址上的值為0x3000000C ,因此ldr pc, _undef_addr 就等價于ldr pc, =0x3000000C。

(這里起始地址為0x30000000是因為鏈接腳本的原因, 實際的存儲地址需要減去0x30000000)

在這里插入圖片描述

代碼跳到0x3000000C的位置,也就是_undef: 標號的位置。接下來代碼就會按照之前所說的流程來處理。


2.2.1 設置SP

ldr sp, =0x34000000, 重新設置棧,原因有2個:


在發生未定義中斷前,ARM就進入了未定義模式,此時sp(r13)寄存器是此模式下私有的寄存器, 不理解請看 s3c2440學習之路-012-0 異常中斷基礎知識

后續需要跳轉到C函數和壓棧操作,這些都依賴sp寄存器


2.2.2 保存現場

stmdb sp!, {r0-r12, lr}, stmdb是用來存儲多個寄存器的指令。意思是以sp的數值為基礎(0x340000000),依次把r0-r12, lr寄存器入棧保存起來。如果不懂麻煩上網查查,這里不做過深的解釋。


2.2.3 打印數值和點亮led燈

    mrs r0, cpsr

    ldr r1, =undef_string

    bl printf_undef


    mov r0, #4

    bl led_off

    /* resume register */

    ldmia sp!, {r0-r12, pc}^ /*^ means copy spsr to cpsr */


undef_string:

    .string "Undefinf Excption!"

    .align 4


通過mrs命令把cpsr 的數值讀給r0, 把字符串"Undefinf Excption!" 傳遞給r1, 然后調用printf_undef 函數。


printf_undef 函數就是先打印傳進來的字符串,然后輸出cpsr的數值。

如果不清楚為什么要把值傳給r0, r1 請看s3c2440學習之路-003 匯編給C傳參數 點亮不同led燈


void printf_undef(unsigned int cpsr_status, char *string)

{

    printf("%sn", string);

    printf("cpsr:0x%xn", cpsr_status);

}


最終的實驗結果就是, 先打印hello, 然后輸出Undefinf Excption! ,cpsr的數值為0x600000db

0xdb=1101 1011b, 最低5位為11011b 也就是Undefinded模式。從這里可以看出,ARM確實進入了未定義模式。

在這里插入圖片描述

在這里插入圖片描述

2.2.4 恢復現場

ldmia sp!, {r0-r12, pc}^, 一條指令就搞定了。ldmia 對應前面的stmdb 命令,一個是把寄存器入棧存起來,一個是把寄存器出棧取出來。這里有2小點要注意


保存的時候是stmdb sp!, {r0-r12, lr}, 而取出時是ldmia sp!, {r0-r12, pc}^, 最后面一個是保存lr, 一個是取出pc, 就等價于把lr 賦值給pc了

ldmia sp!, {r0-r12, pc}^ 最后還有一個"^" 符號, 意思是把spsr賦值給cpsr

這么簡單的一條指令就完成了3個動作:


把之前保存的寄存器r0-r12恢復

spsr賦值給cpsr

lr 賦值給pc ,代碼就回到了發生未定義中斷前的位置了


2.3 總結

通過2.2 節 可以看出,對于未定義中斷,整個軟件部分的處理流程就如同1.2 小節說所的。

這里還有一個小疑問,問什么程序的一開始就是2條跳轉指令呢?

2條跳轉指令的地址分別位于0x0, 0x4, 這是ARM在發生復位操作和未定義中斷時會去訪問的地址。這里拿未定義中斷來說,軟件的處理有很多步驟需要完成,因此一條指令是無法完成的,所以在0x4的位置直接執行跳轉ldr pc, _undef_addr, 跳轉到其地方來完成,避免占用到0x0~0x1C的位置。(0x0 ~ 0x1C是ARM不同異常時的向量地址,不過目前我的程序只需要0x0 和0x4 不被占用)

因此,2440 uboot 的開頭就是一堆的跳轉指令,而且是按照下面的異常向量表來寫的。

在這里插入圖片描述

2440 uboot的start.S 開頭部分代碼

在這里插入圖片描述

3 遺留問題

3.1 bl print_hello

sdram_addr:


    bl uart0_init

    bl print_hello


    .word 0xdeadc0de


    bl print_hello


    ldr pc, =main /* abs jump to main */


loop:

    b loop


在執行 .word 0xdeadc0de 前先執行了bl print_hello。但是發現如果把bl print_hello 去掉后,就不會出現未定異常了,換句話說.word 0xdeadc0de 指令被忽視了。

由此做了一個小測試,在word 0xdeadc0de 后面打印了cpsr的值,分2種情況


去掉106 行的bl print_hello,bl printf_cpsr的值為:0x200000d3

保留106 行的bl print_hello,bl printf_cpsr的值為:0x600000d3

唯一的不同就是最高的拿一個字節,一個是0x2, 一個是0x6。查看手冊可以得到, 0x2表示進為,0x6 表示溢出位,為何0x2不會被執行,而0x6會被執行,這里暫時不清楚,不過區別點就在這里。我們這里的原因就是word 0xdeadc0de 指令被忽視了,沒有執行。


為了保證指令被執行,需要將0xdeadc0de 修改為0xeeadc0de , 這樣最高位就是1110b, 一定會被執行。這樣即使去掉106行的去掉106 行的bl print_hello, .word 0xeeadc0de 也一定會被執行,一定會產生未定義中斷。

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

進入單片機查看更多內容>>
相關視頻
  • TI 新一代 C2000? 微控制器:全方位助力伺服及馬達驅動應用

  • MSP430電容觸摸技術 - 防水Demo演示

  • 直播回放: Microchip Timberwolf? 音頻處理器在線研討會

  • 新唐 8051單片機教程

  • 基于靈動MM32W0系列MCU的指夾血氧儀控制及OTA升級應用方案分享

  • 基于靈動MM32SPIN系列MCU的無感FOC便攜冰箱應用方案分享

    相關電子頭條文章
萝卜大香蕉