基于TI-RTOS的CC2650DK开发(6)---硬件中断

Lee_的头像

3.4 硬件中断

硬件中断(Hwis)是应用为了回应外部异步事件所必须处理的关键进程。SYS/BIOS中特定target/device的Hwi模块用于管理硬件中断。请阅读概述,请参考document introducing Hwis。

在典型的嵌入式系统中,中断由装置周边设备或由外部设备传给处理器。两种情况都使中断由处理器导向ISR地址。任何影响Swi和Task调度的SYS/BIOS APIs中断处理都必须使用C或C++编写。早期SYS/BIOS版本所为调用汇编语言所使用的HWI_enter()/HWI_exit宏都不再提供。

跟SYS/BIOS没有互动的汇编ISRs可由Hwi_plug()指定。这类ISRs必须做自己上下文的相关内容。它们可以使用中断关键字、C函数或汇编语言函数。

所有硬件中断都会运行至结束,如果Hwi在它的ISR有机会运行前提交了多次,那么ISR只会运行一次。为此,应当将执行Hwi中断的函数代码最小化。

如果要让中断为全局可用,可以调用Hwi_enable(),ISR可被任意eanbled过的中断抢占。

Hwis不能为指定target使用芯片支持库(CSL)。作为替代,可参考第8章硬件抽象层APIs的相关内容。

可创建一个Hwi对象来联合使用ISR函数和指定中断。

3.4.1 创建Hwi对象

Hwi模块维护了一张指向Hwi对象的指针表,它包含了调度器产生的每个Hwi的管理信息(或调度器不提供的,由平台产生的中断存根)。使用以下方式动态创建一个Hwi对象:
Hwi_Handle hwi0;
Hwi_Params hwiParams;
Error_Block eb;

Error_init(&eb);
Hwi_Params_init(&hwiParams);

hwiParams.arg = 5;
hwi0 = Hwi_create(id, hwiFunc, &hwiParams, &eb);
if (hwi0 == NULL)
{
System_abort("Hwi create failed");
}
这里,hwi0是创建Hwi object的句柄,id是定义的中断号,hwiFunc是和Hwi关联的函数名称,hwiParams是包含Hwi实例参数的一个结构体(enable/restore masks,Hwi函数参数等等)。这里,hwiParams.arg设置为5。如果传递的是Null,而不是指向实际的Hwi_Params结构体指针,由会使用默认参数集合。“eb”是一个错误块,它可以让你在创建Hwi对象期间处理错误。

相应的静态创建Hwi对象的语法如下:
var Hwi = xdc.useModule('ti.sysbios.hal.Hwi');
var hwiParams = new Hwi.Params;
hwiParams.arg = 5;
Program.global.hwi0 = Hwi.create(id, '&hwiFunc', hwiParams);
"hwiParams = new Hwi.Params" 声明相当于创建并初始化了一个使用默认值的hwiParams结构体。在静态配置的情况下,“create”函数无需错误块。对于静态创建的Hwi对象来说,“Program.global.hwi0”成为运行时访问句柄(符号name=“hwi0”)。

3.4.2 硬件中断嵌套和系统栈尺寸

当Hwi运行时,它的函数调用使用到了系统栈。最坏情况下,每个Hwi都获得一个嵌套的调度函数(也就是最低优先级Hwi被下个最高优先级Hwi抢占,转而又被下一个最高优先级抢占……)。这导致了每个Hwi优先级实际使用的栈尺寸增加。

默认的系统栈尺寸是4096字节。你可以在配置脚本中增加以下语句来设置系统栈尺寸:
Program.stack = yourStackSize;
下表显示了Hwi中断最坏情况下,系统栈所需要的尺寸。第一个数字是某target在首个优先级中所需的系统栈空间大小。第二个数字显示的是应用中每个之后的优先级所需的栈空间大小。

基于TI-RTOS的CC2650DK开发(6)---硬件中断

请参考3.5.3节了解软件中断所使用的系统栈,3.6.3节获取task栈尺寸相关信息。

3.4.3 Hwi Hooks

Hwi模块支持以下Hook函数:
Register:在运行时,任何Hwis静态创建前需要调用的初始化函数。register hook在main()和中断使能前的启动时间调用 。
Create:Hwi创建时调用的函数。包括Hwis静态创建时以及使用Hwi_create()进行动态创建时。
Begin:在运行Hwi ISR函数之前调用的函数。
End:在Hwi ISR函数结束后调用的函数。
Delete:在运行时,使用Hwi_delete()删除Hwi时调用的函数。
以下HookSet结构体类型定义是Hwi模块所支持的hook函数封装:
typedef struct Hwi_HookSet
{
Void (*registerFxn)(Int); /* Register Hook */
Void (*createFxn)(Handle, Error.Block *); /* Create Hook */
Void (*beginFxn)(Handle); /* Begin Hook */
Void (*endFxn)(Handle); /* End Hook */
Void (*deleteFxn)(Handle); /* Delete Hook */
};
Hwi Hook函数仅可以静态配置。

3.4.3.1 Register函数

register函数用于允许一个hook集合存放它们对应的hook ID。此ID可传递给Hwi_setHookContext() 和Hwi_getHookContext()用来设置或获取特定hook的Context。如果hook的执行需要使用到Hwi_setHookContext()或Hwi_getHookContext()register函数,则必须指定register函数。
在中断使能之前的系统初始化期间,会调用registerFxn hook函数。
register函数语法如下:
Void registerFxn(Int id);

3.4.3.2 Create和Delete函数

无论何时,创建和删除Hwi都需要调用Create和Delete函数。Create函数需要传递一个Error_Block,它需要传递给应用程序的Memory_alloc(),这会增加额外的上下文存储空间。
createFxn和deleteFxn函数在中断使能时调用(除了在启动或main()时)。
函数语法如下:
Void createFxn(Hwi_Handle hwi, Error_Block *eb);
Void deleteFxn(Hwi_Handle hwi);
当定义多个Hook集时,公共类型的单个hook函数将以hook ID的顺序调用。

3.4.3.4 Hwi Hooks 示例

以下例子使用了两个Hwi hook集。Hwi关联了一个静态创建的计时器,用于Hwi hook函数。此例演示了如何读写每个hook集所关联的Hook Context Pointer。
配置脚要和程序输出在C代码后面列出。
这是未例的C代码:
(书上给的代码是有错误的,害我又浪费了几天时间,真是一路坎坷。还好,这个过程其实也是一个学习的过程。下面就用自己的语言写出创建项目的详细过程吧。)
1、首先按照前面日志所述方法新创建一个CCS的Empty项目。
2、打开empty.c文件,删除里面所有代码,把下面代码粘贴进去。

基于TI-RTOS的CC2650DK开发(6)---硬件中断
// HookSet 1 注册函数, 在Hwi模块启动时,先于main()调用*/
Void myRegister1(Int hookSetId)
{
System_printf("myRegister1: assigned hookSet Id = %d\n", hookSetId);
myHookSetId1 = hookSetId;
}
// 在Hwi模块启动,静态创建Hwis时调用,先于Main()
Void myCreate1(Hwi_Handle hwi, Error_Block *eb)
{
Ptr pEnv;
pEnv = Hwi_getHookContext(hwi, myHookSetId1);
/* pEnv此处应为0,否则就是一个bug. */
System_printf("myCreate1: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
Hwi_setHookContext(hwi, myHookSetId1, (Ptr)0xdead1);
}
//先于Hwi计时器函数前调用
Void myBegin1(Hwi_Handle hwi)
{
Ptr pEnv;
pEnv = Hwi_getHookContext(hwi, myHookSetId1);
System_printf("myBegin1: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
Hwi_setHookContext(hwi, myHookSetId1, (Ptr)0xbeef1);
}
//Hwi计时器函数结束后调用
Void myEnd1(Hwi_Handle hwi)
{
Ptr pEnv;
pEnv = Hwi_getHookContext(hwi, myHookSetId1);
System_printf("myEnd1: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
Hwi_setHookContext(hwi, myHookSetId1, (Ptr)0xc0de1);
}
// HookSet 2 注册函数, 在Hwi模块启动时,先于main()调用*/
Void myRegister2(Int hookSetId)
{
System_printf("myRegister2: assigned hookSet Id = %d\n", hookSetId);
myHookSetId2 = hookSetId;
}
// 在Hwi模块启动,静态创建Hwis时调用,先于Main()
Void myCreate2(Hwi_Handle hwi, Error_Block *eb)
{
Ptr pEnv;
pEnv = Hwi_getHookContext(hwi, myHookSetId2);
/* pEnv此处应为0,否则就是一个bug. */
System_printf("myCreate2: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
Hwi_setHookContext(hwi, myHookSetId2, (Ptr)0xdead2);
}
//先于Hwi计时器函数前调用
Void myBegin2(Hwi_Handle hwi)
{
Ptr pEnv;
pEnv = Hwi_getHookContext(hwi, myHookSetId2);
System_printf("myBegin2: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
Hwi_setHookContext(hwi, myHookSetId2, (Ptr)0xbeef2);
}
//Hwi计时器函数结束后调用
Void myEnd2(Hwi_Handle hwi)
{
Ptr pEnv;
pEnv = Hwi_getHookContext(hwi, myHookSetId2);
System_printf("myEnd2: pEnv = 0x%x, time = %d\n", pEnv, Timestamp_get32());
Hwi_setHookContext(hwi, myHookSetId2, (Ptr)0xc0de2);
myEnd2Flag = TRUE;
}
//中断计时器回调函数
Void myTimerFunc(UArg arg)
{
System_printf("Entering myTimerHwi\n");
}
//task回调函数
Void myTaskFunc(UArg arg0, UArg arg1)
{
System_printf("Entering myTask.\n");
Timer_start(myTimer);
/* wait for timer interrupt and myEnd2 to complete */
while (!myEnd2Flag) {
;
}
System_printf("myTask exiting ...\n");
}
//Idle回调函数
Void myIdleFunc()
{
System_printf("Entering myIdleFunc().\n");
System_exit(0);
}
//主函数
Int main(Int argc, Char* argv[])
{
System_printf("Starting HwiHookExample...\n");
BIOS_start();
return (0);
}

3、打开empty.cfg文件,单击窗体下方【cfg Script】标签进入到配置脚本文件

将其中所其中所有代码删除,并粘贴入如下代码:
/* pull in Timestamp to print time in hook functions */
xdc.useModule('xdc.runtime.Timestamp');
/* Disable Clock so that ours is the only Timer allocated */
var BIOS = xdc.useModule('ti.sysbios.BIOS');
BIOS.clockEnabled = false;
var Idle = xdc.useModule('ti.sysbios.knl.Idle');
Idle.addFunc('&myIdleFunc');
/* Create myTask with default task params */
var Task = xdc.useModule('ti.sysbios.knl.Task');
var taskParams = new Task.Params();
Program.global.myTask = Task.create('&myTaskFunc', taskParams);
/* Create myTimer as source of Hwi */
var Timer = xdc.useModule('ti.sysbios.hal.Timer');
var timerParams = new Timer.Params();
timerParams.startMode = Timer.StartMode_USER;
timerParams.runMode = Timer.RunMode_ONESHOT;
timerParams.period = 1000; // 1ms
Program.global.myTimer = Timer.create(Timer.ANY, "&myTimerFunc", timerParams);
/* Define and add two Hwi HookSets
* Notice, no deleteFxn is provided.
*/
var Hwi = xdc.useModule('ti.sysbios.hal.Hwi');
/* Hook Set 1 */
Hwi.addHookSet({
registerFxn: '&myRegister1',
createFxn: '&myCreate1',
beginFxn: '&myBegin1',
endFxn: '&myEnd1',
});
/* Hook Set 2 */
Hwi.addHookSet({
registerFxn: '&myRegister2',
createFxn: '&myCreate2',
beginFxn: '&myBegin2',
endFxn: '&myEnd2',
});

4、编译并将程序烧进开发板,完成后单击工具栏的绿色三角按钮(Resume(F8))。在控制台显示下列运行结果:
[Cortex_M3_0] myRegister1: assigned hookSet Id = 0
myRegister2: assigned hookSet Id = 1
myCreate1: pEnv = 0x0, time = 0
myCreate2: pEnv = 0x0, time = 0
Starting HwiHookExample...
Entering myTask.
myBegin1: pEnv = 0xdead1, time = 74
myBegin2: pEnv = 0xdead2, time = 84
Entering myTimerHwi
myEnd1: pEnv = 0xbeef1, time = 98
myEnd2: pEnv = 0xbeef2, time = 108
myTask exiting ...
Entering myIdleFunc().

下面来解释这个程序:
Hook的主要作用可以认为是用于监测某一事件的发生,比如为某Hwi注册一个Hook,当此Hwi发生中断前,会触发Hook的begin回调函数;当中断执行完毕后,会触发Hook的end回调函数。这时你就可以在begin和end回调函数内加入你想要监测的代码了。
这个程序演示了如何为一个Hwi注册Hook,更为重要的是演示了Hwi,Task,Idle和Hook所属回调函数在何时会被调用,调用顺序如何。

来源: abatei的博客