UCOS学习笔记——钩子函数及中断时间管理

钩子函数

空闲任务函数OSIdleTaskHook()

函数代码如下:

void OSIdleTaskHook (void)
{
#ifOS CFG APP HOOKS EN> Ou
    if (OS_ AppIdleTaskHookPtr != (OS_ APP HOOK_ VOID)0) {
       (*OS_ AppIdleTaskHookPtr)(); 
   }
#endif
}

从上面的函数代码中可以看出要使用空闲任务钩子函数的话需要将宏
OS_CFG_APP_HOOKSEN置1,即允许使用空闲任务的钩子函数
。当时使能空闲任务的钩子函数以后每次进入空闲任务就会调用指针OS_ AppIdleTaskHookPtr所指向的函数。
App_ OS_ SetAllHooks()函数代码如下:

void App_ OS_ SetAllHooks (void)
{
#ifOS CFG APP HOOKS EN> Ou
CPU_ SR_ ALLOCO); 

	CPU_CRITICAL_ENTERO;
	OS_AppTaskCreateHookPtr = App_ OS_ TaskCreateHook;
	OS_AppTaskDelHookPtr = App_OS_TaskDelHook;
	OS_AppTaskReturnHookPtr = App_OS_TaskReturnHook;
	OS_AppIdleTaskHookPtr = App_OS_IdleTaskHook;
	OS_AppStatTaskHookPtr = App_OS_StatTaskHook; 
	OS_AppTaskSwHookPtr = App_OS_TaskSwHook;
	OS_AppTimeTickHookPtr = App_OS_TimeTickHook;
	CPU_ CRITICAL_ EXIT();
#endif
}

OS_AppIdleTaskHookPtr = App_OS_IdleTaskHook该语句是将App_OS_IdleTaskHook复制给OS_AppIdleTaskHookPtr,
而OS_AppIdleTaskHookPtr是一个函数:

void App_OS_IdleTaskHook(void)
{

}

空闲任务的钩子函数OSIdleTaskHook()工作原理:OSIdleTaskHook中最终调用的是函数App_ OS_ Idle TaskHook(),也就是说如果我们要想在空闲任务的钩子函数中做一些其他处理的话就需要将程序代码写在App_OS_IdleTaskHook()函数中

注意!:

在空闲任务的钩子函数中不能调用任何可以是空闲进入等待态的代码,原因很简单,CPU总是在不停的运行,需要一直工作,不能让CPU停下来,哪怕是执行一些对应用没有任何用的代码,比如简单的将-一个变量加一。在UCOS中为了让CPU一直工作,在所有应用任务都进入等待态的时候CPU会执行空闲任务,我们可以从空闲任务的任务函数OS_ IdleTask()看出,在OS_ IdleTask(中 没有任何可以让空闲任务进入等待态的代码。如果在OS_ IdleTask()中有可以让空闲任务进入等待态的代码的话有可能会在同一时刻所有任务(应用任务和空闲任务)同时进入等待态,此时CPU就会无所事事了,所以在空闲任务的钩子函数OSIdle TaskHook()
中不能出现可以让空闲任务进入等待态的代码!

中断管理

中断处理过程

在STM32中是支持中断的,中断是一个硬件机制,主要用来向CPU通知一个异步事件发生了,这时CPU就会将当前CPU寄存器值入栈,然后转而执行中断服务程序,在CPU执行中断服务程序的时候有可能有更高优先级的任务就绪,那么当退出中断服务程序的时候,CPU就会直接执行这个高优先级的任务。
UCOSII是支持中断嵌套的,既高优先级的中断可以打断低优先级的中断,在UCOSIII中使用OSIntNestingCtr来记录中断嵌套次数,最大支持250级的中断嵌套,每进入一-次中断服务函数OSIntNestingCtr就会加1,当退出中断服务函数的时候OSIntNestingCtr就会减1。
我们在编写UCOSIII 的中断服务程序的时候需要使用到两个函数OSIntEnter()和
OSIntExit()
,OSIntExit()函数是中断级任务调度器,OSIntEnter()的函数代码如下:

void OSIntEnter (void)
{
    if (OSRunning!=OS_ STATE OS_ RUNNING) {//判断UCOSII是否运行,
	return;
    if (OSIntNestingCtr >= (OS_ NESTING CTR)250u) { //判断中断嵌套次数 是否大于250
	return;
    }
OSIntNestingCtr++;//中断嵌套次数加1
}

从上面代码中可以看出OSIntEnter()函数其实就是将OSIntNestingCtr 进行简单的加一操作,用来中断嵌套的次数而已。
那么我们在UCOSIII环境中如何编写中断服务函数呢我们按照下面所示代码编写中断服务函数:

void XXX_ Handler(void)						(1)
{
	OSIntEnter();//进入中断					(2)
	用户自行编写的中断服务程序;//这部分就是我们的中断服务程序		(3)
	OSIntExit();//触发任务切换软中断				(4)
}

(1)中断服务程序,XXX为不同中断源的中断函数名字。
(2)首先调用OSIntEnter()函数来标记进入中断服务函数,并且记录中断嵌套次数。
(3)这部分就是我们需要自行编写的中断服务程序了,也就是我们平时不使用UCOSIII时的中断服务程序。
(4)退出中断服务函数的时候调用OSIntExit(),发起一.次中断级任务切换。

直接发布与延迟发布

相比UCOSII,UCOSIII对从中断发布消息或者信号的处理有两种模式:直接发布和延迟发布两种方式。我们可以通过宏Os_ CFG ISR_ POST_ DEFERRED_ EN来选择使用直接发布还是延迟发布。宏OS_ CFG_ ISR_ POST DEFERRED EN在os_ cfg.h 文件中有定义,当定义为0时使用直接发布模式,定义为1的时候使用延迟发布模式。不管使用那种方式,我们的应用程序不需要做出任何的修改,编译器会根据不同的设置编译相应的代码。

直接发布

在UCOSII中使用的是直接发布,直接发布如图8.1.1所示:
在这里插入图片描述
(1)外设产生中断请求。
(2)中断服务程序开始运行,该中断服务程序中可能会包含有发送信号量、消息、事件标志组等事件。那么等待这个事件的任务的优先级要么比当前被中断的任务高,要么比其低。
(3)如果中断对应的事件使得某个比被中断的任务优先级低的任务进入就绪态,则中断退出后仍恢复运行被中断的任务。
(4)如果中断对应的事件使得某个比被中断的任务优先级更高的任务进入就绪态,则UCOSII将进行任务切换,中断服务程序推出后就执行更高优先级的任务。
(5)如果使用直接发布模式的话,则UCOSII必须关中断以保护临界段代码,防止中断处理程序访问这些临界段代码。
使用直接发布模式的话,UCOSII会对临界段代码采用关闭中断的保护措施,这样就会延长中断的响应时间。虽然UCOSII已经采用了所有可能的措施来降低中断关闭时间,但仍然有一些复杂的功能会使得中断关闭相对较长的时间。

延迟发布

当设置宏OS_ CFG_ ISR_ POST_ DEFERRED_ EN为1的时候,UCOSIII 不是通过关中断,而是通过给任务调度器上锁的方法来保护临界段代码,在延迟发布模式下基本不存在关闭中断的情况,延迟发布如图8.1.2所示。
在这里插入图片描述
(1)外设产生中断请求。
(2)中断服务程序开始运行,该中断服务程序中可能会包含有发送信号量、消息、事件标志组等事件。那么等待这个事件的任务的优先级要么比当前被中断的任务高,要么比其低。
(3)中断服务程序通过调用系统的发布服务函数向任务发布消息或信号,在延迟发布模式下,这个过程不是直接进行发布操作,而是将这个发布函数调用和相应的参数写入到专用队列中,该队列称为中断队列。然后使中断队列处理任务进入就绪态,这个任务是UCOSIII的内部任务,并且具有最高优先级(0)。
(4)中断服务程序处理结束时,UCOSIII 切换执行中断队列处理任务,该任务从中断队列中提取出发布函数调用信息,此时仍需要关闭中断以防止中断服务程序同时对中断队列进行访问。中断队列处理任务提取出发布函数调用的信息后重新开中断,锁定任务调度器,然后进行发布函数调用,相当于发布函数调用一直是在任务级代码中进行的,这样本来应该在临界段中处理的代码就被放到了任务级完成。
(5)中断队列处理任务将中断队列处理完,将自身挂起,并重新启动任务调度来运行处于最高优先级的就绪任务。如果原先被中断的任务仍然是最高优先级的就绪任务,则UCOSIII恢复运行这个任务。
(6)由于中断队列处理任务的发布操作使得更重要的任务进入就绪态,内核将切换到更高优先级的任务运行。
在使用延迟发布模式额外增加的操作都是为了避免使用关中断来保护临界段代码。这些额外增加的操作仅包括将发布调用及其参数复制到中断队列中、从中断队列提取发布调用和相关参数以及一-次额外的任务切换。

直接发布与延迟发布对比

直接发布模式 延迟发布模式
保护临界段代码 UCOSIII通过关闭中断 UCOSII通过锁定任务调度来保护临界段代码。
访问中断队列 如果应用中存在非常快速的中断请求源,则当UCOSIII在直接发布模式下的中断关闭时间不能满足要求的时候,可以使用延迟发布模式来降低中断关闭时间。 仍需要关闭中断(时间短)

OSTimeTick()函数

就像人的心脏一-样,UCOSIII 需要-一个系统时钟节拍,作为系统心跳,这个时钟我们一般都使用MCU的硬件定时器。Cortex-M 内核提供了一个定时器用于产生系统时钟节拍,这个定时器就是Systick。UCOSIII通过时钟节拍来对任务进行整个节拍的延迟,并为等待事件的任务提供超时判断。时钟节拍中断必须调用OlSTimeTick()函数,我们使用Systick来为系统提供时钟,因此在Systick的中断服务程序中就必须调用OSTimeTick()。
使用注意事项:
(1)时钟节拍中断服务程序中首先会调用钩子函数OSTimeTickHook(),这个函数中用户可以放置一些代码。
(2)如果使用了延迟发布模式,则UCOSII读取当前的时间戳信息,并在中断队列中放入发布函数调用请求和相关参数,延迟向时钟节拍任务发信号的操作。然后,中断队列处理任务根据中断队列向时钟节拍任务发信号。
(3)向时钟节拍任务(OS_ TickTask))发送-一个信号量。
(4)如果UCOSIII使用了时间片轮转调度机制,判断当前任务分配的运行时间片是否已经用完。
(5)如果使用定时器的话就向定时器任务(OS
_TmrTask))发送信号量。

临界段代码保护

有一些代码我们需要保证其完成运行,不能被打断,这些不能被打断的代码就是临界段代码,也叫临界区。我们在进入临界段代码的时候使用宏OS_ CRITICAL ENTER(), 退出临界区的时候使用宏OS_ CRITICAL_ EXIT0或者OS_ CRITICAL_ EXIT_ NO_ SCHED()。当宏OS_ CFG_ ISR_ POST_ DEFERRED EN定义为0的时候,进入临界区的时候UCOSIII会使用关中断的方式,退出临界区以后重新打开中断。当OS_ [CFG ISR POST DEFERRED EN定义为1的时候进入临界区前是给调度器上锁,并在退出临界区的时候给调度器解锁。
使用注意事项:
在这里插入图片描述

时间管理

OSTimeDly()函数

当我们对某一个任务需要进行延时的时候就调用该函数。函数原型如下:

void OSTimeDly(OS_ TICK dly,OS_ OPT opt,OS_ ERR *p_err)
dly 指定延时的时间长度,这里单位为时间节拍数。
opt 指定延迟使用的选项,有四种选项。
OS OPT TIME DLY 相对模式
OS OPT TIME TIMEOUT 和OS_ OPT_ TIME_ DLY一样
OS OPT TIME MATCH 绝对模式
OS_ OPT TIME_ PERIODIC 周期模式
P_ err 指向调用该函数后返回的错误码

“相对模式”在系统负荷较重时有可能延时会少-一个节拍,甚至偶尔差多个节拍,在周期模式下,任务仍然可能会被推迟执行,但它总会和预期的“匹配值”同步。因此,推荐使用“周期模式”来实现长时间运行的周期性延时。
“绝对模式”可以用来在上电后指定的时间执行具体的动作,比如可以规定,上电N秒后关闭某个外设。

OSTimeDlyHMSM()函数

函数原型在这里插入图片描述
对应参数的解释说明:在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章