Event Flags
About Event Flags
Event flags are used when a task needs to synchronize with the occurrence of multiple events. The task can be synchronized when any of the events have occurred, which is called disjunctive synchronization (logical OR). A task can also be synchronized when all events have occurred, which is called conjunctive synchronization (logical AND). Disjunctive and conjunctive synchronization are shown in the figure below.
The application programmer can create an unlimited number of event flag groups (limited only by available RAM). Event flag services in μC/OS-III start with the OSFlag???()
prefix. The services available to the application programmer are described in µC-OS-III API Reference.
The code for event flag services is found in the file os_flag.c
, and is enabled at compile time by setting the configuration constant OS_CFG_FLAG_EN
to DEF_ENABLED
in os_cfg.h
.
(1) A μC/OS-III “event flag group” is a kernel object of type OS_FLAG_GRP
(see os.h
), and consists of a series of bits (8-, 16- or 32-bits, based on the data type OS_FLAGS
defined in os_type.h
). The event flag group also contains a list of tasks waiting for some (or all) of the bits to be set or clear – 1 or 0. An event flag group must be created before it can be used by tasks and ISRs. You need to create event flags prior to starting μC/OS-III, or by a startup task in the application code.
(2) Tasks or ISRs can post to event flags. In addition, only tasks can create, delete, and stop other task from pending on event flag groups.
(3) A task can wait (i.e., pend) on any number of bits in an event flag group (i.e., a subset of all the bits). As with all μC/OS-III pend calls, the calling task can specify a timeout value such that if the desired bits are not posted within a specified amount of time (in ticks), the pending task is resumed and informed about the timeout.
(4) The task can specify whether it wants to wait for “any” subset of bits (OR) to be set (or clear), or wait for “all” bits in a subset of bit (AND) to be set (or clear).
There are a number of operations to perform on event flags, as summarized in the table below.
Function | Name Operation |
---|---|
OSFlagCreate() | Create an event flag group |
OSFlagDel() | Delete an event flag group |
OSFlagPend() | Pend (i.e., wait) on an event flag group |
OSFlagPendAbort() | Abort waiting on an event flag group |
OSFlagPendGetFlagsRdy() | Get flags that caused task to become ready |
OSFlagPost() | Post flag(s) to an event flag group |
Event Flags Internals
The application programmer can create an unlimited number of event flag groups (limited only by available RAM). Event flag services in µC/OS-III start with OSFlag
and the services available to the application programmer are described in the topics below:
Event flag services are enabled at compile time by setting the configuration constant OS_CFG_FLAG_EN
to DEF_ENABLED
in os_cfg.h
.
An event flag group is a kernel object as defined by the OS_FLAG_GRP
data type, which is derived from the structure os_flag_grp
(see os.h
) as shown in the listing below.
The services provided by µC/OS-III to manage event flags are implemented in the file os_flag.c
.
typedef struct os_flag_grp OS_FLAG_GRP; (1) struct os_flag_grp { OS_OBJ_TYPE Type; (2) CPU_CHAR *NamePtr; (3) OS_PEND_LIST PendList; (4) OS_FLAGS Flags; (5) CPU_TS TS; (6) };
(1) In µC/OS-III, all structures are given a data type. In fact, all data types start with “OS_
” and are uppercase. When an event flag group is declared, you simply use OS_FLAG_GRP
as the data type of the variable used to declare the event flag group.
(2) The structure starts with a “Type
” field, which allows it to be recognized by µC/OS-III as an event flag group. In other words, other kernel objects will also have a “Type
” as the first member of the structure. If a function is passed a kernel object, µC/OS-III will be able to confirm that it is being passed the proper data type (assuming OS_CFG_OBJ_TYPE_CHK_EN
is set to 1
in os_cfg.h
). For example, if passing a message queue (OS_Q
) to an event flag service (for example OSFlagPend()
), µC/OS-III will be able to recognize that an invalid object was passed, and return an error code accordingly.
(3) Each kernel object can be given a name to make them easier to be recognized by debuggers or µC/Probe. This member is simply a pointer to an ASCII string, which is assumed to be NUL
terminated.
(4) Because it is possible for multiple tasks to be waiting (or pending) on an event flag group, the event flag group object contains a pend list as described in Pend Lists.
(5) An event flag group contains a series of flags (i.e., bits), and this member contains the current state of these flags. The flags can be implemented using either an 8-, 16- or 32-bit value depending on how the data type OS_FLAGS
is declared in os_type.h
.
(6) An event flag group contains a timestamp used to indicate the last time the event flag group was posted to. µC/OS-III assumes the presence of a free-running counter that allows users to make time measurements. When the event flag group is posted to, the free-running counter is read and the value is placed in this field, which is returned when OSFlagPend()
is called. This value allows an application to determine either when the post was performed, or how long it took for your code to obtain control of the CPU from the post. In the latter case, you can call OS_TS_GET()
to determine the current timestamp and compute the difference.
Even if the user understands the internals of the OS_FLAG_GRP
data type, application code should never access any of the fields in this data structure directly. Instead, you should always use the APIs provided with µC/OS-III.
Event flag groups must be created before they can be used by an application as shown in the listing below.
OS_FLAG_GRP MyEventFlagGrp; (1) void MyCode (void) { OS_ERR err; : OSFlagCreate(&MyEventFlagGrp, (2) "My Event Flag Group", (3) (OS_FLAGS)0, (4) &err); (5) /* Check 'err" */ : }
(1) The application must declare a variable of type OS_FLAG_GRP
. This variable will be referenced by other event flag services.
(2) You create an event flag group by calling OSFlagCreate()
and pass the address to the event flag group allocated in (1).
(3) You can assign an ASCII name to the event flag group, which can be used by debuggers or µC/Probe to easily identify this event flag group. µC/OS-III stores a pointer to the name so there is no practical limit to its size, except that the ASCII string needs to be NUL
terminated.
(4) You initialize the flags inside the event flag group to zero (0) unless the task and ISRs signal events with bits cleared instead of bits set. If using cleared bits, you should initialize all the bits to ones (1).
(5) OSFlagCreate()
returns an error code based on the outcome of the call. If all the arguments are valid, err
will contain OS_ERR_NONE
.
A task waits for one or more event flag bits either from an ISR or another task by calling OSFlagPend()
as shown in the listing below.
OS_FLAG_GRP MyEventFlagGrp; void MyTask (void *p_arg) { OS_ERR err; CPU_TS ts; OS_FLAGS which_flags; : while (DEF_ON) { : which_flags = OSFlagPend(&MyEventFlagGrp, /* (1) Pointer to event flag group */ (OS_FLAGS)0x0F, /* Which bits to wait on */ 10, /* Maximum time to wait */ OS_OPT_PEND_BLOCKING + OS_OPT_PEND_FLAG_SET_ANY, /* Option(s) */ &ts, /* Timestamp of when posted to */ &err); /* Pointer to Error returned */ /* Check "err" (2) */ : : } }
(1) When called, OSFlagPend()
starts by checking the arguments passed to this function to ensure they have valid values (assuming OS_CFG_ARG_CHK_EN
is set to 1
in os_cfg.h
). If the bits the task is waiting for are set (or cleared depending on the option), OSFlagPend()
returns and indicate which flags satisfied the condition. This is the outcome that the caller expects.
If the event flag group does not contain the flags that the caller is looking for, the calling task might need to wait for the desired flags to be set (or cleared). If you specify OS_OPT_PEND_NON_BLOCKING
as the option (the task is not to block), OSFlagPend()
returns immediately to the caller and the returned error code indicates that the bits have not been set (or cleared).
If you specify OS_OPT_PEND_BLOCKING
as the option, the calling task will be inserted in the list of tasks waiting for the desired event flag bits. The task is not inserted in priority order but simply inserted at the beginning of the list. This is done because whenever bits are set (or cleared), it is necessary to examine all tasks in this list to see if their desired bits have been satisfied.
If you further specify a non-zero timeout
, the task will also be inserted in the tick list. A zero value for a timeout
indicates that the calling task is willing to wait forever for the desired bits.
The scheduler is then called since the current task is no longer able to run (it is waiting for the desired bits). The scheduler will run the next highest-priority task that is ready-to-run.
When the event flag group is posted to and the task that called OSFlagPend()
has its desired bits set or cleared, a task status is examined to determine the reason why OSFlagPend()
is returning to its caller. The possibilities are:
- The desired bits were set (or cleared)
- The pend was aborted by another task
- The bits were not set (or cleared) within the specified timeout
- The event flag group was deleted
When OSFlagPend()
returns, the caller is notified of the above outcome through an appropriate error code.
(2) If OSFlagPend()
returns with err
set to OS_ERR_NONE
, you can assume that the desired bits were set (or cleared) and the task can proceed with servicing the ISR or task that created those events. If err
contains anything else, OSFlagPend()
either timed out (if the timeout argument was non-zero), the pend was aborted by another task or, the event flag group was deleted by another task. It is always important to examine the returned error code and not assume everything went as planned.
To set (or clear) event flags (either from an ISR or a task), you simply call OSFlagPost()
, as shown in the listing below:
OS_FLAG_GRP MyEventFlagGrp; void MyISR (void) { OS_ERR err; OS_FLAGS flags_cur; : flags_cur = OSFlagPost(&MyEventFlagGrp, (1) (OS_FLAGS)0x0C, (2) OS_OPT_POST_FLAG_SET, (3) &err); (4) /* Check 'err" */ : : }
(1) A task posts to the event flag group by calling OSFlagPost()
. Specify the desired event flag group to post by passing its address. Of course, the event flag group must have been previously created. OSFlagPost()
returns the current value of the event flags in the event flag group after the post has been performed.
(2) The next argument specifies which bit(s) the ISR (or task) will be setting or clearing in the event flag group.
(3) You can specify OS_OPT_POST_FLAG_SET
or OS_OPT_POST_FLAG_CLR
.
If you specify OS_OPT_POST_FLAG_SET
, the bits specified in the second arguments will set the corresponding bits in the event flag group. For example, if MyEventFlagGrp.Flags
contains 0x03, the code in the listing will change MyEventFlagGrp.Flags
to 0x0F
.
If you specify OS_OPT_POST_FLAG_CLR
, the bits specified in the second arguments will clear the corresponding bits in the event flag group. For example, if MyEventFlagGrp.Flags
contains 0x0F
, the code in the listing will change MyEventFlagGrp.Flags
to 0x03
.
When calling OSFlagPost()
you can specify as an option (i.e., OS_OPT_POST_NO_SCHED)
to not call the scheduler. This means that the post is performed, but the scheduler is not called even if a higher-priority task was waiting for the event flag group. This allows the calling task to perform other post functions (if needed) and make all the posts take effect simultaneously.
(4) OSFlagPost()
returns an error code based on the outcome of the call. If the call was successful, err
will contain OS_ERR_NONE
. If not, the error code will indicate the reason of the error (see Appendix A, µC-OS-III Configuration Manual for a list of possible error codes for OSFlagPost()
.
Using Event Flags
When a task or an ISR posts to an event flag group, all tasks that have their wait conditions satisfied will be resumed.
It’s up to the application to determine what each bit in an event flag group means and it is possible to use as many event flag groups as needed. In an event flag group you can, for example, define that bit #0 indicates that a temperature sensor is too low, bit #1 may indicate a low battery voltage, bit #2 could indicate that a switch was pressed, etc. The code (tasks or ISRs) that detects these conditions would set the appropriate event flag by calling OSFlagPost()
and the task(s) that would respond to those conditions would call OSFlagPend()
.
The listing below shows how to use event flags.
#define TEMP_LOW (OS_FLAGS)0x0001 (1) #define BATT_LOW (OS_FLAGS)0x0002 #define SW_PRESSED (OS_FLAGS)0x0004 OS_FLAG_GRP MyEventFlagGrp; (2) void main (void) { OS_ERR err; OSInit(&err); : OSFlagCreate(&MyEventFlagGrp, (3) "My Event Flag Group", (OS_FLAGS)0, &err); /* Check 'err" */ : OSStart(&err); } void MyTask (void *p_arg) (4) { OS_ERR err; CPU_TS ts; while (DEF_ON) { OSFlagPend(&MyEventFlagGrp, (5) TEMP_LOW + BATT_LOW, (OS_TICK )0, (OS_OPT)OS_OPT_PEND_FLAG_SET_ANY, &ts, &err); /* Check 'err" */ : } } void MyISR (void) (6) { OS_ERR err; : OSFlagPost(&MyEventFlagGrp, (7) BAT_LOW, (OS_OPT)OS_OPT_POST_FLAG_SET, &err); /* Check 'err" */ : }
(1) You need to define some bits in the event flag group.
(2) You have to declare an object of type OS_FLAG_GRP
. This object will be referenced in all subsequent µC/OS-III calls that apply to this event flag group. For the sake of discussions, assume that event flags are declared to be 16-bits in os_type.h
(i.e., of type CPU_INT16U
).
(3) Event flag groups must be “created” before they can be used. The best place to do this is in your startup code as it ensures that no tasks, or ISR, will be able to use the event flag group until µC/OS-III is started. In other words, the best place is to create the event flag group is in main()
. In the example, the event flag was given a name and all bits start in their cleared state (i.e., all zeros).
(4) You can assume here that the application created “MyTask()
” which will be pending on the event flag group.
(5) To pend on an event flag group, you call OSFlagPend()
and pass it the address of the desired event flag group.
The second argument specifies which bits the task will be waiting to be set (assuming the task is triggered by set bits instead of cleared bits).
You also need to specify how long to wait for these bits to be set. A timeout value of zero (0) indicates that the task will wait forever. A non-zero value indicates the number of ticks the task will wait until it is resumed if the desired bits are not set.
Specifying OS_OPT_FLAG_SET_ANY
indicates that the task will wake up if either of the two bits specified is set.
A timestamp is read and saved when the event flag group is posted to. This timestamp can be used to determine the response time to the event.
OSFlagPend()
performs a number of checks on the arguments passed (i.e., did you pass NULL
pointers, invalid options, etc.), and returns an error code based on the outcome of the call (assuming OS_CFG_ARG_CHK_EN
is set to DEF_ENABLED
in os_cfg.h
). If the call was successful “err
” will be set to OS_ERR_NONE
.
(6) An ISR (it can also be a task) is setup to detect when the battery voltage of the product goes low (assuming the product is battery operated). The ISR signals the task, letting the task perform whatever corrective action is needed.
(7) The desired event flag group is specified in the post call as well as which flag the ISR is setting. The third option specifies that the error condition will be “flagged” as a set bit. Again, the function sets “err” based on the outcome of the call.
Event flags are generally used for two purposes: status and transient events. Typically you would use different event flag groups to handle each of these as shown in the figure below .
Tasks or ISRs can report status information such as a temperature that has exceeded a certain value, that RPM is zero on an engine or motor, or there is fuel in the tank, and more. This status information cannot be “consumed” by the tasks waiting for these events, because the status is managed by other tasks or ISRs. Event flags associated with status information are monitored by other task by using non-blocking wait calls.
Tasks will report transient events such as a switch was pressed, an object was detected by a motion sensor, an explosion occurred, etc. The task that responds to these events will typically block waiting for any of those events to occur and “consume” the event.