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:

  1. In Solution Explorer, right-click the project name and then click Properties.
  2. In (Project name) Property Pages dialog box, in the left pane, expand the C/C++ list and click General.
  3. In the right area, in the Additional Include Directories box, enter "$(RTX64SDKDir4)include;$(KINGSTARSDKDir4)include;%(AdditionalIncludeDirectories)" without space.
  4. In the left pane, expand the Linker list and click General.
  5. In the right area, in the Additional Library Directories box, enter "$(RTX64SDKDir4)lib\$(Rtx64Platform);$(KINGSTARSDKDir4)lib\amd64;%(AdditionalLibraryDirectories)" without space.
  6. In the left pane, in the Linker list, click Input.
  7. In the right area, in the Additional Dependencies box, enter the following strings depending on the build target:
  8. Click Apply and then click OK.
  9. In the header file of the project (the name is ProjectName.h), under #endif // UNDER_RTSS, enter the following code:
  10. #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.

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;

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.

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: