Having access to a Real-time Operating System (RTOS) provides embedded system developers many advantages such as:
In the bare-metal universe, developers must fight with their system to adjust timing with every code change and manage the system timing all by themselves. Using an RTOS such as ThreadX, which is included in the Renesas Synergy™ Platform toolchain with no additional costs, cannot only simplify embedded software development but provide developers with the tools necessary to ensure their system performs as expected. In this series, Transitioning to ThreadX, we will explore RTOS concepts and demonstrate how they can be immediately applied to the Synergy Platform to help developers decrease system complexity.
The first concept a developer needs to grasp about RTOSes is that the software can now be broken up into individual tasks or threads. A thread is a semi-independent program segment that performs a specific function within the application. For our purposes, threads and tasks are synonyms but refer to the ThreadX documentation for a detailed explanation on the minor differences. Threads give an embedded system the illusion that they are running concurrently. The RTOS kernel manages the threads such that the highest priority thread that is ready to run can use the microcontrollers CPU to execute while the remaining threads are suspended waiting for their turn on the CPU.
Each thread has a thread control block, TCB, which contains information about the thread such as its priority, a pointer to the function that should be executed when the thread is ready to execute, a string name and many other elements including a pointer to the threads stack space. Yes, you read that correctly! Every thread has its own, personal and individual stack space that is separate from every other thread. It is up to the developer to determine the appropriate size for the stack and size it accordingly. Before we concern ourselves about stack sizes, we should first examine how we can go about creating a thread in the first place.
For a developer that is not using the Synergy Platform, creating a thread requires calling the tx_thread_create API and passing the necessary initialization parameters into the function. This is not necessarily a big deal but as can be seen below, providing the right information to the create API call can require some time to pull together the correct parameters and is error prone:
tx_thread_create (&blinky_thread, // pointer to thread
(CHAR *) "Blinky Thread", // pointer to String name
blinky_thread_func, // Thread entry function
(ULONG) NULL, // Entry parameter pointer
&blinky_thread_stack, // Stack start pointer
1024, // Stack size
1, // Priority
1, // Preemption Threshold
1, // Time Slice
TX_AUTO_START); // Autostart
When using the Synergy Configurator, developers need to simply visit the Threads tab and then within the Threads section, select the green plus symbol as highlighted below in red:
Pressing the add thread button will create a new thread which contains the default settings. Developers can view the thread properties and adjust the name, stack size, priority and even whether the thread is started immediately or enabled by the application code. The parameters that are accessible to the developer can be seen below:
Once a developer updates these properties and presses the Generate Project Content button, the development environment will generate initialization code and the thread. The initialization code and the thread create function can be found under the /src/synergy_gen/ folder in the .h and .c files with the thread name. These files are automatically generated and cannot be changed by the developer. However, the thread code is located under the /src/ folder and is has the thread name appended with _entry. For example, new_thread0 would have a new_thread0_entry.c module that contains a default new_thread0_entry function just waiting for a developer to add their own custom code to. The default code that is generated would be:
Notice that the thread has an infinite while loop included. Since every thread is a semi-independent program, it looks just like a normal bare-metal main function that would include an infinite loop to keep the application code running. A thread can be created that does not include this infinite loop but the thread would run once and then be marked as completed and never execute again without calling a kernel function to restart the thread.
A best practice is to never allow a thread to complete on its own but to instead use a kernel feature to halt or remove it if necessary. ThreadX handles running a thread to completion without issues but other RTOSes will define such an event as an undefined feature and can cause a system failure. FreeRTOS for example will fail silently and stop executing code if a task runs to completion, leaving the developer chasing his tail to figure out why their application code stopped executing. ThreadX thankfully continues to execute as if nothing had happened other than a thread has completed.
There is so much more to discuss about ThreadX and how to get the most out of it when using the Synergy Platform but hopefully this introduction has wetted your whistle for now. Until next time,
Live long and profit!
Hot Tip of the Week
Using multiple threads can be especially effective in complex applications that have several tasks that need to execute in parallel. For example, a web camera needs to manage real time video, an HTTP connection, JPEG conversion and control or diagnostic connectivity, perhaps over USB. This would be a very tall order for a single loop implementation. Check out the following application project that illustrates and demonstrates how multiple threads can be used to make such complex projects manageable. https://www.renesas.com/en-us/doc/products/renesas-synergy/apn/r12an0046eu0120-synergy-pdc-camera.pdf