【UCOSIII】UCOSIII的中断和时间管理

UCOSIII的中断管理

UCOSIII中断处理过程

在STM32中是支持中断的,中断是一个硬件机制,主要用来向CPU通知一个异步事件发生了,这时CPU就会将当前CPU寄存器值入栈,然后转而执行中断服务程序,在CPU执行中断服务程序的时候有可能有更高优先级的任务就绪,那么当退出中断服务程序的时候,CPU就会直接执行这个高优先级的任务。

UCOSIII是支持中断嵌套的,即高优先级的中断可以打断低优先级的中断,在UCOSIII中使用OSIntNestingCtr来记录中断嵌套次数,最大支持250级的中断嵌套,每进入一次中断服务函数OSIntNestingCtr就会加1,当退出中断服务函数的时候OSIntNestingCtr就会减1。

在编写UCOSIII的中断服务程序的时候需要使用到两个函数OSIntEnter()和OSIntExit(),OSIntExit()函数我们前面已经讲过了是中断级任务调度器,可以查看链接:【UCOSIII】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(),发起一次中断级任务切换。

直接发布和延迟发布

相比UCOSII,UCOSIII对从中断发布消息或者信号的处理有两种模式:直接发布和延迟发布两种方式。

我们可以通过宏OS_CFG_ISR_POST_DEFERRED_EN来选择使用直接发布还是延迟发布(还是中断服务管理任务是否启用的宏)。宏OS_CFG_ISR_POST_DEFERRED_EN在os_cfg.h文件中有定义,当定义为0时使用直接发布模式,定义为1的时候使用延迟发布模式。不管使用那种方式,我们的应用程序不需要做出任何的修改,编译器会根据不同的设置编译相应的代码。

直接发布

在UCOSII中使用的就是直接发布,直接发布如下图所示:

  1. 外设产生中断请求;
  2. 中断服务程序开始运行,该中断服务程序中可能会包含有发送信号量、消息、事件标志组等事件。那么等待这个事件的任务的优先级要么比当前被中断的任务高,要么比其低;
  3. 如果中断对应的事件使得某个比被中断的任务优先级低的任务进入就绪态,则中断退出后仍恢复运行被中断的任务;
  4. 如果中断对应的事件使得某个比被中断的任务优先级更高的任务进入就绪态,则UCOSIII将进行任务切换,中断服务程序推出后就执行更高优先级的任务;
  5. 如果使用直接发布模式的话,则UCOSIII就必须关中断以保护临界段代码,防止中断处理程序访问这些临界段代码。

直接发布模式下,从中断发布消息和信号是直接在中断服务程序中发布的。

针对直接发布模式,如果进行临界段保护:UCOSIII会对临界段代码采用关闭中断的保护措施,这样就会延长中断的响应时间。虽然UCOSIII已经采用了所有可能的措施来降低中断关闭时间,但仍然有一些复杂的功能会使得中断关闭相对较长的时间。

延迟发布

当设置宏OS_CFG_ISR_POST_DEFERRED_EN为1的时候(UCOSIII中断服务管理任务),UCOSIII不是通过关中断,而是通过给任务调度器上锁的方法来保护临界段代码,在延迟发布模式下基本不存在关闭中断的情况,延迟发布如下图所示:

  1. 外设产生中断请求;
  2. 中断服务程序开始运行,该中断服务程序中可能会包含有发送信号量、消息、事件标志组等事件。那么等待这个事件的任务的优先级要么比当前被中断的任务高,要么比其低;
  3. 中断服务程序通过调用系统的发布服务函数向任务发布消息或信号,在延迟发布模式下,这个过程不是直接进行发布操作,而是将这个发布函数调用和相应的参数写入到专用队列中,该队列称为中断队列。然后使中断队列处理任务进入就绪态,这个任务是UCOSIII的内部任务,并且具有最高优先级0;
  4. 中断服务程序处理结束时,UCOSIII切换执行中断队列处理任务,该任务从中断队列中提取出发布函数调用信息,此时仍需要关闭中断以防止中断服务程序同时对中断队列进行访问。中断队列处理任务提取出发布函数调用的信息后重新开中断,锁定任务调度器,然后进行发布函数调用,相当于发布函数调用一直是在任务级代码中进行的,这样本来应该在中断中处理的代码(发布函数调用)就被放到了任务级完成;
  5. 中断队列处理任务将中断队列处理完,将自身挂起,并重新启动任务调度来运行处于最高优先级的就绪任务。如果原先被中断的任务仍然是最高优先级的就绪任务,则UCOSIII恢复运行这个任务;
  6. 由于中断队列处理任务的发布操作使得更重要的任务进入就绪态,内核将切换到更高优先级的任务运行。

延迟发布模式下,从中断发布消息和信号是延迟发布的,就是将要发布的内容下入到一个专用的队列中,最后由中断服务管理任务进行发布。

针对延迟发布模式,如果进行临界段保护:在使用延迟发布模式额外增加的操作都是为了避免使用关中断来保护临界段代码。延迟发布模式下,是通过任务调度来实现的(任务调度到中断服务管理任务),那么就需要锁定任务调度来保护临界段代码。这些额外增加的操作仅包括将发布调用及其参数复制到中断队列中、从中断队列提取发布调用和相关参数以及一次额外的任务切换。

直接发布和延迟发布的对比

  • 直接发布模式下,UCOSIII通过关闭中断来保护临界段代码;
  • 延迟发布模式下,UCOSIII通过锁定任务调度来保护临界段代码。在延迟发布模式下,UCOSIII在访问中断队列时,仍然需要关闭中断,但这个时间是非常短的。

如果应用中存在非常快速的中断请求源,则当UCOSIII在直接发布模式下的中断关闭时间不能满足要求的时候,可以使用延迟发布模式来降低中断关闭时间。

UCOSIII的临界段代码保护

有一些代码我们需要保证其完成运行,不能被打断,这些不能被打断的代码就是临界段代码,也叫临界区。我们在进入临界段代码的时候使用宏OS_CRITICAL_ENTER(),退出临界区的时候使用宏OS_CRITICAL_EXIT()或者OS_CRITICAL_EXIT_NO_SCHED()。

当宏OS_CFG_ISR_POST_DEFERRED_EN定义为0的时候,进入临界区的时候UCOSIII会使用关中断的方式,退出临界区以后重新打开中断。当OS_CFG_ISR_POST_DEFERRED_EN定义为1的时候进入临界区前是给调度器上锁,并在退出临界区的时候给调度器解锁。进入和退出临界段的宏在os.h文件中有定义,代码如下:

#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

采用调度器加锁的方式保护临界代码区,因为OSSchedLockNestingCtr是全局变量,我们在访问全局资源的时候一定要加保护,这里使用宏CPU_CRITICAL_ENTER()来保护OSSchedLockNestingCtr,当给OSSchedLockNestingCtr加1,也就是调度器上锁以后再调用宏CPU_CRITICAL_EXIT()退 出 中 断 。 注 意 这 里 仅 仅 是 因 为 要 操 作 全 局 资 源OSSchedLockNestingCtr才会关闭和打开中断,真正对于临界段代码的保护还是采用的调度器加锁的方式!

其实这里讲的就是直接发布和延迟发布的代码表示:

  • 直接发布:关中断--临界区代码--开中断;
  • 延迟发布:关中断--从中断队列中提取出发布函数调用信息--开中断--临界区代码(中端服务管理任务中执行)。

 

UCOSIII的时钟管理

就像人的心脏一样,UCOSIII需要一个系统时钟节拍,作为系统心跳,这个时钟我们一般都使用MCU的硬件定时器。Cortex-M内核提供了一个定时器用于产生系统时钟节拍,这个定时器就是Systick。

UCOSIII通过时钟节拍来对任务进行整个节拍的延迟,并为等待事件的任务提供超时判断。时钟节拍中断必须调用OSTimeTick()函数,我们使用Systick来为系统提供时钟,因此在Systick的中断服务程序中就必须调用OSTimeTick(),函数代码如下:

void  OSTimeTick (void)
{
    OS_ERR  err;
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
    CPU_TS  ts;
#endif

    OSTimeTickHook();                                       /* 钩子函数   */

#if OS_CFG_ISR_POST_DEFERRED_EN > 0u                    //延时发布模式

    ts = OS_TS_GET();                                       /* 得到时间戳  */
    OS_IntQPost((OS_OBJ_TYPE) OS_OBJ_TYPE_TICK,             /* Post to ISR queue        */
                (void      *)&OSRdyList[OSPrioCur],
                (void      *) 0,
                (OS_MSG_SIZE) 0u,
                (OS_FLAGS   ) 0u,
                (OS_OPT     ) 0u,
                (CPU_TS     ) ts,
                (OS_ERR    *)&err);

#else

   (void)OSTaskSemPost((OS_TCB *)&OSTickTaskTCB,            /* 向时钟节拍任务发送一个信号量     */
                       (OS_OPT  ) OS_OPT_POST_NONE,
                       (OS_ERR *)&err);


#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
    OS_SchedRoundRobin(&OSRdyList[OSPrioCur]);
#endif

#if OS_CFG_TMR_EN > 0u
    OSTmrUpdateCtr--;
    if (OSTmrUpdateCtr == (OS_CTR)0u) {
        OSTmrUpdateCtr = OSTmrUpdateCnt;
        OSTaskSemPost((OS_TCB *)&OSTmrTaskTCB,              /* 向定时器任务发送信号量      */
                      (OS_OPT  ) OS_OPT_POST_NONE,
                      (OS_ERR *)&err);
    }
#endif

#endif
}

 

UCOSIII的时间管理

任务延时

UCOSIII中的任务是一个无限循环并且还是一个抢占式内核,为了使高优先级的任务不至于独占CPU,可以给其他优先级较低任务获取CPU使用权的机会,UCOSIII中除空闲任务外的所有任务必须在合适的位置调用系统提供的延时函数,让当前的任务暂停运行一段时间并进行一个任务切换。

延时函数有两种,OSTimeDly()和OSTimeDlyHMSM()。

  • OSTimeDly()函数有三种工作模式:相对模式、周期模式和绝对模式;
  • OSTimeDlyHMSM()函数仅在相对模式下工作。

OSTimeDly()函数

void  OSTimeDly (OS_TICK   dly,                            //指定延时的时间长度,这里单位为时间节拍数。
                 OS_OPT    opt,                            //指定延迟使用的选项
                 OS_ERR   *p_err)    
{
    ...
}
延迟使用的选项
选项含义
OS_OPT_TIME_DLY相对模式
OS_OPT_TIME_TIMEOUT和OS_OPT_TIME_DLY一样
OS_OPT_TIME_MATCH绝对模式
OS_OPT_TIME_PERIODIC周期模式

相对模式在系统负荷较重时有可能延时会少一个节拍,甚至偶尔差多个节拍,在周期模式下,任务仍然可能会被推迟执行,但它总会和预期的“匹配值”同步。因此,推荐使用周期模式来实现长时间运行的周期性延时。

绝对模式可以用来在上电后指定的时间执行具体的动作,比如可以规定,上电N秒后关闭某个外设。

OSTimeDlyHMSM()函数

void  OSTimeDlyHMSM (CPU_INT16U   hours,                //需要延时的小时数
                     CPU_INT16U   minutes,              //需要延时的分钟数
                     CPU_INT16U   seconds,
                     CPU_INT32U   milli,
                     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的范围应该是0~99,minutes的范围应该是0~59,seconds的范围为0~59,milli的范围为0~999;
  • 使用OS_OPT_TIME_HMSM_NON_STRICT选项的话,hours的范围为0~999,minutes的范围为0~9999,seconds的范围为0~65535,mili的范围为0~4294967259。

取消任务的延时

OSTimeDlyResume()函数

一 个 任 务 可 以 通 过 调 用 这 个 函 数 来 “ 解 救 ” 那 些 因 为 调 用 了OSTimeDly()或者OSTimeDlyHMSM()函数而进入等待态的任务,函数原型如下:

void  OSTimeDlyResume (OS_TCB  *p_tcb,
                       OS_ERR  *p_err)
{
    ...
}

延时任务任务可通过在其他任务中调用函数OSTimeDlyResume()取消延时而进入就绪状态,此函数最后会引发一次任务调度。

获取和设置系统时间

UCOSIII定义了一个CPU_INT32U类型的全局变量OSTickCtr来记录系统时钟节拍数,在调用OSInit()时被初始化为0,以后每发生1个时钟节拍,OSTickCtr加1。

  • OSTimeSet()允许用户改变当前时钟节拍计数器的值,慎用!!!!!
  • OSTimeGet()用来获取动迁时钟节拍计数器的值。

 

相关推荐
©️2020 CSDN 皮肤主题: 程序猿惹谁了 设计师:白松林 返回首页