//=====================================================================
//TITLE:
// INTC_ActivateInterrupt函数的实现
//AUTHOR:
// norains
//DATE:
// Monday 24-October-2010
//Environment:
// KEIL MDK 4.0
// .NET Micro Framework Porting 4.1
// RedCow Board
//=====================================================================
.Net Micro Framework的INTC_ActivateInterrupt函数作用是将中断和函数相互连接起来,简单点来说,就是中断发生后,自动调用所链接的函数。熟悉Cortex-M3架构的朋友都知道,向量表存储的就是各个中断函数的地址,当中断发生的时候,就会自动调用。似乎一切都能联系得起来,看起来很美好,不是么?
很可惜,向量表中的函数和INTC_ActivateInterrupt要求的不相同。向量表的函数是无返回值无形参的,INTC_ActivateInterrupt传入的虽然也是无返回值,但是有传入形参的。具体来说,向量表中存储的函数的原型应该是如此:
void InterruptHandler();
对于INTC_ActivateInterrupt来说,却是这样:
void InterruptHandler(void *param);
叶帆在他的文章《【.Net Micro Framework PortingKit - 07】NVIC中断处理》(http://blog.csdn.net/yefanqiu/archive/2010/01/18/5210149.aspx)也提到过这函数,但却没有去实现文档所要求的功能,而是另辟蹊径,以另一函数CPU_INTC_ActivateInterruptEx(UINT32 Irq_Index, UINT32 ISR)替代。因为我也只是刚开始尝试着移植.Net Micro Framework,并不清楚叶帆这样的做法是否对后续的工作有影响,但直觉告诉我,按微软的要求实现所其的函数,应该很保险。所以,这次我们就来看看该函数应该如何实现为好。
以上面的说法,我们可以很清楚知道两者的分歧在于INTC_ActivateInterrupt要求传入的函数必须带有形参。如何将这带有形参的函数与中断相链接,并传入形参,是我们所需要解决的难题。
我们换个思路,就以无形参的函数导入向量表,当中断发生后,在该函数中再调用有形参的函数不就可以了么?又因为该函数应该能够通过代码进行链接,所以又要求其应该存在于内存之中。综上所述,我们可以以向量表的大小为基准,再申请两块内存,一块存储函数地址,另一块则存储形参,问题不就迎刃而解了么?
那么,我们就一步一步,看看如何化解吧。
因为数据是存储于内存的,为了简便,就将这两块内存放置于向量表之后,如:
Vectors_Size EQU 512
AREA |.text|, CODE, READONLY
ARM_Vectors
SPACE Vectors_Size
ARM_Vectors_End
Vectors_Handler_Function
SPACE Vectors_Size
Vectors_Handler_Parameter
SPACE Vectors_Size
以Vectors_Handler_Function命名的内存块存储的是函数地址,Vectors_Handler_Parameter则是用来存储形参。
可能有不少朋友觉得奇怪,为什么这里会定义一个ARM_Vectors_End?理由其实很简单,就是为了能够得到向量表的大小。虽然Vectors_Size是一个常量,并且定义了相应的大小,但该Vectors_Size常量却不能为C语言使用。如果需要获知向量表的大小,就必须要有个变通,在这里就是ARM_Vectors_End - ARM_Vectors。这个算法计算的其实是地址的差值,因为每个内存地址就是1Byte,得出的结果就恰好是容量大小。
如果我们需要在C/C++中使用这两个内存块,方法也非常简单:
extern UINT32 Vectors_Handler_Function; extern UINT32 Vectors_Handler_Parameter; g_pHandlerFun = reinterpret_cast<UINT32 *>(&Vectors_Handler_Function); g_pHandlerParam = reinterpret_cast<UINT32 *>(&Vectors_Handler_Parameter);
需要注意的是,这里转换的是Vectors_Handler_Function的地址,而不是其中的数值,所以前面带有&符号。
我们为了代码的简便,在这两个内存块存储的相应函数和形参的序号和该中断在向量表的位置一致。比如说,定时器中断,在向量表处于的位置是15,那么在这两个内存块中是15。以代码作为解释,则简单如下:
#define IRQ_SysTick 15 g_pHandlerFun[IRQ_SysTick]; g_pHandlerParam [IRQ_SysTick];
不过,这样的代码仅仅是获得函数的地址和形参的数值,我们还需要做一些转换才能将地址转为函数并调用,如:
//将地址转换为函数 HAL_CALLBACK_FPN pFunc = reinterpret_cast<HAL_CALLBACK_FPN>(g_pHandlerFun[dwIrq]); //调用函数 (*pFunc)(reinterpret_cast<void *>(g_pHandlerParam[dwIrq]));
原理就是这么简单,但却很方便地将中断和带形参的函数联接起来。最后,就以计时器中断为例,综合来看看这方式的全貌吧:
;向量表
ARM_Vectors
;略
SysTicker_Handler ;计时器中断发生时自动调用SysTicker_Handler函数
//C/C++ #define IRQ_SysTick 15 external “C” SysTicker_Handler() { //将地址转换为函数 HAL_CALLBACK_FPN pFunc = reinterpret_cast<HAL_CALLBACK_FPN>(g_pHandlerFun[IRQ_SysTick]); //调用函数 (*pFunc)(reinterpret_cast<void *>(g_pHandlerParam[IRQ_SysTick])); }