Skip to main content

Creating Cams Programmatically

As of CODESYS SoftMotion version 4.17.0.0, the CamBuilder function block provides an interface for creating cams programmatically directly in the IEC application.

For more information, see the example: Creating Cams Programmatically

Using the CamBuilder function block (as of SM 4.17.0.0)

The following cam is created by default when a cam object is created in the device tree:

_sm_img_cam_diagram.png

The cam consists of three fifth-degree polynomials with the following four boundary values:

X

Y

V

A

0

0

0

0

120

120

1

0

240

240

1

0

360

360

0

0

To create this cam programmatically, an instance of the CamBuilder function block is first declared:

VAR
    camBuilder : SMCB.CamBuilder;
END_VAR

In the implementation part, the CamBuilder instance first has to be initialized. Three segments of type Poly5 can then be added using the Append method:

camBuilder.Init();
 
camBuilder.Append(
    SMCB.Poly5(
	SMCB.BoundImplicit(),
	SMCB.Bound(120, 120, 1)));
camBuilder.Append(
    SMCB.Poly5(
	SMCB.BoundImplicit(),
	SMCB.Bound(240, 240, 1)));
camBuilder.Append(
    SMCB.Poly5(
        SMCB.BoundImplicit(),
        SMCB.Bound(360, 360)));

The polynomials are defined via the left and right boundary conditions. In the example, the BoundImplicit function is always used for the left boundary. As a result, the right boundary condition of the previous segment is applied. If the BoundImplicit function is used as the left boundary for the first segment, then it starts at zero: in this example, with Poly5 segment at (X, Y, V, A) = (0, 0, 0, 0).

When the MC_CamTableSelect and MC_CamIn function blocks are used, the cam defined in the CamBuilder function block finally has to be converted into an MC_CamRef. There are two ways to do this, depending on where the CamBuilder is called:

  1. Calling the CamBuilder in the bus task:

    First, the declaration part must be extended by the corresponding instances:

    VAR
        ...
        camRef : MC_CAM_REF;
        aCamSegments : ARRAY[1..3] OF SMC_CAM_SEGMENT;
    END_VAR

    Then the function block instance MC_CAM_REF is initialized and written using the Write method of the CamBuilder function block.

    SMCB.InitCamRef(camRef, ADR(aCamSegments), XSIZEOF(aCamSegments));
    camBuilder.Write(camRef);
  2. Calling the CamBuilder in another task (multitask, multicore):

    First, a multitask/multicore-safe instance of the cam is created in a GVL, which is accessed by both the bus task and the CamBuilder task.

    VAR_GLOBAL
        safeCam : SMCB.CAM_REF_MULTICORE_SAFE;
    END_VAR

    Then the creation of the cam in the other task is started from the bus task.

    1. To determine in the bus task when the new cam was written in the other task, the program remembers the CamId in STATE_INIT_ONLINE_TABLE_MULTITASK before the cam is created.

    2. Then the creation of the cam is started in the other task in the STATE_START_CREATE_ONLINE_TABLE_MULTITASK state.

    3. Then, the created cam is read in the STATE_READ_ONLINE_TABLE_MULTITASK state.

    PROGRAM BUS_TASK
    VAR
        state : UDINT;
        error : SMC_ERROR;
        camIdBeforeCreate : UDINT;
        camSegments: ARRAY[0..99] OF SMC_CAM_SEGMENT;
        camRef: MC_CAM_REF;
    END_VAR
    VAR CONSTANT
        STATE_INIT_ONLINE_TABLE_MULTITASK : UDINT := 0;
        STATE_START_CREATE_ONLINE_TABLE_MULTITASK : UDINT := 10;
        STATE_READ_ONLINE_TABLE_MULTITASK : UDINT := 20;
        STATE_ERROR : UDINT := 1000;
    END_VAR
    
    
    CASE state OF
    STATE_INIT_ONLINE_TABLE_MULTITASK:
        camIdBeforeCreate := GVL.safeCam.CamId;
    
        state := STATE_START_CREATE_ONLINE_TABLE_MULTITASK;
    
    STATE_START_CREATE_ONLINE_TABLE_MULTITASK:
        CamBuilderTask.BuildCam := TRUE;
    
        state := STATE_READ_ONLINE_TABLE_MULTITASK;
    
    STATE_READ_ONLINE_TABLE_MULTITASK:
        IF CamBuilderTask.Error THEN
            error := CamBuilderTask.ErrorId;
            state := state + STATE_ERROR;
        ELSIF GVL.safeCam.CamId <> camIdBeforeCreate THEN
            error := GVL.safeCam.GetCopy(
                camRef:= camRef,
                pCamSegments:= ADR(camSegments),
                arraySize:= XSIZEOF(camSegments));
    
            IF error = SMC_NO_ERROR THEN
                state := state + 10;
            ELSE
                state := state + STATE_ERROR;
            END_IF
        END_IF
    END_CASE

    In the CamBuilder task, the multitask/multicore-safe cam is written by calling CamBuilder.WriteMulticoreSafe():

    PROGRAM CamBuilderTask
    VAR_INPUT
        BuildCam : BOOL;
    END_VAR
    VAR_OUTPUT
        Error : BOOL;
        ErrorId : SMC_ERROR;
    END_VAR
    VAR
        camBuilder : SMCB.CamBuilder;
    END_VAR
    
    IF BuildCam THEN
        BuildCam := FALSE;
    
        camBuilder.Init();
        camBuilder.Append(SMCB.Poly5(SMCB.BoundImplicit(), SMCB.Bound(120, 120, 1)));
        camBuilder.Append(SMCB.Poly5(SMCB.BoundImplicit(), SMCB.Bound(240, 240, 1)));
        camBuilder.Append(SMCB.Poly5(SMCB.BoundImplicit(), SMCB.Bound(360, 360)));
         
        Error := camBuilder.IsErrorPending(errorID=> ErrorId);
    
        IF NOT Error THEN
            ErrorId := camBuilder.WriteMulticoreSafe(GVL.safeCam);
            Error := ErrorId <> SMC_NO_ERROR;
        END_IF
    END_IF

Implicit and explicit boundary conditions

An implicit boundary condition defined with BoundImplicit makes sure that the transition to the adjacent segment is as smooth as possible. To do this, the boundary condition of the adjacent segment needs to be explicitly defined using the Bound method. So if the left boundary condition of a segment is implicit, then the right boundary condition of the previous segment has to be explicit. Conversely, if the right boundary condition is implicit, then the left boundary condition of the subsequent segment has to be explicit.

The most common use case is presumably that only the right boundary conditions of the segments are explicitly specified, as in the example above. Due to the implicit left boundary conditions, the segment transitions are automatically as smooth as possible and there are no gaps in the definition area. The following example is a simple case where it is helpful to deviate from this approach:

The slave axis should travel at a constant velocity from position 20 to 100:

camBuilder.Append(
    SMCB.Line(
        SMCB.Bound(20, 20),
        SMCB.Bound(100, 100)));

Before and after this, a Poly5 segment is used for acceleration and deceleration:

camBuilder.Append(
    SMCB.Poly5(
        SMCB.BoundImplicit(),
        SMCB.BoundImplicit()));
camBuilder.Append(
    SMCB.Line(
        SMCB.Bound(20, 20),
        SMCB.Bound(100, 100)));
camBuilder.Append(
    SMCB.Poly5(
        SMCB.BoundImplicit(),
        SMCB.Bound(120, 120, 0)));

The cam defined in this way has unwanted acceleration and deceleration phases in the Poly5 segments (velocity in blue):

_sm_img_cam_diagram2.png

To avoid this, it is sufficient to adjust the master position in the segment of type Line (for example, that of the left boundary from 20 to 30 and that of the right boundary from 100 to 90):

...
camBuilder.Append(
    SMCB.Line(
        SMCB.Bound(30, 20),
        SMCB.Bound(90, 100)));
...
_sm_img_cam_diagram3.png

It is not necessary to adjust the segments of type Poly5 because they are automatically added to the line segment as smoothly as possible due to the boundary conditions defined using the BoundImplicit function.

Error Handling

The Append method of the CamBuilder function block returns TRUE when a segment has been successfully added, and returns FALSE if an error has occurred. After an error, no more segments can be added and the CamBuilder function block has to be reinitialized using the Init method. The cause of the error can be determined using the IsErrorPending method:

camBuilder.IsErrorPending(errorId=> errorId);

Manual creation of the data structure MC_CAM_REF (before SM 4.17.0.0)

Important

It is recommended to use the SM3_CamBuilder library as of CODESYS SoftMotion 4.17.0.0.

Data structures of cams

On project compile, the created cam data is converted internally into a global variable list. By clicking Display generated code in the cam editor, you can display the automatically created global variables.

Each cam is represented by the data structure MC_CAM_REF. You can access this data structure by means of the IEC program or by preprocessing functions and function blocks. It is available by the SM3_Basic library.

A function block that describes a cam can also be generated or populated by the IEC program at runtime.

Example 7. Example

Definition of the data structure:

TYPE mySMC_CAMTable_LREAL_10000_2 :
STRUCT
        Table: ARRAY[0..9999] OF ARRAY[0..1] OF LREAL;
        (* set all scaling definitions to 0 and 1
           result: all values of the table are not scaled *)
        fEditorMasterMin: REAL := 0;
        fEditorMasterMax: REAL := 1;
        fEditorSlaveMin: REAL := 0;
        fEditorSlaveMax: REAL := 1;
        fTableMasterMin: REAL := 0;
        fTableMasterMax: REAL := 1;
        fTableSlaveMin: REAL := 0;
        fTableSlaveMax: REAL := 1;
END_STRUCT
END_TYPE

Instantiating the data structure:

Cam: MC_CAM_REF;
Cam_PointArray : mySMC_CAMTable_LREAL_10000_2;

Calculating the cam:

Cam.byType:=2;
Cam.byVarType:=6;
Cam.nTappets:=0;
Cam.strCAMName:='myCAM';
Cam.pce:= ADR(CAM_PointArray);
FOR i:=0 TO 9999 DO
        (* example cam: master 0..360, slave 0..100,
           constant velocity *)
        Cam_PointArray.Table[i][0]:=UDINT_TO_LREAL(I)/10000 * 360;             (* X *)
        Cam_PointArray.Table[i][1]:=UDINT_TO_LREAL(I)/10000 * 100;             (* Y *)
END_FOR
Cam.nElements:=10000
Cam.xStart:=0.0;
Cam.xEnd:=360.0;


Manually generated cams

A cam can be created in an IEC program without using the cam editor.

Example 8. Example

Declaration:

VAR
i: INT;
CAM: MC_CAM_REF := (
        byType:=2, (* non-equidistant *)
        byVarType:=2, (* UINT *)
        nElements:=128,
        xStart:=0,
        xEnd:=360);
Table: SMC_CAMTable_UINT_128_2 := (
        fEditorMasterMin := 0, fEditorMasterMax := 360,
        fTableMasterMin := 0, fTableMasterMax := 6000,
        fEditorSlaveMin := 0, fEditorSlaveMax := 360,
        fTableSlaveMin := 0, fTableSlaveMax := 6000);
END_VAR

Implementation:

(* Create cam disk (example straight line); unambiguous *)
FOR i:=0 TO 127 DO
        Table.Table[i][0] := Table.Table[i][1] := REAL_TO_UINT(i / 127.0 * 6000);
END_FOR
(* Link pointer; must be done in every cycle! *)
CAM.pce := ADR(Table);


This generated cam can be specified in the MC_CamTableSelect function block and its output used again for MC_CamIn.

Compiling cam definitions

At compile time, variables of type MC_CAM_REF are created for a cam. They include a description of each segment of the cam. Data structures of this kind are passed to the MC_CamTableSelect function block. The structure is part of the SM3_Basic library.