on
2022-09-06
12:52 AM
- edited on
2025-06-26
8:12 AM
by
Laurids_PETERSE
The purpose of this article is to provide a brief explanation with a working example of how to implement and use the Event Flag resources. Although the example is using the STM32G474 Discovery kit, you can apply this knowledge base in any other ST board or microcontroller.
Event flags are a very useful extension to semaphores and mutexes and provide a powerful tool for thread synchronization. Upon creation using the tx_event_flags_create() call, 32 event flags in the group are initialized with zero. Each event flag is represented by a single bit, thus the event has 32 bits. Threads can ask to watch specific bits by OR or AND and they can operate on all 32 event flags in a group at the same time. If these bits are set, the thread is informed and this can be used to synchronize multiple threads.
The image below shows a quick flowchart of the possible and most typical combinations of OR and AND.
Event flags are created by the function tx_eventflags_create.
/* GLOBAL VARIABLES */
TX_EVENT_FLAGS_GROUP event_ptr;
/* In main */
tx_event_flags_create(&event_ptr, "External Interrupt");
The first argument is event flag handle event_ptr. The second argument is event name, in this case: External Interrupt.
The event flags are set by the function tx_event_flags_set. The first argument is the event flags handle. The second argument are the flags to be set. Third argument is the way how the flags are set, either ORed or AND. In the following example, the flag bit 1 (10b) are ORed with event flag content (the AND mode is used mainly to clear event flags already set).
VOID my_thread_entry2(ULONG initial_input)
{
while(1)
{
tx_thread_sleep(50);
tx_event_flags_set(&event_ptr, 0x2, TX_OR);
}
}
For a thread to check if a flag is set or not, we can use a function called tx_event_flags_get.
tx_event_flags_get(&event_ptr, 0x3, TX_AND_CLEAR ,&events, TX_WAIT_FOREVER);
The first argument is the event flag handle event_ptr. The second argument is flags positions desired 0x3 (11b). The third argument is how to get the flags: TX_AND, TX_OR, TX_OR_CLEAR, TX_AND_CLEAR. The fourth argument is a pointer to grab the flag result. The fifty and last argument is the waiting time, ranging from: TX_NO_WAIT (0x00) to TX_WAIT_FOREVER (0xFFFFFFFF). Event flags get with OR:
Event flags get with AND:
Event flags get with OR CLEAR:
Event flags get with AND CLEAR:
To show an implementation of the EF (Event Flag) resource, this article uses the RGB LED and joystick buttons available on the STM32G474 Discovery kit. Hence, an event flag group is created, where each bit is associated with a joystick button. The flags are set by the EXTI (External Interrupt) callback function, which checks which button has been pressed. To manage the LED color, three timer channels in PWM mode are associated with each signal LED pin.
According to the flag set, two threads manage the PWM timers changing the timer capture compare registers, and the following functions are implemented:
The first thread, called by OneColorThread is waiting for an event flag from UP, RIGHT, DOWN or LEFT button. When a flag is set, the thread runs, cleaning the flag and updating the timer capture compare values, that affect the PWM duty cycles changing the LED color.
The second thread, called by RainbowThread runs while the SELECT button flag is set, different from the other thread. This does not clear the flag when run. The flag that corresponds to the SELECT button is never cleared by the EXTI Callback function to update to the next stage.
The second thread should be running continuously, because the PWM duty cycles need to be updated continuously to change the LED color, this is also useful to demonstrate an alternative way of management using the event flag resources. To simplify the code, a lookup table is created to generate a triangle waveform with 30% of time at 0%. So, a 120° phase is applied in each channel according to generate the following waves and colors.
To develop this article’s demo, the following materials are needed:
Let us start creating a new STM32 Project on STM32CubeIDE for the STM32G474RET microcontroller.
Name your project, remember to avoid space and special characters. The name used in this article is “EV_FLAG_G474”.
To import the Azure RTOS to the project, click on “Software Packs”, and then “Select Components”.
In the “Software Packs Component Selector” menu, locate STMicroelectronics.X-CUBE-AZRTOS-G4 → RTOS ThreadX → ThreadX, check the “Core” box and click OK.
Add the Azure RTOS ThreadX checking the box inside Software Packs → STMicroelectronics.X-CUBE-AZRTOS-G4. As this demo is just meant to create small timers and GPIOs managements according to the event flags, the default settings are fine.
By default, the HAL driver uses the SysTick as its primary time base, but this timer should be left to the AzureRTOS only. If the HAL library does not have a separate time source, the compilation and code execution might fail because both libraries want to use the SysTick_Handler interrupt. To prevent this, 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:
Go to clock configurations and adjust the settings as shown in the image to achieve the HLCK at 170 MHz.
Configure the following pins as table shown:
Component | Pin | Configuration | Label |
Joystick Select | PC13 | GPIO External Interrupt mode with Falling Edge trigger detection and pull-up | JOYSTICK_SELECT |
Joystick Up | PB10 | JOYSTICK_UP | |
Joystick Right | PB2 | JOYSTICK_RIGHT | |
Joystick Down | PC5 | JOYSTICK_DOWN | |
Joystick Left | PC4 | JOYSTICK_LEFT | |
RGB LED Red | PC6 | TIM3_CH1 | PWM_RED |
RGB LED GREEN | PC8 | TIM3_CH3 | PWM_GREEN |
RGB LED BLUE | PA8 | TIM1_CH1 | PWM_BLUE |
Enable the GPIO External interrupts on NVIC tab and give them a 14 Preemption Priority (this priority will be 1 level lower than thread priorities).
Set timer 1 and 3 configurations as the following settings:
The other settings are default values.
The last step before the code generation is to enable the “Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral” in Project Manager → Code Generator. This should be helpful to HAL functions and resources management.
All set, we can press Alt+K to generate the code or click on the following button.
After generation, locate and open the app_threadx.c file. There is inside Core → Src folder on project files. In this file, let us start including some headers to allow the access of HAL Timer and GPIO configurations.
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "main.h"
#include "tim.h"
#include "gpio.h"
/* USER CODE END Includes */
Create some masks for each event button, as it is helpful to manage the event flags. The EV_UNION_MSK it is a union between directional buttons events (up, right, down, and left buttons).
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define EV_SELECT_MSK 0b00000001
#define EV_UP_MSK 0b00000010
#define EV_RIGHT_MSK 0b00000100
#define EV_DOWN_MSK 0b00001000
#define EV_LEFT_MSK 0b00010000
#define EV_UNION_MSK (EV_UP_MSK | EV_RIGHT_MSK | EV_DOWN_MSK | EV_LEFT_MSK)
#define THREAD_STACK_SIZE 1024
/* USER CODE END PD */
Since this demo uses 2 threads to manage the RGB Color LED, we need to create 2 stack buffers, 2 thread pointers and 2 thread prototype functions OneColorThread and RainbowThread. If you need more information about the ThreadX resources, refer to: How to create a Thread using AzureRTOS and STM32CubeIDE
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint8_t thread_stack1[THREAD_STACK_SIZE];
uint8_t thread_stack2[THREAD_STACK_SIZE];
TX_THREAD threadOneColor_ptr;
TX_THREAD threadRainbow_ptr;
TX_EVENT_FLAGS_GROUP event_ptr;
const uint8_t lookupTABLE[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
VOID RGBColorChange(uint8_t RED_PWM, uint8_t GREEN_PWM, uint8_t BLUE_PWM);
VOID OneColorThread(ULONG initial_input);
VOID RainbowThread(ULONG initial_input);
/* USER CODE END PFP */
The RGBColorChange function changes the LED color according to the parameters sent, this updates the PWM duty cycles changing the pulse compare timer registers. It was implemented to avoid repeated code (this function could be placed into user code region 1, that is, between user code begin/end 1).
VOID RGBColorChange(uint8_t RED_PWM, uint8_t GREEN_PWM, uint8_t BLUE_PWM)
{
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, RED_PWM); /* RED PWM */
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, GREEN_PWM); /* GREEN PWM */
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, BLUE_PWM); /* BLUE PWM */
}
In the App_ThreadX_Init function starts the three PWM channels, create the event flags using tx_event_flags_create function and two threads with tx_thread_create.
UINT App_ThreadX_Init(VOID *memory_ptr)
{
UINT ret = TX_SUCCESS;
TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr;
/* USER CODE BEGIN App_ThreadX_MEM_POOL */
(void)byte_pool;
/* USER CODE END App_ThreadX_MEM_POOL */
/* USER CODE BEGIN App_ThreadX_Init */
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
tx_event_flags_create(&event_ptr, "External Interrupt");
tx_thread_create(&threadOneColor_ptr, "Change Color thread", OneColorThread, 0x1234, thread_stack1, THREAD_STACK_SIZE, 15, 15, 1, TX_AUTO_START);
tx_thread_create(&threadRainbow_ptr, "Rainbow thread", RainbowThread, 0x1234, thread_stack2, THREAD_STACK_SIZE, 15, 15, 1, TX_AUTO_START);
/* USER CODE END App_ThreadX_Init */
return ret;
}
Redeclare the HAL_GPIO_EXTI_Callback function in the app_threadx file (it could also be placed into user code region 1). In this function, clear the SELECT button event to update the next color that should be shown. Add a switch case to check what button was pressed and set their respective event flag.
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
ULONG event;
tx_event_flags_get(&event_ptr, EV_SELECT_MSK, TX_OR_CLEAR, &event, TX_NO_WAIT);
switch(GPIO_Pin)
{
case JOYSTICK_SELECT_Pin:
tx_event_flags_set(&event_ptr, EV_SELECT_MSK, TX_OR);
break;
case JOYSTICK_UP_Pin:
tx_event_flags_set(&event_ptr, EV_UP_MSK, TX_OR);
break;
case JOYSTICK_RIGHT_Pin:
tx_event_flags_set(&event_ptr, EV_RIGHT_MSK, TX_OR);
break;
case JOYSTICK_DOWN_Pin:
tx_event_flags_set(&event_ptr, EV_DOWN_MSK, TX_OR);
break;
case JOYSTICK_LEFT_Pin:
tx_event_flags_set(&event_ptr, EV_LEFT_MSK, TX_OR);
break;
}
}
Now, create the OneColorThread function. This thread should wait for an event flag according to the Event Union Mask. Whenever an event flag is set by a directional button pressing, this thread clears that and check what flag was set. According to this flag, the thread changes the LED color calling the RGBColorChange.
VOID OneColorThread(ULONG initial_input)
{
ULONG event;
while(1)
{
tx_event_flags_get(&event_ptr, EV_UNION_MSK, TX_OR_CLEAR, &event, TX_WAIT_FOREVER);
if(event & EV_LEFT_MSK) /* Turn LED Off */
{
RGBColorChange(0, 0, 0); /* RED = 0, GREEN = 0, BLUE = 0 */
}
else if(event & EV_UP_MSK) /* Turn LED RED */
{
RGBColorChange(10, 0, 0); /* RED = 10, GREEN = 0, BLUE = 0 */
}
else if(event & EV_RIGHT_MSK) /* Turn LED GREEN */
{
RGBColorChange(0, 10, 0); /* RED = 0, GREEN = 10, BLUE = 0 */
}
else if(event & EV_DOWN_MSK) /* Turn LED BLUE */
{
RGBColorChange(0, 0, 10); /* RED = 0, GREEN = 0, BLUE = 10 */
}
}
}
The RainbowThread function it is a thread that manages the colors when the LED is set to a rainbow mode. This thread runs while the SELECT event button is set because this flag is not cleared in the get function. To change the color, add a counter and use this as the index of the lookup table to send the parameters to the RGBColorChange function. Finally, add a logic to give a phase between the LED signals as the following code:
VOID RainbowThread(ULONG initial_input)
{
ULONG event;
uint8_t count = 0;
while(1)
{
tx_event_flags_get(&event_ptr, EV_SELECT_MSK, TX_OR, &event, TX_WAIT_FOREVER);
if(count < 16)
{
RGBColorChange(lookupTABLE[count], lookupTABLE[count + 16], lookupTABLE[count + 32]);
count++;
}
else if(count < 32)
{
RGBColorChange(lookupTABLE[count], lookupTABLE[count + 16], lookupTABLE[count - 16]);
count++;
}
else if(count < 48)
{
RGBColorChange(lookupTABLE[count], lookupTABLE[count - 32], lookupTABLE[count - 16]);
count ++;
}
else
{
RGBColorChange(lookupTABLE[count], lookupTABLE[count - 32], lookupTABLE[count - 16]);
count = 0;
}
tx_thread_sleep(15);
}
}
This is all the necessary code. So, press Ctrl + B to build the project and check if no one errors or warnings occurs. Connect the board to the computer and enter in debug mode clicking on the following button.
In the Debug Configurations menu, let the standard settings and then click on debug. Once in the Debug perspective, click on “Window → Show view → ThreadX → ThreadX Event Flags”.
It should open a tab at a bottom window group of STM32CubeIDE.
Run the code, test the demo by pressing the buttons and enjoy the RGB LED behavior. Pause the code in the Debug, and check the ThreadX event flags tab. In this tab, you can see the created event groups, their actual flags counting, and the threads that interact with that. Since this code clears the flag after the color update, the flag should be zero if you pause the code when the LED is not in rainbow mode. Furthermore, the threads are suspended because these are waiting for an event.
If you pause the code during the rainbow mode, you see a flag set. The Change Color thread is suspended and the Rainbow thread runs continuously, updating the LED color.
The code was built to change the LED color according to the following button events:
The following image shows the behavior of the OS, it is possible to check how the application works.
In the first block, we have the kernel initialization, the Event Creation (EC), two Thread Creations (TC) and after their first Event Gets (EG) as programmed.
The second block, we can see an EG at the interrupt level (it is the event get inside the EXTI callback function, that is, a button was pressed), then an ES that set a flag bit (also inside the EXTI callback). After the flag is set, the Change Color Thread runs, gets and clears the flag, changes the LED color, and then enters in suspend mode waiting for a new Event. This cycle occurs four times, the same as shown in the previous image.
The third block shows the AzureRTOS kernel behavior when the LED is in Rainbow mode. At the beginning of the block, we have an event get and set (in the EXTI) and then the Rainbow thread starts to run. As we can see, this thread runs continuously because it does not clear the flag to keep the thread running and updating the color of the LED.
This graph was made by the TraceX feature. If you need more information about TraceX and a tutorial showing how to implement this, refer to: How can I add TraceX support in STM32CubeIDE?
B.Montanari's KB articles are normally excellent and I've learned a lot from reading many of of them. This one, however, fails to provide basic and crucial context so it's not entirely clear what the article is even about (unless you already knew what event flags are to begin with).
Issue 1: "Event Flags" are a software abstraction provided by the threadx RTOS which ST has been increasingly transitioning it's offering towards. Note that (currently) the words "threadx" or "RTOS" don't actually appear anywhere in this article. Nonetheless, one hint is that all the API functions mentioned are named with a "tx_" prefix, since that is part of the code convention for threadx APIs.
Issue 2: "Event flags" are NOT a hardware feature. You will find no mention of them in the RM for the G4 family, which is the MCU used in the article. Various STM32 register do include "Events" which serve as a way of communicating, but they are not not what this article is about.
Would be great to have links to other parts of this article:
https://community.st.com/t5/stm32-mcus/how-does-the-event-flag-work-on-stm32-part-2/ta-p/49386
https://community.st.com/t5/stm32-mcus/how-does-the-event-flag-work-on-stm32-part-3/ta-p/49388
@BarryWhit wrote:
"... the threadx RTOS which ST has been increasingly transitioning it's offering towards"
ST has now (May 2025) reversed that - going back to FreeRTOS:
New strategic directions for STM32Cube
FreeRTOS replacing ThreadX as the main kernel - May 2025 STM32 Summit
Hello all,
Thank you very much for your feedback.
We have merged the articles and specified the title.
Best regards,
Laurids