文章目录
- 1 外部中断初始化与中断服务函数
- 1.2 外部中断初始化函数 `exti_init`
- 1.2.1 GPIO引脚配置
- 1.2.2 中断使能与注册
- 1.2.3 `GIC_EnableIRQ()`函数的分析
- 1.3 中断服务函数 `gpio1_io20_irqhandler`
- 1.3.1 消抖处理
- 1.3.2 中断事件处理
- 1.3.3 清除中断标志
- 2 BUG处理
- 2.1 问题描述
- 2.2 解决过程
- 2.3 解决方式
- 本文章结合了正点原子的 i.mx6u嵌入式Linux开发指南和笔者的理解。
- 前面我们进行了编写GPIO 中断管理与配置函数,下面将具体使用这些GPIO 中断管理与配置函数来进行一个具体的中断初始化,以及中断服务函数的编写
1 外部中断初始化与中断服务函数
这段代码主要用于初始化特定的GPIO端口(GPIO1_IO18)作为外部中断,并定义中断服务函数来处理中断事件。以下是每个部分的详细说明:
1.2 外部中断初始化函数 exti_init
1.2.1 GPIO引脚配置
key_config.direction = kGPIO_DigitalInput;
key_config.interruptMode = kGPIO_IntFallingEdge;
gpio_init(GPIO1, 18, &key_config);
设置GPIO1_IO18为数字输入并配置为下降沿触发中断。
1.2.2 中断使能与注册
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
system_register_irqhandler(GPIO1_Combined_16_31_IRQn, gpio1_io18_irqhandler, NULL);
gpio_int_enable(GPIO1, 18);
在GIC(通用中断控制器)中使能对应的中断,并注册中断服务函数gpio1_io18_irqhandler
。
1.2.3 GIC_EnableIRQ()
函数的分析
/*
* 使能指定的中断
*/
FORCEDINLINE __STATIC_INLINE void GIC_EnableIRQ(IRQn_Type IRQn)
{
GIC_Type *gic = (GIC_Type *)(__get_CBAR() & 0xFFFF0000UL);
gic->D_ISENABLER[((uint32_t)(int32_t)IRQn) >> 5] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL));
}
这段代码定义了一个名为 GIC_EnableIRQ
的函数,用于启用指定的中断。
-
函数首先获取 GIC 控制器基地址。
__get_CBAR()
函数返回 GIC 控制器基地址,并将其与0xFFFF0000UL
进行按位与运算,得到 GIC 控制器基地址的最高 16 位。Cortex-A7 Technical ReferenceManua.pdf
P138
-
然后,函数计算出要操作的寄存器地址。
((uint32_t)(int32_t)IRQn) >> 5
将中断号右移 5 位,得到中断号所在的寄存器索引。
-
最后,函数设置该寄存器中的相应位,从而启用该中断。
((uint32_t)(int32_t)IRQn) & 0x1FUL
将中断号与0x1FUL
进行按位与运算,得到中断号在寄存器中的位索引。- 然后,将
1UL
左移位索引
位,得到一个掩码,并将其写入D_ISENABLER
寄存器中,从而启用该中断。
-
D_ISENABLER
寄存器是 GIC 分发器中的一组寄存器,用于控制每个中断的启用状态。
ARM Generic Interrupt Controller(ARM GIC控制器)V2.0.pdf
P93
- 每个寄存器包含 32 个 Set-enable 位,分别对应于 32 个中断。
- 当 Set-enable 位被设置为 1 时,对应中断会被启用,这意味着 GIC 会将该中断转发到 CPU。
- 当 Set-enable 位被设置为 0 时,对应中断会被禁用,这意味着 GIC 不会将该中断转发到 CPU。
根据中断 ID (m),对应 GICD_ISENABLER 寄存器的编号 (n) 可以通过
n = m DIV 32
计算得到,也就是IRQn) >> 5
。- 对应 GICD_ISENABLER 寄存器的偏移地址为
0x100 + (4*n)
,这里不用手动计算,通过gic->D_ISENABLER·
就可以得到。 - 对应 GICD_ISENABLER 寄存器中需要设置的位的编号为
m MOD 32
,也就是IRQn & 0x1FUL
。
1.3 中断服务函数 gpio1_io20_irqhandler
1.3.1 消抖处理
delay(10);
简单的延时用于消除由于机械或电气噪声产生的误触发。
1.3.2 中断事件处理
if(gpio_pin_read(GPIO1, 18) == 0){
state = !state;
beep_switch(state);
}
读取GPIO1_IO18的状态,如果为低电平(按键被按下),则切换状态并控制蜂鸣器的开关状态。
1.3.3 清除中断标志
gpio_int_flagClear(GPIO1, 18);
完成事件处理后,清除中断标志以准备接收下一个中断。
2 BUG处理
2.1 问题描述
程序烧写后,发现当尝试按下按键,进入按键中断处理函数时,灯会一直保持在按键按下那一刻的状态,程序卡死。
2.2 解决过程
- 查看反汇编文件,发现bss段放在了程序起始地址,导致中断向量表的位置不对了(之前说过,应该放在程序的起始位置)
BSS 段(Block Started by Symbol)是程序内存中用于存储未初始化全局变量和静态变量的区域。
2.3 解决方式
在_start:
中,将bss段初始化放到中断向量表初始化后面
_start:
@ 在处理器启动时执行一次,用于初始化中断向量表
ldr pc, =Reset_Handler @ 复位中断服务函数
ldr pc, =Undefined_Handler @ 未定义指令中断服务函数
ldr pc, =SVC_Handler @ SVC中断服务函数
ldr pc, =PreAbort_Handler @ 预取终止
ldr pc, =DataAbort_Handler @ 数据终止
ldr pc, =NotUsed_Handler @ 未使用
ldr pc, =IRQ_Handler @ IRQ中断服务函数
ldr pc, =FIQ_Handler @ FIQ中断服务函数
@ 复位中断服务函数
Reset_Handler:
@ 0.禁止IRQ中断
@ 方式:修改PSTATE处理器状态寄存器
@ Change PE State (CPS) 用于修改处理器状态寄存器 (PSTATE) 中的某些位
@ PSTATE 是一个特殊的寄存器,它包含了处理器当前的运行状态信息,包括:
@ A (Application) 位: 控制应用程序模式下的中断是否允许。
@ I (IRQ) 位: 控制 IRQ 中断是否允许。
@ F (FIQ) 位: 控制 FIQ 中断是否允许。
@ M (Mode) 位: 控制处理器当前运行的模式。
cpsid i
@ 1.关闭I/D Cache, MMU
@ 方式:修改SCTLR寄存器
@ (System Control Register,可以通过 CP15 协处理器访问)
@ SCTLR寄存器:
@ bit0:MMU
@ bit1:对齐控制
@ bit2:D Cache
@ bit11:分支预测控制
@ bit12:I Cache
MRC p15, 0, r0, c1, c0, 0 @ Move to Register from Coprocessor(这个形式操作的是SCTLR寄存器)
bic r0, r0, #(1<<12) @ 关闭I Cache(bic:Bit Clear)
bic r0, r0, #(1<<11) @ 关闭分支预测
bic r0, r0, #(1<<2) @ 关闭D Cache
bic r0, r0, #(1<<1) @ 关闭对齐控制
bic r0, r0, #(1<<0) @ 关闭MMU
MCR p15, 0, r0, c1, c0, 0 @ Move to Coprocessor from Register
#if 0
@ 2.设置中断向量偏移
@ 方式:修改VBAR寄存器
@ (Vector Base Address Register,可以通过 CP15 协处理器访问)
ldr r0, =0x87800000
dsb
isb
MCR p15, 0, r0, c12, c0, 0 @ 设置VBAR寄存器为0x87800000
dsb
isb
#endif
.global _bss_start @ 声明_bss_start符号为全局可见
_bss_start: @ 定义_bss_start标签
.word _bss_start @ 将_bss_start标签的地址存储为一个字(4字节)
.global _bss_end @ 声明_bss_end符号为全局可见
_bss_end: @ 定义_bss_end标签
.word _bss_end @ 将_bss_end标签的地址存储为一个字(4字节)
@ 3.清除bss段
@ 方式:使用循环逐个清除
ldr r0, _bss_start @ 将_bss_start地址加载到r0寄存器
ldr r1, _bss_end @ 将_bss_end地址加载到r1寄存器
mov r2, #0 @ 将0存储到r2寄存器
bss_loop:
stmia r0!, {r2} @ 存储r2寄存器的值到r0指向的内存地址,并自增r0
cmp r0, r1 @ 比较r0和r1的值
ble bss_loop @ 如果r0小于等于r1,则跳转到bss_loop标签(继续循环)
@ 4.设置处理器进入IRQ模式
@ 方式:修改cpsr寄存器
@ (Current Program Status Register,当前程序状态寄存器)
@ 它包含了处理器状态和控制信息,例如 APSR、指令集状态、IT 块状态、字节序和当前处理器模式。
mrs r0, cpsr @ 读取CPSR寄存器的值到r0寄存器
bic r0, r0, #0x1f @ 通过位清除操作,清除r0寄存器的低5位
orr r0, r0, #0x12 @ 使用IRQ模式
msr cpsr, r0 @ 将r0寄存器的值写回CPSR寄存器
ldr sp, =0x80600000 @ 设置IRQ下的sp指针
@ 5.设置处理器进入SYS模式
@ 方式:修改cpsr寄存器
@ (Current Program Status Register,当前程序状态寄存器)
@ 它包含了处理器状态和控制信息,例如 APSR、指令集状态、IT 块状态、字节序和当前处理器模式。
mrs r0, cpsr @ 读取CPSR寄存器的值到r0寄存器
bic r0, r0, #0x1f @ 通过位清除操作,清除r0寄存器的低5位
orr r0, r0, #0x1f @ 使用SYS模式
msr cpsr, r0 @ 将r0寄存器的值写回CPSR寄存器
ldr sp, =0x80400000 @ 设置SYS下的sp指针
@ 6.设置处理器进入SVC模式
@ 方式:修改cpsr寄存器
@ (Current Program Status Register,当前程序状态寄存器)
@ 它包含了处理器状态和控制信息,例如 APSR、指令集状态、IT 块状态、字节序和当前处理器模式。
mrs r0, cpsr @ 读取CPSR寄存器的值到r0寄存器
bic r0, r0, #0x1f @ 通过位清除操作,清除r0寄存器的低5位
orr r0, r0, #0x13 @ 使用SVC模式
msr cpsr, r0 @ 将r0寄存器的值写回CPSR寄存器
ldr sp, =0x80200000 @ 设置SVC下的sp指针
@ 7.使能IRQ中断
@ 方式:修改PSTATE处理器状态寄存器
@ Change PE State (CPS) 用于修改处理器状态寄存器 (PSTATE) 中的某些位
@ PSTATE 是一个特殊的寄存器,它包含了处理器当前的运行状态信息,包括:
@ A (Application) 位: 控制应用程序模式下的中断是否允许。
@ I (IRQ) 位: 控制 IRQ 中断是否允许。
@ F (FIQ) 位: 控制 FIQ 中断是否允许。
@ M (Mode) 位: 控制处理器当前运行的模式。
cpsie i
@ 8.跳转到main函数的入口
b main @ 跳转到main函数的入口