1. 中断是什么?
中断是一种异常,特点是CPU被打断后执行中断处理程序,然后再恢复执行原来的程序。
中断是软件依托硬件提供的机制实现的,理解异常,必须先理解硬件的机制。
2. 硬件提供的机制——异常向量表
当发生异常时,硬件会自动让pc跳到异常向量表对应位置执行,
异常向量表如下:
可以看出异常向量表就是数组,数组元素是函数指针,按照访问内存的方式访问。
板子刚启动时,处于SVC模式,查阅 iROM_Application_note 可以知道异常向量表的基地址。
我们可以根据上面知识写出下面代码:
#define EXCEPTION_BASE 0xD0037400 #define rFIQ_EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE + 0x1C)) #define rIRQ_EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE + 0x18)) #define rDATA_ABORT_EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE + 0x10)) #define rPREFECTH_ABORT_EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE + 0x0C)) #define rSOFTWARE__EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE + 0x08)) #define rUNDEFINED_EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE + 0x04)) #define rRESET_EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE)) #include "stdio.h" void fiq_exception(void) { printf("fiq_exception\n"); } void irq_exception(void) { printf("irq_exception\n"); } void data_abort_exception(void) { printf("data_abort_exception\n"); } void prefetch_exception(void) { printf("prefetch_exception\n"); } void software_exception(void) { printf("software_exception\n"); } void undefined_exception(void) { printf("undefined_exception\n"); } void reset_exception(void) { printf("reset_exception\n"); } void system_exception_init() { rFIQ_EXCEPTION = (unsigned int)fiq_exception; rIRQ_EXCEPTION = (unsigned int)irq_exception; rDATA_ABORT_EXCEPTION = (unsigned int)data_abort_exception; rPREFECTH_ABORT_EXCEPTION = (unsigned int)prefetch_exception; rSOFTWARE__EXCEPTION = (unsigned int)software_exception; rUNDEFINED_EXCEPTION = (unsigned int)undefined_exception; rRESET_EXCEPTION = (unsigned int)reset_exception; }
这样将中断处理程序绑定到异常向量表,如果发生了异常就会跳转执行对应打印语句。
但上面的代码不能实现中断,因为中断是停下手上的工作,去完成中断服务,然后返回继续原先的工作,这要求保护现场,处理中断,恢复现场。
所以异常向量表绑定的程序应该这样写:
irq_exception: ldr sp, =IRQ_STACK @由于异常发生时,CPU模式切换,IRQ的栈需要设置 sub lr, lr, #4 @ 由于ARM的流水线技术,所以 返回地址需要计算 stmfd sp!, {r0-r12, lr} @ 将返回地址和寄存器值保持到栈中 bl irq_handler @ 处理中断 ldmfd sp!, {r0-r12, pc}^ @ 先将栈中保持的值恢复到寄存器中,然后pc跳转到返回地址,最后 cpsr 恢复
上面还有一个细节,代码中没有体现,即异常发生时 硬件自动将 cpsr 的值保存到 spsr 中,所以再保护现场中我们没有手动完成这步。
上面就完成了中断处理的框架,下面讨论 irq_handler
3.硬件对异常的支持
irq_handler的目标是找到中断处理程序,并执行。
这就需要了解SoC对中断提供的其他支持了。
以S5PV210为例,210有120种左右的中断,每个中断都有一个自己的寄存器用来绑定自己的处理程序入口地址。
并且210将这120种中断分为4组,每组有一个状态寄存器,当发生中断时,状态 寄存器的对应为置一,
每组还有一个地址寄存器,当发生中断时,硬件自动将中断的入口地址写入那个地址寄存器。
下面给出与中断相关寄存器的总结:
中断最基本寄存器大致分为三类: (1)中断查看 IRQStatus FIQStatus RawInterrupt (2)中断设置 禁止和使能 VICINTENABLE VICINTENCLEAR 模式选择 VICINTSELECT (3)ISR vector address 地址绑定 VICVECTADDR[0-31] 地址获取 VICADDRESS
这样我们就知道了编程思路:
中断发生前
(1)初始化中断:
先禁止所有中断(防止跑飞),选择中断模式,中断地址寄存器清零。
将要使用的中断的程序入口地址绑定到对应的寄存器中。
开启要使用的中断。
中断发生后
(2)寻找中断服务程序,并执行
通过查看4组中断状态寄存器,如果非0,即为该组发生了中断,中断服务程序即在该组的地址寄存器中。
从地址寄存器中取出程序入口地址并执行。
(3)清中断
当中断处理完后,将4组中断状态寄存器置0。
那么(1)初始化中断就应该这样:
void intc_clearvectaddr(void) { VIC0ADDR = 0; VIC1ADDR = 0; VIC2ADDR = 0; VIC3ADDR = 0; } void int_init() { // 禁止所有中断 VIC0INTENCLEAR = 0xffffffff; VIC1INTENCLEAR = 0xffffffff; VIC2INTENCLEAR = 0xffffffff; VIC3INTENCLEAR = 0xffffffff; // 选择中断类型为IRQ VIC0INTSELECT = 0x0; VIC1INTSELECT = 0x0; VIC2INTSELECT = 0x0; VIC3INTSELECT = 0x0; // 清零地址寄存器 intc_clearvectaddr(); } void system_exception_init() { // 设置异常向量表 rFIQ_EXCEPTION = (unsigned int)fiq_exception; rIRQ_EXCEPTION = (unsigned int)irq_exception; rDATA_ABORT_EXCEPTION = (unsigned int)data_abort_exception; rPREFECTH_ABORT_EXCEPTION = (unsigned int)prefetch_exception; rSOFTWARE__EXCEPTION = (unsigned int)software_exception; rUNDEFINED_EXCEPTION = (unsigned int)undefined_exception; rRESET_EXCEPTION = (unsigned int)reset_exception; // 初始化中断 int_init(); }
main.c中绑定要用的中断服务程序和开启中断
void intc_setvectaddr(unsigned long intnum, void (*handler)(void)) { //VIC0 if(intnum<32) { *( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) = (unsigned)handler; } //VIC1 else if(intnum<64) { *( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler; } //VIC2 else if(intnum<96) { *( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler; } //VIC3 else { *( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler; } return; } // 使能中断 void intc_enable(unsigned long intnum) { unsigned long temp; if(intnum<32) { temp = VIC0INTENABLE; temp |= (1<<intnum); // 如果是第一种设计则必须位操作,第二种设计可以 // 直接写。 VIC0INTENABLE = temp; } else if(intnum<64) { temp = VIC1INTENABLE; temp |= (1<<(intnum-32)); VIC1INTENABLE = temp; } else if(intnum<96) { temp = VIC2INTENABLE; temp |= (1<<(intnum-64)); VIC2INTENABLE = temp; } else if(intnum<NUM_ALL) { temp = VIC3INTENABLE; temp |= (1<<(intnum-96)); VIC3INTENABLE = temp; } // NUM_ALL : enable all interrupt else { VIC0INTENABLE = 0xFFFFFFFF; VIC1INTENABLE = 0xFFFFFFFF; VIC2INTENABLE = 0xFFFFFFFF; VIC3INTENABLE = 0xFFFFFFFF; } }
int main() { uart_init(); system_exception_init(); // 绑定isr到中断控制器硬件 intc_setvectaddr(KEY_EINT2, isr_eint2); intc_setvectaddr(KEY_EINT3, isr_eint3); // 使能中断 intc_enable(KEY_EINT2); intc_enable(KEY_EINT3); printf("hello world\n"); return 0; }
第二步这样做
unsigned long intc_getvicirqstatus(unsigned long ucontroller) { if(ucontroller == 0) return VIC0IRQSTATUS; else if(ucontroller == 1) return VIC1IRQSTATUS; else if(ucontroller == 2) return VIC2IRQSTATUS; else if(ucontroller == 3) return VIC3IRQSTATUS; else {} return 0; } void irq_handler(void) { printf("irq_handler.\n"); unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR}; int i=0; void (*isr)(void) = NULL; for(i=0; i<4; i++) { if(intc_getvicirqstatus(i) != 0) { isr = (void (*)(void)) vicaddr[i]; break; } } (*isr)(); }
第三步
void isr_eint2(void) {// 第一,中断处理代码 printf("isr_eint2_LEFT.\n"); // 第二,清除中断挂起 intc_clearvectaddr(); }
这样就可以处理SoC内部外设产生的中断,但是对于外部设备的中断还不能处理。
4.外部中断
中断分为SoC内部中断,如定时器,外部中断,如按键。
对于外部中断是通过GPIO传输的,所以需要将GPIO设置为外部中断模式。
外部中断重要的寄存器如下:
EXT_INT_CON:
设置触发模式为电平触发还是边沿触发。
EXT_INT_MASK:
禁止或使能
EXT_INT_PEND:
外部中断挂起,但中断发生后,PEND的对应位会置1,就会不停的向SoC发中断请求。
那么对于按键的中断初始化就如下:
// 以中断方式来处理按键的初始化 void key_init_interrupt(void) { // 1. 外部中断对应的GPIO模式设置 rGPH0CON |= 0xFF<<8; // GPH0_2 GPH0_3设置为外部中断模式 rGPH2CON |= 0xFFFF<<0; // GPH2_0123共4个引脚设置为外部中断模式 // 2. 中断触发模式设置 rEXT_INT_0_CON &= ~(0xFF<<8); // bit8~bit15全部清零 rEXT_INT_0_CON |= ((2<<8)|(2<<12)); // EXT_INT2和EXT_INT3设置为下降沿触发 rEXT_INT_2_CON &= ~(0xFFFF<<0); rEXT_INT_2_CON |= ((2<<0)|(2<<4)|(2<<8)|(2<<12)); // 3. 中断允许 rEXT_INT_0_MASK &= ~(3<<2); // 外部中断允许 rEXT_INT_2_MASK &= ~(0x0f<<0); // 4. 清挂起,清除是写1,不是写0 rEXT_INT_0_PEND |= (3<<2); rEXT_INT_2_PEND |= (0x0F<<0); }
对于外部中断,中断处理完后还需要清中断挂起
void isr_eint2(void) { printf("isr_eint2_LEFT.\n"); // 第二,清除中断挂起 rEXT_INT_0_PEND |= (1<<2); intc_clearvectaddr(); }
以上就完成中断。