Using the Renesas ThreadX RTOS and Messaging Framework with Multiple Sensors / Threads

Created by Austin Oltmanns on Jul 18, 2017

Welcome to another guide about the Synergy Software Platform. In this guide, information will be given regarding setting up multiple threads to poll multiple sensors, sending the information from those sensors to another thread for processing and finally sending that processed information to another thread for output. This guide will use concepts from the previous guides in this series, but aims to give standalone instructions for setting up a working demo of the project. This guide will be using the DK-S124, available from Digi-Key: YSDKS124S20 Renesas Electronics America Inc | Development Boards, Kits, Programmers | DigiKey

This guide will comprise of an overview of the example program, the setup/configuration procedure for the project in e2 studio, the commented source code of the project and finally the program output and a conclusion. As always, useful links are attached below.

Dev-Board User’s Manual: https://www.renesas.com/en-us/doc/products/renesas-synergy/doc/r12um0006eu0100-synergy-dk-s124.pdf

S124 Data Sheet: https://www.renesas.com/en-us/doc/products/renesas-synergy/doc/r01ds0264eu0100_synergy_s124.pdf

S124 User’s Manual: https://www.renesas.com/en-us/doc/products/renesas-synergy/doc/r01um0003eu0120-synergy-s124.pdf

Synergy Software Package User’s Manual: https://synergygallery.renesas.com/media/products/1/156/en-US/r01us0171eu0096_synergy_ssp.pdf

Basics of Synergy Software Platform Book: https://www.renesas.com/en-us/media/products/synergy/book/Basics_of_the_Renesas_Synergy_Platform_1703.pdf

Program Overview

Suppose a simple program structure like the diagram below.

In this program, two sensor threads are polling independent sensors and sending the data as a “Sensor Message” to a third thread which processes the data and sends the processed data as an “Output Message”. This example could easily be extended to more sensor threads or even different sensor message types if needed.

To realize this example, the sensors will be spoofed for simplicity, the processing will be very light, and the output will be done over UART. The key feature of the Synergy Software Platform that will be used is the Renesas Messaging Framework with multiple threads.

Setup/Configuration for e2 Studio

Open up e2 studio and create a new Synergy C Project. This guide will be using the name MultiThreadMessageGuide for the project. Be sure that the correct device is selected (R7FS124773A01CFM for the DK-S124) and choose BSP for the project template. If these steps seem unfamiliar, please see the Configuring and Using the UART Interface on the DK-S124 guide for creating a project after having done the initial setup of e2 studio.

Once the new project is open, go to the threads tab of the Project Configuration file. As this program has 4 threads, 4 threads must be created. Go ahead and create “sensor_thread1”, “sensor_thread2”, “computation_thread” and “output_thread”. Next, the needed Thread Stacks will be added. Because we have 4 threads which normally claim 1024 bytes of RAM each and the DK-S124 only has 3960 bytes available with this setup, it will be necessary to trim the Stack size of one of the threads slightly. So, on the Output Thread, change the “Stack Size” property to 800 bytes instead of 1024.

This project only needs two Thread Stacks, the Messaging Framework and the UART Driver. Technically, the Messaging Framework could be added to any non-HAL/Common thread. For this guide, it will be added to the Computation Thread because it is known that this thread will exist regardless to how the sensor threads might change. The UART Driver will go on the Output Thread, obviously, because it is the thread that will use it directly.

Add the Messaging Framework Thread Stack to the Computation Thread (add Thread Stack>Framework>Services>Messaging Framework). All of its default options should work fine for this guide. Next add the UART Driver Thread Stack (add Thread Stack>Driver>Connectivity>UART Driver). Unlike in previous guides, the “UART callback function to be defined by user” will be left as “user_uart_callback” because it will be used to determine when the UART device is done writing. Similar to previous guides, the Receive, Transmit, and Transmit End Interrupt Priorities must be changed from “Disabled” to some priority between 0 and 2 inclusive. If these steps seem unfamiliar, please refer to Configuring and Using the UART Interface on the DK-S124 for detailed instructions on adding and configuring Thread Stacks.

Now, the Messaging Framework must be configured from its own tab (the “Messaging” tab). To do this, go to the Messaging tab and create a new Event Class and name it SensorData. The rest of the fields should populate accordingly. If this step is unfamiliar, please refer to the “Setting up the Messaging Framework” section of Using the ADC and ThreadX RTOS Messaging Framework on the Renesas DK-S124.

For this example, only this Event Class will be used, even by the output thread. Perhaps this device is doing some sensor fusion, and only exposing one set of values to the outside world. However, this example could be extended to use a different event class for output data if needed.

Finally, some subscribers must be added to the SensorData event class. Because there will be multiple subscribers to the same event class, the event instance field will be used. Add a subscriber with the new subscriber button, use the drop down to select “Computation Thread” and fill in 0 for “Start” and 1 for “End”. This means the computation thread will receive any SensorData message posted with its header.event_b.class_instance value between 0 and 1 inclusive. The sensor threads will generate those messages. Next, add one more subscriber. Subscribe the Output Thread to all SensorData messages with a class instance value of 2 by filling in both “Start” and “End” fields with the number 2. The Computation Thread will generate these messages. Note that a separate event class could have been used for this purpose, but by doing it this way an example of using the class instance value to direct messages can be given.

Press the Generate Project Content button, and your screen should now look like this:

Commented Source Code

First, the sensordata_api.h file must be created and populated. Right click the “src” directory of the MultiThreadMessageGuide project and select New>HeaderFile. Put sensordata_api.h for the name. This file will detail the structure of the messages used by the application.

For this example, suppose that each sensor has a few channels of 16-bit data. This could be different axes from an accelerometer or multiple temperatures from a string of sensors. Each sensor thread will be posting a message of this type.
sensordata_api.h

#ifndef _SENSORDATA_API_H_
#define _SENSORDATA_API_H_
 
#define NUMCHANNELS (uint8_t)10
 
#include "sf_message_api.h" //this is a message, so the message api is needed
 
typedef struct sensordata_payload_s
{
    sf_message_header_t header; //every message must include a header of this type
    uint8_t sensorchannel[NUMCHANNELS]; //suppose there are a number of channels from the sensor
    uint16_t sensordata[NUMCHANNELS]; //this is the data from each channel
} sensordata_payload_t; //This name is specified in "Event Class" properties as "Payload Type"
 
#endif

Next, sensor_thread1.c and sensor_thread2.c:

sensor_thread1_entry.c

#include "sensor_thread1.h"
#include "sensordata_api.h"
 
/* Sensor Thread 1 entry function */
void sensor_thread1_entry(void)
{
    //Message init
    //sending sensordata init
    sf_message_header_t * pPostBuffer; //pointer for the buffer that must be acquired
    sf_message_acquire_cfg_t acquireCfg = {.buffer_keep =false}; //do not keep the buffer, other threads need it
    ssp_err_t errorBuff; //place for error codes from buffer acquisition to go
    sf_message_post_err_t errPost; //place for posting error codes to go
    sf_message_post_cfg_t post_cfg =
    {
      .priority = SF_MESSAGE_PRIORITY_NORMAL, //normal priority
      .p_callback = NULL //no callback needed
    };
    sensordata_payload_t * pDataPayload; //pointer to the receiving message payload
 
    while (1)
    {
        //send as message if possible
        errorBuff = g_sf_message0.p_api->bufferAcquire(g_sf_message0.p_ctrl, &pPostBuffer, &acquireCfg, 0);
        if (errorBuff==SSP_SUCCESS)
        {
            pDataPayload = (sensordata_payload_t *) pPostBuffer; //cast buffer to our payload
            pDataPayload->header.event_b.class_code = SF_MESSAGE_EVENT_CLASS_SENSORDATA; //set the event class
            pDataPayload->header.event_b.class_instance = 0; //set the class instance
            pDataPayload->header.event_b.code = SF_MESSAGE_EVENT_NEW_DATA; //set the message type
 
            for (uint8_t temp=0; temp<NUMCHANNELS;temp++) pDataPayload->sensordata[temp] = temp; //fill the payload with dummy data
            for (uint8_t temp=0;temp<NUMCHANNELS;temp++) pDataPayload->sensorchannel[temp] = temp;
            g_sf_message0.p_api->post(g_sf_message0.p_ctrl, (sf_message_header_t *) pDataPayload,
                                      &post_cfg, &errPost, TX_WAIT_FOREVER); //post the message
        }
        tx_thread_sleep (205);
    }
}

sensor_thread2_entry.c

#include "sensor_thread2.h"
#include "sensordata_api.h"
 
/* Sensor Thread 2 entry function */
void sensor_thread2_entry(void)
{
    //Message init
    //sending sensordata init
    sf_message_header_t * pPostBuffer; //pointer for the buffer that must be acquired
    sf_message_acquire_cfg_t acquireCfg = {.buffer_keep =false}; //do not keep the buffer, other threads need it
    ssp_err_t errorBuff; //place for error codes from buffer acquisition to go
    sf_message_post_err_t errPost; //place for posting error codes to go
    sf_message_post_cfg_t post_cfg =
    {
      .priority = SF_MESSAGE_PRIORITY_NORMAL, //normal priority
      .p_callback = NULL //no callback needed
    };
    sensordata_payload_t * pDataPayload; //pointer to the receiving message payload
 
    while (1)
    {
        //send as message if possible
        errorBuff = g_sf_message0.p_api->bufferAcquire(g_sf_message0.p_ctrl, &pPostBuffer, &acquireCfg, 0);
        if (errorBuff==SSP_SUCCESS)
        {
            pDataPayload = (sensordata_payload_t *) pPostBuffer; //cast buffer to our payload
            pDataPayload->header.event_b.class_code = SF_MESSAGE_EVENT_CLASS_SENSORDATA; //set the event class
            pDataPayload->header.event_b.class_instance = 1; //set the class instance
            pDataPayload->header.event_b.code = SF_MESSAGE_EVENT_NEW_DATA; //set the message type
 
            for (uint8_t temp=0; temp<NUMCHANNELS;temp++) pDataPayload->sensordata[temp] = temp+20; //fill payload with dummy data
            for (uint8_t temp=0;temp<NUMCHANNELS;temp++) pDataPayload->sensorchannel[temp] = temp+17;
            g_sf_message0.p_api->post(g_sf_message0.p_ctrl, (sf_message_header_t *) pDataPayload,
                                      &post_cfg, &errPost, TX_WAIT_FOREVER); //post the message
        }
        tx_thread_sleep (100);
    }
}

In this example, these two threads are nearly identical, however; in an actual application, it is likely that the two sensors would be polled differently. Perhaps one sensor is i2c and the other is simply a GPIO sensor, or maybe one of them is read using the ADC. For this reason, having two sensor threads makes sense. For this example, there are two key differences in the threads: the tx_thread_sleep times are different to simulate sensors with different refresh rates and the dummy data are different to simulate different sensor values.

Next is the computation thread. For this example, the values of the two sensors will simply be added together before being passed to the output thread. In a more complicated application, the values could be filtered or combined to discover new things from the sensor readings. Maybe a foreign body is very close and very warm, this information could be read, and a command to fire the emergency proximity coolant could be given over UART. Below is the code.

computation_thread_entry.c

#include "computation_thread.h"
#include "sensordata_api.h"
/* Computation Thread entry function */
void computation_thread_entry(void)
{
    //Messaging init
    ////Recieving init
    sf_message_header_t * pSensorDataHeader; //pointer to the message header
    sensordata_payload_t * pSensorDataPayload; //pointer to the message payload
 
    ////Sending init
    sf_message_header_t * pOutputBuffer; //pointer for the buffer that must be acquired
    sf_message_acquire_cfg_t acquireCfgPost = {.buffer_keep =false}; //could keep the buffer, because this thread is the only one posting to the receiving thread
    ssp_err_t errorBuffPost; //place for error codes from buffer acquisition to go
    sf_message_post_err_t errPost; //place for posting error codes to go
    sf_message_post_cfg_t post_cfg =
    {
      .priority = SF_MESSAGE_PRIORITY_NORMAL, //normal priority
      .p_callback = NULL //no callback needed
    };
    sensordata_payload_t * pOutputPost; //pointer to data to be sent to uart
 
    uint16_t lastknownsensor1values[NUMCHANNELS]; //buffer to keep sensor1 readings in
    uint16_t lastknownsensor2values[NUMCHANNELS]; //buffer to keep sensor2 readings in
 
    while (1)
    {
        g_sf_message0.p_api->pend(g_sf_message0.p_ctrl, &computation_thread_message_queue,
                             &pSensorDataHeader, TX_NO_WAIT); //if a message has been posted to the queue, store its address in pSensorDataHeader
 
        if (pSensorDataHeader->event_b.class_code == SF_MESSAGE_EVENT_CLASS_SENSORDATA) //if the message is the right kind
        {
            pSensorDataPayload = (sensordata_payload_t *) pSensorDataHeader; //cast the received message to the custom type
            //store the sensor information in some buffers, this part is application dependent
            if (pSensorDataPayload->header.event_b.code == SF_MESSAGE_EVENT_NEW_DATA) //if the message event is the right kind
            {
                if (pSensorDataPayload->header.event_b.class_instance ==0) //from sensor 1
                {
                    for (uint8_t temp=0; temp<NUMCHANNELS;temp++) lastknownsensor1values[temp] = pSensorDataPayload->sensordata[temp];
                }
                if (pSensorDataPayload->header.event_b.class_instance ==1) //from sensor 2
                {
                    for (uint8_t temp=0; temp<NUMCHANNELS;temp++) lastknownsensor2values[temp] = pSensorDataPayload->sensordata[temp];
                }
                g_sf_message0.p_api->bufferRelease(g_sf_message0.p_ctrl, pSensorDataHeader, SF_MESSAGE_RELEASE_OPTION_NONE);
            }
        }
 
        //send a message about the most recent sensor data
        errorBuffPost = g_sf_message0.p_api->bufferAcquire(g_sf_message0.p_ctrl, &pOutputBuffer, &acquireCfgPost, 300); //attempt to acquire the posting buffer
        if (errorBuffPost==SSP_SUCCESS)
        {
            pOutputPost = (sensordata_payload_t *) pOutputBuffer; //cast buffer to our payload
            pOutputPost->header.event_b.class_code = SF_MESSAGE_EVENT_CLASS_SENSORDATA; //set the event class
            pOutputPost->header.event_b.class_instance = 2; //set the class instance
            pOutputPost->header.event_b.code = SF_MESSAGE_EVENT_NEW_DATA; //set the message type
            for (uint8_t temp=0; temp<NUMCHANNELS;temp++) pOutputPost->sensordata[temp] = lastknownsensor1values[temp] + lastknownsensor2values[temp];
            //for this demo, just add the two sensors read values
            for (uint8_t temp=0;temp<NUMCHANNELS;temp++) pOutputPost->sensorchannel[temp] = temp;
            g_sf_message0.p_api->post(g_sf_message0.p_ctrl, (sf_message_header_t *) pOutputPost,
                                                          &post_cfg, &errPost, TX_WAIT_FOREVER); //post the message
        }
        tx_thread_sleep (50);
    }
}

And finally, the output thread is given below. It may look similar to the one used in all the previous tutorials, however; this time it has added a blocking feature to the call to write to the UART. This ensures that the UART device has finished writing the data from the message before the buffer is released. If this were not put in place, it would be possible to overwrite the data that the UART module was reading, before it was done outputting it! Obviously, this is not desired behavior.

With the way it is configured now, if messages are being posted faster than the UART can write them, the buffer may overflow and hang the program. If the thread did not block until the UART was done writing, the message buffer would be released right after the write had been started and if another message was posted by the control thread before the UART module was done writing the data, the new message would overwrite the old message and the UART module would start writing the new data from where it was with the old data.

output_thread_entry.c

#include "output_thread.h"
#include "sensordata_api.h"
#include <stdio.h>
 
/* Output Thread entry function */
static volatile uint8_t uartdone=0;
 
void user_uart_callback(uart_callback_args_t *p_args)
{
    if (p_args->event == UART_EVENT_TX_COMPLETE)
        uartdone=1;
    return;
}
 
void output_thread_entry(void)
{
    uint8_t cstr[18*NUMCHANNELS];// = "Channel X: 12345\n"; //the text to be sent, stored as unsigned 8 bit data.
 
    g_uart0.p_api->open(g_uart0.p_ctrl, g_uart0.p_cfg); //initialization of the UART module
 
    sf_message_header_t * pHeader; //pointer to the message header
    sensordata_payload_t * thepayload; //pointer to the message payload
 
    while (1)
    {
        g_sf_message0.p_api->pend(g_sf_message0.p_ctrl, &output_thread_message_queue,
                                  &pHeader, TX_WAIT_FOREVER); //wait for a message forever
 
        if (pHeader->event_b.class_code == SF_MESSAGE_EVENT_CLASS_SENSORDATA) //if the message if the right kind
        {
            thepayload = (sensordata_payload_t *) pHeader; //cast the received message to the custom type
            if (thepayload->header.event_b.code == SF_MESSAGE_EVENT_NEW_DATA) //if the message event is the right kind
            {
                //spit out to UART
                for (uint8_t index=0; index<NUMCHANNELS; index++)
                {
                    sprintf(cstr +index*18, "Channel %1c: %5d\n", thepayload->sensorchannel[index]+'0', thepayload->sensordata[index]);
                }
                g_uart0.p_api->write(g_uart0.p_ctrl, cstr, 18*NUMCHANNELS); //send the information over UART
                while(uartdone==0); //block until uart completes
                uartdone=0;
                g_sf_message0.p_api->bufferRelease(g_sf_message0.p_ctrl, pHeader, SF_MESSAGE_RELEASE_OPTION_NONE);
            }
        }
        tx_thread_sleep (1);
    }
}

The astute reader may have noticed that the hal_entry.c file has not been included. Its default contents are fine for this application.

The Output/Conclusion

As can be seen in the image above, this program adds the two sets of data to create a third set of data, and sends that third set over UART. This is a simple demo program which illustrates how to use multiple message source threads under the Renesas Messaging Framework.

This guide has provided a basic framework for a multiple message source program. The sample code is general enough to be expanded upon and used for different applications. Feel free to copy and modify it if you have found it useful.

Questions/Comments

Any questions or comments please go to Digi-Key’s TechForum