Search Microcontrollers

Friday, August 14, 2015

TI RTOS - thread synchronization

I don't have much free time these days, but when I have a spare hour, I like to play with the TI RTOS, following the great online workshop I discussed in my previous post.

The BIOS (Kernel) of the OS allows you to create threads (functions) that run with different priorities and that respond to an hardware interrupt (Hwis) or a software generated "event" (Swis, Tasks) or simply run continuously whenever the OS is not busy with other stuff (Idle).

All that is quite cool and also rather easy to implement, but soon you discover you may need to implement some sort of synchornization between your threads, eventually passing values from one thread to another.
Say you have a Hwi that triggers whenever data is received on the UART, it sends the buffer to a Swi that reads it and interprets data (i.e. commands you are sending remotely, let's imagine it's ASCII based and at every \n you mark the end of a command).

What happens is that once you receive a command, you want to react accordingly and this reaction may take a few CPU cycles, for instance to turn a stepping motor X steps in a given direction.
Meanwhile your UART is still receiving other commands.

You see it's easy to get in a difficult situation where buffers are overrun or changed while you are using them.

The worst solution to exchange data across the different threads is to use global variables as buffers, these will easily get manipulated while you are using them.
sure, there are a few ways to protect your system from this to happen, but most of those "solutions" are likely to introduce even worse problems.

But hey, we have an OS, remember?
The RTOS has built in capabilities to regulate the execution of the tasks.

Semaphores

A Semaphore can be used to pause (pend) a task untill another task tells it (post) to run.

It is somehow similar to an event, but it does not trigger a function (like an event would do), instead it tells it (whenever the bios scheduler decides to give back the Program Counter to it, based on priority settings) to keep running.

Example 

void myTask()
{
  .. do some init stuff...
  while (1) // run forever
 {
    ... do some other stuff ...
   Semaphore_pend(myCoolSemaphore);
   ... get the data and use it...
  }
}

It is cool, indeed, but if we need to echange data, the Semaphore is not enough.

So, there are features in the OS that allow one thread to send a buffer with a message.
This message can be in a Queue or in a Mailbox.

Queues and mailboxes are slightly different, the main difference is probably that messages in mailboxes are in fact copies of the data, meaning that the sender and the receiver do not share the same memory locations when accessing the data, while queues pass pointers.
Another important difference is that mailboxes have built-in semaphores, while Queues don't, you need to provide a semaphore for synchronization purposes.

The overall idea is that along with a synchronization signal you can send data, i.e. the buffer containing the command.
However we can have several mails in a mailbox, making the system automaGically multi-buffered.

So, going from a single buffer to a n-buffer solution if using queues and mailboxes is mainly a configuration task (you set up values in the GUI, no coding needed for this).


The picture (app.cfg edited in CCS 6.1.0) shows a double buffer implemented with a mailbox with 2 max messages.

The example, taken from the TI RTOS Workshop (see previous post) lab 9 does the following:

1) An Hardware timer generates an interrupt every 500ms
2) An Hwi function associated to the timer int posts a semaphore

void Timer_ISR(void)
{
    TimerIntClear(TIMER2_BASE, TIMER_TIMA_TIMEOUT);
Semaphore_post(mailbox_queue_Sem);
}

3) A mailbox management task waits (pends) for the semaphore and when it receiveds a green light, it posts to the mailbox.
Notice that in this case the "data" is generated here (msg.val is toggled).

void mailbox_queue(void)
{
  MsgObj msg;
  msg.val = 1;
  while(1)
  {
   msg.val ^= 1; // toggle msg.val (LED state)
   Semaphore_pend(mailbox_queue_Sem, 
                  BIOS_WAIT_FOREVER);
   Mailbox_post (LED_Mbx, &msg, 
                  BIOS_WAIT_FOREVER);      }
}

4) A task that actually reacts on the data (toggles a led) waits for the mailbox

void ledToggle(void)
{
  MsgObj msg;
  while(1)
  {
   Mailbox_pend(LED_Mbx, &msg, 
                BIOS_WAIT_FOREVER);
   if (msg.val)
   {     // turn LED on

    GPIOPinWrite(GPIO_PORTF_BASE,
           GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 8)  
   }
   else
   { // turn LED off
    GPIOPinWrite(GPIO_PORTF_BASE, 
            GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 0)
   }
  }
}

We can observe what happens thanks to the UIA (Unified Instrumentation Architecture) that allows to interface with the OS via the JTAG port and gather execution details.



In the picture you can see that while the system is Idle (green line) a semaphore is posted (first green flag) by the Hwi function.
Control is handed over the mailbox management function (higher blue line) which is pending on the semaphore (first red flag).
It executes toggling the msg.val and then posts a mail (second green flag), then goes back pending on the semaphore again (second red flag).
Only then the bios scheduler (lower blue line) passes the control over to the ledToggle function (red line) which is pending on the mailbox, it gets the message (third green flag -it is green because it does not block the task, and it does not block the task because there is a message in the mailbox), manages the led based on the value in the message and finally waits again for the mailbox (third red flag).
At this point there are no other messages in the mailbox, so the flag is red, the task gives back control to the sceduler.
Guess what happens when a new timer int is fired?
Yup, rinse & repeat.

Neat eh?
If you are interested in knowing more on the subject, I encourage you to try the free TI RTOS Workshop, the example illustrated here (and the relative code) is taken from the Lab 9 material.

No comments: