龙空技术网

「正点原子Linux连载」第十七章GPIO中断试验(一)

正点原子原子哥 81

前言:

眼前同学们对“单片机的中断系统提高了cpu的工作效率”大体比较着重,姐妹们都需要剖析一些“单片机的中断系统提高了cpu的工作效率”的相关资讯。那么小编同时在网络上网罗了一些关于“单片机的中断系统提高了cpu的工作效率””的相关内容,希望看官们能喜欢,小伙伴们快快来学习一下吧!

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 硬件原理分析

本试验用到的硬件资源和第十五章的硬件资源一模一样。

标签: #单片机的中断系统提高了cpu的工作效率