Event Flag Management

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 Service

Enabled 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.

 

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.