CODESYS Example Projectを調べてみた#7(Cartesian XY Chart)
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を操作するには、これらの型の変数を操作するようです。
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内で呼ばれる関数です。波形に使う新しい値を設定します。
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)は、カウンタのみ実装されています。
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を設定すればキャンセルできるようです。
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