Synergy and ThreadX: exploring basic RTOS structure

In our last article we saw Renesas Synergy product line and how to create threads in ThreadX by using E2 Studio. In this article we are going to look at some details regarding ThreadX operation and take a look at some kernel’s API functions.

The first thing to know about ThreadX (and RTOSs in general) is that usually no user code is written inside main() function! In fact, main() function code is absolutely simple and it is automatically generated by E2 Studio’s Synergy Configuration tool. Let’s take a look at the main() function code of our last examples:

int main(void)
{
    __disable_irq ();
    tx_kernel_enter ();
    return 0;
}

Simple isn’t it? Notice all the magic actually happens inside tx_kernel_enter() function which initializes ThreadX and starts the task scheduler. Some readers might wonder: what happens once the function returns? Why there is no for or while loop inside main()?

The answer is that tx_kernel_enter() function actually does not return! Once the task scheduler is started, the RTOS kernel takes control over the machine and only thread code created by user is run! In fact, the kernel actually calls tx_application_define() function which creates all threads and other application objects. Simplified code for tx_application_define() code related to our last example is shown below:

void tx_application_define(void * first_unused_memory)
{
    g_ssp_common_thread_count = 0;
    g_ssp_common_initialized = false;
    /* Create semaphore to make sure common init is done before threads start running. */
    tx_semaphore_create (&g_ssp_common_initialized_semaphore, "SSP Common Init Sem", 1);
    blinky_thread_create ();
    red_thread_create ();
    tx_application_define_user (first_unused_memory);
}

Functions blinky_thread_create() and red_thread_create() were created when we instructed Synergy Configuration to generate code. They are coded inside blinky_thread.c and red_thread.c respectively (these files and their headers are also automatically generated by Synergy Configuration tool and can be found inside src/synergy_gen folder within the project structure). Below we can see red_thread creation function:

void red_thread_create(void)
{
    /* Increment count so we will know the number of ISDE created threads. */
    g_ssp_common_thread_count++;
    /* Initialize each kernel object. */
    tx_thread_create (&red_thread, (CHAR *) "Red Thread", red_thread_func, (ULONG) NULL, &red_thread_stack, 1024, 1, 1,
                      1, TX_AUTO_START);
}

Now things are becoming interesting! Let’s take a look at tx_thread_create() function, it is a kernel function for creating new threads in ThreadX. Its arguments are the following: tx_thread_create(TX_THREAD *thread_ptr, CHAR *name_ptr, VOID (*entry_function)(ULONG), ULONG entry_input, VOID *stack_start, ULONG stack_size, UINT priority, UINT preempt_threshold, ULONG time_slice, UINT auto_start), where:

  1. thread_ptr – pointer to the thread control block (TCB) structure;
  2. name_ptr – pointer to a string with the thread name (the same as the name field in thread’s properties inside Synergy Configuration);
  3. entry_function – thread’s entry function;
  4. entry_input – entry function parameter;
  5. stack_start – pointer to the start of the thread’s stack area;
  6. stack_size – thread’s stack size in bytes (the same as the stack size field in Synergy Configuration);
  7. priority – thread’s priority level (the same as priority field in Synergy Configuration);
  8. preemp_threshold – thread’s priority threshold;
  9. time_slice – thread’s time slices;
  10. auto_start – selects if the thread will be started automatically with the kernel or manually by another thread.

Understanding most of these parameters is imperative in order to get the most of ThreadX, we discuss some of them below.

Stack sizing

Special care should be taken when sizing a thread stack. Keep in mind that an RTOS thread is nothing else then a C function, thus all local variables will be stored into thread’s stack. Moreover, function calls inside the thread code will also use that stack as well as all local variables of those functions. That means thread stack usage can quickly increase and lead to a system crash if code inside the thread tries to use more stack space than it is available, a condition known as stack overflow.  A stack overflow can be harmful due to the potential overwriting of other thread’s data or even kernel’s data, in most cases it would lead to an erratic behavior or to a complete application failure. Notice this is not an RTOS exclusive, it happens (a lot) to bare-metal systems too when system stack is incorrectly sized, but an RTOS can magnify that issue due to the multiple threads and stacks.

ThreadX (as well other RTOS) offers several features to help the developer dealing with stack issues. One of them, which is enabled by default, is the stack pattern filling, which fills all stack positions with a known pattern (0xEF) upon thread creation. It allows easy stack usage evaluation and sizing it according to specific thread needs. Another feature is the run-time stack checking (option TX_ENABLE_STACK_CHECKING) which makes use of the stack pattern filling to check for any stack corruption at run-time. This mechanism checks the stack every time a thread is suspended or resumed and can call a specified function when a corruption event is detected.

As a rule-of-thumb, development starts with an over sized stack and as development evolves, stack usage can be measured in several conditions enabling fine-tuning during later development stages. By default, Synergy Configuration tool sets a 1,024-byte stack size for every new thread, this size can be changed at any time at the thread property window. Note: do not change the stack size directly in the code as E2 Studio will surely override your changes!

Thread priority

An interesting feature of an RTOS is the possibility to assign different priority levels for each thread. The task scheduler then will run all ready tasks starting from the highest priority to the lowest priority (a lower priority thread is only resumed if there is no other higher priority thread ready to run). That means a priority-level 1 thread (considering 0 as the highest priority) will only be able to run if no level 0 thread is ready to run. A priority-level 2 thread will only run if no level 0 or 1 threads are ready to run and so on. By default, ThreadX has a 32-level priority system (from 0 to 31, with 0 as the highest priority), but that number can be changed by the user (up to 1,024 levels, in steps of 32). Note that for each 32 levels added, kernel RAM footprint increases in 128 bytes.

If multiple threads of the same priority-level are ready to run, the task scheduler uses a round-robin scheduler to run each thread within a time-slot. Optionally it is possible to change the number of time-slots assigned to a thread, by changing the “Time slicing interval (ticks)” property in the thread’s property window, thus enabling a thread to run for more time than another (of the same priority level). Keep in mind that this feature slightly increases memory usage and context switching time.

The kernel also has a special function (tx_thread_relinquish()) which can be used to relinquish CPU time, giving another thread of the same or higher priority level a chance to run. The kernel uses a round-robin mechanism so that the thread is moved to the end of the queue and will only run after all ready-threads with the same or higher priority had a chance to run. This performs similarly to a cooperative multitasking system.

ThreadX also includes the Preemption-Threshold feature which allows specifying a threshold for the thread preemption, that is, the thread can only be interrupted by threads with a priority higher than the threshold. Suppose an application with three threads (A with priority 8, B with priority 9 and C with priority 10) and thread C has its preemption-threshold set to 9, that means C may be interrupted (preempted) by thread A but will not be preempted by B! Of course, once thread C suspends, thread B will be able to run (if ready). Notice thread C preemption-threshold is only effective while C is running, otherwise, the task scheduler will follow the priority levels as expected (A will take precedence over B which will take precedence over C). It is even possible to dynamically change this setting by calling tx_thread_preemption_change() function!

Important: a thread with its preemption-threshold set to zero, won’t be preempted ever!

The number of priority levels and preemption-threshold availability are configured within the kernel properties as we will see below. By default, each thread created in E2 Studio has a priority level equal to 1 and a time slicing interval of 1. These properties can be changed at any time in the thread’s property window.

Changing ThreadX kernel properties in E2 Studio

Changing ThreadX kernel properties in E2 Studio can be done within the Synergy Configuration perspective. Select HAL/Common thread inside the Hal/Common Stacks window and then click on button + and select “X-Ware > ThreadX > ThreadX Source” as shown in figure below.

Figure 1

Once ThreadX source is added to the project, a new box (ThreadX Source) will appear along with the other HAL/Common drivers, it will show a red X indicating its configuration has an issue.

Figure 2

In order to solve that, click on the box we just added and then go the its properties window (bottom left), roll down the configuration list until the end and change option “Show linkage warning” to disabled.

Figure 3

Now it is possible to change any other kernel configuration as needed! We are not changing any of the default settings right now, so let’s just regenerate our application code by clicking on the “Generate Project Content”.

Example: thread stack sizing

Before closing this article, let’s show how to correctly size a thread’s stack and also how to manually start a thread. We are going to use our project from the last article and change it slightly. Our new project will be named MultiThreadLED2. By using the Synergy Configuration tool, let’s change red_thread’s “Auto start” property to disabled.

Let’s regenerate the code (button Generate Project Content in the Synergy Configuration tool) e then change blinky_thread code as shown below. Don’t forget to change tx_thread_sleep() calls on red_thread_entry to 50 as well!

#include "blinky_thread.h"

extern TX_THREAD red_thread;

void blinky_thread_entry(void) {
    uint8_t redStarted = 0;
    /* LED type structure */
    bsp_leds_t leds;
    /* Get LED information for this board */
    R_BSP_LedsGet(&leds);
    while (1) {
        //g_ioport.p_api->pinWrite(IOPORT_PORT_06_PIN_00, IOPORT_LEVEL_LOW);
        g_ioport.p_api->pinWrite(leds.p_leds[0], IOPORT_LEVEL_LOW);
        tx_thread_sleep (50);
        //g_ioport.p_api->pinWrite(IOPORT_PORT_06_PIN_00, IOPORT_LEVEL_HIGH);
        g_ioport.p_api->pinWrite(leds.p_leds[0], IOPORT_LEVEL_HIGH);
        tx_thread_sleep (50);
        if (!redStarted) {
            redStarted = 1;
            tx_thread_resume(&red_thread);
        }
    }
}

Once we start running the application, the green LED will blink once and then tx_thread_resume() is called (since redStarted is zero), starting the second thread (red_thread). This function instructs the kernel to resume the specified thread (as long as it is suspended). Figure 4 shows the modification result, upon start, both LEDs are lit, blinky_thread starts and turn off the green LED, after 500ms the LED is turned on again, the thread sleeps for more 500ms and then red_thread is started, from now on, both LEDs blink together.

 

Figure 4

Now let’s take a look at the thread stack usage. As we said, ThreadX enables by default the stack pattern filling with 0xEF. This feature is used by E2 Studio in order to analyze stack usage. To open that visualization window, select “Renesas View > Partner OS > RTOS Resources” in the main menu. A new tab (RTOS Resources) will appear in the bottom of the screen. This tab includes several views of ThreadX internals. Figure 5 shows the Thread tab where we can see information about all threads in our application.

Figure 5

By clicking on the Stack tab, we can see information regarding each thread stack. The last column shows the maximum stack usage for each thread!

Figure 6

We can see each stack is configured for a 1,024 bytes size, but thread 1 (Blinky Thread) topped at only 152 bytes! We could surely reduce stack size to, let’s say 256 bytes, or even less! Just be aware that the stack size must be a multiple of 8!

Warning! Fine tuning the stack size should be a very careful task, always make sure to exhaustively test the application within all possible situations!

Next time we will discuss shared resources and mutex on ThreadX, see you!

References

Leave a Reply