本文共 4029 字,大约阅读时间需要 13 分钟。
CPU在工作的过程中,经常需要与外设进行交互,交互的方式包括“轮询方式”,“中断方式”。
1. 轮询方式:CPU不断地查询设备的状态。该方式实现比较简单,但CPU利用率很低,不适合多任务的系统。 2. 中断方式:CPU在告知硬件开始一项工作后,就去做别的事去了,当硬件完成了该项工作后,向CPU发送一个信号,告知CPU它已经完成了这项工作。中断的生命周期:中断源产生中断信号,信号被送往中断控制器进行中断信号过滤,如果通过则发往cpu进行中断处理。
在中断的生命周期中,中断源的作用是负责产生中断信号。S3C2440支持60个中断源 ;
S3C6410支持64个中断源 ; S5PV210支持93个中断源 ;从S3C2440的数据手册可以看到,S3C2440支持60个中断源,并且列出两个表格罗列出所有的中断源,一个表格为interrupt source(中断源),一个表格为interrupt sub source(子中断源)。interrupt source表格中有些中断源是含有几个子中断源。如interrupt source表格中的INT_UART0包含有三个子中断源:ERR, RXD和TXD,这三个子中断源可以在interrupt sub source表格中找到。
中断过滤
由上图可以看出,中断过滤根据产生中断的中断源不同会有两种不同的处理方式。子中断源产生中断后,会在寄存器SUBSRCPND(子中断源挂起寄存器)对应的位置置1,也即发起中断请求;发起的中断请求由寄存器SUBMASK(子中断屏蔽寄存器)的相应位判断请求通不通过;若通过则在寄存器SRCPND相应位置1,并由寄存器MASK判断中断是否能通过;若通过,则根据该中断的优先级来决定该中断是立刻处理还是排队等候。MODE用于设置中断是快速中断还是普通中断。
中断处理
中断的处理有两种方式: ①非向量方式(2440) ②向量方式(6410,210)1.非向量方式:中断产生后,cpu的pc指针会跳转到一个固定的地址入口(硬件完成),跳转后执行相应代码,该部分代码需要保存中断前的环境,然后判断产生中断的中断源,然后调用对应该中断的中断处理函数,处理完成后还需要清除一些与中断有关的寄存器的值(在中断的生命周期中,当发生中断后,一些寄存器的相应位会被置1,因此在处理完中断后,我们需要对这些寄存器的相应位清0,特殊的是,向这几个寄存器的需要清零的位写入1才能实现清0),最后回复中断前的环境。
2.向量方式:中断产生后,cpu直接跳转到中断处理程序处,这与非向量方式代码需要判断中断源以及跳转到相应处理函数不同。在向量方式中,判断中断源以及跳转由硬件完成,至于跳转的地址由我们初始化的时候设置好。
判断中断源:S3C2440的寄存器INTERRUPT OFFSET (INTOFFSET) REGISTER是用于判断中断源的种类。通过读取该寄存器的值就可以判断中断源。
由中断的生命周期可以看出,初始化中断,使程序能够使用中断,代码需要完成的工作有:
①中断源初始化 ②初始化中断控制器 ③中断处理 除了以上三点,还需要使能中断。1.中断源初始化,这里采用的中断源是按键中断
#define GPFCON (volatile unsigned long *)0x56000050/*K1,K2,K3,K4对应GPF1、GPF4、GPF2、GPF0*/#define GPF0_int (0x2<<(0*2))#define GPF1_int (0x2<<(1*2))#define GPF2_int (0x2<<(2*2))#define GPF4_int (0x2<<(4*2))#define GPF0_msk (3<<(0*2))#define GPF1_msk (3<<(1*2))#define GPF2_msk (3<<(2*2))#define GPF4_msk (3<<(4*2))void button_init(){ *(GPFCON) &= ~(GPF0_msk | GPF1_msk | GPF2_msk | GPF4_msk);//先对相应位清零 *(GPFCON) |= GPF0_int | GPF1_int | GPF2_int | GPF4_int;}
2.中断控制器初始化以及开中断
#define INTMSK (volatile unsigned long *)0x4A000008#define EINTMASK (volatile unsigned long *)0x560000a4void init_irq(){ // 对于EINT4,需要在EINTMASK寄存器中使能它,必须在初始化INTMSK之前初始化该寄存器 *(EINTMASK) &= ~(1<<4); // EINT0、EINT1、EINT2、EINT4_7使能 *(INTMSK) &= (~(1<<0)) & (~(1<<1)) & (~(1<<2)) & (~(1<<4)); __asm__( /*开中断*/ "mrs r0,cpsr\n" "bic r0, r0, #0x80\n" "msr cpsr_c, r0\n" : : );}
3.中断处理:产生中断时,pc指针会跳转到中断向量表中,而在初始化中断向量表的时候已经设置了该地址执行指令ldr pc, _irq,因此会跳转到irq去执行处理函数。保存环境主要是保存通用寄存器r0-r12,以及程序正在执行的地址。当cpu正在执行的指令的地址为n时,lr寄存器的值是n+8;当发生中断后,cpu会执行完正在执行的指令然后跳转去处理中断;当处理完中断后,cpu需要继续执行原程序,由于地址为n的指令已经执行完,所以此时需要执行的指令的地址为n+4,也即将lr寄存器的值减4然后赋给pc寄存器。
irq: sub lr, lr, #4 /*lr=lr-4*/ stmfd sp!, {r0-r12, lr} /* 保护现场,将r0到r12,lr寄存器的值保存到堆栈 */ bl handle_int ldmfd sp!, {r0-r12, pc}^ /* 恢复现场,将堆栈中的数据赋给r0到r12,pc寄存器,^表示把spsr恢复到cpsr */ void handle_int(){ /*读取产生中断的源*/ unsigned long value = *(INTOFFSET); switch(value) { case 0: //EINT0~K4 led_on(); break; case 1: //EINT1~K1 led_off(); break; case 2: //EINT2~K3 led_on(); break; case 4: //EINT4~K2 led_off(); break; default: break; } /* 中断清除 */ if(value == 4) *(EINTPEND) = (1 << 4); *(SRCPND) = 1 << value; *(INTPND) = 1 << value;}
中断还无法工作,原因在于栈的初始化。之前的bootloader中对栈进行了初始化,但初始化的仅仅是svc模式下的栈,中断模式下的栈还没有初始化。要初始化中断模式下的栈,首先进行中断模式然后初始化栈,初始化后再进入svc模式。
init_stack: msr cpsr_c, #0xd2 @进入中断模式 ldr sp, =0x33000000 @此处实际设置的是r13_irq msr cpsr_c, #0xd3 @进入svc模式 ldr sp, =0x34000000 @此处实际设置的是r13_svc
S3C6410和S5PV210另外需要注意的地方:
6410和210需要设置发生中断的时间,可设置成低电平触发,高电平触发,上升沿触发,下降沿触发等。 6410支持64个中断,这64个中断源分为两组,VIC0组,VIC1组。每个中断源都有一个寄存器,共64个寄存器;该寄存器存储着该中断的处理函数的地址。因此发生中断后,硬件自动判断中断源并根据这些地址跳转到相应的处理函数处执行指令。因此初始化时还需要建立中断向量表。6410向下兼容2440的中断处理方式,既支持向量方式,也支持非向量方式,默认是采用非向量方式。因此,要采用向量方式还需要在协处理器cp15处进行设置(210默认是采用向量方式)。
清除中断:6410除了64个存储着中断处理函数的地址的寄存器外,还有两个与地址有关的寄存器VIC0ADDRESS,VIC1ADDRESS。当所发生的中断属于VIC0组时,cpu除了会根据相应寄存器保存的地址跳转到处理函数外,还会将这个地址写入VIC0ADDRESS,因此执行完处理函数后需要清除该寄存器。VIC1组与VIC0组一样。