FB pixel

Understand How to Use Message Queues in Azure RTOS ThreadX | Renesas RA - 25

Published


Hi again! This is the last tutorial devoted to the Azure RTOS ThreadX services for the threads interconnection. In the previous three tutorials we already got acquainted with semaphores, mutexes and event flags. This time we will discover the message queues which, unlike the previous tutorials, are used not only to synchronize the threads but to exchange some information between them. Let’s consider them in more detail. As usual, for more information you can refer to the official Microsoft documentation.

Introduction into Message Queues

As I said before, message queues are used for communication between threads. The message queue is the region of memory into which some thread can send the message, which another thread can receive.

The sent messages are copied from the source memory into the queue, and while receiving they are also copied from the queue into the destination memory. That’s why you need to make sure that you have enough memory for copying the messages.

The length of each message can be 1 to 16 32-bit words. If you want to send a longer message, you can locate it somewhere in RAM and send a pointer to this memory region inside the message. The message length is specified during creation of the message queue and can’t be changed.

Apart from the message size you need to specify the size of the whole message queue in bytes. So please be attentive here. If you set the message length as 5 and the message queue size as 120, then you can store not 120 / 5 = 24 messages but just 120 / (5 x 4) = 6 messages. This is because the message length is set in 4-byte words, as I mentioned before.

Like the other services we have considered before, message queues can suspend the threads under two condition:

  • on an attempt to send the message when the queue is full;
  • on an attempt to receive the message from the empty queue.

If there are several messages suspended while waiting for the message to be sent into the queue, they will receive the messages and be resumed in the order of their suspension.

Like event flags, message queues can throw notifications when a message is sent. These notifications are processed in the dedicated callback functions and can be used to resume the required threads.

When using the message queues you need to make sure you have enough memory to store the messages otherwise the memory will be corrupted, and the program will crash.

Let’s now consider the practical example of using message queues in RA MCUs.

Test Setup

To demonstrate the operation of the message queues we will use the same setup as the previous two times (Figure 1).

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

Here we will use the button to send messages to the threads inside which the LEDs will be controlled. Buttons will set the frequency of blinking or flashing the LEDs:

  • S1 sets the pulse width of 2000 ms;
  • S2 sets the pulse width of 1000 ms;
  • S3 sets the pulse width of 500 ms;
  • S4 sets the pulse width of 250 ms;
  • LED1 will blink continuously with the frequency of (1 / (2 x pulse width)) Hz;
  • LED2 will flash once after receiving the message with the specified pulse width, then the thread will be suspended waiting for another message;
  • LED3 will flash once after receiving the message with the doubled pulse width, then the thread will be suspended waiting for another message;

So in the application we will have four threads: one for processing all four buttons and three for each LED.

Test Program Description

This time let’s not create a new project but import & rename it from the “threadx_event_flags” project in the way I have described several times in the previous three tutorials (Figure 2).

Renaming & importing the existing project
Figure 2 - Renaming & importing the existing project

Let’s call the new project “threadx_message_queue” as follows from Figure 2.

Now we need to open the “configuration.xml” file and switch to the “Stacks” tab. There we need to select the existing “Event Flags” object and click the “Remove” button as we will not use event flags in this tutorial (Figure 3).

Removing of the existing event flags
Figure 3 - Removing of the existing event flags

Now we need to click the “New Object >” button and select the “Queue” in the drop-down list (Figure 4).

Adding new message queue
Figure 4 - Adding new message queue

Now we need to change the properties of the Queue object according to Figure 5.

Properties of the Queue object
Figure 5 - Properties of the Queue object

The “Name” and the “Symbol” fields have the same meaning as for all other objects. Here, I set them as “Message Queue” and “g_message_queue”, respectively.

The “Message Size (Words)” field sets the length of the message in the 32-bit words. As we will send the delay value, the length of one word will be more than enough.

The “Queue Size (Byte)” field sets the size of the whole queue. Here I have increased it from 20 to 40 bytes, so now the queue may contain up to 40 / (4 x 1) = 10 messages.

These are all changes we need to make here. We have already configured the required IO pins and created the required threads last time.

Now we can click the “Generate Project Content” button to update the project content, and then switch to the source code in the “src” folder.

First file which we will consider is “buttons_thread_entry.c”.

#include "buttons_thread.h"

/* Buttons Thread entry function */

void buttons_thread_entry(void)

{

ULONG message; //Message to be sent

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

{

message = 2000; //Set the pulse width in ms

tx_queue_send(&g_message_queue, &message, TX_NO_WAIT); //Send the message

}

}

}

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

{

message = 1000; //Set the pulse width in ms

tx_queue_send(&g_message_queue, &message, TX_NO_WAIT); //Send the message

}

}

}

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

{

message = 500; //Set the pulse width in ms

tx_queue_send(&g_message_queue, &message, TX_NO_WAIT); //Send the message

}

}

}

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

{

message = 250; //Set the pulse width in ms

tx_queue_send(&g_message_queue, &message, TX_NO_WAIT); //Send the message

}

}

}

}

}

As you can see, the code of the current file is very similar to the code of the previous tutorial. Here, we also process the button presses of buttons S1 (lines 9-22), S2 (lines 24-37), S3 (lines 39-52), and S4 (lines 54-67). Let’s consider button S1 in more detail.

As you can see, in the initialization part of the function we declare the variable message of type ULONG (line 6). This variable represents the message which will be sent to the queue and received by other threads. Its value is the time in milliseconds during which LEDs should be on and off.

Lines 18 and 19 are the payload of processing S1. In line 18 we set the value of the message variable as 2000 according to the given task. In line 19 we send the message into the queue using the function tx_queue_send. This function has three arguments:

  • the pointer to the queue object to which the message will be sent;
  • the pointer to the message that will be sent. Please note, that the message can also be an array of ULONG type with the length corresponding to the one that we set in the properties (Figure 5);
  • the waiting option which is the same as in other similar services. This argument sets the time in system ticks during which the sender will be suspended if the queue is full. In the current case we use the TX_NO_WAIT option to indicate that the thread will not be suspended if the queue is full.

Processing of the other buttons is the same except for the value of the message variable which is 1000 for S2 (line 33), 500 for S3 (line 48), and 250 for S4 (line 63).

Now let’s consider the code of the “led1_thread_entry.c” file.

#include "led1_thread.h"

/* LED1 Thread entry function */

void led1_thread_entry(void)

{

ULONG pulse_width = 250; //Pulse width received in the message

while (1)

{

tx_queue_receive(&g_message_queue, &pulse_width, TX_NO_WAIT); //Receive the message

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

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

tx_thread_sleep(pulse_width / 10); //Delay for the specified time

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

tx_thread_sleep(pulse_width / 10); //Delay for the specified time

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

}

}

It is also very similar to Tutorial 20.

In the initialization part of the led1_thread_entry function, we declare the variable pulse_width of ULONG type and assign it with the value 250 (line 6). This variable will be used to receive the message from the queue and to set the delay between changing the LED1 states. The initial state of this variable is needed because in this thread, reading the message from the queue will be non-blocking, so LED1 will start to blink immediately after reset, so we should set the initial frequency of its blinking.

In the beginning of the main loop of the thread we invoke the tx_queue_receive function which receives the message from the queue. It has the same three arguments as the tx_queue_send function:

  • the pointer to the queue object from which the message will be received;
  • the pointer to the variable or array into which the message from the queue will be copied;
  • the waiting option which is the same as in other similar services. This argument sets the time in system ticks during which the receiver will be suspended if the queue is empty. In the current case, we use the TX_NO_WAIT option to indicate that the thread will not be suspended if there is no message in the queue.

So if there is a message in the queue, LED1 Thread will receive it and update the pulse width. Otherwise it will keep blinking LED1 with the previous pulse width value.

The blinking of the LED is performed in the usual way. First we turn on the LED (line 11), then suspend the thread for the (pulse_width / 10) ticks (line 12). Here we divide the value by 10 because each system tick is 10 ms. Then we turn off the LED (line 13) and suspend the thread again (line 14) to provide the off-state delay.

This is all about the LED1 Thread. Now let’s consider the content of the “led2_thread_entry.c” file.

#include "led2_thread.h"

/* LED2 Thread entry function */

void led2_thread_entry(void)

{

ULONG pulse_width; //pulse width received in the message

while (1)

{

tx_queue_receive(&g_message_queue, &pulse_width, TX_WAIT_FOREVER); //Receive the message

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

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

tx_thread_sleep(pulse_width / 10); //Delay for the specified time

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

tx_thread_sleep(pulse_width / 10); //Delay for the specified time

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

}

}

It is very similar but has certain significant differences. First, in line 6 we don’t need to initialize the variable pulse_width because in this thread LED2 will not blink continuously but just flash once after receiving the message with the pulse width value.

In line 9 we invoke the same tx_queue_receive function but the waiting option is TX_WAIT_FOREVER now. This means that the thread will be suspended at this line until a message appears in the queue.

Everything else is the same as in the previous thread. The delay in line 14 is needed to prevent LED2 being on continuously if we click the buttons very fast.

And finally, the “led3_thread_entry.c” file.

#include "led3_thread.h"

/* LED3 Thread entry function */

void led3_thread_entry(void)

{

ULONG pulse_width; //pulse width received in the message

while (1)

{

tx_queue_receive(&g_message_queue, &pulse_width, TX_WAIT_FOREVER); //Receive the message

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

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

tx_thread_sleep(pulse_width / 5); //Delay for the specified time

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

tx_thread_sleep(pulse_width / 5); //Delay for the specified time

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

}

}

This code is very similar to the previous one, just the sleep time of the thread is calculated as (pulse_width / 5) instead of (pulse_width / 10) because according to the given task the pulse width for LED3 should be twice as long.

Now please note that as LED2 Thread and LED3 Thread are suspended waiting for the message, they will receive it first one by one, and LED1 Thread will be able to receive the message only when other two threads are running.

Let’s see how it all works in practice. To do this, let’s build the project, connect the board to the PC and start the debugging. As in the previous four tutorials I will illustrate the text with the screenshots from 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.

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

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

As you can see, all threads except for the Button Thread are suspended. But LED1 Thread is suspended because of turning into sleep (TZ), and LED2 Thread and LED3 Thread are suspended when trying to receive the message from the queue (QR). Visually you should see that LED1 is blinking with the frequency of 2Hz (1 / (250ms + 250ms)).

Let’s now quickly press the S1 button three times to send the message which will set the pulse width of 2000ms. You will see that first LED2 will turn on, then LED3, and finally LED1 will change the blinking frequency. This is because the LED2 Thread was suspended first (Figure 6), and LED1 Thread was not suspended by the message queue at all. In TraceX, this will look as follows (Figure 7).

Resuming of LED2 Thread and LED3 Thread by sending messages to the queue
Figure 7 - Resuming of LED2 Thread and LED3 Thread by sending messages to the queue

As you can see, we have sent three messages (QS) with Button Thread (blocks 713, 754, and 797). The first message immediately resumes LED2 Thread which turns on LED2 then suspends by turning into sleep. The second message resumes the next thread which is suspended waiting for the message in the queue, which is LED3 Thread. The third message remains in the queue, as all three threads are now doing their business and are not waiting for the message at the time. Later, when LED1 Thread is done, it will receive this message and change the blinking frequency (Figure 8).

Receiving of the third message by LED1 Thread
Figure 8 - Receiving of the third message by LED1 Thread

Now you can try to press any button quickly several times. You will see that even after you finish pressing the button, LED2 and LED3 will keep blinking (and LED3 frequency will be half as fast). This is because we have filled the full queue with the messages, and now the LED2 and LED3 Threads are not suspended until the queue becomes empty again.

You can press any buttons in any sequence and see how this affects the pulse width of the LEDs flashing. The most difficult is to set the pulse width of 250 ms for LED1 because you need to press the S4 button very fast to leave the message for LED1 Thread, as the other threads will receive them very quickly. I’m not showing these cases with the TraceX as they are the same as in Figure 7 and 8.

That’s actually all I wanted to tell you about the message queues. Now we know all ThreadX services used for thread interconnection and communication. In the next tutorial I will talk about software timers which can be created in this RTOS and, after that, we will consider a practical example of creating the complex project using ThreadX.

As homework, I suggest you change the conditions of receiving and sending the messages, also to increase and decrease the queue size, and see how these changes affect the operation of the system.

Make Bread with our CircuitBread Toaster!

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

What are you looking for?