PREMIUM FEATURE
Leverage Real-Time Tasks (RTTasks) alongside RMP firmware to execute deterministic user logic, motion control, and I/O operations without specialized real-time programming expertise.
- Note
- This page is intended to give you a better understanding of RTTasks structure and usage. For a guide on integrating RTTasks into your application, see: Create a Real-Time Task
🔹 What are Real-Time Tasks?
Real-Time Tasks (RTTasks) provide a powerful framework to run functions that use RapidCode alongside the RMP firmware that simplifies real-time programming for industrial automation. This feature enables machine builders and software developers to write and run deterministic user logic, motion control, and I/O operations without requiring specialized expertise in real-time operating systems.
To build an RTTasks project you compile your C++ task functions into a shared library (for example RTTaskFunctions.dll on Windows, RTTaskFunctions.rsl on INtime, or libRTTaskFunctions.so on Linux). The RTTaskManager, distributed with RMP, loads that library at runtime. Your user application, written in any RapidCode-supported language, uses the RapidCode API to start a manager instance and submit tasks by name from the shared library.
Every sampling interval, the RTTaskManager firmware executes scheduled RTTasks with precise timing guarantees, making them ideal for applications where consistent, predictable execution is critical to system performance.
Architecture and Key Components
Real-Time Tasks (RTTasks) provide a layered architecture that defines clear roles for each component and separates real-time from non-real-time execution.
- RTTaskManager (Firmware) – Distributed with RMP, the manager runs inside the real-time operating system, loads the RTTaskFunctions library, and schedules the functions according to their configured periods and priorities.
- RTTask (Execution Unit) — Represents an individual real-time function instance managed by the RTTaskManager. Each task runs at a defined priority and period.
- RTTaskFunctions library (Real-Time Logic) – This C++ project produces a shared library of deterministic task functions and the accompanying GlobalData definition. It cannot run on its own. The RTTaskManager loads and executes the functions from this library at runtime as RTTask's.
- GlobalData (Shared Memory Bridge) — A structured data block defined in the RTTaskFunctions project that both environments can safely access. RTTasks read or write GlobalData members inside the real-time loop, while the user application reads or updates those values from the host side using the RealTimeTasks API.
- User application – Runs in the non-real-time environment using RapidCode (C++, C#, or Python) or RapidCodeRemote (any gRPC-capable language). It communicates with the RTTaskManager to submit, monitor, and control tasks. It can also read and write to GlobalData through methods in the RealTimeTasks API. All high-level logic remains here, outside the real-time environment.
This structure makes the boundaries between environments explicit: write your real-time logic in C++, compile it into the shared RTTaskFunctions library, and keep high-level orchestration and coordination in your preferred RapidCode language.
🔹 Why use Real-Time Tasks?
RTTasks solve several critical challenges in industrial automation development:
Simplified Real-Time Programming
Traditional real-time programming requires expertise in:
- Thread management and scheduling
- Priority handling
- Resource sharing and synchronization
- Real-time debugging techniques
RTTasks eliminate these complexities by providing:
- Automatic thread creation and management
- Simple priority configuration
- Safe inter-task communication
- Support for familiar debugging tools
Deterministic Execution
RTTasks guarantee consistent execution timing, critical for:
- Precise motion control
- Synchronized multi-axis movements
- Consistent sampling of sensor data
- Time-sensitive I/O operations
Examples of RTTask Applications
- Real-time sensor feedback loops for height control in 3D printing
- Custom motion profiles beyond standard PVT moves
- Synchronization of external devices with motion
- Complex condition monitoring and fault detection
- High-frequency data acquisition and processing
🔹 Global Data Communication
Safe communication between real-time tasks and non-real-time client applications is achieved through a shared memory structure called GlobalData. You define this structure inside your RTTaskFunctions project, and it becomes accessible from the non-real-time environment through the RealTimeTasks API so both layers can exchange data safely.
The GlobalData mechanism provides a structured way to exchange information between:
- Different real-time tasks running in the same RTTaskManager
- Real-time tasks and non-real-time application code
- Motion controller firmware and user application logic
How GlobalData Works
- User defines a custom GlobalData structure containing shared variables using the RSI_GLOBAL macro
- Metadata about the global is stored using the REGISTER_GLOBAL macro
- Real-time tasks receive a pointer to this structure and can read/write its members
- Application code can access these same variables using the RealTimeTasks API's RTTaskManager global methods
Defining GlobalData
To create shared variables, define them in your GlobalData structure using the RSI_GLOBAL macro and register them with the REGISTER_GLOBAL macro:
{
GlobalData() { std::memset(this, 0, sizeof(*this)); }
GlobalData(GlobalData&& other) { std::memcpy(this, &other, sizeof(*this)); }
RSI_GLOBAL(int64_t, counter);
RSI_GLOBAL(int32_t, startingSample);
RSI_GLOBAL(double, sensorValue);
};
inline constexpr GlobalMetadataMap<RSI::RapidCode::RealTimeTasks::GlobalMaxSize> GlobalMetadata(
{
REGISTER_GLOBAL(counter),
REGISTER_GLOBAL(startingSample),
REGISTER_GLOBAL(sensorValue),
});
Global Variable Access Methods
| Method | Description | Parameters | Return Value |
| GlobalNamesGet | Retrieves names of all registered global variables | Optional library name and directory | Vector of variable names |
| GlobalValueGet (by name) | Reads a global variable's value by its name | Variable name, optional library name and directory | FirmwareValue containing the value |
| GlobalValueSet | Sets a global variable's value | New value, variable name, optional library name and directory | None |
| GlobalTypeGet (by name) | Reads a global variable's type by its name | Variable name, optional library name and directory | RSIDataType of the global |
Example Usage
In real-time task code:
RSI_TASK(MyTask) {
double currentPosition = data->PositionTarget;
double newPosition = currentPosition + 0.1;
data->PositionTarget = newPosition;
}
In application code:
double position = value.
Double;
switch (type)
{
case RapidCode::RSIDataType::RSIDataTypeINT32:
break;
case RapidCode::RSIDataType::RSIDataTypeUINT64:
break;
case RapidCode::RSIDataType::RSIDataTypeDOUBLE:
break;
default:
}
taskManager->GlobalValueSet(newValue, "PositionTarget");
RSIDataType
Data types for User Limits and other triggers.
Union representing a generic RMP firmware value with multiple data types, stored in 64-bits.
double Double
Double precision (64-bit) floating-point.
Global variables provide:
- Type-safe access to shared data
- Atomic operations for race-free updates
- Consistent read/write semantics across real-time boundaries
- Zero-copy data exchange between tasks and applications
🔹 API
The Real-Time Tasks API provides comprehensive interfaces for creating, managing, and monitoring deterministic user-defined tasks that execute alongside the RMP firmware.
Core Classes
| Class | Description |
| RTTaskManager | Manages real-time tasks firmware including creating, scheduling, and controlling multiple tasks |
| RTTask | Controls and monitors an individual real-time task |
RTTaskManager Methods
RTTask Methods
Key Structures
These structures will help you initialize and evaluate your RTTask applications.
Task Manager States
The RTTaskManager will persist beyond the life of your application unless terminated with Shutdown.
- Note
- By default, the RTTaskManager requires RMP to run (and continue running), but this may be overridden via the NoRmp field. Examine the RTTaskManagerCreationParameters API reference for more information.
Task States
Task Priorities
Assign RTTasks priorities corresponding to their importance to determine the order of preemption. The default is TaskPriority::Medium. An important task (e.g., feeding position data to a motor) deserves a higher priority than an unimportant one (e.g., periodically sending data to a UI).
📜 Sample Code
Learn how to create and manage real-time tasks for deterministic execution.
- Warning
- This is a sample program to assist in the integration of the RMP motion controller with your application. It may not contain all of the logic and safety features that your application requires. We recommend that you wire an external hardware emergency stop (e-stop) button for safety when using our code sample apps. Doing so will help ensure the safety of you and those around you and will prevent potential injury or damage.
The sample apps assume that the system (network, axes, I/O) are configured prior to running the code featured in the sample app. See the Configuration page for more information.
- Additional Resources
-
Basic RTTask Creation and Management
This sample demonstrates how to create a simple RTTask that runs every sample interval.
This is the code that will be running cyclically in the RTTask. All it does is increment a global counter.
RSI_TASK(Increment)
{
data->counter += 1;
}
These are the RTTaskManagerCreationParameters we will be using. The required parameters are different depending on whether the task manager is going to run in Windows, INtime, or Linux.
#if defined(WIN32) && defined(NDEBUG)
RTTaskManagerCreationParameters GetTaskManagerCreationParameters()
{
RTTaskManagerCreationParameters parameters;
std::snprintf(
RTTaskManagerCreationParameters::DirectoryLengthMaximum,
RMP_INSTALL_PATH
);
parameters.
Platform = PlatformType::INtime;
std::snprintf(
RTTaskManagerCreationParameters::NameLengthMaximum,
"NodeA"
);
return parameters;
}
#else
RTTaskManagerCreationParameters GetTaskManagerCreationParameters()
{
RTTaskManagerCreationParameters parameters;
std::snprintf(
RTTaskManagerCreationParameters::DirectoryLengthMaximum,
RMP_INSTALL_PATH
);
return parameters;
}
#endif
This the is application code, which creates the RTTaskManager, submits the Increment RTTask, and examines the counter global.
std::cout << "Submitting task..." << std::endl;
RTTask task = manager->TaskSubmit(params);
std::cout << "Getting counter global tag..." << std::endl;
{
exitCode = -1;
std::cout << "Counter is not greater than 0. The task did not run correctly." << std::endl;
}
else
{
exitCode = 0;
std::cout <<
"Counter: " << counter.
Int64 << std::endl;
}
Using RapidCode objects
In order to command motion using RMP, you will need to use RapidCode objects. This sample demonstrates how to do so inside an RTTask.
RapidCode object pointers will be available via getter functions provided by rttaskglobals.h. Prefer using these calls over regular RapidCode calls, as they have null pointer checks that will prevent segmentation faults.
Here is an example RTTask that initializes global data.
RSI_TASK(Initialize)
{
data->counter = 0;
data->sensorValue = 0;
data->startingSample = 0;
std::srand(std::time(nullptr));
}
In the application code, we submit the Initialize task as follows.
void SubmitInitializationTask(RTTaskManager &manager)
{
std::cout << "Running initialization task..." << std::endl;
RTTaskCreationParameters initParams("Initialize");
initParams.Repeats = RTTaskCreationParameters::RepeatNone;
constexpr int timeoutMs = 5000;
}
Here is an example of a task function that uses a RapidCode object. It reads a simulated sensor value, then moves the axis to it.
RSI_TASK(FollowSensor)
{
if (data->startingSample == 0) {
data->startingSample = controller->SampleCounterGet();
}
data->sensorValue = getSensorValue(controller, data->startingSample);
RTAxisGet(0)->MoveTrapezoidal(data->sensorValue, 5, 10, 10);
}
Here is the application code that puts it all together, submitting the Initialize task first and then submitting the FollowSensor task. See the follow-sensor.cpp C++ sample for the complete source file.
Axis *axis = controller->AxisGet(0);
std::cout << "Creating task manager..." << std::endl;
PrintGlobals(manager.value());
RTTaskHelper::SubmitInitializationTask(manager.value());
std::cout << "Submitting task..." << std::endl;
params.Period = 10;
RTTask task = manager->TaskSubmit(params);
PrintTasks(manager.value());
PrintStatusWhileRunning(manager.value(), axis);
if (executionCount <= 0)
{
exitCode = -1;
std::cout << "Execution count is not greater than 0. The task did not run correctly." << std::endl;
}
else
{
exitCode = 0;
std::cout << "Execution count: " << executionCount << " (>= 300 expected)" << std::endl;
}
std::cout << "State: " << RTTaskHelper::StateToString(taskState) << " (DISABLED expected)" << std::endl;
- Note
- The above code uses the Helpers and RTTaskHelper namespaces because it is an excerpt from follow-sensor.cpp in the C++ sample apps. For details on the implementation of these helper functions, check their source files in the C++ sample apps. Similarly, the printing helper functions (e.g., printGlobals, printTasks) can be found in the follow-sensor.cpp source file.
🔹 Best Practices
Execution Time Management
- Keep task execution times short and consistent
- Avoid blocking operations in real-time tasks
- Use the RTTaskStatus timing statistics to identify performance issues
- Set appropriate priorities based on timing requirements
Memory Management
- Avoid dynamic memory allocation in real-time tasks
- Pre-allocate resources before entering real-time execution
- Prefer stack-based variables over heap-based objects
Thread Safety
- Use atomic operations for shared variables
- Avoid complex synchronization mechanisms
- Leverage GlobalData for safe inter-task communication
Testing and Debugging
- Test tasks in non-real-time mode before deploying
- Use stepping and debugging in development environments
- Monitor task execution times and states during operation
- Use RTTaskManager status information for diagnostics
🔹 Next Steps
After familiarizing yourself with the RTTask API, try examining a more complex application, like our RapidLaser Gimbal Demo
❓ FAQ
When should I use RTTasks instead of RapidCode?
RTTasks guarantees deterministic, real-time performance where regular RapidCode applications cannot. This is useful for cases where you need to meet strict timing requirements, such as responding to input within a certain number of milliseconds, or periodic tasks that cannot miss a cycle.
What are the limitations of an RTTask?
While there are no strict limitations on the kind of C++ code that you can write, your RTTasks will be subject to timing restraints based on your sample rate. Ensure that you have enough headroom in each sample for both the RMP firmware and your RTTask operations to execute.
What happens to the RTTaskManager after my application ends?
The RTTask manager will persist independently of your RapidCode application unless a call to Shutdown is made.
Should I have RMP running before starting my RTTaskManager?
In general, yes. The RTTaskManager uses RMP interrupts for its timing by default. This can be disabled with the NoRmp flag (see RTTaskManagerCreationParameters for more details), but you must have RMP running to command motion with any RapidCode objects.
Can I use controller->AxisGet() in my RTTask function?
While it is possible, it is strongly suggested you use RTAxisGet(), defined in rttaskglobals.h, instead. This method and other RTObjectGet() methods have built-in error checking for RTTasks users.
See Using RapidCode objects for more information.
How do I make an RTTask repeat forever?
While configuring your RTTaskCreationParameters, set the RTTaskCreationParameters::Repeats field to the special static constant value, as shown below:
params.Repeats = RTTaskCreationParameters::RepeatForever;
Then, when you submit this task, the RTTaskManager will repeat it until stopped.
How can I create an RTTasks library with a custom name and directory?
Use the instructions under the "Optional" sections of the guide Create a Real-Time Task.
Why would I run RTTasks on Windows?
Windows users will typically run their RTTasks on INtime. However, users may run their RTTasks on Windows to debug them without needing the INtime SDK.
Important caveat: RTTasks running on Windows will not have real-time performance. This means that it should not be used outside of development, and that bugs related to real-time performance may not be present.
How do I debug RTTasks on Windows?
Windows users who want to debug their RTTasks on Windows should set the RTTaskManagerCreationParameters::Platform to Windows. Additionally, comment out any lines which set RTTaskManagerCreationParameters::NodeName until returning to running on INtime.
- Note
- This is intended for debugging only. For real-time performance, Windows users must run RTTasks on INtime.
When should I split one RTTask into two?
Whenever you want a separation of concerns (e.g., one task should continue even if the other fails), it is recommended to use multiple tasks. Additionally, any case where tasks require differences in their RTTaskCreationParameters (e.g., priority, period, repeats) naturally requires multiple tasks.