Thread Synchronization

The TI-RTOS kernel provides several modules for synchronizing tasks such as Semaphore, Event, and Queue. The following sections discuss these common TI-RTOS primitives.

Semaphores

Semaphores are commonly used for task synchronization and mutual exclusions throughout TI-RTOS applications. Figure 5. shows the semaphore functionality. Semaphores can be counting semaphores or binary semaphores. Counting semaphores keep track of the number of times the semaphore is posted with Semaphore_post(). When a group of resources are shared between tasks, this function is useful. Such tasks might call Semaphore_pend() to see if a resource is available before using one. Binary semaphores can have only two states: available (count = 1) and unavailable (count = 0). Binary semaphores can be used to share a single resource between tasks or for a basic-signaling mechanism where the semaphore can be posted multiple times. Binary semaphores do not keep track of the count; they track only whether the semaphore has been posted.

../_images/fig-semaphore.jpg

Figure 5. Semaphore Functionality

Initializing a Semaphore

The following code depicts how a semaphore is initialized in TI-RTOS. Semaphores can be created and constructed as explained in Creating vs. Constructing.

See Listing 2. on how to create a Semaphore.

See Listing 3. on how to construct a Semaphore.

Pending on a Semaphore

Semaphore_pend() is a blocking function call. This call may only be called from within a Task context. A task calling this function will allow lower priority tasks to execute, if they are ready to run. A task calling Semaphore_pend() will block if its counter is 0, otherwise it will decrement the counter by one. The task will remain blocked until another thread calls Semaphore_post() or if the supplied system tick timeout has occurred; whichever comes first. By reading the return value of Semaphore_pend() it is possible to distinguish if a semaphore was posted or if it timed out.

Listing 4. Pending on a Semaphore
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
bool isSuccessful;
uint32_t timeout = 1000 * (1000/Clock_tickPeriod);

/* Pend (approximately) up to 1 second */
isSuccessful = Semaphore_pend(sem, timeoutInTicks);

if (isSuccessful)
{
    System_printf("Semaphore was posted");
}
else
{
    System_printf("Semaphore timed out");
}

Note

The default TI-RTOS system tick period is 1 millisecond. This default is reconfigured to 10 microseconds for CC13x0 by setting Clock.tickPeriod = 10 in the .cfg file.

Given a system tick of 10 microseconds, timeout in Listing 4. will be approximately 1 second.

Posting a Semaphore

Posting a semaphore is accomplished via a call to Semaphore_post(). A task that is pending on a posted semaphore will transition from a blocked state to a ready state. If no higher priority thread is ready to run, it will allow the previously pending task to execute. If no task is pending on the semaphore, a call to Semaphore_post() will increment its counter. Binary semaphores have a maximum count of 1.

Listing 5. Posting a Semaphore
1
Semaphore_post(sem);

Event

Semaphores themselves provide rudimentary synchronization between threads. There are cases just the Semaphore itself is enough to understand on what process needs to be triggered. Often however, a specific causes for the synchronization need to be passed across threads as well. To help accomplish this, one can utilize the TI-RTOS Event module.

Events are similar to Semaphores in a sense that each instance of an Event object actually contains a Semaphore. The added advantage of using Events lie in the fact that tasks can be notified of specific events in a thread-safe manner.

Initializing an Event

Creating and constructing Events follow the same guidelines as explained in Creating vs. Constructing. Shown in Listing 6. is an example on how to construct an Event instance.

Listing 6. Constructing an Event
1
2
3
4
5
6
7
8
9
Event_Handle event;
Event_Params eventParams;
Event_Struct structEvent; /* Memory allocated at build time */

Event_Params_init(&eventParams);
Event_construct(&structEvent, 0, &eventParams);

/* It's optional to store the handle */
event = Event_handle(&structEvent);

Pending on an Event

Similar to Semaphore_pend(), a Task thread would typically block on an Event_pend() until an event is posted via an Event_post() or if the specified timeout expired. Shown in Listing 7. is a snippet of a task pending on any of the 3 sample event IDs shown below. BIOS_WAIT_FOREVER is used to prevent a timeout from occurring. As a result, Event_pend() will have one or more events posted in the returned bit-masked value.

Each event returned from Event_pend() has been automatically cleared within the event instance in a thread-safe manner. Therefore, it is only necessary to keep a local copy of posted events. For full details on how to use Event_pend(), see the TI-RTOS Kernel User Guide.

Listing 7. Pending on an Event
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#define START_ADVERTISING_EVT         Event_Id_00
#define START_CONN_UPDATE_EVT         Event_Id_01
#define CONN_PARAM_TIMEOUT_EVT        Event_Id_02

void TaskFxn(..)
{
    /* Local copy of events that have been posted */
    uint32_t events;

    while(1)
    {
        /* Wait for an event to be posted */
        events = Event_pend(event,
                            Event_Id_NONE,
                            START_ADVERTISING_EVT |
                            START_CONN_UPDATE_EVT |
                            CONN_PARAM_TIMEOUT_EVT,
                            BIOS_WAIT_FOREVER);

        if (events & START_ADVERTISING_EVT)
        {
            /* Process this event */
        }

        if (events & START_CONN_UPDATE_EVT)
        {
            /* Process this event */
        }

        if (events & CONN_PARAM_TIMEOUT_EVT)
        {
            /* Process this event */
        }
    }
}

Note

The default TI-RTOS system tick period is 1 millisecond. This default is reconfigured to 10 microseconds for CC26xx and CC13xx devices by setting Clock.tickPeriod = 10 in the .cfg file.

Given a system tick of 10 microseconds, timeout in Listing 4. will be approximately 1 second.

Posting an Event

Events may be posted from any TI-RTOS kernel contexts and is simply done by calling Event_post() of the Event instance and the Event ID. Listing 8. shows how a high priority thread such as a Swi could post a specific event.

Listing 8. Posting an Event
1
2
3
4
5
6
7
8
#define START_ADVERTISING_EVT         Event_Id_00
#define START_CONN_UPDATE_EVT         Event_Id_01
#define CONN_PARAM_TIMEOUT_EVT        Event_Id_02

void SwiFxn(UArg arg)
{
    Event_post(event, START_ADVERTISING_EVT);
}