分析2440test中的中断处理部分
- 格式:doc
- 大小:38.50 KB
- 文档页数:6
这个 2440test里面的中断写的向量有些隐蔽,兜了很多个圈,也难怪这么难理解,下面就对这个东西抽丝剥茧,看清楚这究竟是一个怎么样的过程。
中断向量b HandlerIRQ ;handler for IRQ interrupt很自然,因为所有的单片机都是那样,中断向量一般放在开头,用过单片机的人都会很熟悉那就不多说了。
异常服务程序这里不用中断(interrupt)而用异常(exception),毕竟中断只是异常的一种情况,呵呵下面主要分析的是“中断异常”说白了,就是我们平时单片机里面用的中断!!!所有有器件引起的中断,例如TIMER中断,UART中断,外部中断等等,都有一个统一的入口,那就是中断异常 IRQ ! 然后从IRQ的服务函数里面分辨出,当前究竟是什么中断,再跳转到相应的中断服务程序。
这样看来,ARM比单片机要复杂一些了,不过原理是不变的。
上面说的就是思路,跟着这个思路来接着分析。
HandlerIRQ 很明显是一个标号,我们找到了HandlerIRQ HANDLER HandleIRQ这里是一个宏定义,我们再找到这个宏,看他是怎么定义的:MACRO$HandlerLabel HANDLER $HandleLabel$HandlerLabelsub sp,sp,#4 ;decrement sp(to store jump address)stmfd sp!,{r0} ;PUSH the work register to stack(lr does not push because it return to origin al address)ldr r0,=$HandleLabel ;load the address of HandleXXX to r0ldr r0,[r0] ;load the contents(service routine start address) of HandleXXXstr r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stackldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR)MEND用 HandlerIRQ 将这个宏展开之后得到的结果实际是这样的HandlerIRQsub sp,sp,#4 ;decrement sp(to store jump address)stmfd sp!,{r0} ;PUSH the work register to stack(lr does not push because it return to origin aladdress)ldr r0,=HandleIRQ ;load the address of HandleXXX to r0ldr r0,[r0] ;load the contents(service routine start address) of HandleXXXstr r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stackldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR)至于具体的跳转原理下面再说好了,这样的话就容易看的多了,很明显, HandlerIRQ 还是一个标号,IRQ异常向量就是跳转到这里执行的,这里粗略看一下,应该是保存现场,然后跳转到真正的处理函数,那么很容易发现了这么一句 ldr r0,=HandleIRQ ,没错,我们又找到了一个标号 HandleIRQ ,看来真正的处理函数应该是这个 HandleIRQ ,继续寻找AREA RamData, DA TA, READWRITE^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00HandleReset # 4HandleUndef # 4HandleSWI # 4HandlePabort # 4HandleDabort # 4HandleReserved # 4HandleIRQ # 4最后我们发现在这里找到了 HandleIRQ ,^ 其实就是 MAP ,这段程序的意思是,从 _ISR_STARTADDRESS开始,预留一个变量,每个变量一个标号,预留的空间为 4个字节,也就是 32BIT,其实这里放的是真正的C写的处理函数的地址,说白了,就是函数指针 - -这样做的话就很灵活了接着,我们需要安装IRQ处理句柄,说白了,就是设置处理函数的地址,让PC指针可以正确的跳转。
MDK下S3C2440裸机中断,断断续续将耗费了我将近了一个月的时间,在贵师大图书馆里几乎翻遍了有关图书,有关这方面的介绍寥寥而过,想必出书的人也未必弄得清楚。
网上的资料也五花八门,有些说MDK自带的2440的启动代码有误(确实跟2410的启动代码不同,但经过这段时间的验证,启动代码绝对没有任何问题);有些说必须内存映射到正确的地址,才可以进入相应异常(从NORFLASH启动绝对可以进入IRQ中断);大部分在书写中断服务程序时,__irq关键字都放在函数名的前面(ADS编译器__irq关键之放置在函数名前面,但MDK必须将__irq关键字放在函数最末端);有些说必须修改2440启动代码才能进入中断(没有对启动代码做任何修改,实现了IRQ中断)……在这些一大堆乱七八糟的资料中,我差点都放弃了ARM的学习。
为了实现IRQ中断而很吃力地学习了ARM汇编,试着修改2440的启动代码;学习了内存管理,试着映射异常向量的地址;如今回过头去看,能挺过来挺欣慰的。
为实现简单IRQ中断其实很简单的,真是踏破铁鞋无觅处,得来全不费功夫。
以下是关键的几点程序说明,希望对后面学习的朋友,有些帮助。
void irq_init() //irq初始化,这个函数其实比较简单,适当看看2440PDF就应该能设定寄存器了{GPFCON = 0xaaaa;SRCPND = 0x17;INTMSK &= ~(0x17);INTPND = 0x17;EINTPEND =(1<<4);__asm{nop}EINTMASK &=~(1<<4);__asm{nop}EXTINT0 = 0x0;}//这是个很关键的函数,但其实也简单中断服务函数照着这个形式写就应该没能进入IRQ中断了。
void IRQ_Handler(void) __irq //irq中断函数{int j=0;__asm{nop};__asm{nop};j= INTOFFSET;SRCPND = SRCPND | 0x17;INTPND = INTPND | 0x17;switch(j){case 0:irq_ent0();//为中断0break;case 1:irq_ent1();//外中断1break;case 2:irq_ent2();//外中断2break;case 4:irq_ent4();//外中断4irq_init();break;}}以上两个函数式实现IQR中断的关键函数,能弄清楚上面的两个函数,IRQ中断应该就没什么大问题了。
S3C2440之中断操作(MDK4.22)
背景知识:2440 中断控制器接收60 个中断源。
中断被分为多种类别,此处为32 类别,正好用32 位。
图上可以很好的表示整个中断的流程。
有些中断源对应一个中断类别,比如
串口中断发送,接收最后都对应到串口中断。
submask 屏蔽子类别,未屏蔽的会引起srcpnd 相应位置位,如果mask 未屏蔽的话,就会紧接着判断优先级,最后导致intpnd 置位,然后产生了IRQ,而FIQ 不需要优先级判断,会直接产生,由intmod 设置。
注意的是:进入ISR 后,清除中断的顺序很重要,首先是srcpnd 接着是intpnd,如果还需要清除eintpnd 的话,要最先清除。
实验过程:
编译工具--MDK4.22
硬件图
首先需要配置GPF0/1/2/4 的脚为EINT0/1/2/4。
由于用到了外部4-7 中断,需要开启EINTMASK 寄存器相应位。
设置优先
级寄存器,设置INTMSK 寄存器。
在2440A.s 中已经设置了I F 位开启,可以接收中断了。
在中断初始化代码里,需要将中断函数的地址安装到中断向量表中。
程序如下:
MDK4.22 的启动代码设置
配置了堆,栈,以及中断向量表的地址为0x33ffff20 处。
配置了B 口和F 口,B 口试LED 的灯显示,F 口是连接按键的外部中断。
按一个键会亮一个LED。
int.c 代码。
按键与LED灯,四个中断控制四个灯LED引脚连接按键引脚连接://===================================================================== =// 开发板:GT2440// 工程名称:KEY_EINT// 功能描述:外部中断0 2 11 19 控制四个LED小灯//===================================================================== =/******************************************************************************4 个用户按键四个输入引脚:EINT0 -----( GPF0 )----INPUT---K1EINT2 -----( GPF2 )----INPUT---K2EINT11 -----( GPG3 )----INPUT---K3EINT19 -----( GPG11 )----INPUT---K4******************************************************************************/ #include "def.h"#include "2440addr.h"#include "2440lib.h"#include "string.h"#include "uart.h"#define LED1ON 0xFDE //LED1点亮值为0x7DE(低电平点亮)#define LED2ON 0xFBE#define LED3ON 0xF7E#define LED4ON 0xEFE#define LEDOFF 0xFFF //LED熄灭值为0xFFFvoid __irq EintHandler0(void);void __irq EintHandler2(void);void __irq EintHandler8_23(void);void Main(void){memcpy((unsigned char *)0x0,(unsigned char *)0x30000000,0x1000);SetSysFclk(FCLK_400M); //设置系统时钟400MChangeClockDivider(2, 1); //设置分频1:4:8CalcBusClk(); //计算总线频Uart_Select(0);Uart_Init(0,115200);Uart_Printf("KEY EINT TEST\n");Uart_Printf("Use Eint 0,2,11,19 falling edge\n");rGPBCON = (rGPBCON | 0xFFFFFFF) & 0xFFFd57FC; //GPB5--GPB8设置为output rGPBUP = rGPBUP & 0xFE1F; //使能GPB5 6 7 8上拉电阻rGPBDAT = 0x7FE; //GPB5 6 7 8位初始化为1/***********************************************************//GPF0 GPF2设置为EINT[0],EINT[2] 10 = EINT[2] 10 = EINT[0]//设置GPGCON的3,11为中断功能,分别对应EINT[11] EINT[19]*************************************************************/rGPFCON = (rGPFCON|0xFFFF)&0xFFFFFFEE;rGPGCON = (rGPGCON|0xFFFFFFFF)&0xFFBFFFBF;/*************************************************************** START:外部中断控制寄存器3位一个设置触发沿, 设置外部中断0 ,2下降沿触发外部中断控制寄存器3位设置一个触发沿, 设置外部中断11下降沿触发外部中断控制寄存器3位设置一个触发沿, 设置外部中断19下降沿触发01x = 下降沿触发*****************************************************************/ rEXTINT0 &= ~(7<<0 | 7<<8 );rEXTINT0 |= (2<<0 | 2<<8) ;rEXTINT1 &= ~(7<<12 );rEXTINT1 |= (2<<12) ;rEXTINT2 &= ~(7<<12 );rEXTINT2 |= (2<<12) ;/*************************************************************** END*****************************************************************//********************************************************//外部中断0~3保留为0 不用使能EINTPEND和EINTMASK rEINTPEND |= (1<<0|1<<2); //clear eint 4 rEINTMASK &= ~(1<<0|1<<2); //enable eint************************************************************//********************************************************//外部中断11,19需要对EINTPEND(外部中断挂起寄存器)清零并对EINTMASK(外部中断屏蔽寄存器)清零使能EINTPEND写1清零使能中断,EINTMASK清零使能中断************************************************************/ rEINTPEND |= (1<<11|1<<19);rEINTMASK &= ~(1<<11|1<<19);/***********************************************************清源挂起(SRCPND)寄存器和中断挂起(INTPND)寄存器,可以直接操作寄存器这两个函数在2440addr.h中定义*************************************************************/ ClearPending(BIT_EINT0);ClearPending(BIT_EINT2);ClearPending(BIT_EINT8_23);/***********************************************************pISR_EINT0,2中断的入口地址,定义在2440addr.inc中2440addr.inc文件内定义了用于汇编的s3c2440寄存器变量和地址*************************************************************/ pISR_EINT0 =(unsigned)EintHandler0;pISR_EINT2 =(unsigned)EintHandler2;pISR_EINT8_23 =(unsigned)EintHandler8_23;/*********************************************************** 使能或禁止中断,这是个宏定义,其原型为rINTMSK &= ~(bit)这里的操作就是取消了屏蔽,中断就开始了*************************************************************/ EnableIrq(BIT_EINT0);EnableIrq(BIT_EINT2);EnableIrq(BIT_EINT8_23);while(1);}//外部中断0,服务函数void __irq EintHandler0(void){if(rINTPND==BIT_EINT0){ClearPending(BIT_EINT0);Uart_Printf("KEY To LED1 ON \n");rGPBDAT = LED1ON;Delay(500);rGPBDAT = LEDOFF;}}//外部中断2,服务函数void __irq EintHandler2(void){if(rINTPND==BIT_EINT2){ClearPending(BIT_EINT2);Uart_Printf("KEY To LED2 ON \n");rGPBDAT = LED2ON;Delay(500);rGPBDAT = LEDOFF;}}//外部中断11,19服务函数void __irq EintHandler8_23(void){if(rINTPND==BIT_EINT8_23){ClearPending(BIT_EINT8_23);if(rEINTPEND&(1<<11)){Uart_Printf("KEY To LED3 ON \n");rGPBDAT = LED3ON;Delay(500);rGPBDAT = LEDOFF;rEINTPEND |= 1<< 11;//写1清零该位}if(rEINTPEND&(1<<19)){Uart_Printf("KEY To LED4 ON \n");rGPBDAT = LED4ON;Delay(500);rGPBDAT = LEDOFF;rEINTPEND |= 1<< 19;//写1清零该位}}}。
#include <windows.h>//#include <ceddk.h>#include <nkintr.h>//#include <oalintr.h>#include <pm.h>#include "pmplatform.h"#include "Pkfuncs.h"#include "s2440.h"volatile IOPreg *s2440IOP = (IOPreg *)IOP_BASE;//定义s2440IOP为基地址,其中IOPreg 是s2440.h一个结构体volatile INTreg *s2440INT = (INTreg *)INT_BASE;//定义中断基地址UINT32 g_KeySysIntr[6];HANDLE KeyThread;HANDLE KeyEvent;HANDLE APIEvent;BYTE KeyValues[6] = { 0, 0, 0, 0, 0, 0};//按键值void Virtual_Alloc(); // Virtual allocation‘虚拟内存分配函数DWORD UserKeyProcessThread(void);//申明用户按键线程const TCHAR szevtUserInput[] = L"FriendlyARM/ButtonEvent";DWORD UserKeyProcessThread(void)//用户按键线程实现{UINT32 IRQ;//无符号32位整型变量,中断变量/*HANDLE CreateEvent(LPSECURITY_A TTRIBUTES lpEventAttributes, // SDBOOL bManualReset, // reset typeBOOL bInitialState, // initial stateLPCTSTR lpName // object name);该函数创建一个Event同步对象,并返回该对象的HandlelpEventAttributes 一般为NULLbManualReset 创建的Event是自动复位还是人工复位,如果true,人工复位,一旦该Event被设置为有信号,则它一直会等到ResetEvent()API被调用时才会恢复为无信号. 如果为false,Event被设置为有信号,则当有一个wait到它的Thread时, 该Event就会自动复位,变成无信号.bInitialState 初始状态,true,有信号,false无信号lpName Event对象名一个Event被创建以后,可以用OpenEvent()API来获得它的Handle,用CloseHandle() 来关闭它,用SetEvent()或PulseEvent()来设置它使其有信号,用ResetEvent()来使其无信号,用WaitForSingleObject()或WaitForMultipleObjects()来等待其变为有信号.PulseEvent()是一个比较有意思的使用方法,正如这个API的名字,它使一个Event对象的状态发生一次脉冲变化,从无信号变成有信号再变成无信号,而整个操作是原子的.对自动复位的Event对象,它仅释放第一个等到该事件的thread(如果有),而对于人工复位的Event对象,它释放所有等待的thread. *///该函数创建一个Event同步对象,并返回该对象的HandleHANDLE UserInputEvent = CreateEvent(NULL, FALSE, FALSE, szevtUserInput);//自动复位,初始状态无信号,对象的名称,成功则返回对象的句柄/*使用KernelIoControl函数将物理中断和逻辑中断联系起来该函数为内核提供执行IO操作的通用IO控制,返回TRUE表示成功,FALSE表示失败BOOL KernelIoControl(DWORD dwIoControlCode,//IO控制代码,支持OAL级的IO控制LPVOID lpInBuf,// 指向一个缓冲区,其包含执行操作所必须的数据。
注:1本文档为个人学习总结,不保证完全正确。
2本文档根据TQ2440测试程序,进过修改,在MICRO2440上运行正常。
正文:在2440中断程序学习过程中,可谓一波三折,由于以前从来没有系统学习过C语言,并且第一次接触外扩FLASH以及SDRAM的系统,中断处理移至是困扰我的问题,由于不能处理中断,好多程序无法编写,也可能是因为本人基础薄弱,尝试了很多次都以失败告终。
偶有一天在HACKCHINA网掌上看到了TQ2440的测试程序,浏览一遍,与MICRO2440的基本一致,怀疑有亲缘关系,由于此测试程序是在MDK下生成的,而我以前也一直在用MDK,正好不用再忍受ADS的丑陋与崩溃,兴致又起,这次终于把中断看明白了。
哈哈,下面是总结,以便以后遗忘TQ2440的测试程序貌似是从ADS平台上一直到MDK平台的,包括2440init.s也与ADS的一致。
那道源代码后第一反应就是看看结构,哇塞,与MICRO2440的基本一致,大部分函数名也一样,直接在开发板上测试,可以运行但是现实与LED定义不一样,按照友善的手册修改后,上电测试,完全OK,只是功能略有不同,不过中断程序没有问题,包括使用JLINK 调试。
确认程序正确后,开始分析代码。
最重要的文件就是初始化文件:2440INIT.S。
文件中与中断有关的第一部分代码如下:;注意下面这段程序是个宏定义,下面包含的HandlerXXX HANDLER HandleXXX将都被下面这段程序展开;这段程序用于把中断服务程序的首地址装载到pc中;本初始化程序定义了一个数据区(在文件最后),34个字空间,存放相应中断服务程序的首地址。
;每个字空间都有一个标号,以Handle***命名。
MACRO$HandlerLabel HANDLER $HandleLabel$HandlerLabelsub sp,sp,#4 ;decrement sp(to store jump address)stmfd sp!,{r0} ;PUSH the work register to stack(lr does not push because it return to original address)ldr r0,=$HandleLabel ;load the address of HandleXXX to r0ldr r0,[r0] ;load the contents(service routine start address) of HandleXXX str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stackldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR)MEND原文件的注释写得很清楚,该处定义可一个宏,该宏的作用就是从特定地址拿到中断程序的入口地址,然后送到PC进行跳转,其中完成必要的堆栈操作。
ARM S3C2440中断分析1.什么是中断所谓中断,是指CPU在正常运行程序时,由于内部/外部事件或由程序预先安排的事件,引起CPU中断正在运行的程序,而转到为内部/外部事件或为预先安排的事件服务的中断程序中去,服务完毕,再返回去执行刚才被中断的程序。
2.什么是中断优先级中断优先级是指,中断源被响应和处理的优先等级。
设置优先级的目的是为了在有多个中断源同时发出中断请求时,CPU能够按照预定的顺序(如:按事件的轻重缓急处理)进行响应并处理。
3.什么是中断嵌套中断嵌套是指当CPU正在处理某个中断源即正在执行中断服务程序时,会出现优先级更高的中断源申请中断,为了使更急的中断源及时得到服务,需要暂时中断(挂起)当前正在执行的级别较低的中断服务程序,去处理更高级别的中断源,待执行完毕后再返回来执行低优先级的中断服务程序。
但中断级别低的中断源不能中断级别高的中断服务,这就是中断嵌套,并且称这种中断嵌套方式为完全嵌套方式。
4.什么是中断向量中断向量是中断服务程序的入口地址,中断向量一般是固定的,我们需要把我们写好的中断服务程序(ISR)的入口地址写道中断向量表中,这样在发生中断时,CPU就会自动跳转到中断向量表中找到它要执行的中断服务程序了。
5.什么是硬中断,什么是软中断硬中断是由外部事件引起的因此具有随机性和突发性;软中断是执行中断指令产生的,无面外部施加中断请求信号,因此中断的发生不是随机的而是由程序安排好的。
S3C2440/S3C2410中断体系结构:6.ARM体系 CPU的7种工作模式:·用户模式(usr): ARM处理器正常的程序执行状态·快速中断模式(fiq): 用于高速数据传输或通道处理·中断模式(irq):用于通用的中断处理·管理模式(svc):操作系统使用的保护模式·数据访问终止模式(abt): 当数据或指令预取时进入该模式,可用于虚拟存储及存储保护·系统模式(sys):运行具有特权操作系统任务·未定义指令中止模式(und): 当为定义的指令执行时进入该模式,可用于支持硬件协处理的软件仿真可以通过软件来进行模式切换,或者发生各类中断,异常时CPU自动进入相应的模式。
这个 2440test里面的中断写的向量有些隐蔽,兜了很多个圈,也难怪这么难理解,下面就对这个东西抽丝剥茧,看清楚这究竟是一个怎么样的过程。
中断向量b HandlerIRQ ;handler for IRQ interrupt很自然,因为所有的单片机都是那样,中断向量一般放在开头,用过单片机的人都会很熟悉那就不多说了。
异常服务程序这里不用中断(interrupt)而用异常(exception),毕竟中断只是异常的一种情况,呵呵下面主要分析的是“中断异常”说白了,就是我们平时单片机里面用的中断!!!所有有器件引起的中断,例如TIMER中断,UART中断,外部中断等等,都有一个统一的入口,那就是中断异常 IRQ ! 然后从IRQ的服务函数里面分辨出,当前究竟是什么中断,再跳转到相应的中断服务程序。
这样看来,ARM比单片机要复杂一些了,不过原理是不变的。
上面说的就是思路,跟着这个思路来接着分析。
HandlerIRQ 很明显是一个标号,我们找到了HandlerIRQ HANDLER HandleIRQ这里是一个宏定义,我们再找到这个宏,看他是怎么定义的:MACRO$HandlerLabel HANDLER $HandleLabel$HandlerLabelsub sp,sp,#4 ;decrement sp(to store jump address)stmfd sp!,{r0} ;PUSH the work register to stack(lr does not push because it return to originaladdress)ldr r0,=$HandleLabel ;load the address of HandleXXX to r0ldr r0,[r0] ;load the contents(service routine start address) of HandleXXXstr r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stackldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR)MEND用 HandlerIRQ 将这个宏展开之后得到的结果实际是这样的HandlerIRQsub sp,sp,#4 ;decrement sp(to store jump address)stmfd sp!,{r0} ;PUSH the work register to stack(lr does not push because it return to originaladdress)ldr r0,=HandleIRQ ;load the address of HandleXXX to r0ldr r0,[r0] ;load the contents(service routine start address) of HandleXXXstr r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stackldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR)至于具体的跳转原理下面再说好了,这样的话就容易看的多了,很明显, HandlerIRQ 还是一个标号,IRQ异常向量就是跳转到这里执行的,这里粗略看一下,应该是保存现场,然后跳转到真正的处理函数,那么很容易发现了这么一句 ldr r0,=HandleIRQ ,没错,我们又找到了一个标号 HandleIRQ ,看来真正的处理函数应该是这个 HandleIRQ ,继续寻找AREA RamData, DATA, READWRITE^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00HandleReset # 4HandleUndef # 4HandleSWI # 4HandlePabort # 4HandleDabort # 4HandleReserved # 4HandleIRQ # 4最后我们发现在这里找到了 HandleIRQ ,^ 其实就是 MAP ,这段程序的意思是,从 _ISR_STARTADDRESS 开始,预留一个变量,每个变量一个标号,预留的空间为 4个字节,也就是 32BIT,其实这里放的是真正的C写的处理函数的地址,说白了,就是函数指针 - -这样做的话就很灵活了接着,我们需要安装IRQ处理句柄,说白了,就是设置处理函数的地址,让PC指针可以正确的跳转。
于是我们在接着的找到安装句柄的语句; Setup IRQ handlerldr r0,=HandleIRQ ;This routine is neededldr r1,=IsrIRQ ;if there is not 'subs pc,lr,#4' at 0x18, 0x1cstr r1,[r0]说白了就是将 IsrIRQ 的地址填到 HandleIRQ对应的地址里面,前面说了 HandleIRQ 放的是中断处理的函数的入口地址,我们继续找 IsrIRQIsrIRQsub sp,sp,#4 ;reserved for PCstmfd sp!,{r8-r9}ldr r9,=INTOFFSETldr r9,[r9]ldr r8,=HandleEINT0add r8,r8,r9,lsl #2ldr r8,[r8]str r8,[sp,#8]ldmfd sp!,{r8-r9,pc}要理解这个代码,得先学学2440的中断系统了,INTOFFSET存放的是当前中断的偏移号,根据偏移就知道当前是哪个中断源发生的中断。
注意了,我们说的是中断,而不是异常,看看原来的表是啥样子的^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00HandleReset # 4HandleUndef # 4HandleSWI # 4HandlePabort # 4HandleDabort # 4HandleReserved # 4HandleIRQ # 4HandleFIQ # 4HandleEINT0 # 4HandleEINT1 # 4HandleEINT2 # 4HandleEINT3 # 4.......可以看到,前面几个是异常,从 HandleEINT0 就是 IRQ异常的向量存放的地方了,这样就可以理解为什么上面 IsrIRQ 里面里面要执行那条指令ldr r8,=HandleEINT0add r8,r8,r9,lsl #2道理很简单, HandleEINT0 就是所有IRQ中断向量表的入口,在这个地址上面,加上一个适当的偏移量,INTOFFSET ,那么我们知道现在,到底是哪个IRQ在申请中断了。
至于具体怎么跳转的?首先,我们说了,HandleEINT0 开始的一段内存里面,存放的就是中断服务函数的函数指针,ARM的体系的话,每个指针变量就是占4个字节,这里就解释了,为什么这里为每个标号分配了4个字节的空间,里面放的就是函数指针!!!下面再看看怎么跳转,继续看 IsrIRQ 里面就实现了跳转了str r8,[sp,#8]ldmfd sp!,{r8-r9,pc}其实最核心就是这两句了,先查找到当前中断服务程序的地址,将他放到 R8 里面,然后出栈,弹出给PC 那么PC很自然就跳到中断服务程序了。
至于这里的堆栈问题又是一个非常棘手的,需要好好的参透ARM的中断架构,需要了解的可以自己仔细的阅读《ARM体系结构与编程》里面说的很详细。
我们这里的重点是研究怎么跳转。
最后,我们看看在C代码中是怎么安装终端向量的,例如看按键的外部中断,是怎么具体设置的,参看/src/keyscan.c 里面的代码很简单,里面只有3个函数KeyScan_Test 是按键测试的主函数Key_ISR 是按键中断服务函数在 KeyScan_Test里面,我们发现了有这么一句pISR_EINT0 = pISR_EINT2 = pISR_EINT8_23 = (U32)Key_ISR;可以理解否? Key_ISR就是上面提到的按键中断服务函数,函数的名字,代表的就是函数的地址!!!!将中断服务函数的地址,注意了,是地址,这是一个 U32型的变量。
送到几个变量,我们以pISR_EINT0作为例子,查看头文件定义,在 2440addr.h 里面找到// Interrupt vector#define pISR_EINT0 (*(unsigned *)(_ISR_STARTADDRESS+0x20))_ISR_STARTADDRESS有没有似曾相识的感觉?没错,刚才分析的汇编代码里面就提到了^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00HandleReset # 4HandleUndef # 4......对,地址就是这里,然后 _ISR_STARTADDRESS+0x20 就是跳过前面的异常向量,进入IRQ中断向量的入口所以说到尾pISR_EINT0 = (U32)Key_ISR;完成的操作就是,将 Key_ISR 的地址存放到HandleEINT0 # 4这个IRQ向量表里面!!!!当按键中断发生的时候,发生IRQ异常中断当前PC值-4 保存到LR_IRQ里面,然后执行b HandlerIRQ然后是执行HandlerIRQsub sp,sp,#4 ; 预留一个用来存放PC地址stmfd sp!,{r0} ; 保存R0,因为下面使用了ldr r0,=HandleIRQ ; 将HandleIRQ(服务程序)的地址装载到R0ldr r0,[r0]str r0,[sp,#4] ; 保存到刚才预留的地方ldmfd sp!,{r0,pc} ; 弹出堆栈,恢复R0,并且将刚才计算好的 HandleIRQ 地址弹出到 PC堆栈是向下生长的,所以 SUB SP,SP,#4 就相当于 PUSH XX,但是这个XX这个时候并没有用,因为这里用的是强制移动 SP 指针实现的。
然后得到服务程序的地址,再将这个值放回刚才预留的栈的空位上面,最后就是POP出R0恢复,并且将刚才得到的服务程序的地址送到 PC,那么实现的效果就是跳转到 HandleIRQ 里面了。
接着看刚才是怎么安装的HandleIRQ 的; Setup IRQ handlerldr r0,=HandleIRQ ;This routine is neededldr r1,=IsrIRQ ;if there is not 'subs pc,lr,#4' at 0x18, 0x1cstr r1,[r0]可以看出,这里将 IsrIRQ 的地址的值保存到 HandleIRQ 中,也就是说,上面的 IRQ 服务程序,这个时候实际上就是指 IsrIRQ !所以接着的事情就是转移到 IsrIRQ 中执行:IsrIRQsub sp,sp,#4 ; 预留一个值来保存PCstmfd sp!,{r8-r9}ldr r9,=INTOFFSET ; 计算偏移量,下面解释ldr r9,[r9]ldr r8,=HandleEINT0add r8,r8,r9,lsl #2ldr r8,[r8]str r8,[sp,#8] ; 因为保存了2个寄存器R8 R9 ,所以SP下移了8位ldmfd sp!,{r8-r9,pc} ; 恢复寄存器,弹出到PC,同上面的一样怎么保存,操作SP,跟最后弹出到PC的部分和上面的例子一样,下面说说中间的计算部分计算偏移量,其实原理很简单,首先 INTOFFSET 保存着当前是哪个IRQ中断,例如 0代表着 HandleEINT0,1代表HandleEINT1 ..... 等等,这不是乱来,有一个表的,这个是由 S3C2440 的datasheet说的,自己可以去查看。