EX9: Semaphore example#

This exercise will demonstrate the use of semaphores.

The learning outcome of this problem is to:

  • Be able to use a semaphore to:

    • protect a shared resource, and

    • synchronize to tasks.

Protecting shared resources#

As mentioned in EX8: A basic RTOS application the OSTimeDlyHMSM function is used to block the tasks for 3 seconds. Both tasks are using the same resource for writing to the standard output and thus there is a risk that both tasks will use this resources at the same time, resulting in a corrupted output. However, the execution time of the printf command in this example is in the order of a few hundered microseconds. The chance that the higher priority task may interrupt the lower priority task in the middle of a printf execution is therefore very low.

To better demonstrate this scenario we will have to increase the time it takes to write to the JTAG UART. In addition we can also increase the repetition rate of each task, that is, how often they run. This can be achieved by looping through each character of the text string and using the command putchar to write a single character at the time, and reducing the task’s time delay. Modify both tasks according to the suggested code below. The use of the strlen function requires the string.h header file to be included (#include <string.h>).

#define TASK1_PRIORITY      4
#define TASK2_PRIORITY      5

void task1(void* pdata)
{
  char text[] = "Hello from task1\n";
  int i;
  while (1)
  {
    for (i = 0; i < strlen(text); i++){
      putchar(text[i]);
    }
    //printf("Hello from task2\n");
    OSTimeDlyHMSM(0, 0, 0, 20);
  }
}

void task2(void* pdata)
{
  char text[] = "Hello from task2\n";
  int i;
  while (1)
  {
    for (i = 0; i < strlen(text); i++){
      putchar(text[i]);
    }
    //printf("Hello from task2\n");
    OSTimeDlyHMSM(0, 0, 0, 4);
  }
}

Compile the modified code and observe the result in the Nios-II terminal application.

When running the code the output will sometimes be corrupted, e.g., like the output example shown below. That is, the higher priority task will interrupt the lower priority task before it has completed writing the full text string to the standard output.

Hello from Task2
Hello from Task1
Hello from Task2
Hello from Task2
Hello from Task2
HelloHello from Task1
 from Task2
Hello from Task2
Hello from Task2
Hello from Task1
Hello from Task2
Hello from Task2

Since both tasks are using the same resource we need to implement a mechanism that controls the access to this resource. In \(\mu\)C/OS-II this can be acheived by using a semaphore, where the semaphore needs to be aquired by each task before using the shared resource. When the task no longer needs the resources, the task needs to release the semaphore.

The relevant uC/OS-II functions to be used to declare, create, and use semaphores are shown below.

//Declare a pointer of type OS_EVENT.
OS_EVENT *shared_jtag_sem;
//create semaphores in the main() function before starting the multitasking system, and initialize to 1. That is, the semaphore is available from start. If initialize to 0, the semaphore is not available from start.
shared_jtag_sem = OSSemCreate(1);
//Prototype functions for OSSemPend and OSSemPost
void OSSemPend (OS_EVENT *pevent,
                INT16U    timeout,
                INT8U    *err);

INT8U OSSemPost(OS_EVENT *pevent);

More information about the function calls can be found in the \(\mu\)c/OS-II API reference section of the \(\mu\)C/OS-II user manual [Mic] Direct Link

Modify the code to use a semaphore called shared_jtag_sem to control the access to the JTAG UART. Compile and download the software to observe the difference with respect to not protecting the shared variable with a semaphore.

See also the recoded video lectures on Semaphores.

Synchronization of tasks#

In the example above the semaphore was used to protect the access to a shared resource. In addition a semaphore can also be used to synchronize the execution of a task with another task or event. In Section 3.6 we implemented interrupt handling for an external push button. In this case we can make use of a semaphore to execture a task every time the push button generates is pressed.

When used for synchronization a semaphore must be initialize to 0, and then made available (posted) from the interrupt handling routine on an active interrupt. The task being synchronized to the interrupt must wait for the semaphore (pend).

Your task will now be to synchronize task1 from the example above with the interrupt generated by the push button. Try to solve the problem by yourself before you look at the solution example below.