💡

CODESYS Example Projectを調べてみた#7(Cartesian XY Chart)

2025/02/01に公開

Cartesian XY Chart

今回は、Cartesian XY Chartについて調べました。このプロジェクトでは、以下のトピックが扱われています。

  • Overlapping curves
  • Programming of curves
  • Zooming and panning

Setup

以前にくらべて、CODESYSのExample Projectのページが大幅に整理されていました。新しいサイトでは、Exampleのプロジェクトごとにカテゴライズされています。かなり見やすくなっていますね。ソースのダウンロードは、ページ下のDOWNLOADボタンから行えます。ボタンを押すとPackage Installerが起動します。

ライセンス条項を確認し、左下のチェックボックスにチェックを入れ、Agreeボタンを押下します。

ダウンロードが成功すると、CODESYS Examples > Visu Element XY Chart Example > 1.0.0.0 > Filesというフォルダに4つのプロジェクトファイルが保存されます。

プログラムの起動は、XYChartExample.projectファイルをダブルクリックすることで行えます。ファイルを開くと、1つのデバイスの下に3つのアプリケーションが設定されたプロジェクトとして見えます。ほかのファイルを開くと単独のアプリケーションが開かれます。複数アプリケーションを含むプロジェクトを保存するとこのような分類になるのかもしれないですが、未確認です。(知ってる方いれば教えてほしい。)

プログラムの内容

XYChartCurvesOverlapping

このプログラムを実行すると、下の画面が表示され、ボタンを押すと曲線の描画が始まります。Zoomボタンを押すと軸のスケールがZoomIn/Outします。適当にクリックしていると古い曲線がZoom前の軸で表示されたままの時があったのでバグがあるのかもしれません。チェックボックスをONにすると描画点に対する水平・垂直の線が追加で表示されます。

プログラムの処理は3つのファイルに実装されています。

Overlap(PRG)は、XY Chartの操作が行われています。このプログラム中の変数は、VisualizationのXY Chart Configurationと対応しています。具体的には、XY Chart > Extended variables > Element variableは、Overlap.xyChartに、XY Chart > Curves > Data Xは、Overlap.DataXに、Extended variables > Curve variableは、Overlap.Curveにマップされています。

変数の中にVisuStructXYChart, VisuStructXYChartAxis, VisuStructXYChartCurve型のものがあります。これらは、VisuElemXYChart(System)ライブラリで定義されている構造体です。XYChartを操作するには、これらの型の変数を操作するようです。

Overlap(PRG)
PROGRAM Overlap

VAR_INPUT
	xyChart: VisuStructXYChart;         // Variable for element configuration
	AxisX: VisuStructXYChartAxis;       // Variable for dynamic X axis configuration
	AxisY1 : VisuStructXYChartAxis;     // Variable for dynamic Y1 axis configuration
   Curve : VisuStructXYChartCurve;     // Variable for dynamic curve configuration

   // Arrays of data used for curves   
   DataX : ARRAY [1..200] OF REAL ;
   DataY : ARRAY [1..200] OF REAL ;
   
   iCurveHeapSize : INT := 4;    // Heap size
   iNumCurve : INT;              // Curves to display
   xStart : BOOL;          // Execution start/stop
   sText : STRING;         // text for button
   rOffset : REAL;         // Curve offset
   xShowTrace : BOOL;      // Show the tracing 
   xNotVisMsg : BOOL := TRUE;
   xChangeZoom : BOOL;
END_VAR

VAR
   xFirst : BOOL := TRUE;        // First entry in the loop
   xDoIt : BOOL;
   ePendingCmd : ENUM_CMD_PENDING := ENUM_CMD_PENDING.NO_CMD;  // There's a command pending
   rTrigStart :  R_TRIG;         // Trigger to detect start execution
   fTrigTimer :  F_TRIG;         // Trigger to detect square wave timer
   rTrigZoom :   R_TRIG;         // Trigger to detect zoom changing
   iCalc : INT;                  // Value for calculation
   iLoop : INT;                  // Loops already executed
   ix : INT;                     // General index 
   timer : TP ;                  // Timer for curve growing effect
   sampleTime : TIME := T#40MS;  // Timer value for curve growing effect
   xTimerEnd : BOOL;             // Bool for timer elapsing   
   rXValue : REAL;               // X value 
   rYValue : REAL;               // Y value 
   udiCycleCounter : UDINT;      // System cycle counter 
   udiCycleWait : UDINT;         // How many cycle to wait 
   tDelay : TON;
   xDelay : BOOL;
   xDelayEnd : BOOL;
   iActualZoom : INT;
END_VAR

//------------------------------------------------------------------
// Initial settings
IF xFirst THEN
   Curve.iCurveHeapSize := iCurveHeapSize;                  // We will maintain 4 curves in the heap
   Curve.eOverlap := VisuEnumXYChartCvOverlapType.OVERLAP;  // We will work in overlapping mode 
   Curve.diIndexMin := 1;                                   // We set the initial indexes to use
   Curve.diIndexMax := 1;
   iNumCurve := Curve.iCurveHeapSize;                       // We will display as many curves as the heap size
   xNotVisMsg := TRUE;
END_IF

//------------------------------------------------------------------
// We change the text of the button
IF xStart THEN
   sText := 'Stop drawing';
ELSE   
   sText := 'Start drawing';
END_IF   

//------------------------------------------------------------------
// We enable/disable the tracking of the curve drawing using level lines
xDoIt := FALSE;
IF xShowTrace THEN
   xDoIt := TRUE;
END_IF   
AxisX.aLevelLine[1].xEnable  := xDoIt;
AxisY1.aLevelLine[1].xEnable := xDoIt;

//------------------------------------------------------------------
// Detect the zoom cahnging 
rTrigZoom(CLK:=xChangeZoom, Q=>xDoIt);
IF xDoIt THEN
   xChangeZoom := xDoIt := FALSE;
   IF iActualZoom = 0 THEN
      iActualZoom := 1;
      AxisX.rMin := -5;
      AxisX.rMax := 20;
      AxisY1.rMin := 0;
      AxisY1.rMax := 10;
   ELSE   
      iActualZoom := 0;
      AxisX.rMin := -2;
      AxisX.rMax := 15;
      AxisY1.rMin := -1;
      AxisY1.rMax := 5;
	END_IF
END_IF   
 
//------------------------------------------------------------------
// We start the curve drawing 
rTrigStart(CLK:=xStart, Q=>xDoIt);
IF xDoIt THEN
   xDoIt := FALSE;
   // We clean up the arrays
   FOR ix := 1 TO 200 BY 1 DO
       DataX[ix] := 0.0;
       DataY[ix] := 0.0;
   END_FOR
   iCalc := 1;          // The initial calculation point of our curve
   iLoop := 0;          // No loop right now
   rOffset := 0.0;      // No curve offset right now
   
   // If we did a "start" and next a "stop", we can have some curves in the heap.
   // In this way we will remove them
   Curve.eCurveHeapCmd := VisuEnumXYChartCvHeapCmd.CLEAN;
   udiCycleCounter := VisuSync.udiCycleCounter;  // Save cycle counter 
   ePendingCmd := ENUM_CMD_PENDING.CLEAN;
   udiCycleWait := 10;                 // How many cycle to wait the command ending  
   xNotVisMsg := TRUE;
END_IF

//------------------------------------------------------------------
// Timer for data generation 
timer(IN:=xStart, PT:=sampleTime, Q=>xTimerEnd) ;
IF xTimerEnd = FALSE THEN
   timer(IN:=xTimerEnd, PT:=sampleTime, Q=>xTimerEnd) ;
END_IF ;
	
// Timer clock detection
fTrigTimer(CLK:=xTimerEnd, Q=>xDoIt) ;
IF xDoIt THEN
   xDoIt := FALSE;
   IF (ePendingCmd = ENUM_CMD_PENDING.NO_CMD) AND NOT xDelay THEN   // only if we don't have pending command
      
      // When arrive the tick of the clock
      // we do the curve calculation
      IF iCalc <= 126 THEN                            // If we are in the calculation limit
         FctGetXYValues(iCalc-1, rXValue, rYValue);   // Get new values  
         DataX[iCalc] := rXValue + rOffset;           // Store the value in the arrays
         DataY[iCalc] := rYValue + rOffset;
         Curve.diIndexMax := iCalc;                   // The last valid value in the array 
         iCalc := iCalc+1;                      
      ELSE                       // We have finish to calculate the curve
         xDelay := TRUE;         // Enable delay time to finish the display curve
		END_IF
      AxisX.aLevelLine[1].rValue  := DataX[Curve.diIndexMax];      // Update the position for curve tracking
      AxisY1.aLevelLine[1].rValue := DataY[Curve.diIndexMax];
   END_IF
END_IF

//------------------------------------------------------------------
// Test the delay time to finish the display curve 
tDelay(IN:=xDelay, PT:=T#2S, Q=>xDelayEnd);
IF xDelay THEN
   IF xDelayEnd THEN
      xDelay := FALSE;
      
      // We have finish to calculate the curve
      iCalc := 1;                            // Reset the calculation value
      iLoop := iLoop+1;                      // We are facing a new loop
      IF iLoop < iNumCurve THEN              // I've room again, update the curve !
         Curve.eCurveHeapCmd := VisuEnumXYChartCvHeapCmd.UPDATE;     // We update the curve
         ePendingCmd := ENUM_CMD_PENDING.UPDATE;  // Update pending
      ELSE                                   // Heap full
         iLoop := 0 ;                        // We will start with a neww loop
         Curve.eCurveHeapCmd := VisuEnumXYChartCvHeapCmd.CLEAN;     // We clean the heap   
         ePendingCmd := ENUM_CMD_PENDING.CLEAN;   // Clean 
         udiCycleWait := 50;                 // How many cycle to wait the command ending, to have time to see the last curve, before clean all 
         xNotVisMsg := FALSE;
      END_IF    
	END_IF
   udiCycleCounter := VisuSync.udiCycleCounter;  // save cycle counter 
END_IF

//------------------------------------------------------------------
// If there's a clean command pending, we wait some cycles
// to have the possibility to see the last curve
IF ePendingCmd = ENUM_CMD_PENDING.CLEAN THEN
   IF (ABS(VisuSync.udiCycleCounter - udiCycleCounter) > udiCycleWait) AND (Curve.eCurveHeapCmd = VisuEnumXYChartCvHeapCmd.NO) THEN
      iLoop := 0 ;                        // We will start with a neww loop
      rOffset := 0.0;                     // No curve offset
      ePendingCmd := ENUM_CMD_PENDING.NO_CMD;      
      xNotVisMsg := TRUE;
   END_IF
END_IF

//----------------------------------------------------------------------------------------
// If there's an update command pending, we simply wait the end of the command
IF ePendingCmd = ENUM_CMD_PENDING.UPDATE THEN
   IF Curve.eCurveHeapCmd = VisuEnumXYChartCvHeapCmd.NO THEN
      iCalc := 1;                         // Reset the calculation value
      rOffset := rOffset + 0.3;           // Next curve will have an offset
      Curve.diIndexMax := iCalc;          // Reset to init curve
      ePendingCmd := ENUM_CMD_PENDING.NO_CMD;      
      xNotVisMsg := TRUE;
   END_IF
END_IF

xFirst := FALSE;

FctGetXYValues(FUN)は、Overlap内で呼ばれる関数です。波形に使う新しい値を設定します。

FctGetXYValues(FUN)
FUNCTION FctGetXYValues : bool

VAR_INPUT
   iValue : INT;                     // Input value for calculation
   rXValue : REFERENCE TO REAL;      // X value 
   rYValue : REFERENCE TO REAL;      // Y value 
END_VAR

VAR
   rValue : REAL;
END_VAR

rValue  := INT_TO_REAL(iValue);
rValue  := rValue/10.0;
rXValue := rValue - 2.0*SIN(rValue);
rYValue := 2.0 - 2.0*COS(rValue);

VisuSync(PRG)は、カウンタのみ実装されています。

VisuSync(PRG)
PROGRAM VisuSync

VAR_INPUT
   udiCycleCounter : UDINT;
END_VAR

udiCycleCounter := udiCycleCounter +1;

XYChartProgrammingMode

先ほどの例と似ていますが、VisuStructXYChartをプログラミングモードで使う方法を示しています。プログラミングモードでは、XXXができます。プログラミングモードを使うには、eProg : VisuEnumXYChartProgType にMODIFY(既存Curveの編集)やNEW_CURVE(新しいCurveの編集)を設定します。

eCommand : VisuEnumXYChartCommandsにPRG_SAVEでプログラムされたデータを保存します。PRG_UNDO, PRG_UNDO_ALLを設定すればキャンセルできるようです。

Prog(PRG)
PROGRAM Prog

VAR_INPUT
	xyChart: VisuStructXYChart;         // Variable for element configuration
   
	AxisX: VisuStructXYChartAxis;       // Variable for dynamic X axis configuration
	AxisY1 : VisuStructXYChartAxis;     // Variable for dynamic Y1 axis configuration
   
   Curve1 : VisuStructXYChartCurve;    // Variable for dynamic curve 1 configuration

   // Arrays of data used for curves   
   DataX1 : ARRAY [1..100] OF REAL ;
   DataY1 : ARRAY [1..100] OF REAL ;

   iCurve1ProgType : INT;              // How to programm the curve 1, 0=modify existig, 1=create a new one
   xProgCurve1 : BOOL;                 // Start programming curve 1

   xProgOff : BOOL;                    // Stop programming
   xProgSave : BOOL;                   // Save the curve
   xProgUndoS : BOOL;                  // Undo single point   
   xProgUndo : BOOL;                   // Undo all the curve
   xSetOrigCurve : BOOL;               // Set original curves
   
   sProgClient : STRING;               // On which client programming is active
   xProgMode : BOOL;                   // Element is in programming mode   
   xChangePending : BOOL;              // Change is pending
   
   udiCycleCounter : UDINT;
END_VAR

VAR
   xInitDone : BOOL;             // If the initialization has been done correctly

   // Here we save the initial element configuration   
	SVxyChart : VisuStructXYChart;
	SVAxisX   : VisuStructXYChartAxis;
	SVAxisY1  : VisuStructXYChartAxis;
   SVCurve1  : VisuStructXYChartCurve;

   xDoIt            : BOOL;
   xDelayProgCurve1 : BOOL;
   xDelayProgOff    : BOOL;
   xDelayProgSave   : BOOL;
   
   rTrigProgCurve1 :  R_TRIG;    // Trigger to detect start programming curve 1 
   rTrigProgOff    :  R_TRIG;    // Trigger to detect stop programming
   rTrigOrig       :  R_TRIG;    // Trigger to detect set original curves
   rTrigUndoS      :  R_TRIG;    // Trigger to detect undo single point
   rTrigUndo       :  R_TRIG;    // Trigger to detect undo curve
   rTrigSave       :  R_TRIG;    // Trigger to detect save curve
END_VAR

//------------------------------------------------------------------
// During the first entry we do some initial activities
IF NOT xInitDone THEN
   IF ABS(VisuSync.udiCycleCounter - udiCycleCounter) > 2 THEN    // Valid when "Visu" is running
      // We save the initial element configuration for further uses
      SVxyChart := xyChart;
      SVAxisX   := AxisX;
      SVAxisY1  := AxisY1;
      SVCurve1  := Curve1;

      // We limit the use of the array
      Curve1.diIndexMin := 1 ;        
      Curve1.diIndexMax := 10 ;
      
      FctSetDefaultDataCurve();  // Set default data for curves   
      
      xInitDone := TRUE;         // done
   ELSE
      RETURN ;                   // We will do nothing if we don't' have the correct data          
	END_IF
END_IF


//------------------------------------------------------------------
// We start the programming on curve 
rTrigProgCurve1(CLK:=xProgCurve1, Q=>xDoIt);
IF xDoIt THEN
   xProgCurve1 := FALSE;
   rTrigProgCurve1(CLK:=xProgCurve1, Q=>xDoIt);
   IF xyChart.xProgMode = FALSE THEN                              // If it's not already in programming mode
      xyChart.wsFocus := SVCurve1.wsCvName;                       // We need to inform the element, what's the curve that has the focus 
      IF iCurve1ProgType = 0 THEN
         xyChart.eProg := VisuEnumXYChartProgType.MODIFY;         // We inform the element to enter in programming mode for exixting curve modification
      ELSE   
         xyChart.eProg := VisuEnumXYChartProgType.NEW_CURVE;      // We inform the element to enter in programming mode for a new curve modify
      END_IF
      udiCycleCounter  := VisuSync.udiCycleCounter;               // We save the actual cycle counter 
      xDelayProgCurve1 := TRUE;
   END_IF
END_IF

// We wait some cycles before to act in programming mode,
// to give to the element the time to activate the programming mode
IF xDelayProgCurve1 THEN
   IF xyChart.xProgMode THEN                                            // Command has been executed
      xDelayProgCurve1       := FALSE;
      Curve1.ePointStyle     := VisuEnumXYChartPointStyle.DOT;          // We set the visualization of the line using also dot  
      xyChart.eFocusDisplay  := VisuEnumXYChartFocusType.FOC_REV_COL;   // We show the axis that has the focus, with reverse colors on numbers
      xyChart.diCursorColorM := VisuElemXYChart.COLOR.Red;              // We change the color of the cursor
	ELSE
      IF ABS(VisuSync.udiCycleCounter - udiCycleCounter) > 100 THEN
         xDelayProgCurve1 := FALSE;                                     // Something went wrong 
      END_IF
   END_IF
END_IF


//------------------------------------------------------------------
// We stop the programming
rTrigProgOff(CLK:=xProgOff, Q=>xDoIt);
IF xDoIt THEN
   xProgOff := FALSE;
   rTrigProgOff(CLK:=xProgOff, Q=>xDoIt);
   xyChart.eProg   := VisuEnumXYChartProgType.NO;    // We inform the element to stop the programming mode for a new curve modify 
   udiCycleCounter := VisuSync.udiCycleCounter;      // We save the actual cycle counter 
   xDelayProgOff   := TRUE;
END_IF

// We wait some cycles before to exit from programming mode,
// to give to the element the time to update the visualization
IF xDelayProgOff THEN
   IF NOT xyChart.xProgMode THEN                                  // Command has been executed
      xDelayProgOff          := FALSE;
      Curve1.ePointStyle     := SVCurve1.ePointStyle;             // We change it to the original one
      xyChart.wsFocus        := SVCurve1.wsCvName;                // We set the focus as default on curve1 
      xyChart.eFocusDisplay  := SVxyChart.eFocusDisplay;          // We change it to the original one
      xyChart.diCursorColorM := SVxyChart.diCursorColorM;         // We change it to the original one    
	ELSE
      IF ABS(VisuSync.udiCycleCounter - udiCycleCounter) > 100 THEN
         xDelayProgOff := FALSE;                                  // Something went wrong 
      END_IF
   END_IF
END_IF


//------------------------------------------------------------------
// We save the curve 
rTrigSave(CLK:=xProgSave, Q=>xDoIt);
IF xDoIt THEN
   xProgSave := FALSE;
   rTrigSave(CLK:=xProgSave, Q=>xDoIt);
   xyChart.eCommand := VisuEnumXYChartCommands.PRG_SAVE;          // We inform the element to save the curvestop the programming mode for a new curve modify
   udiCycleCounter := VisuSync.udiCycleCounter;                   // We save the actual cycle counter 
   xDelayProgSave := TRUE;
END_IF

// We wait some cycles,
// to give to the element the time to save all the data
IF xDelayProgSave THEN
   IF xyChart.eCommand = VisuEnumXYChartCommands.NO THEN   // Command has been executed
      xDelayProgSave := FALSE;
      xProgOff := TRUE;                                    // We will do the same operations like programming off 
	ELSE
      IF ABS(VisuSync.udiCycleCounter - udiCycleCounter) > 100 THEN
         xDelayProgSave := FALSE;                          // Something went wrong 
      END_IF
   END_IF
END_IF


//------------------------------------------------------------------
// Undo single point 
rTrigUndoS(CLK:=xProgUndoS, Q=>xDoIt);
IF xDoIt THEN
   xProgUndoS := FALSE;
   rTrigUndoS(CLK:=xProgUndoS, Q=>xDoIt);
   xyChart.eCommand := VisuEnumXYChartCommands.PRG_UNDO;
END_IF


//------------------------------------------------------------------
// Undo all 
rTrigUndo(CLK:=xProgUndo, Q=>xDoIt);
IF xDoIt THEN
   xProgUndo := FALSE;
   rTrigUndo(CLK:=xProgUndo, Q=>xDoIt);
   xyChart.eCommand := VisuEnumXYChartCommands.PRG_UNDO_ALL;
END_IF


//------------------------------------------------------------------
// Set original curve 
rTrigOrig(CLK:=xSetOrigCurve, Q=>xDoIt);
IF xDoIt THEN
   xSetOrigCurve := FALSE;
   rTrigOrig(CLK:=xSetOrigCurve, Q=>xDoIt);

   // We set all the data as the original   
   xyChart := SVxyChart;
   AxisX   := SVAxisX;
   AxisY1  := SVAxisY1;
   Curve1  := SVCurve1;

   // We limit the use of the array
   Curve1.diIndexMin := 1 ;        
   Curve1.diIndexMax := 10 ;
   
   FctSetDefaultDataCurve();  // Set default data for curves   
END_IF


//------------------------------------------------------------------
// On line situation 
sProgClient := VisuElemXYChart.VisuFctGetClientName(xyChart.eClientProg);     // On which client programming is active
xProgMode := xyChart.xProgMode;                                               // Element is in programming mode
xChangePending := xyChart.xChangePending;                                     // Change is pending

XYChartZoomingPranning

後日、追記。

学びと気づき

  • VisuElemXYChartを使うと、標準設定だけでは物足りないカスタマイズ(例:複数軸の切り替えやアニメーション的な表示)をより細かく制御できる。
  • ただし、ライブラリが持つ属性が非常に多いため、目的ごとに公式ドキュメントを参照して段階的に理解するのがよい。

まとめ

  • XYChartをカスタムして使うには、VisuElemXYChartライブラリを使う。
  • VisuStructXYChart, VisuStructXYChartAxis, VisuStructXYChartCurve型の変数を作って操作する。

Discussion