近來爲了學習Linux嵌入式系統的移植,買了塊OK6410A的開發板,當然從裸機程序的開發開始了,然後不可避免遇到了按鍵中斷這樣的程序,下面按照思考的過程,寫下總結吧!
一:首先,弄清楚硬件連接,通過查看原理圖,得到了這樣的硬件連接
KEYINT1-GPN0-EINT_G0_0
KEYINT2-GPN1-EINT_G0_1
KEYINT3-GPN2-EINT_G0_2
KEYINT4-GPN3-EINT_G0_3
KEYINT5-GPN4-EINT_G0_4
KEYINT6-GPN5-EINT_G0_5,然後就去S3C6410datasheet尋找對應的端口說明吧,一看不知道,看了嚇一跳,三星這個英文文檔寫的,感覺忒不嚴謹了,有叫“External Interrupt Group 1”的,有叫“Ext. Interrupt Group 8”的,還有“Ext. Interrupt Group”的,其實他們都是並列關係,外部中斷的9個Group,你說爲啥不統一寫成“Ext. Interrupt Group0、Ext. Interrupt Group1”這樣呢,非要一會兒縮寫一會兒把0都省去了,害得我前後看了幾遍GPIO部分。
既然端口對應上了外部中斷EINT,決定去統一的整理下S3C6410的中斷機制。
1、外部中斷源控制器EINT,他一共被分爲9個組,每組裏面對應不同的IO管腳,S3C6410共有127個外部中斷,其外接I/O引腳及分組如下:
外部中斷組0 EINT_G0 GPN0---GPN15 、GPL8---GPL14、GPM0---GPM4
外部中斷組1 EINT_G1 GPA0---GPA7、GPB0---GPB6
外部中斷組2 EINT_G2 GPC0---GPC7
外部中斷組3 EINT_G3 GPD0---GPD5
外部中斷組4 EINT_G4 GPF0---GPF14
外部中斷組5 EINT_G5 GPG0---GPG7
外部中斷組6 EINT_G6 GPH0---GPH9
外部中斷組7 EINT_G7 GPO0---GPO15
外部中斷組8 EINT_G8 GPP0---GPP14
外部中斷組9 EINT_G9 GPQ0---GPQ9
以上127個引腳每個引腳都可以產生一個外部中斷
2、ARM的總中斷控制器VIC,他由兩個VIC組成,聯合起來控制了64箇中斷源,每個控制32個,因此我們發現VIC的控制寄存器都是對應的有VIC0就有VIC1,其中我們找到了與外部中斷有關的127個外部中斷在VIC裏的中斷號的對應關係:
NO. 中斷源 說明明 GROUP
0 INT_EINT0 外部中斷組0 (EINT_G0)引腳號0-3 VIC0
1 INT_EINT1 外部中斷組0 (EINT_G0)引腳號4-11 VIC0
32 INT_EINT2 外部中斷組0 (EINT_G0)引腳號12-19 VIC1
33 INT_EINT3 外部中斷組0 (EINT_G0)引腳號20-27 VIC1
53 INT_EINT4 外部中斷組1-9 (EINT_G1~9) VIC1
我們發現屬於外部中斷組0的27箇中斷佔用了VIC裏的4箇中斷號,外部中斷組1-9只佔用了1箇中斷號,世界就是這麼的不公平,努力吧!
3、S3C6410的中斷流程,說到這裏,大家是不是覺得有點亂(如果使用過STM32應該不會亂,因爲STM32的中斷也是兩級控制的),我們總結下具體的中斷流程:
外設(GPIO)——》EINT——》VIC——?,因此對應的我們編程序也就是
外設配置——》EINT配置——VIC配置——?,這是我們現在所能想到的,大家也覺得分析的不錯吧,可惜錯誤也再此埋下了伏筆,即是問號處,
二: 前面大概講了一下外部中斷的流程,現在我就開始按照此流程,結合具體的硬件進行整理和編程了。下面講到具體的寄存器都按照OK6410開發板對應的GPN講解。
1、GPIO的配置
相信你已經開始寫中斷程序了,基礎的IO控制LED的程序肯定已經寫過了,那一定知道控制GPIO一般就如下幾個寄存器:
GPNCON——端口控制寄存器、可讀可寫
GPNDAT——端口數據寄存器、可讀可寫
GPNPUD——端口上下拉寄存器、可讀可寫,對於按鍵中斷,顯然我們只需要用到GPNCON,將端口設置爲中斷模式即可,按照下圖,將對應的位設置爲10即可。
rGPNCON &= ~((0x03<<0)|(0x03<<2)|(0x03<<4)|(0x03<<6)|(0x03<<8)|(0x03<<10));//把兩位都變爲00,也可以寫成~(0xfff<<0)
rGPNCON |= ((0x02<<0)|(0x02<<2)|(0x02<<4)|(0x02<<6)|(0x02<<8)|(0x02<<10));//把前一位變爲1,也可寫成0xaaa<<0
此處爲了更清楚的表明設置的過程,設置的稍有複雜,大家可以直接簡化爲註釋後面的,同時爲了不對其他端口造成影響,嚴格採用了對應的與或操作。
2、EINT配置,對應前面描述的中斷知識,本按鍵GPN0~5只涉及到了GROUP0的中斷EINT即EINT_G0,具體說是EINT_G0_0~5。
關於EINT的配置,我們先總結一下可能涉及到的寄存器:
總結下來就是四類,EINT控制寄存器,濾波控制寄存器,中斷屏蔽寄存器,中斷掛載寄存器,當然還有關於優先級等寄存器,如PRIORTY,SERVICE,SERVICEPEND這些,比較高級的功能,我也沒有深入,此處暫不表。
此處說一下常用的這三類,(1)EINT控制寄存器,用來控制外部中斷的觸發方式,比如此處可以設定爲低電平觸發。
//設置外部中斷EINT觸發方式低電平觸發爲x000,最高位忽略,默認爲x000,兩個共用一個
rEINT0CON0 &= ~((0x07<<0)|(0x07<<4)|(0x07<<8));//把三位都變爲0,也可以寫爲~(0x777<<0)
(2)中斷屏蔽寄存器EIN0MASK,可讀可寫,寫1屏蔽中斷,寫0使能中斷。此時,我們首先肯定要使能按鍵中斷口。
//設置中斷使能,即將中斷屏蔽寄存器置0,
rEINT0MASK &= ~((0x01<<0)|(0x01<<1)|(0x01<<2)|(0x01<<3)|(0x01<<4)|(0x01<<5));//把一位變爲0,也可以寫成~(0x3f<<0)
(3)中斷掛載寄存器EINT0PEND,可讀可寫,讀1表示發生中斷,讀0表示未發生中斷,寫1表示清除外部中斷。
//清楚外部中斷掛載,即外部中斷掛載寄存器寫1,清楚中斷
rEINT0PEND |= ((0x01<<0)|(0x01<<1)|(0x01<<2)|(0x01<<3)|(0x01<<4)|(0x01<<5));//把一位變爲1,也可以寫成(0x3f<<0)
三:
VIC的配置,對應前面講的對應前面描述的中斷知識,本按鍵GPN0~5只涉及到了GROUP0的中斷EINT即EINT_G0,具體說是EINT_G0_0~5。再次對應到VIC,我們看到其中GPN0~3對應了EINT_G0_0~3,對應了INT_EINT0;GPN4~5對應了EINT_G0_4~5,對應了INT_EINT1,這兩者都是在VIC0中,分屬NO0和NO1。
然後關於VIC我們總結下相應的寄存器:此處僅以VIC0爲例,選出了常用的一些寄存器,其他類似於中斷優先級,軟中斷等等,還沒有研究,需要共同學習。
(1)VIC0INTSELECT——中斷選擇寄存器,可讀可寫,0-選擇IRQ,1-選擇FIQ,FIQ更快,一般選IRQ,32位分別對應每一箇中斷源;
(2)VIC0INTENABLE與VIC0INTENCLEAR,這兩者是一對的,其中VIC0INTENABLE——系統中斷使能寄存器,可讀可寫,0-中斷禁止,1-中斷使能,一般只能用來使能中斷,不能用來寫0禁止中斷,禁止中斷使用VIC0INTENCLEAR——系統中斷清除,可寫,寫0無效,寫1-禁止中斷,大家注意配合使用,32位分別對應每一箇中斷源;
(3)VIC0VECTADDR[0~31] ——矢量地址寄存器,即對應的中斷服務程序的入口地址,一共32個地址,對應VIC0的32箇中斷源。可讀可寫;
(4)VIC0ADDRESS——又一個矢量地址寄存器,糊塗了吧,記住它的功能就行,通過向其寫入任意值,就可以清除中斷服務標誌,類似於外部中斷EINT中的EINT0PEND了,我們可以通過它在中斷服務程序中清除系統中斷標誌,32位分別對應每一箇中斷源;
介紹完了這些VIC對應的寄存器,現在開始說說其配置的流程:
(1) 首先肯定是需要設定中斷的類型IRQ還是FIQ,配置之前呢,先把對應的中斷關了,因此,先需要禁止對應的系統中斷。
//關閉對應的中斷,對應位寫1關閉系統中斷,只能寫32位,同rVIC1INTENCLEAR一起與rVIC0INTENABLE/rVIC1INTENABLE配套
rVIC0INTENCLEAR |= (0x01<<0|(0x01<<1));//外部中斷0_0~5屬於系統中斷INT_EINT0/INT_EINT1
(2)那其次就是上面說的設定中斷的類型IRQ還是FIQ了。
//設置系統中斷類型,對應位寫0-IRQ,1-FIQ,只能寫32位,同rVIC1INTSELECT一起
rVIC0INTSELECT &= ~((0x01<<0)|(0x01<<1));//外部中斷0_0~5屬於系統中斷INT_EINT0/INT_EINT1
(3)然後呢清除系統中斷標誌
/清除系統中斷,對應位寫0或者1都行,只能寫32位,同rVIC1ADDRESS一起
rVIC0ADDRESS &= ~((0x01<<0)|(0x01<<1));//外部中斷0_0~5屬於系統中斷INT_EINT0/INT_EINT1
(4)設定中斷服務程序的入口地址
//寫中斷處理程序地址,rVIC0VECTADDR
VIC0VECTADDR[0] = (unsigned)Key1_handler;//unsigned說明是32位地址
VIC0VECTADDR[1] = (unsigned)Key2_handler;//unsigned說明是32位地址,這裏需要說明,這邊是32個32位的數組對應32箇中斷源的處理地址,因此宏定義的時候需要注意,此處宏定義跟其他寄存器定義有區別。
#define VIC0VECTADDR (( unsigned *)(0x71200100))
#define VIC1VECTADDR (( unsigned *)(0x71300100))
(5)使能系統中斷
//使能系統中斷,對應位寫1使能系統中斷,只能寫32位,同rVIC1INTENABLE一起與rVIC0INTENCLEAR/rVIC1INTENCLEAR配套
rVIC0INTENABLE |= (0x01<<0|(0x01<<1));//外部中斷0_0~5屬於系統中斷INT_EINT0/INT_EINT1
、GPIO的配置
這樣一個簡單的系統中斷VIC配置就算完成了。
4、接下來就是中斷服務程序了,這個大家都弄膩了,需要注意的就是清除中斷標誌,從上面可以看出,這裏需要清除兩個中斷標誌,分別是外部中斷EINT和系統中斷VIC的標誌,例如rVIC0ADDRESS &= ~(0x01<<0);//清除系統中斷INT_EINT0
以及rEINT0PEND |= (0x01<<0);//清除外部中斷0_0
例外可以通過讀rEINT0PEND的某一位,知道具體是外部中斷EINT_G0的哪一個口發生中斷。
大家是不是覺得這樣就OK了呢,我也是這麼想的,然後就下載程序進行了在線調試,可惜啥效果沒有,然後又認真的把每一個寄存器又都檢查了一遍,還是沒有發現錯誤,疑惑不解啊。這就又回到了一開始埋下的伏筆那,
四:
經過前面的準備,0K6410的按鍵中斷基本搞定了,但是還有一個地方被我們大家遺漏了,於是我跟衆多網友一樣,停滯在這裏,調不通按鍵中斷了,沒辦法啊,中斷就是不響應。
於是又回到了一開始埋下的伏筆那,S3C6410的中斷響應流程那,外設(GPIO)——》EINT——》VIC——?,因此對應的我們編程序也就是外設配置——》EINT配置——VIC配置——?,這個問號處,是被我們忽視的地方。(此處各種上網查找,也有好多網友給出了正確答案,這裏不一一致謝了哈,反正是各種實驗哦)
下面是S3C6410應用筆記(apnv1.0)中斷部分的截圖,通過圖我們發現,第一部分,我們沒有做啊,即使能VIC接口(enable vic port)。當然還有另外一個方法,不太常用,一般都用這個調用VIC PORT。
按照筆記,使能VIC PORT,這裏給出了一段彙編代碼,尼瑪,彙編不太會啊,這個放在哪兒呢,如果放在C程序中,需要內嵌彙編,使用asm又報錯,沒能解決,參考網上,有人放在了啓動代碼中,果斷複製到啓動代碼中。
;------------------------------------
; Enable VIC Port
;------------------------------------
mrc p15,0,r0,c1,c0,0
orr r0,r0,#(1<<24)
mcr p15,0,r0,c1,c0,0
到了這裏,好多網友都運行成功了,可是我這裏還是沒有運行成功,這又讓我很鬱悶,最終在網上衆多博客中,找到了一篇http://blog.csdn.net/tankai19880619/article/details/8310050
,他裏面另外還有一段彙編,使能IRQ,我把這段彙編copy到啓動代碼中,經過嘗試,成功了,哈哈,雖然他博客中寫的他自己沒有調試成功。這個哥們的博客http://blog.csdn.net/yin138/article/details/6738917中也有類似的這段代碼。
;------------------------------------
; Enable IRQ
;------------------------------------
mrs r0,cpsr
bic r0,r0,#0x80
msr cpsr_c,r0
就這樣,最終按鍵中斷的功能得以實現,真是一波三折啊。最終彙總一下整個流程:
外設(GPIO)——》EINT——》VIC——協處理器,因此對應的我們編程流程也就是
外設配置——》EINT配置——VIC配置——使能VIC PORT和使能IRQ