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:

  1. 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.
  2. 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.
  3. 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.

    Copy
        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 };
  4. 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.
  5. Copy
        //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);
  6. 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.
  7. Copy
        KsCommandStatus groupCamSwitch = SetGroupPathCamSwitch(
            Group,              //Index
            absolute,           //MotionCommand
            3,                  //SwitchLength
            Switches,           //Switches
            2,                  //TrackLength
            Outputs,            //Outputs
            Tracks,             //Tracks
            3                   //EnableMask, enabling Track Zero and One
        );
  8. To know where the switches can be triggered, we need to do a few calculations. First, we define four variables:
  9. Copy
        /*-----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);
  10. 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.
  11. 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.

    Copy
        //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");

  12. To detect whether the switches are triggered, we use vector to create a range-based for loop, and declare the variable Count.
  13. Copy
        /*-----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;

  14. 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 and Value becomes one. Value then will be reset until the next switch is triggered. After MoveLinearAbsolute is done, the while loop ends.
  15. Copy
        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");
                    }
                }
            }
        }

  16. Since SetGroupPathCamSwitch is bound with absolute (the state of MoveLinearAbsolute), its Done state is synchronized with absolute's Done, but on some computers SetGroupPathCamSwitch might be done after absolute. To make sure it is completed, we use Sleep(1) to wait 1 millisecond, and then get the state of SetGroupPathCamSwitch.
  17. Copy
        //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);

  18. Finally, we display MoveLinearAbsolute and SetGroupPathCamSwitch's Done state, and the end positions of the axes.
  19. Copy
        //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:

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