Event Flag Management

Event Flag Configuration

µC/OS-II event flags consist of two elements: a series of bits (8, 16 or 32) used to hold the current state of the events in the group, and a list of tasks waiting for a combination of these bits to either be set (1) or cleared (0). µC/OS-II provides six services to access semaphores: OSFlagAccept(), OSFlagCreate(), OSFlagDel(), OSFlagPend(), OSFlagPost() and OSFlagQuery().

To enable µC/OS-II event flags services, you must set the configuration constants in OS_CFG.H. Specifically, table 9.1 shows which services are compiled based on the value of configuration constants found in OS_CFG.H. You should note that NONE of the event flag services are enabled when OS_FLAG_EN is set to 0. To enable the feature (i.e. service), simply set the configuration constant to 1. You will notice that OSFlagCreate(), OSFlagPend() and OSFlagPost() cannot be individually disabled like the other services because they are always needed when you enable µC/OS-II event flag management.

Table - Table 9.1 Event Flag configuration constants in OS_CFG.H
µC/OS-II Event Flag ServiceEnabled when set to 1 in OS_CFG.H
OSFlagAccept()OS_FLAG_ACCEPT_EN
OSFlagCreate()
OSFlagDel()OS_FLAG_DEL_EN
OSFlagPend()
OSFlagPost()
OSFlagQuery()OS_FLAG_QUERY_EN


Figure 9.1 shows a flow diagram to illustrate the relationship between tasks, ISRs, and a event flags. Note that the symbology used to represent an event flag group is a series of 8 bits even though the event flag group can contain 8, 16 or 32 bits (see OS_FLAGS in OS_CFG.H ). The hourglass represents a timeout that can be specified with the OSFlagPend() call.

As you can see from Figure 9.1, a task or an ISR can call OSFlagAccept(), OSFlagPost() or OSFlagQuery(). However, only tasks are allowed to call OSFlagCreate(), OSFlagDel() or OSFlagPend().

Figure - Figure 9.1, µC/OS-II Event Flag services


Event Flag Internals

A µC/OS-II's event flag group consist of three elements as shown in the OS_FLAG_GRP structure below.

Listing - Listing 9.1, Event Flag Group data structure
typedef struct {
    INT8U     OSFlagType;      (1)
    void     *OSFlagWaitList;  (2)
    OS_FLAGS  OSFlagFlags;     (3)
} OS_FLAG_GRP;

(1) OSFlagType is a variable which is used to make sure that you are pointing to an event flag group. This field is the first field of the structure because it allows µC/OS-II services to ‘validate’ the type of structure being pointed to. For example, if you were to pass a pointer to an event flag group to OSSemPend(), µC/OS-II would return an error code indicating that you are not passing the proper ‘object’ to the semaphore pend call. You should note that an ECB (Event Control Block) also has its first byte containing the type of OS object (i.e. semaphore, mutex, message mailbox or message queue).

(2) OSFlagWaitList contains a list of tasks waiting for events.

(3) OSFlagFlags is a series of flags (i.e. bits) that holds the current status of events. The number of bits used is decided at compile time and can either be 8, 16 or 32 depending on the data type you assign to OS_FLAGS in OS_CFG.H.


You should note that the wait list for event flags is different than the other wait lists in µC/OS-II. With event flags, the wait list is accomplished through a doubly linked list as shown in figure 9.2. Three data structures are involved. OS_FLAG_GRP (mentioned above), OS_TCB which is the task control block and OS_FLAG_NODE which is used to keep track of which bits the task is waiting for and what type of wait (AND or OR). As you can see, there are a lot of pointers involved.

Figure - Figure 9.2, Relationship between Event Flag Group, Event Flag Nodes and TCBs


An OS_FLAG_NODE is created when a task desires to wait on bits of an event flag group and the node is ‘destroyed’ when the event(s) occur. In other words, a node is created by OSFlagPend() as we will see shortly. Before we discuss this, let’s look at the OS_FLAG_NODE data structure.

Listing - Listing 9.2, Event Flag Group node data structure
typedef struct {
    void     *OSFlagNodeNext;             (1)
    void     *OSFlagNodePrev;    
    void     *OSFlagNodeTCB;              (2)
    void     *OSFlagNodeFlagGrp;          (3)
    OS_FLAGS  OSFlagNodeFlags;            (4)
    INT8U     OSFlagNodeWaitType;         (5)
} OS_FLAG_NODE;

(1) The OSFlagNodeNext and OSFlagNodePrev are used to maintain a doubly linked list of OS_FLAG_NODEs. The doubly linked list allows us to easily insert and especially remove nodes from the wait list.

(2) OSFlagNodeTCB is used to point to the TCB of the task waiting on flags belonging to the event flag group. In other words, this pointer allows us to know which tasks is waiting for the specified flags.

(3) OSFlagNodeFlagGrp allows a link back to the event flag group. This pointer is used when removing the node from the doubly linked list and is needed by OSTaskDel() when the pended task needs to be deleted.

(4) The OSFlagNodeFlags contains the bit-pattern of the flags that the task is waiting for. For example, your task might have performed an OSFlagPend() and specified that the task wants to wait for bits 0, 4, 6 and 7 (bit 0 is the rightmost bit). In this case, OSFlagNodeFlags would contain 0xD1. Depending on the size of the data type OS_FLAGS, OSFlagNodeFlags is either 8, 16 or 32 bits. OS_FLAGS is specified in your application configuration file (i.e., OS_CFG.H). Because µC/OS-II and the ports are provided in source form, you can easily change the number of bits in an event flag group to satisfy your requirements for a specific application or product. The reason you would limit the number of bits to 8 is to reduce both RAM and ROM for your application. However, for maximum portability of your applications, you should set OS_FLAGS to an INT32U data type.

(5) The last member of the OS_FLAG_NODE data structure is OSFlagNodeWaitType which determines whether the task is waiting for ALL (AND wait) the bits in the event flag group that matches OSFlagNodeFlags or, ANY (OR wait) of the bits in the event flag group that matches OSFlagNodeFlags. OSFlagNodeWaitType can be set to:

OS_FLAG_WAIT_CLR_ALL      
OS_FLAG_WAIT_CLR_AND      
OS_FLAG_WAIT_CLR_ANY      
OS_FLAG_WAIT_CLR_OR 
OS_FLAG_WAIT_SET_ALL 
OS_FLAG_WAIT_SET_AND      
OS_FLAG_WAIT_SET_ANY      
OS_FLAG_WAIT_SET_OR


You should note that AND and ALL means the same thing and either one can be used. I prefer to use OS_FLAG_WAIT_???_ALL because it’s more obvious but you are certainly welcomed to use OS_FLAG_WAIT_???_AND. Similarly, OR or ANY means the same thing and either one can be used. Again, I prefer to use OS_FLAG_WAIT_???_ANY because it’s more obvious but again, you can use OS_FLAG_WAIT_???_OR. The other thing to notice is that you can wait for either bits to be SET or CLEARED.

Creating an Event Flag Group, OSFlagCreate()

The code to create an event flag group is shown in listing 9.3.

Listing - Listing 9.3, Creating an Event Flag Group
OS_FLAG_GRP  *OSFlagCreate (OS_FLAGS flags, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3                         
    OS_CPU_SR    cpu_sr;
#endif    
    OS_FLAG_GRP *pgrp;


    if (OSIntNesting > 0) {                         (1)
        *err = OS_ERR_CREATE_ISR;                   
        return ((OS_FLAG_GRP *)0);               
    }
    OS_ENTER_CRITICAL();
    pgrp = OSFlagFreeList;                          (2)
    if (pgrp != (OS_FLAG_GRP *)0) {                 (3)
                                                    (4)
        OSFlagFreeList       = (OS_FLAG_GRP *)OSFlagFreeList->OSFlagWaitList;
        pgrp->OSFlagType     = OS_EVENT_TYPE_FLAG;  (5)
        pgrp->OSFlagFlags    = flags;               (6)
        pgrp->OSFlagWaitList = (void *)0;           (7)
        OS_EXIT_CRITICAL();
        *err                 = OS_NO_ERR;
    } else {
        OS_EXIT_CRITICAL();
        *err                 = OS_FLAG_GRP_DEPLETED;
    }
    return (pgrp);                                  (8)
}

(1) OSFlagCreate() starts by making sure it’s not called from an ISR because that’s not allowed.

(2) OSFlagCreate() then attempts to get a free Event Flag Group (i.e., an OS_FLAG_GRP) from the free list.

(3) An non-NULL pointer indicates that an event flag group is available.

(4) Once a group is allocated, the free list pointer is adjusted. Note that the number of Event Flag Groups that you can create is determined by the #define constant OS_MAX_FLAGS which is defined in OS_CFG.H in your application.

(5) OSFlagCreate() then fills in the fields in the event flag group. OS_EVENT_TYPE_FLAG indicates that this control block is an event flag group. Because this is the first field in the data structure, it’s at offset zero. In µC/OS-II, the first byte of an event flag group or an event control block used for semaphores, mailboxes, queues and mutexes indicates the type of kernel object. This allows us to check that we are pointing to the proper object.

(6) OSFlagCreate() then stores the initial value of the event flags into the event flag group. Typically, you would initialize the flags to all 0s but, if you are checking for CLEARED bits then, you could initialize the flags to all 1s.

(7) Because we are creating the group, there are no tasks waiting on the group and thus, the wait list pointer is initialized to NULL.

(8) The pointer to the created event flag group is returned. If there were no more groups available, OSFlagCreate() would return a NULL pointer.

Figure - Figure 9.3 Event Flag group just before OSFlagCreate() returns


Deleting an Event Flag Group, OSFlagDel()

The code to delete an event flag group is shown in listing 9.4.

This is a function you should use with caution because multiple tasks could attempt to access a deleted event flag group. You should always use this function with great care. Generally speaking, before you would delete an event flag group, you would first delete all the tasks that access the event flag group.

Listing - Listing 9.4, Deleting an Event Flag Group
OS_FLAG_GRP  *OSFlagDel (OS_FLAG_GRP *pgrp, INT8U opt, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3                                
    OS_CPU_SR     cpu_sr;
#endif    
    BOOLEAN       tasks_waiting;
    OS_FLAG_NODE *pnode;


    if (OSIntNesting > 0) {                                 (1)
        *err = OS_ERR_DEL_ISR;                             
        return (pgrp);
    }
#if OS_ARG_CHK_EN > 0
    if (pgrp == (OS_FLAG_GRP *)0) {                         (2)
        *err = OS_FLAG_INVALID_PGRP;
        return (pgrp);
    }
    if (pgrp->OSFlagType != OS_EVENT_TYPE_FLAG) {           (3)
        *err = OS_ERR_EVENT_TYPE;
        return (pgrp);
    }
#endif
    OS_ENTER_CRITICAL();
    if (pgrp->OSFlagWaitList != (void *)0) {                (4)
        tasks_waiting = TRUE;                              
    } else {
        tasks_waiting = FALSE;                             
    }
    switch (opt) {
        case OS_DEL_NO_PEND:                                (5)
             if (tasks_waiting == FALSE) {
                 pgrp->OSFlagType     = OS_EVENT_TYPE_UNUSED;
                 pgrp->OSFlagWaitList = (void *)OSFlagFreeList;   (6)
                 OSFlagFreeList       = pgrp;
                 OS_EXIT_CRITICAL();
                 *err                 = OS_NO_ERR;
                 return ((OS_FLAG_GRP *)0);                 (7) 
             } else {
                 OS_EXIT_CRITICAL();
                 *err                 = OS_ERR_TASK_WAITING;
                 return (pgrp);
             }

        case OS_DEL_ALWAYS:                                 (8) 
             pnode = pgrp->OSFlagWaitList;
             while (pnode != (OS_FLAG_NODE *)0) {           (9) 
                 OS_FlagTaskRdy(pnode, (OS_FLAGS)0);
                 pnode = pnode->OSFlagNodeNext;
             }
             pgrp->OSFlagType     = OS_EVENT_TYPE_UNUSED;
             pgrp->OSFlagWaitList = (void *)OSFlagFreeList; (10)
             OSFlagFreeList       = pgrp;
             OS_EXIT_CRITICAL();
             if (tasks_waiting == TRUE) {                   (11)
                 OS_Sched();                               
             }
             *err = OS_NO_ERR;
             return ((OS_FLAG_GRP *)0);                     (12)

        default:
             OS_EXIT_CRITICAL();
             *err = OS_ERR_INVALID_OPT;
             return (pgrp);
    }
}

(1) OSFlagDel() starts by making sure that this function is not called from an ISR because that’s not allowed.

(2) & (3) We then validate the arguments passed to OSFlagDel() . First, we make sure that pgrp is not a NULL pointer and pgrp points to point to an event flag group. Note that this code is conditionally compiled and thus, if OS_ARG_CHK_EN is set to 0 then this code is NOT compiled. This is done to allow you to reduce the amount of code space needed by this module.

(4) OSFlagDel() then determines whether there are any tasks waiting on the event flag group and sets the local BOOLEAN variable tasks_waiting accordingly.

Based on the option (i.e. opt) passed in the call, OSFlagDel() will either delete the event flag group only if no tasks are pending on the event flag group (opt == OS_DEL_NO_PEND) or, delete the event flag group even if tasks are waiting (opt == OS_DEL_ALWAYS).

(5) & (6) When opt is set to OS_DEL_NO_PEND and there is no task waiting on the event flag group, OSFlagDel() marks the group as unused and the event flag group is returned to the free list of groups. This will allow another event flag group to be created by reusing this event flag group.

(7) You will note that OSFlagDel() returns a NULL pointer since, at this point, the event flag group should no longer be accessed through the original pointer.

(8) & (9) When opt is set to OS_DEL_ALWAYS then all tasks waiting on the event flag group will be readied. Each task will think the event(s) that the task was waiting for occurred. We will discuss OS_FlagTaskRdy() when we look at the code for OSFlagPost() .

(10) Once all pending tasks are readied, OSFlagDel() marks the event flag group as unused and the group is returned to the free list of groups.

(11) The scheduler is called only if there were tasks waiting on the event flag group.

(12) You will note that OSFlagDel() returns a NULL pointer since, at this point, the event flag group should no longer be accessed through the original pointer.


Waiting for event(s) of an Event Flag Group, OSFlagPend()

The code to wait for event(s) of an event flag group is shown in listing 9.5.

Listing - Listing 9.5, Waiting for event(s) of an event flag group
OS_FLAGS  OSFlagPend (OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U wait_type, INT16U timeout, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3                                
    OS_CPU_SR     cpu_sr;
#endif    
    OS_FLAG_NODE  node;
    OS_FLAGS      flags_cur;
    OS_FLAGS      flags_rdy;
    BOOLEAN       consume;
 
 
    if (OSIntNesting > 0) {                                (1)
        *err = OS_ERR_PEND_ISR;                            
        return ((OS_FLAGS)0);
    }
#if OS_ARG_CHK_EN > 0
    if (pgrp == (OS_FLAG_GRP *)0) {                        (2)
        *err = OS_FLAG_INVALID_PGRP;
        return ((OS_FLAGS)0);
    }
    if (pgrp->OSFlagType != OS_EVENT_TYPE_FLAG) {          (3)
        *err = OS_ERR_EVENT_TYPE;
        return ((OS_FLAGS)0);
    }
#endif
    if (wait_type & OS_FLAG_CONSUME) {                     (4)
        wait_type &= ~OS_FLAG_CONSUME;
        consume    = TRUE;
    } else {
        consume    = FALSE;
    }
    OS_ENTER_CRITICAL();
    switch (wait_type) {                                   (5)
        case OS_FLAG_WAIT_SET_ALL:                         
             flags_rdy = pgrp->OSFlagFlags & flags;        (6)
             if (flags_rdy == flags) {                     (7)
                 if (consume == TRUE) {                    (8)
                     pgrp->OSFlagFlags &= ~flags_rdy;      (9)
                 }
                 flags_cur = pgrp->OSFlagFlags;            (10)
                 OS_EXIT_CRITICAL();                       
                 *err      = OS_NO_ERR;
                 return (flags_cur);                       (11)
             } else {                                      (12)
                 OS_FlagBlock(pgrp, &node, flags, wait_type, timeout); 
                 OS_EXIT_CRITICAL();
             }
             break;
 
        case OS_FLAG_WAIT_SET_ANY:
             flags_rdy = pgrp->OSFlagFlags & flags;        (13) 
             if (flags_rdy != (OS_FLAGS)0) {               (14) 
                 if (consume == TRUE) {                    (15) 
                     pgrp->OSFlagFlags &= ~flags_rdy;      (16) 
                 }
                 flags_cur = pgrp->OSFlagFlags;            (17) 
                 OS_EXIT_CRITICAL();                       
                 *err      = OS_NO_ERR;
                 return (flags_cur);                       (18) 
             } else {                                      (19) 
                 OS_FlagBlock(pgrp, &node, flags, wait_type, timeout); 
                 OS_EXIT_CRITICAL();
             }
             break;
 
#if OS_FLAG_WAIT_CLR_EN > 0
        case OS_FLAG_WAIT_CLR_ALL:                         
             flags_rdy = ~pgrp->OSFlagFlags & flags;       
             if (flags_rdy == flags) {                     
                 if (consume == TRUE) {                    
                     pgrp->OSFlagFlags |= flags_rdy;       
                 }
                 flags_cur = pgrp->OSFlagFlags;            
                 OS_EXIT_CRITICAL();                       
                 *err      = OS_NO_ERR;
                 return (flags_cur);
             } else {                                      
                 OS_FlagBlock(pgrp, &node, flags, wait_type, timeout); 
                 OS_EXIT_CRITICAL();
             }
             break;
 
        case OS_FLAG_WAIT_CLR_ANY:
             flags_rdy = ~pgrp->OSFlagFlags & flags;       
             if (flags_rdy != (OS_FLAGS)0) {               
                 if (consume == TRUE) {                    
                     pgrp->OSFlagFlags |= flags_rdy;       
                 }
                 flags_cur = pgrp->OSFlagFlags;            
                 OS_EXIT_CRITICAL();                       
                 *err      = OS_NO_ERR;
                 return (flags_cur);
             } else {                                      
                 OS_FlagBlock(pgrp, &node, flags, wait_type, timeout); 
                 OS_EXIT_CRITICAL();
             }
             break;
#endif
 
        default:
             OS_EXIT_CRITICAL();
             flags_cur = (OS_FLAGS)0;
             *err      = OS_FLAG_ERR_WAIT_TYPE;
             return (flags_cur);
    }
    OS_Sched();                                            (20)
    OS_ENTER_CRITICAL();
    if (OSTCBCur->OSTCBStat & OS_STAT_FLAG) {              (21)
        OS_FlagUnlink(&node);                              (22)
        OSTCBCur->OSTCBStat = OS_STAT_RDY;                 
        OS_EXIT_CRITICAL();
        flags_cur           = (OS_FLAGS)0;
        *err                = OS_TIMEOUT;                  
    } else {
        if (consume == TRUE) {                             (23)
            switch (wait_type) {
                case OS_FLAG_WAIT_SET_ALL:
                case OS_FLAG_WAIT_SET_ANY:                 (24)
                     pgrp->OSFlagFlags &= ~OSTCBCur->OSTCBFlagsRdy;
                     break;
                     
                case OS_FLAG_WAIT_CLR_ALL:
                case OS_FLAG_WAIT_CLR_ANY:                 
                     pgrp->OSFlagFlags |= OSTCBCur->OSTCBFlagsRdy;
                     break;
            }
        }
        flags_cur = pgrp->OSFlagFlags;                     (25)
        OS_EXIT_CRITICAL();
        *err      = OS_NO_ERR;                             
    }
    return (flags_cur);
}

(1) Like all µC/OS-II pend calls, OSFlagPend() cannot be called from an ISR and thus, OSFlagPend() checks for this condition first.

(2) & (3) Assuming that the configuration constant OS_ARG_CHK_EN is set to 1, OSFlagPend() makes sure that the ‘handle’ pgrp is not a NULL pointer and that pgrp points to an event flag group that should have been created by OSFlagCreate() .

OSFlagPend() allows you to specify whether you will SET or CLEAR flags once they satisfy the condition you are waiting for. This is accomplished by ADDing (or ORing) OS_FLAG_CONSUME to the wait_type argument during the call to OSFlagPend(). For example, if you want to wait for BIT0 to be SET in the event flag group and if BIT0 is in fact SET, it will be CLEARED by OSFlagPend() if you ADD OS_FLAG_CONSUME to the type of wait desired as shown below:

OSFlagPend(OSFlagMyGrp, 
	(OS_FLAGS)0x01, 
	FLAG_WAIT_SET_ANY + OS_FLAG_CONSUME,
	0,
	&err);

(4) Because the ‘consumption’ of the flag(s) is done later in the code, OSFlagPend() saves the ‘consume’ option in the BOOLEAN variable called consume.

(5) OSFlagPend() then executes code based on the wait type specified in the function called. There are four choices:

  1. wait for ALL bits specified to be SET in the event flag group
  2. wait for ANY bit specified to be SET in the event flag group
  3. wait for ALL bits specified to be CLEARED in the event flag group
  4. wait for ANY bit specified to be CLEARED in the event flag group

The last two choices are identical to the first two choices except that OSFlagPend() ‘looks’ for the bits specified to be CLEARED (i.e. 0) instead for them being SET (i.e. 1). For this reason, I will only discuss the first two choices. In fact, in order to conserve ROM, you may not need to look for bits to be cleared and thus, you can ‘compile-out’ all the corresponding code out by setting OS_FLAG_WAIT_CLR_EN to 0 in OS_CFG.H.

Wait for ALL of the specified bits to be SET:

(6) When wait_type is set to either OS_FLAG_WAIT_SET_ALL or OS_FLAG_WAIT_SET_AND, OSFlagPend() will ‘extract’ the desired bits in the event flag group which are specified in the flags argument.

(7) If all the bits extracted matches the bits that you specified in the flags argument then, the event flags that the task wants are all set and thus, the PEND call would return to the caller.

(8) & (9) Before we return, we need to determine whether we need to ‘consume’ the flags and if so, we will CLEAR all the flags that satisfied the condition.

(10) & (11) The new value of the event flag group is obtained and returned to the caller.

(12) If ALL the desired bits in the event flag group were not SET then the calling task will block (i.e. suspend) until ALL the bits are either SET or a timeout occurs. Instead of repeating code for all four types of wait, I created a function (OS_FlagBlock()) to handle the details of blocking the calling task (described later).

Wait for ANY of the specified bits to be SET:

(13) When wait_type is set to either OS_FLAG_WAIT_SET_ANY or OS_FLAG_WAIT_SET_OR, OSFlagPend() will ‘extract’ the desired bits in the event flag group which are specified in the flags argument.

(14) If any of the bits extracted matches the bits that you specified in the flags argument then the PEND call will return to the caller.

(15) & (16) Before we return, we need to determine whether we need to ‘consume’ the flag(s) and if so, we need to CLEAR all the flag(s) that satisfied the condition.

(17) & (18) The new value of the event flag group is obtained and returned to the caller.

(19) If NONE of the desired bits in the event flag group were not SET then the calling task will block (i.e. suspend) until ANY of the bits is either SET or a timeout occurs.


As mentioned above, if the desired bits and conditions of a PEND call are not satisfied the calling task is suspended until either the event or a timeout occurs. The task is suspended by OS_FlagBlock() (see Listing 9.6) which adds the calling task to the wait list of the event flag group. The process is shown in Figure 9.4.

Figure - Figure 9.4, Adding the current task to the wait list of the Event Flag Group


Listing - Listing 9.6, Adding a task to the event flag group wait list
static void OS_FlagBlock (OS_FLAG_GRP *pgrp, OS_FLAG_NODE *pnode, OS_FLAGS flags, INT8U wait_type, INT16U timeout)
{
    OS_FLAG_NODE  *pnode_next;
 
 
    OSTCBCur->OSTCBStat      |= OS_STAT_FLAG;         (1)
    OSTCBCur->OSTCBDly        = timeout;              
#if OS_TASK_DEL_EN > 0
    OSTCBCur->OSTCBFlagNode   = pnode;                (2)
#endif
    pnode->OSFlagNodeFlags    = flags;                (3)
    pnode->OSFlagNodeWaitType = wait_type;            
    pnode->OSFlagNodeTCB      = (void *)OSTCBCur;     (4)
    pnode->OSFlagNodeNext     = pgrp->OSFlagWaitList; (5)
    pnode->OSFlagNodePrev     = (void *)0;            (6)
    pnode->OSFlagNodeFlagGrp  = (void *)pgrp;         (7)
    pnode_next                = pgrp->OSFlagWaitList; 
    if (pnode_next != (void *)0) {                    
        pnode_next->OSFlagNodePrev = pnode;           (8)
    } 
    pgrp->OSFlagWaitList = (void *)pnode;             (9) 
                                                      (10)
    if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) {
        OSRdyGrp &= ~OSTCBCur->OSTCBBitY;
    }
}

The notes below apply both and simultaneously to Listing 9.6 and Figure 9.4. When reading each numbered note, refer to both the listing and the figure.

(1) OS_FlagBlock() starts by setting the appropriate fields in the task control block. You should note that an OS_FLAG_NODE is allocated on the stack of the calling task (see OSFlagPend() , L9.5). This means that we don’t need to keep a separate ‘free list’ of OS_FLAG_NODE since these data structures can simply be allocated on the stack of the calling task. That being said, the calling task must have sufficient stack space to allocate this structure on its stack.

(2) We then link the OS_FLAG_NODE to the TCB, but only if OS_TASK_DEL_EN is set to 1. This link allows OSTaskDel() to remove the task being suspended from the wait list should another task decide to delete this task.

(3) Next, OS_FlagBlock() saves the flags that the task is waiting for as well as the wait type in the OS_FLAG_NODE structure.

(4) We then link the TCB to the OS_FLAG_NODE .

(5) The OS_FLAG_NODE is then linked to the other OS_FLAG_NODEs in the wait list.

(6) You should note that the OS_FLAG_NODE is simply inserted at the beginning of the doubly-linked list for simplicity sake.

(7) We then link the event flag group to the OS_FLAG_NODE . This is again done to allow us to delete the task that is being added to the wait list of the event flag group.

(8) OS_FlagBlock() then links the previous ‘first’ node in the wait list to the new OS_FLAG_NODE .

(9) & (10) Finally, the pointer of the beginning of the wait list is updated to point to the new OS_FLAG_NODE and, the calling task is made NOT ready-to-run.

You should note that interrupts are disabled during the process of blocking the calling task.

(20) When OS_FlagBlock() returns, the scheduler is called because, of course, the calling task is no longer able to run since the event(s) it was looking for did not occur.

(21) When µC/OS-II resumes the calling task, OSFlagPend() checks HOW the task was readied. If the status field in the TCB still indicates that the task is still waiting for event flags to be either set or cleared then, the task MUST have been readied because of a timeout.

(22) In this case, the OS_FLAG_NODE is removed from the wait list by calling OS_FlagUnlink() and, an error code is returned to the caller indicating the outcome of the call. The code for OS_FlagUnlink() is shown in Listing 9.7 and should be quite obvious since we are simply removing a node from a doubly linked list. The code provided on the CD-ROM contains comments so you can easily follow what’s going on.

(23) & (24) If the calling task is NOT resumed because of a timeout then, it MUST have been resumed because the event flags that it was waiting for have been either set or cleared. In this case, we determine whether the calling task wanted to consume the event flags. If this is the case, the appropriate flags are either set or cleared based on the wait type.

(25) Finally, OSFlagPend() obtains the current value of the event flags in the group in order to return this information to the caller.


Setting or Clearing event(s) in an Event Flag Group, OSFlagPost()

The code to either setting or clearing bits in an event flag group is done by calling OSFlagPost() and the code for this function is shown in listing 9.7.

Listing - Listing 9.7, Setting or Clearing bits (i.e., events) in an Event Flag Group
OS_FLAGS  OSFlagPost (OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U opt, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3                          
    OS_CPU_SR     cpu_sr;
#endif    
    OS_FLAG_NODE *pnode;
    BOOLEAN       sched;
    OS_FLAGS      flags_cur;
    OS_FLAGS      flags_rdy;


#if OS_ARG_CHK_EN > 0
    if (pgrp == (OS_FLAG_GRP *)0) {                  (1)
        *err = OS_FLAG_INVALID_PGRP;
        return ((OS_FLAGS)0);
    }
    if (pgrp->OSFlagType != OS_EVENT_TYPE_FLAG) {    (2)         
        *err = OS_ERR_EVENT_TYPE;
        return ((OS_FLAGS)0);
    }
#endif
    OS_ENTER_CRITICAL();
    switch (opt) {                                   (3)
        case OS_FLAG_CLR:
             pgrp->OSFlagFlags &= ~flags;            (4)
             break;
             
        case OS_FLAG_SET:
             pgrp->OSFlagFlags |=  flags;            (5)
             break;
          
        default:
             OS_EXIT_CRITICAL();                     
             *err = OS_FLAG_INVALID_OPT;
             return ((OS_FLAGS)0);
    }
    sched = FALSE;                                   (6)
    pnode = pgrp->OSFlagWaitList;                
    while (pnode != (OS_FLAG_NODE *)0) {             (7)
        switch (pnode->OSFlagNodeWaitType) {
            case OS_FLAG_WAIT_SET_ALL:               (8)
                 flags_rdy = pgrp->OSFlagFlags & pnode->OSFlagNodeFlags;
                 if (flags_rdy == pnode->OSFlagNodeFlags) {          (9) 
                     if (OS_FlagTaskRdy(pnode, flags_rdy) == TRUE) { (10)
                         sched = TRUE;                               (11)
                     }
                 }
                 break;

            case OS_FLAG_WAIT_SET_ANY:               
                 flags_rdy = pgrp->OSFlagFlags & pnode->OSFlagNodeFlags;
                 if (flags_rdy != (OS_FLAGS)0) {    
                     if (OS_FlagTaskRdy(pnode, flags_rdy) == TRUE) {
                         sched = TRUE;                              
                     }
                 }
                 break;

#if OS_FLAG_WAIT_CLR_EN > 0
            case OS_FLAG_WAIT_CLR_ALL:               
                 flags_rdy = ~pgrp->OSFlagFlags & pnode->OSFlagNodeFlags;
                 if (flags_rdy == pnode->OSFlagNodeFlags) {     
                     if (OS_FlagTaskRdy(pnode, flags_rdy) == TRUE) {
                         sched = TRUE;                              
                     }
                 }
                 break;

            case OS_FLAG_WAIT_CLR_ANY:               
                 flags_rdy = ~pgrp->OSFlagFlags & pnode->OSFlagNodeFlags;
                 if (flags_rdy != (OS_FLAGS)0) {    
                     if (OS_FlagTaskRdy(pnode, flags_rdy) == TRUE) {
                         sched = TRUE;                              
                     }
                 }
                 break;
#endif                 
        }
        pnode = pnode->OSFlagNodeNext;               (12)
    }
    OS_EXIT_CRITICAL();
    if (sched == TRUE) {                             (13)
        OS_Sched();                                  (14)
    }
    OS_ENTER_CRITICAL();
    flags_cur = pgrp->OSFlagFlags;                   (15)
    OS_EXIT_CRITICAL();
    *err      = OS_NO_ERR;
    return (flags_cur);                              (16)
}

(1) & (2) Assuming that the configuration constant OS_ARG_CHK_EN is set to 1, OSFlagPost() makes sure that the ‘handle’ pgrp is not a NULL pointer and that pgrp points to an event flag group that should have been created by OSFlagCreate() .

(3) & (4) & (5) Depending on the option you specified in the opt argument of OSFlagPost() , the flags specified in the flags argument will either be SET ( when opt == OS_FLAG_SET ) or CLEARED ( when opt == OS_FLAG_CLR ). If opt is not one of the two choices, the call is aborted and an error code is returned to the caller.

(6) We next start by assuming that POSTing doesn’t make a higher priority task ready-to-run and thus, we set the BOOLEAN variable sched to FALSE. If this assumption is not verified because we will make a higher-priority-task ready-to-run then sched will simply be set to TRUE.

(7) We then go through the wait list to see if any task is waiting on one or more events.

(15) & (16) If the wait list is empty, we simply get the current state of the event flag bits and return this information to the caller.

(8) If there is one or more tasks waiting on the event flag group, we go through the list of OS_FLAG_NODEs to see if the new event flag bits now satisfies any of the waiting task conditions. Each one of the tasks can be waiting for one of four conditions:

  1. ALL of the bits specified in the PEND call to be set.
  2. ANY of the bits specified in the PEND call to be set.
  3. ALL of the bits specified in the PEND call to be cleared.
  4. ANY of the bits specified in the PEND call to be cleared.

(9) & (10) Note that the last two condition can be ‘compiled-out’ by setting OS_FLAG_WAIT_CLR_EN to 0 (see OS_CFG.H ). You would do this if you didn’t need the functionality of waiting for cleared bits and/or you need to reduce the amount of ROM in your product. When a waiting task’s condition is satisfied, the waiting task is readied by calling OS_FlagTaskRdy() (see Listing 9.9). I will only discuss the first wait condition because the other cases are similar enough.

(11) Because a task is made ready-to-run, the scheduler will have to be called. However, we will only do this after going through all waiting tasks because, there is no need to call the scheduler every time a task is made ready-to-run.

(12) We proceed to the next node by following the linked list.

You should note that interrupts are disabled while we are going through the wait list. The implication is that OSFlagPost() can potentially disable interrupts for a long period of time, especially if multiple tasks are made ready-to-run. However, execution time is bounded and still deterministic.

(13) & (14) When we have gone through the whole waiting list, we examine the sched flag to see if we need to run the scheduler and thus possibly perform a context switch to a higher priority task that just received the event flag(s) it was waiting for.

(15) & (16) OSFlagPost() returns the current state of the event flag group.


As previously mentioned, the code in listing 9.8 is executed to make a task ready-to-run.

Listing - Listing 9.8, Make a waiting Task Ready-to-Run
static  BOOLEAN  OS_FlagTaskRdy (OS_FLAG_NODE *pnode, OS_FLAGS flags_rdy)
{
    OS_TCB   *ptcb;
    BOOLEAN   sched;
          
                                                        
    ptcb                = (OS_TCB *)pnode->OSFlagNodeTCB;
    ptcb->OSTCBDly      = 0;                             
    ptcb->OSTCBFlagsRdy = flags_rdy;                     
    ptcb->OSTCBStat    &= ~OS_STAT_FLAG;
    if (ptcb->OSTCBStat == OS_STAT_RDY) {                  (1)                
        OSRdyGrp               |= ptcb->OSTCBBitY;
        OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
        sched                   = TRUE;                    (2)
    } else {
        sched                   = FALSE;                   (3)
    }
    OS_FlagUnlink(pnode);                                  (4)
    return (sched);
}

(1) & (2) & (3) Note that even though this function ‘removes’ the waiting task from the event flag group wait list, the task could still be suspended and may not be ready-to-run. This is why the BOOLEAN variable sched is used and returned to the caller.

(4) This is a standard procedure in µC/OS-II (see section 6.02, Making a Task Ready) except for the fact that the OS_FLAG_NODE needs to be unlinked from the waiting list of the event flag group as well as the task’s OS_TCB.


The unlinking of the OS_FLAG_NODE is performed by the function OS_FlagUnlink() as shown in listing 9.9. Figure 9.5 shows the four possible locations of an OS_FLAG_NODE which needs to be removed from the event flag wait list. This is a classical doubly linked list removal problem except that there are also other pointers to adjust.

Listing - Listing 9.9, Unlinking a Flag
void  OS_FlagUnlink (OS_FLAG_NODE *pnode)
{
#if OS_TASK_DEL_EN > 0
    OS_TCB       *ptcb;
#endif
    OS_FLAG_GRP  *pgrp;
    OS_FLAG_NODE *pnode_prev;
    OS_FLAG_NODE *pnode_next;
    
    
    pnode_prev = pnode->OSFlagNodePrev;                                (1)
    pnode_next = pnode->OSFlagNodeNext;                                (2)
    if (pnode_prev == (OS_FLAG_NODE *)0) {                             (3)
        pgrp                 = pnode->OSFlagNodeFlagGrp;               (4)
        pgrp->OSFlagWaitList = (void *)pnode_next;                     (5)
        if (pnode_next != (OS_FLAG_NODE *)0) {                         (6)
            pnode_next->OSFlagNodePrev = (OS_FLAG_NODE *)0;            (7)
        }
    } else {                                                              
        pnode_prev->OSFlagNodeNext = pnode_next;                       (8)
        if (pnode_next != (OS_FLAG_NODE *)0) {                         (9)
            pnode_next->OSFlagNodePrev = pnode_prev;                  (10)
        }
    }
#if OS_TASK_DEL_EN > 0
    ptcb                = (OS_TCB *)pnode->OSFlagNodeTCB;             (11)
    ptcb->OSTCBFlagNode = (void *)0;                                  (12)
#endif
}

(1) & (2) OS_FlagUnlink() starts off by setting up two local pointers: pnode_next and pnode_prev which point to the next and previous OS_FLAG_NODE in the wait list, respectively.

(3) & F9.5(A, B) The previous pointer is examined to see if we have the first two cases of figure 9.6 (an OS_FLAG_NODE which is the first node in the wait list).

(4) & (5) If the OS_FLAG_NODE is the first node, the wait list pointer of the event flag group will need to point to the node immediately after the OS_FLAG_NODE to remove.

(6) & (7)

F9.5(B) If there is an OS_FLAG_NODE to the right of the node to delete then, that node will now point to where the previous pointer of the node to delete is pointing to which is of course a NULL pointer since the node to remove was the first one.

(8) & F9.5(C, D) Because the node to delete is not the first node in the wait list, the node to the left of the node to delete must now point to the node to the right of the node to delete.

(9) & (10) If there is a node to the right of the node to delete, the previous pointer of that node must now point to the previous node of the node to delete.

(11) & (12) In all cases, the .OSTCBFlagNode field must now point to NULL because the node to be deleted will no longer exist once it’s deallocated from the task that created the node in the first place.


Figure - Figure 9.5, Removing an OS_FLAG_NODE from the wait list

Figures 9.6 through 9.9 shows the before and after for each case mentioned. The number in parenthesis corresponds to the number in parenthesis of listing 9.9. You will notice that OS_FlagUnlink() updates at most three pointers. Because the node being removed exist on the stack of the task that is being readied (it was allocated by OSFlagPend() ), that node will automatically disappear! As far as the task that pended on the event flag is concerned, it doesn’t even know about the OS_FLAG_NODE .

Figure - Figure 9.6, Removing an OS_FLAG_NODE from the wait list, Case A



Figure - Figure 9.7, Removing an OS_FLAG_NODE from the wait list, Case B


Figure - Figure 9.8, Removing an OS_FLAG_NODE from the wait list, Case C

Figure - Figure 9.9, Removing an OS_FLAG_NODE from the wait list, Case D

Looking for event(s) of an Event Flag Group, OSFlagAccept()

The code to look for desired event(s) from an event flag group without waiting is shown in listing 9.10. This function is quite similar to OSFlagPend() except that the caller will not be suspended (i.e. blocked) should the event(s) not be present. The only two things that are different are:

  1. OSFlagAccept() can be called from an ISR unlike some of the other calls.
  2. If the conditions are NOT met, the call does not block and simply returns an error code that the caller should check.

Listing - Listing 9.10, Looking for Event Flags without waiting
OS_FLAGS  OSFlagAccept (OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U wait_type, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3      
    OS_CPU_SR     cpu_sr;
#endif    
    OS_FLAGS      flags_cur;
    OS_FLAGS      flags_rdy;
    BOOLEAN consume;
#if OS_ARG_CHK_EN > 0
    if (pgrp == (OS_FLAG_GRP *)0) {    
  *err = OS_FLAG_INVALID_PGRP;
  return ((OS_FLAGS)0);
    }
    if (pgrp->OSFlagType != OS_EVENT_TYPE_FLAG) {   
  *err = OS_ERR_EVENT_TYPE;
  return ((OS_FLAGS)0);
    }
#endif
    if (wait_type & OS_FLAG_CONSUME) { 
  wait_type &= ~OS_FLAG_CONSUME;
  consume    = TRUE;
    } else {
  consume    = FALSE;
    }
    OS_ENTER_CRITICAL();
    switch (wait_type) {
  case OS_FLAG_WAIT_SET_ALL:     
      flags_rdy = pgrp->OSFlagFlags & flags; 
       if (flags_rdy == flags) { 
           if (consume == TRUE) {      
       pgrp->OSFlagFlags &= ~flags_rdy;      
   }
    flags_cur = pgrp->OSFlagFlags;     
           OS_EXIT_CRITICAL();   
           *err      = OS_NO_ERR;
      } else {
   flags_cur = pgrp->OSFlagFlags;
   OS_EXIT_CRITICAL();
   *err      = OS_FLAG_ERR_NOT_RDY;
      }
      break;
  case OS_FLAG_WAIT_SET_ANY:
      flags_rdy = pgrp->OSFlagFlags & flags; 
       if (flags_rdy != (OS_FLAGS)0) { 
     if (consume == TRUE) {      
       pgrp->OSFlagFlags &= ~flags_rdy;      
          }
    flags_cur = pgrp->OSFlagFlags;     
           OS_EXIT_CRITICAL();   
   *err      = OS_NO_ERR;
      } else {
   flags_cur = pgrp->OSFlagFlags;
   OS_EXIT_CRITICAL();
   *err      = OS_FLAG_ERR_NOT_RDY;
      }
      break;
#if OS_FLAG_WAIT_CLR_EN > 0
  case OS_FLAG_WAIT_CLR_ALL:     
      flags_rdy = ~pgrp->OSFlagFlags & flags; 
             if (flags_rdy == flags) { 
     if (consume == TRUE) {      
       pgrp->OSFlagFlags |= flags_rdy; 
   }
    flags_cur = pgrp->OSFlagFlags;     
           OS_EXIT_CRITICAL();   
   *err      = OS_NO_ERR;
      } else {
   flags_cur = pgrp->OSFlagFlags;
   OS_EXIT_CRITICAL();
   *err      = OS_FLAG_ERR_NOT_RDY;
      }
      break;
  case OS_FLAG_WAIT_CLR_ANY:
      flags_rdy = ~pgrp->OSFlagFlags & flags; 
       if (flags_rdy != (OS_FLAGS)0) { 
     if (consume == TRUE) {      
                     pgrp->OSFlagFlags |= flags_rdy; 
   }
    flags_cur = pgrp->OSFlagFlags;     
           OS_EXIT_CRITICAL();   
   *err      = OS_NO_ERR;
      } else {
   flags_cur = pgrp->OSFlagFlags;
   OS_EXIT_CRITICAL();
   *err      = OS_FLAG_ERR_NOT_RDY;
      }
      break;
#endif
  default:
      OS_EXIT_CRITICAL();
      flags_cur = (OS_FLAGS)0;
      *err      = OS_FLAG_ERR_WAIT_TYPE;
      break;
    }
    return (flags_cur);
}


Querying an Event Flag Group, OSFlagQuery()

OSFlagQuery() allows your code to get the current value of the event flag group. The code for this function is shown in listing 9.11.

Listing - Listing 9.11, Obtaining the current flags of an event flag group
OS_FLAGS  OSFlagQuery (OS_FLAG_GRP *pgrp, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3                
    OS_CPU_SR  cpu_sr;
#endif    
    OS_FLAGS   flags;
#if OS_ARG_CHK_EN > 0
    if (pgrp == (OS_FLAG_GRP *)0) { (1) 
  *err = OS_FLAG_INVALID_PGRP;
  return ((OS_FLAGS)0);
    }
    if (pgrp->OSFlagType != OS_EVENT_TYPE_FLAG) { (2) 
  *err = OS_ERR_EVENT_TYPE;
  return ((OS_FLAGS)0);
    }
#endif    
    OS_ENTER_CRITICAL();
    flags = pgrp->OSFlagFlags;      (3) 
    OS_EXIT_CRITICAL();
    *err = OS_NO_ERR;
    return (flags);                        (4) 
}

(1) & (2) As with all µC/OS-II calls, OSFlagQuery() performs argument checking if this feature is enabled when OS_ARG_CHK_EN is set to 1 in OS_CFG.H .

(3) & (4) If there are no errors, OSFlagQuery() obtains the current state of the event flags and returns this to the caller.


OSFlagQuery() is passed two arguments: pgrp contains a pointer to the event flag group which was returned by OSFlagCreate() when the event flag group is created and, err which is a pointer to an error code that will let the caller know whether the call was successful or not.