Queues

The TI-RTOS Queue module provides a thread-safe unidirectional message passing module operating in a first in, first out (FIFO) basis. Queues are commonly used to allow high priority threads to pass messages to lower priority tasks for deferred processing; therefore allowing low priority tasks to block until necessary to run.

In Figure 59. a queue is configured for unidirectional communication from task A to task B. Task A “puts” messages into the queue and task B “gets” messages from the queue.

../_images/fig-queue-messaging-process.jpg

Figure 59. Queue Messaging Process

In BLE5-Stack, TI-RTOS Queue functions have been abstracted into functions in util.c See the Queue module documentation in the TI-RTOS Kernel User Guide for the underlying functions. The functions in util.c combine a queue from the Queue module with an event from the Event module to pass messages between threads.

In CC13x2 or CC26x2 software, ICall uses queues and events from their respective modules to pass messages between the application and stack tasks. An example of this can be seen in SimpleCentral_enqueueMsg(). A high priority Task, Swi, or Hwi queues a message to the application task. The application task will then process this message in its own context when no other high priority threads are running.

The util module contains a set of abstracted TI-RTOS Queue functions as shown here:

Functional Example

Figure 60. and Figure 61. illustrate how a queue is used to enqueue a button press message from a Hwi (to a Swi in the Board Key module) to be post-processed within a task context. This example is taken from the from the simple_central project in BLE5-Stack.

@startuml
hide footbox

box "Swi context"
    participant "Board Key module" as A
    participant simple_central.c as B
    database appMsgQueue as C
end box

-[#red]> A : Key press interrupt
<[#red]-- A

activate A

autonumber
A -> B : SimpleCentral_keyChangeHandler();
activate B

note right: Add SC_KEY_CHANGE_EVT into the queue
B -> B : SimpleCentral_enqueueMsg();
activate B
autonumber stop
B -> : ICall_malloc();
B -> C: Util_enqueueMsg();
activate C
C --> B:
deactivate C
B -> : Event_post();
deactivate B
B --> A
deactivate B
deactivate A

@enduml

Figure 60. Sequence diagram for enqueuing a message

With interrupts enabled, a pin interrupt can occur asynchronously within a Hwi context. To keep interrupts as short as possible, the work associated to the interrupt is deferred to tasks for processing. In the simple_central example found in BLE5-Stack, pin interrupts are abstracted via the Board Key module. This module notifies registered functions via a Swi callback. In this case, SimpleCentral_keyChangeHandler is the registered callback function.

Step 1 in Figure 60. shows the callback to SimpleCentral_keyChangeHandler when a key is pressed. This event is placed into the application’s queue for processing.

Listing 9. Defining SimpleCentral_keyChangeHandler()
1
2
3
4
void SimpleCentral_keyChangeHandler(uint8 keys)
{
  SimpleCentral_enqueueMsg(SC_KEY_CHANGE_EVT, keys, NULL);
}

Step 2 in Figure 60. shows how this key press is enqueued for simple_central task. Here, memory is allocated via ICall_malloc() so the message can be added to the queue. Once added, Util_enqueueMsg() will generate a UTIL_QUEUE_EVENT_ID event to signal the application for processing.

Listing 10. Defining SimpleCentral_enqueueMsg()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
static uint8_t SimpleCentral_enqueueMsg(uint8_t event, uint8_t state, uint8_t *pData)
{
  scEvt_t *pMsg = ICall_malloc(sizeof(scEvt_t));

  // Create dynamic pointer to message.
  if (pMsg)
  {
    pMsg->hdr.event = event;
    pMsg->hdr.state = state;
    pMsg->pData = pData;

    // Enqueue the message.
    return Util_enqueueMsg(appMsgQueue, syncEvent, (uint8_t *)pMsg);
  }

  return (false);
}

@startuml
hide footbox

box "Task context"
    participant simple_central.c as A
    database appMsgQueue as B
end box

activate A
A -> : Event_pend()
note right: Task called Event_pend() and gets blocked
deactivate A

...

-> A : Posted event
activate A
autonumber 3
A -> A : while (pMsg = Util_dequeueMsg())
activate A
autonumber stop
note right: Util_dequeueMsg() checks and dequeues any message

autonumber resume
A -> : SimpleCentral_processAppMsg(pMsg);
note right: SimpleCentral_processAppMsg \n{\n\tcase (SC_KEY_CHANGE_EVT):\n\t\tSimpleCentral_handleKeys()\n};
autonumber resume
A -> : ICall_free(pMsg)

autonumber stop
note right: Repeat while there are more messages\nin the queue
deactivate A

@enduml

Figure 61. Sequence diagram for dequeuing a message

Step 3 in Figure 61., the simple_central application is unblocked by the posted UTIL_QUEUE_EVENT_ID event where it proceeds to check if messages have been placed in the queue for processing.

Listing 11. Processing application messages
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// If RTOS queue is not empty, process app message
if (events & SC_QUEUE_EVT)
{
    scEvt_t *pMsg;
    while (pMsg = (scEvt_t *)Util_dequeueMsg(appMsgQueue))
    {
        // Process message
        SimpleCentral_processAppMsg(pMsg);

        // Free the space from the message
        ICall_free(pMsg);
    }
}

Step 4 in Figure 61., the simple_central application takes the dequeued message and processes it.

Listing 12. Processing key interrupt message
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static void SimpleCentral_processAppMsg(sbcEvt_t *pMsg)
{
  switch (pMsg->hdr.event)
  {
    case SC_KEY_CHANGE_EVT:
      SimpleCentral_handleKeys(pMsg->hdr.state);
      break;
    //...
  }
}

Step 5 in Figure 61., the simple_central application can now free the memory allocated in Step 2.