Table of Contents

Equipment - Axes PlcOpen (unmanaged)

motoric axes are often a complicated task in automation. Especially for PlcOpen motion instances, there are a lot of objects which have to be handled and many parameters which have to be set, but not with Zeugwerk Framework. We have a well designed PlcOpen motion implementation in our equipment library with a standardized interface and clear architecture.

In this tutorial we will discuss how to use a PlcOpen axes in legacy or non template projects (unmanaged) and use it with a small example in usermode runtime. Therefore there is no need to have a special hardware, except you want to drive with the simulation mode of plcopen (Twincats Nc-Motion implementation) itself, but also this will be discussed in this tutorial.

The Zeugwerk Framework PlcOpen Motion implementation offers 3 different use-cases:

  • normal operation: usage of all objects with real hardware and the real time nc-motion kernel from twincat
  • nc-motion simulation: usage of all objects on a dedicated ipc or real runtime with nc-motion kernel but this time in simulation mode
  • Zeugwerk PlcOpen simulation: usage of the zeugwerk simulation axis but also with the standardized interface and without nc-motion kernel

All of the following examples start with a prepared solution for testing in a usermode runtime environment and with our logging object to observe whats going on. Check here for a small tutorial on how to use the usermode runtime in Twincat

Normal Operation

First lets implement a small example. We want one Axis to move from its current position to 10mm with a velocity of 1mm/s. What we have to do is, we need to instantiate a AxisPlcOpenMcUM Axis and call some initialization code (more on initialization and parametrization later). To let the axis be in fully operation mode, we have to call its cyclic method at the very top of the program (or somewhereelse in your program, just make sure it gets called every plc-cycle). After that we need to wait until the axis is initialized and in idle state.

Activation & Simple Movement

In our example we now wait for a manually set start flag, to be able to proper observe whats happening while starting a movement. After setting ManualStart to TRUE in online mode of Twincat we start a movement from 0.0 to 10mm and wait until the axis has reached its target. First lets implement the testing code:

PROGRAM MAIN
VAR
  DateTime : ZAux.DateTimeUM;
  Logger : ZAux.LoggerFile7FFUM(datetime:=DateTime, filePath:='C:\temp\logfile.log', target:='');
  Step : ZCore.Step(0, 100);
  Axis1 : ZEquipment.AxisPlcOpenMcUM;
  ManualStart : BOOL;
END_VAR
-----------------------------------------------
DateTime.Cyclic();
Logger.Cyclic();
Axis1.Cyclic();

CASE Step.Index OF
  0:
    IF Logger.Operational
    THEN
      Logger.Info('Appliation started');
      Step.SetNext(10);
    END_IF

  10:
    Axis1.SetLogger(Logger);
    Axis1.SetName('XAxis');
    Axis1.SetSimulation(FALSE);
    Axis1.Parameter.Plc.MaximumSpeed := 1000;
    
    Step.SetNext(20);
    
  20:
    IF Axis1.State = ZCore.ObjectState.Idle
    THEN
      Step.SetNext(30);
    END_IF

  30:
    IF ManualStart THEN
      ManualStart := FALSE;
      Step.SetNext(40);
    END_IF
    
  40: 
    IF Step.OnEntry() THEN
      Axis1.MoveAbsoluteAsync(0, 10, 1);
    END_IF
    
    IF Axis1.Error
    THEN
      Logger.Error(Axis1.ErrorMessage());
      Step.SetNext(30);
    ELSIF NOT Axis1.Busy
    THEN
      Logger.Info('Axis1 is in Position!');
      Step.SetNext(30);
    END_IF

END_CASE

If this is finished try to build your code to create the neccessary input and output variables which get linked to the nc-motion axis instance.

For our real use-case, with a (e.g.) servodrive, we no need to add an ethercat fieldbus master and scan out drive-controller. In our case its a Lenze i950, but generally speaking every Ethercat and Plcopen-motion device is capable for usage with nc-motion and this implementation.

Now your solution should have a NC-motion instance and an ethercat master with a real hardware drive controller attached. If Twincat creates thos objects automatically, it will also make a proper link between the nc-motion instance and the real fieldbus hardware instance.

Now we have to link our software instance in the PLC to the previously added NC-Motion instance of that axis from Twincat.

In order to keep this tutorial short, we do not discuss how to parametrize this nc-motion instance of the axis. We should be fine with standard parameters which are set from Twincat. Now we can Rebuild the solution once more and try to activate this application on the runtime.

When the application is finished with booting and all initialization was successfull the Step object should now stay in step 30 and wait until we set ManualStart flag to TRUE in order to start the movement. Lets open the axis node at the top of this online view to be able to observe the movement by looking at ActualPosition variable.

Now lets "press" the start button by forcing ManualStart to TRUE. The AxisPlcOpenMcUM object tries now to switch on the controller and start a movement. If an error is present on the controller or in NC-Motion, this error gets reset.

Another cool feature of all equipment is the integration of the Zeugwerk Framework logging object. Every step which is done by the axis gets logged automatically if we add a logger to the axis instance in initialization.

The following lines get automatically written when moving an axis, this is very usefull while debugging code without setting the machine to halt by breakpoints.

Movement & Disable Axis

Now lets add some lines of code to this project to move the axis between 0 and then every time we set ManualStart to TRUE and lets add the ability to disable the controller after pressing another button ManualDisable.

PROGRAM MAIN
VAR
  DateTime : ZAux.DateTimeUM;
  Logger : ZAux.LoggerFile7FFUM(datetime:=DateTime, filePath:='C:\temp\logfile.log', target:='');
  Step : ZCore.Step(0, 100);
  Axis1 : ZEquipment.AxisPlcOpenMcUM;
  ManualStart : BOOL;
  ManualDisable : BOOL;
END_VAR
----------------------------------------
DateTime.Cyclic();
Logger.Cyclic();
Axis1.Cyclic();

CASE Step.Index OF
  0:
    IF Logger.Operational
    THEN
      Logger.Info('Appliation started');
      Step.SetNext(10);
    END_IF

  10:
    Axis1.SetLogger(Logger);
    Axis1.SetName('XAxis');
    Axis1.SetSimulation(FALSE);
    Axis1.Parameter.Plc.MaximumSpeed := 1000;
    
    Step.SetNext(20);
    
  20:
    IF Axis1.State = ZCore.ObjectState.Idle
    THEN
      Step.SetNext(30);
    END_IF

  30:
    IF ManualStart THEN
      ManualStart := FALSE;
      Step.SetNext(40);
    END_IF
    
    IF ManualDisable THEN
      ManualDisable := FALSE;
      Step.SetNext(50);
    END_IF
    
  40: 
    IF Step.OnEntry() THEN
      IF ZCore.IsEqualLreal(Axis1.ActualPosition, 10, 0.1)
      THEN
        Axis1.MoveAbsoluteAsync(0, 0, 1);
      ELSE
        Axis1.MoveAbsoluteAsync(0, 10, 1);  
      END_IF
    END_IF
    
    IF Axis1.Error
    THEN
      Logger.Error(Axis1.ErrorMessage());
      Step.SetNext(30);
    ELSIF NOT Axis1.Busy
    THEN
      Logger.Info('Axis1 is in Position!');
      Step.SetNext(30);
    END_IF

  50:
    IF Step.OnEntry() THEN
      Axis1.EnableDriveAsync(0, FALSE);
    END_IF
    
    IF Axis1.Error
    THEN
      Logger.Error(Axis1.ErrorMessage());
      Step.SetNext(30);
    ELSIF NOT Axis1.Busy
    THEN
      Logger.Info('Axis1 is now disabled!');
      Step.SetNext(30);
    END_IF
    
END_CASE

Here we automatically move from 0mm to 10mm when starting by setting ManualStart to TRUE. We can also disable the controller by setting ManualDisable to TRUE and see how the axis gets automatically enabled when start the movement again afterwards.

Stop Axis during Movement

Last but not least we want to stop the axis by again, setting a manual flag. Implement the following code, activate and start a movement:

PROGRAM MAIN
VAR
  DateTime : ZAux.DateTimeUM;
  Logger : ZAux.LoggerFile7FFUM(datetime:=DateTime, filePath:='C:\temp\logfile.log', target:='192.168.0.80.1.1');
  Step : ZCore.Step(0, 100);
  Axis1 : ZEquipment.AxisPlcOpenMcUM;
  ManualStart : BOOL;
  ManualDisable : BOOL;
  ManualStop : BOOL;
END_VAR
--------------------------------------------
DateTime.Cyclic();
Logger.Cyclic();
Axis1.Cyclic();

CASE Step.Index OF
  0:
    IF Logger.Operational
    THEN
      Logger.Info('Appliation started');
      Step.SetNext(10);
    END_IF

  10:
    Axis1.SetLogger(Logger);
    Axis1.SetName('XAxis');
    Axis1.SetSimulation(FALSE);
    Axis1.Parameter.Plc.MaximumSpeed := 1000;
    
    Step.SetNext(20);
    
  20:
    IF Axis1.State = ZCore.ObjectState.Idle
    THEN
      Step.SetNext(30);
    END_IF

  30:
    IF ManualStart THEN
      ManualStart := FALSE;
      Step.SetNext(40);
    END_IF
    
    IF ManualDisable THEN
      ManualDisable := FALSE;
      Step.SetNext(50);
		END_IF
    
  40: 
    IF Step.OnEntry() THEN
      IF ZCore.IsEqualLreal(Axis1.ActualPosition, 10, 0.1)
      THEN
        Axis1.MoveAbsoluteAsync(0, 0, 1);
      ELSE
        Axis1.MoveAbsoluteAsync(0, 10, 1);  
			END_IF
    END_IF
    
    IF Axis1.Error
    THEN
      Logger.Error(Axis1.ErrorMessage());
      Step.SetNext(30);
    ELSIF NOT Axis1.Busy
    THEN
      Logger.Info('Axis1 is in Position!');
      Step.SetNext(30);
    END_IF

  50:
    IF Step.OnEntry() THEN
      Axis1.EnableDriveAsync(0, FALSE);
    END_IF
    
    IF Axis1.Error
    THEN
      Logger.Error(Axis1.ErrorMessage());
      Step.SetNext(30);
    ELSIF NOT Axis1.Busy
    THEN
      Logger.Info('Axis1 is now disabled!');
      Step.SetNext(30);
    END_IF
    
  60:
    IF Step.OnEntry() THEN
      Axis1.StopAsync(0);
    END_IF
    
    IF NOT Axis1.Busy
    THEN
      Logger.Info('Axis1 is now stopped!');
      Step.SetNext(30);
    END_IF    
  
END_CASE

IF ManualStop THEN
  ManualStop := FALSE;
  Step.SetNext(60);
END_IF

While the axis is moving set the ManualStop flag to TRUE and observe the logfile to see whats happening. The axis is now stopping when this is finished it waits on its position for a next command. We now set ManualStart to TRUE and the axis will automatically move again.

How to use a StartToken

Some might have seen that in Zeugwerk Framework on all Async methods the first parameter is an interface to a StartToken. This is because if one wants to start an object by using one of its async methos (e.g. for an axis MoveAbsoluteAsync() or for an actuator MovePlusAsync()), but the object is already busy, its hard to get informed on the caller side that there was something problematic happening. For exactly that use case the StartToken was introduced. Here one can give a StartToken object to the calling of an async method

VAR
  StartTokenObj : ZCore.StartToken
  StringBuilder : ZAux.StringBuilder;
END_VAR
--------------------------
StartTokenObj.Recover()
AxisHorizontal.MoveAbsoluteAsync(StartTokenObj, 10.0, 1.0);
IF StartTokenObj.Error THEN
  Logger.Error(StringBuilder.Append('Starting movement failed (error:).Append(StartTokenObj.ErrorMessage).Append(')'));
  RETURN;
END_IF

If this call fails because the object was already busy or there is another problem happening, then this StartToken will contain a proper error message and an error flag which can get testet on the caller side. Then the caller has to decide if he wants to wait and try at another time or simply bring the error to the operators view.

Here is our first movement example with using a StartToken on calling MoveAbsoluteAsync:

PROGRAM MAIN
VAR
  DateTime : ZAux.DateTimeUM;
  Logger : ZAux.LoggerFile7FFUM(datetime:=DateTime, filePath:='C:\temp\logfile.log', target:='');
  Step : ZCore.Step(0, 100);
  Axis1 : ZEquipment.AxisPlcOpenMcUM;
  StartTokenObj : StartToken;
  ManualStart : BOOL;
END_VAR
-----------------------------------------------
DateTime.Cyclic();
Logger.Cyclic();
Axis1.Cyclic();

CASE Step.Index OF
  0:
    IF Logger.Operational
    THEN
      Logger.Info('Appliation started');
      Step.SetNext(10);
    END_IF

  10:
    Axis1.SetLogger(Logger);
    Axis1.SetName('XAxis');
    Axis1.SetSimulation(FALSE);
    Axis1.Parameter.Plc.MaximumSpeed := 1000;
    
    Step.SetNext(20);
    
  20:
    IF Axis1.State = ZCore.ObjectState.Idle
    THEN
      Step.SetNext(30);
    END_IF

  30:
    IF ManualStart THEN
      ManualStart := FALSE;
      Step.SetNext(40);
    END_IF
    
  40: 
    IF Step.OnEntry() THEN
      StartTokenObj.Recover();
      Axis1.MoveAbsoluteAsync(StartTokenObj, 10, 1);
      IF StartTokenObj.Error THEN
        Logger.Error('Starting Movement failed!');
        RETURN;
      END_IF
    END_IF
    
    IF Axis1.Error
    THEN
      Logger.Error(Axis1.ErrorMessage());
      Step.SetNext(30);
    ELSIF NOT Axis1.Busy
    THEN
      Logger.Info('Axis1 is in Position!');
      Step.SetNext(30);
    END_IF

END_CASE

Nc-Motion Simulation

Sometimes there is no real hardware axis available for tests, therefore we have to simulate the behavior. Unfortunately there are two possibilites and the first one, with NC-Motion will be discussed here.

If you were going through the first part of the tutorial, you have been able to see how an axis can be moved, disabled and stopped. Now we want to make one step, a simple movement without connecting a real axis.

take the first example code and save your project.

PROGRAM MAIN
VAR
  DateTime : ZAux.DateTimeUM;
  Logger : ZAux.LoggerFile7FFUM(datetime:=DateTime, filePath:='C:\temp\logfile.log', target:='');
  Step : ZCore.Step(0, 100);
  Axis1 : ZEquipment.AxisPlcOpenMcUM;
  ManualStart : BOOL;
END_VAR
-----------------------------------------------
DateTime.Cyclic();
Logger.Cyclic();
Axis1.Cyclic();

CASE Step.Index OF
  0:
    IF Logger.Operational
    THEN
      Logger.Info('Appliation started');
      Step.SetNext(10);
    END_IF

  10:
    Axis1.SetLogger(Logger);
    Axis1.SetName('XAxis');
    Axis1.SetSimulation(FALSE);
    Axis1.Parameter.Plc.MaximumSpeed := 1000;
    
    Step.SetNext(20);
    
  20:
    IF Axis1.State = ZCore.ObjectState.Idle
    THEN
      Step.SetNext(30);
    END_IF

  30:
    IF ManualStart THEN
      ManualStart := FALSE;
      Step.SetNext(40);
    END_IF
    
  40: 
    IF Step.OnEntry() THEN
      Axis1.MoveAbsoluteAsync(0, 10, 1);
    END_IF
    
    IF Axis1.Error
    THEN
      Logger.Error(Axis1.ErrorMessage());
      Step.SetNext(30);
    ELSIF NOT Axis1.Busy
    THEN
      Logger.Info('Axis1 is in Position!');
      Step.SetNext(30);
    END_IF

END_CASE

Now we have to switch the NC-motion instance into simulation mode. This is done by the following small steps:

  • removing the connection of NC-axis to the real axis on the fieldbus
  • assuring that the encoder is now a simulation encoder
  • setting the axis type to Standard (Mapping via Encoder and Drive).

After this is done, the NC-motion will simply copy its command values to the actual values (no following error is created).

Also take a look into the logfile, for your software it does not make any difference if the real axis is activated or a simulated one (of course except creating no following error).

The only problem here is, one needs a proper runtimere where an NC-Motion instance can get activated, for a fully simulated environment for example on a local notebook, there is only the way over Beckhoffs Usermode runtime, but here no NC-Motion is supported. For this problem we have a second way of simulating the axis behavior.

Fully simulated usage

A third option for simulated usage of an axis is to use the Zeugwerk AxisSimulated. This is done by simply setting Simulation to TRUE in initialization phase of the PlcOpen Axis instance. For this we can also disable the NC-Motion node, because this object is not required. All trajectories are here calculated by the AxisSimulation instance.

Take this example code to switch to simulation mode, we leave everything the same as in the example above except setting Simulation to TRUE:

DateTime.Cyclic();
Logger.Cyclic();
Axis1.Cyclic();

CASE Step.Index OF
  0:
    IF Logger.Operational
    THEN
      Logger.Info('Appliation started');
      Step.SetNext(10);
    END_IF

  10:
    Axis1.SetLogger(Logger);
    Axis1.SetName('XAxis');
    Axis1.SetSimulation(TRUE);
    Axis1.Parameter.Plc.MaximumSpeed := 1000;
    
    Step.SetNext(20);
    
  20:
    IF Axis1.State = ZCore.ObjectState.Idle
    THEN
      Step.SetNext(30);
    END_IF

  30:
    IF ManualStart THEN
      ManualStart := FALSE;
      Step.SetNext(40);
    END_IF
    
  40: 
    IF Step.OnEntry() THEN
      Axis1.MoveAbsoluteAsync(0, 10, 1);
    END_IF
    
    IF Axis1.Error
    THEN
      Logger.Error(Axis1.ErrorMessage());
      Step.SetNext(30);
    ELSIF NOT Axis1.Busy
    THEN
      Logger.Info('Axis1 is in Position!');
      Step.SetNext(30);
    END_IF

END_CASE

Here we have to disable the fieldbus and the NC-Motion because these two parts cannot run in the Usermode Runtime. Now start a Usermode Runtime (see here for more details on this) and select this newly created instance as Twincat target. Activate and login to this target and finally start the movement by giving a rising edge to ManualStart BOOL.

Also here the normal logging object is working and is generating the same messages as in the other examples above, which is great for debugging and testing offline and without hardware.