Thread Synchronization 2 – RT-Thread Tutorial Part 5

This article is a continuation of the  Series on RT-Thread STM32 Tutorials and carries the discussion on RT-Thread RTOS and implementation with STM32. The aim of this series is to provide easy and practical examples that anyone can understand. In our last post, we have seen Critical section and Semaphore in the RT-Thread RTOS. In this post, we will see the Mutex and Event in RT-Thread RTOS (Thread Synchronization 2).

You can see the video explanation of Getting started with the RT-Thread RTOS.

Prerequisites

This is the continuation of the below tutorials. If you don’t read it, please read that also before this.

Tools and Components Required

  • STM32F411 Dev Board (You can use any STM32F4 controller)
  • RT-Thread Studio

Thread Synchronization 2 (Semaphore in RT-Thread)

We discussed the Critical section and Semaphore in our last tutorial. Mutex and Events are pending. We will see that now.

Mutex in RT-Thread RTOS

Mutexes, also known as mutually exclusive semaphores, are special binary semaphores. We have already discussed this mutex and other RTOS concepts in this post. So, we will directly go to the programming.

Operations in Mutex

In RT-Thread RTOS, mutex supports the below operations.

Creating/initiating a mutex

The below API is used to create the mutex dynamically.

rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag);

When this function is called, the system will first allocate a mutex object from the object manager, initialize the object, and then initialize the parent class IPC object and the mutex-related part. Input parameters and return values of rt_mutex_create()

ParametersDescription
nameMutex name
flagMutex flag, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO
Return——
Mutex handleCreated successfully
RT_NULLCreation failed

You can use the below API to create the Static mutex.

rt_err_t rt_mutex_init (rt_mutex_t mutex, const char* name, rt_uint8_t flag);

The memory of a static mutex object is allocated by the compiler during system compilation and is usually placed in a read-write data segment or an uninitialized data segment. Before using such static mutex objects, you need to initialize them first. To initialize the mutex, use the above function interface.

Input parameters and return values of rt_mutex_init()

ParametersDescription
mutexThe handle of the mutex object, which is provided by the user and points to the memory block of the mutex object
nameMutex name
flagMutex flag, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO
Return——
RT_EOKInitialization successful

Deleting/detaching a mutex

You can use the below API to delete the dynamically created Mutex (Mutex which has been created using the rt_mutex_create()).

rt_err_t rt_mutex_delete (rt_mutex_t mutex);

If you have used static mutex (Mutex which has been initialized using rt_mutex_init() ), then you can use the below API to detach the Mutex.

rt_err_t rt_mutex_delete (rt_mutex_t mutex);

Note: When a mutex is deleted or detached, all threads waiting for this mutex will be woken up, and the return value for the waiting threads is – RT_ERROR.

Obtaining a mutex

 To obtain the mutex, use the following function interface.

rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time);

Input parameters and return values of rt_mutex_take()

ParametersDescription
mutexThe handle of the mutex object
timeSpecified waiting time
Return——
RT_EOKSuccessfully obtained mutex
RT_ETIMEOUTTimeout
-RT_ERRORFailed to obtain

Once the thread obtains the mutex, the thread has ownership of the mutex, that is, a mutex can only be held by one thread at a time.

Releasing a mutex

Whoever has obtained the mutex, they have to release it. So that other threads can obtain the mutex in time.

To release the mutex, use the following function interface:

rt_err_t rt_mutex_release(rt_mutex_t mutex);

Note: In the RT-Thread operating system, a mutex can solve the priority inversion problem and implement the priority inheritance algorithm. At the time of initialization, the mutex is always unlocked, and when it is held by the thread, it immediately becomes locked.

Mutex Example

Source code

In the below code, we have created the two threads. Both are accessing the same resource called global_var. So, both thread has to be synchronized. In this example, both tasks are synchronized using the Mutex that has been created Dynamically.

You can get the entire project source code from GitHub.

#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG

#include <rtdbg.h>
#include <drv_common.h>
#include <rtdevice.h>

uint32_t global_var = 10;

static rt_mutex_t mutex = RT_NULL;

static rt_thread_t threadId1 = RT_NULL;
static rt_thread_t threadId2 = RT_NULL;

/* Thread 1 */
static void thread1_function(void *parameter)
{
    while (1)
    {
        rt_mutex_take(mutex, RT_WAITING_FOREVER);

        global_var++;                       //Accessing common resource
        LOG_D("Thread 1: global_var = %d\n", global_var);

        rt_mutex_release(mutex);

        rt_thread_mdelay(1000);
    }
}

/* Thread 2 */
static void thread2_function(void *parameter)
{
    while (1)
    {
        rt_mutex_take(mutex, RT_WAITING_FOREVER);

        global_var--;                       //Accessing common resource
        LOG_D("Thread 2: global_var = %d\n", global_var);

        rt_mutex_release(mutex);


        rt_thread_mdelay(1000);
    }
}


int main(void)
{
    /* Create a dynamic mutex */
    mutex = rt_mutex_create("EtXmutex", RT_IPC_FLAG_FIFO);
    if (mutex == RT_NULL)
    {
        LOG_D("create dynamic mutex failed.\n");
        return -1;
    }

    /* Create thread 1 */
    threadId1 = rt_thread_create("thread1_fn",  //Name
                            thread1_function,   //Function address
                            RT_NULL,            //Thread function parameter
                            1024,               //Stack Size
                            1,                  //Thread priority
                            2000);              //Time slice in ticks

    /* Create thread 2 */
    threadId2 = rt_thread_create("thread2_fn",  //Name
                            thread2_function,   //Function address
                            RT_NULL,            //Thread function parameter
                            1024,               //Stack Size
                            0,                  //Thread priority
                            2000);              //Time slice in ticks

    /* Start both threads */
    rt_thread_startup(threadId1);
    rt_thread_startup(threadId2);

    while(1);

    return RT_EOK;
}

Output

\ | /
- RT -     Thread Operating System
 / | \     4.1.1 build Mar  3 2023 15:38:52
 2006 - 2022 Copyright by RT-Thread team
←[0m[D/main] Thread 1: global_var = 11
←[0m
←[0m[D/main] Thread 2: global_var = 10
←[0m
←[0m[D/main] Thread 1: global_var = 11
←[0m
←[0m[D/main] Thread 2: global_var = 10
←[0m
←[0m[D/main] Thread 1: global_var = 11
←[0m
←[0m[D/main] Thread 2: global_var = 10
←[0m
←[0m[D/main] Thread 1: global_var = 11

An event in RT-Thread RTOS

An event set is also one of the mechanisms for synchronization between threads. An event set can contain multiple events. Unlike semaphores, an Event set can be used to complete one-to-many, many-to-many thread synchronization. The events are managed with a 32-bit value. So, we can send 32 events at a time.

Note: Event is only used for synchronization and does not provide data transfer functionality. Events are not queuing. So if we send the same event to the thread multiple times (if the thread has not had time to read it), the effect is equivalent to sending it only once.

In the RT-Thread, each thread has an event information tag with three attributes.

  • RT_EVENT_FLAG_AND (logical AND)
  • RT_EVENT_FLAG_OR (logical OR)
  • RT_EVENT_FLAG_CLEAR (clear flag)

We will consider one example. One thread is registered for 3 events ( 0th bit, 2nd bit, and 10th bit). If the thread event attribute is set to RT_EVENT_FLAG_AND, then the thread won’t wake up until it receives all the 3 events. If the attribute is set to RT_EVENT_FLAG_OR, then the thread will wake up at least any one of the events that has been received. If RT_EVENT_FLAG_CLEAR is set, then when the thread wakes up, it will clear all the events that are set.

Operations in Event

Creating/initiating an event

We can create the event dynamically by using the below API.

rt_event_t rt_event_create(const char* name, rt_uint8_t flag);

Input parameters and return values for rt_event_create()

ParametersDescription
nameName of the event set.
flagThe flag of the event set that can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO
Return——
RT_NULLCreation failed.
Handle of the event objectCreation successful

The below API is used to initialize the statically created event.

rt_err_t rt_event_init(rt_event_t event, const char* name, rt_uint8_t flag);

Input parameters and return values of rt_event_init()

ParametersDescription
eventThe handle of the event set object
nameThe name of the event set
flagThe flag of the event set, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO
Return——
RT_EOKSuccess

Deleting/detaching an event

You can use the below API to delete the dynamically created event (an event that has been created using the rt_event_create()).

rt_err_t rt_event_delete(rt_event_t event);

If you have used a static event (an event that has been initialized using rt_event_init() ), then you can use the below API to detach the event.

rt_err_t rt_event_detach(rt_event_t event);

Send an event

The send event function can send one or more events in the event set as follows:

rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);

Input parameters and return values of rt_event_send()

ParametersDescription
eventThe handle of the event set object
setThe flag value of one or more events sent
Return——
RT_EOKSuccess

Receive an event

The below API is used to receive the events.

rt_err_t rt_event_recv(rt_event_t event,
                           rt_uint32_t set,
                           rt_uint8_t option,
                           rt_int32_t timeout,
                           rt_uint32_t* recved);

Input parameters and return values of rt_event_recv()

ParametersDescription
eventThe handle of the event set object
setReceive events of interest to the thread
optionReceive options
( RT_EVENT_FLAG_OR, RT_EVENT_FLAG_AND, RT_EVENT_FLAG_CLEAR)
timeoutTimeout
recvedPoint to the received event
Return——
RT_EOKSuccessful
-RT_ETIMEOUTTimeout
-RT_ERRORError

Event Example

In this example, we have created two threads. Thread 1 will send event 1 when the count is equal to 5. Then it will send event 2 when the count is equal to 10. Thread 2 will be waiting for the events. We have used RT_EVENT_FLAG_AND attribute. So, Thread 2 will be in a blocking state until it receives two events. Once it receives both events, then it will print the log. We have created the static event in this example.

Source code

You can get this entire project’s source code from GitHub.

#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG

#include <rtdbg.h>
#include <drv_common.h>
#include <rtdevice.h>

#define FIRST_EVENT  (1 << 0)
#define SECOND_EVENT (1 << 1)

static struct rt_event event;   //static event

static rt_thread_t threadId1 = RT_NULL;
static rt_thread_t threadId2 = RT_NULL;

/* Thread 1 */
static void thread1_function(void *parameter)
{
    uint8_t count = 0;
    while (1)
    {
        count++;
        LOG_D("Thread 1: count = %d\n", count);
        if( count == 5 )
        {
            //send 1st event
            rt_event_send(&event, FIRST_EVENT);
        }
        else if( count == 10 )
        {
            //send 2nd event and clear the count
            rt_event_send(&event, SECOND_EVENT);
            count = 0;
        }
        rt_thread_mdelay(1000);
    }
}

/* Thread 2 */
static void thread2_function(void *parameter)
{
    uint32_t received_evt;
    while (1)
    {
        if (rt_event_recv(  &event,
                            (FIRST_EVENT | SECOND_EVENT),
                            RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
                            RT_WAITING_FOREVER,
                            &received_evt) == RT_EOK)
        {
            LOG_D("Thread 2: Received both Events\n");
        }
    }
}

int main(void)
{
    /* Initialize event object */
    if( rt_event_init(&event, "event", RT_IPC_FLAG_FIFO) != RT_EOK )
    {
        rt_kprintf("Event init failed.\n");
        return -1;
    }

    /* Create thread 1 */
    threadId1 = rt_thread_create("thread1_fn",  //Name
                            thread1_function,   //Function address
                            RT_NULL,            //Thread function parameter
                            1024,               //Stack Size
                            1,                  //Thread priority
                            2000);              //Time slice in ticks

    /* Create thread 2 */
    threadId2 = rt_thread_create("thread2_fn",  //Name
                            thread2_function,   //Function address
                            RT_NULL,            //Thread function parameter
                            1024,               //Stack Size
                            1,                  //Thread priority
                            2000);              //Time slice in ticks

    /* Start both threads */
    rt_thread_startup(threadId1);
    rt_thread_startup(threadId2);

    while(1);

    return RT_EOK;
}

Output

 \ | /
- RT -     Thread Operating System
 / | \     4.1.1 build Mar  3 2023 20:45:48
 2006 - 2022 Copyright by RT-Thread team
←[0m[D/main] Thread 1: count = 1
←[0m
←[0m[D/main] Thread 1: count = 2
←[0m
←[0m[D/main] Thread 1: count = 3
←[0m
←[0m[D/main] Thread 1: count = 4
←[0m
←[0m[D/main] Thread 1: count = 5
←[0m
←[0m[D/main] Thread 1: count = 6
←[0m
←[0m[D/main] Thread 1: count = 7
←[0m
←[0m[D/main] Thread 1: count = 8
←[0m
←[0m[D/main] Thread 1: count = 9
←[0m
←[0m[D/main] Thread 1: count = 10
←[0m
←[0m[D/main] Thread 2: Received Events
←[0m
←[0m[D/main] Thread 1: count = 1

You can also read the below tutorials.

Linux Device Driver TutorialsC Programming Tutorials
FreeRTOS TutorialsNuttX RTOS Tutorials
RTX RTOS TutorialsInterrupts Basics
I2C Protocol – Part 1 (Basics)I2C Protocol – Part 2 (Advanced Topics)
STM32 TutorialsLPC2148 (ARM7) Tutorials
PIC16F877A Tutorials8051 Tutorials
Unit Testing in C TutorialsESP32-IDF Tutorials
Raspberry Pi TutorialsEmbedded Interview Topics
Reset Sequence in ARM Cortex-M4BLE Basics
VIC and NVIC in ARMSPI – Serial Peripheral Interface Protocol
STM32F7 Bootloader TutorialsRaspberry PI Pico Tutorials
STM32F103 Bootloader TutorialsRT-Thread RTOS Tutorials
Zephyr RTOS Tutorials – STM32Zephyr RTOS Tutorials – ESP32
AUTOSAR TutorialsUDS Protocol Tutorials
Product ReviewsSTM32 MikroC Bootloader Tutorial
VHDL Tutorials
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Table of Contents