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