MceRunInformJob

0.1.0

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

Important

Without this robot setup, the MceRunInformJob function cannot work.

General Purpose IO used in this example (referenced ones in bold):

I/O groupI/O numberAddressDescription
IG#200IN#1593#02000run job
IG#200IN#1594#02001confirm job number
IG#201IN#1601 to 1604#02010 to 02013job number (first 4 bits)
OG#200OUT#1593#12000job done
OG#201OUT#1601 to 1604#12010 to 12013job 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...
Note

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
Note

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 MASTER job contains the logic for calling the request sub job and handles the handshaking with the state machine in the MceRunInformJob function.
  • 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
Note

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:

  1. Key switch to TEACH
  2. Menu job » ctrl master » select » setting master job
  3. Now selecting your master job.
    set-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...
  1. 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 system
    
    Note

    Per MotoLogix system one MceRunInformJob instance is needed.

  2. Create the instances:

    // in MAIN:
    fbRunInformJob: ARRAY [0..GVL.MLX_UBOUND] OF MceRunInformJob;
    
  3. 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]);
    
  4. 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

  deGroot

Changes

Added:
  • initial release

Overview

kindnametypedefaultcomment
inputoffsetCmdByteUSINT0command byte offset in WritePacket.DigitalOutputs (0 to 7)
inputoffsetStsByteUSINT0status byte offset in ReadPacket.DigitalInputs (0 to 7)
in_outioMceRunInformJobIOinterface data
in_outMLXMLxDataMotoLogix shared memory variable

Details

offsetCmdByte

USINT
(default: 0)

For 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

USINT
(default: 0)

For 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

MLxData

The 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

Snippet of the function call:
fbRunInformJob : ARRAY[0..0] OF MceRunInformJob;

// fbRunInformJob[0].offsetCmdByte := ;
// fbRunInformJob[0].offsetStsByte := ;
fbRunInformJob[0]( io := dummy,  MLX := dummy );

Pages built with Hugo - 04 Mar 2026 16:05 CET