APIs, concepts, guides, and more
Real-Time Tasks

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

  1. User defines a custom GlobalData structure containing shared variables using the RSI_GLOBAL macro
  2. Metadata about the global is stored using the REGISTER_GLOBAL macro
  3. Real-time tasks receive a pointer to this structure and can read/write its members
  4. 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:

struct GlobalData
{
GlobalData() { std::memset(this, 0, sizeof(*this)); }
GlobalData(GlobalData&& other) { std::memcpy(this, &other, sizeof(*this)); }
// A shared integer counter
RSI_GLOBAL(int64_t, counter);
// Shared values for FollowSensor
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) {
// Read a global variable
double currentPosition = data->PositionTarget;
// Process data
double newPosition = currentPosition + 0.1;
// Write back to global variable
data->PositionTarget = newPosition;
}

In application code:

// Read a global variable
RSI::RapidCode::FirmwareValue value = taskManager->GlobalValueGet("PositionTarget");
double position = value.Double; // If you know the type already, you can read it directly
// If you don't know the type, you can get the type first and handle each type differently
RSI::RapidCode::RSIDataType type = taskManager->GlobalTypeGet("PositionTarget");
switch (type)
{
case RapidCode::RSIDataType::RSIDataTypeINT32:
// Handle int32
break;
case RapidCode::RSIDataType::RSIDataTypeUINT64:
// Handle uint64
break;
case RapidCode::RSIDataType::RSIDataTypeDOUBLE:
// Handle double
break;
//... Handle other types
default:
//...
}
// Write to a global variable
newValue.Double = 10.5;
taskManager->GlobalValueSet(newValue, "PositionTarget");
RSIDataType
Data types for User Limits and other triggers.
Definition rsienums.h:664
Union representing a generic RMP firmware value with multiple data types, stored in 64-bits.
Definition rsi.h:468
double Double
Double precision (64-bit) floating-point.
Definition rsi.h:477

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

Category Methods Description
Creation Discover, Get, Create Methods to create or discover task manager instances
Task Management TaskSubmit, TasksGet Submit new tasks and retrieve existing tasks
Global Data GlobalNamesGet, GlobalValueGet, GlobalValueSet, GlobalTypeGet Manage shared data between tasks and applications
Control Shutdown Control manager lifecycle
Information StatusGet, InfoGet, IdGet Retrieve manager status and information

RTTask Methods

Category Methods Description
Control Stop, TimingReset Control task execution and reset timing statistics
Status StatusGet, ExecutionCountAbsoluteWait, ExecutionCountRelativeWait Monitor task status and wait for execution events
Information InfoGet, IdGet Retrieve task configuration and identification

Key Structures

These structures will help you initialize and evaluate your RTTask applications.

Structure Description
RTTaskCreationParameters Defines how a task should be created and configured
RTTaskInfo Provides configuration information about a task
RTTaskStatus Provides execution status and timing statistics for a task
RTTaskManagerCreationParameters Defines how a manager should be created and configured
RTTaskManagerInfo Provides configuration information about a manager
RTTaskManagerStatus Provides status information about a manager

Task Manager States

State Description
RTTaskManagerState::Dead Task manager is not initialized or has been terminated
RTTaskManagerState::Running Task manager is running
RTTaskManagerState::Stopped Task manager is not running, but is initialized

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

State Description
RTTaskState::Dead Task is not initialized or has been terminated
RTTaskState::Disabled Task is initialized but not currently executing
RTTaskState::Waiting Task is waiting for its next scheduled execution time
RTTaskState::Running Task is currently executing

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).

Priority
TaskPriority::NonRealTime
TaskPriority::Lowest
TaskPriority::Low
TaskPriority::MediumLow
TaskPriority::Medium
TaskPriority::MediumHigh
TaskPriority::High
TaskPriority::Highest

📜 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.

// increment the counter in the global data
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.

// get the creation parameters for the task manager
#if defined(WIN32) && defined(NDEBUG)
// if we are on windows and not debug, then we need to create an intime task manager
RTTaskManagerCreationParameters GetTaskManagerCreationParameters()
{
RTTaskManagerCreationParameters parameters;
std::snprintf(
parameters.RTTaskDirectory,
RTTaskManagerCreationParameters::DirectoryLengthMaximum,
RMP_INSTALL_PATH // the rmp install directory
);
parameters.Platform = PlatformType::INtime;
std::snprintf(
parameters.NodeName,
RTTaskManagerCreationParameters::NameLengthMaximum,
"NodeA"
);
return parameters;
}
#else
// otherwise, we are on linux or debug, so we can create a native task manager
RTTaskManagerCreationParameters GetTaskManagerCreationParameters()
{
RTTaskManagerCreationParameters parameters;
std::snprintf(
parameters.RTTaskDirectory,
RTTaskManagerCreationParameters::DirectoryLengthMaximum,
RMP_INSTALL_PATH // the rmp install directory
);
// for linux real-time set this to an isolated core
// parameters.CpuCore = -1;
return parameters;
}
#endif // defined(WIN32) && defined(NDEBUG)

This the is application code, which creates the RTTaskManager, submits the Increment RTTask, and examines the counter global.

// tell the task manager the name of the function to run as a task, the name
// of the library that contains the function, and the directory where the
// library is located. The Increment function is defined in the RTTaskFunctions library.
// it increments a global counter variable.
std::cout << "Submitting task..." << std::endl;
RTTaskCreationParameters params("Increment");
RTTask task = manager->TaskSubmit(params);
// wait for the task to run for a bit
// get the counter global tag to see if the task ran correctly
std::cout << "Getting counter global tag..." << std::endl;
RSI::RapidCode::FirmwareValue counter = manager->GlobalValueGet("counter");
if (counter.Int64 <= 0)
{
// the task did not run correctly
exitCode = -1;
std::cout << "Counter is not greater than 0. The task did not run correctly." << std::endl;
}
else
{
// the task ran correctly
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.

Function Returns
RTMotionControllerGet() MotionController pointer
RTAxisGet() Axis pointer
RTMultiAxisGet() MultiAxis pointer
RTNetworkNodeGet() NetworkNode pointer

Here is an example RTTask that initializes global data.

// initialize the global data and random number generator
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.

// this calls a task to initialize the global variables and get pointers to rapidcode objects
// in the rttaskfunctions library. it needs to be called before any task
// that uses rapidcode objects.
void SubmitInitializationTask(RTTaskManager &manager)
{
std::cout << "Running initialization task..." << std::endl;
RTTaskCreationParameters initParams("Initialize");
initParams.Repeats = RTTaskCreationParameters::RepeatNone;
RTTask task = manager.TaskSubmit(initParams);
constexpr int timeoutMs = 5000;
task.ExecutionCountAbsoluteWait(1, timeoutMs); // wait for the task to execute once.
}

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.

// move the axis to follow a simulated sensor value
RSI_TASK(FollowSensor)
{
MotionController *controller = RTMotionControllerGet();
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.

// configure the axis
Axis *axis = controller->AxisGet(0);
axis->PositionSet(0);
axis->AmpEnableSet(true);
// create the task manager
RTTaskManagerCreationParameters parameters = RTTaskHelper::GetTaskManagerCreationParameters();
std::cout << "Creating task manager..." << std::endl;
manager = RTTaskManager::Create(parameters);
Helpers::CheckErrors(&manager.value());
// List all globals defined in RTTaskFunctions library
PrintGlobals(manager.value());
// call the initialization task to initialize the global variables and get pointers to RapidCode objects
// in the RTTaskFunctions library. This needs to be called before any task that uses RapidCode objects.
RTTaskHelper::SubmitInitializationTask(manager.value());
// tell the task manager the name of the function to run as a task, the name
// of the library that contains the function, and the directory where the
// library is located. The FollowSensor function is defined in the RTTaskFunctions library.
// It moves the axis back and forth based on a simulated sensor value.
std::cout << "Submitting task..." << std::endl;
RTTaskCreationParameters params("FollowSensor");
params.Period = 10;
RTTask task = manager->TaskSubmit(params);
// Print the submitted tasks
PrintTasks(manager.value());
// Let the task run while we log information
PrintStatusWhileRunning(manager.value(), axis);
// Wait until either the task has run at least 300 times (in total) or 500ms passes
// Stop the task
task.Stop();
axis->MotionDoneWait(TIMEOUT_MS); // The axis might still be moving from the task, so wait for it to finish
axis->AmpEnableSet(false);
// Use the RTTaskStatus struct to check the execution count and state
RTTaskStatus taskStatus = task.StatusGet();
int64_t executionCount = taskStatus.ExecutionCount;
RTTaskState taskState = taskStatus.State;
if (executionCount <= 0)
{
// the task did not run correctly
exitCode = -1;
std::cout << "Execution count is not greater than 0. The task did not run correctly." << std::endl;
}
else
{
// the task ran correctly
exitCode = 0;
std::cout << "Execution count: " << executionCount << " (>= 300 expected)" << std::endl;
}
// Print the final axis position
std::cout << "Axis position: " << axis->CommandPositionGet() << std::endl;
// Print the task state
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?

How can I create an RTTasks library with a custom name and directory?

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?

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.