本文是基于韦东山视频的学习笔记
总汇
根据电路原理图
- S2 ~ EINT0 //按键2对应中断0
- S3 ~ EINT2 //按键3对应中断2
- S4 ~ EINT11 //按键4对应中断11
- S5 ~ EINT19 //按键5对应中断19
中断也是一种异常,但中断较为复杂一点,除了一般异常的处理流程
- 保护现场
- 处理 (handle_irq_c )
- 判断中断源 [INTOFFSET]
- 跳到中断处理函数 (key_action)[EINTPEND]
- 清除中断 [SRCPND] [INTBEND]
- 恢复现场
当然还有寄存器设置函数 key_enit_init [EXTINTn] [EINTPEND]
更为甚者,我们可以来参考手册上的流程图
对于我们的按键中断来讲,都是without sub-register。
寄存器
使用什么外设,其实都是和寄存器打交道。那么,这次又和哪些寄存器打交道呢。
总中断寄存器
- [INTMASK] Determine which interrupt source is masked.
The masked interrupt source will not be
serviced.(需要设置,在 register_irq 里设置)- [INTBEND] Indicate the interrupt request status.(需要清零, handle_irq_c )
- [INTOFFSET] Indicate the IRQ interrupt request source(只读, handle_irq_c )
- [SRCPND] Indicate the interrupt request status.(需要清零, handle_irq_c )
INTMSK 寄存器 中断屏蔽寄存器,置1是屏蔽中断源,置0是使能,默认是全部屏蔽,所以需要设置。
- 地址:0X4A000008
- [0] : EINT0 = 0x0(S2 - EINT0)
- [2] : EINT2 = 0x0(S3 ~ EINT2)
- [5] : EINT8_23 = 0x0(S4 ~ EINT11,S5 ~ EINT19)
- 即 [0X4A000008] &= ((0<<0) | (0<<2) | (0<<5))
INTPND 寄存器 Indicate the interrupt request status.
- 地址: 0X4A000010
表明被请求了的寄存器 - [0] : EINT0 (S2 - EINT0)
- [2] : EINT2 (S3 ~ EINT2)
- [5] : EINT8_23 (S4 ~ EINT11,S5 ~ EINT19)
INTOFFSET 寄存器 Indicate the IRQ interrupt request source
- 地址:0x4A000014
只读寄存器 - 0 : EINT0 (S2 - EINT0)
- 2 : EINT2 (S3 ~ EINT2)
- 5 : EINT8_23 (S4 ~ EINT11,S5 ~ EINT19)
SRCPND寄存器 Indicate the interrupt request status.
一共32位代表不同的中断,置0是关闭中断,置1是使能中断
- 地址: 0X4A000000
- [0] : EINT0 = 0x1(S2 - EINT0)
- [2] : EINT2 = 0x1(S3 ~ EINT2)
- [5] : EINT8_23 = 0x1(S4 ~ EINT11,S5 ~ EINT19)
外部(按键)中断设置
- [EXTINT0] / [EXTINT1] / [EXTINT2]
External interrupt control register 0 / 1 / 2(需要设置, key_enit_init )- [EINTMASK] External interrupt mask register (需要设置, key_enit_init )
- [EINTPEND] External interrupt pending register(需要清零, key_action)
EXTINT0 寄存器 External interrupt control register 0
- 地址:0x56000088
- [2:0] :EINT0 = 0x111(11x = Both edge triggered)
- [10:8]:EINT2 = 0x111(11x = Both edge triggered)
EXTINT1 寄存器 External interrupt control register 1
地址:0x5600008c
- [14:12] :EINT11 = 0x111(11x = Both edge triggered)
EXTINT2 寄存器 External interrupt control register 2
地址:0x56000090
- [14:12] :EINT19 = 0x111(11x = Both edge triggered)
EINTMASK 寄存器 外部中断屏蔽寄存器,置1是屏蔽中断源,置0是使能,默认是全部屏蔽,所以需要设置。
- 地址:0x560000a4(External interrupt mask register )
- [11] :EINT11 = 0x0
- [19] :EINT19 = 0x0
EINTPEND 寄存器 判断哪个中断发生
- 地址:0x560000a8(External interrupt pending register)
- 逐一对应
中断流程
首先是按键中断的各种寄存器初始化,这个真少不了。
/* 初始化按键中断 */
void key_eint_init()
{
/* 初始化按键寄存器
* 设置GPFCON的GPF0/2、GPG3/11为中断引脚
* 按键寄存器
* C_S2 ~ 0 ~ GPF0
* C_S3 ~ 4 ~ GPF2
* C_S4 ~ 6 ~ GPG3
* C_S5 ~ 22 ~ GPG11
* D_S2 ~ 0 ~ GPF0
* D_S3 ~ 2 ~ GPF2
* D_S4 ~ 3 ~ GPG3
* D_S5 ~ 11 ~ GPG11
*/
GPFCON &= ~((3<<C_S2) | (3<<C_S3)); /* 把GPFCON 需要位置清零 */
GPGCON &= ~((3<<C_S4) | (3<<C_S5)); /* 把GPGCON 需要位置清零 */
GPFCON |= ((2<<C_S2) | (2<<C_S3)); /* 把GPFCON 需要位置置位0x10 */
GPGCON |= ((2<<C_S4) | (2<<C_S5)); /* 把GPGCON 需要位置置位0x10 */
/* EXTINT0 寄存器 External interrupt control register 0
- 地址:0x56000088
- [2:0] :EINT0 = 0x111(11x = Both edge triggered)
- [10:8] :EINT2 = 0x111(11x = Both edge triggered)
* EXTINT1 寄存器 External interrupt control register 1
* 地址:0x5600008c
- [14:12] :EINT11 = 0x111(11x = Both edge triggered)
* EXTINT2 寄存器 External interrupt control register 2
* 地址:0x56000090
- [14:12] :EINT19 = 0x111(11x = Both edge triggered)
* EINTMASK 寄存器 I/O中断屏蔽寄存器,置1是屏蔽中断源,置0是使能,默认是全部屏蔽,所以需要设置。
- 地址:0x560000a4(External interrupt mask register )
- [11] :EINT11 = 0x0
- [19] :EINT19 = 0x0
*/
EXTINT0 |= ((7<<0) | (7<<8)); /* s2, s3*/
EXTINT1 |= ((7<<12)); /* s4 */
EXTINT2 |= ((7<<12)); /* s5 */
EINTMASK &= ~((1<<11) | (1<<19)); /* 使能外部中断,关闭屏蔽信号 */
register_irq(0, key_irq);
register_irq(2, key_irq);
register_irq(5, key_irq);
}
一按下按键,硬件处理,首先就会跳到向量组 vector 0x18,通过汇编跳到 do_irq, 在这里完成保护现场和恢复现场,处理异常跳到c函数 handle_irq_c 。
do_irq:
/* 重要!栈未设置,需要重新设置,指向一个不会被用到的地方 */
ldr sp, =0x33d00000
/* 保护现场 */
sub lr, lr, #4 //根据lr恢复规则,异常前的lr-4 = 异常后的lr
stmdb sp!, {r0-r12, lr} //目前位置 lr 里面有被打断前的下一条将要执行的指令,所以也要保存
/* 处理异常 */
bl handle_irq_c
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
来到函数 handle_irq_c,做三个动作,分别是分辨中断源和清除中断,处理中断通过 irq_array[bit](bit) 语句跳转,这个其实是函数指针,in this case,处理函数是 key_action,里面就可以处理按键按下后的事情,整个中断流程至此。
typedef void(*irq_func)(int);
irq_func irq_array[32];
void handle_irq_c()
{
/* 分辨中断源 */
/*INTOFFSET 寄存器** Indicate the IRQ interrupt request source
- 地址:0x4A000014
只读寄存器
- 0 : EINT0 (S2 - EINT0)
- 2 : EINT2 (S3 ~ EINT2)
- 5 : EINT8_23 (S4 ~ EINT11,S5 ~ EINT19)
*/
int bit = INTOFFSET; //读出来是哪个中断
/* 处理中断 */
irq_array[bit](bit);
/* 清除中断 */
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
还有一个问题没解决 irq_array[bit](bit) 怎么指向 key_irq ?
我们搞一个函数,注册中断函数 ,在里面顺便设置中断屏蔽
/* 注册中断的指针函数, 附加使能总中断
* irq - 第几个处理函数,fp - 传入的中断处理函数
*/
void register_irq(int irq, irq_func fp)
{
irq_array[irq] = fp;
/* [INTMSK] 寄存器 中断屏蔽寄存器
* 置1是屏蔽中断源,置0是使能,默认是全部屏蔽,所以需要设置。
- 地址:0X4A000008
- [0] : EINT0 = 0x0(S2 - EINT0)
- [2] : EINT2 = 0x0(S3 ~ EINT2)
- [5] : EINT8_23 = 0x0(S4 ~ EINT11,S5 ~ EINT19)
- 即 [0X4A000008] &= ((0<<0) | (0<<2) | (0<<5))
*/
INTMSK &= ~(1<<irq);
}
相关代码
中断初始化(总/外部中断)
/* 初始化中断控制器 */
void interrput_init()
{
/* SRCPND寄存器 Indicate the interrupt request status.
* 一共32位代表不同的中断,置0是关闭中断,置1是使能中断
- 地址: 0X4A000000
- [0] : EINT0 = 0x1(S2 - EINT0)
- [2] : EINT2 = 0x1(S3 ~ EINT2)
- [5] : EINT8_23 = 0x1(S4 ~ EINT11,S5 ~ EINT19)
* INTMSK 寄存器 中断屏蔽寄存器
* 置1是屏蔽中断源,置0是使能,默认是全部屏蔽,所以需要设置。
- 地址:0X4A000008
- [0] : EINT0 = 0x0(S2 - EINT0)
- [2] : EINT2 = 0x0(S3 ~ EINT2)
- [5] : EINT8_23 = 0x0(S4 ~ EINT11,S5 ~ EINT19)
- 即 [0X4A000008] &= ((0<<0) | (0<<2) | (0<<5))
*/
INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
}
按键中断处理
/* 按键处理函数,包括按键处理
irq - 0 : EINT0 (S2 - EINT0)
irq - 2 : EINT2 (S3 ~ EINT2)
irq - 5 : EINT8_23 (S4 ~ EINT11,S5 ~ EINT19)
*/
int key_action(int irq)
{
unsigned int val = EINTPEND;
/* S2-GPF0 、 S3-GPF2 、 S4 - GPG3
* S2~GPF6、S3~GPF5、S4~GPF4
* 如果按键对应DAT寄存器是零,即按键按下,反之
*/
/* S2控制LED3 */
if (irq == 0)
{
if (GPFDAT&(1<<D_S2)){
GPFDAT |= (1<<D_LED3);
}
else{
GPFDAT &= ~(1<<D_LED3);
}
}
/* S3控制LED2 */
else if (irq == 2)
{
if (GPFDAT&(1<<D_S3)){
GPFDAT |= (1<<D_LED2);
}
else{
GPFDAT &= ~(1<<D_LED2);
}
}
/* S4控制LED1 */
else if (irq == 5)
{
if (val & (1<<11))
{
if (GPGDAT&(1<<D_S4)){
GPFDAT |= (1<<D_LED1);
}
else{
GPFDAT &= ~(1<<D_LED1);
}
}
else if (val & (1<<19))
{
if (GPGDAT&(1<<D_S5)){
GPFDAT |= ((1<<D_LED1) | (1<<D_LED2) | (1<<D_LED3));
}
else{
GPFDAT &= ~((1<<D_LED1) | (1<<D_LED2) | (1<<D_LED3));
}
}
}
EINTPEND = val; //清除外部中断
}
void handle_irq_c()
{
/* 分辨中断源 */
/*INTOFFSET 寄存器** Indicate the IRQ interrupt request source
- 地址:0x4A000014
只读寄存器
- 0 : EINT0 (S2 - EINT0)
- 2 : EINT2 (S3 ~ EINT2)
- 5 : EINT8_23 (S4 ~ EINT11,S5 ~ EINT19)
*/
int bit = INTOFFSET; //读出来是哪个中断
/* 处理中断 */
if(bit == 0 || bit == 2 || bit == 5)
key_action(bit);
/* 清除中断 */
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
汇编
_start:
b reset /* 0x30000000 */
// ldr pc, =do_und /* 0x30000004 */
ldr pc, und_addr
ldr pc, swi_addr
b halt /* vector 0x0c : prefetch aboot */
b halt /* vector 0x10 : data abort */
b halt /* vector 0x14 : reserved */
ldr pc, irq_addr /* vector 0x18 : irq */
b halt /* vector 0x1c : fiq */
……
do_irq:
/* 重要!栈未设置,需要重新设置,指向一个不会被用到的地方 */
ldr sp, =0x33d00000
/* 保护现场 */
sub lr, lr, #4 //根据lr恢复规则,异常前的lr-4 = 异常后的lr
stmdb sp!, {r0-r12, lr} //目前位置 lr 里面有被打断前的下一条将要执行的指令,所以也要保存
/* 处理异常 */
bl handle_irq_c
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */