【.Net MF深入研究】中断处理机制

.Net Micro Framework的中断处理机制相对比较简单,不支持中断嵌套,中断优先级功能的实现由相关硬件提供支持,软件层面仅仅进行相关优先级的设定即可。

下面以TI DM335开发板为例简单介绍一下相关技术细节(这里仅介绍普通中断IRQ)。

1 中断向量表
我们知道中断向量表一般默认的存放在内存0起始地址处。

先让我们在NativeSample或TinyCLR目录中找到Scatterfile_tools_xxx.xml文件,我们会发现在其中都会有如下这么一段文字:

 

 
 
  1. <ExecRegion Name="ER_RAM_RO" Base="0x00000000" Options="ABSOLUTE" Size="0x1A000"> 
  2.  
  3.     <FileMapping Name="VectorsTrampolines.obj" Options="(+RO, +FIRST)" />   
  4.  
  5. </ExecRegion> 

这是一个编译选项,告诉ARM编译器,连接时把VectorsTrampolines.obj的相关内容放在位置0处(我们可以用WinHex查看编译后的Bin文件,我们会发现Bin文件中最开始的内容就是中断向量表的内容)。

下面是VectorsTrampolines.s文件的汇编代码:

 
 
  1. ;**************************************************************************  
  2.  
  3.     IMPORT  UNDEF_SubHandler  
  4.  
  5.     IMPORT  ABORTP_SubHandler  
  6.  
  7.     IMPORT  ABORTD_SubHandler    
  8.  
  9.     IMPORT  IRQ_Handler      ; from stubs.cpp   
  10.  
  11.     EXPORT  ARM_Vectors  
  12.  
  13. ;**************************************************************************  
  14.  
  15.     AREA |.text|, CODE, READONLY  
  16.  
  17.     ; ARM directive is only valid for ARM/THUMB processor, but not CORTEX   
  18.  
  19.     IF :DEF:COMPILE_ARM :LOR: :DEF:COMPILE_THUMB  
  20.  
  21.     ARM  
  22.  
  23.     ENDIF      
  24.  
  25. ARM_Vectors  
  26.  
  27.     ; RESET  
  28.  
  29. RESET_VECTOR  
  30.  
  31.     b       UNDEF_VECTOR  
  32.  
  33.     ; UNDEF INSTR  
  34.  
  35. UNDEF_VECTOR  
  36.  
  37.     ldr     pc, UNDEF_SubHandler_Trampoline  
  38.  
  39.     ; SWI  
  40.  
  41. SWI_VECTOR  
  42.  
  43.     DCD     0xbaadf00d  
  44.  
  45.     ; PREFETCH ABORT  
  46.  
  47. PREFETCH_VECTOR  
  48.  
  49.     ldr     pc, ABORTP_SubHandler_Trampoline  
  50.  
  51.     ; DATA ABORT  
  52.  
  53. DATA_VECTOR  
  54.  
  55.     ldr     pc, ABORTD_SubHandler_Trampoline  
  56.  
  57.     ; unused  
  58.  
  59. USED_VECTOR  
  60.  
  61.     DCD     0xbaadf00d  
  62.  
  63.     ; IRQ  
  64.  
  65. IRQ_VECTOR  
  66.  
  67.     ldr     pc, IRQ_SubHandler_Trampoline  
  68.  
  69.     ; FIQ  
  70.  
  71.     ; we place the FIQ handler where it was designed to go, immediately at the end of the vector table  
  72.  
  73.     ; this saves an additional 3+ clock cycle branch to the handler  
  74.  
  75. FIQ_Handler  
  76.  
  77.     IF :DEF: FIQ_SAMPLING_PROFILER      
  78.  
  79.     ldr     pc,FIQ_SubHandler_Trampoline          
  80.  
  81. FIQ_SubHandler_Trampoline      
  82.  
  83.     DCD     FIQ_SubHandler  
  84.  
  85.     ENDIF      
  86.  
  87. UNDEF_SubHandler_Trampoline  
  88.  
  89.     DCD     UNDEF_SubHandler  
  90.  
  91. ABORTP_SubHandler_Trampoline  
  92.  
  93.     DCD     ABORTP_SubHandler  
  94.  
  95. ABORTD_SubHandler_Trampoline  
  96.  
  97.     DCD     ABORTD_SubHandler  
  98.  
  99. ; route the normal interupt handler to the proper lowest level driver  
  100.  
  101. IRQ_SubHandler_Trampoline  
  102.  
  103.     DCD       IRQ_Handler  
  104.  
  105. ;**************************************************************************  
  106.  

从中我们可以看出,当硬件触发IRQ中断时,跳转的最终位置为IRQ_Handler。

 

【疑问】中断向量表的内容最开始的时候一定是存放在Flash(或其它存储介质)中,它是如何被转移到内存0处的呢(当然调试时也可以通过调试器把中断向量表直接Download到内存中去)?

【解答】在FirstEntry.s汇编代码中有这么一句代码 “bl  BootstrapCode”,BootstrapCode是一个子函数,该函数优先于“b   BootEntry”执行,在BootstrapCode函数中又调用了一个子函数PrepareImageRegions,在PrepareImageRegions函数中我们就可以看到拷贝中断向量表的相关代码:

 

 
 
  1. UINT32* src = (UINT32*)&LOAD_RAM_RO_BASE;   
  2.  
  3. UINT32* dst = (UINT32*)&IMAGE_RAM_RO_BASE;  
  4.  
  5. UINT32  len = (UINT32 )&IMAGE_RAM_RO_LENGTH + (UINT32)&Image$$ER_RAM_RW$$Length;   
  6.  
  7. Prepare_Copy( src, dst, len );  

 

注:FIQ_SAMPLING_PROFILER选项是没有定义的,所以在DM335开发板上支持FIQ是有问题的。

2 中断函数(IRQ_Handler)
中断向量表一经设置,则触发相关中断时,CPU会把PC指针指到相关的入口地址处,对IRQ中断来说,其入口地址就是IRQ_Handler。

对DM335平台,IRQ_Handler函数的内容如下:

 
 
  1. void __irq IRQ_Handler(void)  
  2.  
  3. {  
  4.  
  5.     DM335_INTC_Driver::IRQ_Handler();  
  6.  
  7. }  
  8.  
  9. void /*__irq*/DM335_INTC_Driver::IRQ_Handler(void)  
  10.  
  11. {  
  12.  
  13.     DM335_INTC &pAic = DM335::INTC();  
  14.  
  15.     unsigned int index;  
  16.  
  17.     // set before jumping elsewhere or allowing other interrupts  
  18.  
  19.     SystemState_SetNoLock(SYSTEM_STATE_ISR);  
  20.  
  21.     SystemState_SetNoLock(SYSTEM_STATE_NO_CONTINUATIONS);      
  22.  
  23.          while(pAic.IRQENTRY != 0x0)  
  24.  
  25.     {  
  26.  
  27.         index = (pAic.IRQENTRY>>2) - 1;  
  28.  
  29.              if(index < 32)  
  30.  
  31.                 pAic.IRQ0 |= 1 << index;  
  32.  
  33.         else 
  34.  
  35.                 pAic.IRQ1 |= 1 << (index % 32);  
  36.  
  37.         s_IsrTable[index].Handler.Execute();  //执行对应的中断处理函数  
  38.  
  39.     }   
  40.  
  41.     SystemState_ClearNoLock(SYSTEM_STATE_NO_CONTINUATIONS); // nestable  
  42.  
  43.     SystemState_ClearNoLock(SYSTEM_STATE_ISR); // nestable  
  44.  
  45. }  
  46.  

在中断函数执行之前,其实已经执行了中断寄存器的初始工作,比如根据需要打开或关闭(Enable/Disable)相关的中断,对我们的应用来说串口和USB的中断是要打开的,而DMA中断就是关闭的。此外根据你应用的需求,你也可以设置不同中断的优先级(0~7,其中0~1对应快速中断,对我们来说由于快速中断无效,所以中断的优先级最高可设为2)。

相同等级的中断同时来到,是谁来排序呢?

对DM335来说,是由硬件完成的,DM335提供一个寄存器IRQENTRY,IRQENTRY寄存器中存放的值,就是下一个要执行的中断号(要经过一定的换算),该寄存器就像一个队列的出队口,排好队的中断依次出列,直到IRQENTRY的值为0为止(表示当前队列中没有任何中断被触发)。

(当然我们也可以直接根据IRQ0和IRQ1中的值触发二级中断函数,但是这样来做,设定的中断优先级就没有任何意义了。)

这时候我们就可以根据不同的中断号,执行相对应的二级中断函数了。

那中断来了,程序是如何知道该执行那个二级中断函数呢?请看下一节内容就明白了。

3 中断连接函数
CPU_INTC_ActivateInterrupt函数是把硬件的中断号和要执行的中断函数联系起来,其原型是:

BOOL CPU_INTC_ActivateInterrupt(UINT32 Irq_Index, HAL_CALLBACK_FPN ISR, void *ISR_Param);

Irq_Index – 中断号(可根据相关硬件手册进行设定)

ISR – 该中断触发时所要执行的中断函数

ISR_Param –中断函数的入口参数(可以为空)

 

当然根据需要也可以断开这种连接,或关掉相应的中断,这些函数由于比较好理解,所以这里就不过多介绍了。    

     我们以USB驱动为例,简单介绍一下CPU_INTC_ActivateInterrupt函数的使用,在USB的初始化函数中,我们添加了如下代码:

   

 
 
  1. CPU_INTC_ActivateInterrupt(DM335_INTC::c_IRQ_Index_USBINT, Global_ISR, NULL);  
  2.  
  3.    其中断函数Global_ISR的部分代码如下:  
  4.  
  5.    void DM335_USB_Driver::Global_ISR(void *Param)  
  6.  
  7.  
  8.  
  9.        DM335_USB &usb = DM335_USB::Type();  
  10.  
  11. T32 endpoint = 0;  
  12.  
  13.        UINT32 tmp = usb.Base.INTSRCR;  
  14.  
  15.        usbusb.Base.INTCLRR = usb.Base.INTSRCR;  
  16.  
  17.  
  18.  
  19.        //在这里面就可以根据USB相关寄存器的值,确定那一个中断被触发了  
  20.  
  21.        // … …  
  22.  
  23.     }  

这样一层一层,由根到枝,按图索骥,获知实际的中断,并最终执行相关的处理代码(如接收数据或发送数据等等)。

4 中断控制
     在Porting Kit提供的样例代码中,我们会在中断函数中看到不少GLOBAL_LOCK(irq)代码,由于MF目前不支持中断嵌套,所以从某种意义上说,在中端函数中添加代码GLOBAL_LOCK(irq)是没有意义的(对IRQ中断来说,CPU在触发该中断时,就已经关闭了IRQ中断位,除非你在中断函数中又打开了,当然你这样做的目的一般就是支持中断嵌套了,这部分内容暂不在讨论之列)。

GLOBAL_LOCK是一个宏,其实现为:

#define GLOBAL_LOCK(x)     SmartPtr_IRQ x

而SmartPtr_IRQ类的代码为:

 
 
  1. class SmartPtr_IRQ  
  2.  
  3. {  
  4.  
  5.     UINT32 m_state;  
  6.  
  7.     void*  m_context;  
  8.  
  9. public:  
  10.  
  11.     SmartPtr_IRQ(void* context=NULL)  { m_context = context; Disable(); }  
  12.  
  13.     ~SmartPtr_IRQ() { Restore(); }  
  14.  
  15. …  
  16.  
  17. }  
  18.  
  19. void SmartPtr_IRQ::Disable()  
  20.  
  21. {  
  22.  
  23.     UINT32 Cp;  
  24.  
  25.     UINT32 Cs;  
  26.  
  27.     __asm  
  28.  
  29.     {  
  30.  
  31.         MRS     Cp, CPSR  
  32.  
  33.         ORR     Cs, Cp, #0x80  
  34.  
  35.         MSR     CPSR_c, Cs  
  36.  
  37.     }  
  38.  
  39.     m_state = Cp;  
  40.  
  41. }  
  42.  
  43. void SmartPtr_IRQ::Restore()  
  44.  
  45. {  
  46.  
  47.     UINT32 Cp = m_state;  
  48.  
  49.     if((Cp & DISABLED_MASK) == 0)  
  50.  
  51.     {  
  52.  
  53.         ASSERT_SYSTEM_IRQ_MODE(Cp);  
  54.  
  55.         __asm  
  56.  
  57.         {  
  58.  
  59.             MRS     Cp, CPSR  
  60.  
  61.             BIC     Cp, Cp, #0x80  
  62.  
  63.             MSR     CPSR_c, Cp  
  64.  
  65.         }  
  66.  
  67.     }  
  68.  
  69. }  
  70.  

 

由以上代码可知,定义SmartPtr_IRQ的实例的同时,就执行了构造函数,也就是执行了Disable函数(关闭IRQ中断)。换句话说,GLOBAL_LOCK(irq)代码的作用就是,在irq的生命周期内关闭IRQ中断。

相信,如果MF的后续版本开始支持中断嵌套,该部分的内容才会变的复杂。

5 补充说明
MF应用程序(C#)中的中断(或事件函数)其实和我们这章内容所说的中断不是一会事,由以上内容我们可以了解到,中断函数串序执行,所以每一个中断函数执行时间尽量短,否则整个系统的效率便会大大降低。所以我们上层的事件函数是不可能直接勾连在中端函数内,因为在实际的应用中,我们非常有可能在事件函数中执行很多耗时的操作的。实际上TinyCLR代码对这两部分进行了一定得隔离处理,等我们有时间在一一剖析相关的实现机理。

除了中断函数被执行外,对嵌入式应用来说剩下的也就是一个Main函数(对MF来说就是ApplicationEntryPoint)和一个while循环了。对于我们的TinyCLR来说其实就是一个在相对简陋的操作系统上的一个精彩应用而已:

 

 
 
  1. void ApplicationEntryPoint()  
  2.  
  3. {  
  4.  
  5.     CLR_SETTINGS clrSettings;  
  6.  
  7.     memset(&clrSettings, 0, sizeof(CLR_SETTINGS));  
  8.  
  9.     clrSettings.MaxContextSwitches = 50;  
  10.  
  11. #if !defined(BUILD_RTM)  
  12.  
  13.     clrSettings.PerformGarbageCollection = false;  
  14.  
  15.     clrSettings.PerformHeapCompaction    = false;  
  16.  
  17. #endif  
  18.  
  19.     clrSettings.WaitForDebugger          = false;  
  20.  
  21.     ClrStartup( clrSettings );  
  22.  
  23. #if !defined(BUILD_RTM)  
  24.  
  25.     debug_printf( "Exiting.\r\n" );  
  26.  
  27. #else  
  28.  
  29.     ::CPU_Reset();  
  30.  
  31. #endif  
  32.  
  33. }  
  34.  

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值