Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

The code in Listing 3.7 is used to place a task in the ready list. prio is the task’s priority.

 

As you can see from Figure 3.4, the lower three bits of the task’s priority are used to determine the bit position in OSRdyTbl[], and the next three most significant bits are used to determine the index into OSRdyTbl[]. Note that OSMapTbl[] (see OS_CORE.C) is in ROM and is used to equate an index from 0 to 7 to a bit mask, as shown in Table 3.1.

...

Index

Bit Mask (Binary)

0

00000001

1

00000010

2

00000100

3

00001000

4

00010000

5

00100000

6

01000000

7

10000000

...

 

A task is removed from the ready list by reversing the process using the code in Listing 3.8.

 

This code clears the ready bit of the task in OSRdyTbl[] and clears the bit in OSRdyGrp only if all tasks in a group are not ready to run; that is, all bits in OSRdyTbl[prio >> 3] are 0. Another table lookup is performed, rather than scanning through the table starting with OSRdyTbl[0], to find the highest priority task ready to run. OSUnMapTbl[256] is a priority resolution table (see OS_CORE.C). Eight bits represent when tasks are ready in a group. The least significant bit has the highest priority. Using this byte to index OSUnMapTbl[] returns the bit position of the highest priority bit set — a number between 0 and 7. Determining the priority of the highest priority task ready to run is accomplished with the code in Listing 3.9.

 

For example, as shown in Figure 3.5, if OSRdyGrp contains 01101000 (binary) or 0x68, then the table lookup OSUnMapTbl[OSRdyGrp] yields a value of 3, which corresponds to bit 3 in OSRdyGrp. Note that bit positions are assumed to start on the right with bit 0 being the rightmost bit. Similarly, if OSRdyTbl[3] contains 11100100 (binary) or 0xE4, then OSUnMapTbl[OSRdyTbl[3]] results in a value of 2 (bit 2). The task priority (prio) is then 26 (i.e. 3 x 8 + 2). Getting a pointer to the OS_TCB for the corresponding task is done by indexing into OSTCBPrioTbl[] using the task’s priority.

...

priority

...

.

Task Scheduling

µC/OS-II always executes the highest priority task ready to run. The determination of which task has the highest priority, and thus which task will be next to run, is determined by the scheduler. Task-level scheduling is performed by OS_Sched(). ISR-level scheduling is handled by another function [OSIntExit()] described later. The code for OS_Sched() is shown in Listing 3.10. µC/OS-II task-scheduling time is constant irrespective of the number of tasks created in an application.

...

  • A Stack Pointer (SP)
  • A Program Counter (PC)
  • A Processor Status Word (PSW)
  • Four general purpose registers (R1, R2, R3 and R4)

...

  • )

...

Figure 3.7 shows the state of the variables and data structures after calling OS_TASK_SW() and after saving the context of the task to suspend.

...

.

Figure 3.8 shows the state of the variables and data structures after executing the last part of the context switch code.

...

.

The pseudo code for the context switch is shown in Listing 3.11. OSCtxSw() is generally written in assembly language because most C compilers cannot manipulate CPU registers directly from C. In Chapter 14, 80x86 Large Model Port, we will see how OSCtxSw() as well as other µC/OS-II functions look on a real processor, the Intel 80x86.

Locking and Unlocking the Scheduler

...

Figure 3.9 illustrates the flow of execution when initializing the statistic task.

...

task

...

.

The code for OSStatInit() is shown in Listing 3.16.

 

The code for OS_TaskStat() is shown in Listing 3.17.

[3.1]

[3.2]

Multiplying OSIdleCtr by 100 limits the maximum value that OSIdleCtr can take, especially on fast processors. In other words, in order for the multiplication of OSIdleCtr to not overflow, OSIdleCtr must never be higher than 42,949,672! With fast processors, it’s quite likely that OSIdleCtr can reach this value. To correct this potential problem, all we need to do is divide OSIdleCtrMax by 100 instead as shown below.

[3.3]

The local variable max is thus precomputed to hold OSIdleCtrMax divided by 100.

Interrupts under µC/OS-II

...

Interrupts under µC/OS-II

µC/OS-II requires that an Interrupt Service Routine (ISR) be written in assembly language. However, if your C compiler supports in-line assembly language, you can put the ISR code directly in a C source file.

...

The above description is further illustrated in Figure 3.10.

Figure 3.10 Servicing an interrupt.

The code for The code for OSIntEnter() is shown in Listing 3.19 and the code for OSIntExit() is shown in Listing 3.20. Very little needs to be said about OSIntEnter().OSIntExit() looks strangely like OS_Sched() except for three differences:

You need to call OSIntCtxSw() instead of OS_TASK_SW() because the ISR has already saved the CPU registers onto the interrupted task and thus shouldn’t be saved again. Implementation details about OSIntCtxSw() are provided in Chapter 13, Porting µC/OS-II.

...

You MUST enable ticker interrupts AFTER multitasking has started; that is, after calling OSStart(). In other words, you should initialize ticker interrupts in the first task that executes following a call to OSStart(). A common mistake is to enable ticker interrupts after OSInit() and before OSStart() as shown in Listing 3.22.

 

Potentially, 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 your application will crash.

...

The code for OSTimeTick() is shown in Listing 3.24.

If you don’t like to make ISRs any longer than they must be, OSTimeTick() can be called at the task level as shown in Listing 3.25. To do this, create a task that has a higher priority than all your other application tasks. The tick ISR needs to signal this high-priority task by using either a semaphore or a message mailbox.

 

You obviously need to create a mailbox (contents initialized to NULL) that will be used to signal the task that a tick interrupt has occurred (Listing 3.26).

µC/OS-II Initialization

A requirement of µC/OS-II is that you call OSInit() before you call any of µC/OS-II’s other services. OSInit() initializes all µC/OS-II variables and data structures (see OS_CORE.C).

OSInit() creates the idle task OSTaskIdle(), which is always ready to run. The priority of OSTaskIdle() is always set to OS_LOWEST_PRIO. If OS_TASK_STAT_EN and OS_TASK_CREATE_EXT_EN (see OS_CFG.H) are both set to 1, OSInit() also creates the statistic task OS_TaskStat() and makes it ready to run. The priority of OS_TaskStat() is always set to OS_LOWEST_PRIO-1.

Figure 3.11 shows the relationship between some µC/OS-II variables and data structures after calling OSInit(). The illustration assumes that the following #define constants are set as follows in OS_CFG.H:

  • OS_TASK_STAT_EN is set to 1,
  • OS_FLAG_EN is set to 1,
  • OS_LOWEST_PRIO is set to 63, and
  • OS_MAX_TASKS is set to 62.

F3.11(1) You will notice that the task control blocks (OS_TCBs) of OS_TaskIdle() and OS_TaskStat() are chained together in a doubly linked list.

F3.11(2) OSTCBList points to the beginning of this chain. When a task is created, it is always placed at the beginning of the list. In other words, OSTCBList always points to the OS_TCB of last task created.

F3.11(3) Both ends of the doubly linked list point to NULL (i.e., 0).

F3.11(4) Because both tasks are ready to run, their corresponding bits in OSRdyTbl[] are set to 1. Also, because the bits of both tasks are on the same row in OSRdyTbl[], only one bit in OSRdyGrp is set to 1.

...

to signal the task that a tick interrupt has occurred (Listing 3.26).

µC/OS-II Initialization

A requirement of µC/OS-II is that you call OSInit() before you call any of µC/OS-II’s other services. OSInit() initializes all µC/OS-II variables and data structures (see OS_CORE.C).

OSInit() creates the idle task OSTaskIdle(), which is always ready to run. The priority of OSTaskIdle() is always set to OS_LOWEST_PRIO. If OS_TASK_STAT_EN and OS_TASK_CREATE_EXT_EN (see OS_CFG.H) are both set to 1, OSInit() also creates the statistic task OS_TaskStat() and makes it ready to run. The priority of OS_TaskStat() is always set to OS_LOWEST_PRIO-1.

Figure 3.11 shows the relationship between some µC/OS-II variables and data structures after calling OSInit(). The illustration assumes that the following #define constants are set as follows in OS_CFG.H:

  • OS_TASK_STAT_EN is set to 1,
  • OS_FLAG_EN is set to 1,
  • OS_LOWEST_PRIO is set to 63, and
  • OS_MAX_TASKS is set to 62.

µC/OS-II also initializes five pools of free data structures as shown in Figure 3.12. Each of these pools is a singly linked list and allows µC/OS-II to obtain and return an element from and to a pool quickly.

...

.

...

After OSInit() has been called, the OS_TCB pool contains OS_MAX_TASKS entries. The OS_EVENT pool contains OS_MAX_EVENTS entries, the OS_Q pool contains OS_MAX_QS entries, the OS_FLAG_GRP pool contains OS_MAX_FLAGS entries and finally, the OS_MEM pool contains OS_MAX_MEM_PART entries. Each of the free pools are NULL pointer terminated to indicate the end. The pool is of course empty if any of the list pointers point to NULL. The size of these pools are defined by you in OS_CFG.H.

Starting µC/OS-II

You start multitasking by calling OSStart(). However, before you start µC/OS-II, you must create at least one of your application tasks as shown in Listing 3.27.

The code for OSStart() is shown in Listing 3.28.

Figure 3.13 shows the contents of the variables and data structures after multitasking has started. Here, I assume that the task you created has a priority of 6. Notice that OSTaskCtr indicates that three tasks have been created: OSRunning is set to TRUE, indicating that multitasking has started, OSPrioCur and OSPrioHighRdy contain the priority of your application task, and OSTCBCur and OSTCBHighRdy both point to the OS_TCB of your task.

...

.

Obtaining the Current µC/OS-II Version

...