Module 5
Time and Event management in RTOS
Introduction
• Time vs Event
Aspect Time Event
Synchronization or
Purpose Delay or periodic timing
communication
Trigger Source System clock tick ISR or another task
OSSemPend(),
Typical Function OSTimeDly()
OSMboxPend()
Wakeup Condition Timeout expires Event is posted or flag set
Task waits for new sensor
Usage Example Task sleeps for 10 ms
data
Clock Tick
•Definition: A clock tick is a special interrupt that occurs periodically and can be regarded as the
system's heartbeat.
•Timing: The duration between clock ticks is application-specific, typically ranging from 10 to 200
milliseconds.
•Functionality:
•The clock tick interrupt enables the kernel to delay tasks for an integral number of clock ticks.
•It provides timeouts for tasks waiting for events to occur.
•Impact of Tick Rate:
•A faster tick rate increases the overhead imposed on the system due to more frequent interrupts.
•Task Delays:
•All kernels permit tasks to be delayed for a specified number of clock ticks.
•Resolution vs. Accuracy:
•The resolution for delayed tasks is 1 clock tick, but this does not imply that the accuracy of the delays is
also 1 clock tick.
• OSTimeDly(10);
and your tick rate is 100 Hz (tick every 10 ms),
→ The task will delay for 10 ticks × 10 ms = 100 ms.
Case 1: how a task behaves when it calls
OSTimeDly(1)
1. Tick Interrupt (20 ms):
1. The system clock generates periodic interrupts, called "ticks," every 20 milliseconds (ms). Each tick acts as a
reference point to schedule and delay tasks.
2. Tick ISR (Interrupt Service Routine):
1. When a tick interrupt occurs, the ISR is executed to handle the tick. The shaded regions in this line indicate
the execution of the ISR at regular intervals of 20 ms. Update the task delay and system time.
3. All Higher Priority Tasks:
1. Tasks with higher priority than the delayed task are executed immediately after the tick ISR. These tasks are
depicted as shaded blocks, which occur after each tick interrupt. Their execution time reduces the time
available for lower-priority tasks.
4. Delayed Task:
1. This task is delayed by one tick. The delay is requested with a call to delay by 1 tick, i.e., 20 ms. However, due
to higher priority tasks, the actual delay of the task (denoted as t1, t2, and t3) varies.
1. t1 (19 ms): The task resumes slightly earlier than the full 20 ms due to the execution of higher-priority
tasks taking up 1 ms.
2. t2 (17 ms): Similarly, the task resumes slightly later, with higher-priority tasks consuming more time.
3. t3 (27 ms): In this case, the delay extends longer than the 20 ms due to more time consumed by higher-
priority tasks.
Clock Tick Behavior:
• In this context, a "tick" is a unit of time (20 ms in this case) used by the
system to manage task scheduling.
• A task can be delayed by a specific number of ticks, but the actual delay
(t1, t2, t3) depends on the time taken by higher-priority tasks that
preempt the delayed task. Therefore, the delayed task’s execution may
occur at varying intervals based on system load and priorities.
• This figure demonstrates the variability in task delays within an RTOS due
to preemption by higher-priority tasks, even when a fixed delay (1 tick or
20 ms) is requested.
• This will thus cause the execution of the task to jitter.
Case 2
Case Description
The call happens shortly before a tick interrupt. The next tick occurs soon
(after 6 ms), then the task must wait one full tick (20 ms) more before
t1 (6 ms)
resuming. → 6 + 20 = 26 ms, approximately shown as 6 ms effective waiting
from call to resume in the figure.
The call happens just before a tick (1 ms early). It waits for the next tick, then
t2 (19 ms) another full tick period → total ≈ 21 ms delay, shown as 19 ms effective
waiting.
The call happens just after a tick interrupt, so it must wait almost two full ticks
t3 (27 ms)
(≈ 40 ms). The effective delay appears as 27 ms.
• Delayed Task:
• The delayed task is requested to wait for 1 tick (20 ms). However,
higher-priority tasks interrupt its execution, causing the actual delay
to vary. The three specific delays are labeled as t1, t2, and t3:
• t1 (6 ms): The task resumes after only 6 ms, much earlier than the full 20 ms
tick, due to the minimal execution time taken by higher-priority tasks.
• t2 (19 ms): In this case, the task resumes almost after a complete 20 ms
delay, but slightly earlier, at 19 ms.
• t3 (27 ms): In this case, the delay extends to 27 ms due to the execution of
higher-priority tasks, delaying the lower-priority task's resumption.
Case 3
shows a situation where the execution times of all higher-priority tasks and ISRs extend beyond one clock tick. In this case, the
task that tries to delay for 1 tick will actually execute 2 ticks later! In this case, the task missed its deadline. This might be
acceptable in some applications, but in most cases it isn't.
• These situations exist with all real-time kernels. They are related to CPU
processing load and possibly incorrect system design.
• Here are some possible solutions to these problems:
a) Increase the clock rate of your microprocessor.
b) Increase the time between tick interrupts.
c) Rearrange task priorities.
d) Avoid using floating-point math (if you must, use single precision).
e) Get a compiler that performs better code optimization.
f) Write time-critical code in assembly language
Typical Delay
Higher-priority task Actual delay Reason for delay
Case When the delay call occurs Example (for 1 tick
status duration duration
= 20 ms)
Because the task
suspends right
Just before the next tick
Case No higher-priority before the tick
interrupt (i.e., near the end of ≈ 1 tick period ≈ 20 ms
#1 tasks ready occurs, it resumes
the current tick)
almost immediately
on the next tick
The task must wait
for the next full tick
Just after a tick interrupt (i.e.,
Case No higher-priority period, plus nearly
at the beginning of the new tick ≈ 2 tick periods ≈ 40 ms
#2 tasks ready another full tick
period)
since it just missed
the previous one
The task is ready
A higher-priority Slightly more than after 1 tick, but
Case Just after a tick interrupt
task runs when the 1 tick period (1–2 must wait until the ≈ 26 ms (example)
#3 (similar to Case #2)
next tick happens ticks) higher-priority task
finishes
What a clock tick is and how µC/OS-II handles
it,
• µC/OS-II requires that you provide a periodic time source to keep
track of time delays and timeouts.
• A tick should occur between 10 and 100 times per second. The faster
the tick rate, the higher the overhead imposed on the system.
• The actual frequency of the clock tick depends on the desired tick
resolution of your application.
• You can obtain a tick source by either dedicating a hardware timer, or
generating an interrupt from an AC power line (50/60 Hz) signal.
OSInit(); — Initialize µC/OS-II:
•This function initializes the µC/OS-II kernel. It must be called before using any other µC/OS-II function.
This setup process initializes data structures that the kernel uses to manage tasks, including ready lists, time
management structures, and task control blocks (TCBs).
•Essentially, it sets up the internal state so that the operating system is prepared to handle task management,
context switching, and other kernel-related functionalities.
Create at Least One Task by Calling OSTaskCreate():
•In this section, you will create one or more tasks (processes) that your application will run.
•OSTaskCreate() is used to define a task (or thread) that will run concurrently with others in a multitasking
environment. Each task must have a unique function, stack, and priority.
•Tasks can be created to handle various parts of your application, like sensor readings, communication, or
control logic.
OSStart(); — Start Multitasking:
•This is the final step in initializing and starting the RTOS. Once you call OSStart(), the RTOS takes control
of the system, and multitasking begins. From this point onward, the tasks you have created (with
OSTaskCreate()) will be managed by µC/OS-II.
•The OS will handle context switching between tasks based on priority and timing (using tick interrupts) to
ensure that all tasks are executed properly in a time-sliced or priority-based manner.
What could happen is that the tick interrupt could be serviced before µC/OS-II
starts the first task. At this point, µC/OS-II is in an unknown state and will cause
your application to crash.
When µC/OS-II starts, these steps occur in order:
• The hardware initializes (clock, memory, peripherals).
• µC/OS-II initializes its internal data structures (like task control blocks, ready
lists, etc.).
• The system tick timer (e.g., a hardware timer that generates interrupts
periodically) is configured and started.
• The first user task is created and scheduled using OSStart() — this is when the
RTOS actually begins running tasks.
If the tick interrupt happens before µC/OS-II calls OSStart(), then:
• The tick ISR (Interrupt Service Routine) runs.
• This ISR tries to update the OS kernel’s internal data (e.g., task
delays, tick counters, ready lists).
• But at this moment, the RTOS kernel is not yet initialized — those
data structures may contain random or invalid values.
• As a result, the tick ISR modifies uninitialized memory or invalid
pointers.
• When the OS later starts the first task, it finds corrupted internal
state → leading to a crash, lockup, or unpredictable behavior.
To prevent this, follow proper initialization order:
• Initialize µC/OS-II using OSInit();
• Create all required tasks (using OSTaskCreate() or OSTaskCreateExt()).
• Start the system tick timer only after tasks and OS are ready —
typically right before calling OSStart();
• Then call OSStart(); — the RTOS takes control safely.
• µC/OS-II’s clock tick is serviced by calling OSTimeTick() from a tick ISR
•Save processor registers:
•When an interrupt occurs, the current state of the CPU (including registers) must be saved so that the interrupted
task can resume correctly after the ISR is completed.
•This is typically done by pushing the register contents onto the stack to ensure the context of the interrupted task is
preserved.
•Call OSIntEnter() or increment OSIntNesting:
•OSIntEnter() or incrementing the OSIntNesting variable marks the start of the interrupt processing and notifies
the OS that an ISR is in progress. This is crucial in the context of nested interrupts.
•OSIntNesting is a counter that keeps track of how many nested interrupts are currently being handled. If interrupts
are re-enabled inside the ISR (which allows higher-priority interrupts), the OS needs to know how deeply nested the
interrupts are.
•OSIntEnter() is used to:
•Signal to the kernel that an interrupt has started.
•Prevent task scheduling while the ISR is executing.
•Call OSTimeTick():
•This is the most important function in the tick ISR. OSTimeTick() is responsible for:
•Updating the OS's internal time tick count (used for delays and timeouts).
•Checking which tasks are waiting on time delays and decrementing their delay counters.
•If a task’s delay expires, the task is moved to the ready state so it can be scheduled again.
•This function also checks if any periodic timers or task timeouts have expired, ensuring that tasks are
woken up at the correct time.
•Call OSIntExit():
•OSIntExit() signals that the interrupt processing is complete and allows the kernel to resume normal
operation.
•It typically decrements the OSIntNesting counter. When the nesting level reaches zero (i.e., no more
interrupts are active), the kernel may perform a context switch if a higher-priority task has become ready
to run.
•In some cases, OSIntExit() may trigger a context switch, allowing the kernel to switch to a higher-
priority task if one has become ready during the interrupt.
•Restore processor registers:
•After the ISR has completed, the CPU registers that were saved at the start of the ISR are restored from
the stack. This step ensures that the interrupted task can resume from exactly where it left off before the
interrupt occurred.
Time Management
• µC/OS-II (as do other kernels) requires 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
100 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.
• five services that deal with time issues:
Delaying a task, OSTimeDly()
• 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() will be made ready-to run as soon as the
time specified expires or, if another task cancels the delay
Example
• Let’s say you have three tasks in a system with the following priorities
(lower number means higher priority):
[Link] A: Priority 3
[Link] B: Priority 2
[Link] C: Priority 1
• In this scenario:
• Task C has the highest priority and will run first if it’s ready.
• Task B is the next highest priority task.
• Task A has the lowest priority among these three.
Scenario
[Link] C is currently running, and it calls OSTimeDly(5) to delay its
execution by 5 ticks.
•At this point, Task C is placed in a “delayed” state for 5 ticks.
•µC/OS-II performs a context switch and checks for the next highest-
priority task that is ready-to-run.
[Link] kernel identifies Task B as the next highest-priority task, so it starts
executing Task B.
[Link] Task B is running, Task C is still in the delayed state, and Task A
remains idle, as it has the lowest priority.
[Link] Task C’s delay of 5 ticks expires, it becomes ready-to-run again.
•Since Task C has the highest priority among all tasks, the kernel will
perform another context switch from Task B to Task C.
[Link] C resumes execution right after its delay
In this example, the OSTimeDly() function allows Task C to release the CPU
voluntarily and resume only after its delay period expires. This gives Task B
an opportunity to run while Task C is waiting. As soon as the delay ends,
Task C preempts Task B because it has a higher priority, and µC/OS-II
always favors the highest-priority ready task.
This approach ensures efficient time-sharing among tasks and prioritization
based on task importance.
• Line-by-line Explanation
1. Condition Check (Line 1):
• if (ticks > 0) {
This checks if the ticks parameter is greater than zero. If ticks is zero or negative, the
function exits without doing anything. This ensures that a delay only occurs when a
positive tick count is specified.
2. Enter Critical Section:
• OS_ENTER_CRITICAL();
Entering a critical section disables interrupts, preventing context switches or other tasks
from interfering with this function’s execution. It’s necessary to maintain atomicity and
protect shared resources.
3. Update Ready Table (Line 2):
if ((OSRdyTbl [OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) {
OSRdyGrp &= ~OSTCBCur->OSTCBBitY;
}
•OSRdyTbl and OSRdyGrp are data structures that keep track of which tasks are ready
to run.
•OSTCBCur is a pointer to the Task Control Block (TCB) of the currently running task.
•The current task’s bit (OSTCBBitX) is cleared in the OSRdyTbl entry corresponding to
the task’s priority (OSTCBY). This indicates that the task is no longer ready.
•The condition checks if the entire byte for that priority in OSRdyTbl is now zero
(meaning no tasks of this priority are ready). If so, it also clears the corresponding bit in
OSRdyGrp.
•OSRdyGrp is a bitmap that indicates which priority groups have ready tasks. If a
priority group becomes empty (no tasks in that group are ready), the corresponding bit
is cleared here.
Field Meaning
OSTCBCur Pointer to the TCB (Task Control Block) of the currently running task
OSTCBCur->OSTCBY Index (0–7) of the ready table entry that corresponds to the current task
OSTCBCur->OSTCBBitX Bitmask for the task’s bit within OSRdyTbl[OSTCBY]
OSTCBCur->OSTCBBitY Bitmask for the task’s group bit within OSRdyGrp
Set Delay for Current Task (Line 3):
• OSTCBCur->OSTCBDly = ticks;
•This sets the OSTCBDly field in the current task’s TCB to the specified ticks value, which is
the duration of the delay in ticks.
•While OSTCBDly is greater than zero, the task will remain in a delayed state. A timer tick
handler in µC/OS-II will decrement this delay periodically until it reaches zero, at which point
the task becomes ready to run again
Exit Critical Section:
• OS_EXIT_CRITICAL();
Exiting the critical section re-enables interrupts, allowing the system to respond to them. At
this point, any modifications to shared data structures are complete, so it’s safe to allow
interrupts again.
Task Scheduling (Line 4):
• OSSched();
•OSSched() is the µC/OS-II scheduler function, which determines the
highest-priority task that’s ready to run and performs a context switch if
needed.
•Since the current task has been delayed, it’s no longer ready-to-run, so
OSSched() will find the next highest-priority ready task and switch to it.
Parameter Meaning Example Value
Time between two timer
Tick period 1 ms
interrupts
Delay resolution Smallest possible delay 1 ms
Clock delay (OSTCBDly) Delay count in ticks for a task 10 (→ 10 ms total delay)
Delay resolution
• It is important to realize that the resolution of a delay is between 0 and 1 tick.
• Figure illustrates what happens. A tick interrupt occurs every 10 mS F5-1(1). Assuming
that you are not servicing any other interrupts and you have interrupts enabled, the
tick ISR will be invoked F5-1(2).
• You may have a few high priority tasks (i.e. HPTs) that were waiting for time to expire
so they will get to execute next F5-1(3).
• The low priority task (i.e. LPT) then gets a chance to execute and, upon completion,
calls OSTimeDly(1) at the moment shown at F5-1(4). µC/OS-II puts the task to sleep
until the next tick. When the next tick arrives, the tick ISR executes F5-1(5) but this
time, there are no HPTs to execute and thus, µC/OS-II executes the task that delayed
itself for 1 tick F5-1(6).
• 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 would result in almost no delay because the task would immediately be
rescheduled.
• If your application must delay for at least one tick, you must call OSTimeDly(2) thus
specifying a delay of 2 ticks!
Delaying a task, OSTimeDlyHMSM()
• OSTimeDly() is a very useful function but, application needs to know time in
term of ticks. You can use the global #define constant OS_TICKS_PER_SEC (see
S_CFG.H) to convert time to ticks
• The function OSTimeDlyHMSM() has been added 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() will be made ready-to-run as soon as the time
specified expires or if another task cancels the delay by calling
OSTimeDlyResume(). Again, this task will run only when it’s the highest priority
task.
• avoid delaying a task for long periods of time because, it’s always a
good idea to get some ‘feedback activity’ from a task (incrementing
counter, blinking an LED, etc.).
• If however, you do need long delays, µC/OS-II can delay a task for 256
hours (close to 11 days)!
1. Check if Delay is Required: If all the delay parameters are zero, the function will
return an error code OS_TIME_ZERO_DLY (9). This condition avoids
unnecessary delay if all time values are zero.
2. Input Validation:
•Checks if minutes is valid (0 to 59).
•Checks if seconds is valid (0 to 59).
•Checks if milli is valid (0 to 999).
If any of these are invalid, the function returns a corresponding error code
(OS_TIME_INVALID_MINUTES, OS_TIME_INVALID_SECONDS, or
OS_TIME_INVALID_MILLI).
3. Calculate Total Ticks: Converts hours, minutes, seconds, and milli into a unified
tick count based on the OS's tick rate (OS_TICKS_PER_SEC).
•Milliseconds are converted using ((INT32U)milli + 500L/OS_TICKS_PER_SEC) /
1000L, which allows rounding the milliseconds to the nearest tick.
4. Calculate and Handle Large Delays: Since OSTimeDly can only handle
up to 65535 ticks at once, the function calculates how many full 65536-
tick chunks (loops) are needed and the remainder (ticks).
5. Delay Execution: Calls OSTimeDly for the initial delay of the
remaining ticks after 65536-tick chunks are accounted for.
•For each full 65536-tick chunk, calls OSTimeDly twice with 32768 ticks
to avoid exceeding 65535 ticks.
•The loop repeats until all loops have been completed.
6. Return Status: Returns OS_NO_ERR to indicate a successful delay
operation.
• For example, if OS_TICKS_PER_SEC was 100 and you wanted a delay
of 15 minutes then OSTimeDlyHMSM() would have to delay for 15 *
60 * 100 or, 90000 ticks.
• This delay is broken down into two delays of 32768 and one delay of
24464 ticks. In this case, we would first take care of the remainder
L5.2(6) and then, the number of times we exceeded 65536 L5.2(7)-(8)
(i.e. done with two 32768 tick delays). `
Experiment NO 8
Resuming a delayed task, OSTimeDlyResume()
• µC/OS-II allows you to resume a task that delayed itself. In other
words, instead of waiting for the time to expire, a delayed task can be
made ready-to-run by another task which ‘cancels’ the delay. This is
done by calling OSTimeDlyResume() and specifying the priority of the
task to resume.
• The code for OSTimeDlyResume() is shown in listing 5.3 and starts by
making sure you specify a valid priority L5.3(1).
• Next, we verify that the task to resume does in fact exist L5.3(2).
• If the task exist, we check to see if the task is waiting for time to expire
L5.3(3).
• Whenever the OS_TCB field OSTCBDly contains a non-zero value, the task
is waiting for time to expire, whether because the task called OSTimeDly(),
OSTimeDlyHMSM().
• The delay is then cancelled by forcing OSTCBDly to zero L5.3(4).
• A delayed task may also have been suspended and thus, the task is only
made ready-to-run if the task was not suspended L5.3(5).
• The task is placed in the ready list when the above conditions are satisfied
L5.3(6). At this point, we call the scheduler to see if the resumed task has
a higher priority than the current task L5.3(7). This could result in a
context switch