Cortex-A7中断系统
STM32中断系统
请参考这位写的博文STM32启动过程详解。想详细了解中断系统,务必先阅读推荐的这篇博客
在启动文件中,做一下步骤,最后进入我们的main函数
;* - Set the initial SP
;* - Set the initial PC == Reset_Handler
;* - Set the vector table entries with the exceptions ISR address
;* - Configure the system clock and the external SRAM mounted on
;* STM324xG-EVAL board to be used as data memory (optional,
;* to be enabled by user)
;* - Branches to __main in the C library (which eventually
;* calls main()).
;* After Reset the CortexM4 processor is in Thread mode,
;* priority is Privileged, and the Stack is set to Main.
在ARM Cortex-M架构中,中断向量表的默认位置是地址0x00000000。然而,STM32微控制器的Flash存储器通常从地址0x08000000开始。为了解决这个问题,Cortex-M架构引入了中断向量表偏移(Vector Table Offset)的概念,允许将中断向量表放置在任意地址。
中断向量表偏移的配置
中断向量表偏移是通过向SCB->VTOR
寄存器写入新的中断向量表首地址来实现的。以下是一个典型的配置示例:
#define FLASH_BASE ((uint32_t)0x08000000)
#define VECT_TAB_OFFSET 0x0
void SystemInit(void)
{
// 初始化时钟等
// ...
// 设置中断向量表偏移
SCB->VTOR = FLASH_BASE + VECT_TAB_OFFSET;
}
中断向量表示例
以下是一个典型的STM32中断向量表示例:
__attribute__((section(".isr_vector")))
void (*const g_pfnVectors[])(void) = {
&_estack, // 栈顶地址
Reset_Handler, // 复位处理程序
NMI_Handler, // NMI处理程序
HardFault_Handler, // 硬故障处理程序
MemManage_Handler, // 内存管理故障处理程序
BusFault_Handler, // 总线故障处理程序
UsageFault_Handler, // 使用故障处理程序
0, // 保留
0, // 保留
0, // 保留
0, // 保留
SVC_Handler, // SVC处理程序
DebugMon_Handler, // 调试监控处理程序
0, // 保留
PendSV_Handler, // PendSV处理程序
SysTick_Handler, // SysTick处理程序
// 外设中断向量...
};
详细解释
- 中断向量表的默认位置:
- 在ARM Cortex-M架构中,中断向量表的默认位置是地址0x00000000。
- 中断向量表的第一项是栈顶指针(
__initial_sp
),第二项是复位处理程序的入口地址(Reset_Handler
),依此类推。
- STM32的Flash存储器:
- STM32微控制器的Flash存储器通常从地址0x08000000开始。
- 因此,中断向量表需要放置在Flash存储器的起始地址(0x08000000)。
- 中断向量表偏移:
- 为了将中断向量表放置在Flash存储器的起始地址,Cortex-M架构引入了中断向量表偏移的概念。
- 通过向
SCB->VTOR
寄存器写入新的中断向量表首地址(例如0x08000000),可以实现中断向量表的偏移。
示例代码解释
以下是示例代码的详细解释:
#define FLASH_BASE ((uint32_t)0x08000000)
#define VECT_TAB_OFFSET 0x0
void SystemInit(void)
{
// 初始化时钟等
// ...
// 设置中断向量表偏移
SCB->VTOR = FLASH_BASE + VECT_TAB_OFFSET;
}
FLASH_BASE
:定义Flash存储器的起始地址(0x08000000)。VECT_TAB_OFFSET
:定义中断向量表的偏移量(0x0)。SystemInit
函数:在系统初始化时,设置中断向量表偏移。
总结
通过设置中断向量表偏移,可以将中断向量表放置在STM32微控制器的Flash存储器起始地址(0x08000000),从而解决中断向量表默认位置与实际存储位置不一致的问题。这种方法在Cortex-M架构中非常常见,确保了中断处理的正确性和可靠性。
NVIC(Nested Vectored Interrupt Controller)
NVIC是Cortex-M内核的中断管理系统,负责管理中断的优先级、使能和禁用中断等功能。NVIC通过向量表来管理中断向量,每个中断向量对应一个中断服务程序(ISR)
中断使能
在STM32中,使用NVIC来使能和配置中断。以下是一个使能外部中断的示例:
NVIC_InitTypeDef NVIC_InitStructure;
// 配置EXTI2中断
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);
中断服务函数
中断服务函数是中断发生时执行的代码。在STM32中,中断服务函数通常定义在启动文件中,并在中断向量表中注册。以下是一个STM32中断服务函数的示例:
/* 外部中断2服务程序 */
void EXTI2_IRQHandler(void)
{
// 中断处理代码
// ...
// 清除中断标志
EXTI_ClearITPendingBit(EXTI_Line2);
}
Cortex-A7 中断系统简介
和STM32一样,Cortex-A7也有属于自己的中断向量表,放在代码的最前面,Cortex-A7 内核有 8 个异常中断。
Cortex-A7虽然有八个中断,但实际上只有七个中断,相比于STM32看起来少了很多,难道一个能跑Linux的IC只有这么少的中断???明显不可能。
对于Cortex-A7,内核CPU的所有外部中断都属于IRQ中断,当任意一个外部中断发生都会触发IRQ中断,在IRQ中断服务函数中可以读取指定寄存器来判断发生的具体是什么中断。
①、复位中断(Rest),CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面
做一些初始化工作,比如初始化 SP 指针、DDR 等等。
②、未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。
③、软中断(Software Interrupt,SWI),由 SWI 指令引起的中断,Linux 的系统调用会用 SWI
指令来引起软中断,通过软中断来陷入到内核空间。
④、指令预取中止中断(Prefetch Abort),预取指令的出错的时候会产生此中断。
⑤、数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断。
⑥、IRQ 中断(IRQ Interrupt),外部中断,前面已经说了,芯片内部的外设中断都会引起此
中断的发生。
⑦、FIQ 中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中断。
中断向量表编写
.global _start
_start:
ldr pc, =Reset_Handler /* 复位中断 */
ldr pc, =Undefined_Handler /* 未定义中断 */
ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */
ldr pc, =PrefAbort_Handler /* 预取终止中断 */
ldr pc, =DataAbort_Handler /* 数据终止中断 */
ldr pc, =NotUsed_Handler /* 未使用中断 */
ldr pc, =IRQ_Handler /* IRQ中断 */
ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断
通过使用ldr pc, =address
指令,可以将中断服务函数的地址加载到程序计数器(PC),从而实现中断向量表的定义。这种定义方式是基于Cortex-A内核的硬件支持和汇编指令的特性,确保了中断发生时能够正确跳转到相应的中断服务函数
GIC控制器
GIC(General Interrupt Controller)是ARM Cortex-A内核的中断控制器,负责管理多个中断源的中断请求,并将其分发给合适的处理器核心。GIC支持多种中断类型,包括软件触发中断(SGI)、私有外设中断(PPI)和共享外设中断(SPI)。
①、SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core 共享的中断,这个是最常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
②、PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
③、SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信
GIC的结构
GIC由以下几个主要部分组成:
-
Distributor(分发器):
- 负责管理所有中断源,包括中断使能、中断优先级、中断目标处理器等。
- 分发器将中断请求分发给合适的处理器核心。
- 主要做如下工作
- 全局中断使能控制。
- 控制每一个中断的使能或者关闭。
- 设置每个中断的优先级
- 设置每个中断的目标处理器列表。
- 设置每个外部中断的触发模式:电平触发或边沿触发
- 设置每个中断属于组 0 还是组 1。
-
CPU Interface(CPU接口):
- 每个处理器核心都有一个CPU接口,负责与分发器通信。
- CPU接口接收来自分发器的中断请求,并将其传递给处理器核心。
- 主要工作如下
- 使能或者关闭发送到 CPU Core 的中断请求信号。
- 应答中断
- 通知中断处理完成
- 设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core。
- 定义抢占策略
- 当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core。
-
Interrupt Controller(中断控制器):
- 负责处理中断请求的优先级、中断使能和中断状态等。
GIC的寄存器
GIC的寄存器分为两部分:分发器寄存器和CPU接口寄存器。以下是一些常用的寄存器:
-
Distributor寄存器:
GICD_CTLR
:控制分发器的全局使能和禁用。GICD_ISENABLERn
:使能中断。GICD_ICENABLERn
:禁用中断。GICD_IPRIORITYRn
:设置中断优先级。GICD_ITARGETSRn
:设置中断目标处理器。GICD_ICFGRn
:配置中断类型(电平触发或边沿触发)。
-
CPU Interface寄存器:
GICC_CTLR
:控制CPU接口的全局使能和禁用。GICC_PMR
:设置中断优先级屏蔽寄存器。GICC_BPR
:设置二进制优先级寄存器。GICC_IAR
:读取当前中断的ID。GICC_EOIR
:结束中断处理。
GIC控制器结构体
以下是一个典型的GIC控制器结构体定义,展示了GIC的分发器端和CPU接口端的寄存器:
c
复制
/* * GIC 寄存器描述结构体, * GIC 分为分发器端和 CPU 接口端 */ typedef struct { /* 分发器端寄存器 */ uint32_t RESERVED0[1024]; __IOM uint32_t D_CTLR; /* Offset: 0x1000 (R/W) */ __IM uint32_t D_TYPER; /* Offset: 0x1004 (R/ ) */ __IM uint32_t D_IIDR; /* Offset: 0x1008 (R/ ) */ uint32_t RESERVED1[29]; __IOM uint32_t D_IGROUPR[16]; /* Offset: 0x1080 - 0x0BC (R/W) */ uint32_t RESERVED2[16]; __IOM uint32_t D_ISENABLER[16];/* Offset: 0x1100 - 0x13C (R/W) */ uint32_t RESERVED3[16]; __IOM uint32_t D_ICENABLER[16];/* Offset: 0x1180 - 0x1BC (R/W) */ uint32_t RESERVED4[16]; __IOM uint32_t D_ISPENDR[16]; /* Offset: 0x1200 - 0x23C (R/W) */ uint32_t RESERVED5[16]; __IOM uint32_t D_ICPENDR[16]; /* Offset: 0x1280 - 0x2BC (R/W) */ uint32_t RESERVED6[16]; __IOM uint32_t D_ISACTIVER[16];/* Offset: 0x1300 - 0x33C (R/W) */ uint32_t RESERVED7[16]; __IOM uint32_t D_ICACTIVER[16];/* Offset: 0x1380 - 0x3BC (R/W) */ uint32_t RESERVED8[16]; __IOM uint8_t D_IPRIORITYR[512];/* Offset: 0x1400 - 0x5FC (R/W) */ uint32_t RESERVED9[128]; __IOM uint8_t D_ITARGETSR[512];/* Offset: 0x1800 - 0x9FC (R/W) */ uint32_t RESERVED10[128]; __IOM uint32_t D_ICFGR[32]; /* Offset: 0x1C00 - 0xC7C (R/W) */ uint32_t RESERVED11[32]; __IM uint32_t D_PPISR; /* Offset: 0x1D00 (R/ ) */ __IM uint32_t D_SPISR[15]; /* Offset: 0x1D04 - 0xD3C (R/ ) */ uint32_t RESERVED12[112]; __OM uint32_t D_SGIR; /* Offset: 0x1F00 ( /W) */ uint32_t RESERVED13[3]; __IOM uint8_t D_CPENDSGIR[16];/* Offset: 0x1F10 - 0xF1C (R/W) */ __IOM uint8_t D_SPENDSGIR[16];/* Offset: 0x1F20 - 0xF2C (R/W) */ uint32_t RESERVED14[40]; __IM uint32_t D_PIDR4; /* Offset: 0x1FD0 (R/ ) */ __IM uint32_t D_PIDR5; /* Offset: 0x1FD4 (R/ ) */ __IM uint32_t D_PIDR6; /* Offset: 0x1FD8 (R/ ) */ __IM uint32_t D_PIDR7; /* Offset: 0x1FDC (R/ ) */ __IM uint32_t D_PIDR0; /* Offset: 0x1FE0 (R/ ) */ __IM uint32_t D_PIDR1; /* Offset: 0x1FE4 (R/ ) */ __IM uint32_t D_PIDR2; /* Offset: 0x1FE8 (R/ ) */ __IM uint32_t D_PIDR3; /* Offset: 0x1FEC (R/ ) */ __IM uint32_t D_CIDR0; /* Offset: 0x1FF0 (R/ ) */ __IM uint32_t D_CIDR1; /* Offset: 0x1FF4 (R/ ) */ __IM uint32_t D_CIDR2; /* Offset: 0x1FF8 (R/ ) */ __IM uint32_t D_CIDR3; /* Offset: 0x1FFC (R/ ) */ /* CPU 接口端寄存器 */ __IOM uint32_t C_CTLR; /* Offset: 0x2000 (R/W) */ __IOM uint32_t C_PMR; /* Offset: 0x2004 (R/W) */ __IOM uint32_t C_BPR; /* Offset: 0x2008 (R/W) */ __IM uint32_t C_IAR; /* Offset: 0x200C (R/ ) */ __OM uint32_t C_EOIR; /* Offset: 0x2010 ( /W) */ __IM uint32_t C_RPR; /* Offset: 0x2014 (R/ ) */ __IM uint32_t C_HPPIR; /* Offset: 0x2018 (R/ ) */ __IOM uint32_t C_ABPR; /* Offset: 0x201C (R/W) */ __IM uint32_t C_AIAR; /* Offset: 0x2020 (R/ ) */ __OM uint32_t C_AEOIR; /* Offset: 0x2024 ( /W) */ __IM uint32_t C_AHPPIR; /* Offset: 0x2028 (R/ ) */ uint32_t RESERVED15[41]; __IOM uint32_t C_APR0; /* Offset: 0x20D0 (R/W) */ uint32_t RESERVED16[3]; __IOM uint32_t C_NSAPR0; /* Offset: 0x20E0 (R/W) */ uint32_t RESERVED17[6]; __IM uint32_t C_IIDR; /* Offset: 0x20FC (R/ ) */ uint32_t RESERVED18[960]; __OM uint32_t C_DIR; /* Offset: 0x3000 ( /W) */ } GIC_Type;
第 59行是 GIC 的分发器端相关寄存器,其相对于 GIC 基地址偏移为 0X1000,因此我们获
取到 GIC 基地址以后只需要加上 0X1000 即可访问 GIC 分发器端寄存器
中断处理
在IRQ中断服务函数中,需要读取GIC的寄存器来判断具体的中断源(中断ID),并调用相应的中断服务函数进行处理
每个中断源都有一个唯一的中断ID(Interrupt ID),用于区分不同的中断源。中断ID的范围是从ID0到ID1019,总共1020个中断ID。这些中断ID被分配给不同类型的中断,包括软件触发中断(SGI)、私有外设中断(PPI)和共享外设中断(SPI)。
中断ID的分配
- SGI(Software Generated Interrupt):
- ID范围:ID0~ID15
- 数量:16个
- 用途:用于处理器核心之间的通信,通常用于触发软件中断。
- PPI(Private Peripheral Interrupt):
- ID范围:ID16~ID31
- 数量:16个
- 用途:用于处理器核心的私有外设中断,例如定时器中断、看门狗中断等。
- SPI(Shared Peripheral Interrupt):
- ID范围:ID32~ID1019
- 数量:988个
- 用途:用于共享外设中断,例如GPIO中断、串口中断等。具体的中断ID对应哪个中断由半导体厂商根据实际情况定义。
I.MX6U的中断ID分配
I.MX6U使用了128个中断ID,加上前面属于PPI和SGI的32个ID,总共160个中断ID。这些中断ID的具体分配可以在《I.MX6ULL参考手册》的“3.2 Cortex A7 interrupts”小节中找到。
CP15协处理器
CP15协处理器是ARM Cortex-A内核中的一个重要组件,主要用于存储系统管理。它包含16个32位寄存器(c0~c15),可以通过特定的指令(MRC和MCR)进行读写操作。以下是对CP15协处理器及其相关寄存器的详细解释。
CP15协处理器的访问指令
- MRC(Move to ARM Register from Coprocessor):
- 将CP15协处理器中的寄存器数据读到ARM寄存器中。
- 指令格式:
MRC{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
- MCR(Move to Coprocessor from ARM Register):
- 将ARM寄存器的数据写入到CP15协处理器寄存器中。
- 指令格式:
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
- cond:指令执行的条件码,如果忽略的话就表示无条件执行。
- opc1:协处理器要执行的操作码
- Rt:ARM 源寄存器,要写入到 CP15 寄存器的数据就保存在此寄存器中。
- MRC 的指令格式和 MCR 一样,只不过在 MRC 指令中 Rt 就是目标寄存器,也就是从
CP15 指定寄存器读出来的数据会保存在 Rt 中。而 CRn 就是源寄存器,也就是要读取的写处
理器寄存器
- MRC 的指令格式和 MCR 一样,只不过在 MRC 指令中 Rt 就是目标寄存器,也就是从
- CRn :CP15 协处理器的目标寄存器。
- CRm: :协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将
- opc2 :可选的协处理器特定操作码,当不需要的时候要设置为 0。
CP15寄存器
c0寄存
P15 协处理器有 16 个 32 位寄存器,c0~c15,在使用 MRC 或者 MCR 指令访问这 16 个
寄存器的时候,指令中的 CRn、opc1、CRm 和 opc2 通过不同的搭配,其得到的寄存器含义是
不同的。
当 MRC/MCR 指令中的 CRn=c0,opc1=0,CRm=c0,opc2=0 的时候就表示
此时的 c0 就是 MIDR 寄存器,也就是主 ID 寄存器,这个也是 c0 的基本作用
2. c1寄存
c1寄存器在不同的配置下有不同的含义。以下是一些常见的配置:
如果要读写 SCTLR 的话,就可以使用如下命令:
MRC p15, 0, <Rt>, c1, c0, 0
;读取 SCTLR 寄存器,数据保存到 Rt 中。
MCR p15, 0, <Rt>, c1, c0, 0
;将 Rt 中的数据写到 SCTLR(c1)寄存器中。
-
SCTLR(System Control Register):
-
配置:
MRC p15, 0, <Rt>, c1, c0, 0
-
作用:控制系统的各种功能,如MMU、I/D Cache等。
-
-
含义:
- bit13:V,中断向量表基地址选择位。
- bit12:I,I Cache使能位。
- bit11:Z,分支预测使能位。
- bit10:SW,SWP和SWPB使能位。
- bit2:C,D Cache和缓存一致性使能位。
- bit1:A,内存对齐检查使能位。
- bit0:M,MMU使能位。
-
3. c12寄存器
c12寄存器在不同的配置下有不同的含义。以下是一些常见的配置:
-
VBAR(Vector Base Address Register):
-
配置:
MRC p15, 0, <Rt>, c12, c0, 0
-
作用:设置中断向量表的基地址。
-
示例: VBAR 寄存器,也就是向量表基地址寄存器。设置中断向量表偏移的时候就需要
将新的中断向量表基地址写入VBAR
中,比如在前面的例程中,代码链接的起始地址为
0X87800000
,而中断向量表肯定要放到最前面,也就是0X87800000
这个地址处。所以就需要
设置VBAR
为0X87800000
,设置命令如下ldr r0, =0x87800000 ; r0=0x87800000 MCR p15, 0, r0, c12, c0, 0 ; 将r0里面的数据写入到c12中,即c12=0x87800000
-
4. c15寄存器
c15寄存器在不同的配置下有不同的含义。以下是一些常见的配置:
-
CBAR(Configuration Base Address Register):
-
配置:
MRC p15, 4, <Rt>, c15, c0, 0
-
作用:获取GIC(General Interrupt Controller)的基地址。
-
示例:
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 接口端寄存器起始地址 可以看看前面的GIC控制器的定义代码
LDR r0, [r1, #0XC] ;读取 CPU 接口端起始地址+0XC 处的寄存器值,也就是寄存器GIC_IAR 的值
示例代码
以下是一个示例代码,展示了如何使用CP15协处理器寄存器:
; 读取MIDR寄存器
MRC p15, 0, r0, c0, c0, 0 ; 将MIDR寄存器的值读取到r0中
; 读取SCTLR寄存器
MRC p15, 0, r1, c1, c0, 0 ; 将SCTLR寄存器的值读取到r1中
; 设置SCTLR寄存器
ldr r2, =0x00000001 ; 使能MMU
MCR p15, 0, r2, c1, c0, 0 ; 将r2的值写入到SCTLR寄存器中
; 设置VBAR寄存器
ldr r3, =0x87800000 ; 设置中断向量表基地址为0x87800000
MCR p15, 0, r3, c12, c0, 0 ; 将r3的值写入到VBAR寄存器中
; 获取GIC基地址
MRC p15, 4, r4, c15, c0, 0 ; 将GIC基地址读取到r4中
总结
- c0寄存器:用于获取处理器的主ID信息。
- c1寄存器:用于控制系统功能,如MMU、I/D Cache等。
- c12寄存器:用于设置中断向量表的基地址。
- c15寄存器:用于获取GIC的基地址。
通过合理配置和使用CP15协处理器寄存器,可以有效地管理系统资源和中断处理,提高系统的性能和可靠性。
在ARM Cortex-A内核中,中断使能包括两个主要部分:IRQ和FIQ总中断使能,以及ID0~ID1019这1020个中断源的使能。以下是对这两个部分的详细解释。
IRQ和FIQ总中断使
IRQ和FIQ分别是外部中断和快速中断的总开关。要使用I.MX6U上的外设中断,必须先打开IRQ中断(本教程不使用FIQ)。可以通过设置CPSR寄存器的I位和F位来控制IRQ和FIQ的使能和禁止。此外,还可以使用特定的汇编指令来完成IRQ和FIQ的使能和禁止。
开关中断指令
指令 | 描述 |
---|---|
cpsid i | 禁止IRQ中断。 |
cpsie i | 使能IRQ中断。 |
cpsid f | 禁止FIQ中断。 |
cpsie f | 使能FIQ中断。 |
ID0~ID1019中断使能和禁止
GIC(General Interrupt Controller)寄存器GICD_ISENABLERn
和GICD_ICENABLERn
用于完成外部中断的使能和禁止。对于Cortex-A7内核,中断ID只使用了512个。一个bit控制一个中断ID的使能,因此需要512/32=16个GICD_ISENABLER
寄存器来完成中断的使能,同样也需要16个GICD_ICENABLER
寄存器来完成中断的禁止。
GICD_ISENABLERn和GICD_ICENABLERn寄存器
- GICD_ISENABLER0:
- bit[15:0]:对应ID15~0的SGI中断。
- bit[31:16]:对应ID31~16的PPI中断。
- GICD_ISENABLER1~GICD_ISENABLER15:
- 控制ID32~ID511的SPI中断。
示例代码
以下是一个示例代码,展示了如何使能和禁止IRQ和FIQ中断,以及如何使能和禁止特定的中断ID。
使能和禁止IRQ和FIQ中断
; 使能IRQ中断
cpsie i
; 禁止IRQ中断
cpsid i
; 使能FIQ中断
cpsie f
; 禁止FIQ中断
cpsid f
使能和禁止特定中断ID
void GIC_EnableIRQ(uint32_t irq_number)
{
uint32_t reg_index = irq_number / 32;
uint32_t bit_index = irq_number % 32;
GICD->ISENABLER[reg_index] |= (1 << bit_index);
}
void GIC_DisableIRQ(uint32_t irq_number)
{
uint32_t reg_index = irq_number / 32;
uint32_t bit_index = irq_number % 32;
GICD->ICENABLER[reg_index] |= (1 << bit_index);
}
总结
- IRQ和FIQ总中断使能:
- 通过设置CPSR寄存器的I位和F位,或使用
cpsid i
、cpsie i
、cpsid f
、cpsie f
指令来控制IRQ和FIQ的使能和禁止。
- 通过设置CPSR寄存器的I位和F位,或使用
- ID0~ID1019中断使能和禁止:
- 使用GIC寄存器
GICD_ISENABLERn
和GICD_ICENABLERn
来使能和禁止特定的中断ID。
- 使用GIC寄存器
通过合理配置IRQ和FIQ总中断使能,以及使能和禁止特定的中断ID,可以有效地管理系统的中断,提高系统的实时性和可靠性。
优先级数配置
GIC控制器最多可以支持256个优先级,数字越小,优先级越高。Cortex-A7选择了32个优先级。在使用中断时,需要初始化GICC_PMR
寄存器,此寄存器用来决定使用几级优先级。
GICC_PMR寄存器
GICC_PMR
寄存器只有低8位有效,这8位最多可以设置256个优先级。
bit7:0 | 优先级数 |
---|---|
11111111 | 256个优先级 |
11111110 | 128个优先级 |
11111100 | 64个优先级 |
11111000 | 32个优先级 |
11110000 | 16个优先级 |
对于I.MX6U(Cortex-A7内核),支持32个优先级,因此GICC_PMR
要设置为0b11111000
。
2. 抢占优先级和子优先级位数设
抢占优先级和子优先级各占多少位是由寄存器GICC_BPR
来决定的。
GICC_BPR寄存器
GICC_BPR
寄存器只有低3位有效,其值不同,抢占优先级和子优先级占用的位数也不同。配置如表
Binary Point | 抢占优先级域 | 子优先级域 | 描述 |
---|---|---|---|
0 | [7:1] | [0] | 7级抢占优先级,1级子优先级 |
1 | [7:2] | [1:0] | 6级抢占优先级,2级子优先级 |
2 | [7:3] | [2:0] | 5级抢占优先级,3级子优先级 |
3 | [7:4] | [3:0] | 4级抢占优先级,4级子优先级 |
4 | [7:5] | [4:0] | 3级抢占优先级,5级子优先级 |
5 | [7:6] | [5:0] | 2级抢占优先级,6级子优先级 |
6 | [7:7] | [6:0] | 1级抢占优先级,7级子优先级 |
7 | 无 | [7:0] | 0级抢占优先级,8级子优先级 |
为了简单起见,一般将所有的中断优先级位都配置为抢占优先级。比如I.MX6U的优先级位数为5(32个优先级),所以可以设置Binary point
为2,表示5个优先级位全部为抢占优先级。
3. 优先级设
前面已经设置好了I.MX6U一共有32个抢占优先级,数字越小优先级越高。具体要使用某个中断时,可以设置其优先级为0~31。某个中断ID的中断优先级设置由寄存器GICD_IPRIORITYR
来完成。Cortex-A7使用了512个中断ID,每个中断ID配有一个优先级寄存器,所以一共有512个GICD_IPRIORITYR
寄存器。如果优先级个数为32的话,使用寄存器GICD_IPRIORITYR
的bit7:4来设置优先级,也就是说实际的优先级要左移3位。
示例代码
以下是一个示例代码,展示了如何设置中断优先级:
void GIC_SetPriority(uint32_t irq_number, uint8_t priority)
{
uint32_t reg_index = irq_number / 4;
uint32_t shift = (irq_number % 4) * 8;
// 设置优先级,实际优先级要左移3位
GICD->IPRIORITYR[reg_index] &= ~(0xFF << shift);
GICD->IPRIORITYR[reg_index] |= (priority << 3) << shift;
}
总结
- 优先级数配置:
- 设置
GICC_PMR
寄存器,配置优先级个数,例如I.MX6U支持32级优先级。
- 设置
- 抢占优先级和子优先级位数设置:
- 设置
GICC_BPR
寄存器,配置抢占优先级和子优先级的位数。一般为了简单起见,将所有的位数都设置为抢占优先级。
- 设置
- 优先级设置:
- 设置指定中断ID的优先级,使用
GICD_IPRIORITYR
寄存器。实际的优先级要左移3位。
- 设置指定中断ID的优先级,使用
通过合理配置中断优先级,可以有效地管理系统的中断,提高系统的实时性和可靠性。
代码实现
start.S
.global _start /* 全局标号 */
/*
* 描述: _start函数,首先是中断向量表的创建
* 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器)
* ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常)
*/
_start:
ldr pc, =Reset_Handler /* 复位中断 */
ldr pc, =Undefined_Handler /* 未定义中断 */
ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */
ldr pc, =PrefAbort_Handler /* 预取终止中断 */
ldr pc, =DataAbort_Handler /* 数据终止中断 */
ldr pc, =NotUsed_Handler /* 未使用中断 */
ldr pc, =IRQ_Handler /* IRQ中断 */
ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */
/* 复位中断 */
Reset_Handler:
cpsid i /* 关闭全局中断 */
/* 关闭I,DCache和MMU
* 采取读-改-写的方式。
*/
mrc p15, 0, r0, c1, c0, 0 /* 读取CP15的C1寄存器到R0中 */
bic r0, r0, #(0x1 << 12) /* 清除C1寄存器的bit12位(I位),关闭I Cache */
bic r0, r0, #(0x1 << 2) /* 清除C1寄存器的bit2(C位),关闭D Cache */
bic r0, r0, #0x2 /* 清除C1寄存器的bit1(A位),关闭对齐 */
bic r0, r0, #(0x1 << 11) /* 清除C1寄存器的bit11(Z位),关闭分支预测 */
bic r0, r0, #0x1 /* 清除C1寄存器的bit0(M位),关闭MMU */
mcr p15, 0, r0, c1, c0, 0 /* 将r0寄存器中的值写入到CP15的C1寄存器中 */
#if 0
/* 汇编版本设置中断向量表偏移 */
ldr r0, =0X87800000
dsb
isb
mcr p15, 0, r0, c12, c0, 0
dsb
isb
#endif
/* 设置各个模式下的栈指针,
* 注意:IMX6UL的堆栈是向下增长的!
* 堆栈指针地址一定要是4字节地址对齐的!!!
* DDR范围:0X80000000~0X9FFFFFFF
*/
/* 进入IRQ模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x12 /* r0或上0x13,表示使用IRQ模式 */
msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
ldr sp, =0x80600000 /* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */
/* 进入SYS模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x1f /* r0或上0x13,表示使用SYS模式 */
msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
ldr sp, =0x80400000 /* 设置SYS模式下的栈首地址为0X80400000,大小为2MB */
/* 进入SVC模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */
msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
ldr sp, =0X80200000 /* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */
cpsie i /* 打开全局中断 */
#if 0
/* 使能IRQ中断 */
mrs r0, cpsr /* 读取cpsr寄存器值到r0中 */
bic r0, r0, #0x80 /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */
msr cpsr, r0 /* 将r0重新写入到cpsr中 */
#endif
b main /* 跳转到main函数 */
/* 未定义中断 */
Undefined_Handler:
ldr r0, =Undefined_Handler
bx r0
/* SVC中断 */
SVC_Handler:
ldr r0, =SVC_Handler
bx r0
/* 预取终止中断 */
PrefAbort_Handler:
ldr r0, =PrefAbort_Handler
bx r0
/* 数据终止中断 */
DataAbort_Handler:
ldr r0, =DataAbort_Handler
bx r0
/* 未使用的中断 */
NotUsed_Handler:
ldr r0, =NotUsed_Handler
bx r0
/* IRQ中断!重点!!!!! */
IRQ_Handler:
/*1. 保存上下文*/
push {lr} /* 保存链接寄存器lr,lr保存了被中断程序的返回地址 */
push {r0-r3, r12} /* 保存通用寄存器r0-r3和r12,这些寄存器在中断处理过程中可能会被修改。 */
mrs r0, spsr /* 将当前模式下的状态寄存器spsr读取到r0寄存器中 */
push {r0} /* 保存spsr寄存器 */
/*2. 获取中断号*/
mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中
* 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
* Cortex-A7 Technical ReferenceManua.pdf P68 P138
*/
add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
* GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
* 这个中断号来绝对调用哪个中断服务函数
*/
push {r0, r1} /* 保存r0,r1 */
/*3.调用C语言中断处理函数*/
cps #0x13 /* 进入SVC模式,允许其他中断再次进去 */
push {lr} /* 保存SVC模式的lr寄存器 */
ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中*/
blx r2 /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */
/*4. 恢复上下文*/
pop {lr} /* 恢复SVC模式的链接寄存器lr */
cps #0x12 /* 切换回IRQ模式 */
pop {r0, r1} /* 恢复中断号和GIC的CPU接口端基地址*/
str r0, [r1, #0X10] /* 将中断号写入GICC_EOIR寄存器,表示中断处理完成 */
pop {r0} /* 恢复spsr */
msr spsr_cxsf, r0 /* 将spsr寄存器的值写回到状态寄存器。 */
pop {r0-r3, r12} /* 恢复通用寄存器r0-r3和r12 */
pop {lr} /* 恢复IRQ模式的链接寄存器lr */
subs pc, lr, #4 /* 将lr-4赋给pc,返回到被中断的程序。lr-4是因为ARM架构中,lr保存的是被中断程序的下一条指令的地址,减去4后才是被中断的指令地址 */
/* FIQ中断 */
FIQ_Handler:
ldr r0, =FIQ_Handler
bx r0
IRQ中断处理的流程
- 保存上下文:
- 保存链接寄存器
lr
、通用寄存器r0-r3
和r12
、状态寄存器spsr
。
- 保存链接寄存器
- 获取中断号:
- 从GIC的CPU接口端基地址读取GICC_IAR寄存器,获取中断号。
- 调用C语言中断处理函数:
- 切换到SVC模式,调用C语言中断处理函数。
- 恢复上下文:
- 恢复之前保存的上下文,包括链接寄存器
lr
、通用寄存器r0-r3
和r12
、状态寄存器spsr
。
- 恢复之前保存的上下文,包括链接寄存器
- 返回:
- 将
lr-4
赋给pc
,返回到被中断的程序。
- 将
通用中断管理框架
bsp_int.h
#ifndef __BSP_INT_H__
#define __BSP_INT_H__
#include "imx6ul.h"
/* 中断服务函数指针 */
/*定义了一个函数指针类型system_irq_handler_t,用于指向中断服务函数。*/
typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);
/* 中断服务函数结构体 */
typedef struct _sys_irq_handle
{
system_irq_handler_t irqHandler; /* 中断服务函数 */
void *userParam; /* 中断服务函数参数 */
} sys_irq_handle_t;
/* 函数声明 */
void int_init(void);
void system_irqtable_init(void);
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam);
void system_irqhandler(unsigned int giccIar);
void default_irqhandler(unsigned int giccIar, void *userParam);
#endif /* __BSP_INT_H__ */
bsp_int.c
#include "bsp_int.h"
/* 中断嵌套计数器 */
static unsigned int irqNesting;
/* 中断服务函数表 */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
/*
* @description : 中断初始化函数
* @param : 无
* @return : 无
*/
void int_init(void)
{
GIC_Init(); /* 初始化GIC */
system_irqtable_init(); /* 初始化中断表 */
__set_VBAR((uint32_t)0x87800000);
}
/*
* @description : 初始化中断服务函数表
* @param : 无
* @return : 无
*/
void system_irqtable_init(void)
{
unsigned int i = 0;
irqNesting = 0;
/* 先将所有的中断服务函数设置为默认值 */
for(i = 0; i < NUMBER_OF_INT_VECTORS; i++)
{
system_register_irqhandler((IRQn_Type)i, default_irqhandler, NULL);
}
}
/*
* @description : 给指定的中断号注册中断服务函数
* @param - irq : 要注册的中断号
* @param - handler : 要注册的中断处理函数
* @param - usrParam : 中断服务处理函数参数
* @return : 无
*/
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam)
{
irqTable[irq].irqHandler = handler;
irqTable[irq].userParam = userParam;
}
/*
* @description : C语言中断服务函数,irq汇编中断服务函数会
调用此函数,此函数通过在中断服务列表中查
找指定中断号所对应的中断处理函数并执行。
* @param - giccIar : 中断号
* @return : 无
*/
void system_irqhandler(unsigned int giccIar)
{
uint32_t intNum = giccIar & 0x3FFUL;
/* 检查中断号是否符合要求 */
if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
{
return;
}
irqNesting++; /* 中断嵌套计数器加一 */
/* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/
irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);
irqNesting--; /* 中断执行完成,中断嵌套寄存器减一 */
}
/*
* @description : 默认中断服务函数
* @param - giccIar : 中断号
* @param - usrParam : 中断服务处理函数参数
* @return : 无
*/
void default_irqhandler(unsigned int giccIar, void *userParam)
{
while(1)
{
}
}
主要内容
-
中断嵌套计数器:
static unsigned int irqNesting;
- 用于记录中断嵌套的层数。
-
中断服务函数表:
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
- 用于存储每个中断号对应的中断服务函数和参数。
-
中断初始化函数:
void int_init(void)
- 初始化GIC(Generic Interrupt Controller)。
- 初始化中断服务函数表。
- 设置中断向量表基地址。
-
初始化中断服务函数表:
void system_irqtable_init(void)
- 将所有中断服务函数初始化为默认值
default_irqhandler
。
-
注册中断服务函数:
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam)
- 将指定的中断服务函数和参数注册到中断服务函数表中。
-
C语言中断服务函数:
void system_irqhandler(unsigned int giccIar)
- 从GICC_IAR寄存器中提取中断号。
- 检查中断号是否有效。
- 增加中断嵌套计数器。
- 调用对应的中断服务函数。
- 减少中断嵌套计数器。
-
默认中断服务函数:
void default_irqhandler(unsigned int giccIar, void *userParam)
- 默认的中断服务函数,通常用于未处理的中断。
GPIO管理宽假
bsp_gpio.h
#ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#define _BSP_KEY_H
#include "imx6ul.h"
/*
* 枚举类型和结构体定义
*/
typedef enum _gpio_pin_direction
{
kGPIO_DigitalInput = 0U, /* 输入 */
kGPIO_DigitalOutput = 1U, /* 输出 */
} gpio_pin_direction_t;
/*
* GPIO中断触发类型枚举
*/
typedef enum _gpio_interrupt_mode
{
kGPIO_NoIntmode = 0U, /* 无中断功能 */
kGPIO_IntLowLevel = 1U, /* 低电平触发 */
kGPIO_IntHighLevel = 2U, /* 高电平触发 */
kGPIO_IntRisingEdge = 3U, /* 上升沿触发 */
kGPIO_IntFallingEdge = 4U, /* 下降沿触发 */
kGPIO_IntRisingOrFallingEdge = 5U, /* 上升沿和下降沿都触发 */
} gpio_interrupt_mode_t;
/*
* GPIO配置结构体
*/
typedef struct _gpio_pin_config
{
gpio_pin_direction_t direction; /* GPIO方向:输入还是输出 */
uint8_t outputLogic; /* 如果是输出的话,默认输出电平 */
gpio_interrupt_mode_t interruptMode; /* 中断方式 */
} gpio_pin_config_t;
/* 函数声明 */
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
int gpio_pinread(GPIO_Type *base, int pin);
void gpio_pinwrite(GPIO_Type *base, int pin, int value);
void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pinInterruptMode);
void gpio_enableint(GPIO_Type* base, unsigned int pin);
void gpio_disableint(GPIO_Type* base, unsigned int pin);
void gpio_clearintflags(GPIO_Type* base, unsigned int pin);
#endif
bsp_gpio.c
#include "bsp_gpio.h"
/*
* @description : GPIO初始化。
* @param - base : 要初始化的GPIO组。
* @param - pin : 要初始化GPIO在组内的编号。
* @param - config : GPIO配置结构体。
* @return : 无
*/
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
base->IMR &= ~(1U << pin);
if(config->direction == kGPIO_DigitalInput) /* GPIO作为输入 */
{
base->GDIR &= ~( 1 << pin);
}
else /* 输出 */
{
base->GDIR |= 1 << pin;
gpio_pinwrite(base,pin, config->outputLogic); /* 设置默认输出电平 */
}
gpio_intconfig(base, pin, config->interruptMode); /* 中断功能配置 */
}
/*
* @description : 读取指定GPIO的电平值 。
* @param - base : 要读取的GPIO组。
* @param - pin : 要读取的GPIO脚号。
* @return : 无
*/
int gpio_pinread(GPIO_Type *base, int pin)
{
return (((base->DR) >> pin) & 0x1);
}
/*
* @description : 指定GPIO输出高或者低电平 。
* @param - base : 要输出的的GPIO组。
* @param - pin : 要输出的GPIO脚号。
* @param - value : 要输出的电平,1 输出高电平, 0 输出低低电平
* @return : 无
*/
void gpio_pinwrite(GPIO_Type *base, int pin, int value)
{
if (value == 0U)
{
base->DR &= ~(1U << pin); /* 输出低电平 */
}
else
{
base->DR |= (1U << pin); /* 输出高电平 */
}
}
/*
* @description : 设置GPIO的中断配置功能
* @param - base : 要配置的IO所在的GPIO组。
* @param - pin : 要配置的GPIO脚号。
* @param - pinInterruptMode: 中断模式,参考枚举类型gpio_interrupt_mode_t
* @return : 无
*/
void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pin_int_mode)
{
volatile uint32_t *icr;
uint32_t icrShift;
icrShift = pin;
base->EDGE_SEL &= ~(1U << pin);
if(pin < 16) /* 低16位 */
{
icr = &(base->ICR1);
}
else /* 高16位 */
{
icr = &(base->ICR2);
icrShift -= 16;
}
switch(pin_int_mode)
{
case(kGPIO_IntLowLevel):
*icr &= ~(3U << (2 * icrShift));
break;
case(kGPIO_IntHighLevel):
*icr = (*icr & (~(3U << (2 * icrShift)))) | (1U << (2 * icrShift));
break;
case(kGPIO_IntRisingEdge):
*icr = (*icr & (~(3U << (2 * icrShift)))) | (2U << (2 * icrShift));
break;
case(kGPIO_IntFallingEdge):
*icr |= (3U << (2 * icrShift));
break;
case(kGPIO_IntRisingOrFallingEdge):
base->EDGE_SEL |= (1U << pin);
break;
default:
break;
}
}
/*
* @description : 使能GPIO的中断功能
* @param - base : 要使能的IO所在的GPIO组。
* @param - pin : 要使能的GPIO在组内的编号。
* @return : 无
*/
void gpio_enableint(GPIO_Type* base, unsigned int pin)
{
base->IMR |= (1 << pin);
}
/*
* @description : 禁止GPIO的中断功能
* @param - base : 要禁止的IO所在的GPIO组。
* @param - pin : 要禁止的GPIO在组内的编号。
* @return : 无
*/
void gpio_disableint(GPIO_Type* base, unsigned int pin)
{
base->IMR &= ~(1 << pin);
}
/*
* @description : 清除中断标志位(写1清除)
* @param - base : 要清除的IO所在的GPIO组。
* @param - pin : 要清除的GPIO掩码。
* @return : 无
*/
void gpio_clearintflags(GPIO_Type* base, unsigned int pin)
{
base->ISR |= (1 << pin);
}
按键中断
bsp_exit.h
#ifndef _BSP_EXIT_H
#define _BSP_EXIT_H
#include "imx6ul.h"
/* 函数声明 */
void exit_init(void); /* 中断初始化 */
void gpio1_io18_irqhandler(void); /* 中断处理函数 */
#endif
bsp_exit.c
#include "bsp_exit.h"
#include "bsp_gpio.h"
#include "bsp_int.h"
#include "bsp_delay.h"
#include "bsp_beep.h"
/*
* @description : 初始化外部中断
* @param : 无
* @return : 无
*/
void exit_init(void)
{
gpio_pin_config_t key_config;
/* 1、设置IO复用 */
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); /* 复用为GPIO1_IO18 */
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
/* 2、初始化GPIO为中断模式 */
key_config.direction = kGPIO_DigitalInput;
key_config.interruptMode = kGPIO_IntFallingEdge;
key_config.outputLogic = 1;
gpio_init(GPIO1, 18, &key_config);
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); /* 使能GIC中对应的中断 */
system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL); /* 注册中断服务函数 */
gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18的中断功能 */
}
/*
* @description : GPIO1_IO18最终的中断处理函数
* @param : 无
* @return : 无
*/
void gpio1_io18_irqhandler(void)
{
static unsigned char state = 0;
/*
*采用延时消抖,中断服务函数中禁止使用延时函数!因为中断服务需要
*快进快出!!这里为了演示所以采用了延时函数进行消抖,后面我们会讲解
*定时器中断消抖法!!!
*/
delay(10);
if(gpio_pinread(GPIO1, 18) == 0) /* 按键按下了 */
{
state = !state;
beep_switch(state);
}
gpio_clearintflags(GPIO1, 18); /* 清除中断标志位 */
}