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.
Table of Contents
Prerequisites
This is the continuation of the below tutorials. If you don’t read it, please read that also before this.
- RT-Thread RTOS Introduction
- Getting started with RT-Thread RTOS – Part 1
- Thread management of RT-Thread RTOS – Part 2
- Timer management of RT-Thread RTOS – Part 3
- Thread Synchronization 1 – RT-Thread Tutorial Part 4
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()
Parameters | Description |
---|---|
name | Mutex name |
flag | Mutex flag, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO |
Return | —— |
Mutex handle | Created successfully |
RT_NULL | Creation 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()
Parameters | Description |
---|---|
mutex | The handle of the mutex object, which is provided by the user and points to the memory block of the mutex object |
name | Mutex name |
flag | Mutex flag, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO |
Return | —— |
RT_EOK | Initialization 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()
Parameters | Description |
---|---|
mutex | The handle of the mutex object |
time | Specified waiting time |
Return | —— |
RT_EOK | Successfully obtained mutex |
–RT_ETIMEOUT | Timeout |
-RT_ERROR | Failed 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()
Parameters | Description |
---|---|
name | Name of the event set. |
flag | The flag of the event set that can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO |
Return | —— |
RT_NULL | Creation failed. |
Handle of the event object | Creation 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()
Parameters | Description |
---|---|
event | The handle of the event set object |
name | The name of the event set |
flag | The flag of the event set, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO |
Return | —— |
RT_EOK | Success |
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()
Parameters | Description |
---|---|
event | The handle of the event set object |
set | The flag value of one or more events sent |
Return | —— |
RT_EOK | Success |
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()
Parameters | Description |
---|---|
event | The handle of the event set object |
set | Receive events of interest to the thread |
option | Receive options ( RT_EVENT_FLAG_OR , RT_EVENT_FLAG_AND , RT_EVENT_FLAG_CLEAR ) |
timeout | Timeout |
recved | Point to the received event |
Return | —— |
RT_EOK | Successful |
-RT_ETIMEOUT | Timeout |
-RT_ERROR | Error |
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.
Embedded Software | Firmware | Linux Devic Deriver | RTOS
Hi, I’m SLR. I am a tech blogger and an Embedded Engineer. I am always eager to learn and explore tech-related concepts. And also, I wanted to share my knowledge with everyone in a more straightforward way with easy practical examples. I strongly believe that learning by doing is more powerful than just learning by reading. I love to do experiments. If you want to help or support me on my journey, consider sharing my articles, or Buy me a Coffee! Thank you for reading my blog! Happy learning!