Thread Synchronization – RT-Thread Tutorial Part 4

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 Timers in the RT-Thread RTOS. In this post, we will see the RT-Thread Thread Synchronization.

You can see the video explanation of Getting started 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

RT-Thread Interthread Synchronization Tutorial

Thread Synchronization

When you work in a multithread or multitasking environment, you need to synchronize the thread or task. That is really needed. Why is it? We have already discussed this concept in this post. So we are not stepping into this topic.

Types of thread synchronization

There are many ways to synchronize threads.

In this post, we will see the critical section and Semaphore. In our next post, we will see Mutex and Events.

Critical Section

In two ways we can enter into the critical section.

  • By disabling the hardware interrupt
  • By using the APIs

Disabling the hardware interrupt

We can call the rt_hw_interrupt_disable() to enter the critical section and call rt_hw_interrupt_enable() to exit the critical section. When we disable the hardware interrupts, the context switch will not happen. So, the other task won’t get the chance to run. So only one thread can run at a time and access the common resource. Once that thread is done, then it should re-enable the hardware interrupt.

By using the APIs

RT-Thread has special APIs to enter and exit the critical section. Call rt_enter_critical() to enter the critical section and call rt_exit_critical() to exit the critical section.

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, we have used rt_enter_critical() and rt_exit_critical().

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_thread_t threadId1 = RT_NULL;
static rt_thread_t threadId2 = RT_NULL;

/* Thread 1 */
static void thread1_function(void *parameter)
{
    while (1)
    {
        //rt_hw_interrupt_disable();
        rt_enter_critical();

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

        //rt_hw_interrupt_enable(0);
        rt_exit_critical();

        rt_thread_mdelay(1000);
    }
}

/* Thread 2 */
static void thread2_function(void *parameter)
{
    while (1)
    {
        //rt_hw_interrupt_disable();
        rt_enter_critical();

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

        //rt_hw_interrupt_enable(0);
        rt_exit_critical();


        rt_thread_mdelay(1000);
    }
}


int main(void)
{
    /* 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 Feb 18 2023 17:43:20
 2006 - 2022 Copyright by RT-Thread team
[D/main] Thread 1: global_var = 11

[D/main] Thread 2: global_var = 10

[D/main] Thread 1: global_var = 11

[D/main] Thread 2: global_var = 10

[D/main] Thread 1: global_var = 11

[D/main] Thread 2: global_var = 10

[D/main] Thread 1: global_var = 11

[D/main] Thread 2: global_var = 10

[D/main] Thread 1: global_var = 11

[D/main] Thread 2: global_var = 10

[D/main] Thread 1: global_var = 11

[D/main] Thread 2: global_var = 10

[D/main] Thread 1: global_var = 11

[D/main] Thread 2: global_var = 10

[D/main] Thread 1: global_var = 11

[D/main] Thread 2: global_var = 10

[D/main] Thread 1: global_var = 11

[D/main] Thread 2: global_var = 10

Semaphores

You can see the complete details about the semaphores here.

Create and Delete the semaphores

We can use the below API to create the semaphore dynamically.

rt_sem_t rt_sem_create(const char *name,
                        rt_uint32_t value,
                        rt_uint8_t flag);

When this function is called, the system will first allocate a semaphore object from the object manager and initialize the object.

Parameters Description
name Semaphore Name
value Semaphore Initial Value
flag Semaphore flag, which can be the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO
Return ——
RT_NULL Creation failed
semaphore control block pointer Creation successful

The semaphore flag parameter determines the queuing way of how multiple threads wait when the semaphore is not available. When the RT_IPC_FLAG_FIFO (first in, first out) mode is selected, the waiting thread queue will be queued in a first in first out manner. The first thread that goes in first obtains the waiting semaphore. When the RT_IPC_FLAG_PRIO (priority waiting) mode is selected, the waiting threads will be queued in order of priority. Threads waiting with the highest priority will get the wait semaphore first.

You can use the rt_err_t rt_sem_delete(rt_sem_t sem) function to delete the semaphore, if you have created the semaphore using the rt_sem_create().

You can create the static semaphore using the below function interface. When this function is called, the system will initialize the semaphore object.

rt_err_t rt_sem_init(rt_sem_t       sem,
                    const char     *name,
                    rt_uint32_t    value,
                    rt_uint8_t     flag)
Parameters Description
sem Semaphore object handle
name Semaphore name
value Semaphore initial value
flag Semaphore flag, which can be the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO
Return ——
RT_EOK Initialization successful

You can use the rt_err_t rt_sem_detach(rt_sem_t sem) function to detach the semaphore, if you have initialized it using the rt_sem_init() function.

Take and Release the Semaphore

When the thread wants to access the common resource, then it has to take the semaphore. The below function interface is used to do that.

rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time);

When the semaphore value is greater than zero, the thread will get the semaphore, and the corresponding semaphore value will be reduced by 1. When calling this function, if the value of the semaphore is zero, it means the current semaphore resource instance is not available, and the thread applying for the semaphore will wait based on the time parameter. While waiting, if other threads or ISR released the semaphore, then the thread will stop the waiting. If the semaphore is still not available after parameter specified time, the thread will time out and return, the return value is -RT_ETIMEOUT

If you don’t want to wait, then you can call the function with time parameters as 0 like rt_sem_take(sem, 0). You can use this rt_sem_trytake(rt_sem_t sem) function also if you don’t want to wait.

Once you are done with the resources, then the thread can release the Semaphore. You can release the semaphore using the below function interface.

rt_err_t rt_sem_release(rt_sem_t sem);

When you do that, it will wake up the other threads who is waiting or blocking for this semaphore. If no thread is waiting for this, it will increment the semaphore count by 1.

Example Program

In the below program, I have created two threads. Thread 1 will read the Button state which is connected to the PA0. If the button is pressed, then it will release the semaphore. In the meantime, thread 2 will be waiting for the semaphore. Once thread 1 releases the semaphore, thread 2 will blink the LED which is connected to the PC13.

You can get this complete project 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 INPUT_PIN   GET_PIN(A, 0)       //PA0
#define LED_PIN     GET_PIN(C, 13)      //PC13

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

static rt_sem_t semaphore_sync = RT_NULL;

/* Thread 1 */
static void thread1_function(void *parameter)
{
    rt_pin_mode( INPUT_PIN , PIN_MODE_INPUT_PULLUP);

    while (1)
    {
        if( rt_pin_read(INPUT_PIN) == 0 )
        {
            LOG_D("Button Pressed - Released the Semaphore!!!\n");

            /* Release the semaphore */
            rt_sem_release(semaphore_sync);
        }
        rt_thread_mdelay(200);
    }
}

/* Thread 2 */
static void thread2_function(void *parameter)
{
    rt_pin_mode( LED_PIN , PIN_MODE_OUTPUT);
    rt_pin_write(LED_PIN, PIN_HIGH);

    while (1)
    {
        rt_sem_take(semaphore_sync, RT_WAITING_FOREVER);
        LOG_D("Got Semaphore - Blink the LED!!!\n");

        rt_pin_write(LED_PIN, PIN_LOW);
        rt_thread_mdelay(2000);
        rt_pin_write(LED_PIN, PIN_HIGH);
        rt_thread_mdelay(1000);
    }
}


int main(void)
{
    /* Create thread 1 */
    threadId1 = rt_thread_create("thread1_fn",  //Name
                            thread1_function,   //Function address
                            RT_NULL,            //Thread function parameter
                            1024,               //Stack Size
                            5,                  //Thread priority
                            10);                //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
                            5,                  //Thread priority
                            10);                //Time slice in ticks

    /* Create the Semaphore */
    semaphore_sync = rt_sem_create( "Semaphore_Sync",            //Name
                                    0,                           //Initial value
                                    RT_IPC_FLAG_FIFO );          //Flag

    if( semaphore_sync == RT_NULL )
    {
        LOG_D("Semaphore Creation Failed!!!\n");
        return RT_EOK;
    }

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

    while(1);

    return RT_EOK;
}

Output

What’s Next

In our next tutorial, we will see another method of RT-Thread Inter thread synchronization called a mutex and event.

You can also read the below tutorials.

Linux Device Driver Tutorials C Programming Tutorials
FreeRTOS Tutorials NuttX RTOS Tutorials
RTX RTOS Tutorials Interrupts Basics
I2C Protocol – Part 1 (Basics) I2C Protocol – Part 2 (Advanced Topics)
STM32 Tutorials LPC2148 (ARM7) Tutorials
PIC16F877A Tutorials 8051 Tutorials
Unit Testing in C Tutorials ESP32-IDF Tutorials
Raspberry Pi Tutorials Embedded Interview Topics
Reset Sequence in ARM Cortex-M4 BLE Basics
VIC and NVIC in ARM SPI – Serial Peripheral Interface Protocol
STM32F7 Bootloader Tutorials Raspberry PI Pico Tutorials
STM32F103 Bootloader Tutorials RT-Thread RTOS Tutorials
Zephyr RTOS Tutorials – STM32 Zephyr RTOS Tutorials – ESP32
AUTOSAR Tutorials UDS Protocol Tutorials
Product Reviews STM32 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