0

I am new to FreeRTOS and have been reading the FreeRTOS documentation and writing simple code using FreeRTOS on an STM32F767 Nucleo Board. In the simple program that I wrote, I used Binary Semaphores only to signal certain tasks when LPTIM and GPIO interrupts occur through xSemaphoreGiveFromISR(), and to signal a different task to perform certain operations from another task through xSemaphoreGive().

Suppose that I have an I2C1 peripheral connected to two different equipments:

  • An accelerometer that triggers a GPIO interrupt to the microcontroller whenever an activity/movement occurs. This GPIO interrupt signals the microcontroller that a piece of data inside its Interrupt Event registers must be read so that the next activity/movement event can be signalled again.
  • An equipment that must be read from periodically, which will be triggered through an LPTIM or TIM peripheral

Can I use a Mutex and a Binary Semaphore in the situation above?

The Binary Semaphores will indicate to the task that an operation needs to be performed based on the respective interrupts that were triggered, but the Mutex will be shared between those two tasks, where Task1 will be responsible with reading data from the accelerometer, and Task2 will be responsible for reading data from the other equipment. I was thinking that a Mutex will be used since these two operations should never occur together, so that there are no overlapping I2C transactions that happen on the bus that could potentially lock up either of the I2C devices.

The code would look like the following:

void Task1_AccelerometerOperations(void *argument)
{
   /* The Semaphore will be given from the GPIO Interrupt Handler, signalling that a piece of 
      data needs to be read from the accelerometer through I2C. */
   if(xSemaphoreTake(xSemaphore_GPIOInterruptFlag, portMAX_DELAY) == pdTRUE)
   {
      /* This Mutex should ensure that only one I2C transaction can happen at a time */
      if(xSemaphoreTakeRecursive(xMutex_I2CBus, 2000/portTICK_PERIOD_MS) == pdTRUE)
      {
         /* Perform I2C Transaction */
         /* Perform operations with the data received */

         /* Mutex will be given back, indicating that the shared I2C Bus is now available */
         xSemaphoreGiveRecursive(xMutex_I2CBus);
      }
      else
      {
         /* Mutex was not available even after 2 seconds since the GPIO interrupt triggered. 
            Perform Error Handling for the event that the I2C bus was locked */
      }

      /* Piece of code that could take a few hundreds milliseconds to execute */
   }
}

void Task2_OtherEquipmentOperations(void *argument)
{
   /* The Semaphore will be given from the LPTIM Interrupt Handler, signalling that some maintenance 
      or periodic operation needs to be performed through I2C. */
   if(xSemaphoreTake(xSemaphore_LPTIMInterruptFlag, portMAX_DELAY) == pdTRUE)
   {
      /* Only perform the I2C operations when the Mutex is available */
      if(xSemaphoreTakeRecursive(xMutex_I2CBus, 2000/portTICK_PERIOD_MS) == pdTRUE)
      {
         /* Perform I2C Transaction */

         /* Mutex will be given back, indicating that the shared I2C Bus is now available */
         xSemaphoreGiveRecursive(xMutex_I2CBus);
      }
      else
      {
         /* Mutex was not available even after 2 seconds since the LPTIM interrupt triggered. 
            Perform Error Handling for the event that the I2C bus was locked */
      }

      /* Piece of code that could take a few seconds to execute */
   }
}

Are Mutexes often used to avoid Priority Inversion scenarios, or are they (more often) widely used to prevent two operations from possibly happening together? I can't think of a simple scenario where if a Priority Inversion occurs, it could be critical for the software.

Thank you!

Cimory
  • 82
  • 1
  • 8
  • 1
    You are correct. – Mike Robinson May 26 '21 at 01:48
  • @MikeRobinson What are some examples where people would use Mutexes to avoid priority inversion? Are there certain guidelines/tricks when trying to identify possibilities of priority inversion? Or is priority inversion not a huge problem when the more time-consuming tasks have lower priorities? – Cimory May 26 '21 at 02:25
  • Maybe [this](https://stackoverflow.com/questions/2332765/lock-mutex-semaphore-whats-the-difference) would help you? – fpiette May 26 '21 at 13:33
  • @fpiette I already looked into the differences semaphores and mutexes through similar posts like those such as [this](https://stackoverflow.com/questions/62814/difference-between-binary-semaphore-and-mutex) and [this](https://stackoverflow.com/questions/4039899/when-should-we-use-mutex-and-when-should-we-use-semaphore), but they don't really have the explanation I am looking for. – Cimory May 26 '21 at 20:43

2 Answers2

1

In general, I think your design could work. The semaphores are signals for the two tasks to do work. And the mutex protects the shared I2C resource.

However, sharing a resource with a mutex can lead to complications. First, your operations tasks are not responsive to new semaphore signals/events while they are waiting for the I2C resource mutex. Second, if the application gets more complex and you add more blocking calls then the design can get into a vicious cycle of blocking, starvation, race conditions, and deadlocks. Your simple design isn't there yet but you're starting down a path.

As an alternative, consider making a third task responsible for handling all I2C communications. The I2C task will wait for a message (in a queue or mailbox). When a message arrives then the I2C task will perform the associated I2C communication. The operations tasks will wait for the semaphore signal/event like they do now. But rather than waiting for the I2C mutex to become available, now the operations tasks will send/post a message to the I2C task. With this design you don't need a mutex to serialize access to the I2C resource because the I2C task's queue/mailbox does the job of serializing the message communication requests from the other tasks. Also in this new design each task blocks at only one place, which is cleaner and allows the operations tasks to be more responsive to the signals/events.

kkrambo
  • 6,643
  • 1
  • 17
  • 30
  • Thanks for the suggestion on using a separate task for the I2C operations! So in most FreeRTOS applications, it is usually best to only have one blocks at one place? I.e. no two blocks should be in the same task? Is it a general rule that if I need more than one blocks, I should just make a new task? – Cimory May 26 '21 at 20:33
  • Also, let's say that the scenario you spoke of happens, where the operation tasks are not responsive to new semaphore signals because they are waiting for the I2C mutex. In this case, I assume that the associated interrupt would be triggered twice, and `xSemaphoreGiveFromISR()` from inside the ISR would be called twice. What would happen when a Binary Semaphore is given twice without being taken in between? Does that mean that the task associated with it would just perform the operation once instead of twice, because the Semaphore's status would still be 'Available' and not changed afterwards? – Cimory May 26 '21 at 20:39
  • 1
    Yes, I think that's a general rule of thumb but there is more to it. See [Beyond the RTOS: Part 2 by Miro Samek](https://youtu.be/U1pHSkftzhw) and the references he shares on slides 19 and 20 of the presentation. Or search for "Active Object", especially articles by Herb Sutter at Dr. Dobb's. – kkrambo May 27 '21 at 14:36
  • 1
    Yes, if the ISR posts to the binary semaphore twice before the operations task gets the semaphore once then one event will be lost because the binary semaphore only counts to one. You could use a counting semaphore to stack multiple event occurrences. – kkrambo May 27 '21 at 14:40
  • Thanks for the confirmation on both questions! – Cimory May 27 '21 at 19:36
0

IMO it is a completely wrong design.

If you want to inform the task that new data is - use queues and post to the queue. You can post to more than one queue if more than one task wants to get that data (you can have array of queues to send). You can even runtime add queues.

0___________
  • 60,014
  • 4
  • 34
  • 74
  • Thanks for the answer! The reason why I thought it could be implemented with Mutex is because I had the impression that Mutexes could be used to prevent several operations from possibly happening at the same time, and that it could be used for limited resources. I was also reading about Mutexes and was thinking if this could be one way of using it. – Cimory May 27 '21 at 00:23
  • Mutex in freeRTOS is semaphore. Semaphores are implemented with queues. So it will be the same expensive. – 0___________ May 27 '21 at 00:27
  • I thought Mutexes are slightly different from Binary Semaphores? Wouldn't a Mutex need to be given from the same task that took the Mutex, whereas a Binary Semaphore can be given from multiple locations (tasks, ISRs)? I was also mainly wondering when one would certainly need a Mutex instead of a Binary Semaphore to tackle priority inversion, and in what cases priority inversion would be a huge issue. – Cimory May 27 '21 at 00:36
  • 1
    @Cimory mutex in freeRTOS uses semaphore as a mechanism with priority inheritance. Mutexes should not be used in the interrupts!!!! – 0___________ May 27 '21 at 00:58