User Interpolation Sample
This sample demonstrates how to use a direct control mode of the KINGSTAR Subsystem. The direct control mode allows the application to use its own interpolation and send cyclic commands to the drives through the Subsystem. The Subsystem accepts position, velocity, or torque direct commands. However, not all drives support torque commands. This sample uses position commands. Velocity and torque commands work in the same way.
The sample also introduces the KINGSTAR Fieldbus functions that handle EtherCAT communications, configure EtherCAT slaves and KINGSTAR Subsystem, and others. It also introduces the KINGSTAR Motion functions that configures axes and sets an axis' position. It does not include move functions. If you want to use move functions, you need to use one of the master interpolation modes.
Compile and run the sample
The file is located at C:\Users\Public\Public Documents\IntervalZero\KINGSTAR SDK\<Version Number>\Samples\UserInterpolationSample
. Open UserInterpolationSample.sln
and compile it.
NOTE: File Explorer has two paths: the hierarchy path and full path. The hierarchy path is shown in the address bar. The full path is shown in <File Name> Properties—if you right-click UserInterpolationSample.sln
and click Properties, you'll see the Location is C:\Users\Public\Documents\IntervalZero\KINGSTAR SDK\<Version Number>\Samples\UserInterpolationSample
, which is the full path. Notice that in the hierarchy path it is Public Documents
. If you are using a non-English Windows system, and you want to copy and paste the path to the address bar to find the sample quickly, you must use the full path. If you want to browse to the sample folder through clicks, use the hierarchy path. For English Windows, folder redirection is done automatically. Even if you paste the hierarchy path, File Explorer can lead you to the sample.
![]() |
![]() |
The figure below shows the output of the sample:
Set project properties in Visual Studio
The sample is developed using C in Visual Studio 2019. When developing your own applications, you can choose your own development environment, as long as the application is 64-bit. The use of 64-bit is required to control the real-time Subsystem.
When you create your own application, you need to modify the following properties in Visual Studio:
- In Solution Explorer, right-click the project name and then click Properties.
- In (Project name) Property Pages dialog box, in the left pane, expand the C/C++ list and click General.
- In the right area, in the Additional Include Directories box, enter "
$(RTX64SDKDir4)include;$(KINGSTARSDKDir4)include;%(AdditionalIncludeDirectories)
" without space. - In the left pane, expand the Linker list and click General.
- In the right area, in the Additional Library Directories box, enter "
$(RTX64SDKDir4)lib\$(Rtx64Platform);$(KINGSTARSDKDir4)lib\amd64;%(AdditionalLibraryDirectories)
" without space. - In the left pane, in the Linker list, click Input.
- In the right area, in the Additional Dependencies box, enter the following strings depending on the build target:
- RtssDebug and RtssRelease builds: enter "
startupCRT.lib;libcmtd.lib;libcpmtd.lib;libucrtd.lib;libvcruntimed.lib;rtx_rtss.lib;ksapi_rtss.lib
" without space. - Debug and Release builds: enter "
rtapi.lib;ksapi.lib
" without space. - Click Apply and then click OK.
- In the header file of the project (the name is
ProjectName.h
), under #endif // UNDER_RTSS, enter the following code:
#include <ksapi.h>
#include <ksmotion.h>
Grouped functions
To improve the readability of the code, the functions are grouped using #pragma
in the sample. You can know the use of the function by its #pragma
code.
Run the cyclical task
We define a function CyclicTask. This function will be called every cycle.
int CyclicTask(PVOID Context) {
int TargetAxis = 0;
// Moving at a fixed velocity of 360 degrees per second
double Position = 0;
GetAxisPosition(TargetAxis, mcSetValue, &Position);
BOOL HomeAbsSwitch;
BOOL LimitSwitchPos;
BOOL LimitSwitchNeg;
BOOL Simulation;
BOOL CommunicationReady;
BOOL ReadyForPowerOn;
BOOL PowerOn;
BOOL IsHomed;
BOOL AxisWarning;
GetAxisInfo(TargetAxis, &HomeAbsSwitch, &LimitSwitchPos, &LimitSwitchNeg, &Simulation, &CommunicationReady, &ReadyForPowerOn, &PowerOn, &IsHomed, &AxisWarning);
if (PowerOn == TRUE) {
Position += (double)360 / (double)1000;
SetAxisPosition(TargetAxis, Position);
}
return 0;
}
We declare two variables: TargetAxis
and Position
.
TargetAxis
is the index of an axis.Position
is the position of an axis.
We use GetAxisPosition to get the current position of an axis. Next, we use GetAxisInfo to make sure the axis is powered on, and then use SetAxisPosition to set the target position of an axis if the axis is powered on.
Configure the KINGSTAR Subsystem
Before you start the KINGSTAR Subsystem, you must first initialize the process and variables. If an unexpected error occurs, the call will return the error code from the KsError type. No other function is available until the Subsystem initializes successfully. Any previous settings are overwritten.
Before we initialize the process and variables, we declare the following variables. These variables are used across int _tmain.
SlaveStatus axis = { 0 };
int axisResolution = 10000;
McPidSettings myPid = { 1, 0, 0, 0, 1, 0.003, 0.003, 0, 0.2, 0.1, FALSE, FALSE, 0, 5000 };
McProfileSettings Motion = { 3, 3600, 3600, 36000, 36000, 3600000, 0 };
KsCommandStatus Command = { 0 };
KsError Code = errNoError;
SlaveStatus axis = { 0 }
– SlaveStatus is a structure that contains the details of an EtherCAT slave. We useaxis
to get our slave's information.int axisResolution = 10000
–axisResolution
is the resolution of an axis (servo drive). The initial value is set to 10000. The actual value is based on the axis you use.McPidSettings myPid
– McPidSettings is a structure that contains the PID settings of KINGSTAR Motion. We usemyPid
to set initial PID values.McProfileSettings Motion
– McProfileSettings is a structure that contains the settings relevant to motion, such as acceleration, deceleration, and jerk. We useMotion
to set initial motion values.KsCommandStatus Command = { 0 }
– KsCommandStatus is a structure that contains the states of a function. You can know whether the function is running, completes its work, returns any error, and what the error is. We useCommand
to get the state of a function.KsError Code = errNoError
– KsError is an enum type that contains all error codes of KINGSTAR.errNoError
means the function succeeded.
The initialization process is contained in #pragma region Initialization
. Notice that we use Code
to check whether a function runs successfully. It receives the return value of a function. If Code
is not errNoError
, the program will jump to the End
block and run the code in it.
Prepare to link to the KINGSTAR Subsystem
First, we call Create, which prepares to link your application to the KINGSTAR Subsystem. Before you start anything, you must call Create first.
Code = Create(0, 0);
if (Code != errNoError) {
RtPrintf("Failed to create: 0x%x\n", Code);
goto End;
}
Set the EtherCAT cycle time
SetCycleTime sets the EtherCAT cycle time, in seconds. The unit of time is second. In the sample, cycle1000
means 1 millisecond. If you want to type a number, it is SetCycleTime(0.001). We use variables to represent numbers. You can find the following definitions in ksapi.h
, which is located at C:\Program Files\IntervalZero\KINGSTAR SDK\4.0\Include
.
Variables | Number |
---|---|
cycle100 | 0.0001 |
cycle125 | 0.000125 |
cycle250 | 0.000250 |
cycle500 | 0.0005 |
cycle1000 | 0.001 |
To use the cycle time less than 1 millisecond, High Speed Timer Package is required. Note that not all axes support fast cycle times. If an unsupported cycle time is selected, the update time for each axis is automatically and independently extended. To use fast cycle times, ensure that the network card on the computer is capable. Only the network cards with low latencies are able to support fast cycles.
Code = SetCycleTime(cycle1000);
if (Code != errNoError) {
RtPrintf("Failed to set cycle time: 0x%x\n", Code);
Destroy();
goto End;
}
Disable the logs on the RTX64 Server Console
EnableServerLog enables or disables the real-time messages on the RTX64 Server Console. If you disable them, the console will display only KINGSTAR messages. We choose to disable it.
Code = EnableServerLog(FALSE);
if (Code != errNoError) {
RtPrintf("Failed to set server log: 0x%x\n", Code);
Destroy();
goto End;
}
Set an access mode
SetAxisAccessMode sets the data transfer mode for EtherCAT drives. The access mode determines the control mode your axes can use. An access mode can be selected from the KsAccessMode enum type. By default, the access mode is accessVelPos
. We use accessPosVel
.
Code = SetAxisAccessMode(accessPosVel);
if (Code != errNoError) {
RtPrintf("Failed to set access mode: 0x%x\n", Code);
Destroy();
goto End;
}
Enable the digital input of an axis
EnableAxisInput enables or disables the access to the digital inputs of an axis. The first three bits of the inputs are Negative Overtravel, Positive Overtravel, and Home Sensor. If you enable the inputs, you can access the Overtravel bits.
Code = EnableAxisInput(TRUE);
if (Code != errNoError) {
RtPrintf("Failed to set servo inputs: 0x%x\n", Code);
Destroy();
goto End;
}
Enable Hot Connect and set the number of simulated axes
EnableHotConnect allows new hardware to be added while the EtherCAT network is running. SetConfiguredAxesCount sets the number of simulated axes. In this sample, we use one simulated axis.
Code = EnableHotConnect(TRUE);
Code = SetConfiguredAxesCount(1);
Complete the Subsystem configuration
KINGSTAR provides many settings to configure the Subsystem, only a few are used in this sample. Other settings can be found in the online or offline help > KINGSTAR RT and Win32 APIs > KINGSTAR Fieldbus > Functions > Axis variable.
Most settings are optional. However, two configuration settings are required: EtherCAT cycle time and access mode. If the default value for either setting does not suit your needs, change the value(s) to something more appropriate.
In the sample, we use the following code to show that the Subsystem configuration is done.
RtPrintf("Subsystem configured\n");
Output:
Start the KINGSTAR Subsystem
We use Start to start the KINGSTAR Subsystem and EtherCAT network. In the event that the Subsystem is unable to be started, causing the program to wait forever for it to complete, we use WaitForCommand to set a 30-second timeout for Start. If the KINGSTAR Subsystem can't be started, we use Destroy to close the link to the Subsystem and terminate it.
Command = WaitForCommand(30, TRUE, Start());
if (!Command.Done) {
RtPrintf("Failed to start EtherCAT: 0x%x\n", Command.ErrorId);
Destroy();
goto End;
}
RtPrintf("Subsystem Started\n");
Output:
After the Subsystem is started, we need to retrieve its current state. To do this, we use the SubsystemStatus structure and the GetStatus function.
SubsystemStatus Subsystem = { ecatOffline, ecatOffline,
0, 0, 0, {ecatOffline}, {ecatOffline}, {axisOffline} };
SubsystemStatus contains the details of an EtherCAT network. We can know the EtherCAT state of the master and slaves, the total number of EtherCAT slaves, the number of axes and I/O modules on the EtherCAT network, and others. We use the variable Subsystem
to get the information of the EtherCAT network. We assign initial values to Subsystem
, so we can tell whether Subsystem
's content is changed when data is passed to it.
Code = GetStatus(&Subsystem, NULL);
if (Code != errNoError) {
RtPrintf("Failed to get status: 0x%x\n", Code);
goto Exit;
}
RtPrintf("Subsystem status\n %d Slaves\n %d I/Os\n %d Axes\n\n",
Subsystem.SlaveCount, Subsystem.IOCount, Subsystem.AxesCount);
GetStatus gets the state of the EtherCAT network we created. The state is passed to Subsystem
. We fill NULL
for the second parameter because we don't use it. Again, we use Code
to check whether GetStatus runs successfully. If it does, the Subsystem state, which contains the number of EtherCAT slaves, axes, I/O modules, will be displayed on the RTX64 Server Console.
Output:
Axis configuration
We need to configure the settings of an axis before we move it. Before we configure anything, we declare the following variable:
int TargetAxis = 0;
TargetAxis
is the index of an axis. This variable is used across the #pragma region AxisConfiguration
block.
Get the state of an axis
We use GetAxisByIndex to get the state of an axis. We set TargetAxis
to zero. It means we want to get the information of the axis whose index is zero.
Next, we call GetAxisByIndex. axis
is a SlaveStatus variable. axisResolution
is a variable we declare at the beginning of the int _tmain function. Its initial value is 10000. The last two parameters are set to NULL
because we don't use them.
Code = GetAxisByIndex(TargetAxis, &axis, &axisResolution, NULL, NULL);
if (Code != errNoError) {
RtPrintf("Failed to get axis status: 0x%x\n", Code);
goto Exit;
}
RtPrintf("Axis status\n Name: %s\n Vendor: 0x%x\n Product: 0x%x\n
Revision: %d\n Alias: %d\n Explicit ID: %d\n\n", axis.Name,
axis.VendorId, axis.ProductCode, axis.RevisionNumber,
axis.AliasAddress, axis.ExplicitId);
We use Code
to check whether the function runs successfully, as usual. If it does, the information of Axis Zero will be displayed in the RTX64 Console.
Output:
Convert the axis unit
We use SetAxisCountsPerUnit to convert the position unit of an axis into degree. It is easier for us to know how far an axis travels. You can use this function to set the unit you want.
Code = SetAxisCountsPerUnit(TargetAxis, axisResolution, 360, FALSE);
if (Code != errNoError) {
RtPrintf("Failed to set axis unit: 0x%x\n", Code);
goto Exit;
}
Enable the unit conversion
To apply the unit we set in SetAxisCountsPerUnit, we need to use EnableAxisUnitConversion to enable the conversion.
Code = EnableAxisUnitConversion(TargetAxis, TRUE);
if (Code != errNoError) {
RtPrintf("Failed to enable axis unit: 0x%x\n", Code);
goto Exit;
}
Set a control mode for an axis
We use SetAxisControlMode to set the control mode for our axis. The Direct Position mode is used in this sample, so we set the control mode to modeDirectPos
. To prevent the function from running too long, we use WaitForCommand to set a 1-second timeout for SetAxisControlMode.
Command = WaitForCommand(1, TRUE, SetAxisControlMode(TargetAxis, modeDirectPos));
if (!Command.Done) {
RtPrintf("Failed to set axis control mode: 0x%x\n", Command.ErrorId);
goto Exit;
}
Update the PID and motion parameters
PID and motion parameters must be updated after the unit is converted. We use SetAxisVelocityPid to update the PID settings in the velocity mode – you can use SetAxisTorquePid if your axis is in the torque mode. SetAxisMotionProfile configures the motion settings of an axis. The instance Motion
, declared at the beginning of int _tmain, is applied here.
Code = SetAxisVelocityPid(TargetAxis, myPid);
if (Code != errNoError) {
RtPrintf("Failed to set axis PID: 0x%x\n", Code);
goto Exit;
}
Code = SetAxisMotionProfile(TargetAxis, profileUnitPerSecond, Motion);
if (Code != errNoError) {
RtPrintf("Failed to set axis motion profile: 0x%x\n", Code);
goto Exit;
}
Complete the axis configuration
KINGSTAR provides many settings to configure an axis. This sample only uses a few of them. You can find information on the other settings in the online or offline help under > KINGSTAR RT and Win32 APIs > KINGSTAR Motion > Functions > Axis configuration.
Most settings are optional. Control mode, however, is required.
In the sample, we use the following code to show that the axis is configured.
RtPrintf("Axis configured\n");
Output:
Check the operating state of an axis every cycle
We use RegisterCallback to register CyclicTask so KINGSTAR Subsystem will call it every cycle when new data comes in. CyclicTask moves the axis at a constant velocity. For more information about CyclicTask, see Run the cyclical task earlier in this guide.
Code = RegisterCallback(&CyclicTask, NULL);
Enable an axis
After the axis configuration is completed, we can enable an axis and move it.
Reset an alarm
Before we enable an axis, we use ResetAxis to reset the alarm of an axis in case it has any error. To prevent the function from running too long, we use WaitForCommand to set a 5-second timeout for ResetAxis.
Command = WaitForCommand(5, TRUE, ResetAxis(TargetAxis));
if (!Command.Done) {
RtPrintf("Failed to reset the axis: 0x%x\n", Command.ErrorId);
goto Exit;
}
Enable an axis
We use PowerAxis to enable an axis. In the function, the first parameter is the index of an axis, the second controls the power of an axis, and the third and the fourth controls the direction an axis moves. To prevent the function from running too long, we use WaitForCommand to set a 1-second timeout for PowerAxis.
Command = WaitForCommand(1, FALSE, PowerAxis(TargetAxis, TRUE, TRUE, TRUE));
if (!Command.Done) {
RtPrintf("Failed to enable the axis: 0x%x\n", Command.ErrorId);
goto Exit;
}
RtPrintf("Axis enabled\n");
After the axis is enabled, we let the axis move for a while.
Sleep(30000);
Disable an axis
After the axis moves for a while, we use PowerAxis to disable it.
Command = WaitForCommand(1, FALSE, PowerAxis(TargetAxis, FALSE, TRUE, TRUE));
if (!Command.Done) {
RtPrintf("Failed to disable the axis: 0x%x\n", Command.ErrorId);
goto Exit;
}
RtPrintf("Axis disabled\n");
Output:
Stop the KINGSTAR Subsystem
We use two functions to stop the KINGSTAR Subsystem: Stop and Destroy.
- Stop stops the EtherCAT network and the KINGSTAR Subsystem.
- Destroy closes the link to the KINGSTAR Subsystem and terminates the Subsystem.
Since the Subsystem needs some time to stop, we use WaitForCommand to give it 10 seconds to stop. If the Subsystem does not stop before 10 seconds is up, the next command, Destroy, will be executed.
Exit:
//Stop EtherCAT
Command = WaitForCommand(10, FALSE, Stop());
if (!Command.Done) {
RtPrintf("Failed to stop EtherCAT: %d\n", Command.ErrorId);
}
else {
RtPrintf("EtherCAT stopped\n");
}
//Stop the Subsystem.
//Never stop the Subsystem if another application or the PLC is running. It would cause a BSOD.
Code = Destroy();
if (Code != errNoError) {
RtPrintf("Destroy failed: %d\n", Code);
}
Stop and Destroy are contained in the Exit
block, which is run when the Subsystem is not started successfully. When you use these two functions, it's not necessary to contain them in any block. You can use them depending on your needs.
The End
block is executed when a function doesn't run successfully.
End:
RtPrintf("User Interpolation Sample ended\n");
Output: