以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
参考博客
s5pv210——中断 - biaohc - 博客园
S5PV210的中断体系简介_天糊土的博客-CSDN博客
一、S5PV210的中断流程
第一部分是我们为中断响应而做的预备工作。
1、初始化中断控制器
比如先关闭所有中断,然后设置中断方式为IRQ,接着把保存有中断服务程序ISR地址的寄存器的内容置为0(方便下次装入ISR地址)。
2、将isr绑定到中断控制器
也就是把中断服务程序ISR的地址,放入这个中断对应的寄存器中。如何对应呢?每个中断都对应着一个中断号,比如S5PV210的按键简介讲述的,按键sw5、sw6对应着的中断号为2、3,也就是说,如果我想使用中断方式来处理按键sw5、sw6,我通过查原理图可以知道这两个按键对应的中断号。然后通过判断这个中断号的大小,来确定这个中断号对应哪个寄存器,然后再把这个中断的中断服务程序的地址写进这个中断对应的寄存器中。
3、相应中断的所有条件使能
也就是把表示中断是否开启的寄存器的相关的位置1。
第二部分是当硬件产生中断后如何自动执行isr。
1、自动根据异常向量表跳转到IRQ处理入口IRQ_handle
发生中断的时候,硬件自动跳到IRQ的处理入口IRQ_handle。这个IRQ_handle是某个汇编文件里的标号,是自己随意取的名字。之所以使用汇编语言编写这个文件,是因为要完成栈的设置、现场保存等工作。
2、在IRQ_handle里完成一些设置后,跳转到真正的中断服务程序ISR
在这个汇编文件的IRQ_handle这个标号下,需要完成栈的设置、现场保存、跳转到真正的中断服务程序isr、处理完中断后返回并恢复现场等工作。注意,这个IRQ_handle不是真正的中断服务程序,而是在IRQ_handle中设置好栈、保存好好现场之后,才跳转到真正的中断服务程序isr中。
3、中断服务程序isr完成相关内容
在中断服务程序中,先判断哪个VICnIRQSTATUS不为1,不为1则表示发生了中断,然后直接去这个VICnIRQSTATUS对应的VICnADDRESS寄存器中,取出isr的地址然后执行。
4、 中断现场恢复
中断服务程序isr完成后,返回继续做常规任务。
流程如下图:
二、代码
1、构建异常向量表
/** 异常向量表初始化*/#define VECTOR_TABLE_BASE 0xD0037400 #define Reset_offset 0x0 #define Undef_offset 0x4 #define SVC_offset 0x8 #define Prectch_offset 0xC #define Data_Abort_offset 0x10 #define IRQ_offset 0x18 #define FIQ_offset 0x1C#define _PFUNC_Reset (*(unsigned int*)(VECTOR_TABLE_BASE+Reset_offset)) #define _PFUNC_Undef (*(unsigned int*)(VECTOR_TABLE_BASE+Undef_offset)) #define _PFUNC_SVC (*(unsigned int*)(VECTOR_TABLE_BASE+SVC_offset)) #define _PFUNC_Prectch (*(unsigned int*)(VECTOR_TABLE_BASE+Prectch_offset)) #define _PFUNC_Data_Abort (*(unsigned int*)(VECTOR_TABLE_BASE+Data_Abort_offset)) #define _PFUNC_IRQ (*(unsigned int*)(VECTOR_TABLE_BASE+IRQ_offset)) #define _PFUNC_FIQ (*(unsigned int*)(VECTOR_TABLE_BASE+FIQ_offset)) extern void IRQ_handle(void);//因为我们只处理IRQ和FIQ这两种异常,所以其他异常的处理函数是空的函数体 void Reset_handle(void){} void Undef_handle(void){} void SVC_handle(void){} void Prectch_handle(void){} void Data_Abort_handle(void){}//构建异常向量表 void vector_table_init(void) { _PFUNC_Reset = (unsigned int)Reset_handle;_PFUNC_Undef = (unsigned int)Undef_handle;_PFUNC_SVC = (unsigned int)SVC_handle;_PFUNC_Prectch = (unsigned int)Prectch_handle;_PFUNC_Data_Abort = (unsigned int)Data_Abort_handle;_PFUNC_IRQ = (unsigned int)IRQ_handle;//假定FIQ、IRQ都是采用IRQ中断_PFUNC_FIQ = (unsigned int)IRQ_handle;//因此但凡是中断,都会进入此函数}
其中,IRQ_handle函数要写在汇编IRQ_handle.S中,因为要设置栈以及保存现场 。
IRQ_handle.S文件内容如下:
#define IRQ_STACK 0xD0037F80 .global IRQ_handleIRQ_handle://设置IRQ的栈ldr sp, =IRQ_STACK//由于三级流水线的存在,pc为此时的程序语句+8,保存的时候要把下一句保存到lr中sub lr, lr, #4//保存现场stmd sp! {r0-r12, lr}//跳转到中断处理函数bl isr_handler//恢复现场ldmfd sp! {r0-r8, pc}^//这里为什么不是恢复r0~r12
ARM保存中断时为什么使用 sub lr, lr, #4?
(1)在arm执行过程中一般分为取指,译码,执行阶段;假设当前第一条指令在执行阶段,第二条指令在译码阶段,第三条指令在取指阶段;若当前正在执行的指令地址为pc-8,那第二条就为pc-4,第三条则为pc(它指向取址)。
(2)当发生中断时,pc指向第三条指令,而从中断返回肯定不是执行第三条指令,而是紧接着的第二条指令,所以 lr 应该保存的是 pc - 4。但是当执行到“sub lr,lr,#4”这个位置时,pc值又会发生改变,还好发生中断时会“mov lr,pc” ,所以这里可以直接使用“sub lr,lr,#4”,即 lr=pc-4。
2、中断控制器的初始化
//清除4个中断处理函数的地址 void clean_vicaddress(void) {_REG_VIC0ADDRESS = 0x0;_REG_VIC1ADDRESS = 0x0;_REG_VIC2ADDRESS = 0x0;_REG_VIC3ADDRESS = 0x0;}//初始化中断 void interrupt_init(void) {//第一步初始化中断之前要关闭所有中断_REG_VIC0INTENCLEAR = 0xFFFFFFFF;_REG_VIC1INTENCLEAR = 0xFFFFFFFF;_REG_VIC2INTENCLEAR = 0xFFFFFFFF;_REG_VIC3INTENCLEAR = 0xFFFFFFFF;//第三步:设置中断为IRQ中断_REG_VIC0INTSELECT = 0x0;_REG_VIC1INTSELECT = 0x0;_REG_VIC2INTSELECT = 0x0;_REG_VIC3INTSELECT = 0x0;//第三步:清中断处理函数地址clean_vicaddress();}//禁止某个中断 void int_disable(unsigned int num) {if (num < 32) {_REG_VIC0INTENCLEAR = (0x1<<num);}else if (num < 64) {_REG_VIC1INTENCLEAR = (0x1<<(num-32)); }else if (num < 96) {_REG_VIC2INTENCLEAR = (0x1<<(num-64));}else if (num < 128) {_REG_VIC3INTENCLEAR = (0x1<<(num-96));}else {} }//使能某个中断 void int_enable(unsigned int num) {if (num < 32) {_REG_VIC0INTENABLE = (0x1<<num);}else if (num < 64) {_REG_VIC1INTENABLE = (0x1<<(num-32)); }else if (num < 96) {_REG_VIC2INTENABLE = (0x1<<(num-64));}else if (num < 128) {_REG_VIC3INTENABLE = (0x1<<(num-96));}else {_REG_VIC0INTENABLE = 0xFFFFFFFF;_REG_VIC1INTENABLE = 0xFFFFFFFF;_REG_VIC2INTENABLE = 0xFFFFFFFF;_REG_VIC3INTENABLE = 0xFFFFFFFF;}} //先由中断编号得到对应的VICnVECTADDRm 寄存器 //然后将中断服务函数的地址放入对应的 VICnVECTADDRm 寄存器中 void creat_israddr(unsigned int num, void (*PIRQ_handler)(void)) {if (num < 32) { //*( (void (*)(void))(VIC0VECTADDR + 4*num) )= PIRQ_handler;*( (volatile unsigned long *)(VIC0VECTADDR + 4*(num-0)) ) = (unsigned)PIRQ_handler;}else if (num < 64) {//(void (*)(void))(VIC1VECTADDR + 4*(num-32))= PIRQ_handler;*( (volatile unsigned long *)(VIC1VECTADDR + 4*(num-32)) ) = (unsigned)PIRQ_handler;}else if (num < 96) {//(void (*)(void))(VIC2VECTADDR + 4*(num-64))= PIRQ_handler;*( (volatile unsigned long *)(VIC2VECTADDR + 4*(num-64)) ) = (unsigned)PIRQ_handler;}else {//(void (*)(void))(VIC3VECTADDR + 4*(num-96))= PIRQ_handler;*( (volatile unsigned long *)(VIC3VECTADDR + 4*(num-96)) ) = (unsigned)PIRQ_handler;} }//判断中断在哪个address中 static int check_int_addr(void) {if (_REG_VIC0IRQSTATUS) {return 0;}else if (_REG_VIC1IRQSTATUS) {return 1;}else if (_REG_VIC2IRQSTATUS) {return 2;}else if (_REG_VIC3IRQSTATUS) {return 3;}else {return -1;} } // void isr_handler(void) {void (*p_isr)(void) = NULL;int i;i = check_int_addr();switch (i) {case 0 :p_isr = (void (*)(void))_REG_VIC0ADDRESS;break;case 1 :p_isr = (void (*)(void))_REG_VIC1ADDRESS;break;case 2 :p_isr = (void (*)(void))_REG_VIC2ADDRESS;break;case 3 :p_isr = (void (*)(void))_REG_VIC2ADDRESS;break;default :break;}p_isr();}
3、中断处理函数
//中断处理函数 void int_led_blink(void) {led_blink();//清楚外部中断挂起,注意写1清挂起clean_int_pend();//清vicaddressclean_vicaddress(); }
4、将中断号与中断服务程序isr绑定
#include "interrupt.h" #include "stdio.h" extern void led_blink(void); extern void led1_on(void); extern void vector_table_init(void); extern void key_init(void); extern void uart_init(void);int main(void) {//按键初始化key_inter_init();//异常向量表初始化vector_table_init();//中断初始化interrupt_init();//将中断号与中断服务程序isr绑定creat_israddr(NUM_EINT2, int_led_blink);creat_israddr(NUM_EINT3, int_led_blink);creat_israddr(NUM_EINT16_31, int_led_blink);//使能中断int_enable(NUM_EINT2);int_enable(NUM_EINT3);int_enable(NUM_EINT16_31);while (1) {printf("a");}}