cancel
Showing results for 
Search instead for 
Did you mean: 

How does the ThreadX Semaphore work

B.Montanari
ST Employee

How does the ThreadX Semaphore work?

ThreadX provides 32-bit counting semaphores that range in value between 0 and 4,294,967,295. There are two operations for counting semaphores: tx_semaphore_get and tx_semaphore_put. The get operation decreases the semaphore by one. If the semaphore is 0, the get operation is not successful. The inverse of the get operation is the put operation. It increases the semaphore by one. Each counting semaphore is a public resource. ThreadX places no constraints on how counting semaphores are used. Counting semaphores are typically used for mutual exclusion. However, counting semaphores can also be used as a method for event notification.

1. Goal

The purpose of this article is to provide a brief explanation with a working example on how the semaphore works

Although the example is using the NUCLEO-H723ZG, you can use the same steps for other STM32H7 based boards. The main differences are usually pinout and clock configuration.
This article will show you how to start a project from scratch, with the goal of getting 3 Threads with same priorities to showcase the usage of a counting semaphore to only print a given message upon receiving the semaphore token twice. In this demo, the Thread3 will wait until Thread1 and Thread2 uses the tx_semaphore_put function to synchronize the printf message
Demo block diagram is shown here in the following animation:
249.gif

2. STM32CubeIDE – step by step demo:

Launch STM32CubeIDE (version used 1.7.0)

Select your preferred workspace path, click on Launch

In the information Center, select to Start New STM32 Project
For this demo, we’ll use the NUCLEO-H723ZG as the starting point:

250.png

Give your project a name, just remember to avoid space and special characters – if you are missing any libraries, the STM32CubeIDE will automatically start the download now

251.png
 

A pop up asking if we should initialize the peripherals in the default mode, click "Yes". Then second popup to ask to open device configuration perspective, click "Yes" again

Time to add the software pack:

252.png

Browse and locate the AzureRTOS pack in the component selection window:

253.png

Open the RTOS ThreadX and check the Core box and click OK

254.png

This will add the Software Pack in the lower part of the Categories. Now, by clicking on it, you can add the RTOS ThreadX box, this will show the configuration options for the AzureRTOS application

255.png

As this demo is just meant to create small prints to indicate which thread it is, the default settings are alright, but we do need to do one other modification. By default, the HAL driver will use the Systick as its primary time base, but this timer should be left to the AzureRTOS only. To prevent this conflict, we can simply select a different time base for the HAL by clicking in the System Core/SYS and selecting the time base Source as TIM6:

256.png

One final modification, to prevent generating too much unused code, we can decide to not create the functions for USB and Ethernet, as they were natively added when we created the design based on the board, this can be done by removing the Generate Code for them:

257.png

All set, we can press Alt+K to generate the code and Ctrl+S to save it:

258.png
 

The last part is to create our demo code. Open Core\Src \app_threadx.c – Here we’ll create the 3 threads using the command tx_thread_create and the semaphore, by using tx_semaphore_create.

All 3 threads are quite simple, where Thread1 and Thread2 are sleeping for 2 seconds and then using the tx_semaphore_put in an endless loop. The Thread3 will use the tx_semaphore_get twice in a row, to wait for both threads to share their semaphore token before it can use the printf. You can copy and paste the code below - Please use the "USER CODE BEGIN" as a reference to paste in the generated code:

/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */

/* USER CODE BEGIN PD */
#define THREAD_STACK_SIZE   512
/* USER CODE END PD */

/* USER CODE BEGIN PV */
uint8_t thread_stack1[THREAD_STACK_SIZE];
uint8_t thread_stack2[THREAD_STACK_SIZE];
uint8_t thread_stack3[THREAD_STACK_SIZE];
TX_THREAD thread_ptr1;
TX_THREAD thread_ptr2;
TX_THREAD thread_ptr3;
TX_SEMAPHORE semaphore_0;
/* USER CODE END PV */

/* USER CODE BEGIN PFP */
VOID Thread1 (ULONG initial_input);
VOID Thread2 (ULONG initial_input);
VOID Thread3 (ULONG initial_input);
/* USER CODE END PFP */

/* USER CODE BEGIN App_ThreadX_Init */
tx_semaphore_create(&semaphore_0, "semaphore 0", 1);
tx_thread_create(&thread_ptr1,"Thread1",Thread1, 0, thread_stack1,THREAD_STACK_SIZE,15,15,1,TX_AUTO_START);
tx_thread_create(&thread_ptr2,"Thread2",Thread2, 0, thread_stack2,THREAD_STACK_SIZE,15,15,1,TX_AUTO_START);
tx_thread_create(&thread_ptr3,"Thread3",Thread3, 0, thread_stack3,THREAD_STACK_SIZE,15,15,1,TX_AUTO_START);
/* USER CODE END App_ThreadX_Init */

/* USER CODE BEGIN 1 */
VOID Thread1 (ULONG initial_input){
/* Infinite loop */
for(;;){
  /* Sleep for 2seconds */
  tx_thread_sleep(200);
  printf("Thread1 Release counting semaphore\r\n");
  /* Release the semaphore.  */
  tx_semaphore_put(&semaphore_0);
}
}
VOID Thread2 (ULONG initial_input){
for(;;){
  /* Sleep for 2seconds */
  tx_thread_sleep(200);
  printf("Thread2 Release counting semaphore\r\n");
  /* Release the semaphore.  */
  tx_semaphore_put(&semaphore_0);
}
}
VOID Thread3 (ULONG initial_input){
for(;;){
  /* Get the semaphore with suspension.  */
  tx_semaphore_get(&semaphore_0, TX_WAIT_FOREVER);
  /* Get the semaphore with suspension.  */
  tx_semaphore_get(&semaphore_0, TX_WAIT_FOREVER);
  printf("Thread3 synchronized\r\n");
}
}
/* USER CODE END 1 */

Open Core\Src \main.c – you can copy and paste the code below, but please notice the" USER CODE BEGIN", to properly place the code snippet:

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#define USE_UART_PRINT
void __io_putchar(char ch) {
#ifdef USE_UART_PRINT
    // Code to write character 'ch' on the UART
    HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 10);
#else
    ITM_SendChar(ch);
#endif
}
/* USER CODE END 0 */

From this point, press Ctrl+B to build and you should see 0 errors and 0 warnings.

Connect the board to the computer and enter in debug mode by first clicking in the project name, then menu Run/Debug As/STM32:

259.png

In Edit Configuration window, click Debug:

260.png

Once in the Debug perspective, click on the resume to enjoy your ThreadX application with semapahore working. You can also use the Window/Show View/ThreadX/ThreadX Thread List and do it again for the ThreadX Semaphores to enhance the overall debugging options (the thread list will be automatically loaded once the application runs for a bit and is paused):

261.png

Now, the last step is to enable the console inside the STM32CubeIDE to see the print messages. Locate the Console tab and click Command Shell Console:


262.png

In the pop-up window, change the Connection Type to Serial Port and click on New… to add the VCOM related to your STLINK:


265.png

You can give the port a name, so it is easier for later usage as well, in this case, it will be labelled as NUCLEO_H723ZI:


269.png

By clicking finish, the message on the console will indicate it is now connected, by resuming the application, this is the print display:


272.png

3. Useful links:

4. Conclusion:​​​​​​​

Adding semaphore to a ThreadX application consists of:

    • Adding the ThreadX component in STM32CubeMX/STM32CubeIDE

    • Generate the code from STM32CubeMX/ STM32CubeIDE

    • Use ThreadX API to create ThreadX components thread and semaphore

Comments
'SMike
Associate III

Is the semaphore release scheduled, or immediate? For instance I put within an interrupt, will the get statement unblock immediately?

B.Montanari
ST Employee

Hi @'SMike​ . The tx_semaphore_put() will happen immediate, so you can have its call inside a thread or interrupt and by the time the function execution is completed, the semaphore is already incremented. It is worth mentioning that the tx_semaphore_put will disable the interrupts as the first action when entering in it and will re-enable it once the semaphore action is completed and the function returns. The overall time it takes for this to be completed is in the few hundreds of nanoseconds, so this shouldn't jeopardize any activities. Another point that might be interesting is to comment that there is a function called tx_semaphore_put_notify(), which, as the name implies, will notify via a callback function once the semaphore is present.

Still inside the put function, there is a check to see if there are any suspended threads due to this semaphore and if there are, the _tx_thread_system_resume() will be called pointing to the first suspended thread. If multiple threads are suspended on the same counting semaphore, they are resumed in the same order they were suspended (FIFO). However, priority resumption is also possible if the application calls tx_semaphore_prioritize prior to the semaphore put call that lifts thread suspension. The semaphore prioritize service places the highest priority thread at the front of the suspension list, while leaving all other suspended threads in the same FIFO order.

Using ISR with services can't create some pitfalls if not managed properly, so I do recommend checking the end of chapter 3, there is a (Interrupts

topic)> Chapter 3 - Functional Components of Azure RTOS ThreadX | Microsoft Learn , with an interesting disclosure on how ThreadX Managed Interrupts. You can also see an example for semaphores and interrupt here> Chapter 5 - Device Drivers for Azure RTOS ThreadX | Microsoft Learn

Best Regards

Version history
Last update:
‎2021-12-14 07:43 AM
Updated by: