Learn how to create a Real-Time Task (RTTasks).
PREMIUM FEATURE
- Recommended
- This guide will help you integrate RTTasks into your application. If you're new to RTTasks or want to better understand how they work, refer to the following resources:
Real-Time Tasks Function Library
This project, located in the examples/ folder under your RMP install directory (e.g., C:/RSI/X.X.X/examples/ or /rsi/examples/), is a CMake template for building a task functions library for use with Real-Time Tasks (RTTasks).
RTTasks offer a powerful API for responding to input with strict timing guarantees. This guide describes an example project that will move an axis in response to an analog input.
πΉ Understand the architecture
Before writing code, make sure you understand how the pieces fit together:
- 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.
- 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.
Keeping these boundaries clear prevents common mistakes and confusion. The RTTaskFunctions project used in this guide defines the RTTaskFunctions library and the GlobalData. It will not be runnable on its own. You will need to start the RTTaskManager separately and submit the tasks to it. You can start the RTTaskManager through the RealTimeTasks API, the rsiconfig utility, or the command line. For more information on using the RealTimeTasks API refer to the Real-Time Tasks concept page in the documentation.
β οΈ Important Note
These samples focus on demonstrating API usage. They may not include the complete safety logic required for your machine. When working with real hardware always wire an external emergency stop, verify limits, and start with conservative motion constants.
β
Prerequisites
Before generating or building Real-Time Tasks, make sure your development environment meets these requirements:
- Windows hosts:
- Visual Studio 2022 with the βDesktop development with C++β workload and CMake integration component (βC++ CMake tools for Windowsβ).
- Install either the INtime Runtime (CDEV) or the INtime SDK if you plan to deploy or run RTTasks on an INtime node. Without either you can still build and test RTTasks on Windows, but it will not have real-time performance.
- Linux hosts:
- A recent GCC or Clang toolchain.
- The distributionβs standard build tools (for example, install build-essential on Debian/Ubuntu).
- CMake 3.15 or newer
For environment setup and Visual Studio generator scripts, see the documentation for the C++ sample applications. For conceptual background and runtime behavior, refer to the Real-Time Tasks concept page in our docs.
π Quickstart
To get up and running quickly, follow these steps:
- Copy the RTTaskFunctions (found in the examples folder) into your project
- Add any required global variables to src/rttaskglobals.h
- Define your task functions in src/rttaskfunctions.cpp, using Increment as a template
- Build the shared library using CMake
- In your application, create an RTTaskManager and use TaskSubmit to launch your tasks from the shared library
For a more detailed walkthrough, continue below.
βοΈ Set up your project
The examples folder in your RMP installation includes a CMake project called RTTaskFunctions. This serves as a starting point for building a shared library containing RTTask functions. Copy this folder into your project directory and rename it appropriately.
Note: If you plan to use Visual Studio to manage your project, then run the generator script for your Visual Studio version (located in examples/VisualStudioGeneratorScripts/) before copying the project folder. This will generate a Visual Studio solution inside examples/RTTaskFunctions/VisualStudio.
Here is an overview of the key files:
π Layout
RTTaskFunctions/
βββ CMakeLists.txt # CMake setup for building the shared library
βββ src/
βββ rttaskglobals.h # Defines Global variables
βββ rttaskfunctions.cpp # Task function implementations
How it works
The template project takes all the source files (.h, .cpp) in the src/ directory and compiles them into a shared library for the target platform (Windows/INtime or Linux). It automatically includes the RMP headers, links the RMP libraries, and applies necessary configuration for use with RTTasks. The output is a .dll/rsl/.so file that will be loaded by the RTTaskManager. There is no standalone executable produced by this project, so you must launch a manager instance separately.
By default, the resulting library is named RTTaskFunctions and is output to the default RMP install directory (e.g., /rsi or C:/RSI/X.X.X). If these paths or names are modified, you must specify them explicitly when submitting a task from your application. Otherwise, they will be discovered automatically.
If you want to change any of the default behavior or are interested in learning more about how it works, then look at the CMakeLists.txt located in the root of the project folder.
π Create an RTTask functions library
In this guide, youβll build a simple application that moves an axis based on the value of an analog input. It will use one global variable and two RTTasks. The first task, CalculateTarget, reads the analog input and calculates a target position, storing it in the global variable targetPosition. The second task, FollowTarget, moves the axis to the specified target.
Create a global variable
Open src/rttaskglobals.h. This file is where the global variables are defined and registered before being exposed to the host through the RealTimeTasks API.
To add a new global of type double called targetPosition:
- Add RSI_GLOBAL(double, targetPosition) to the GlobalData struct
- Add REGISTER_GLOBAL(targetPosition) to the GlobalMetaDataMap
The result should be:
struct GlobalData
{
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);
RSI_GLOBAL(double, targetPosition);
RSI_GLOBAL(double, potentialTarget);
};
inline constexpr GlobalMetadataMap<RSI::RapidCode::RealTimeTasks::GlobalMaxSize> GlobalMetadata(
{
REGISTER_GLOBAL(counter),
REGISTER_GLOBAL(startingSample),
REGISTER_GLOBAL(sensorValue),
REGISTER_GLOBAL(targetPosition),
REGISTER_GLOBAL(potentialTarget),
});
Create a task function
Open src/rttaskfunctions.cpp. This is the file where the task functions are defined. A task function must follow this template:
RSI_TASK(FunctionName)
{
...
}
The example below splits execution into two tasks: CalculateTarget and FollowTarget. This is to demonstrate using shared memory for inter-task communication. Real-world integrations would use multiple RTTasks when they need different priorities, periods, or life cycles.
We will add a new task function called CalculateTarget that will read the analog input, scale it to a value between 0 and 1, and store it in the global created in the previous step.
RSI_TASK(CalculateTarget)
{
constexpr int NODE_INDEX = 0;
constexpr int ANALOG_INDEX = 0;
constexpr int ANALOG_MAX = 65536;
int32_t analogInVal = RTNetworkNodeGet(NODE_INDEX)->AnalogInGet(ANALOG_INDEX);
double newTarget = double(analogInVal) / ANALOG_MAX;
data->potentialTarget = scaledVal;
}
Then we will add another task function called FollowTarget that will move the axis towards the target position.
For tasks involving RapidCode objects, rttaskglobals.h includes the functions RTMotionControllerGet(), RTAxisGet(), RTMultiAxisGet(), and RTNetworkNodeGet() for your convenience. For more information, see the Concepts > General > Real-Time Tasks page in our docs.
RSI_TASK(FollowTarget)
{
constexpr int AXIS_INDEX = 0;
constexpr double TOLERANCE = 0.02;
if (std::abs(data->potentialTarget - data->targetPosition) > TOLERANCE)
{
double newTarget = data->potentialTarget;
data->targetPosition = newTarget;
RTAxisGet(AXIS_INDEX)->MoveSCurve(data->targetPosition);
}
}
Once the task functions have been added, build the project to produce a new library.
The runtime of all your RTTasks put together is subject to timing requirements based on your sample rateβbe wary of very long-running tasks. See the Concepts > General > Real-Time Tasks page in our docs for more details on timing requirements.
Optional: Build to a custom library name and directory
Using the default library name (RTTaskFunctions) and directory (RMP's installation folder) will work fine for many applications. However, in the case that you would like more control (e.g., to avoid library collisions when working on multiple RTTask projects) you may override both.
As an example, we will create a library with the name CustomRTTaskFunctions and the directory ./RTTaskFunctions/lib (relative to your project folder).
First, change the CMakeLists.txt in the RTTaskFunctions folder. Replace every instance of RTTaskFunctions with your custom library name. Then, set OUTPUT_DIR using your custom directory.
# ./RTTaskFunctions/CMakeLists.txt
project(CustomRTTaskFunctions)
# ...
set(OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib)
# ...
add_library(CustomRTTaskFunctions SHARED ${SOURCE_FILES} ${HEADER_FILES})
target_include_directories(CustomRTTaskFunctions PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(CustomRTTaskFunctions PRIVATE RSI::RapidCode)
set_target_properties(CustomRTTaskFunctions PROPERTIES
# ...
)
if (INTIME_BUILD_ENABLED)
configure_intime_target(CustomRTTaskFunctions)
endif()
Then, make sure that the CMakeLists.txt in the root of your project folder includes the RTTaskFunctions subdirectory.
add_subdirectory(RTTaskFunctions)
This will build our function library CustomRTTaskFunctions to the folder ./RTTaskFunctions/lib. To properly use this library, see the optional steps below.
π Launch your tasks
In your user application, written in C++, C#, or Python with RapidCode, or in any gRPC-capable language using RapidCodeRemote, create an RTTaskManager instance after configuring your RapidCode objects. Submit your tasks to the manager, which will load the shared library built in the previous steps. You can also start the manager from utilities like rsiconfig if you prefer to manage tasks outside of application code.
Note: Make sure to include the RTTask header, rttask.h.
#include "rttask.h"
RMP_INSTALL_PATH);
"NodeA");
static RTTaskManager Create(const RTTaskManagerCreationParameters ¶meters)
Create a new RTTaskManager instance.
Interface for managing real-time tasks firmware. See Real-Time Tasks for more information.
The RealTimeTasks namespace.
char RTTaskDirectory[DirectoryLengthMaximum]
Path to the directory containing the rttaskmanager executable.
RTTaskManagerCreationParameters specifies all the information required to create and configure an RTT...
int32_t CpuCore
[Linux] CPU core to which the manager should be pinned (-1 for no pinning).
PlatformType Platform
Platform on which the manager firmware will run.
static constexpr int32_t DirectoryLengthMaximum
Maximum length of the directory path.
char NodeName[NameLengthMaximum]
[INtime] Name of the node on which the manager will run. By default, this is set to (and verified as)...
static constexpr int32_t NameLengthMaximum
Maximum length of name fields (node name, user label).
Next, run the Initialize task one time, to get access to RapidCode objects and initialize your global variables.
constexpr int timeoutMs = 5000;
int64_t ExecutionCountAbsoluteWait(int64_t count=ExecutionCountDefault, int32_t timeoutMs=ExecutionCountWaitTimeoutMillisecondsDefault)
Wait for the task to reach a specific execution count.
Interface for controlling and monitoring a single real-time task. See RTTaskManager::TaskSubmit and R...
RTTask TaskSubmit(const RTTaskCreationParameters ¶meters)
Submit a new task to the manager using creation parameters.
RTTaskCreationParameters specifies all the information required to create and configure a real-time t...
static constexpr int32_t RepeatNone
Special value to indicate the task should not repeat.
Then submit the tasks created in the previous section.
calcParams.Period = 10;
followParams.Period = 10;
static constexpr int32_t RepeatForever
Special value to indicate the task should repeat forever.
Note: the fields RepeatForever and RepeatNone are static constant expressions provided for convenience and readability. They can not be written to on instances of RTTaskCreationParameters. See the our API reference for more static fields.
While your tasks are running, you can use RTTask::StatusGet and RTTaskManager::GlobalValueGet to monitor your tasks.
std::cout <<
"Target position: " << value.
Double << std::endl;
RSI::RapidCode::FirmwareValue GlobalValueGet(int32_t offset)
Read a GlobalTag by its offset. (internal use).
Union representing a generic RMP firmware value with multiple data types, stored in 64-bits.
double Double
Double precision (64-bit) floating-point.
At the end of your program, stop the tasks and manager.
void Stop()
Stop the task from executing.
void Shutdown()
Shutdown the RTTaskManager firmware.
Before launching your RTTasks executable, it is important to run and configure RMP. This can be done via your RapidCode application, rsiconfig, or some other tool. Use the steps below as a checklist to ensure your RTTasks application is ready to run:
- Required:
- RMP running
- RapidCode object counts set
- Suggested:
- Network started/phantom axes created
- User units set
- Limits & actions configured
- Amplifier enabled
- Other RapidCode configuration
Optional: Using a custom function library
If you built your library to a custom name and directory (as shown in the optional section above), you must provide this information to RTTasks API objects.
You should pass in the library name and directory when getting/setting globals, constructing RTTaskCreationParameters objects, and any other cases indicated by the API reference.
You should not pass in the library directory when configuring RTTaskManagerCreationParameters. The RTTaskDirectory field is the directory that contains the rttaskmanager.exe executable. Typically, this is the RMP installation folder, so this field can be left unmodified.
Once you have implemented the sample code above, make the modifications given here:
const char* libraryDirectory = "/home/<USER>/.../RTTasksProject/RTTasksFunctions/lib";
const char* libraryName = "CustomRTTaskFunctions";
RMP_INSTALL_PATH);
β 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:
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.