The steps to use an RTOS message queue to exchange data between tasks. In this post, we post data from the low level hardware (UART peripheral) to a task that handles a logic part of our firmware.

 

This is a direct follow up from the previous FreeRTOS post, where semaphores and triggers were used to collect incoming UART data without burning CPU cycles.

Now, we add the mechanism to give that data to the logic part of the application, while keeping the UART / low-level part lean.

We'll use message queues for that. One of the common RTOS mechanisms to give away data and continue with core business.

 

From the Previous Post: use FreeRTOS and Triggers to get UART data in the background

 

You'll need to read the previous post. It explains how to use the PSoC smart buffered Serial Communication Blocks, with a trigger and RTOS, to get packages of data. Without polling. Without looping over the serial read...

The mechanism sets up the firmware to lay low until the agreed number of bytes have been received on the serial port. The controller can spend its powers to do other things, or to go lower power.

What do we gain:

  • the hardware layer stays lean and reactive. There's very little latency between getting bits at the serial port and accepting them.
    This part is trigger driven. We get a trigger when an pre-defined number of bytes arrives. We act on that with a little logic as possible: put the payload on a queue.
    Then go back to being ready for new serial data.
  • That physical data is queued and handed over to the business logic.
    Two gains actually: separation between physical implementation and business logic, and a buffer queue that can take a number of data feeds in case the controller is more busy then usual on something else.

 

Using a Message Queue to send data to a task

 

In this post I focus on a few of the advantages:

  • keep the UART handling as lean and reactive as possible
  • isolation between hardware (UART) and logic
  • buffering to deal with peak loads without losing serial data

 

The first one is fairly easy. When you have a queue set up, the UART handler just has to put the received data on it, and continue with its business to handle incoming data.

This is executed right after the UART trigger woke op our FreeRTOS task:

 

      /* Handle received data */
      if(telemetry_queue) { 
        if( xQueueSend( telemetry_queue, ( void * ) rxBuffer, ( TickType_t ) 10 ) != pdPASS ) {
            /* Failed to post the message, even after 10 ticks. */
          CY_ASSERT_L3(!0);
        }
      }

 

This is only 2 lines of active code.

  • Check if the queue exists (in my code not really needed, because I create it before any logic runs)
  • Copy the just received serial data onto the buffer and return to deal with UART data coming in (if any)

The rest is just debugging code that I used to assert that the job was not skipping any packages (queue full).

 

At this point. The UART part of our code forgets about that data. It is pushed onto the queue. It is now a job for the business logic to eat that queue data.

The mechanism of reading the data off the queue is similar to a trigger. Our receiving task will not consume any CPU, unless a message was pushed onto the queue it is waiting for.

 

void telemetry_receive_task(void *pvParameters) {
  for( ;; ) {
    if(xQueueReceive(telemetry_queue, message, portMAX_DELAY ))     {
      cyhal_gpio_toggle(CYBSP_USER_LED);
//      // process the telemetry data.
//      uint32_t checksum = 0;
//      for (int i = 0; i < sizeof(message); i++) {
//          checksum += message[i];
//      }
////      CY_ASSERT_L3((checksum > 0U));
    }
  } // end for
}

 

Again very simple. The task is a loop that does not continuously burn clock ticks.

It has a guard condition: a message should be available on the queue it's watching.

 

Once a message arrives on the queue, the task wakes up and takes that message.

In our scenario here, that is the same data that was received on the UART port from previous post.

We can then process that message.

In my example here, I toggle a LED . It may sound silly, but when you start developing a mechanism like this, nothing beats a visual clue when you try to see real time reactivity.

The code in comments is there to simulate a real action on the received data.

This is the point in your firmware where you do the handling of that telemetry data you got from the UART port.

 

The only part of the code that I haven't shown is the creation of the queue. That is simple too:

 

void initTelemetryQueue() {
  telemetry_queue =  xQueueCreate( 10, TELEMETRY_MESSAGE_SIZE );
}

 

One line of code. And that reflects the complexity of using queues:

  • one line of code to create a message queue
  • one line of code to push a message onto it
  • one line of code to wait for messages without polling away clock ticks, and retrieve the data.

Maybe by describing the mechanism, what the gains are and how simple it is to do that, I take away some of the mystery of RTOS.

 

 

What did I gain by using an RTOS?

 

This functionality can perfectly be achieved in several other ways. Many of them without an RTOS.

I did get some advantages by default though:

 

  • a task scheduler that deals with priorities, dormant threads, communication, loose coupling
  • in this example no task is active unless something happens. You can configure the RTOS to go to lower power or deal with other logic (MQTT, ...).
    • the UART task sleeps, only waking up on a trigger when all data arrived
    • the Telemetry Receive task sleeps, only waking up when a message arrived on the queue
    • queue takes processing time only when pushing and taking message, else sleeps too.
    • the only other process running is the scheduler, and that has been proven in many real world applications to be lean and efficient.
  • messaging, sleeping, delaying/waiting, signaling are available. And tested in many projects too.
  • Hooks are available for low power modes. You can specify what to do when the "yield" task runs.
  • That yield task can, besides going to low power, also do cleanup.
  • you can conditionally compile in metrics and diagnostics gathering without writing code. That allows you to profile the behaviour of your application. Using existing analysis tools.
  • debuggers understand the main RTOS flavours and assist.
  • there is a crowd that understands the mechanisms without studying a legacy.
  • only 3 functions are PSoC specific: the UART Init,  UART Trigger and UART Read. All the rest is portable without changes. They do not care bout the controller used, and do not know the data came from UART because it's "a queued message".

 

If you have another framework or if you are a natural better programmer than the rest, you can do this yourself. Or if your project is trivial.

 

 

The ModusToolbox project is attached.

It's configured for: UART 5.0 and 5.1 (the Debug USB COM port), 9600 / 8 / 1 / N

If you want to use other pins, check uart_task.h. Theres's an example for pins 10.0 and 10.1 that you can use as inspiration...

 

PSoC 6 series
Create a Project with its own Board- and Module configurations
Low Power Management - prepare the board for current measurements
Power consumption without WiFi or Bluetooth
Create a FreeRTOS 10.3 Project
UART receiver with FreeRTOS
FreeRTOS message queue
UART receiver with FreeRTOS - Deep Sleep support