Digital group cam switch
Group cam switches work similar to cam switches. The differences are that they are triggered by group motion and the distance the group travels. To set digital group cam switches, you need to define how switches are triggered and select the outputs to be controlled by switches. In this section, you'll learn how to use SetGroupPathCamSwitch to set digital group cam switches and use them to control digital outputs' value.
Process of using digital cam switch
The following process shows how digital group cam switches are set:
McCamSwitch, McTrack, and McOutput -> MoveLinear/MoveCircular/MoveHelical ->SetGroupPathCamSwitch -> ReadOutputBit
Structures
McCamSwitch: defines a digital cam switch.
McTrack: time compensation and hysteresis to be applied to a switch on a track.
McOutput: selects which digital output will be controlled.
Functions
MoveLinearAbsolute: moves an axis group to an absolute position. Digital group cam switches are attached to this function.
SetGroupPathCamSwitch: uses an axis group's travel distance to control a switch that triggers a digital output. When a group travels certain distance, a switch is turned on or off. A switch can be controlled by a forward and backward movement of a group.
GetCommandStatus: gets the state of a command.
ReadOutputBit: reads a bit from a digital output of a real or simulated I/O module.
Code
We divide the digital group cam switch code into steps to explain. In the last part, we combine all the code for you to see the completed one.
In DigitalSwitch.cpp
, add the following code:
- Use McCamSwitch, McTrack, and McOutput to configure the settings of digital group cam switches. There is no priority among them. In this guide, we configure switches first, in which you set tracks to contain switches, and use axis' distance or duration to determine where and how long a switch is on. Each track can have up to eight switches. The distance are set by FirstOnPosition and LastOnPosition. The duration is set by FirstOnPosition and Duration.
- In this guide, there are three switches in total. Two switches for Track Zero and one switch for Track One. Because FirstOnPosition and LastOnPosition are distance, the switches are triggered by how far the axes move, not the positions they reach. Since the distance of the second switch is the shortest, this switch will be triggered first, and then the first, finally the third. The distance needs to be calculated by unit vector. We'll talk about this in the later steps.
- Add time compensation and hysteresis.
- Select which digital output will be controlled by a corresponding track. The first output is controlled by the first track (Track Zero), and the second is controlled by the second track (Track One).
- Next, we need to decide how far the group goes. In this guide, we use MoveLinearAbsolute to move the group, so we set target positions. If you use MoveLinearRelative or MoveLinearAdditive, you set distance.
- To trigger the switches, we send MoveLinearAbsolute. Don't use WaitForCommand because we want to check whether the switches are turned on and off as we expected while the motion command is running. If you use WaitForCommand, the program will wait until the command is completed. Moreover, you must create an instance of KsCommandStatus for MoveLinearAbsolute. This instance will be used in SetGroupPathCamSwitch in the next step.
- Use SetGroupPathCamSwitch to set digital group cam switches. Because SetGroupPathCamSwitch requires you to give a state of a motion command (KsCommandStatus instance), we need to send a motion command first, and then send SetGroupPathCamSwitch, in which we apply the state of the motion command, which is
absolute
. This attaches the switches to the command. - To know where the switches can be triggered, we need to do a few calculations. First, we define four variables:
axisDistance
: the distance an axis has traveled.startPosition
: the position an axis begins to move. We set it to zero. You can set it to other values if you want.currentPosition
: the current position of an axis.Square
: the square of each axis' target position (EndPosition
).- Next, we add up the square of each axis' target positon and calculate the square root of it. The square root is used to calculate each axis' unit vector.
- X: Axis X's target position
- Y: Axis Y's target position
- Z: Axis Z's target position
- Switch-on position = unit vector x FirstOnPosition
- Switch-off position = unit vector x LastOnPosition
- To detect whether the switches are triggered, we use
vector
to create a range-basedfor
loop, and declare the variableCount
. - The
for
loop: since the switches will be triggered in the order 2->1->3, we need to specify the range for the loop instead of iterating the sequence of numbers. The switches are defined in theSwitches
array. Count
: it is a flag used to check whether a switch's output has been read and a message has been displayed. If it has, the count value will be incremented. This flag can ensureRtPrintf
is executed only once when it displays the output value of the I/O module controlled by the switch.- We use a
while
loop to detect whether the axes are still moving. Inside the loop we use GetCommandStatus to get the state of MoveLinearAbsolute to know if it is done. We use GetGroupPosition to get the current position of the axes and calculate how far each axis moves. Whenever an axis moves into its own triggering range, a message is displayed andValue
becomes one.Value
then will be reset until the next switch is triggered. After MoveLinearAbsolute is done, thewhile
loop ends. - Since SetGroupPathCamSwitch is bound with
absolute
(the state of MoveLinearAbsolute), itsDone
state is synchronized withabsolute
'sDone
, but on some computers SetGroupPathCamSwitch might be done afterabsolute
. To make sure it is completed, we useSleep(1)
to wait 1 millisecond, and then get the state of SetGroupPathCamSwitch. - Finally, we display MoveLinearAbsolute and SetGroupPathCamSwitch's
Done
state, and the end positions of the axes.
We use the variable Value
to check whether the output value of the I/O module controlled by the switches is TRUE or FALSE. By default, it is FALSE.
VOID GroupCamSwitch(int Group)
{
RtPrintf("Enable group digital cam switches using a group's distance.\n\n");
/*Value is used to check whether a switch has been triggered. By default,
it is FALSE (zero).*/
BOOL Value = FALSE;
ReadOutputBit(2, 0, &Value);
RtPrintf("Read the output bit corresponding to Track Zero: %d\n\n", Value);
//Defines each switch. Each track can have up to eight switches.
//TrackNumber: the index of the track a switch belongs to.
/*FirstOnPosition: the distance between the original starting position and FirstOnPosition
in a path motion command, must be nonnegative.*/
/*LastOnPosition: the distance between the original starting position and LastOnPosition
in a path motion command, must be positive and greater than FirstOnPosition.*/
//AxisDirection: not used, because FirstOnPosition and LastOnPosition are distance.
//CamSwitchMode: the switch mode. 0: Distance, 1: Time.
//Duration: the duration in seconds the switch stays enabled. For time switches.
//The first three switches correspond to Output[0], the last corresponds to Output[1].
McCamSwitch Switches[3] =
{
//TrackNumber FirstOnPosition LastOnPosition AxisDirection CamSwitchMode Duration
{ 0, 2000, 3000, 0, 0, 0 },
{ 0, 1000, 1500, 0, 0, 0 },
{ 1, 4000, 6000, 0, 1, 3 },
};
//On compensation is a delay in seconds before the output is turned on when a switch becomes active.
//Off compensation is a delay in seconds before the outout is turned off when a switch becomes inactive.
//Hysteresis adds a minimum distance in between switches to prevent an output from switching on and off.
McTrack Tracks[2] =
{
//OnCompensation OffCompensation Hysteresis
{ 0, 0, 0 },
{ 0, 0, 0 }
};
//Defines the output for each track.
//Each track controls one output.
//The type selects between axes and I/O modules. TRUE: Axis, FALSE: I/O module.
//The index of the axis or I/O module.
//The offset of the output bit in the output buffer of a device.
McOutput Outputs[2] =
{
//Axis Index Offset
{ FALSE, 2, 0 },
{ FALSE, 3, 0 }
};
We declare EndPositions
to define our target positions, which are (5000, 5500, 6000).
When you set target positions or distance, keep the following in mind:
Target positions or distance affect the unit vector, which affects where a switch is triggered. For example, if target positions are (3000, 2000, 2000), the unit vector is (0.7276, 0.485, 0.485). If target positions are (5000, 5500, 6000), the unit vector is (0.5234, 0.5758, 0.6281).
We need to use unit vector to calculate the range between switch-on and switch-off positions, which will be explained in the later steps.
const int LENGTH = 3; //The length of the Position array.
//First switch on: (1046.8, 1151.6, 1256.2) off: (1570.2, 1727.4, 1884.3)
//Second switch on: (523.4, 575.8, 628.1) off: (785.1, 863.65, 942.16)
//Third switch on: (2093.6, 2303.2, 2512.4) off: (3140.4, 3454.8, 3768.6)
//Unit vector: (0.5234, 0.5758, 0.6281)
double EndPositions[LENGTH] = { 5000, 5500, 6000 };
//Move an axis to trigger a switch.
RtPrintf("Make a linear absolute move.\n\n");
KsCommandStatus absolute = MoveLinearAbsolute(Group, LENGTH,
EndPositions, 500, 5000, 5000, 500000, mcAxisCoordSystem,
mcAborting, mcNone, NULL);
if (absolute.Error)
RtPrintf("MoveLinearAbsolute failed: %d\n", absolute.ErrorId);
KsCommandStatus groupCamSwitch = SetGroupPathCamSwitch(
Group, //Index
absolute, //MotionCommand
3, //SwitchLength
Switches, //Switches
2, //TrackLength
Outputs, //Outputs
Tracks, //Tracks
3 //EnableMask, enabling Track Zero and One
);
/*-----Calculate where the switches will be triggered-----*/
RtPrintf("Calculate where the switches will be triggered.\n\n");
double axisDistance[3] = { 0 };
double startPosition[3] = { 0 };
double currentPosition[3] = { 0 };
double Square[3] = { 0 };
//Calculate the square of each EndPosition.
Square[0] = pow(EndPositions[0], 2);
Square[1] = pow(EndPositions[1], 2);
Square[2] = pow(EndPositions[2], 2);
Unit vector =
After we get the unit vectors, we need to calculate how many units an axis should move to trigger a switch. The equations are as follows:
The range between switch-on and switch-off positions is where a switch is triggered. Every time you change the target positions (or distance), FirstOnPosition
, or LastOnPosition
, you need to recalculate the switch-on and switch-off positions.
After the calculations are done, we display each axis' unit vector, switch-on and switch-off positions to see where the switches will be triggered.
//Calculate the square root of the sum of each EndPositions' square number.
double sqrtRoot = sqrt(Square[0] + Square[1] + Square[2]);
printf("Square root: %f\n\n", sqrtRoot);
//Calculate the unit vector for each axis.
double unitVector[3] = { 0 };
unitVector[0] = EndPositions[0] / sqrtRoot;
unitVector[1] = EndPositions[1] / sqrtRoot;
unitVector[2] = EndPositions[2] / sqrtRoot;
//Display each unit vector.
/*After we get unit vectors, we need to calculate how many units an axis should
move to trigger a switch. The equations are as follows:
unitVector * FirstOnPosition = A
unitVector * LastOnPosition = B
The range between A and B is where a switch is triggered.*/
printf("unitVector[0]: %f\n", unitVector[0]);
printf("unitVector[1]: %f\n", unitVector[1]);
printf("unitVector[2]: %f\n\n", unitVector[2]);
for (int i = 0; i < 3; i++)
{
printf("unitVector[%d] * Switches[%d].FirstOnPosition: %f\n", i, i, unitVector[i] * Switches[i].FirstOnPosition);
printf("unitVector[%d] * Switches[%d].LastOnPosition: %f\n", i, i, unitVector[i] * Switches[i].LastOnPosition);
}
RtPrintf("\n");
/*-----Trigger the switches-----*/
RtPrintf("Trigger the switches.\n\n");
//Use vector to declare a dynamic array that creates a range-based for loop.
vector<int> Loop = { 1, 0, 2 };
/*Count is a flag used to check whether a switch's output has been read and displayed.
If it has, the count value will be incremented. This flag can ensure RtPrintf is executed
only once when it displays the output of the switch.*/
int Count = 0;
while (!absolute.Done)
{
//Get the state of MoveLinearAbsolute and pass it to absolute.
absolute = GetCommandStatus(absolute);
//Get the set position of a group.
GetGroupPosition(Group, mcAxisCoordSystem, mcSetValue, LENGTH, currentPosition);
//Calculate how far three axes move.
axisDistance[0] = currentPosition[0] - startPosition[0];
axisDistance[1] = currentPosition[1] - startPosition[1];
axisDistance[2] = currentPosition[2] - startPosition[2];
/*Use for loop to check whether the switches are triggered.
The switches are triggered in this sequence: 2->1->3.*/
for (auto i : Loop)
{
/*Value is used to check whether a switch has been triggered. If it has,
Value will be changed to one and displayed, and then be reset for the
next switch.*/
Value = 0;
//Check whether the axes trigger their own switches.
if (axisDistance[i] >= (unitVector[i] * Switches[i].FirstOnPosition) &&
axisDistance[i] <= (unitVector[i] * Switches[i].LastOnPosition))
{
/*Use switch-case statement to test whether a switch has been triggered.
If it has, display its output value once.*/
switch (i)
{
//Check whether the second switch is triggered.
case 1:
if (Count >= 1)
continue;
ReadOutputBit(2, 0, &Value);
RtPrintf("Read the output bit corresponding to Track Zero, 2nd switch: %d\n", Value);
Count++;
break;
//Check whether the first switch is triggered.
case 0:
if (Count >= 2)
continue;
ReadOutputBit(2, 0, &Value);
RtPrintf("Read the output bit corresponding to Track Zero, 1st switch: %d\n", Value);
Count++;
break;
//Check whether the third switch is triggered.
case 2:
if (Count >= 3)
continue;
ReadOutputBit(3, 0, &Value);
RtPrintf("Read the output bit corresponding to Track One, 1st switch: %d\n\n", Value);
Count++;
break;
default:
RtPrintf("Out of range.\n\n");
}
}
}
}
//Wait 1 millisecond to make sure everything is done.
//On the computers that have better performance, this command can be skipped.
Sleep(1);
//Get the state of SetGroupPathCamSwitch and pass it to groupCamSwitch.
groupCamSwitch = GetCommandStatus(groupCamSwitch);
//Display the Done state of MoveLinearAbsolute and SetGroupPathCamSwitch.
RtPrintf("MoveLinearAbsolute.Done: %d\n", absolute.Done);
RtPrintf("SetGroupPathCamSwitch.Done: %d\n\n", groupCamSwitch.Done);
//Display end positions of all axes.
RtPrintf("End position:\n");
GetAGroupPosition(Group);
}
Complete code
The complete code should be as follows:
VOID GroupCamSwitch(int Group)
{
RtPrintf("Enable group digital cam switches using a group's distance.\n\n");
/*Value is used to check whether a switch has been triggered. By default,
it is FALSE (zero).*/
BOOL Value = FALSE;
ReadOutputBit(2, 0, &Value);
RtPrintf("Read the output bit corresponding to Track Zero: %d\n\n", Value);
//Defines each switch. Each track can have up to eight switches.
//TrackNumber: the index of the track a switch belongs to.
/*FirstOnPosition: the distance between the original starting position and FirstOnPosition
in a path motion command, must be nonnegative.*/
/*LastOnPosition: the distance between the original starting position and LastOnPosition
in a path motion command, must be positive and greater than FirstOnPosition.*/
//AxisDirection: not used, because FirstOnPosition and LastOnPosition are distance.
//CamSwitchMode: the switch mode. 0: Distance, 1: Time.
//Duration: the duration in seconds the switch stays enabled. For time switches.
//The first three switches correspond to Output[0], the last corresponds to Output[1].
McCamSwitch Switches[3] =
{
//TrackNumber FirstOnPosition LastOnPosition AxisDirection CamSwitchMode Duration
{ 0, 2000, 3000, 0, 0, 0 },
{ 0, 1000, 1500, 0, 0, 0 },
{ 1, 4000, 6000, 0, 1, 3 },
};
//On compensation is a delay in seconds before the output is turned on when a switch becomes active.
//Off compensation is a delay in seconds before the outout is turned off when a switch becomes inactive.
//Hysteresis adds a minimum distance in between switches to prevent an output from switching on and off.
McTrack Tracks[2] =
{
//OnCompensation OffCompensation Hysteresis
{ 0, 0, 0 },
{ 0, 0, 0 }
};
//Defines the output for each track.
//Each track controls one output.
//The type selects between axes and I/O modules. TRUE: Axis, FALSE: I/O module.
//The index of the axis or I/O module.
//The offset of the output bit in the output buffer of a device.
McOutput Outputs[2] =
{
//Axis Index Offset
{ FALSE, 2, 0 },
{ FALSE, 3, 0 }
};
const int LENGTH = 3; //The length of the Position array.
//First switch on: (1046.8, 1151.6, 1256.2) off: (1570.2, 1727.4, 1884.3)
//Second switch on: (523.4, 575.8, 628.1) off: (785.1, 863.65, 942.16)
//Third switch on: (2093.6, 2303.2, 2512.4) off: (3140.4, 3454.8, 3768.6)
//Unit vector: (0.5234, 0.5758, 0.6281)
double EndPositions[LENGTH] = { 5000, 5500, 6000 };
//Move an axis to trigger a switch.
RtPrintf("Make a linear absolute move.\n\n");
KsCommandStatus absolute = MoveLinearAbsolute(Group, LENGTH,
EndPositions, 500, 5000, 5000, 500000, mcAxisCoordSystem,
mcAborting, mcNone, NULL);
if (absolute.Error)
RtPrintf("MoveLinearAbsolute failed: %d\n", absolute.ErrorId);
KsCommandStatus groupCamSwitch = SetGroupPathCamSwitch(
Group, //Index
absolute, //MotionCommand
3, //SwitchLength
Switches, //Switches
2, //TrackLength
Outputs, //Outputs
Tracks, //Tracks
3 //EnableMask, enabling Track Zero and One
);
/*-----Calculate where the switches will be triggered-----*/
RtPrintf("Calculate where the switches will be triggered.\n\n");
double axisDistance[3] = { 0 };
double startPosition[3] = { 0 };
double currentPosition[3] = { 0 };
double Square[3] = { 0 };
//Calculate the square of each EndPosition.
Square[0] = pow(EndPositions[0], 2);
Square[1] = pow(EndPositions[1], 2);
Square[2] = pow(EndPositions[2], 2);
//Calculate the square root of the sum of each EndPositions' square number.
double sqrtRoot = sqrt(Square[0] + Square[1] + Square[2]);
printf("Square root: %f\n\n", sqrtRoot);
//Calculate the unit vector for each axis.
double unitVector[3] = { 0 };
unitVector[0] = EndPositions[0] / sqrtRoot;
unitVector[1] = EndPositions[1] / sqrtRoot;
unitVector[2] = EndPositions[2] / sqrtRoot;
//Display each unit vector.
/*After we get unit vectors, we need to calculate how many units an axis should
move to trigger a switch. The equations are as follows:
unitVector * FirstOnPosition = A
unitVector * LastOnPosition = B
The range between A and B is where a switch is triggered.*/
printf("unitVector[0]: %f\n", unitVector[0]);
printf("unitVector[1]: %f\n", unitVector[1]);
printf("unitVector[2]: %f\n\n", unitVector[2]);
for (int i = 0; i < 3; i++)
{
printf("unitVector[%d] * Switches[%d].FirstOnPosition: %f\n", i, i, unitVector[i] * Switches[i].FirstOnPosition);
printf("unitVector[%d] * Switches[%d].LastOnPosition: %f\n", i, i, unitVector[i] * Switches[i].LastOnPosition);
}
RtPrintf("\n");
/*-----Trigger the switches-----*/
RtPrintf("Trigger the switches.\n\n");
//Use vector to declare a dynamic array that creates a range-based for loop.
vector<int> Loop = { 1, 0, 2 };
/*Count is a flag used to check whether a switch's output has been read and displayed.
If it has, the count value will be incremented. This flag can ensure RtPrintf is executed
only once when it displays the output of the switch.*/
int Count = 0;
while (!absolute.Done)
{
//Get the state of MoveLinearAbsolute and pass it to absolute.
absolute = GetCommandStatus(absolute);
//Get the set position of a group.
GetGroupPosition(Group, mcAxisCoordSystem, mcSetValue, LENGTH, currentPosition);
//Calculate how far three axes move.
axisDistance[0] = currentPosition[0] - startPosition[0];
axisDistance[1] = currentPosition[1] - startPosition[1];
axisDistance[2] = currentPosition[2] - startPosition[2];
/*Use for loop to check whether the switches are triggered.
The switches are triggered in this sequence: 2->1->3.*/
for (auto i : Loop)
{
/*Value is used to check whether a switch has been triggered. If it has,
Value will be changed to one and displayed, and then be reset for the
next switch.*/
Value = 0;
//Check whether the axes trigger their own switches.
if (axisDistance[i] >= (unitVector[i] * Switches[i].FirstOnPosition) &&
axisDistance[i] <= (unitVector[i] * Switches[i].LastOnPosition))
{
/*Use switch-case statement to test whether a switch has been triggered.
If it has, display its output value once.*/
switch (i)
{
//Check whether the second switch is triggered.
case 1:
if (Count >= 1)
continue;
ReadOutputBit(2, 0, &Value);
RtPrintf("Read the output bit corresponding to Track Zero, 2nd switch: %d\n", Value);
Count++;
break;
//Check whether the first switch is triggered.
case 0:
if (Count >= 2)
continue;
ReadOutputBit(2, 0, &Value);
RtPrintf("Read the output bit corresponding to Track Zero, 1st switch: %d\n", Value);
Count++;
break;
//Check whether the third switch is triggered.
case 2:
if (Count >= 3)
continue;
ReadOutputBit(3, 0, &Value);
RtPrintf("Read the output bit corresponding to Track One, 1st switch: %d\n\n", Value);
Count++;
break;
default:
RtPrintf("Out of range.\n\n");
}
}
}
}
//Wait 1 millisecond to make sure everything is done.
//On the computers that have better performance, this command can be skipped.
Sleep(1);
//Get the state of SetGroupPathCamSwitch and pass it to groupCamSwitch.
groupCamSwitch = GetCommandStatus(groupCamSwitch);
//Display the Done state of MoveLinearAbsolute and SetGroupPathCamSwitch.
RtPrintf("MoveLinearAbsolute.Done: %d\n", absolute.Done);
RtPrintf("SetGroupPathCamSwitch.Done: %d\n\n", groupCamSwitch.Done);
//Display end positions of all axes.
RtPrintf("End position:\n");
GetAGroupPosition(Group);
}
Output:
Trigger detection: the magenta trace is Output Two, triggered by the first and second switches (second is triggered first). The orange trace is Output Three, triggered by the third switch.