1)實驗平台:正點原子Linux開發闆
2)摘自《正點原子I.MX6U嵌入式Linux驅動開發指南》關注官方微信号公衆号,獲取更多資料:正點原子
第十七章GPIO中斷試驗
中斷系統是一個處理器重要的組成部分,中斷系統極大的提高了CPU的執行效率,在學習STM32的時候就經常用到中斷。本章就通過與STM32的對比來學習一下Cortex-A7(I.MX6U)中斷系統和Cortex-M(STM32)中斷系統的異同,同時,本章會将I.MX6U的一個IO作為輸入中斷,借此來講解如何對I.MX6U的中斷系統進行編程。
17.1 Cortex-A7中斷系統詳解
17.1.1 STM32中斷系統回顧
STM32的中斷系統主要有以下幾個關鍵點:
①、中斷向量表。
②、NVIC(内嵌向量中斷控制器)。
③、中斷使能。
④、中斷服務函數。
1、中斷向量表
中斷向量表是一個表,這個表裡面存放的是中斷向量。中斷服務程序的入口地址或存放中斷服務程序的首地址成為中斷向量,因此中斷向量表是一系列中斷服務程序入口地址組成的表。這些中斷服務程序(函數)在中斷向量表中的位置是由半導體廠商定好的,當某個中斷被觸發以後就會自動跳轉到中斷向量表中對應的中斷服務程序(函數)入口地址處。中斷向量表在整個程序的最前面,比如STM32F103的中斷向量表如下所示:
示例代碼17.1.1.1 STM32F103中斷向量表
1 __Vectors DCD __initial_sp ; Top of Stack
2 DCD Reset_Handler ; Reset Handler
3 DCD NMI_Handler ; NMI Handler
4 DCD HardFault_Handler ; Hard Fault Handler
5 DCD MemManage_Handler ; MPU Fault Handler
6 DCD BusFault_Handler ; Bus Fault Handler
7 DCD UsageFault_Handler ; Usage Fault Handler
8 DCD 0 ; Reserved
9 DCD 0 ; Reserved
10 DCD 0 ; Reserved
11 DCD 0 ; Reserved
12 DCD SVC_Handler ; SVCall Handler
13 DCD DebugMon_Handler ; Debug Monitor Handler
14 DCD 0 ; Reserved
15 DCD PendSV_Handler ; PendSV Handler
16 DCD SysTick_Handler ; SysTick Handler
17
18 ; External Interrupts
19 DCD WWDG_IRQHandler ; Window Watchdog
20 DCD PVD_IRQHandler ; PVD through EXTI Line detect
21 DCD TAMPER_IRQHandler ; Tamper
22 DCD RTC_IRQHandler ; RTC
23 DCD FLASH_IRQHandler ; Flash
24
25 /* 省略掉其它代碼 */
26
27 DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & l5
28 __Vectors_End
“示例代碼17.1.1.1”就是STM32F103的中斷向量表,中斷向量表都是鍊接到代碼的最前面,比如一般ARM處理器都是從地址0X00000000開始執行指令的,那麼中斷向量表就是從0X00000000開始存放的。“示例代碼17.1.1.1”中第1行的“__initial_sp”就是第一條中斷向量,存放的是棧頂指針,接下來是第2行複位中斷複位函數Reset_Handler的入口地址,依次類推,直到第27行的最後一個中斷服務函數DMA2_Channel4_5_IRQHandler的入口地址,這樣STM32F103的中斷向量表就建好了。
我們說ARM處理器都是從地址0X00000000開始運行的,但是我們學習STM32的時候代碼是下載到0X8000000開始的存儲區域中。因此中斷向量表是存放到0X8000000地址處的,而不是0X00000000,這樣不是就出錯了嗎?為了解決這個問題,Cortex-M架構引入了一個新的概念——中斷向量表偏移,通過中斷向量表偏移就可以将中斷向量表存放到任意地址處,中斷向量表偏移配置在函數SystemInit中完成,通過向SCB_VTOR寄存器寫入新的中斷向量表首地址即可,代碼如下所示:
示例代碼17.1.1.2 STM32F103中斷向量表偏移
1void SystemInit (void)
2{
3 RCC->CR |=(uint32_t)0x00000001;
4
5 /* 省略其它代碼 */
6
7 #ifdef VECT_TAB_SRAM
8 SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;
9 #else
10 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
11 #endif
12}
第8行和第10行就是設置中斷向量表偏移,第8行是将中斷向量表設置到RAM中,第10行是将中斷向量表設置到ROM中,基本都是将中斷向量表設置到ROM中,也就是地址0X8000000處。第10行用到了FALSH_BASE和VECT_TAB_OFFSET,這兩個都是宏,定義如下所示:
#define FLASH_BASE ((uint32_t)0x08000000)
#define VECT_TAB_OFFSET 0x0
因此第10行的代碼就是:SCB->VTOR=0X080000000,中斷向量表偏移設置完成。通過上面的講解我們了解了兩個跟STM32中斷有關的概念:中斷向量表和中斷向量表偏移,那麼這個跟I.MX6U有什麼關系呢?因為I.MX6U所使用的Cortex-A7内核也有中斷向量表和中斷向量表偏移,而且其含義和STM32是一模一樣的!隻是用到的寄存器不通而已,概念完全相同!
2、NVIC(内嵌向量中斷控制器)
中斷系統得有個管理機構,對于STM32這種Cortex-M内核的單片機來說這個管理機構叫做NVIC,全稱叫做Nested Vectored Interrupt Controller。關于NVIC本教程不作詳細的講解,既然Cortex-M内核有個中斷系統的管理機構—NVIC,那麼I.MX6U所使用的Cortex-A7内核是不是也有個中斷系統管理機構?答案是肯定的,不過Cortex-A内核的中斷管理機構不叫做NVIC,而是叫做GIC,全稱是general interrupt controller,後面我們會詳細的講解Cortex-A内核的GIC。
3、中斷使能
要使用某個外設的中斷,肯定要先使能這個外設的中斷,以STM32F103的PE2這個IO為例,假如我們要使用PE2的輸入中斷肯定要使用如下代碼來使能對應的中斷:
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //搶占優先級2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子優先級2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中斷通道
NVIC_Init(&NVIC_InitStructure);
上述代碼就是使能PE2對飲過的EXTI2中斷,同理,如果要使用I.MX6U的某個中斷的話也需要使能其對應的中斷。
4、中斷服務函數
我們使用中斷的目的就是為了使用中斷服務函數,當中斷發生以後中斷服務函數就會被調用,我們要處理的工作就可以放到中斷服務函數中去完成。同樣以STM32F103的PE2為例,其中斷服務函數如下所示:
/*外部中斷2服務程序 */
void EXTI2_IRQHandler(void)
{
/*中斷處理代碼*/
}
當PE2引腳的中斷觸發以後就會調用其對應的中斷處理函數EXTI2_IRQHandler,我們可以在函數EXTI2_IRQHandler中添加中斷處理代碼。同理,I.MX6U也有中斷服務函數,當某個外設中斷發生以後就會調用其對應的中斷服務函數。
通過對STM32中斷系統的回顧,我們知道了Cortex-M内核的中斷處理過程,那麼Cortex-A内核的中斷處理過程是否是一樣的,有什麼異同呢?接下來我們帶着這樣的疑問來學習Cortex-A7内核的中斷系統。
17.1.2 Cortex-A7中斷系統簡介
跟STM32一樣,Cortex-A7也有中斷向量表,中斷向量表也是在代碼的最前面。Cortex-A7内核有8個異常中斷,這8個異常中斷的中斷向量表如表17.1.2.1所示:
表17.1.2.1 Cortex-A7中斷向量表表17.1.2.1 Cortex-A7中斷向量表
中斷向量表裡面都是中斷服務函數的入口地址,因此一款芯片有什麼中斷都是可以從中斷向量表看出來的。從表17.1.2.1中可以看出,Cortex-A7一共有8個中斷,而且還有一個中斷向量未使用,實際隻有7個中斷。和“示例代碼17.1.1.1”中的STM32F103中斷向量表比起來少了很多!難道一個能跑Linux的芯片隻有這7個中斷?明顯不可能的!那類似STM32中的EXTI9_5_IRQHandler、TIM2_IRQHandler這樣的中斷向量在哪裡?I2C、SPI、定時器等等的中斷怎麼處理呢?這個就是Cortex-A和Cotex-M在中斷向量表這一塊的區别,對于Cortex-M内核來說,中斷向量表列舉出了一款芯片所有的中斷向量,包括芯片外設的所有中斷。對于Cotex-A内核來說并沒有這麼做,在表17.1.2.1中有個IRQ中斷,Cortex-A内核CPU的所有外部中斷都屬于這個IQR中斷,當任意一個外部中斷發生的時候都會觸發IRQ中斷。在IRQ中斷服務函數裡面就可以讀取指定的寄存器來判斷發生的具體是什麼中斷,進而根據具體的中斷做出相應的處理。這些外部中斷和IQR中斷的關系如圖17.1.2.1所示:
圖17.1.2.1外部中斷和IRQ中斷關系
在圖17.1.2.1中,左側的Software0_IRQn~PMU_IRQ2_IRQ這些都是I.MX6U的中斷,他們都屬于IRQ中斷。當圖17.1.2.1左側這些中斷中任意一個發生的時候IRQ中斷都會被觸發,所以我們需要在IRQ中斷服務函數中判斷究竟是左側的哪個中斷發生了,然後再做出具體的處理。
在表17.1.2.1中一共有7個中斷,簡單介紹一下這7個中斷:
、複位中斷(Rest),CPU複位以後就會進入複位中斷,我們可以在複位中斷服務函數裡面做一些初始化工作,比如初始化SP指針、DDR等等。
、未定義指令中斷(Undefined Instruction),如果指令不能識别的話就會産生此中斷。
、軟中斷(Software Interrupt,SWI),由SWI指令引起的中斷,Linux的系統調用會用SWI指令來引起軟中斷,通過軟中斷來陷入到内核空間。
、指令預取中止中斷(Prefetch Abort),預取指令的出錯的時候會産生此中斷。
、數據訪問中止中斷(Data Abort),訪問數據出錯的時候會産生此中斷。
、IRQ中斷(IRQ Interrupt),外部中斷,前面已經說了,芯片内部的外設中斷都會引起此中斷的發生。
、FIQ中斷(FIQ Interrupt),快速中斷,如果需要快速處理中斷的話就可以使用此中。
在上面的7個中斷中,我們常用的就是複位中斷和IRQ中斷,所以我們需要編寫這兩個中斷的中斷服務函數,稍後我們會講解如何編寫對應的中斷服務函數。首先我們要根據表17.1.2.1的内容來創建中斷向量表,中斷向量表處于程序最開始的地方,比如我們前面例程的start.S文件最前面,中斷向量表如下:
示例代碼17.1.1.1 Cortex-A向量表模闆
1.global _start /* 全局标号 */
2
3 _start:
4 ldr pc,=Reset_Handler /* 複位中斷 */
5 ldr pc,=Undefined_Handler /* 未定義指令中斷 */
6 ldr pc,=SVC_Handler /* SVC(Supervisor)中斷 */
7 ldr pc,=PrefAbort_Handler /* 預取終止中斷 */
8 ldr pc,=DataAbort_Handler /* 數據終止中斷 */
9 ldr pc,=NotUsed_Handler /* 未使用中斷 */
10 ldr pc,=IRQ_Handler /* IRQ中斷 */
11 ldr pc,=FIQ_Handler /* FIQ(快速中斷)未定義中斷 */
12
13/* 複位中斷 */
14 Reset_Handler:
15 /* 複位中斷具體處理過程 */
16
17/* 未定義中斷 */
18 Undefined_Handler:
19 ldr r0,=Undefined_Handler
20 bx r0
21
22/* SVC中斷 */
23 SVC_Handler:
24 ldr r0,=SVC_Handler
25 bx r0
26
27/* 預取終止中斷 */
28 PrefAbort_Handler:
29 ldr r0,=PrefAbort_Handler
30 bx r0
31
32/* 數據終止中斷 */
33 DataAbort_Handler:
34 ldr r0,=DataAbort_Handler
35 bx r0
36
37/* 未使用的中斷 */
38 NotUsed_Handler:
39
40 ldr r0,=NotUsed_Handler
41 bx r0
42
43/* IRQ中斷!重點!!!!! */
44 IRQ_Handler:
45 /* 複位中斷具體處理過程 */
46
47/* FIQ中斷 */
48 FIQ_Handler:
49 ldr r0,=FIQ_Handler
50 bx r0
第4到11行是中斷向量表,當指定的中斷發生以後就會調用對應的中斷複位函數,比如複位中斷發生以後就會執行第4行代碼,也就是調用函數Reset_Handler,函數Reset_Handler就是複位中斷的中斷複位函數,其它的中斷同理。
第14到50行就是對應的中斷服務函數,中斷服務函數都是用彙編編寫的,我們實際需要編寫的隻有複位中斷服務函數Reset_Handler和IRQ中斷服務函數IRQ_Handler,其它的中斷本教程沒有用到,所以都是死循環。在編寫複位中斷複位函數和IRQ中斷服務函數之前我們還需要了解一些其它的知識,否則的話就沒法編寫。
17.1.3 GIC控制器簡介
1、GIC控制器總覽
STM32(Cortex-M)的中斷控制器叫做NVIC,I.MX6U(Cortex-A)的中斷控制器叫做GIC,關于GIC的詳細内容請參考開發闆光盤中的文檔《ARM Generic Interrupt Controller(ARM GIC控制器)V2.0.pdf》。
GIC是ARM公司給Cortex-A/R内核提供的一個中斷控制器,類似Cortex-M内核中的NVIC。目前GIC有4個版本:V1~V4,V1是最老的版本,已經被廢棄了。V2~V4目前正在大量的使用。GIC V2是給ARMv7-A架構使用的,比如Cortex-A7、Cortex-A9、Cortex-A15等,V3和V4是給ARMv8-A/R架構使用的,也就是64位芯片使用的。I.MX6U是Cortex-A内核的,因此我們主要講解GIC V2。GIC V2最多支持8個核。ARM會根據GIC版本的不同研發出不同的IP核,那些半導體廠商直接購買對應的IP核即可,比如ARM針對GIC V2就開發出了GIC400這個中斷控制器IP核。當GIC接收到外部中斷信号以後就會報給ARM内核,但是ARM内核隻提供了四個信号給GIC來彙報中斷情況:VFIQ、VIRQ、FIQ和IRQ,他們之間的關系如圖17.1.3.1所示:
圖17.1.3.1中斷示意圖
在圖17.1.3.1中,GIC接收衆多的外部中斷,然後對其進行處理,最終就隻通過四個信号報給ARM内核,這四個信号的含義如下:
VFIQ:虛拟快速FIQ。
VIRQ:虛拟快速IRQ。
FIQ:快速中斷IRQ。
IRQ:外部中斷IRQ。
VFIQ和VIRQ是針對虛拟化的,我們讨論虛拟化,剩下的就是FIQ和IRQ了,我們前面都講了很多次了。本教程我們隻使用IRQ,所以相當于GIC最終向ARM内核就上報一個IRQ信号。那麼GIC是如何完成這個工作的呢?GICV2的邏輯圖如圖17.1.3.2所示:
圖17.1.3.2 GICV2總體框圖
圖17.1.3.1中左側部分就是中斷源,中間部分就是GIC控制器,最右側就是中斷控制器向處理器内核發送中斷信息。我們重點要看的肯定是中間的GIC部分,GIC将衆多的中斷源分為分為三類:
①、SPI(Shared Peripheral Interrupt),共享中斷,顧名思義,所有Core共享的中斷,這個是最常見的,那些外部中斷都屬于SPI中斷(注意!不是SPI總線那個中斷)。比如按鍵中斷、串口中斷等等,這些中斷所有的Core都可以處理,不限定特定Core。
②、PPI(Private Peripheral Interrupt),私有中斷,我們說了GIC是支持多核的,每個核肯定有自己獨有的中斷。這些獨有的中斷肯定是要指定的核心處理,因此這些中斷就叫做私有中斷。
③、SGI(Software-generated Interrupt),軟件中斷,由軟件觸發引起的中斷,通過向寄存器GICD_SGIR寫入數據來觸發,系統會使用SGI中斷來完成多核之間的通信。
2、中斷ID
中斷源有很多,為了區分這些不同的中斷源肯定要給他們分配一個唯一ID,這些ID就是中斷ID。每一個CPU最多支持1020個中斷ID,中斷ID号為ID0~ID1019。這1020個ID包含了PPI、SPI和SGI,那麼這三類中斷是如何分配這1020個中斷ID的呢?這1020個ID分配如下:
ID0~ID15:這16個ID分配給SGI。
ID16~ID31:這16個ID分配給PPI。
ID32~ID1019:這988個ID分配給SPI,像GPIO中斷、串口中斷等這些外部中斷,至于具體到某個ID對應哪個中斷那就由半導體廠商根據實際情況去定義了。比如I.MX6U的總共使用了128個中斷ID,加上前面屬于PPI和SGI的32個ID,I.MX6U的中斷源共有128 32=160個,這128個中斷ID對應的中斷在《I.MX6ULL參考手冊》的“3.2 Cortex A7 interrupts”小節,中斷源如表17.1.3.1所示:
表17.1.3.1 I.MX6U中斷源
限于篇幅原因,表17.1.3.1中并沒有給出I.MX6U完整的中斷源,完整的中斷源自行查閱《I.MX6ULL參考手冊》的3.2小節。打開裸機例程“9_int”,我們前面移植了NXP官方SDK中的文件MCIMX6Y2C.h,在此文件中定義了一個枚舉類型IRQn_Type,此枚舉類型就枚舉出了I.MX6U的所有中斷,代碼如下所示:
示例代碼17.1.3.1 中斷向量
1 #define NUMBER_OF_INT_VECTORS 160/* 中斷源160個,SGI PPI SPI*/
2
3typedefenum IRQn {
4 /* Auxiliary constants */
5 NotAvail_IRQn =-128,
6
7 /* Core interrupts */
8 Software0_IRQn =0,
9 Software1_IRQn =1,
10 Software2_IRQn =2,
11 Software3_IRQn =3,
12 Software4_IRQn =4,
13 Software5_IRQn =5,
14 Software6_IRQn =6,
15 Software7_IRQn =7,
16 Software8_IRQn =8,
17 Software9_IRQn =9,
18 Software10_IRQn =10,
19 Software11_IRQn =11,
20 Software12_IRQn =12,
21 Software13_IRQn =13,
22 Software14_IRQn =14,
23 Software15_IRQn =15,
24 VirtualMaintenance_IRQn =25,
25 HypervisorTimer_IRQn =26,
26 VirtualTimer_IRQn =27,
27 LegacyFastInt_IRQn =28,
28 SecurePhyTimer_IRQn =29,
29 NonSecurePhyTimer_IRQn =30,
30 LegacyIRQ_IRQn =31,
31
32 /* Device specific interrupts */
33 IOMUXC_IRQn =32,
34 DAP_IRQn =33,
35 SDMA_IRQn =34,
36 TSC_IRQn =35,
37 SNVS_IRQn =36,
…… ...... ......
151 ENET2_1588_IRQn =153,
152 Reserved154_IRQn =154,
153 Reserved155_IRQn =155,
154 Reserved156_IRQn =156,
155 Reserved157_IRQn =157,
156 Reserved158_IRQn =158,
157 PMU_IRQ2_IRQn =159
158} IRQn_Type;
3、GIC邏輯分塊
GIC架構分為了兩個邏輯塊:Distributor和CPU Interface,也就是分發器端和CPU接口端。這兩個邏輯塊的含義如下:
Distributor(分發器端):從圖17.1.3.2可以看出,此邏輯塊負責處理各個中斷事件的分發問題,也就是中斷事件應該發送到哪個CPU Interface上去。分發器收集所有的中斷源,可以控制每個中斷的優先級,它總是将優先級最高的中斷事件發送到CPU接口端。分發器端要做的主要工作如下:
①、全局中斷使能控制。
②、控制每一個中斷的使能或者關閉。
③、設置每個中斷的優先級。
④、設置每個中斷的目标處理器列表。
⑤、設置每個外部中斷的觸發模式:電平觸發或邊沿觸發。
⑥、設置每個中斷屬于組0還是組1。
CPU Interface(CPU接口端):CPU接口端聽名字就知道是和CPU Core相連接的,因此在圖17.1.3.2中每個CPU Core都可以在GIC中找到一個與之對應的CPU Interface。CPU接口端就是分發器和CPU Core之間的橋梁,CPU接口端主要工作如下:
①、使能或者關閉發送到CPU Core的中斷請求信号。
②、應答中斷。
③、通知中斷處理完成。
④、設置優先級掩碼,通過掩碼來設置哪些中斷不需要上報給CPU Core。
⑤、定義搶占策略。
⑥、當多個中斷到來的時候,選擇優先級最高的中斷通知給CPU Core。
例程“9_int”中的文件core_ca7.h定義了GIC結構體,此結構體裡面的寄存器分為了分發器端和CPU接口端,寄存器定義如下所示:
示例代碼17.1.3.2 GIC控制器結構體
/*
* GIC寄存器描述結構體,
* GIC分為分發器端和CPU接口端
*/
1typedefstruct
2{
3/* 分發器端寄存器 */
4uint32_t RESERVED0[1024];
5 __IOM uint32_t D_CTLR; /* Offset: 0x1000 (R/W) */
6 __IM uint32_t D_TYPER; /* Offset: 0x1004 (R/ ) */
7 __IM uint32_t D_IIDR; /* Offset: 0x1008 (R/ ) */
8uint32_t RESERVED1[29];
9 __IOM uint32_t D_IGROUPR[16];/* Offset: 0x1080 - 0x0BC (R/W) */
10uint32_t RESERVED2[16];
11 __IOM uint32_t D_ISENABLER[16];/* Offset: 0x1100 - 0x13C (R/W) */
12uint32_t RESERVED3[16];
13 __IOM uint32_t D_ICENABLER[16];/* Offset: 0x1180 - 0x1BC (R/W) */
14uint32_t RESERVED4[16];
15 __IOM uint32_t D_ISPENDR[16];/* Offset: 0x1200 - 0x23C (R/W) */
16uint32_t RESERVED5[16];
17 __IOM uint32_t D_ICPENDR[16]; /* Offset: 0x1280 - 0x2BC (R/W) */
18uint32_t RESERVED6[16];
19 __IOM uint32_t D_ISACTIVER[16];/* Offset: 0x1300 - 0x33C (R/W) */
20uint32_t RESERVED7[16];
21 __IOM uint32_t D_ICACTIVER[16];/* Offset: 0x1380 - 0x3BC (R/W) */
22uint32_t RESERVED8[16];
23 __IOM uint8_t D_IPRIORITYR[512];/* Offset: 0x1400 - 0x5FC (R/W) */
24uint32_t RESERVED9[128];
25 __IOM uint8_t D_ITARGETSR[512];/* Offset: 0x1800 - 0x9FC (R/W) */
26uint32_t RESERVED10[128];
27 __IOM uint32_t D_ICFGR[32];/* Offset: 0x1C00 - 0xC7C (R/W) */
28uint32_t RESERVED11[32];
29 __IM uint32_t D_PPISR; /* Offset: 0x1D00 (R/ ) */
30 __IM uint32_t D_SPISR[15]; /* Offset: 0x1D04 - 0xD3C (R/ ) */
31uint32_t RESERVED12[112];
32 __OM uint32_t D_SGIR; /* Offset: 0x1F00 ( /W) */
33uint32_t RESERVED13[3];
34 __IOM uint8_t D_CPENDSGIR[16];/* Offset: 0x1F10 - 0xF1C (R/W) */
35 __IOM uint8_t D_SPENDSGIR[16];/* Offset: 0x1F20 - 0xF2C (R/W) */
36uint32_t RESERVED14[40];
37 __IM uint32_t D_PIDR4; /* Offset: 0x1FD0 (R/ ) */
38 __IM uint32_t D_PIDR5; /* Offset: 0x1FD4 (R/ ) */
39 __IM uint32_t D_PIDR6; /* Offset: 0x1FD8 (R/ ) */
40 __IM uint32_t D_PIDR7; /* Offset: 0x1FDC (R/ ) */
41 __IM uint32_t D_PIDR0; /* Offset: 0x1FE0 (R/ ) */
42 __IM uint32_t D_PIDR1; /* Offset: 0x1FE4 (R/ ) */
43 __IM uint32_t D_PIDR2; /* Offset: 0x1FE8 (R/ ) */
44 __IM uint32_t D_PIDR3; /* Offset: 0x1FEC (R/ ) */
45 __IM uint32_t D_CIDR0; /* Offset: 0x1FF0 (R/ ) */
46 __IM uint32_t D_CIDR1; /* Offset: 0x1FF4 (R/ ) */
47 __IM uint32_t D_CIDR2; /* Offset: 0x1FF8 (R/ ) */
48 __IM uint32_t D_CIDR3; /* Offset: 0x1FFC (R/ ) */
49
50/* CPU接口端寄存器 */
51 __IOM uint32_t C_CTLR; /* Offset: 0x2000 (R/W) */
52 __IOM uint32_t C_PMR; /* Offset: 0x2004 (R/W) */
53 __IOM uint32_t C_BPR; /* Offset: 0x2008 (R/W) */
54 __IM uint32_t C_IAR; /* Offset: 0x200C (R/ ) */
55 __OM uint32_t C_EOIR; /* Offset: 0x2010 ( /W) */
56 __IM uint32_t C_RPR; /* Offset: 0x2014 (R/ ) */
57 __IM uint32_t C_HPPIR; /* Offset: 0x2018 (R/ ) */
58 __IOM uint32_t C_ABPR; /* Offset: 0x201C (R/W) */
59 __IM uint32_t C_AIAR; /* Offset: 0x2020 (R/ ) */
60 __OM uint32_t C_AEOIR; /* Offset: 0x2024 ( /W) */
61 __IM uint32_t C_AHPPIR; /* Offset: 0x2028 (R/ ) */
62uint32_t RESERVED15[41];
63 __IOM uint32_t C_APR0; /* Offset: 0x20D0 (R/W) */
64uint32_t RESERVED16[3];
65 __IOM uint32_t C_NSAPR0; /* Offset: 0x20E0 (R/W) */
66uint32_t RESERVED17[6];
67 __IM uint32_t C_IIDR; /* Offset: 0x20FC (R/ ) */
68uint32_t RESERVED18[960];
69 __OM uint32_t C_DIR; /* Offset: 0x3000 ( /W) */
70} GIC_Type;
“示例代碼17.1.3.2”中的結構體GIC_Type就是GIC控制器,列舉除了GIC控制器的所有寄存器,可以通過結構體GIC_Type來訪問GIC的所有寄存器。
第5行是GIC的分發器端相關寄存器,其相對于GIC基地址偏移為0X1000,因此我們獲取到GIC基地址以後隻需要加上0X1000即可訪問GIC分發器端寄存器。
第51行是GIC的CPU接口端相關寄存器,其相對于GIC基地址的偏移為0X2000,同樣的,獲取到GIC基地址以後隻需要加上0X2000即可訪問GIC的CPU接口段寄存器。
那麼問題來了?GIC控制器的寄存器基地址在哪裡呢?這個就需要用到Cortex-A的CP15協處理器了,下一小節就講解CP15協處理器。
17.1.4 CP15協處理器
關于CP15協處理器和其相關寄存器的詳細内容請參考下面兩份文檔:
《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》第1469頁“B3.17 Oranization ofthe CP15 registers in a VMSA implementation”。
《Cortex-A7 Technical ReferenceManua.pdf》第55頁“Capter4 System Control”。
CP15協處理器一般用于存儲系統管理,但是在中斷中也會使用到,CP15協處理器一共有16個32位寄存器。CP15協處理器的訪問通過如下另個指令完成:
MRC: 将CP15協處理器中的寄存器數據讀到ARM寄存器中。
MCR: 将ARM寄存器的數據寫入到CP15協處理器寄存器中。
MRC就是讀CP15寄存器,MCR就是寫CP15寄存器,MCR指令格式如下:
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
cond:指令執行的條件碼,如果忽略的話就表示無條件執行。
opc1:協處理器要執行的操作碼。
Rt:ARM源寄存器,要寫入到CP15寄存器的數據就保存在此寄存器中。
CRn:CP15協處理器的目标寄存器。
CRm:協處理器中附加的目标寄存器或者源操作數寄存器,如果不需要附加信息就将CRm設置為C0,否則結果不可預測。
opc2:可選的協處理器特定操作碼,當不需要的時候要設置為0。
MRC的指令格式和MCR一樣,隻不過在MRC指令中Rt就是目标寄存器,也就是從CP15指定寄存器讀出來的數據會保存在Rt中。而CRn就是源寄存器,也就是要讀取的寫處理器寄存器。
假如我們要将CP15中C0寄存器的值讀取到R0寄存器中,那麼就可以使用如下命令:
MRC p15, 0, r0, c0, c0, 0
CP15協處理器有16個32位寄存器,c0~c15,本章來看一下c0、c1、c12和c15這四個寄存器,因為我們本章實驗要用到這四個寄存器,其他的寄存器大家參考上面的兩個文檔即可。
1、c0寄存器
CP15協處理器有16個32位寄存器,c0~c15,在使用MRC或者MCR指令訪問這16個寄存器的時候,指令中的CRn、opc1、CRm和opc2通過不同的搭配,其得到的寄存器含義是不同的。比如c0在不同的搭配情況下含義如圖17.1.4.1所示:
圖17.1.4.1c0寄存器不同搭配含義
在圖17.1.4.1中當MRC/MCR指令中的CRn=c0,opc1=0,CRm=c0,opc2=0的時候就表示此時的c0就是MIDR寄存器,也就是主ID寄存器,這個也是c0的基本作用。對于Cortex-A7内核來說,c0作為MDIR寄存器的時候其含義如圖17.1.4.2所示:
圖17.1.4.2c0作為MIDR寄存器結構圖
在圖17.1.4.2中各位所代表的含義如下:
bit31:24:廠商編号,0X41,ARM。
bit23:20:内核架構的主版本号,ARM内核版本一般使用rnpn來表示,比如r0p1,其中r0後面的0就是内核架構主版本号。
bit19:16:架構代碼,0XF,ARMv7架構。
bit15:4:内核版本号,0XC07,Cortex-A7 MPCore内核。
bit3:0:内核架構的次版本号,rnpn中的pn,比如r0p1中p1後面的1就是次版本号。
2、c1寄存器
c1寄存器同樣通過不同的配置,其代表的含義也不同,如圖17.1.4.3所示:
圖17.1.4.3c1寄存器不同搭配含義
在圖17.1.4.3中當MRC/MCR指令中的CRn=c1,opc1=0,CRm=c0,opc2=0的時候就表示此時的c1就是SCTLR寄存器,也就是系統控制寄存器,這個是c1的基本作用。SCTLR寄存器主要是完成控制功能的,比如使能或者禁止MMU、I/D Cache等,c1作為SCTLR寄存器的時候其含義如圖17.1.4.4所示:
圖17.1.4.4c1作為SCTLR寄存器結構圖
SCTLR的位比較多,我們就隻看本章會用到的幾個位:
bit13:V , 中斷向量表基地址選擇位,為0的話中斷向量表基地址為0X00000000,軟件可以使用VBAR來重映射此基地址,也就是中斷向量表重定位。為1的話中斷向量表基地址為0XFFFF0000,此基地址不能被重映射。
bit12:I,I Cache使能位,為0的話關閉I Cache,為1的話使能I Cache。
bit11:Z,分支預測使能位,如果開啟MMU的話,此為也會使能。
bit10:SW,SWP和SWPB使能位,當為0的話關閉SWP和SWPB指令,當為1的時候就使能SWP和SWPB指令。
bit9:3:未使用,保留。
bit2:C,D Cache和緩存一緻性使能位,為0的時候禁止D Cache和緩存一緻性,為1時使能。
bit1:A,内存對齊檢查使能位,為0的時候關閉内存對齊檢查,為1的時候使能内存對齊檢查。
bit0:M,MMU使能位,為0的時候禁止MMU,為1的時候使能MMU。
如果要讀寫SCTLR的話,就可以使用如下命令:
MRC p15, 0, <Rt>, c1, c0, 0 ;讀取SCTLR寄存器,數據保存到Rt中。
MCR p15, 0, <Rt>, c1, c0, 0 ;将Rt中的數據寫到SCTLR(c1)寄存器中。
2、c12寄存器
c12寄存器通過不同的配置,其代表的含義也不同,如圖17.1.4.4所示:
圖17.1.4.4c12寄存器不同搭配含義
在圖17.1.4.4中當MRC/MCR指令中的CRn=c12,opc1=0,CRm=c0,opc2=0的時候就表示此時c12為VBAR寄存器,也就是向量表基地址寄存器。設置中斷向量表偏移的時候就需要将新的中斷向量表基地址寫入VBAR中,比如在前面的例程中,代碼鍊接的起始地址為0X87800000,而中斷向量表肯定要放到最前面,也就是0X87800000這個地址處。所以就需要設置VBAR為0X87800000,設置命令如下:
ldr r0, =0X87800000 ; r0=0X87800000
MCR p15, 0, r0, c12, c0, 0 ;将r0裡面的數據寫入到c12中,即c12=0X87800000
3、c15寄存器
c15寄存器也可以通過不同的配置得到不同的含義,參考文檔《Cortex-A7 Technical ReferenceManua.pdf》第68頁“4.2.16c15 registers”,其配置如圖17.1.4.5所示:
圖17.1.4.5c15寄存器不同搭配含義
在圖17.1.4.5中,我們需要c15作為CBAR寄存器,因為GIC的基地址就保存在CBAR中,我們可以通過如下命令獲取到GIC基地址:
MRC p15, 4, r1, c15, c0, 0 ; 獲取GIC基礎地址,基地址保存在r1中。
獲取到GIC基地址以後就可以設置GIC相關寄存器了,比如我們可以讀取當前中斷ID,當前中斷ID保存在GICC_IAR中,寄存器GICC_IAR屬于CPU接口端寄存器,寄存器地址相對于CPU接口端起始地址的偏移為0XC,因此獲取當前中斷ID的代碼如下:
MRC p15, 4, r1, c15, c0, 0 ;獲取GIC基地址
ADD r1, r1, #0X2000 ;GIC基地址加0X2000得到CPU接口端寄存器起始地址
LDR r0, [r1, #0XC] ;讀取CPU接口端起始地址 0XC處的寄存器值,也就是寄存器
;GIC_IAR的值
關于CP15協處理器就講解到這裡,簡單總結一下,通過c0寄存器可以獲取到處理器内核信息;通過c1寄存器可以使能或禁止MMU、I/D Cache等;通過c12寄存器可以設置中斷向量偏移;通過c15寄存器可以獲取GIC基地址。關于CP15的其他寄存器,大家自行查閱本節前面列舉的2份ARM官方資料。
17.1.5中斷使能
中斷使能包括兩部分,一個是IRQ或者FIQ總中斷使能,另一個就是ID0~ID1019這1020個中斷源的使能。
1、IRQ和FIQ總中斷使能
IRQ和FIQ分别是外部中斷和快速中斷的總開關,就類似家裡買的進戶總電閘,然後ID0~ID1019這1020個中斷源就類似家裡面的各個電器開關。要想開電視,那肯定要保證進戶總電閘是打開的,因此要想使用I.MX6U上的外設中斷就必須先打開IRQ中斷(本教程不使用FIQ)。在“6.3.2 程序狀态寄存器”小節已經講過了,寄存器CPSR的 I=1禁止IRQ,當I=0使能IRQ;F=1禁止FIQ,F=0使能FIQ。我們還有更簡單的指令來完成IRQ或者FIQ的使能和禁止,圖表17.1.5.1所示:
表17.1.5.1開關中斷指令
2、ID0~ID1019中斷使能和禁止
GIC寄存器GICD_ISENABLERn和GICD_ ICENABLERn用來完成外部中斷的使能和禁止,對于Cortex-A7内核來說中斷ID隻使用了512個。一個bit控制一個中斷ID的使能,那麼就需要512/32=16個GICD_ISENABLER寄存器來完成中斷的使能。同理,也需要16個GICD_ICENABLER寄存器來完成中斷的禁止。其中GICD_ISENABLER0的bit[15:0]對應ID15~0的SGI中斷,GICD_ISENABLER0的bit[31:16]對應ID31~16的PPI中斷。剩下的GICD_ISENABLER1~GICD_ISENABLER15就是控制SPI中斷的。
17.1.6 中斷優先級設置
1、優先級數配置
學過STM32都知道Cortex-M的中斷優先級分為搶占優先級和子優先級,兩者是可以配置的。同樣的Cortex-A7的中斷優先級也可以分為搶占優先級和子優先級,兩者同樣是可以配置的。Cortex-A7最多可以支持256個優先級,數字越小,優先級越高!半導體廠商自行決定選擇多少個優先級。I.MX6U選擇了32個優先級。在使用中斷的時候需要初始化GICC_PMR寄存器,此寄存器用來決定使用幾級優先級,寄存器結構如圖17.1.6.1所示:
圖17.1.6.1 GICC_PMR寄存器
GICC_PMR寄存器隻有低8位有效,這8位最多可以設置256個優先級,其他優先級數設置如表17.1.6.1所示:
表17.1.6.1優先級數設置
I.MX6U支持32個優先級,所以GICC_PMR要設置為0b11111000。
2、搶占優先級和子優先級位數設置
搶占優先級和子優先級各占多少位是由寄存器GICC_BPR來決定的,GICC_BPR寄存器結構如圖17.1.6.2所示:
圖17.1.6.2 GICC_BPR寄存器結構圖
寄存器GICC_BPR隻有低3位有效,其值不同,搶占優先級和子優先級占用的位數也不同,配置如表17.1.6.2所示:
表17.1.6.2 GICC_BPR配置表
為了簡單起見,一般将所有的中斷優先級位都配置為搶占優先級,比如I.MX6U的優先級位數為5(32個優先級),所以可以設置Binarypoint為2,表示5個優先級位全部為搶占優先級。
3、優先級設置
前面已經設置好了I.MX6U一共有32個搶占優先級,數字越小優先級越高。具體要使用某個中斷的時候就可以設置其優先級為0~31。某個中斷ID的中斷優先級設置由寄存器D_IPRIORITYR來完成,前面說了Cortex-A7使用了512個中斷ID,每個中斷ID配有一個優先級寄存器,所以一共有512個D_IPRIORITYR寄存器。如果優先級個數為32的話,使用寄存器D_IPRIORITYR的bit7:4來設置優先級,也就是說實際的優先級要左移3位。比如要設置ID40中斷的優先級為5,示例代碼如下:
GICD_IPRIORITYR[40] =5<< 3;
有關優先級設置的内容就講解到這裡,優先級設置主要有三部分:
①、設置寄存器GICC_PMR,配置優先級個數,比如I.MX6U支持32級優先級。
②、設置搶占優先級和子優先級位數,一般為了簡單起見,會将所有的位數都設置為搶占優先級。
③、設置指定中斷ID的優先級,也就是設置外設優先級。
17.2 硬件原理分析
本試驗用到的硬件資源和第十五章的硬件資源一模一樣。
,