Event Control Blocks

Use of Event Control Blocks

Figure 6.1 shows how tasks and Interrupt Service Routines (ISRs) can interact with each other. A task or an ISR signals a task through a kernel object called an Event Control Block (ECB). The signal is considered to be an event, which explains my choice of this name.

Figure - Figure 6.1 Use of event control blocks

(A1) An ISR or a task can signal an ECB.

(A2) Only a task can wait for another task or an ISR to signal the object. An ISR is not allowed to wait on an ECB.

(A3) An optional timeout can be specified by the waiting task in case the object is not signaled within a specified time period.

(B) Multiple tasks can wait for a task or an ISR to signal an ECB. When the ECB is signaled, only the highest priority task waiting on the ECB will be “signaled” and made ready to run. An ECB can be either a semaphore, a message mailbox, or a message queue, as discussed later.

(C4) When an ECB is used as a semaphore, tasks can both wait on and signal the ECB.


An ECB is used as a building block to implement services such as Semaphores (chapter 7), Mutual Exclusion Semaphores (chapter 8), Message Mailboxes (chapter 10) and Message Queues (chapter 11).

µC/OS-II maintains the state of an ECB in a data structure called OS_EVENT (see uCOS_II.H). The state of an event consists of the event itself (a counter for a semaphore, a bit for a mutex, a pointer for a message mailbox, or an array of pointers for a queue) and a list of tasks waiting for the event to occur. Each semaphore, mutual exclusion semaphore, message mailbox, and message queue is assigned an ECB. The data structure for an ECB is shown in Listing 6.1 and also graphically in Figure 6.2.

Listing - Listing 6.1 Event control block data structure
typedef struct {
    INT8U     OSEventType;                   /* Event type                        */
    void     *OSEventPtr;                    /* Ptr to message or queue structure */
    INT16U    OSEventCnt;                    /* Count (when event is a semaphore) */
    OS_PRIO   OSEventGrp;                    /* Group for wait list               */
    OS_PRIO   OSEventTbl[OS_EVENT_TBL_SIZE]; /* Wait list for event to occur      */
#if OS_EVENT_NAME_EN > 0u
    INT8U    *OSEventName;
#endif
} OS_EVENT;


Figure - Figure 6.2, Event Control Block (ECB)


.OSEventType

contains the type associated with the ECB and can have the following values: OS_EVENT_TYPE_SEM, OS_EVENT_TYPE_MUTEX, OS_EVENT_TYPE_MBOX, or OS_EVENT_TYPE_Q. This field is used to make sure you are accessing the proper object when you perform operations on these objects through µC/OS-II’s service calls. .OSEventType is the first field (and first byte) of the data structure. This allows run-time checking to determine whether the pointer points to an ECB or an event flag (see Chapter 9).

.OSEventPtr

is only used when the ECB is assigned to a message mailbox or a message queue. It points to the message when used for a mailbox or to a data structure when used for a queue (see Chapter 10, Message Mailboxes, and Chapter 11, Message Queues).

.OSEventCnt

is used to hold the semaphore count when the ECB is used for a semaphore (see Chapter 7, Semaphores) or the mutex and PIP when the ECB is used for a mutex (see Chapter 8, Mutual Exclusion Semaphores).

.OSEventTbl[] and .OSEventGrp

are similar to OSRdyTbl[] and OSRdyGrp, respectively, except that they contain a list of tasks waiting on the event instead of a list of tasks ready to run (see section 3.??, Ready List).

Each task that needs to wait for the event to occur is placed in the wait list consisting of the two variables, .OSEventGrp and .OSEventTbl[]. Note that I used a dot (.) in front of the variable name to indicate that the variable is part of a data structure. Task priorities are grouped (eight tasks per group) in .OSEventGrp. Each bit in.OSEventGrp is used to indicate when any task in a group is waiting for the event to occur. When a task is waiting, its corresponding bit is set in the wait table,.OSEventTbl[]. The size (in bytes) of .OSEventTbl[] depends on OS_LOWEST_PRIO (see uCOS_II.H). This allows µC/OS-II to reduce the amount of RAM (i.e., data space) when your application requires just a few task priorities.

The task that is resumed when the event occurs is the highest priority task waiting for the event and corresponds to the lowest priority number that has a bit set inOSEventTbl[]. The relationship between .OSEventGrp and .OSEventTbl[] is shown in Figure 6.3 and is given by the following rules.

Bit 0 in .OSEventGrp is 1 when any bit in .OSEventTbl[0] is 1.

Bit 1 in .OSEventGrp is 1 when any bit in .OSEventTbl[1] is 1.

Bit 2 in .OSEventGrp is 1 when any bit in .OSEventTbl[2] is 1.

Bit 3 in .OSEventGrp is 1 when any bit in .OSEventTbl[3] is 1.

Bit 4 in .OSEventGrp is 1 when any bit in .OSEventTbl[4] is 1.

Bit 5 in .OSEventGrp is 1 when any bit in .OSEventTbl[5] is 1.

Bit 6 in .OSEventGrp is 1 when any bit in .OSEventTbl[6] is 1.

Bit 7 in .OSEventGrp is 1 when any bit in .OSEventTbl[7] is 1.

Etc.


Figure - Figure 6.3 Wait list for task waiting for an event to occur


Placing a Task in the ECB Wait List

The following code places a task in the wait list:

Listing - Listing 6.2 Making a task wait for an event
pevent->OSEventTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX;    
pevent->OSEventGrp                   |= OSTCBCur->OSTCBBitY;


You should realize from Listing 6.2 that the time required to insert a task in the wait list is constant and does not depend on how many tasks are in your system. Also, from Figure 6.3, the lower 3 bits of the task’s priority are used to determine the bit position in .OSEventTbl[] , and the next three most significant bits are used to determine the index into OSEventTbl[] . Note that OSMapTbl[] (see OS_CORE.C ) is a table in ROM, used to equate an index from 0 to 7 to a bit mask as shown in the Table 6.1.

Table - Table 6.1 Content of OSMapTbl[]
IndexBit Mask (Binary)
000000001
100000010
200000100
300001000
400010000
500100000
601000000
710000000


Removing a Task from an ECB Wait List

A task is removed from the wait list by reversing the process (Listing 6.3).

Listing - Listing 6.3 Removing a task from a wait list
INT8U  y;

y                       =  ptcb->OSTCBY;
pevent->OSEventTbl[y]  &= (OS_PRIO)~ptcb->OSTCBBitX;    
if (pevent->OSEventTbl[y] == 0u) {
    pevent->OSEventGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}


This code clears the bit corresponding to the task in .OSEventTbl[] and clears the bit in .OSEventGrp only if all tasks in a group are not waiting.

Finding the Highest Priority Task Waiting on an ECB

The code to find the highest priority task waiting for an event to occur is shown in Listing 6.4. Table lookups are again used for performance reasons because we don’t want to scan the .OSEventTbl[] one bit at a time to locate the highest priority task waiting on the event.

Listing - Listing 6.4 Finding the highest priority task waiting for the event
#if OS_LOWEST_PRIO <= 63u
    y    = OSUnMapTbl[pevent->OSEventGrp];              /* Find HPT waiting for message                */
    x    = OSUnMapTbl[pevent->OSEventTbl[y]];
    prio = (INT8U)((y << 3u) + x);                      /* Find priority of task getting the msg       */
#else
    if ((pevent->OSEventGrp & 0xFFu) != 0u) {           /* Find HPT waiting for message                */
        y = OSUnMapTbl[ pevent->OSEventGrp & 0xFFu];
    } else {
        y = OSUnMapTbl[(OS_PRIO)(pevent->OSEventGrp >> 8u) & 0xFFu] + 8u;
    }
    ptbl = &pevent->OSEventTbl[y];
    if ((*ptbl & 0xFFu) != 0u) {
        x = OSUnMapTbl[*ptbl & 0xFFu];
    } else {
        x = OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u;
    }
    prio = (INT8U)((y << 4u) + x);                      /* Find priority of task getting the msg       */
#endif

(1) Using .OSEventGrp as an index into OSUnMapTbl[] (see Listing 6.5) you can quickly locate which entry in .OSEventTbl[] holds the highest priority task waiting for the ECB. OSUnMapTbl[] returns the bit position of the highest priority bit set — a number between 0 and 7 (or 0 and 15). This number corresponds to the Y position in .OSEventTbl[] (see Figure 6.3).

(2) Once we know which ‘row’ (see Figure 6.3) contains the highest priority task waiting for the ECB, we can ‘zoom-in’ on the actual bit by performing another lookup in OSUnMapTbl[] but this time, with the entry in .OSEventTbl[] just found. Again, we get a number between 0 and 7 (or 0 to 15). This number corresponds to the X position in .OSEventTbl[] (see Figure 6.3).

(3) By combining the two previous operations, we can determine the priority number of the highest priority task waiting on the ECB. This is a number between 0 and 63 or 255, depending on whether we support up to 64 tasks or 256 tasks.


Listing - Listing 6.5
INT8U  const  OSUnMapTbl[] = {
    0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0x00 to 0x0F */
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0x10 to 0x1F */
    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0x20 to 0x2F */
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0x30 to 0x3F */
    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0x40 to 0x4F */
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0x50 to 0x5F */
    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0x60 to 0x6F */
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0x70 to 0x7F */
    7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0x80 to 0x8F */
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0x90 to 0x9F */
    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0xA0 to 0xAF */
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0xB0 to 0xBF */
    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0xC0 to 0xCF */
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0xD0 to 0xDF */
    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,     /* 0xE0 to 0xEF */
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0      /* 0xF0 to 0xFF */
};


Let’s look at an example as shown in Figure 6.4., if .OSEventGrp contains 11001000 (binary) or 0xC8, OSUnMapTbl[.OSEventGrp] yields a value of 3, which corresponds to bit 3 in .OSEventGrp and also happens to be the index in .OSEventTbl[] which contains the first non-zero entry. Note that bit positions are assumed to start on the right with bit 0 being the rightmost bit. Similarly, if .OSEventTbl[3] contains 00010000 (binary) or 0x10, OSUnMapTbl[.OSEventTbl[3]] results in a value of 4 (bit 4). The priority of the task waiting (prio) is thus 28 (3 x 8 + 4) which corresponds to the number in .OSEventTbl[] of Figure 6.3.

Figure - Figure 6.4 Example of ECB wait list


List of Free ECBs

The number of ECBs to allocate depends on the number of semaphores, mutual exclusion semaphores, mailboxes, and queues needed for your application. The number of ECBs is established by the #define OS_MAX_EVENTS, which is found in OS_CFG.H. When OSInit() is called (see section 3.??, µC/OS-II Initialization), all ECBs are linked in a singly linked list — the list of free ECBs (Figure 6.5). When a semaphore, mutex, mailbox, or queue is created, an ECB is removed from this list and initialized. ECBs can be returned to the list of free ECBs by invoking the OS???Del() functions for semaphore, mutex, mailbox, or queue services.

Figure - Figure 6.5 List of free ECBs


Four common operations can be performed on ECBs:

  • initialize an ECB,
  • make a task ready,
  • make a task wait for an event, and
  • make a task ready because a timeout occurred while waiting for an event.

To avoid duplicating code and thus to reduce code size, four functions have been created to perform these operations: OS_EventWaitListInit(), OS_EventTaskRdy(), OS_EventWait(), and OS_EventTO(), respectively.

Initializing an ECB, OS_EventWaitListInit()

Listing 6.6 shows the code for OS_EventWaitListInit(), which is a function called when a semaphore, mutex, message mailbox, or message queue is created [see OSSemCreate(), OSMutexCreate(), OSMboxCreate(), or OSQCreate()]. All that is accomplished by OS_EventWaitListInit() is to indicate that no task is waiting on the ECB. OS_EventWaitListInit() is passed a pointer to an event control block, which is assigned when the semaphore, mutex, message mailbox, or message queue is created. The code is implemented inline to avoid the overhead of a for loop.

Listing - Listing 6.6 Initializing the wait list
void  OS_EventWaitListInit (OS_EVENT *pevent)
{
    INT8U  i;

    pevent->OSEventGrp = 0u;                     
    for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
        pevent->OSEventTbl[i] = 0u;
    }
}


Making a Task Ready, OS_EventTaskRdy()

Listing 6.7 shows the code for OS_EventTaskRdy(). This function is called by the POST functions for a semaphore, a mutex, a message mailbox or a message queue when an ECB is signaled and the highest priority task waiting on the ECB needs to be made ready to run. In other words, OS_EventTaskRdy() removes the highest priority task (HPT) from the wait list of the ECB and makes this task ready to run.

Listing - Listing 6.7 Making a task ready to run
INT8U  OS_EventTaskRdy (OS_EVENT *pevent, void *msg, INT8U msk)
{
    OS_TCB   *ptcb;
    INT8U     y;
    INT8U     x;
    INT8U     prio;
#if OS_LOWEST_PRIO > 63u
    OS_PRIO  *ptbl;
#endif

 
 
#if OS_LOWEST_PRIO <= 63u
    y    = OSUnMapTbl[pevent->OSEventGrp];                                (1)
    x    = OSUnMapTbl[pevent->OSEventTbl[y]];
    prio = (INT8U)((y << 3u) + x);                     
#else
    if ((pevent->OSEventGrp & 0xFFu) != 0u) {           
        y = OSUnMapTbl[ pevent->OSEventGrp & 0xFFu];
    } else {
        y = OSUnMapTbl[(OS_PRIO)(pevent->OSEventGrp >> 8u) & 0xFFu] + 8u;
    }
    ptbl = &pevent->OSEventTbl[y];
    if ((*ptbl & 0xFFu) != 0u) {
        x = OSUnMapTbl[*ptbl & 0xFFu];
    } else {
        x = OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u;
    }
    prio = (INT8U)((y << 4u) + x);                      
#endif

    ptcb                  =  OSTCBPrioTbl[prio];                          (2)  
    ptcb->OSTCBDly        =  0u;                                          (3)


#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
    ptcb->OSTCBMsg        =  pmsg;                                        (4)
#else
    pmsg                  =  pmsg;                      
#endif


    ptcb->OSTCBStat      &= (INT8U)~msk;                                  (5)
    ptcb->OSTCBStatPend   =  pend_stat;                 
                                                        
    if ((ptcb->OSTCBStat &   OS_STAT_SUSPEND) == OS_STAT_RDY) {           (6)
        OSRdyGrp         |=  ptcb->OSTCBBitY;           
        OSRdyTbl[y]      |=  ptcb->OSTCBBitX;
    }
    OS_EventTaskRemove(ptcb, pevent);                   


#if (OS_EVENT_MULTI_EN > 0u)
    if (ptcb->OSTCBEventMultiPtr != (OS_EVENT **)0) {   
        OS_EventTaskRemoveMulti(ptcb, ptcb->OSTCBEventMultiPtr);
        ptcb->OSTCBEventPtr       = (OS_EVENT  *)pevent;
    }
#endif

    return (prio);                                                        (7)                              
}

(1) OS_EventTaskRdy() starts by determining the index into .OSEventTbl[] of the HPT, a number between 0 and OS_LOWEST_PRIO/8 + 1.

(2) The task control block (TCB) of the task being readied contains information that needs to be changed. Knowing the task’s priority, you can obtain a pointer to that TCB.

(3) Because the HPT is not waiting anymore, you need to make sure that OSTimeTick() will not attempt to decrement the .OSTCBDly value of that task. This is done by forcing .OSTCBDly to 0.

(4) A message is sent to the HPT if OS_EventTaskRdy() is called by the POST functions for message mailboxes and message queues. This message is passed as an argument and needs to be placed in the task’s TCB.

(5) When OS_EventTaskRdy() is called, the 'msk' argument contains the appropriate bit mask to clear the bit in .OSTCBStat, which corresponds to the type of event signaled (OS_STAT_SEM, OS_STAT_MUTEX, OS_STAT_MBOX, or OS_STAT_Q.

(6) If .OSTCBStat indicates that the task is now ready to run, OS_EventTaskRdy() inserts this task in µC/OS-II’s ready list. Note that the task may not be ready to run because it could have been explicitly suspended [see sections 4.??, Suspending a Task, OSTaskSuspend(), and 4.??, Resuming a Task, OSTaskResume() ].

(7) OS_EventTaskRdy() returns the priority of the task readied.


Note that OS_EventTaskRdy() is called with interrupts disabled.

Making a Task Wait for an Event, OS_EventTaskWait()

Listing 6.8 shows the code for OS_EventTaskWait(). This function is called by the PEND functions of a semaphore, mutex, message mailbox and message queue when a task must wait on an ECB. In other words, OS_EventTaskWait() removes the current task from the ready list and places it in the wait list of the ECB.

Listing - Listing 6.8 Making a task wait on an ECB
void  OS_EventTaskWait (OS_EVENT *pevent)
{
    INT8U  y;

    OSTCBCur->OSTCBEventPtr               = pevent;                          (1)
    pevent->OSEventTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX;             (2)
    pevent->OSEventGrp                   |= OSTCBCur->OSTCBBitY;
    y             =  OSTCBCur->OSTCBY;            
    OSRdyTbl[y]  &= (OS_PRIO)~OSTCBCur->OSTCBBitX;                           (3)
    if (OSRdyTbl[y] == 0u) {                      
        OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
    }
}

(1) The pointer to the ECB is placed in the task’s TCB, linking the task to the event control block.

(2) The task is placed in the wait list for the ECB.

(3) The task is removed from the ready list.