按键中断
/*
*硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
*软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
*参考资料:https://wenku.baidu.com/view/6e2a774a336c1eb91a375d64.html, S3C2440datasheet,开发版原理图,《ARM体系与编程》杜春雷
*/
目录
一、什么是中断
中断:也称为异步中断。因此它是由其他硬件设备依照 CPU 时钟信号随机产生,即意味着中断能在指令之间发生。
中断又分为外部可屏蔽中断(INTR)和外部非屏蔽中断(NMI),所用I0设备产生的中断请求均引起可屏蔽中断。通过按键可以实现按键中断。
二、如何设置按键中断
1、电路分析
由原理图可知
按键S2-EINT0-GPF0 按键S3-EINT2-GPF2
按键S4-EINT11-GPG3 按键S5-EINT19-GPG11
2、中断函数编写思路
1、进行初始化设置:
{
①设置中断,使器件能发出中断信号
②设置中断控制器,使他能发出中断信号给CPU
③设置CPU,开中断(CPSR中的I位为中断总开关)
}
2、处理时,需分辨中断源
3、处理完需清除中断
以上设置都需要通过设置相关寄存器来设置
三、如何配置寄存器
通过查询手册可知,需要配置下列寄存器
1、设置中断,使器件能发出中断信号
1.1 初始化GPXCON,使按键为中断触发引脚
GPFCON &=~((3<<0)|(3<<4));
GPFCON |= ((2<<0)|(2<<4)); //设置S2、S3
GPGCON &=~((3<<6)|(3<<22));
GPGCON |= ((2<<22)|(2<<6)); //设置S4、S5
1.2 配置EXTINTn寄存器,外部中断为双边沿触发
EXTINT0 |= ((7<<0)|(7<<8)); //设置 EINT0、EINT2
EXTINT1 |= (7<<12); //设置EINT11
EXTINT2 |= (7<<12); //设置EINT19
3.3 配置EINTMASK寄存器,允许EINT0,EINT2,EINT11,ENIT19向中断控制器发生中断
!其中EINT0-EINT3的中断信号不需要配置,可以直接到达中断控制器!
EINTMASK &=~ ((1<<11)|(1<<19)); //使能EINT11,19
2、设置中断控制器,使他能发出中断信号给CPU
分析:中断请求由硬件产生,根据中断源类型分别将中断信号送到SUBSRCPND(SubSourcePending)和SRCPND(SourcePending)寄存器,
SUBSRCPND是子中断源暂存寄存器,用来保存子中断源信号,SRCPND是中断源暂存寄存器,用来保存中断源信号。中断信号可通过编程方式屏蔽掉,SUBMASK是子中断源屏蔽寄存器,可以屏蔽指定的子中断信号, MASK功能同SUBMASK用来屏蔽中断源信号。中断分为两种模式:一般中断的和快速中断,MODE是中断模式判断寄存器,用来判断当前中断是否为快速中断,如果为快速中断直接将快速中断信号送给ARM内核,如果不是快速中断,还要将中断信号进行仲裁选择。S3C2440A支持多达60种中断,很有可能多个硬件同时产生中断请求,这时要求中断控制器做出裁决,Priority是中断源优先级仲裁选择器,当多个中断产生时,选择出优先级最高的中断源进行处理,INTPND是中断源结果寄存器,里面存放优先级仲裁出的唯一中断源。
(摘自:https://www.cnblogs.com/mr-raptor/archive/2011/06/20/2347673.html)
在INTERRUPT SUB SOURCES列表里面没有找到EINT0、EINT2、EINT11和EINT19,所以这四者是without sub -register从路线图上看,需要配置SRCPND、INTPND以及使用到相关寄存器。
2.1 SRCPND寄存器
作用:显示哪个中断产生了, 需要清除对应位
清除方式:可以通过将数据写入该寄存器来清除SRCPND寄存器的特定位。它只清除位位置对应于数据中设置为1的SRCPND。
EINT0-BIT[0]
EINT2-BIT[2]
EINR8_23-BIT[5]
2.2 INTMOD寄存器
作用:设置中断模式为FIQ还是IRQ
默认值为0x00000000,不需要重新设置
2.3 INTMSK寄存器
作用:对应位设置为0,CPU为来自相应中断源的中断请求提供服务
EINT0-BIT[0]
EINT2-BIT[2]
EINR8_23-BIT[5]
INTMSK &=~ ((1<<0)|(1<<2)|(1<<5));
2.4 INTPND寄存器
作用:显示当前优先级最高的、正在发生的中断, 需要清除对应位
清除方式:可以通过将数据写入该寄存器来清除INTPND寄存器的特定位。它只清除位位置对应于数据中设置为1的INTPND。
EINT0-BIT[0]
EINT2-BIT[2]
EINT8_23-BIT[5]
2.5 INTOFFSET寄存器
作用:显示IRQ模式的哪个中断请求在INTPND寄存器中,这个位可以通过清除SRCPND和INTPND来自动清除。
EINT0-BIT[0]
EINT2-BIT[2]
EINT8_23-BIT[5]
提问:如何知道EINT8_23中具体哪一个中断产生?
回答:通过读EINTPEND寄存器的值可以知道。
2.6 EINTPEND寄存器
对应位为1,则发生了中断,为0,则无发生。
3、设置CPU,开中断(CPSR中的I位为中断总开关)
配置CPRS寄存器,使其I位置0
bic r0, r0, #(1<<7) /* 清除I位, 使能中断 */
msr cpsr, r0 /* 写入值,改变cpsr的值*/
四、如何在代码中实现
1、新建interrupt.c文件,作为外部中断函数,
功能实现:通过4个按键触发外部中断,控制LED灯的亮灭,编写代码:
#include "s3c2440_soc.h"
/* EINTPEND,可通过读此寄存器知道EINT4-EINT23哪个发生中断,请中断时可往里面写1 */
/* SRCPND 用来显示哪个中断产生了, 需要清除对应位
* EINT0-BIT[0]
* EINT2-BIT[2]
* EINR8_23-BIT[5]
*/
/* INTMOD 用来设置发生中断模式(IRQ和FIQ)
* 0-IRQ,1-FIQ,默认为0
*/
/* INTMSK 用来屏蔽中断, 1-masked
* EINT0-BIT[0]
* EINT2-BIT[2]
* EINR8_23-BIT[5]
*/
/* 初始化中断控制器 */
void interrupt_init(void)
{
INTMSK &=~ ((1<<0)|(1<<2)|(1<<5));
}
/* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
* EINT0-BIT[0]
* EINT2-BIT[2]
* EINR8_23-BIT[5]
*/
/* INTOFFSET 用来显示INTPND中哪一位被设置为1
* 这个位可以通过清除SRCPND和INTPND来自动清除。
*/
/*
*初始化按键并将其设置为中断源
*S2:EINT0:GPF0 S3:EINT2:GPF2 S4:EINT11:GPG3 S5:EINT19:GPG11
*设置中断方式:双边触发
*/
void key_eint_int(void)
{
/*
*配置按键
*设置寄存器,先请零、后设置
*/
GPFCON &=~((3<<0)|(3<<4));
GPFCON |= ((2<<0)|(2<<4)); //设置S2、S3
GPGCON &=~((3<<6)|(3<<22));
GPGCON |= ((2<<22)|(2<<6)); //设置S4、S5
EXTINT0 |= ((7<<0)|(7<<8)); //设置 EINT0、EINT2
EXTINT1 |= (7<<12); //设置EINT11
EXTINT2 |= (7<<12); //设置EINT19
EINTMASK &=~ ((1<<11)|(1<<19)); //使能EINT11,19
/*
*设置LED灯
*配置GPF4,5,6为输出口
*先对寄存器CPFCON清0
*后再配置寄存器,是GPF4,5,6为输出口
*/
GPFCON &=~ ((3<<8)|(3<<10)|(3<<12));
GPFCON |= ((1<<8)|(1<<10)|(1<<12));
}
/* 中断处理函数
* 先分辨中断源,对不同的中断源进行处理
* 后清中断
*/
void key_irq_handle(int flag)
{
unsigned int val = EINTPEND;
unsigned int valf = GPFDAT;
unsigned int valg = GPGDAT;
if (flag == 0)
{
if (valf & (1<<0)) /* s2 --> gpf6 */
{
/* 松开 */
GPFDAT |= (1<<6);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<6);
}
}
else if (flag == 2)
{
if (valf & (1<<2)) /* s3 --> gpf5 */
{
/* 松开 */
GPFDAT |= (1<<5);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<5);
}
}
else if (flag == 5)
{
if (val & (1<<11)) /* eint11 */
{
if (valg & (1<<3)) /* s4 --> gpf4 */
{
/* 松开 */
GPFDAT |= (1<<4);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<4);
}
}
else if (val & (1<<19)) /* eint19 */
{
if (valg & (1<<11))
{
/* 松开 */
/* 熄灭所有LED */
GPFDAT |= ((1<<4) | (1<<5) | (1<<6));
}
else
{
/* 按下: 点亮所有LED */
GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));
}
}
}
EINTPEND = val;
}
void handle_irq_c(void)
{
int bit = INTOFFSET;
if (bit==0||bit==2||bit==5)
{
key_irq_handle(bit);
}
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
2.start.S汇编文件代码如下:
.text
.global _start
_start:
b reset /* vector 0:reset */
ldr pc, und_addr /* vector 4:und_addr */
ldr pc, swi_addr /* vector 8:swi_addr */
b halt/* vector C:Abort (prefetch) */
b halt/* vector 10:Abort (data) */
b halt/* vector 14:Reserved */
ldr pc, irq_addr/* vector 18:IRQ */
b halt/* vector 1C:FIQ */
und_addr:
.word do_und
swi_addr:
.word do_swi
irq_addr:
.word do_irq
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
/*
* 1.保存现场
* 2.处理异常
* 3.恢复现场
*/
ldr sp, =0x34000000 //und模式下的sp寄存器为该模式下专用寄存器,使用前需要对其进行设置
/* 在进入und模式的时候,查表可知r0-r12寄存器的值可能会被修改,所以需要对其进行保存 */
stmdb sp!, {r0-r12,lr}
/* 处理异常函数:把cpsr内的信息放到r0,执行printException */
mrs r0, cpsr
ldr r1, =und_string
bl printException
/* 恢复现场,把spsr寄存器的值传输回cpsr*/
ldmia sp!, {r0-r12,pc}^
und_string:
.string "undefined instruction exception"
.align 4
do_swi:
/* 执行到这里之前:
* 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_svc保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 4. 跳到0x8的地方执行程序
*/
/*
* 1.保存现场
* 2.处理异常
* 3.恢复现场
*/
ldr sp, =0x33e00000 //und模式下的sp寄存器为该模式下专用寄存器,使用前需要对其进行设置
/* 在进入und模式的时候,查表可知r0-r12寄存器的值可能会被修改,所以需要对其进行保存 */
stmdb sp!, {r0-r12,lr}
mov r4,lr //把lr寄存器下一条即将执行的指令的地址传到r0
/* 处理异常函数:把cpsr内的信息放到r0,执行printException */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
sub r0,r4,#4 //r4地址减四就是swi指令执行地址
bl printSWIVal
/* 恢复现场,把spsr寄存器的值传输回cpsr*/
ldmia sp!, {r0-r12,pc}^
swi_string:
.string "swi exception"
.align 4
do_irq:
/* 执行到这里之前:
* 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_irq保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10010, 进入到und模式
* 4. 跳到0x18的地方执行程序
*/
/*
* 1.保存现场
* 2.处理异常
* 3.恢复现场
*/
ldr sp, =0x33d00000 //und模式下的sp寄存器为该模式下专用寄存器,使用前需要对其进行设置
/* 在进入und模式的时候,查表可知r0-r12寄存器的值可能会被修改,所以需要对其进行保存 */
sub lr,lr,#4
stmdb sp!, {r0-r12,lr}
/* 处理中断函数 */
bl handle_irq_c
/* 恢复现场,把spsr寄存器的值传输回cpsr*/
ldmia sp!, {r0-r12,pc}^
reset:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
bl copy_to_sdram
bl clean_bss
/*
*复位之后, cpu处于svc模式
*系统先进入用户模式,修改cpsr寄存器的值
*后在swi指令触发异常
*/
mrs r0,cpsr /* 读出cpsr的值 */
bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */
bic r0, r0, #(1<<7) /* 清除I位, 使能中断 */
msr cpsr, r0 /* 写入值,改变cpsr的值*/
/* 设置usr模式下的sp寄存器 */
ldr sp, =0x33f00000
ldr pc, =sdram
sdram:
bl uart0_init
bl print1
/*故意定义一条未定义指令,使cpu进入und模式 */
und_code:
.word 0xdeadc0de //word定义的是一变量,会开辟占用内存
bl print2
swi 0x123 /* swi指令,用来触发异常 */
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt