FB pixel

What is an Event Flag in Azure RTOS ThreadX and How to Use It | Renesas RA - 24

Published


Hi there! In this tutorial we will consider one more service for the threads interconnection. We already have studied semaphores and mutexes, and now we will discover event flags. They are used for the same purpose as the other two options - to synchronize the threads. But there are certain differences which we will consider.

Introduction into the Event Flags

Each event flag is presented with a single bit. 32 event flags are grouped into one 32-bit variable called “group”, to which the set and get operations can be applied (as you remember, for mutexes and semaphores we used the put and get operations). When implementing these operations the thread can set or get any number of flags within one 32-bit group.

When setting the flags within a group, you can use the OR or AND operation between them. This works as expected with the usual logic operations. If you select the OR operation, the new flags will be ORed with the current group, and the flags that are already set will remain unchanged. If you select the AND type, the AND operation will be implemented between the previous group value and the new flags.

When getting the flags, there are four options: OR, OR and Clear, AND, AND and Clear. If you select the OR option, the thread will resume if any requested flag of the group is set. If you select the AND option, the thread will resume if all requested flags are set. If you select the Clear option (whichever OR or AND), the flags that meet the request will be reset after reading.

When you set any flag of the group, the scheduler will check all threads that want to get the event flags from this group, even if this flag is not requested by these threads. This may take a lot of time, so it’s not recommended to synchronize a lot of threads using the same event flag group.

Also, for the event flags there is an option called “set notification”. You can create the callback function in your code, and it will be invoked every time when any flag is set. In this callback, you can resume the required threads when the flags requested by them are set, so this can save time in case of using many threads.

Let’s now see how the value of the event flags group changes when we use different types of setting and getting the flags.

Table 1 - Illustration of the operations with the event flags

Operation

Event flags group value

Initial state

0b00000000000000000000000000000000

set OR 0b0000000001010101

0b00000000000000000000000001010101

set OR 0b0000000001111111

0b00000000000000000000000001111111

set AND 0b0000001110000111

0b00000000000000000000000000000111

get OR 0b0000000001010101 (Success)

0b00000000000000000000000000000111

get AND 0b0000000001010101 (Fail)

0b00000000000000000000000000000111

get AND Clear 0b0000000001010101 (Fail)

0b00000000000000000000000000000010

get OR Clear 0b0000000001010101 (Fail)

0b00000000000000000000000000000010

In this table I marked the flags that were changed because of the set and get operations with yellow, the flags that don’t meet the request with red, and the flags that meet the request with green.

To demonstrate the operation of the event flags we will use the same setup as the previous tutorial (Figure 1).

Figure 1 - Schematic diagram of the test setup
Figure 1 - Schematic diagram of the test setup

Here we will use the buttons to set the event flags and use the LEDs to indicate when getting the flag is successful by flashing an LED once for 1 second. The correspondence between the buttons, LEDs and operations with the event flags is present in table 2.

Table 2 - Correspondence between the buttons, LEDs and operations with the event flags

Part

Operation

S1

set OR 0b0001

S2

set OR 0b0010

S3

set OR 0b0100

S4

set AND 0b0011

LED1

get OR Clear 0b0011

LED2

get AND Clear 0b0101

LED3

get OR 0b0100

As you see, buttons S1-S3 set just one flag which corresponds to their number using the OR function, which means that no flags will be cleared when these buttons are pressed. S4 button sets flags #1 and #3 using the AND function. This means that the flag #3 will be cleared and flags #1 and #2 will remain unchanged.

LED1 will turn on when either flag #1 or flag #2 is set, after which the flags will be cleared.

LED2 will turn on when both flag #1 and flag #3 are set, after which the flags will be cleared.

LED3 will turn on when only flag #3 is set. Actually, as here we check only one flag, both OR or AND options will work well.

Test Program Description

Let’s now open e2 studio and create a new project based on the Azure RTOS ThreadX and the Minimal template (like we did in the previous tutorial), and call it “threadx_event_flags”. In the FSP Configurator window, switch to the “Pins” tab, as we first need to set up all the pins to which external parts are connected.

All the pins to which the push buttons are connected (P301, P302, P410, P015) should be configured as follows (Figure 2).

Figure 2 - Configuration of the P301 pin
Figure 2 - Configuration of the P301 pin

We should set the “Mode” field as “Input mode” and “Pull up” field as “input pull-up”. Also, we need to set the “Symbolic Name” field for the pins according to Figure 1: P301 - “S1”, P302 - “S2”, P410 - “S3”, P015 - “S4”. In the code we will use these names to address the buttons.

The pins to which LEDs are connected (P107, P106, P002) should be configured as follows (Figure 3).

Figure 3 - Configuration of the P107 pin
Figure 3 - Configuration of the P107 pin

Here we should change the “Mode” to “Output mode (Initial Low)” and give the symbolic names to pins: P107 - “LED1”, P106 - “LED2”, P002 - “LED3”. As for the “Drive capacity” field, we can leave it “Low” as with the 1kOhm series resistor, the output current will be quite low.

Now, as the pin configuration is completed, let’s switch to the “Stacks” tab and create the new thread. Then configure it as follows (Figure 3).

Figure 4 - Configuration of Button Thread
Figure 4 - Configuration of Button Thread

In the common ThreadX settings (all lists except for “Thread”) we need only to enable the Event Trace in order to use the TraceX tool, and decrease the TraceX buffer size from 65536 to 16384.

In the thread settings we only will change its Symbol to “buttons_thread” and Name to “Buttons Thread”.

Now we need to create three more threads - “LED1 Thread”, “LED2 Thread”, and “LED3 Thread”, each of which will get the corresponding event flag and turn on the LED for 1 second according to table 2. The properties of LED1 Thread are shown in Figure 4.

Figure 5 - Configuration of LED1 Thread
Figure 5 - Configuration of LED1 Thread

The configuration of LED2 Thread and LED3 Thread are the same, you just need to change the digit in the “Symbol” and “Name” fields. Please pay attention that the priority of these threads is 1. This is done to prevent processing the buttons when an LED is on.

Now let’s create the new event flags. To do this click the “New object >” button and select the “Event Flags” in the drop-down list (Figure 5).

Figure 6 - Adding new Event Flags
Figure 6 - Adding new Event Flags

As you can see, event flags don’t have any specific properties - just Symbol and Name. Let’s change them according to Figure 6.

Figure 7 - Configuration of the event flags
Figure 7 - Configuration of the event flags

These are all configurations that need to be done. Finally the “Stacks” tab should look like the following (Figure 7).

Figure 8 - Stacks configuration of the project
Figure 8 - Stacks configuration of the project

Now we can press the “Generate Project Content” button and switch to the “src” folder. Let’s start with the “buttons_thread_entry.c” file.

#include "buttons_thread.h"

/* Buttons Thread entry function */

void buttons_thread_entry(void)

{

while (1)

{

if (R_BSP_PinRead(S1) == BSP_IO_LEVEL_LOW) //If S1 button is pressed

{

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S1) == BSP_IO_LEVEL_LOW) //If S1 button is still pressed

{

while (R_BSP_PinRead(S1) == BSP_IO_LEVEL_LOW); //Wait while it is pressed

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S1) == BSP_IO_LEVEL_HIGH) //If S1 button is released

{

tx_event_flags_set(&g_my_event_flags, 0b0001, TX_OR); //Set event flags

}

}

}

if (R_BSP_PinRead(S2) == BSP_IO_LEVEL_LOW) //If S2 button is pressed

{

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S2) == BSP_IO_LEVEL_LOW) //If S2 button is still pressed

{

while (R_BSP_PinRead(S2) == BSP_IO_LEVEL_LOW); //Wait while it is pressed

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S2) == BSP_IO_LEVEL_HIGH) //If S2 button is released

{

tx_event_flags_set(&g_my_event_flags, 0b0010, TX_OR); //Set event flags

}

}

}

if (R_BSP_PinRead(S3) == BSP_IO_LEVEL_LOW) //If S3 button is pressed

{

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S3) == BSP_IO_LEVEL_LOW) //If S3 button is still pressed

{

while (R_BSP_PinRead(S3) == BSP_IO_LEVEL_LOW); //Wait while it is pressed

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S3) == BSP_IO_LEVEL_HIGH) //If S3 button is released

{

tx_event_flags_set(&g_my_event_flags, 0b0100, TX_OR); //Set event flags

}

}

}

if (R_BSP_PinRead(S4) == BSP_IO_LEVEL_LOW) //If S4 button is pressed

{

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S4) == BSP_IO_LEVEL_LOW) //If S4 button is still pressed

{

while (R_BSP_PinRead(S4) == BSP_IO_LEVEL_LOW); //Wait while it is pressed

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S4) == BSP_IO_LEVEL_HIGH) //If S4 button is released

{

tx_event_flags_set(&g_my_event_flags, 0b0011, TX_AND); //Set event flags

}

}

}

}

}

Even though the code is relatively long, it consists of four very similar parts in which the pressing of the buttons are processed: S1 - lines 8-20, S2 - lines 22-24, S3 - lines 36-48, S4 - lines 50-62.

When the S1 button is pressed we set the flag 0b0001 (line 17) using the function tx_event_flag_set. This function has three arguments:

  • the pointer to the event flags group which we want to change;
  • the flags that we want to set (please pay attention, I use the expression of just 4 bits but the maximum length of this parameter is 32 bits);
  • the type of setting the flags. This parameter can have two values: TX_OR or TX_AND which correspond to the logical operation applied between the event flags group and the new values of the flags.

When the S2 button is pressed, we set the flag 0b0010 (line 31) using the same function and the TX_OR parameter.

When the S3 button is pressed, we set the flag 0b0100 (line 45) also using the TX_OR parameter.

And when the S4 button is pressed, we set flags 0b0011 (line 59) using the TX_AND parameters which as I mentioned before will clear the flag #3 and remain unchanged flags #1 and #2.

This thread has the same priority as the others and doesn’t have any blocking calls so it is always implemented.

Let’s now consider the code of the “led1_thread_entry.c” file.

#include "led1_thread.h"

/* LED1 Thread entry function */

void led1_thread_entry(void)

{

ULONG actual_events; //Actual flags that are set

while (1)

{

tx_event_flags_get(&g_my_event_flags, 0b0011, TX_OR_CLEAR, &actual_events, TX_WAIT_FOREVER);

R_BSP_PinAccessEnable(); //Enable access to the IO registers

R_BSP_PinWrite(LED1, BSP_IO_LEVEL_HIGH); //Turn on LED1

R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //Delay for 1 second

R_BSP_PinWrite(LED1, BSP_IO_LEVEL_LOW); //Turn off LED1

R_BSP_PinAccessDisable(); //Disable access to the IO registers

}

}

This thread is much shorter, and still quite simple.

In line 6 we declare the variable actual_events that will consist of the actual value of the event flags group.

In the main loop of the thread (lines 7-15) we first try to get the event flags using the function tx_event_flags_get. This function has five parameters:

  • the pointer to the event flags group which we want to get;
  • the flags that we want to get within this group;
  • the type of getting the flags. This parameter can have four values: TX_OR, TX_AND, TX_OR_CLEAR and TX_AND_CLEAR which were considered in detail earlier;
  • the pointer to the variable in which the actual value of the event flags group will be stored;
  • the waiting option, which is the same as for the mutexes and semaphores. This value can be from 0x00000000 (TX_NO_WAIT) to 0xFFFFFFFF (TX_WAIT_FOREVER) and sets the number of ticks during which the thread will be suspended waiting for the required event flags. In our case we use the last value, so the thread will wait for infinity for the event flags.

So as you can see in the current case we request flag #1 or flag #2. If any of them are set, the thread is resumed, and lines 10-14 are implemented. Also, the flags that are set by the moment of invoking the tx_event_flags_get will be cleared.

In line 10, we enable access to the IO registers then in line 11 we turn on LED1, perform the 1 second delay (line 12), and then turn off LED1 (line 13) and disable access to the IO registers (line 14).

The content of the next two files is very similar, they just have different conditions of the tx_event_flags_get function, and flash other LEDs. The content of the “led2_thread_entry.c” file is the following:

#include "led2_thread.h"

/* LED2 Thread entry function */

void led2_thread_entry(void)

{

ULONG actual_events; //Actual flags that are set

while (1)

{

tx_event_flags_get(&g_my_event_flags, 0b0101, TX_AND_CLEAR, &actual_events, TX_WAIT_FOREVER);

R_BSP_PinAccessEnable(); //Enable access to the IO registers

R_BSP_PinWrite(LED2, BSP_IO_LEVEL_HIGH); //Turn on LED2

R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //Delay for 1 second

R_BSP_PinWrite(LED2, BSP_IO_LEVEL_LOW); //Turn off LED2

R_BSP_PinAccessDisable(); //Disable access to the IO registers

}

}

As you can see, the main difference is in line 9. This thread will be resumed when both flags #1 and #3 are set. After that, they will be cleared, and LED2 will flash for 1 second.

And finally, the content of the “led3_thread_entry.c” file is the following:

#include "led3_thread.h"

/* LED3 Thread entry function */

void led3_thread_entry(void)

{

ULONG actual_events; //Actual flags that are set

while (1)

{

tx_event_flags_get(&g_my_event_flags, 0b0100, TX_OR, &actual_events, TX_WAIT_FOREVER);

R_BSP_PinAccessEnable(); //Enable access to the IO registers

R_BSP_PinWrite(LED3, BSP_IO_LEVEL_HIGH); //Turn on LED3

R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //Delay for 1 second

R_BSP_PinWrite(LED3, BSP_IO_LEVEL_LOW); //Turn off LED3

R_BSP_PinAccessDisable(); //Disable access to the IO registers

}

}

Here, the thread is resumed after only flag #3 is set. But unlike the previous two cases, in this thread we don’t clear the event flag (TX_OR option in tx_event_flags_get). After flashing LED3 the flag will still remain set, and the thread will not be blocked, so LED3 will flash again, which will look like continuous lighting. To turn it off we need to reset flag #3 somehow, for example press the S4 button which will clear flag #3 or press the S1 button, after which LED2 Thread will clear this flag after getting it.

Let’s now build the project, connect the board to the PC and run the debugging session. We will see in practice how this all works. As in the previous three tutorials I will illustrate the text with the screenshots from the TraceX to see what’s going on there. You can read how to prepare and save to file the g_tx_trace_buffer buffer in tutorial 17. And again, don’t forget to see the correct address of the buffer.

So first, let’s see the current state of the system after the reset (Figure 9).

Figure 9 - Running of the program after resetting
Figure 9 - Running of the program after resetting

As you can see, only the Button Thread remains active, and all other threads become suspended after an attempt to get the event flags (EG events).

Let’s now press button S1. This should set flag #1 which is requested by the LED1 and LED2 Threads. But the latter also requires flag #3 to be set, so only LED1 Thread will be resumed, and you will see LED1 flashing (Figure 10, 11).

Figure 10 - Running the program after pressing the S1 button. LED1 Thread is resumed
Figure 10 - Running the program after pressing the S1 button. LED1 Thread is resumed
off LED1 thread
Figure 11 - Running the program after turning off LED1. LED1 Thread is suspended again

As you can see after getting the flag, it is cleared, so the next attempt of getting it leads to suspension of LED1 Thread.

Now let’s press the S2 button which will set flag #2. The only thread which requested it is LED1 Thread. As it requests flag #1 OR flag #2 it will resume, and suspend again after attemption of getting flag #2 after it has been cleared. Running the program in TraceX looks absolutely the same as in Figure 10, 11.

Next, let’s press the S3 button and set flag #3. This flag is requested by the LED2 Thread and LED3 Thread. The latter one requests only it, so it will resume and turn on LED3. As in the LED3 Thread the flag is not cleared and LED3 will keep lighting (Figure 12, 13).

S3 button LED3 thread
Figure 12 - Running the program after pressing the S3 button. LED3 Thread is resumed
LED3 thread not suspended
Figure 13 - Running the program after turning off LED3. LED3 Thread is not suspended

As I mentioned before, to turn off LED3 we can either press S4 or press S2. Let’s try the first method and press the S4 button. In this case, the set operation with the AND parameter will clear flag #3, and the next time after attempting to get this flag LED3 Thread will be suspended. Please note, that turning off LED3 doesn’t happen immediately after pressing the S3 button but after a 1 second period has elapsed (Figure 14, 15).

Figure 14 - Running of the program after pressing the S4 button. Flag #3 is cleared but LED3 Thread keeps running
Figure 14 - Running of the program after pressing the S4 button. Flag #3 is cleared but LED3 Thread keeps running
Figure 15 - Running of the program after elapsing 1 second. LED3 Thread becomes suspended
Figure 15 - Running of the program after elapsing 1 second. LED3 Thread becomes suspended

Let’s now press the S3 button again to set flag #3 and then press the S1 button to set flag #1. This will lead to turning on all three LEDs:

  • LED3 has been turned on previously;
  • LED1 turns on because of setting flag #1;
  • LED2 turns on because both flag #1 and flag #3 are now set.

This means that, unlike mutexes and semaphores, for which if one thread gets them, the others need to wait while they will be put again, event flags resume all the threads which request them simultaneously (Figure 16).

Figure 16 - Running of the program after pressing the S3 and S1 buttons. All threads become resumed
Figure 16 - Running of the program after pressing the S3 and S1 buttons. All threads become resumed

In one second all three LEDs will turn off because the flags #1 and #3 have been cleared but LED3 will do it earlier because it’s synchronized by event #1 like LED1 Thread and LED2 Threads, but by event #3. After that all three threads will become suspended again (Figure 17, 18).

Figure 17 - Running the program after turning off LED3. LED3 Thread is suspended but LED1 Thread and LED2 Thread are still executing
Figure 17 - Running the program after turning off LED3. LED3 Thread is suspended but LED1 Thread and LED2 Thread are still executing
Figure 18 - Running the program after turning off LED1 and LED2. All threads except for Button Thread are suspended
Figure 18 - Running the program after turning off LED1 and LED2. All threads except for Button Thread are suspended

Well, I think we have considered enough cases to understand how event flags work.

Let’s now change the program to implement the same functionality with the event flag set notifications. To do this let’s first rename and import the current project, like we did in previous tutorials (Figure 19).

Figure 19 - Renaming and importing the new project
Figure 19 - Renaming and importing the new project

As you can see, we called this new project “threadx_event_flags_2”.

First, we need to open the “configuration.xml” file and navigate to the “Stacks” tab, in which we need to open the properties of any thread and enable the “Notify Callbacks” option (Figure 20).

Figure 20 - Enabling the notification callbacks
Figure 20 - Enabling the notification callbacks

Now we can press the “Generate Project Content” button to update the configuration, then move to the “scr” folder and open the “buttons_thread_entry.c” file.

#include "buttons_thread.h"

extern TX_THREAD led1_thread;

extern TX_THREAD led2_thread;

extern TX_THREAD led3_thread;

void my_event_flags_set_notify(TX_EVENT_FLAGS_GROUP *group_ptr);

void my_event_flags_set_notify(TX_EVENT_FLAGS_GROUP *group_ptr) //Event flags set notification callback

{

uint8_t led1_resume = 0, led2_resume = 0, led3_resume = 0; //Variables to indicate that the corresponding thread needs resuming

if (group_ptr->tx_event_flags_group_current & 0b0011) //If flag #1 or flag #2 are set

led1_resume = 1; //Set led1_resume

if ((group_ptr->tx_event_flags_group_current & 0b0001) && (group_ptr->tx_event_flags_group_current & 0b0100)) //If flag #1 and flag #3 are set

led2_resume = 1; //Set led2_resume

if (group_ptr->tx_event_flags_group_current & 0b0100) //If flag #3 is set

led3_resume = 1; //Set led3_resume

if (led1_resume) //If led1_resume is set

{

tx_thread_resume (&led1_thread); //Resume led1_thread

group_ptr->tx_event_flags_group_current &= ~0b0011; //And clear flags #1 and #2

}

if (led2_resume) //If led2_resume is set

{

tx_thread_resume (&led2_thread); //Resume led2_thread

group_ptr->tx_event_flags_group_current &= ~0b0101; //And clear flags #1 and #3

}

if (led3_resume) //If led3_resume is set

tx_thread_resume (&led3_thread); //Resume led3_thread

}

/* Buttons Thread entry function */

void buttons_thread_entry(void)

{

tx_event_flags_set_notify(&g_my_event_flags, my_event_flags_set_notify); //Register the event flags set notification callback

while (1)

{

if (R_BSP_PinRead(S1) == BSP_IO_LEVEL_LOW) //If S1 button is pressed

{

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S1) == BSP_IO_LEVEL_LOW) //If S1 button is still pressed

{

while (R_BSP_PinRead(S1) == BSP_IO_LEVEL_LOW); //Wait while it is pressed

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S1) == BSP_IO_LEVEL_HIGH) //If S1 button is released

{

tx_event_flags_set(&g_my_event_flags, 0b0001, TX_OR); //Set event flags

}

}

}

if (R_BSP_PinRead(S2) == BSP_IO_LEVEL_LOW) //If S2 button is pressed

{

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S2) == BSP_IO_LEVEL_LOW) //If S2 button is still pressed

{

while (R_BSP_PinRead(S2) == BSP_IO_LEVEL_LOW); //Wait while it is pressed

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S2) == BSP_IO_LEVEL_HIGH) //If S2 button is released

{

tx_event_flags_set(&g_my_event_flags, 0b0010, TX_OR); //Set event flags

}

}

}

if (R_BSP_PinRead(S3) == BSP_IO_LEVEL_LOW) //If S3 button is pressed

{

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S3) == BSP_IO_LEVEL_LOW) //If S3 button is still pressed

{

while (R_BSP_PinRead(S3) == BSP_IO_LEVEL_LOW); //Wait while it is pressed

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S3) == BSP_IO_LEVEL_HIGH) //If S3 button is released

{

tx_event_flags_set(&g_my_event_flags, 0b0100, TX_OR); //Set event flags

}

}

}

if (R_BSP_PinRead(S4) == BSP_IO_LEVEL_LOW) //If S4 button is pressed

{

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S4) == BSP_IO_LEVEL_LOW) //If S4 button is still pressed

{

while (R_BSP_PinRead(S4) == BSP_IO_LEVEL_LOW); //Wait while it is pressed

tx_thread_sleep(3); //Delay for 30 ms

if (R_BSP_PinRead(S4) == BSP_IO_LEVEL_HIGH) //If S4 button is released

{

tx_event_flags_set(&g_my_event_flags, 0b0011, TX_AND); //Set event flags

}

}

}

}

}

As you can see, the program became longer. Let’s consider what has changed here. We will start with the buttons_thread_entry function (lines 37-98). It is almost the same as in the previous program, the only difference is that we enable the notification of setting the event flags in the initialization part of this function by invoking the tx_event_flags_set_notify function (line 39). It has two arguments:

  • the pointer to the event flags group which changes we want to supervise;
  • the pointer to the callback function which will handle the notifications of changing the event flags.

As you can see, we set the callback name as my_event_flags_set_notify. This callback is declared in line 7 (we can skip this declaration but in this case the compiler will throw a warning), and the body of the callback function is located in lines 9-34.

Please note that in lines 3-5 we declare the names of the threads led1_thread, led2_thread, and led3_thread which we will use in the callback to control them. As these threads are already declared in other files, we must use the extern modifier.

So the callback function my_event_flags_set_notify is invoked every time when any flag in the g_my_event_flags group is set. This is provided by processing the pressing of the buttons like we did in the previous program. Our goal for the current project is to eliminate the use of the tx_event_flags_get function and thus reduce the work of the scheduler, as now we will check the flags and resume the required threads by ourselves.

The callback function has a single argument of type TX_EVENT_FLAGS_GROUP. This argument is the pointer to the event flags group, which we can now directly use and change in this function.

In line 11 we declare three variables: led1_resume, led2_resume, and led3_resume. They will be used as indicators whether the corresponding thread should be resumed. Why can’t we just use the event flags for this? Because the same flags can resume different threads and then should be cleared. But before clearing them we should resume all required threads, so we use these auxiliary variables to remember which threads are to be resumed.

In line 13 we check if flag #1 or flag #2 is set by invoking the bitwise AND operation between the constant 0b0011 and the value group_ptr->tx_event_flags_group_current which is the current state of the event flags group. The result of this operation will be true if any of bit0 and bit1 in this group are set, and this is what we need. So when the result of this operation is true we set the led1_resume variable (line 14) to indicate that later we will need to resume the led1_thread.

In line 16 we check if bit0 (flag #1) and bit2 (flag #3) are set. And if it is true, we set the led2_resume variable (line 17).

In line 19 we check if flag #3 is set, and in this case we set the led3_resume variable (line 20).

Now we have checked all the flag combinations according to table 2 and set all the required auxiliary variables. Now we should process the latters.

In line 22 we check if led1_resume is set. If yes, we resume led1_thread using the tx_thread_resume function (line 24). After that we clear flag #1 and flag #2 in the event flags group group_ptr->tx_event_flags_group_current using the bitwise AND operation (line 25). As you can see, here we can do it directly without invoking the tx_event_flags_set or tx_event_flags_get functions.

In line 27 we check if led2_resume is set. If yes, we resume led2_thread (line 29) and clear flag #1 and flag #2 (line 30).

Finally, in line 32 we check if led3_resume is set. In this case we only resume led3_thread (line 33) and don’t clear any event flags because of the condition in the last line of table 2.

Now let’s see the changes in other files. The content of the “led1_thread_entry.c” file is the following:

#include "led1_thread.h"

/* LED1 Thread entry function */

void led1_thread_entry(void)

{

while (1)

{

tx_thread_suspend(tx_thread_identify()); //Suspend current thread

R_BSP_PinAccessEnable(); //Enable access to the IO registers

R_BSP_PinWrite(LED1, BSP_IO_LEVEL_HIGH); //Turn on LED1

R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //Delay for 1 second

R_BSP_PinWrite(LED1, BSP_IO_LEVEL_LOW); //Turn off LED1

R_BSP_PinAccessDisable(); //Disable access to the IO registers

}

}

Here the changes are very insignificant. We remove the actual_events variable as we don’t need it anymore and replace the tx_event_flags_get function with the tx_thread_suspend function (line 8). This function allows suspending the thread. As an argument of this function we use tx_thread_identify. It returns the pointer to the currently implementing thread, which in the current case is led1_thread.

The logic of the operation is now the following. led1_thread suspends itself in the beginning of the main loop. When event flag #1 or #2 is set, the my_event_flags_set_notify callback is invoked, and resumes led1_thread (line 24 of the “buttons_thread_entry.c” file) which in its own turn blinks LED1 and suspends itself again waiting for the new event flags to be set.

The content of the “led2_thread_entry.c” file is almost the same, so I will just put it here without any description.

#include "led2_thread.h"

/* LED2 Thread entry function */

void led2_thread_entry(void)

{

while (1)

{

tx_thread_suspend(tx_thread_identify()); //Suspend current thread

R_BSP_PinAccessEnable(); //Enable access to the IO registers

R_BSP_PinWrite(LED2, BSP_IO_LEVEL_HIGH); //Turn on LED2

R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //Delay for 1 second

R_BSP_PinWrite(LED2, BSP_IO_LEVEL_LOW); //Turn off LED2

R_BSP_PinAccessDisable(); //Disable access to the IO registers

}

}

And the “led3_thread_entry.c” file has certain differences because when LED3 is lit up, the flag #3 is not cleared. So now we need to check the state of it inside led3_thread to suspend itself when flag #3 is cleared.

#include "led3_thread.h"

/* LED3 Thread entry function */

void led3_thread_entry(void)

{

CHAR *name; //Event flags group's name

ULONG current_flags; //Current set flags in the event flags group

TX_THREAD *first_suspended; //Pointer to the thread that is first on the suspension list of this event flags group

ULONG suspended_count; //Number of threads currently suspended on this event flags group

TX_EVENT_FLAGS_GROUP *next_group; //Pointer of the next created event flags group

while (1)

{

tx_event_flags_info_get(&g_my_event_flags, &name, ¤t_flags, &first_suspended, &suspended_count, &next_group);//Get the current state of the event flags

if (!(current_flags & 0b0100)) //If flag #3 is 0

tx_thread_suspend(tx_thread_identify()); //Suspend current thread

R_BSP_PinAccessEnable(); //Enable access to the IO registers

R_BSP_PinWrite(LED3, BSP_IO_LEVEL_HIGH); //Turn on LED3

R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //Delay for 1 second

R_BSP_PinWrite(LED3, BSP_IO_LEVEL_LOW); //Turn off LED3

R_BSP_PinAccessDisable(); //Disable access to the IO registers

}

}

As you can see, it’s much longer than the previous two files.

In lines 6-10 we declare the variables which will be needed by the function tx_event_flags_info_get called in line 13. This function provides a lot of information about the event flags group but we will need just the current state of the event flags current_flags (line 7).

In line 14 we check if bit2 (flag #3) of the current_flags variable is 0. In this case led3_thread suspends itself (line 15).

And that’s all about the changes. Let’s now build and run the project to see how it now works.

First, let’s see the current state of the system after the reset (Figure 21).

Figure 21 - Running of the program after resetting
Figure 21 - Running of the program after resetting

As you see, this start differs from the previous program (Figure 9). Here LED1 Thread and LED2 Thread first call the tx_thread_identify function (IT) and then suspend themselves (TS). LED3 Thread first gets the information about the current event flags state (IG) and then self-suspends. The Button Thread remains active all the time.

Let’s now press button S1. This should set flag #1 which is requested by LED1 Thread and LED2 Thread. But the latter also requires flag #3 to be set, so only LED1 Thread will be resumed, and you will see flashing of LED1 (Figure 22, 23).

Figure 22 - Running the program after pressing the S1 button. LED1 Thread is resumed
Figure 22 - Running the program after pressing the S1 button. LED1 Thread is resumed
Figure 23 - Running the program after turning off LED1. LED1 Thread is suspended again
Figure 23 - Running the program after turning off LED1. LED1 Thread is suspended again

The behavior of the program looks very similar to the previous one (Figure 10, 11). But here resuming the LED1 Thread is caused by the tx_thread_resume function but not by getting the event flag. Also, you can notice that invocation of the callback function isn’t shown in the TraceX graph.

I think I will omit consideration of the other cases that we tried in the previous program. You can try them by yourself and make sure that everything works the same, and even in TraceX it all looks very similar.

And that’s really all I wanted to tell you about the event flags. I’ve shown you two approaches to using them. They both are workable, so it’s up to you which one to select.

We’re almost done with the ThreadX services. In the next tutorial we will consider the last of them, called “message queues”.

As for homework, check for yourself that the second program works the same as the first one in all cases. Also, try to change the event flags setting and getting conditions from table 2 and see how it affects the program.

Make Bread with our CircuitBread Toaster!

Get the latest tools and tutorials, fresh from the toaster.

What are you looking for?