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

PREMIUM FEATURE BETA
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.

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

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.

Key Components

Real-Time Tasks consist of several key components:

  1. RTTaskManager firmware: Orchestrates the synchronization and execution of multiple tasks with proper scheduling and priorities
  2. RTTask: Individual units of execution containing user-defined C++ functions
  3. Global Data: User-defined shared memory structure for safe communication between tasks and non-real-time applications

🔹 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. This structure contains variables that can be accessed by both real-time tasks and client applications.

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 RTTaskManager's 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 double variables
RSI_GLOBAL(double, average);
RSI_GLOBAL(double, targetPosition);
};
inline constexpr GlobalMetadataMap<RSI::RapidCode::RealTimeTasks::GlobalMaxSize> GlobalMetadata(
{
REGISTER_GLOBAL(counter),
REGISTER_GLOBAL(average),
REGISTER_GLOBAL(targetPosition),
});

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:

void MyTask(GlobalData* globals) {
// Read a global variable
double currentPosition = globals->PositionTarget;
// Process data
double newPosition = currentPosition + 0.1;
// Write back to global variable
globals->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:654
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

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

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

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

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.

// This task increments the counter in the global data
LIBRARY_EXPORT void Increment(GlobalData* data)
{
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()
{
std::snprintf(
parameters.RTTaskDirectory,
RTTaskManagerCreationParameters::DirectoryLengthMaximum,
RMP_DEFAULT_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()
{
std::snprintf(
parameters.RTTaskDirectory,
RTTaskManagerCreationParameters::DirectoryLengthMaximum,
RMP_DEFAULT_PATH // The RMP install directory
);
// For Linux real-time set this to a 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.

RTTaskManagerCreationParameters parameters = RTTaskHelper::GetTaskManagerCreationParameters();
std::cout << "Creating task manager..." << std::endl;
std::unique_ptr<RTTaskManager> manager(RTTaskManager::Create(parameters));
int exitCode = -1; // Set the exit code to an error value.
try
{
// 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");
params.Repeats = RTTaskCreationParameters::RepeatForever;
std::unique_ptr<RTTask> task(manager->TaskSubmit(params));
// Wait for the task to run for a bit
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// 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;
}
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
// Shutdown the task manager firmware process
manager->Shutdown();

Using RapidCode objects

This sample demonstrates how to use RapidCode objects inside an RTTask.

Before using and RapidCode objects inside of RTTasks you need to initialize the pointers inside the RTTaskManager by calling InitializeRapidCodeObjects() inside an RTTask. After doing so the RapidCode object pointers will be accessible like normal "C-style" global variables (not GlobalData).

Here is an example RTTask that initializes the RapidCode object pointers.

// This task initializes the global data, the random number generator, and gets pointers RapidCode objects
LIBRARY_EXPORT void Initialize(GlobalData* data)
{
// Initialize the pointers to RapidCode objects, which can then be accessed
// as normal C-style global variables (not GlobalData members)
// e.g. controller->..., axes[0]->..., multiAxes[0]->..., networkNodes[0]->...
InitializeRapidCodeObjects();
data->counter = 0;
data->average = 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 InitializeRTTaskObjects(std::unique_ptr<RTTaskManager>& manager)
{
std::cout << "Running initialization task..." << std::endl;
RTTaskCreationParameters initParams("Initialize");
initParams.Repeats = RTTaskCreationParameters::RepeatNone;
std::unique_ptr<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 randomly increments the average global, and moves the axis to it. It is important to check that the axis pointer exists before attempting to use it, otherwise it could cause a segmentation fault inside the task manager.

// This task randomly moves the axis back and forth
LIBRARY_EXPORT void RandomWalk(GlobalData* data)
{
int random = std::rand() % 2;
double step = random ? 0.05 : -0.025; // Randomly increment or decrement the average
data->average += step;
data->counter += 1;
// Make sure the axis object is not null before using it
if (axes[0] != nullptr)
{
axes[0]->MoveSCurve(data->average);
}
}

Here is the application code that puts it all together, submitting the Initialize task first and then submitting the RandomWalk task.

// Configure the axis
Axis *axis = controller->AxisGet(0);
axis->PositionSet(0);
axis->AmpEnableSet(true);
// 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::InitializeRTTaskObjects(manager);
// 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 RandomWalk function is defined in the RTTaskFunctions library.
// It moves the axis back and forth based on std::rand().
std::cout << "Submitting task..." << std::endl;
RTTaskCreationParameters params("RandomWalk");
params.Repeats = RTTaskCreationParameters::RepeatForever;
params.Period = 5;
std::unique_ptr<RTTask> task(manager->TaskSubmit(params));
// Wait for the task to run for a bit
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// Get the counter global tag to see if the task ran correctly
std::cout << "Getting counter global tag..." << std::endl;
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;
}
// The average global tag is the position the axis should be at if the RandomWalk task is working correctly
FirmwareValue average = manager->GlobalValueGet("average");
std::cout << "Average: " << average.Double << std::endl;
// Print the final axis position
std::cout << "Axis position: " << axis->CommandPositionGet() << std::endl;
task->Stop();
axis->MotionDoneWait(TIMEOUT_MS); // The axis might still be moving from the task, so wait for it to finish
axis->AmpEnableSet(false);

🔹 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