Run INFORM jobs on a MotoLogix system.
There might be situations where you want to run an INFORM job on the robot controller. For example, because the desired feature is not yet available as native MotoLogix function.
For this purpose, we have defined a small (1 byte) interface, which we embed
in the 8 byte DigitalInputs / DigitalOutputs of the MotoLogix data packet.
This means that there is no additional data packet required.
The MceRunInformJob function interacts with the robot controller through this
interface, using a command byte,
a status byte and two state machines
(nSmRunInform and
nSmHold).
Job selection is done by 4 bits (decimal range: 0-15). But as job number 0 is reserved, it means that only job number 1-15 are available for your application.
Co-existence of MotoLogix and INFORM
MotoLogix and INFORM are in fact two different worlds, and the robot controller
can only process one of them at a time.
This means it is not possible to start an INFORM job while MotoLogix motions are
active.
Only after the MotoLogix motions have finished
(and MLX.systemState has returned to 3-idle), the INFORM job can be started.
This is interlocked inside the MceRunInformJob function.
For the other way around, the same applies; Only after the INFORM job has
finished, the MotoLogix motions can be started. This has to be interlocked by the
user. The MceRunInformJob function informs the user when the INFORM job is
finished by its io.bDone signal.
Pausing an INFORM job
The regular MotoLogix hold function does not work for INFORM jobs. Therefore we
implemented a hold function in this job interface.
A rising edge of the io.bHoldRestart input allows to hold or restart the
active INFORM job at any time, with the io.bHoldActive output informing about
the hold state.
Besides the io.bHoldRestart input, you can also use the white hold button
and green start button on the Yaskawa classic pendant for hold and restart.
Sometimes, the restart from a hold situation fails, for unknown reasons.
In this case the state machine handling hold/restart (io.nSmHold) immediately
returns to state 20-hold active.
Trying restart a second time usually solves it.
Robot setup
To use this INFORM job interface, some custom CIO mapping and INFORM jobs must be
setup in the robot controller.
This is described in the
robot setup
below.
Robot setup
Without this robot setup, the MceRunInformJob function cannot work.
General Purpose IO used in this example (referenced ones in bold):
| I/O group | I/O number | Address | Description |
|---|---|---|---|
| IG#200 | IN#1593 | #02000 | run job |
| IG#200 | IN#1594 | #02001 | confirm job number |
| IG#201 | IN#1601 to 1604 | #02010 to 02013 | job number (first 4 bits) |
| OG#200 | OUT#1593 | #12000 | job done |
| OG#201 | OUT#1601 to 1604 | #12010 to 12013 | job number (first 4 bits) |
CIO
Below an example CIO for using MceRunInformJob, with
the job interface data mapped to the first byte of DigitalInputs / DigitalOutputs.
Read more...
This is USER CIO, to be placed in CIOPRG.LST between PART 2 and END.
CIO (with comments):
// -----------------------------------------------------------------------------
// MotoLogix I/O; to PLC
// -----------------------------------------------------------------------------
// These 64 bits are mapped to the MLX[].InternalData.ReadPacket by
// the robot system software and mentioned here just for information.
// An offset can be set in robot parameter S3C1383.
//
// .DigitalInputs[0].0 <- general purpose inputs #00010 - #00087
// .DigitalInputs[1].31
// -----------------------------------------------------------------------------
// MotoLogix I/O; from PLC
// -----------------------------------------------------------------------------
// These 64 bits are mapped to the MLX[].InternalData.WritePacket by
// the robot system software and mentioned here just for information.
// An offset can be set in robot parameter S3C1384.
//
// .DigitalOutputs[0].0 -> general purpose outputs #10010 - #10087
// .DigitalOutputs[1].31
// -----------------------------------------------------------------------------
// overview
// -----------------------------------------------------------------------------
// | byte | PLC to Robot | Robot to PLC |
// | :--- | :-------------- | :-------------- |
// | 0 | MceRunInformJob | MceRunInformJob |
// | 1 | spare | spare |
// | 2 | spare | spare |
// | 3 | spare | spare |
// | 4 | spare | spare |
// | 5 | spare | spare |
// | 6 | spare | spare |
// | 7 | spare | spare |
// -----------------------------------------------------------------------------
// MceRunInformJob; from PLC
// -----------------------------------------------------------------------------
// copy the job number (in bit 0-3) from OG#001 to IG#201
STR #70017
WAND #10010,15,#02010
// .DigitalOutputs[0].4 -> confirm job number (used in JOB for handshake)
STR #10014
OUT #02001
// .DigitalOutputs[0].5 -> reset MASTER job (set the cursor to the top)
STR #10015
OUT #40070
// .DigitalOutputs[0].6 -> run MASTER job
STR #10016
OUT #40250
// .DigitalOutputs[0].6 -> run MASTER job (used in JOB for handshake)
STR #10016
OUT #02000
// .DigitalOutputs[0].7 -> hold MASTER job
STR #10017
OUT #40270
// -----------------------------------------------------------------------------
// MceRunInformJob; to PLC
// -----------------------------------------------------------------------------
// copy the active job number (in bit 0-3) from OG#201 to IG#001
STR #12010
OUT #00010
STR #12011
OUT #00011
STR #12012
OUT #00012
STR #12013
OUT #00013
// .DigitalInputs[0].4 <- green start button on pendant
STR #80016
OUT #00014
// .DigitalInputs[0].5 <- job idle (ready for start, with cursor at top)
STR #50020
OUT #00015
// .DigitalInputs[0].6 <- job busy
STR #50640
OUT #00016
// .DigitalInputs[0].7 <- job done (written in JOB)
STR #12000
OUT #00017
Above CIO example code is having comments for better understanding. When implementing, always make sure to remove comments and white lines, as these are not supported by the robot controller.
CIO (clean):
STR #70017
WAND #10010,15,#02010
STR #10014
OUT #02001
STR #10015
OUT #40070
STR #10016
OUT #40250
STR #10016
OUT #02000
STR #10017
OUT #40270
STR #12010
OUT #00010
STR #12011
OUT #00011
STR #12012
OUT #00012
STR #12013
OUT #00013
STR #80016
OUT #00014
STR #50020
OUT #00015
STR #50640
OUT #00016
STR #12000
OUT #00017
Using a different byte
If you want to map the job interface data to a different byte, the IO addresses
must be adjusted.
For example, to use the third byte you would need to change inputs addresses
from #0001x to #0003x and output addresses from #1001x to #1003x.
INFORM
Below the example INFORM jobs for using MceRunInformJob, consisting of one
master job and 15 sub jobs.
Read more...
- The
MASTERjob contains the logic for calling the request sub job and handles the handshaking with the state machine in theMceRunInformJobfunction. - Each sub job can be adjusted to your needs. The timer is just a place holder and can be replaced by your own INFORM code.
MASTER job
/JOB
//NAME MASTER
//POS
///NPOS 0,0,0,0,0,0
//INST
///DATE 2026/02/24 14:45
///ATTR SC,RW
///LVARS 1,1,0,0,0,0,0,0
NOP
'Reset finished
DOUT OT#(1593) OFF
'Reset job number
DOUT OG#(201) 0
'Read job number
DIN LB000 IG#(201)
'Call job
SET LI000 LB000
SWITCH LI000 CASE 0
TIMER T=3.00
CASE 1
CALL JOB:01
CASE 2
CALL JOB:02
CASE 3
CALL JOB:03
CASE 4
CALL JOB:04
CASE 5
CALL JOB:05
CASE 6
CALL JOB:06
CASE 7
CALL JOB:07
CASE 8
CALL JOB:08
CASE 9
CALL JOB:09
CASE 10
CALL JOB:10
CASE 11
CALL JOB:11
CASE 12
CALL JOB:12
CASE 13
CALL JOB:13
CASE 14
CALL JOB:14
CASE 15
CALL JOB:15
ENDSWITCH
'Set finished
DOUT OT#(1593) ON
'Wait for stopped
WAIT IN#(1593)=OFF
'Reset finished
DOUT OT#(1593) OFF
'Reset job number
DOUT OG#(201) 0
END
Sub job 01..15
Sub job 01:
/JOB
//NAME 01
//POS
///NPOS 0,0,0,0,0,0
//INST
///DATE 2026/02/12 10:22
///ATTR SC,RW
///GROUP1 RB1
NOP
'Echo job number
DOUT OG#(201) 1
'Wait for confirmation
WAIT IN#(1594)=ON
'Edit below this line
'----------------
TIMER T=3.00
END
Sub job 02:
/JOB
//NAME 02
//POS
///NPOS 0,0,0,0,0,0
//INST
///DATE 2026/02/12 10:22
///ATTR SC,RW
///GROUP1 RB1
NOP
'Echo job number
DOUT OG#(201) 2
'Wait for confirmation
WAIT IN#(1594)=ON
'Edit below this line
'----------------
TIMER T=3.00
END
Sub job 03 to 14 not displayed to maintain readability of this page.
Sub job 15:
/JOB
//NAME 15
//POS
///NPOS 0,0,0,0,0,0
//INST
///DATE 2026/02/12 10:22
///ATTR SC,RW
///GROUP1 RB1
NOP
'Echo job number
DOUT OG#(201) 15
'Wait for confirmation
WAIT IN#(1594)=ON
'Edit below this line
'----------------
TIMER T=3.00
END
Set master job
After all INFORM jobs are created, there is one thing left to do: The master job must be set.
Read more...
Steps:
- Key switch to TEACH
- Menu job » ctrl master » select » setting master job
- Now selecting your master job.

Usage
MceRunInformJob is best used in combination with McePosTable and its
ActionID mechanism. This makes it very convenient to combine MotoLogix motion
and INFORM jobs. Below steps show you how it is implemented in just a few
minutes.
Read more...
Create the (global) variables for the interface data. These variables are also used for connecting buttons or an HMI.
// in GVL: stRunInformJobIO: ARRAY [0..GVL.MLX_UBOUND] OF MceRunInformJobIO; // data for running INFORM jobs on a MotoLogix systemNotePer MotoLogix system one
MceRunInformJobinstance is needed.Create the instances:
// in MAIN: fbRunInformJob: ARRAY [0..GVL.MLX_UBOUND] OF MceRunInformJob;Add the function call:
// in MAIN.AutomaticMode (in the FOR loop, just below the fbPosTable call) // optional: connect hold button from MceStartStop GVL.stRunInformJobIO[nMLxNumber].bHoldRestart := GVL.stMceStartStopIO[nMLxNumber].bHoldRestart; fbRunInformJob[nMLxNumber]( offsetCmdByte := 0, offsetStsByte := 0, io := GVL.stRunInformJobIO[nMLxNumber], MLX := GVL.stMLX[nMLxNumber]);Now add PosTable custom actions for running the INFORM jobs:
// in MAIN.AutomaticMode (below the FOR loop, just below the fbPosTable call) // ----------------------------------------------------------------------------- // Custom actions // ----------------------------------------------------------------------------- FOR i := 0 TO GVL.MOTION_DEVICES_UBOUND DO + // init + GVL.stRunInformJobIO[i].bEnable := FALSE; IF GVL.stMcePosTableIO[i].bActionStart THEN CASE GVL.stMcePosTableIO[i].nActionID OF 20: // Close gripper GVL.stMcePosTableIO[i].bCustomActionDone := tGripperCloseTime[i].Q; 21: // Open gripper GVL.stMcePosTableIO[i].bCustomActionDone := tGripperOpenTime[i].Q; + 100..115: + // run INFORM job using MceRunInformJob (range: 0-15) + GVL.stRunInformJobIO[i].nJobNumber := INT_TO_USINT(GVL.stMcePosTableIO[i].nActionID - 100); + GVL.stRunInformJobIO[i].bEnable := TRUE; + GVL.stMcePosTableIO[i].bCustomActionDone := GVL.stRunInformJobIO[i].bDone; END_CASE ELSE GVL.stMcePosTableIO[i].bCustomActionDone := FALSE; END_IF
That’s all. Now you can call an INFORM job by just setting the nActionID of
a PosTable entry. Below example of the pick/place trajectory in
GenerateTrajectory shows how instead of closing the gripper with nActionID=20
the INFORM job 01 can be called with nActionID=101:
// in GenerateTrajectory (pick/place trajectory):
// -------------------------------------
// [1..10] = pick trajectory
// -------------------------------------
// pick approach
posTable.stEntry[1] := stTplFastPtpCart;
posTable.stEntry[1].nUserFrameNumber := 1; // user frame for pick
posTable.stEntry[1].aPosition[2] := fApproachHeight;
// pick
posTable.stEntry[2] := stTplFastLinCart;
posTable.stEntry[2].nUserFrameNumber := 1; // user frame for pick
- posTable.stEntry[2].nActionID := 20; // close gripper
+ posTable.stEntry[2].nActionID := 101; // run INFORM job 01
posTable.stEntry[2].nBlendFactor := 0; // exact position
posTable.stEntry[2].aPosition[2] := 0; // Z
// pick approach
posTable.stEntry[3] := posTable.stEntry[1];
posTable.stEntry[3].nMoveType := 1; // linear motion
Version history
0.1.0
Changes
- initial release
Overview
| kind | name | type | default | comment |
|---|---|---|---|---|
| input | offsetCmdByte | USINT | 0 | command byte offset in WritePacket.DigitalOutputs (0 to 7) |
| input | offsetStsByte | USINT | 0 | status byte offset in ReadPacket.DigitalInputs (0 to 7) |
| in_out | io | MceRunInformJobIO | interface data | |
| in_out | MLX | MLxData | MotoLogix shared memory variable |
Details
offsetCmdByte
USINTFor specifying location of the job interface command byte within the
8 byte of MLX.internalData.WritePacket.DigitalOutputs (range: 0 to 7).
- Make sure to adjust your CIO mapping to the location of this command byte.
offsetStsByte
USINTFor specifying location of the job interface status byte within the
8 byte of MLX.internalData.ReadPacket.DigitalInputs (range: 0 to 7).
- Make sure to adjust your CIO mapping to the location of this status byte.
Interface data for this function.
Check the data type for more information.
MLX
MLxDataThe MotoLogix variable which acts as the shared memory for a MotoLogix system.
Check the YaskawaMLx library for more information.
Source code
Declarations
(*Run INFORM jobs on MotoLogix system*)
FUNCTION_BLOCK MceRunInformJob
(*
* -----------------------------------------------------------------------------
* Name : MceRunInformJob
* Version : 0.1.0
* Date : 2026-02-24
* Author : deGroot
* Family : YaskawaMce
* Organisation : github.com/YaskawaEurope/mlx-examples
*
* -----------------------------------------------------------------------------
* Run INFORM jobs on MotoLogix system
* -----------------------------------------------------------------------------
*)
VAR_INPUT
offsetCmdByte : USINT := 0; (*command byte offset in WritePacket.DigitalOutputs (0 to 7)*)
offsetStsByte : USINT := 0; (*status byte offset in ReadPacket.DigitalInputs (0 to 7)*)
END_VAR
VAR_IN_OUT
io : MceRunInformJobIO; (*interface data*)
MLX : MLxData; (*MotoLogix shared memory variable*)
END_VAR
VAR
stCommStsByte : MceRunInformJobStsByte; (*communication: status byte*)
stCommCmdByte : MceRunInformJobCmdByte; (*communication: command byte*)
bAllConditionsOk : BOOL; (*external- and internal conditions ok*)
nJobNrFromStsByte : USINT; (*selected job number (0-15)*)
bOsrHoldRestart : BOOL; (*rising edge*)
bOsrPendantStartButton : BOOL; (*rising edge*)
bInterrupted : BOOL; (*interrupted by hold*)
nSmRunInformStateTime : DINT; (*elapsed time in current state [ms]*)
nSmHoldStateTime : DINT; (*elapsed time in current state [ms]*)
nRestartErrors : DINT; (*count time out errors for restart from hold*)
aOneShots : ARRAY [0..1] OF BOOL; (*bits for one shot signals*)
stStateMonitoringData : ARRAY [0..1] OF MceStateMonitoringData; (*monitor the elapsed time of a state*)
END_VAR
Logic
// -----------------------------------------------------------------------------
// common
// -----------------------------------------------------------------------------
// check ranges
io.nJobNumber := LIMIT(0, io.nJobNumber, 15);
offsetStsByte := LIMIT(0, offsetStsByte, 7);
offsetCmdByte := LIMIT(0, offsetCmdByte, 7);
// init
stCommCmdByte.bReset := FALSE;
stCommCmdByte.bRun := FALSE;
stCommCmdByte.bJobNumberOk := FALSE;
stCommCmdByte.bHold := FALSE;
// conditions
bAllConditionsOk :=
io.bExternalConditionsOk AND
MLX.Signals.MLXGatewayConnected AND
MLX.Signals.RemoteMode AND NOT
MLX.Signals.EStop1Pressed AND NOT
MLX.Signals.EStop2Pressed AND NOT
MLX.Signals.EStop3Pressed AND NOT
MLX.Signals.GuardCircuitOpen AND
(MLX.SystemState = 3);
// -----------------------------------------------------------------------------
// read status byte
// -----------------------------------------------------------------------------
MEMUtils.MemCpy(
pbyDest := ADR(stCommStsByte),
pbySrc := ADR(MLX.InternalData.ReadPacket.digitalInputs[0]) + offsetStsByte,
dwSize := 1);
// read active job number
nJobNrFromStsByte := 0;
nJobNrFromStsByte.0 := stCommStsByte.bJobNrBit0;
nJobNrFromStsByte.1 := stCommStsByte.bJobNrBit1;
nJobNrFromStsByte.2 := stCommStsByte.bJobNrBit2;
nJobNrFromStsByte.3 := stCommStsByte.bJobNrBit3;
// -----------------------------------------------------------------------------
// rising edge signals
// -----------------------------------------------------------------------------
bOsrHoldRestart := io.bHoldRestart AND NOT aOneShots[0];
aOneShots[0] := io.bHoldRestart;
bOsrPendantStartButton := stCommStsByte.bStartButton AND NOT aOneShots[1];
aOneShots[1] := stCommStsByte.bStartButton;
// -----------------------------------------------------------------------------
// State machine: run INFORM job
// -----------------------------------------------------------------------------
CASE io.nSmRunInform OF
// -------------------------------------
// idle, not ready for start
// -------------------------------------
0:
IF bAllConditionsOk THEN
io.nSmRunInform := 1;
END_IF;
// -------------------------------------
// idle, ready for start
// -------------------------------------
1:
IF bAllConditionsOk THEN
// start
IF io.bEnable THEN
io.nSmRunInform := 10;
END_IF;
ELSE
io.nSmRunInform := 0;
END_IF;
// -------------------------------------
// reset MASTER job (cursor to top)
// -------------------------------------
10:
stCommCmdByte.bReset := TRUE;
IF io.bEnable THEN
IF stCommStsByte.bIdle THEN
io.nSmRunInform := 20;
END_IF;
ELSE
io.nSmRunInform := 0;
END_IF;
// -------------------------------------
// start MASTER job
// -------------------------------------
20:
stCommCmdByte.bRun := NOT bInterrupted;
IF io.bEnable THEN
IF stCommStsByte.bBusy THEN
io.nSmRunInform := 30;
END_IF;
ELSE
io.nSmRunInform := 0;
END_IF;
// -------------------------------------
// running MASTER job
// -------------------------------------
30:
stCommCmdByte.bRun := NOT bInterrupted;
stCommCmdByte.bJobNumberOk := (nJobNrFromStsByte = io.nJobNumber);
IF stCommStsByte.bDone THEN
io.nSmRunInform := 40;
END_IF;
IF NOT io.bEnable AND NOT stCommStsByte.bBusy THEN
io.nSmRunInform := 0;
END_IF;
// -------------------------------------
// wait until no longer busy
// -------------------------------------
40:
IF io.bEnable THEN
IF NOT stCommStsByte.bBusy THEN
io.nSmRunInform := 50;
END_IF;
ELSE
io.nSmRunInform := 0;
END_IF;
// -------------------------------------
// done
// -------------------------------------
50:
IF NOT io.bEnable THEN
io.nSmRunInform := 0;
END_IF;
END_CASE;
// -----------------------------------------------------------------------------
// outputs
// -----------------------------------------------------------------------------
io.bIdle := (io.nSmRunInform = 0) OR (io.nSmRunInform = 1);
io.bRunning := (io.nSmRunInform = 30);
io.bDone := (io.nSmRunInform = 50);
io.bBusy := (io.nSmRunInform > 1) AND (io.nSmRunInform < 50);
// -----------------------------------------------------------------------------
// state monitoring
// -----------------------------------------------------------------------------
nSmRunInformStateTime :=
MceStateMonitoring(
nState := io.nSmRunInform,
bFreezeTimer := io.bIdle OR io.bHoldActive,
stateData := stStateMonitoringData[0]);
// -----------------------------------------------------------------------------
// State machine: handle hold/restart/abort
// -----------------------------------------------------------------------------
CASE io.nSmHold OF
// -------------------------------------
// idle
// -------------------------------------
0:
IF io.bRunning AND (bOsrHoldRestart OR NOT stCommStsByte.bBusy OR NOT io.bEnable) THEN
io.nSmHold := 10;
END_IF;
// -------------------------------------
// issue hold command
// -------------------------------------
10:
stCommCmdByte.bHold := TRUE;
IF NOT stCommStsByte.bBusy THEN
IF io.bEnable THEN
io.nSmHold := 20;
ELSE
io.nSmHold := 0;
END_IF;
END_IF;
// -------------------------------------
// hold active
// -------------------------------------
20:
stCommCmdByte.bHold := TRUE;
IF io.bEnable THEN
IF bOsrHoldRestart OR bOsrPendantStartButton THEN
io.nSmHold := 30;
END_IF;
ELSE
io.nSmHold := 0;
END_IF;
// -------------------------------------
// resume
// -------------------------------------
30:
IF stCommStsByte.bBusy OR NOT io.bEnable THEN
io.nSmHold := 0;
// back to hold state if start fails
ELSIF (nSmHoldStateTime > 100) THEN
nRestartErrors := nRestartErrors + 1;
io.nSmHold := 20;
END_IF;
END_CASE;
// -----------------------------------------------------------------------------
// outputs
// -----------------------------------------------------------------------------
bInterrupted := (io.nSmHold >= 10) AND (io.nSmHold <= 20);
io.bHoldActive := (io.nSmHold = 20);
// -----------------------------------------------------------------------------
// state monitoring
// -----------------------------------------------------------------------------
nSmHoldStateTime :=
MceStateMonitoring(
nState := io.nSmHold,
bFreezeTimer := (io.nSmHold = 0),
stateData := stStateMonitoringData[1]);
// -----------------------------------------------------------------------------
// write command byte
// -----------------------------------------------------------------------------
// write job number
stCommCmdByte.bJobNrBit0 := (io.nJobNumber AND 2#0000_0001) <> 0;
stCommCmdByte.bJobNrBit1 := (io.nJobNumber AND 2#0000_0010) <> 0;
stCommCmdByte.bJobNrBit2 := (io.nJobNumber AND 2#0000_0100) <> 0;
stCommCmdByte.bJobNrBit3 := (io.nJobNumber AND 2#0000_1000) <> 0;
MEMUtils.MemCpy(
pbyDest := ADR(MLX.InternalData.WritePacket.digitalOutputs[0]) + offsetCmdByte,
pbySrc := ADR(stCommCmdByte),
dwSize := 1);
Implementation
fbRunInformJob : ARRAY[0..0] OF MceRunInformJob;
// fbRunInformJob[0].offsetCmdByte := ;
// fbRunInformJob[0].offsetStsByte := ;
fbRunInformJob[0]( io := dummy, MLX := dummy );