Synergy and ThreadX: introduction to multitasking

Microcontroller applications are getting more and more complex, using lots of memory and complex peripherals. This complexity increase makes it necessary using software tools to ease application development, so that the designer or designing team can focus their effort in the higher level application development, instead of wasting time writing lower level software.

Among commonly used software tools,  the real time operating systems (RTOS) are gaining more and more space, since an RTOS enables addressing complex problems in a simpler way when compared to the single-task programming paradigm.

In this article we are taking a look at Renesas Synergy Platform, which tries to improve time-to-market using an unusual approach: to offer an ARM Cortex-M based solution paired with a software tool which combines an Eclipse-based IDE, GCC toolchain and ThreadX, a powerful commercial real time operating system. Although RTOS support can be found among several manufacturers, the difference here is that Renesas and Express Logic (ThreadX manufacturer) customized the RTOS and included a full software stack supporting most internal peripherals (the Synergy Support Package or SSP)! The final result is that it is much faster and easier to design complex applications such as those using communication (especially USB, Ethernet and Internet), graphical Human-Machine Interfaces (HMIs) using color displays and touch screens, etc. Synergy product line is split into four lines:

  • S1: Cortex-M0+ microcontrollers (32MHz, 64 to 256kb Flash, 16 to 24kb RAM, 4kb EEPROM, packages from 36 to 64 pins and operating voltages ranging from 1.6 to 5.5V);
  • S3: Cortex-M4 microcontrollers (48MHz, up to 1Mb Flash, up to 192kb RAM, 16kb EEPROM, packages from 64 to 145 pins and operating voltages ranging from 1.6 to 5.5V);
  • S5: Cortex-M4 microcontrollers (120MHz, up to 2Mb Flash, 640kb RAM, 64kb EEPROM, packages from 100 to 176 pins and operating voltages ranging from 2.7 to 3.6V);
  • S7: High-end Cortex-M4 microcontrollers (240MHz, up to 4Mb Flash, 640kb RAM, 64kb EEPROM, packages from 100 to 224 pins and operating voltages ranging from 2.7 to 3.6V);

All Synergy microcontrollers include communication interfaces such as CAN, I2C, SPI, UART and USB, 12 or 14-bit ADCs, 12-bit DAC, capacitive inputs and security (ADC diagnostics, RAM error checking, CRC, etc). S5 and S7 devices also include ethernet controller, 800×480 WVGA display controller (up to 32-bit color depth), 2D acelerator, JPEG CODEC, external memory QSPI interface and MMC/SD interface.

Multitasking

Migration from a single-task paradigm to a preemptive multitasking paradigm requires some understanding of concurrent programming and how a multitasking works. The main concept the programmer must bear in mind is that in a multitasking system (or a multi-threading system as in case of most RTOS), processor time is split among all the tasks (threads), each one running within a time-slice, slot or quantum. The task scheduler periodically suspends a thread and switches for the next one, so that apparently the tasks run at the same time. That opposes to a single-task system in which a task is only interrupted by peripheral interrupts.

From the user’s point-of-view (we are talking about the RTOS user, that is, the embedded system designer/programmer), an RTOS task or thread can be thought as a stand-alone application of a single task system (it will all depend on the availability of a Memory Protection Unit, MPU, or a Memory Management Unit, MMU and how it is used by the kernel). However, due to the RTOS task-scheduler, it is not possible to be sure a thread won’t get interrupted at any point or at any time (that means it is very difficult or mostly impossible to implement bit-banging communication in a thread).

Furthermore, multi-task programming introduces some concepts which may not sound familiar to traditional embedded systems designers:

  • Race conditions: multiple threads accessing the same global resource (a global variable or a peripheral) can lead to data corruption if a thread is interrupted while a transaction is ongoing, that is, a context switch occurs before the thread completes the operation on the global resource and another task resumes and modify the same resource. In order to avoid such problem an RTOS can offer several tools such as: critical section barriers, mutual exclusion, etc;
  • Deadlocks: they occur when two or more threads need resources allocated (blocked) by the other thread, so both threads block and wait one for another indefinitely;
  • Priority inversion: occurs when a higher priority thread waits for a resource being used by a lower priority thread, so the lower priority thread is preempted and the higher priority thread can’t run because it does not have the resources it needs;
  • Starvation: when a system has high and low priority threads, in some cases high priority threads can get most or all of the CPU time, so that low priority threads will never run.

Along with these concepts, multi-task programming also includes a number of important concepts/techniques such as: Inter-Process Communication (IPC), task/thread synchronization and memory allocation in multitasking systems.

Why should I use an RTOS?

The reader might probably wonder: if an RTOS is so complicated, why should I use one? The answer for that is not so simple. The fact is that current embedded applications are becoming  more and more complex, using communication layers and other software stacks, an RTOS can make it easier to design complex applications because it usually provides a working set of tools and drivers, allowing the designer to focus on the higher level of the application. So the simple answer is: an RTOS can ease application design and maintenance.

In fact, any application can get the benefits of using an RTOS, since the use of an operating system helps with problem abstraction in a higher level. Multitasking enables an application to be factored into smaller pieces (threads) which can work almost independently. An application could have a thread dealing with keyboard scanning, another one dealing with a display and application screens, another one processing data acquired from sensors and so on.

In a product such as Synergy, the RTOS (ThreadX) is perfectly integrated to the microcontroller internal peripheral drivers and a middleware is also provided, including communication stacks, file system, graphical user interface management, etc, and all of this is fully tested! So, instead of design and troubleshooting drivers and stacks, you can start from a perfectly working point without worrying with all those lower-level stuff. That results in a substantial decrease in development time.

Along with that, another important aspect which is usually underestimated is code maintenance. Embedded applications, as any other piece of software, are subject to improvement, new features and newer technologies. Whatever is the case, an RTOS-based application will be much easier to maintain and improve than a bare metal (single task) one.

Multi-thread Hello World

Let’s start our ThreadX experiments by creating a single-thread LED blinker on the SK-S7G2 board. We need to create an E2Studio project (E2 Studio is Renesas Eclipse-based IDE), select File > New > Synergy C Project.

Figure 1
Figure 2
Figure 3

Once the project has been created, we should see a screen such as the one shown in figure 4. It yours in not like that, try double-clicking configuration.xml file and then select the Synergy Configuration perspective.

Figure 4

Now we can inspect our thread, click on the “Threads” tab and you shall see two threads. The first one (HAL/Common) has the initialization code for peripherals and system. Note that although it is listed as a thread, HAL/Common is not a real thread, it is just a placeholder for all Synergy initialization code. Also, it cannot be changed. The second thread (Blinky thread) is the user thread created by E2 Studio. It has an example code we are going to change later. It is possible to add and to delete threads by clicking on the + and X buttons (upper right of the thread box) and by clicking a thread it is possible to edit some of its properties (on the Properties window which appears in the bottom left side of the IDE window). Within the properties window we can inspect and change thread’s name, stack size, thread priority, initialization and time slices. We are keeping all as it is for now.

Figure 5

Replace the content of blinky_thread_entry.c file (found in project’s src folder on Project Explorer) by the following code:

#include "blinky_thread.h"

void blinky_thread_entry(void) {
    /* LED type structure */
    bsp_leds_t leds;
    /* Get LED information for this board */
    R_BSP_LedsGet(&leds);
    while (1) {
        g_ioport.p_api->pinWrite(leds.p_leds[0], IOPORT_LEVEL_LOW);
        tx_thread_sleep(100);
        g_ioport.p_api->pinWrite(leds.p_leds[0], IOPORT_LEVEL_HIGH);
        tx_thread_sleep(100);
    }
}

Now if we compile the application and debug it we will see LED1 (green) blinking at 0.5Hz frequency on SK-S7G2 board. The tx_thread_sleep(100) function call makes the thread suspend for 100 OS ticks. By default, ThreadX is configured to operate at 100 ticks per second, that is, each tick takes 10ms.

But what happens when the thread is suspended? Technically it is removed from the ready queue list and an internal counting is started within the kernel. As there is no other tasks to run, the RTOS will simply wait for the next tick and so on until the internal counter reaches 100. When that counter expires, the thread is moved to the READY list again and will be scheduled as the next thread to run if no other higher priority thread is also ready to run (the lower the priority level, the higher is its priority). In our case, as there is no other thread, it will run as soon as that counting expires!

Adding a second thread

Now it’s time to really feel what is multithreading about. Open the Synergy configuration (figure 5) and click the + button within Threads in order to create a new thread. In the thread properties window, rename the thread symbol to red_thread and the thread name to “Red Thread” (without the double quotes).

Once the properties are changed, click the “Generate Project Content” in order to E2 Studio generate project code again (it will automatically include the new thread we’ve just created). Once code is generated, we will see that E2 Studio have added a new file in the src folder: red_thread_entry.c. This file stores code for our second thread, open it and insert the function code as shown below:

void red_thread_entry(void) {
    /* LED type structure */
    bsp_leds_t leds;
    /* Get LED information for this board */
    R_BSP_LedsGet(&leds);
    while (1) {
        g_ioport.p_api->pinWrite(leds.p_leds[1], IOPORT_LEVEL_LOW);
        R_BSP_SoftwareDelay(1000,BSP_DELAY_UNITS_MILLISECONDS);
        g_ioport.p_api->pinWrite(leds.p_leds[1], IOPORT_LEVEL_HIGH);
        R_BSP_SoftwareDelay(1000,BSP_DELAY_UNITS_MILLISECONDS);
    }
}

By reading the code we can see this thread will also blink an LED (this time the second one in the structure got from BSP, that is, red LED) at 0.5Hz frequency (1000ms on and 1000ms off), just as we did in the first thread. The only difference here is that we are using R_BSP_SoftwareDelay(), a software delay function provided by Renesas BSP.

Figure 6

Program testing (figure 6) shows that the threads start at the same time (and so the LEDs) but even having the same theorical delay, soon after started the LEDs are blinking without synchronism. Can you tell why?

The answer is that the first thread relies on the kernel tick (interrupt controlled) to set green LED’s on and off time. The second thread, on the other hand, relies on a software delay to do the same thing. That means every time a tick happens (that is 100 times per second) the thread is interrupted for a while and (supposing thread 1 is suspended) thread 2 is resumed again. That makes the software delay to last for more time than expected (and calculated), so the red LED ends up going out of synchronism because its blinking period is slightly higher! Note that if we had more tasks running, that time would be even higher!

That doesn’t happen to the first thread as its timing comes from the RTOS kernel. In order to verify that hypothesis, let’s replace the R_BSP_SoftwareDelay() calls on the second thread for tx_thread_sleep(100) calls as we did in the first thread. The result is shown in figure 7.

Figure 7

Interesting isn’t it? Now both LEDs blink at the same frequency and in absolute synchrony! This is our first rule regarding the bare-metal to multi-threaded programming paradigm transition: software delays are not reliable within an RTOS and should be avoided at all cost! Instead, prefer using kernel timing functions which are context-switch aware and also allow better CPU utilization. These functions will usually suspend a task until the desired time elapses, allowing the kernel to give more time to other active threads.

In the next article of this series we will take a look at the ThreadX kernel in more details and will see how to configure it using E2 Studio, see you!

References

2 thoughts on “Synergy and ThreadX: introduction to multitasking

Leave a Reply