UCOSIII的中断管理
在STM32中是支持中断的,中断是一种硬件机制,用于通过CPU一个异步事件发生了,CPU在确认中断后,将其部分或全部寄存器入栈保存,并跳转执行一个特殊的函数,这个函数称为中断服务函数。中断服务函数处理该异步事件,处理过程有可能使得更高优先级的任务进入就绪态,这样当中断服务程序结束后,将直接执行该就绪的更高优先级任务且不在返回,否则将返回到被中断的任务继续执行。
UCOSIII是支持中断嵌套的,即高优先级的中断可以打断低优先级的中断,在UCOSIII中使用OSIntNestingCtr来记录中断嵌套次数,最大支持250级的中断嵌套,每进入一次中断服务函数OSIntNestingCtr就会加1,当退出中断服务函数的时候OSIntNestingCtr就会减1。
在编写UCOSIII的中断服务程序的时候需要使用到两个函数OSIntEnter()和OSIntExit(),OSIntExit()函数我们前面已经讲过了是中断级任务调度器,可以查看链接: UCOSIII的任务调度和切换。OSIntEnter()的函数代码如下:
void OSIntEnter (void)
{
if (OSRunning != OS_STATE_OS_RUNNING) { /* Is OS running? */
return; /* No */
}
if (OSIntNestingCtr >= (OS_NESTING_CTR)250u) { /* Have we nested past 250 levels? */
return; /* Yes */
}
OSIntNestingCtr++; /* Increment ISR nesting level */
}
从上面代码中我们可以看出OSIntEnter()函数其实就是将OSIntNestingCtr进行简单的加一操作,用来中断嵌的次数而已。
那么在UCOSIII环境中如何编写中断服务函数呢?我们按照下面所示代码编写中断服务函数:
void xxxx_IRQHandler(void)
{
OSIntEnter();
... //中断服务程序
OSIntExit();
}
首先调用OSIntEnter()函数来标记进入中断服务函数,并且记录中断嵌套次数。退出中断服务函数的时候调用OSIntExit(),发起一次中断级任务切换。
CPU的中断处理
中断控制器允许为每个中断请求设置优先级,可记录哪些中断还未处理,并将当前最高优先级的中断请求的服务程序的地址直接传递到CPU。关闭全部中断后,CPU将忽略所有的中断请求。但是中断控制器将会把这些中断锁存,并在CPU重新打开中断后产生中断请求。
CPU处理中断的两种模式:
1 所有中断映射到一个公用的中断服务程序
2 每个中断映射到各自的中断服务程序
直接发布和延迟发布
在UCOSIII中对从中断发布消息或者信号的处理有两种模式:直接发布和延迟发布。
在OS_cfg.h中,将OS_CFG_ISR_POST_DEFERRED_EN设置为0,使用直接发布,如果是1,则使用延迟发布。
直接发布
1 外设产生中断请求;
2 中断服务程序开始运行,该中断服务程序中可能会包含有发送信号量、消息、事件标志组等事件。那么等待这个事件的任务的优先级要么比当前被中断的任务高,要么比其低;
3 如果中断对应的事件使得某个比被中断的任务优先级低的任务进入就绪态,则中断退出后仍恢复运行被中断的任务;
4 如果中断对应的事件使得某个比被中断的任务优先级更高的任务进入就绪态,则UCOSIII将进行任务切换,中断服务程序推出后就执行更高优先级的任务;
5 如果使用直接发布模式的话,则UCOSIII就必须关中断以保护临界段代码,防止中断处理程序访问这些临界段代码。
直接发布模式下,从中断发布消息和信号是直接在中断服务程序中发布的。
直接发布模式下,仅在处理定时器的时候才会给任务调度器上锁。如果定时器数目不多,并且定时器溢出的预处理函数很简短,则任务延迟时间应该也是很短的。UCOSIII可以精确测量锁定任务调度的时间,以得到任务延迟时间。
延迟发布
在延迟发布模式下,UCOSIII不是通过关中断,而是通过给任务调度器上锁的方式来保护临界段代码。这既保证了中断的相应和处理,也有效地保护了临界代码段。在延迟发布模式下,基本不存在关中断的情况。延迟发布模式如图所示:
1 外设产生中断请求;
2 中断服务程序开始运行,该中断服务程序中可能会包含有发送信号量、消息、事件标志组等事件。那么等待这个事件的任务的优先级要么比当前被中断的任务高,要么比其低;
3 中断服务程序通过调用系统的发布服务函数向任务发布消息或信号,在延迟发布模式下,这个过程不是直接进行发布操作,而是将这个发布函数调用和相应的参数写入到专用队列中,该队列称为中断队列。然后使中断队列处理任务进入就绪态,这个任务是UCOSIII的内部任务,并且具有最高优先级0;
4 中断服务程序处理结束时,UCOSIII切换执行中断队列处理任务,该任务从中断队列中提取出发布函数调用信息,此时仍需要关闭中断以防止中断服务程序同时对中断队列进行访问。中断队列处理任务提取出发布函数调用的信息后重新开中断,锁定任务调度器,然后进行发布函数调用,相当于发布函数调用一直是在任务级代码中进行的,这样本来应该在中断中处理的代码(发布函数调用)就被放到了任务级完成;
5 中断队列处理任务将中断队列处理完,将自身挂起,并重新启动任务调度来运行处于最高优先级的就绪任务。如果原先被中断的任务仍然是最高优先级的就绪任务,则UCOSIII恢复运行这个任务;
6 由于中断队列处理任务的发布操作使得更重要的任务进入就绪态,内核将切换到更高优先级的任务运行。
延迟发布模式减少了关闭中断的时间,因而减少了中断延迟,中断相应和中断恢复的时间。但因为使用给任务调度器上锁的方式保护临界代码段,反而使得任务延迟时间变长了。
直接发布和延迟发布的对比
直接发布模式下,UCOSIII通过关闭中断来保护临界段代码。延迟发布模式下,UCOSIII通过锁定任务调度来保护临界段代码。
延迟发布模式下,UCOSIII在访问中断队列时,仍然需要关闭中断,但是这个时间非常短暂。
如果应用中存在非常迅速的中断请求源,则当UCOSIII在直接发布模式下的中断关闭时间不能满足要求的时候,可以使用延迟发布模式来降低中断关闭时间。
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u /* 调度器上锁的方式保护临界段代码,中断服务管理任务 */
#define OS_CRITICAL_ENTER() \
do { \
CPU_CRITICAL_ENTER(); \ //关中断
OSSchedLockNestingCtr++; \
if (OSSchedLockNestingCtr == 1u) { \
OS_SCHED_LOCK_TIME_MEAS_START(); \
} \
CPU_CRITICAL_EXIT(); \ //开中断
} while (0)
/* Lock the scheduler but re-enable interrupts */
#define OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT() \
do { \
OSSchedLockNestingCtr++; \
\
if (OSSchedLockNestingCtr == 1u) { \
OS_SCHED_LOCK_TIME_MEAS_START(); \
} \
CPU_CRITICAL_EXIT(); \
} while (0)
/* Scheduling occurs only if an interrupt occurs */
#define OS_CRITICAL_EXIT() \
do { \
CPU_CRITICAL_ENTER(); \
OSSchedLockNestingCtr--; \
if (OSSchedLockNestingCtr == (OS_NESTING_CTR)0) { \
OS_SCHED_LOCK_TIME_MEAS_STOP(); \
if (OSIntQNbrEntries > (OS_OBJ_QTY)0) { \
CPU_CRITICAL_EXIT(); \
OS_Sched0(); \
} else { \
CPU_CRITICAL_EXIT(); \
} \
} else { \
CPU_CRITICAL_EXIT(); \
} \
} while (0)
#define OS_CRITICAL_EXIT_NO_SCHED() \
do { \
CPU_CRITICAL_ENTER(); \
OSSchedLockNestingCtr--; \
if (OSSchedLockNestingCtr == (OS_NESTING_CTR)0) { \
OS_SCHED_LOCK_TIME_MEAS_STOP(); \
} \
CPU_CRITICAL_EXIT(); \
} while (0)
#else /* Direct ISR Posts -------------------------------- */
#define OS_CRITICAL_ENTER() CPU_CRITICAL_ENTER() //关中断
#define OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT()
#define OS_CRITICAL_EXIT() CPU_CRITICAL_EXIT() //开中断
#define OS_CRITICAL_EXIT_NO_SCHED() CPU_CRITICAL_EXIT() //开中断
#endif
UCOSIII支持直接发布和延迟发布两种模式,直接发布模式使用关闭中断的方式来保护临界代码段;延迟发布模式使用锁存调度的方式来保护临界代码段。这两种模式的选择取决于应用对中断响应时间和任务相应时间的要求。
直接发布:关中断–临界区代码–开中断;
延迟发布:关中断–从中断队列中提取出发布函数调用信息–开中断–临界区代码(中端服务管理任务中执行)。
UCOSIII的时间管理
UCOSIII通常需要用户提供一个周期性的中断来实现延时和超时等相关操作。这个周期性的时钟叫做时钟节拍,频率通常在10到1000HZ之间(间os_cfg_app.h)中的OS_CFG_TICK_RATE_HZ.时钟节拍的实际频率取决于具体应用对定时精度的要求。然而,时钟节拍的频率越高,给系统带来的负载越重。
延时函数有两种,OSTimeDly()和OSTimeDlyHMSM()。
OSTimeDly()函数有三种工作模式:相对模式、周期模式和绝对模式;
OSTimeDlyHMSM()函数仅在相对模式下工作。
OSTimeDly()函数
void OSTimeDly (OS_TICK dly, //指定延时的时间长度,这里单位为时间节拍数。
OS_OPT opt, //指定延迟使用的选项
OS_ERR *p_err)
{
...
}
opt选项相比OSTimeDly()函数多了两个选项OS_OPT_TIME_HMSM_STRICT和OS_OPT_TIME_HMSM_NON_STRICT,其他四个选项都一样的。
使用OS_OPT_TIME_HMSM_NON_STRICT选项的话将会检查延时参数,hours的范围应该是099,minutes的范围应该是059,seconds的范围为059,milli的范围为0999;
使用OS_OPT_TIME_HMSM_NON_STRICT选项的话,hours的范围为0999,minutes的范围为09999,seconds的范围为065535,mili的范围为04294967259。
取消任务的延时
OSTimeDlyResume()函数
void OSTimeDlyResume (OS_TCB *p_tcb,
OS_ERR *p_err)
{
...
}
任务可以通过调用OSTimeDlyResume()函数来恢复其他调用了OSTimeDly()或OSTimeDlyHMSM()的任务。
获取和设置系统时间
OSTimeGet()将获得当前时钟节拍计数器的值。
OSTimeSet()允许用户改变当前时钟节拍计数器的值。