Time Management
The Clock Tick section established that µC/OS-II requires (as do most kernels) that you provide a periodic interrupt to keep track of time delays and timeouts. This periodic time source is called a clock tick and should occur between 10 and 1,000 times per second, or Hertz. The actual frequency of the clock tick depends on the desired tick resolution of your application. However, the higher the frequency of the ticker, the higher the overhead.
The section Interrupts Under µC/OS-II discussed the tick ISR (Interrupt Service Routine) as well as the function that it needs to call to notify µC/OS-II about the tick interrupt — OSTimeTick()
.
Time Services
This chapter describes five services that deal with time issues:
OSTimeDly()
OSTimeDlyHMSM()
OSTimeDlyResume()
OSTimeGet()
OSTimeSet()
The functions described in this chapter are found in the file OS_TIME.C
.
Some of the time management services must be enabled by seting configuration constants in OS_CFG.H
. Specifically, table 5.1 shows which services are compiled based on the value of configuration constants found in OS_CFG.H
.
µC/OS-II Time Management Service | Enabled when set to 1 in OS_CFG.H |
---|---|
OSTimeDly() | |
OSTimeDlyHMSM() | OS_TIME_DLY_HMSM_EN |
OSTimeDlyResume() | OS_TIME_DLY_RESUME_EN |
OSTimeGet() | OS_TIME_GET_SET_EN |
OSTimeSet() | OS_TIME_GET_SET_EN |
Delaying a Task, OSTimeDly()
µC/OS-II provides a service that allows the calling task to delay itself for a user-specified number of clock ticks. This function is called OSTimeDly()
. Calling this function causes a context switch and forces µC/OS-II to execute the next highest priority task that is ready to run. The task calling OSTimeDly()
is made ready to run as soon as the time specified expires or if another task cancels the delay by calling OSTimeDlyResume()
. Note that this task will run only when it’s the highest priority task.
Listing 5.1 shows the code for OSTimeDly()
. Your application calls this function by supplying the number of ticks to delay — a value between 1 and 65535. A value of 0 specifies no delay.
void OSTimeDly (INT32U ticks) { INT8U y; #if OS_CRITICAL_METHOD == 3u OS_CPU_SR cpu_sr = 0u; #endif if (OSIntNesting > 0u) { return; } if (OSLockNesting > 0u) { return; } if (ticks > 0) { (1) OS_ENTER_CRITICAL(); y = OSTCBCur->OSTCBY; (2) OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX; if (OSRdyTbl[y] == 0u) { OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY; } OSTCBCur->OSTCBDly = ticks; (3) OS_EXIT_CRITICAL(); OSSched(); (4) } }
(1) If you specify a value of 0, you are indicating that you don’t want to delay the task, and the function returns immediately to the caller.
(2) A nonzero value causes OSTimeDly()
to remove the current task from the ready list.
(3) Next, the number of ticks are stored in the OS_TCB
of the current task, where it is decremented on every clock tick by OSTimeTick()
. You should note that the calling task is not placed in any wait list. Simply having a non-zero value in .OSTCBDly
is sufficient for OSTimeTick()
to know that the task has been delayed.
(4) Finally, since the task is no longer ready, the scheduler is called so that the next highest priority task that is ready to run gets executed.
It is important to realize that the resolution of a delay is between zero and one tick. In other words, if you try to delay for only one tick, you could end up with an intermediate delay between 0 and 1 tick. This is assuming, however, that your processor is not heavily loaded. Figure 5.1 illustrates what happens.
(1) A tick interrupt occurs every 10ms.
(2) Assuming that you are not servicing any other interrupts and that you have interrupts enabled, the tick ISR will be invoked.
(3) You may have a few high-priority tasks (HPTs) waiting for time to expire, so they will execute next.
(4) The low-priority task (LPT) shown in Figure 5.1 then gets a chance to execute and, upon completion, calls OSTimeDly(1)
at the moment shown. µC/OS-II puts the task to sleep until the next tick.
(5) & (6) When the next tick arrives, the tick ISR executes, but this time there are no HPTs to execute, and µC/OS-II executes the task that delayed itself for one tick. As you can see, the task actually delayed for less than one tick! On heavily loaded systems, the task may call OSTimeDly(1)
a few tens of microseconds before the tick occurs and thus the delay results in almost no delay because the task is immediately rescheduled. If your application must delay for at least one tick, you must call OSTimeDly(2)
, specifying a delay of two ticks!
Delaying a Task, OSTimeDlyHMSM()
OSTimeDly()
is a very useful function, but your application needs to know time in terms of ticks. You can use the global #define constant OS_TICKS_PER_SEC
(see OS_CFG.H
) to convert time to ticks by declaring some #defines as follows:
#define OS_TIME_100mS (INT16U)((INT32U)OS_TICKS_PER_SEC * 100L / 1000L)
#define OS_TIME_500mS (INT16U)((INT32U)OS_TICKS_PER_SEC * 500L / 1000L)
#define OS_TIME_2S (INT16U)(OS_TICKS_PER_SEC * 2)
However, this is somewhat awkward. I added the function OSTimeDlyHMSM()
so that you can specify time in hours (H), minutes (M), seconds (S), and milliseconds (m), which is more natural. Like OSTimeDly()
, calling this function causes a context switch and forces µC/OS-II to execute the next highest priority task that is ready to run. The task calling OSTimeDlyHMSM()
is made ready to run as soon as the time specified expires or if another task cancels the delay by calling OSTimeDlyResume()
[see section 5.02, Resuming a Delayed Task, OSTimeDlyResume()
]. Again, this task runs only when it again becomes the highest priority task. Listing 5.2 shows the code for OSTimeDlyHMSM()
. As you can see, your application calls this function by supplying the delay in hours, minutes, seconds, and milliseconds. In practice, you should avoid delaying a task for long periods of time because it’s always a good idea to get some feedback activity from a task (increment a counter, blink an LED, etc.). However, if you do need long delays, µC/OS-II can delay a task for 256 hours (close to 11 days).
INT8U OSTimeDlyHMSM (INT8U hours, INT8U minutes, INT8U seconds, INT16U milli) { INT32U ticks; if (OSIntNesting > 0u) { return (OS_ERR_TIME_DLY_ISR); } if (OSLockNesting > 0u) { return (OS_ERR_SCHED_LOCKED); } #if OS_ARG_CHK_EN > 0u if (hours == 0u) { (1) if (minutes == 0u) { if (seconds == 0u) { if (ms == 0u) { return (OS_ERR_TIME_ZERO_DLY); } } } } if (minutes > 59u) { (2) return (OS_ERR_TIME_INVALID_MINUTES); } if (seconds > 59u) { return (OS_ERR_TIME_INVALID_SECONDS); } if (ms > 999u) { return (OS_ERR_TIME_INVALID_MS); } #endif ticks = (INT32U)hours * 3600L * OS_TICKS_PER_SEC (3) + (INT32U)minutes * 60L * OS_TICKS_PER_SEC + (INT32U)seconds * OS_TICKS_PER_SEC + OS_TICKS_PER_SEC * ((INT32U)milli + 500L / OS_TICKS_PER_SEC) / 1000L; (4) OSTimeDly(ticks); return (OS_ERR_NONE); }
(1) As with OSTimeDly()
, OSTimeDlyHMSM()
exits if you specify no delay.
(2) OSTimeDlyHMSM()
then checks that you have specified valid values for its arguments.
(3) Because µC/OS-II only knows about ticks, the total number of ticks is computed from the specified time.
(4) This portion of the equation determines the number of ticks given the specified milliseconds with rounding to the nearest tick. The value 500/OS_TICKS_PER_SECOND
basically corresponds to 0.5 ticks converted to milliseconds. For example, if the tick rate (OS_TICKS_PER_SEC
) is set to 100Hz (10ms), a delay of 4ms would result in no delay! A delay of 5ms would result in a delay of 10ms, and so on.
Resuming a Delayed Task, OSTimeDlyResume()
Instead of waiting for time to expire, a delayed task can be made ready to run by another task that cancels the delay. This is done by calling OSTimeDlyResume()
and specifying the priority of the task to resume. In fact, OSTimeDlyResume()
also can resume a task that is waiting for an event (see Chapters 7 through 11), although this is not recommended. In this case, the task pending on the event thinks it timed out waiting for the event.
The code for OSTimeDlyResume()
is shown in Listing 5.3.
INT8U OSTimeDlyResume (INT8U prio) { #if OS_CRITICAL_METHOD == 3 OS_CPU_SR cpu_sr; #endif OS_TCB *ptcb; if (prio >= OS_LOWEST_PRIO) { (1) return (OS_ERR_PRIO_INVALID); } OS_ENTER_CRITICAL(); ptcb = (OS_TCB *)OSTCBPrioTbl[prio]; if (ptcb == (OS_TCB *)0) { (2) OS_EXIT_CRITICAL(); return (OS_ERR_TASK_NOT_EXIST); } if (ptcb == OS_TCB_RESERVED) { OS_EXIT_CRITICAL(); return (OS_ERR_TASK_NOT_EXIST); } if (ptcb->OSTCBDly == 0u) { OS_EXIT_CRITICAL(); return (OS_ERR_TIME_NOT_DLY); } ptcb->OSTCBDly = 0u; (4) if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) { ptcb->OSTCBStat &= ~OS_STAT_PEND_ANY; ptcb->OSTCBStatPend = OS_STAT_PEND_TO; } else { ptcb->OSTCBStatPend = OS_STAT_PEND_OK; } if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { (5) OSRdyGrp |= ptcb->OSTCBBitY; (6) OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; OS_EXIT_CRITICAL(); OS_Sched(); (7) } else { OS_EXIT_CRITICAL(); } return (OS_ERR_NONE); }
(1) OSTimeDlyResume()
begins by making sure the task has a valid priority.
(2) Next, OSTimeDlyResume()
verifies that the task to resume does in fact exist.
(3) If the task exists, OSTimeDlyResume()
checks to see if the task is waiting for time to expire. Whenever the OS_TCB
field .OSTCBDly
contains a nonzero value, the task is waiting for time to expire because the task called either OSTimeDly()
, OSTimeDlyHMSM()
, or any of the PEND functions described in subsequent chapters.
(4) The delay is then canceled by forcing .OSTCBDly
to 0.
(5) A delayed task may also have been suspended; thus, the task is only made ready to run if the task was not suspended.
(6) The task is placed in the ready list when the time expired.
(7) At this point, OSTimeDlyResume()
calls the scheduler to see if the resumed task has a higher priority than the current task. This would result in a context switch.
Note that you could also have a task delay itself by waiting on a semaphore, mutex, event flag, mailbox, or queue with a timeout (see Chapters 7 through 11). You would resume such a task by simply posting to the semaphore, mutex, event flag, mailbox, or queue, respectively. The only problem with this scenario is that it requires you to allocate an event control block (see section 6.00), so your application would consume a little bit more RAM.
System Time, OSTimeGet() and OSTimeSet()
Whenever a clock tick occurs, µC/OS-II increments a 32-bit counter. This counter starts at zero when you initiate multitasking by calling OSStart()
and rolls over after 4,294,967,295 ticks. At a tick rate of 100Hz, this 32-bit counter rolls over every 497 days. You can obtain the current value of this counter by calling OSTimeGet()
. You can also change the value of the counter by calling OSTimeSet()
. The code for both functions is shown in Listing 5.4. Note that interrupts are disabled when accessing OSTime
. This is because incrementing and copying a 32-bit value on most 8-bit processors requires multiple instructions that must be treated indivisibly.
INT32U OSTimeGet (void) { #if OS_CRITICAL_METHOD == 3 OS_CPU_SR cpu_sr; #endif INT32U ticks; OS_ENTER_CRITICAL(); ticks = OSTime; OS_EXIT_CRITICAL(); return (ticks); } void OSTimeSet (INT32U ticks) { #if OS_CRITICAL_METHOD == 3 OS_CPU_SR cpu_sr; #endif OS_ENTER_CRITICAL(); OSTime = ticks; OS_EXIT_CRITICAL(); }