這個(gè)2440test里面的中斷寫(xiě)的向量有些隱蔽,兜了很多個(gè)圈,也難怪這么難理解,下面就對這個(gè)東西抽絲剝繭,看清楚這究竟是一個(gè)怎么樣的過(guò)程。
中斷向量
bHandlerIRQ;handlerforIRQinterrupt
很自然,因為所有的單片機都是那樣,中斷向量一般放在開(kāi)頭,用過(guò)單片機的人都會(huì )很熟悉
那就不多說(shuō)了。
異常服務(wù)程序
這里不用中斷(interrupt)而用異常(exception),畢竟中斷只是異常的一種情況,呵呵
下面主要分析的是“中斷異常”說(shuō)白了,就是我們平時(shí)單片機里面用的中斷?。?!所有有器件
引起的中斷,例如TIMER中斷,UART中斷,外部中斷等等,都有一個(gè)統一的入口,那就是中斷
異常IRQ!然后從IRQ的服務(wù)函數里面分辨出,當前究竟是什么中斷,再跳轉到相應的中斷
服務(wù)程序。這樣看來(lái),ARM比單片機要復雜一些了,不過(guò)原理是不變的。
上面說(shuō)的就是思路,跟著(zhù)這個(gè)思路來(lái)接著(zhù)分析。
HandlerIRQ很明顯是一個(gè)標號,我們找到了
HandlerIRQHANDLERHandleIRQ
這里是一個(gè)宏定義,我們再找到這個(gè)宏,看他是怎么定義的:
MACRO
$HandlerLabelHANDLER$HandleLabel
$HandlerLabel
subsp,sp,#4;decrementsp(tostorejumpaddress)
stmfdsp!,{r0};PUSHtheworkregistertostack(lrdoesnotpushbecauseitreturntooriginal
address)
ldrr0,=$HandleLabel;loadtheaddressofHandleXXXtor0
ldrr0,[r0];loadthecontents(serviceroutinestartaddress)ofHandleXXX
strr0,[sp,#4];storethecontents(ISR)ofHandleXXXtostack
ldmfdsp!,{r0,pc};POPtheworkregisterandpc(jumptoISR)
MEND
用HandlerIRQ將這個(gè)宏展開(kāi)之后得到的結果實(shí)際是這樣的
HandlerIRQ
subsp,sp,#4;decrementsp(tostorejumpaddress)
stmfdsp!,{r0};PUSHtheworkregistertostack(lrdoesnotpushbecauseitreturntooriginal
address)
ldrr0,=HandleIRQ;loadtheaddressofHandleXXXtor0
ldrr0,[r0];loadthecontents(serviceroutinestartaddress)ofHandleXXX
strr0,[sp,#4];storethecontents(ISR)ofHandleXXXtostack
ldmfdsp!,{r0,pc};POPtheworkregisterandpc(jumptoISR)
至于具體的跳轉原理下面再說(shuō)
好了,這樣的話(huà)就容易看的多了,很明顯,HandlerIRQ還是一個(gè)標號,IRQ異常向量就是跳
轉到這里執行的,這里粗略看一下,應該是保存現場(chǎng),然后跳轉到真正的處理函數,那么很容易
發(fā)現了這么一句ldrr0,=HandleIRQ,沒(méi)錯,我們又找到了一個(gè)標號HandleIRQ,看來(lái)
真正的處理函數應該是這個(gè)HandleIRQ,繼續尋找
AREARamData,DATA,READWRITE
^_ISR_STARTADDRESS;_ISR_STARTADDRESS=0x33FF_FF00
HandleReset#4
HandleUndef#4
HandleSWI#4
HandlePabort#4
HandleDabort#4
HandleReserved#4
HandleIRQ#4
最后我們發(fā)現在這里找到了HandleIRQ,^其實(shí)就是MAP,這段程序的意思是,從_ISR_STARTADDRESS
開(kāi)始,預留一個(gè)變量,每個(gè)變量一個(gè)標號,預留的空間為4個(gè)字節,也就是32BIT,其實(shí)這里放的是真正
的C寫(xiě)的處理函數的地址,說(shuō)白了,就是函數指針--
這樣做的話(huà)就很靈活了
接著(zhù),我們需要安裝IRQ處理句柄,說(shuō)白了,就是設置處理函數的地址,讓PC指針可以正確的跳轉。
于是我們在接著(zhù)的找到安裝句柄的語(yǔ)句
;SetupIRQhandler
ldrr0,=HandleIRQ;Thisroutineisneeded
ldrr1,=IsrIRQ;ifthereisnotsubspc,lr,#4at0x18,0x1c
strr1,[r0]
說(shuō)白了就是將IsrIRQ的地址填到HandleIRQ對應的地址里面,前面說(shuō)了HandleIRQ放的是中斷處理的
函數的入口地址,我們繼續找IsrIRQ
IsrIRQ
subsp,sp,#4;reservedforPC
stmfdsp!,{r8-r9}
ldrr9,=INTOFFSET
ldrr9,[r9]
ldrr8,=HandleEINT0
addr8,r8,r9,lsl#2
ldrr8,[r8]
strr8,[sp,#8]
ldmfdsp!,{r8-r9,pc}
要理解這個(gè)代碼,得先學(xué)學(xué)2440的中斷系統了,INTOFFSET存放的是當前中斷的偏移號,根據偏移就知道
當前是哪個(gè)中斷源發(fā)生的中斷。
注意了,我們說(shuō)的是中斷,而不是異常,看看原來(lái)的表是啥樣子的
^_ISR_STARTADDRESS;_ISR_STARTADDRESS=0x33FF_FF00
HandleReset#4
HandleUndef#4
HandleSWI#4
HandlePabort#4
HandleDabort#4
HandleReserved#4
HandleIRQ#4
HandleFIQ#4
HandleEINT0#4
HandleEINT1#4
HandleEINT2#4
HandleEINT3#4
.......
可以看到,前面幾個(gè)是異常,從HandleEINT0就是IRQ異常的向量存放的地方了,這樣就可以理解為
什么上面IsrIRQ里面里面要執行那條指令
ldrr8,=HandleEINT0
addr8,r8,r9,lsl#2
道理很簡(jiǎn)單,HandleEINT0就是所有IRQ中斷向量表的入口,在這個(gè)地址上面,加上一個(gè)適當的偏移量,
INTOFFSET,那么我們知道現在,到底是哪個(gè)IRQ在申請中斷了。
至于具體怎么跳轉的?
首先,我們說(shuō)了,HandleEINT0開(kāi)始的一段內存里面,存放的就是中斷服務(wù)函數的函數指針,ARM的體系
的話(huà),每個(gè)指針變量就是占4個(gè)字節,這里就解釋了,為什么這里為每個(gè)標號分配了4個(gè)字節的空間,里面
放的就是函數指針?。?!下面再看看怎么跳轉,繼續看IsrIRQ里面就實(shí)現了跳轉了
strr8,[sp,#8]
ldmfdsp!,{r8-r9,pc}
其實(shí)最核心就是這兩句了,先查找到當前中斷服務(wù)程序的地址,將他放到R8里面,然后出棧,彈出給PC
那么PC很自然就跳到中斷服務(wù)程序了。至于這里的堆棧問(wèn)題又是一個(gè)非常棘手的,需要好好的參透ARM的
中斷架構,需要了解的可以自己仔細的閱讀《ARM體系結構與編程》里面說(shuō)的很詳細。我們這里的重點(diǎn)
是研究怎么跳轉。
最后,我們看看在C代碼中是怎么安裝終端向量的,例如看按鍵的外部中斷,是怎么具體設置的,參看
/src/keyscan.c里面的代碼
很簡(jiǎn)單,里面只有3個(gè)函數
KeyScan_Test是按鍵測試的主函數
Key_ISR是按鍵中斷服務(wù)函數
在KeyScan_Test里面,我們發(fā)現了有這么一句
pISR_EINT0=pISR_EINT2=pISR_EINT8_23=(U32)Key_ISR;
可以理解否?Key_ISR就是上面提到的按鍵中斷服務(wù)函數,函數的名字,代表的就是函數的地址?。。?!
將中斷服務(wù)函數的地址,注意了,是地址,這是一個(gè)U32型的變量。送到幾個(gè)變量,我們以pISR_EINT0
作為例子,查看頭文件定義,在2440addr.h里面找到
//Interruptvector
#definepISR_EINT0(*(unsigned*)(_ISR_STARTADDRESS+0x20))
_ISR_STARTADDRESS有沒(méi)有似曾相識的感覺(jué)?沒(méi)錯,剛才分析的匯編代碼里面就提到了
^_ISR_STARTADDRESS;_ISR_STARTADDRESS=0x33FF_FF00
HandleReset#4
HandleUndef#4
......
對,地址就是這里,然后_ISR_STARTADDRESS+0x20就是跳過(guò)前面的異常向量,進(jìn)入IRQ中斷向量的入口
所以說(shuō)到尾
pISR_EINT0=(U32)Key_ISR;
完成的操作就是,將Key_ISR的地址存放到
HandleEINT0#4
這個(gè)IRQ向量表里面?。。?!
當按鍵中斷發(fā)生的時(shí)候,發(fā)生IRQ異常中斷
當前PC值-4保存到LR_IRQ里面,然后執行
bHandlerIRQ
然后是執行
HandlerIRQ
subsp,sp,#4;預留一個(gè)用來(lái)存放PC地址
stmfdsp!,{r0};保存R0,因為下面使用了
ldrr0,=HandleIRQ;將HandleIRQ(服務(wù)程序)的地址裝載到R0
ldrr0,[r0]
strr0,[sp,#4];保存到剛才預留的地方
ldmfdsp!,{r0,pc};彈出堆棧,恢復R0,并且將剛才計算好的HandleIRQ地址彈出到PC
堆棧是向下生長(cháng)的,所以SUBSP,SP,#4就相當于PUSHXX,但是這個(gè)XX這個(gè)時(shí)候并沒(méi)有用,因為這里
用的是強制移動(dòng)SP指針實(shí)現的。然后得到服務(wù)程序的地址,再將這個(gè)值放回剛才預留的棧的空位上面,最后
就是POP出R0恢復,并且將剛才得到的服務(wù)程序的地址送到PC,那么實(shí)現的效果就是跳轉到HandleIRQ里面了。
接著(zhù)看剛才是怎么安裝的HandleIRQ的
;SetupIRQhandler
ldrr0,=HandleIRQ;Thisroutineisneeded
ldrr1,=IsrIRQ;ifthereisnotsubspc,lr,#4at0x18,0x1c
strr1,[r0]
可以看出,這里將IsrIRQ的地址的值保存到HandleIRQ中,也就是說(shuō),上面的IRQ服務(wù)程序,這個(gè)時(shí)候實(shí)際
上就是指IsrIRQ!
所以接著(zhù)的事情就是轉移到IsrIRQ中執行:
IsrIRQ
subsp,sp,#4;預留一個(gè)值來(lái)保存PC
stmfdsp!,{r8-r9}
ldrr9,=INTOFFSET;計算偏移量,下面解釋
ldrr9,[r9]
ldrr8,=HandleEINT0
addr8,r8,r9,lsl#2
ldrr8,[r8]
strr8,[sp,#8];因為保存了2個(gè)寄存器R8R9,所以SP下移了8位
ldmfdsp!,{r8-r9,pc};恢復寄存器,彈出到PC,同上面的一樣
怎么保存,操作SP,跟最后彈出到PC的部分和上面的例子一樣,下面說(shuō)說(shuō)中間的計算部分
計算偏移量,其實(shí)原理很簡(jiǎn)單,首先INTOFFSET保存著(zhù)當前是哪個(gè)IRQ中斷,例如0代表著(zhù)HandleEINT0,1代表
HandleEINT1.....等等,這不是亂來(lái),有一個(gè)表的,這個(gè)是由S3C2440的datasheet說(shuō)的,自己可以去查看。
然后得到中斷處理函數的向量表,這個(gè)表的首地址就是HandleEINT0,那么很自然的想到,怎么查表?那還不簡(jiǎn)
單?HandleEINT0+INTOFFSET不就完了?基地址加偏移量就得到表中某項了,當然,因為這里是中斷處理向量
每一項占用4個(gè)字節,所以用lsl#2處理一下,左移2位相當于乘以4,偏移量乘以4,這應該很好理解的。
我們這個(gè)例子找到的就是HandleEINT0,將里面的值讀出來(lái),里面放的是HandleEINT0服務(wù)函數的地址,這個(gè)
地址怎么來(lái)的?是在C程序里面設置的。我們看keyscan.c程序,找到一個(gè)voidKeyScan_Test(void)函數,
里面有這么一句:
pISR_EINT0=pISR_EINT2=pISR_EINT8_23=(U32)Key_ISR;
這里是安裝了3個(gè)按鍵中斷服務(wù)程序,我們只關(guān)注0號中斷,也就是
pISR_EINT0=(U32)Key_ISR;
這句話(huà)什么意思?先看看pISR_EINT0的定義,在2440addr.h中定義
#definepISR_EINT0(*(unsigned*)(_ISR_STARTADDRESS+0x20))
看到?jīng)]有?_ISR_STARTADDRESS不就是剛才說(shuō)的那個(gè)異常向量的入口地址?加上一個(gè)0x20
之后實(shí)際上指向的,就是HandleEINT0?。?!這么說(shuō)來(lái),上面的意思就是,將Key_ISR處理函數的入口地址,送
到HandleEINT0中。
再來(lái)看Key_ISR,這是一個(gè)典型的服務(wù)程序,加了_irq作為編譯關(guān)鍵字,告訴編譯器,這個(gè)函數是中斷服務(wù)程序
得保存需要的寄存器,免得被破壞。具體可以參考《ARM體系結構與編程》P283頁(yè)的描述。
staticvoid__irqKey_ISR(void)
{
.......
}
加上_irq關(guān)鍵字之后,編譯器就會(huì )處理好所有的保存動(dòng)作了,并不需要多關(guān)心。但是這個(gè)是ARM-CC編譯器的關(guān)
鍵字,GCC中并沒(méi)有這個(gè)東西,所以GCC處理中斷的時(shí)候最好還是自己保存一下。
到這里為止,整個(gè)中斷的過(guò)程就解釋完畢。分析的過(guò)程中確實(shí)學(xué)習了很多。
本文引用地址:http://dyxdggzs.com/article/201611/318875.htm
評論