Motion Control
PLC Motion Control & Servo Programming: PLCopen Function Blocks in Structured Text
From enabling a servo axis to synchronized multi-axis camming — the complete guide to PLCopen motion control function blocks implemented in IEC 61131-3 Structured Text.
Why PLCopen Motion Control?
Every servo drive manufacturer has proprietary motion commands. Siemens has its technology objects, Rockwell has MAPC/MAAG/MAM, Beckhoff has NC PTP. The PLCopen Motion Control standard (Part 1–6) solves this fragmentation by defining a universal set of function blocks that work the same way across platforms.
Once you learn PLCopen, you can program Siemens S7-1500T, Beckhoff TwinCAT, CODESYS-based systems, B&R Automation Studio, and Schneider EcoStruxure — all with the same function block calls.
This article covers real servo programming from power-on to multi-axis synchronization, with production-grade Structured Text code.
The Axis State Machine
Every PLCopen axis follows a strict state model. Understanding it prevents 90% of "why won't my axis move?" debugging sessions:
┌───────────┐
┌───►│ DISABLED │◄──────────────────┐
│ └─────┬─────┘ │
│ MC_Power│(TRUE) │MC_Power(FALSE)
│ ┌─────▼─────┐ │
│ │ STANDSTILL ├──────────────┐ │
│ └──┬──┬──┬──┘ │ │
│ │ │ │ MC_Home │ │
│ │ │ │ ┌──────▼──┐ │
│ │ │ └──────────┤ HOMING │ │
│ │ │ └────┬────┘ │
│ │ │ │Done │
│ │ │ ┌──────▼──┐ │
│ │ └──────────►│STANDSTILL│ │
│ │ └─────────┘ │
│ MC_MoveAbsolute/Relative/Velocity │
│ ┌──▼────────┐ │
│ │ MOVING ├──────Done──►STANDSTILL
│ └──┬────────┘ │
│ MC_Stop│ │
│ ┌──▼────────┐ │
└────┤ STOPPING │────────────────────┘
└───────────┘
Key rules:
Step 1: Power On and Homing
Before any motion, you must enable the drive and establish a reference position:
PROGRAM ServoStartup
VAR
Axis1 : AXIS_REF; // Axis reference (platform-specific) // Function block instances
fbPower : MC_Power;
fbReset : MC_Reset;
fbHome : MC_Home;
fbMoveAbs : MC_MoveAbsolute;
// Sequence control
Step : INT := 0;
EnableDrive : BOOL := FALSE;
HomeComplete : BOOL := FALSE;
AxisReady : BOOL := FALSE;
ErrorPresent : BOOL := FALSE;
END_VAR
// Always running: power management
fbPower(
Axis := Axis1,
Enable := EnableDrive,
Enable_Positive := TRUE, // Allow positive direction
Enable_Negative := TRUE, // Allow negative direction
Status => AxisReady
);
// Startup sequence
CASE Step OF
0: // WAIT FOR ENABLE COMMAND
IF EnableDrive THEN
Step := 1;
END_IF;
1: // WAIT FOR DRIVE READY
IF fbPower.Status THEN
// Drive is enabled and ready
Step := 2;
ELSIF fbPower.Error THEN
// Drive fault — need reset
Step := 10;
END_IF;
2: // START HOMING
fbHome(
Axis := Axis1,
Execute := TRUE,
Position := 0.0, // Define home as position 0
HomingMode := 2, // Platform-specific: sensor-based
Velocity := 10.0 // Slow homing speed (mm/s or deg/s)
);
IF fbHome.Done THEN
fbHome(Execute := FALSE);
HomeComplete := TRUE;
Step := 3;
ELSIF fbHome.Error THEN
fbHome(Execute := FALSE);
Step := 10;
END_IF;
3: // READY FOR OPERATION
AxisReady := TRUE;
// Axis is homed and ready for motion commands
10: // ERROR HANDLING
ErrorPresent := TRUE;
fbReset(
Axis := Axis1,
Execute := TRUE
);
IF fbReset.Done THEN
fbReset(Execute := FALSE);
ErrorPresent := FALSE;
Step := 1; // Try again
END_IF;
END_CASE;
Homing Modes Explained
| Mode | Method | Use Case | | 1 | Direct set position | Absolute encoder — no physical motion needed | | 2 | Sensor-based | Move until home sensor, then index pulse | | 3 | Mechanical stop | Move until torque limit (hard stop homing) | | 4 | Incremental encoder reference | Move to Z-pulse after sensor |
Production tip: Always implement a homing timeout. If homing takes longer than expected, something is physically wrong (sensor failure, mechanical jam). Don't let the axis keep searching forever.
// Homing timeout protection
VAR
tmrHomingTimeout : TON;
END_VARtmrHomingTimeout(IN := (Step = 2), PT := T#30S);
IF tmrHomingTimeout.Q THEN
fbHome(Execute := FALSE);
// Generate alarm: homing timeout — check home sensor
Step := 10;
END_IF;
Step 2: Point-to-Point Motion
The three core motion commands:
// MC_MoveAbsolute — move to a specific position
fbMoveAbs(
Axis := Axis1,
Execute := StartMove,
Position := 150.0, // Target: 150.0 mm
Velocity := 500.0, // Speed: 500 mm/s
Acceleration := 2000.0, // Accel: 2000 mm/s²
Deceleration := 2000.0, // Decel: 2000 mm/s²
Jerk := 10000.0 // Jerk limit: 10000 mm/s³
);
// MC_MoveRelative — move a distance from current position
VAR
fbMoveRel : MC_MoveRelative;
END_VARfbMoveRel(
Axis := Axis1,
Execute := StartRelMove,
Distance := 25.0, // Move 25mm from current position
Velocity := 200.0,
Acceleration := 1000.0,
Deceleration := 1000.0
);
// MC_MoveVelocity — continuous motion at a set speed
VAR
fbMoveVel : MC_MoveVelocity;
END_VARfbMoveVel(
Axis := Axis1,
Execute := StartConveyor,
Velocity := 100.0, // 100 mm/s continuous
Acceleration := 500.0,
Direction := 1 // 1=Positive, 2=Negative
);
// Runs until MC_Stop or another motion command
Motion Profiles: Trapezoidal vs. S-Curve
| Profile | Jerk Parameter | Use Case | | Trapezoidal | 0 or very high | Fast positioning, less smooth | | S-Curve | Finite value | Smooth motion, less mechanical stress |
S-curve profiles reduce vibration and settling time on precision machines. Set the Jerk parameter to a reasonable value (typically 5–20× the acceleration):
// Trapezoidal: instant acceleration change
fbMoveAbs(Axis:=Axis1, Execute:=TRUE, Position:=100.0,
Velocity:=500.0, Acceleration:=5000.0,
Deceleration:=5000.0, Jerk:=0);// S-Curve: smooth acceleration ramp
fbMoveAbs(Axis:=Axis1, Execute:=TRUE, Position:=100.0,
Velocity:=500.0, Acceleration:=5000.0,
Deceleration:=5000.0, Jerk:=50000.0);
Multi-Position Sequencing
Real machines move through sequences of positions. A position table pattern handles this cleanly:
TYPE MotionPosition :
STRUCT
Position : REAL;
Velocity : REAL;
Accel : REAL;
Decel : REAL;
DwellTime : TIME; // Wait time at position
OutputAction : INT; // 0=None, 1=Clamp, 2=Drill, 3=Dispense
END_STRUCT;
END_TYPEPROGRAM PositionSequencer
VAR
Positions : ARRAY[1..20] OF MotionPosition;
NumPositions : INT := 4;
CurrentPos : INT := 1;
SeqState : INT := 0;
fbMoveAbs : MC_MoveAbsolute;
tmrDwell : TON;
CycleRunning : BOOL;
CycleComplete: BOOL;
END_VAR
IF NOT CycleRunning THEN
SeqState := 0;
CurrentPos := 1;
CycleComplete := FALSE;
RETURN;
END_IF;
CASE SeqState OF
0: // START MOVE
fbMoveAbs(
Axis := Axis1,
Execute := TRUE,
Position := Positions[CurrentPos].Position,
Velocity := Positions[CurrentPos].Velocity,
Acceleration := Positions[CurrentPos].Accel,
Deceleration := Positions[CurrentPos].Decel
);
SeqState := 1;
1: // WAIT FOR POSITION REACHED
IF fbMoveAbs.Done THEN
fbMoveAbs(Execute := FALSE);
// Execute action at this position
CASE Positions[CurrentPos].OutputAction OF
1: ( Activate clamp ) ;
2: ( Start drill cycle ) ;
3: ( Dispense material ) ;
END_CASE;
SeqState := 2;
ELSIF fbMoveAbs.Error THEN
fbMoveAbs(Execute := FALSE);
CycleRunning := FALSE; // Abort on error
END_IF;
2: // DWELL TIME
tmrDwell(IN := TRUE, PT := Positions[CurrentPos].DwellTime);
IF tmrDwell.Q THEN
tmrDwell(IN := FALSE);
// Advance to next position
CurrentPos := CurrentPos + 1;
IF CurrentPos > NumPositions THEN
CycleComplete := TRUE;
CycleRunning := FALSE;
ELSE
SeqState := 0; // Move to next position
END_IF;
END_IF;
END_CASE;
Electronic Gearing
MC_GearIn locks one axis (slave) to follow another (master) at a fixed ratio. This is fundamental for:
PROGRAM ElectronicGearing
VAR
MasterAxis : AXIS_REF;
SlaveAxis : AXIS_REF; fbGearIn : MC_GearIn;
fbGearOut : MC_GearOut;
GearRatio_Num : DINT := 3; // Numerator
GearRatio_Den : DINT := 1; // Denominator — slave moves 3:1
EngageGear : BOOL;
DisengageGear : BOOL;
END_VAR
IF EngageGear THEN
fbGearIn(
Master := MasterAxis,
Slave := SlaveAxis,
Execute := TRUE,
RatioNumerator := GearRatio_Num,
RatioDenominator := GearRatio_Den,
Acceleration := 5000.0,
Deceleration := 5000.0
);
IF fbGearIn.InGear THEN
EngageGear := FALSE;
fbGearIn(Execute := FALSE);
// Slave is now locked to master
END_IF;
END_IF;
IF DisengageGear THEN
fbGearOut(
Slave := SlaveAxis,
Execute := TRUE
);
IF fbGearOut.Done THEN
DisengageGear := FALSE;
fbGearOut(Execute := FALSE);
END_IF;
END_IF;
Dynamic Gear Ratio Changes
In winding applications, the gear ratio must change as the roll diameter grows:
// Winding: adjust ratio based on roll diameter
VAR
CoreDiameter : REAL := 76.0; // mm — empty core
CurrentDiameter : REAL;
MaterialThickness: REAL := 0.05; // mm per layer
LayerCount : DINT := 0; LineSpeed : REAL; // Master speed
WinderRPM : REAL; // Required winder speed
END_VAR
// Calculate current roll diameter
CurrentDiameter := CoreDiameter + (2.0 INT_TO_REAL(LayerCount) MaterialThickness);
// Required winder surface speed = line speed
// RPM = (LineSpeed 60) / (PI Diameter)
WinderRPM := (LineSpeed 60.0) / (3.14159 CurrentDiameter);
// Update gear ratio dynamically
// This requires MC_GearInDyn or re-engaging MC_GearIn with new ratio
Electronic Camming
MC_CamIn is the most powerful PLCopen function — it links a slave axis to a master axis through a cam profile (arbitrary position-to-position mapping). Used for:
// Define cam table: master position → slave position
// This example: rotary knife that accelerates to match web speed, cuts, then retracts
TYPE CamPoint :
STRUCT
MasterPos : REAL; // Master degrees (0–360)
SlavePos : REAL; // Slave degrees
END_STRUCT;
END_TYPEPROGRAM CamProfile
VAR
CamTable : ARRAY[1..8] OF CamPoint;
fbCamIn : MC_CamIn;
MasterAxis : AXIS_REF;
SlaveAxis : AXIS_REF;
END_VAR
// Define cam profile — rotary knife
CamTable[1].MasterPos := 0.0; CamTable[1].SlavePos := 0.0;
CamTable[2].MasterPos := 45.0; CamTable[2].SlavePos := 5.0; // Slow start
CamTable[3].MasterPos := 90.0; CamTable[3].SlavePos := 30.0; // Accelerating
CamTable[4].MasterPos := 135.0; CamTable[4].SlavePos := 90.0; // Matching speed
CamTable[5].MasterPos := 180.0; CamTable[5].SlavePos := 180.0; // CUT POINT — 1:1 ratio
CamTable[6].MasterPos := 225.0; CamTable[6].SlavePos := 270.0; // Matching speed
CamTable[7].MasterPos := 315.0; CamTable[7].SlavePos := 350.0; // Decelerating
CamTable[8].MasterPos := 360.0; CamTable[8].SlavePos := 360.0; // Full cycle
// Engage cam
fbCamIn(
Master := MasterAxis,
Slave := SlaveAxis,
Execute := TRUE,
MasterOffset := 0.0,
SlaveOffset := 0.0,
StartMode := 1 // 1=Ramp in, 2=Immediate
);
Cut-to-Length with Registration
For cutting at a specific product length, the master is often a virtual axis driven by an encoder measuring web travel:
PROGRAM CutToLength
VAR
WebEncoder : AXIS_REF; // Virtual axis from web encoder
KnifeAxis : AXIS_REF; // Physical rotary knife
ProductLength : REAL := 300.0; // mm — desired cut length
KnifeCircumf : REAL := 500.0; // mm — knife circumference
// Registration correction
RegSensor : BOOL; // Registration mark sensor
RegPosition : REAL; // Where mark was detected
ExpectedPos : REAL; // Where mark should be
CorrectionOffset : REAL;
CorrectionGain : REAL := 0.5; // Apply 50% of error per cycle
END_VAR// Registration correction — adjust slave offset
IF RegSensor THEN
CorrectionOffset := (ExpectedPos - RegPosition) * CorrectionGain;
// Apply offset to cam — shift cut position
// This smoothly corrects for print stretch, slip, etc.
END_IF;
Position and Torque Monitoring
Production machines need to verify that motion completed correctly:
FUNCTION_BLOCK FB_MotionVerify
VAR_INPUT
Axis : AXIS_REF;
TargetPos : REAL;
PositionWindow : REAL := 0.05; // ±0.05mm acceptable
SettlingTime : TIME := T#200MS;
TorqueLimit : REAL := 80.0; // % of rated torque
END_VAR
VAR_OUTPUT
InPosition : BOOL;
Settled : BOOL;
TorqueWarning : BOOL;
PositionError : REAL;
END_VAR
VAR
tmrSettle : TON;
ActualPos : REAL;
ActualTorque : REAL;
END_VAR// Read actual values from axis (platform-specific)
// ActualPos := MC_ReadActualPosition(Axis);
// ActualTorque := MC_ReadActualTorque(Axis);
PositionError := ABS(ActualPos - TargetPos);
InPosition := PositionError <= PositionWindow;
// Settling: must stay in position window for SettlingTime
tmrSettle(IN := InPosition, PT := SettlingTime);
Settled := tmrSettle.Q;
// Torque monitoring
TorqueWarning := ABS(ActualTorque) > TorqueLimit;
Coordinated Multi-Axis Motion
For XY tables, gantries, and robots, multiple axes must move together. PLCopen Part 4 defines coordinated motion, but even basic synchronization can be done with single-axis blocks:
PROGRAM XY_PickAndPlace
VAR
AxisX : AXIS_REF;
AxisY : AXIS_REF;
AxisZ : AXIS_REF; fbMoveX : MC_MoveAbsolute;
fbMoveY : MC_MoveAbsolute;
fbMoveZ : MC_MoveAbsolute;
Step : INT := 0;
// Pick position
PickX : REAL := 100.0;
PickY : REAL := 200.0;
PickZ : REAL := -50.0; // Down into part
// Place position
PlaceX : REAL := 400.0;
PlaceY : REAL := 200.0;
PlaceZ : REAL := -45.0;
SafeZ : REAL := 0.0; // Clearance height
Gripper : BOOL := FALSE;
END_VAR
CASE Step OF
0: // MOVE Z TO SAFE HEIGHT
fbMoveZ(Axis:=AxisZ, Execute:=TRUE, Position:=SafeZ,
Velocity:=200.0, Acceleration:=2000.0, Deceleration:=2000.0);
IF fbMoveZ.Done THEN fbMoveZ(Execute:=FALSE); Step := 1; END_IF;
1: // MOVE XY TO PICK (simultaneous)
fbMoveX(Axis:=AxisX, Execute:=TRUE, Position:=PickX,
Velocity:=500.0, Acceleration:=3000.0, Deceleration:=3000.0);
fbMoveY(Axis:=AxisY, Execute:=TRUE, Position:=PickY,
Velocity:=500.0, Acceleration:=3000.0, Deceleration:=3000.0);
IF fbMoveX.Done AND fbMoveY.Done THEN
fbMoveX(Execute:=FALSE); fbMoveY(Execute:=FALSE);
Step := 2;
END_IF;
2: // MOVE Z DOWN TO PICK
fbMoveZ(Axis:=AxisZ, Execute:=TRUE, Position:=PickZ,
Velocity:=100.0, Acceleration:=1000.0, Deceleration:=1000.0);
IF fbMoveZ.Done THEN fbMoveZ(Execute:=FALSE); Gripper := TRUE; Step := 3; END_IF;
3: // RETRACT Z
fbMoveZ(Axis:=AxisZ, Execute:=TRUE, Position:=SafeZ,
Velocity:=200.0, Acceleration:=2000.0, Deceleration:=2000.0);
IF fbMoveZ.Done THEN fbMoveZ(Execute:=FALSE); Step := 4; END_IF;
4: // MOVE XY TO PLACE
fbMoveX(Axis:=AxisX, Execute:=TRUE, Position:=PlaceX,
Velocity:=500.0, Acceleration:=3000.0, Deceleration:=3000.0);
fbMoveY(Axis:=AxisY, Execute:=TRUE, Position:=PlaceY,
Velocity:=500.0, Acceleration:=3000.0, Deceleration:=3000.0);
IF fbMoveX.Done AND fbMoveY.Done THEN
fbMoveX(Execute:=FALSE); fbMoveY(Execute:=FALSE);
Step := 5;
END_IF;
5: // PLACE
fbMoveZ(Axis:=AxisZ, Execute:=TRUE, Position:=PlaceZ,
Velocity:=100.0, Acceleration:=1000.0, Deceleration:=1000.0);
IF fbMoveZ.Done THEN fbMoveZ(Execute:=FALSE); Gripper := FALSE; Step := 6; END_IF;
6: // RETRACT AND DONE
fbMoveZ(Axis:=AxisZ, Execute:=TRUE, Position:=SafeZ,
Velocity:=200.0, Acceleration:=2000.0, Deceleration:=2000.0);
IF fbMoveZ.Done THEN fbMoveZ(Execute:=FALSE); Step := 0; END_IF;
END_CASE;
Summary
| Topic | Key Function Block | Purpose | | Power On | MC_Power | Enable drive, allow direction | | Homing | MC_Home | Establish position reference | | Point-to-Point | MC_MoveAbsolute/Relative | Position moves | | Continuous | MC_MoveVelocity | Constant speed (conveyors) | | Stop | MC_Stop | Controlled stop from any state | | Error Reset | MC_Reset | Clear drive faults | | Gearing | MC_GearIn/GearOut | Lock axes at fixed ratio | | Camming | MC_CamIn | Arbitrary position-position profiles | | Multi-Axis | Coordinated sequences | XY/XYZ pick-and-place |
The PLCopen standard is your passport to cross-platform motion control. Learn these function blocks once, and you can program any servo system in the industry.