# Beckhoff TwinCAT PLC - Alle 10 Sekunden Werte von Variablen in eine CSV-Datei



## mv08 (14 Juni 2013)

Hallo zusammen,

ich sitze derzeit daran ein für Profis sehr einfach Programm mi ST zu schreiben.
Leider verzweifel ich derzeit daran alle 10 Sekunden eine Zeile in eine CSV-Datei zu schreiben.
Was ich geschafft habe, ist das Schreiben eines Arrays in die CSV-Datei mit Hilfe der Anleitung von hier: http://infosys.beckhoff.com/content/1031/tcplclib_tc2_utilities/html/tcplclibutilities_csv_sample.htm?id=15741

Der entsprechende Programmcode ist dieser hier:

Die Variablen:

```
PROGRAM P_TextModeWrite
(* Writing of CSV file in text mode. None of the field value is containing any non-printing control characters like line feed, carriage return, quotation marks, semicolon... *)
VAR
    bWrite            : BOOL := FALSE;(* Rising edge starts program execution *)
    sNetId            : T_AmsNetId := '192.168.2.109.1.1';    (* TwinCAT system network address *)
    sFileName    : T_MaxString := '\Hard Disk\test\TextModeGen.csv';(* CSV destination file path and name *)
    sCSVLine        : T_MaxString := '';(* Single CSV text line (row, record), we are using string as record buffer (your are able to see created fields) *)
    sCSVField        : T_MaxString := '';(* Single CSV field value (column, record field) *)
    sTest            : T_MaxString :='DasTest';
    bBusy            : BOOL;
    bError            : BOOL;
    nErrId            : UDINT;
    nRow             : UDINT     := 0;(* Row number (record) *)
    nColumn        : UDINT     := 0;(* Column number (record field) *)
    hFile            : UINT        := 0;(* File handle of the source file *)
    step            : DWORD     := 0;
    fbFileOpen    : FB_FileOpen;(* Opens file *)
    fbFileClose    : FB_FileClose;(* Closes file *)
    fbFilePuts        : FB_FilePuts;(* Writes one record (line) *)
    fbWriter        : FB_CSVMemBufferWriter;(* Helper function block used to create CSV data bytes (single record line) *)


    database        : ARRAY[0..MAX_CSV_ROWS, 0..MAX_CSV_COLUMNS ] OF STRING(MAX_CSV_FIELD_LENGTH) := (* Source PLC database *)
    '0_0', '0_1', '0_2', '0_3', '0_4', '0_5',
    '1_0', '1_1', '1_2', '1_3', '1_4', '1_5',
    '2_0', '2_1', '2_2', '2_3', '2_4', '2_5',
    '3_0', '3_1', '3_2', '3_3', '3_4', '3_5',
    '4_0', '4_1', '4_2', '4_3', '4_4', '4_5',
    '5_0', '5_1', '5_2', '5_3', '5_4', '5_5';


END_VAR
```
Der Code:

```
CASE step OF
    0:    (* Wait for rising edge at bWrite variable *)
        IF bWrite THEN
            bWrite         := FALSE;
            bBusy         := TRUE;
            bError        := FALSE;
            nErrId        := 0;
            hFile        := 0;
            nRow         := 0;
            nColumn    := 0;
            step         := 1;
        END_IF


    1:    (* Open source file *)
        fbFileOpen(  bExecute := FALSE  );
        fbFileOpen(     sNetId := sNetId, sPathName := sFileName, nMode := FOPEN_MODEWRITE OR FOPEN_MODETEXT,(* Open file in TEXT mode! *)
                        ePath := PATH_GENERIC, bExecute := TRUE );
        step := 2;


    2:(* Wait until open not busy *)
        fbFileOpen( bExecute := FALSE, bError => bError, nErrID => nErrID, hFile => hFile );
        IF NOT fbFileOpen.bBusy THEN
            IF NOT fbFileOpen.bError THEN
                step := 3;
            ELSE(* Error: file not found? *)
                step := 100;
            END_IF
        END_IF


    3:(* Convert one PLC record to CSV format *)
        sCSVLine := '';
        fbWriter.eCmd := eEnumCmd_First;(* Write first field value *)
        IF nRow <= MAX_CSV_ROWS THEN


            FOR nColumn := 0 TO MAX_CSV_COLUMNS BY 1 DO


                sCSVField := STRING_TO_CSVFIELD( sTest, FALSE );(* TODO: Get field value from your application *)


                (* Add new field to the record buffer *)
                fbWriter(     pBuffer := ADR( sCSVLine ), cbBuffer := SIZEOF( sCSVLine ) - 1, putValue := sCSVField, pValue := 0, cbValue := 0,
                            bCRLF := ( nColumn = MAX_CSV_COLUMNS ) );(* bCRLF == TRUE => Write CRLF after the last field value *)
                IF fbWriter.bOk THEN
                    fbWriter.eCmd := eEnumCmd_Next;(* Write next field value *)
                ELSE(* Error *)
                    step := 100;
                    RETURN;
                END_IF


            END_FOR(* FOR nColumn := 0... *)


            (* FB_FilePuts adds allready CR (carriage return) to the written line.
            We have to replace the $R$L characters with $L character to avoid double CR. *)
            IF RIGHT( sCSVLine, 2 ) = '$R$L' THEN
                sCSVLine := REPLACE( sCSVLine, '$L', 2, LEN( sCSVLine ) - 1 );
            END_IF


            nRow := nRow + 1;(* Increment number of created records (rows) *)
            step := 4;(* Write record to the file *)


        ELSE(* All rows written => Close file *)
            step := 10;
        END_IF


    4:    (* Write single text line *)
        fbFilePuts( bExecute := FALSE );
        fbFilePuts( sNetId := sNetId, hFile := hFile, sLine := sCSVLine, bExecute := TRUE );
        step := 5;


    5:(* Wait until write not busy *)
        fbFilePuts( bExecute := FALSE, bError => bError, nErrID => nErrID );
        IF NOT fbFilePuts.bBusy THEN
            IF NOT fbFilePuts.bError THEN
                step := 3;(* Write next record *)
            ELSE(* Error *)
                step := 100;
            END_IF
        END_IF


    10:    (* Close source file *)
        fbFileClose( bExecute := FALSE );
        fbFileClose( sNetId := sNetId, hFile := hFile, bExecute := TRUE );
        step := 11;


    11:(* Wait until close not busy *)
        fbFileClose( bExecute := FALSE, bError => bError, nErrID => nErrID );
        IF ( NOT fbFileClose.bBusy ) THEN
            hFile := 0;
            step := 100;
        END_IF


    100: (* Error or ready step => cleanup *)
        IF ( hFile <> 0 ) THEN
            step := 10; (* Close the source file *)
        ELSE
            bBusy := FALSE;
            step := 101;    (* Ready *)
        END_IF


END_CASE
```

Nach meinem Verständnis müsste ich nun bei Step 3 die Schleife ändern und dann irgendwie alle 10 Sekunden eine neue Zeile in die Datei schreiben. Bei beiden vorhaben komme ich derzeit nicht weiter.
Könnt ihr mir weiterhelfen?
Vielen Dank!
LG,
Dennis


----------



## wonderfulworld (14 Juni 2013)

Anbei mal ne ganz schnelle schlampige Idee von mir. 
Vielleicht hiflts ja weiter. Ist ungetestet, aber ich denke die Idee ist klar.
Viel Spaß damit



mv08 schrieb:


> ```
> 3:(* Convert one PLC record to CSV format *)
> sCSVLine := '';
> fbWriter.eCmd := eEnumCmd_First;(* Write first field value *)
> ...



Hi zuerst würde ich mir mit einem Ton eine weiterschaltbedingung machen, dass ich weitermachen darf:


```
MyWaitTON(IN:=startNextColumn , PT:=t#10ms , Q=> , ET=> );
```

Dann die Statemachine vielleicht so aufdrösseln


```
3: (*Starte die Zeit an deinem Ton*)
        startNextColumn := TRUE;
        step := 4;

    4: (* Convert one PLC record to CSV format *)
        sCSVLine := '';
        fbWriter.eCmd := eEnumCmd_First;(* Write first field value *)
        nColumn := 0;
        IF nRow <= MAX_CSV_ROWS THEN
           step := 5;
        ELSE
            step := 10;
        END_IF
      5: (**)
               startNextColumn := TRUE;
               sCSVField := STRING_TO_CSVFIELD( sTest, FALSE );(* TODO: Get field value from your application *)
                (* Add new field to the record buffer *)
                fbWriter(     pBuffer := ADR( sCSVLine ), cbBuffer :=  SIZEOF( sCSVLine ) - 1, putValue := sCSVField, pValue := 0, cbValue :=  0,
                            bCRLF := ( nColumn = MAX_CSV_COLUMNS ) );(*  bCRLF == TRUE => Write CRLF after the last field value *)
                IF fbWriter.bOk THEN
                    fbWriter.eCmd := eEnumCmd_Next;(* Write next field value *)
                ELSE(* Error *)
                    step := 100;
                    RETURN;
                END_IF
         6: IF  nColumn < TO MAX_CSV_COLUMNS BY
              nColumn := nColumn +1;
              step := 7;
             ELSE
              step 8=;
             END_IF
           
         7: IF MyWaitTON.Q THEN
               startNextColumn := FALSE;
               step := 5;
             END_IF




           8: (* FB_FilePuts adds allready CR (carriage return) to the written line.
            We have to replace the $R$L characters with $L character to avoid double CR. *)
           IF RIGHT( sCSVLine, 2 ) = '$R$L' THEN
                sCSVLine := REPLACE( sCSVLine, '$L', 2, LEN( sCSVLine ) - 1 );
            END_IF


            nRow := nRow + 1;(* Increment number of created records (rows) *)
            step := 4;(* Write record to the file *)


        ELSE(* All rows written => Close file *)
            step := 10;
        END_IF
```

EDit!!!
Ups ich habe gerade gesehen, dass du die Zeilen, nicht die Spalten alle 10ms schreiben willst. Aber vielleicht siehst du ja jetzt ungefähr wie es geht.


----------



## wonderfulworld (15 Juni 2013)

Hier jetzt nochmal ne anständige Lösung (natürlich auch ungetestet, aber die Idee müsste klar sein).

Der Code:

```
(*neues ton einfügen. Das TON geht erst nach 10s an, wenn die Eingangsvaribele IN TRUE ist*)
startNewRowTON(IN:= startNewRow, PT:=t#10s , Q=> , ET=> );

CASE step OF
....

    3:(* Convert one PLC record to CSV format *)
(*-------------> Hier wird die Zeit gestartet, bei der du in das ton reinschreiben darfst.*)
        startNewRow := TRUE;
                        ....
    
   4:    (* Write single text line *)
                        ...


    5:(* Wait until write not busy *)
        fbFilePuts( bExecute := FALSE, bError => bError, nErrID => nErrID );
        IF NOT fbFilePuts.bBusy THEN
            IF NOT fbFilePuts.bError 
(*erst weiterschalten, wenn die 10ms abgelaufen sind*)
           AND startNewRowTON.Q
            THEN
(*startNewRow zurücksetzten, weil sonst das Ton nicht mehr bei 0 startet*)
                startNewRow := FALSE;
                step := 3;(* Write next record *)
            ELSE(* Error *)
                step := 100;
            END_IF
        END_IF
                       ...
END_CASE
```

Hoffe das hilft. Bei Fragen einfach fragen. 
Gruß
wonderfulworld


----------



## mv08 (16 Juni 2013)

wonderfulworld schrieb:


> Hoffe das hilft. Bei Fragen einfach fragen.
> Gruß
> wonderfulworld



Hi,

vielen Dank!
Ich werde das nach meinem Urlaub direkt mal testen. Wenn noch Fragen sind, melde ich mich.

LG,
Dennis


----------



## mv08 (3 Juli 2013)

Leider scheint der TON bei mir nicht zu greifen. Er rast einfach ohne 10s Pause durch die Schleifen:

ganz oben:

```
startNewRowTON(IN:= startNewRow, PT:=t#10s , Q=> , ET=> );

CASE step OF
	0:	(* Wait for rising edge at bWrite variable *)
.
.
.
```

und im Code:

```
5:(* Warten, wenn der Writer noch beschäftigt ist *)
		fbFilePuts( bExecute := FALSE, bError => bError, nErrID => nErrID );
		IF NOT fbFilePuts.bBusy THEN
			IF NOT fbFilePuts.bError THEN
			IF  zaehler < 10 THEN
			startNewRow := TRUE;
			(*erst weiterschalten, wenn die 10s abgelaufen sind*)
	        		startNewRowTON.Q;
			(*startNewRow zurücksetzten, weil sonst das Ton nicht mehr bei 0 startet*)
                		startNewRow := FALSE;
			zaehler := zaehler +1;
			step := 4;
			ELSE (*Fertig, jetzt Datei schließen*)
				step := 10;
			END_IF
			ELSE(* Error *)
				step := 100;
			END_IF
		END_IF
```

Kannst du einen Fehler erkennen?

Vielen Dank!
Dennis


----------



## Mensetta (3 Juli 2013)

```
IF  zaehler < 10 THEN
	startNewRow := TRUE; 
	startNewRowTON.Q;
           startNewRow := FALSE;
...
```

Da ist dein Fehler, dein TON Aufruf ganz oben bekommt nie ein StartNewRow := TRUE da du die Variable in jedem Zyklus zwar auf True, aber direkt danach wieder auf False setzt. Den Sinn deiner Zähler-Variable verstehe ich auch nicht ganz!?
Hätte jetzt erwartet, dass step := 10 durchgeführt wird sobald startNewRowTON.Q auf True geht oder?


----------



## mv08 (3 Juli 2013)

Den Zähler kannst du ignorieren, der war nur zum Test drinnen, damit ich keine Schleife habe.
Aber jetzt stehe ich mit dem TON an der Stelle auf dem Schlauch. Wie müsste denn der Quellcode aussehen, damit das TON funktioniert?


```
5:(* Warten, wenn der Writer noch beschäftigt ist *)
		fbFilePuts( bExecute := FALSE, bError => bError, nErrID => nErrID );
		IF NOT fbFilePuts.bBusy THEN
			IF NOT fbFilePuts.bError THEN
			IF  zaehler < 10 THEN
			startNewRow := TRUE;
			(*erst weiterschalten, wenn die 10s abgelaufen sind*)
	        		startNewRowTON.Q;
			(*startNewRow zurücksetzten, weil sonst das Ton nicht mehr bei 0 startet*)
                		startNewRow := FALSE;
			zaehler := zaehler +1;
			step := 4;
			ELSE (*Fertig, jetzt Datei schließen*)
				step := 10;
			END_IF
			ELSE(* Error *)
				step := 100;
			END_IF
		END_IF
```


----------



## Mensetta (3 Juli 2013)

so müsste es klappen

```
tonDelay(IN := xStartDelay, PT := T#10MS);
xStartDelay := NOT tonDelay.Q;
IF tonDelay.Q THEN
	//Anweisungen, welche nach 10 sekunden erfolgen sollen
END_IF
```


----------



## StructuredTrash (3 Juli 2013)

Es muss heissen

```
IF startNewRowTON.Q
THEN
   startNewRow:=FALSE;
   zaehler:=zaehler+1;
   step:=4;
END_IF
```
Und das

```
startNewRow:=TRUE;
```
muss in Step 4.


----------



## mv08 (3 Juli 2013)

Danke Mensetta. Das hat wunderbar geklappt


----------



## Mensetta (3 Juli 2013)

Nicht Dafür


----------



## siegener19 (28 April 2016)

Bei diesem Programm vom Beckhoff bin ich gerade auch. (Erster Beitrag). 
Ich habe 10 Werte (Real, INT,...) und würde diese gerne in die CSV Datei schreiben, in etwa so:

'Messdaten1', 'Strom', Spannung, 'P', Q, 'W,   
 'Messdaten2', '1_1', '1_2', '1_3', '1_4', '1_5',
 'Messdaten3', '2_1', '2_2', '2_3', '2_4', '2_5',
 'Messdaten4', '3_1', '3_2', '3_3', '3_4', '3_5',
 'Messdaten5', '4_1', '4_2', '4_3', '4_4', '4_5',
 'Messdaten6', '5_1', '5_2', '5_3', '5_4', '5_5';

Strom, Spannung usw. berechne ich extern und schreibe das in dieses Array. Ich hoffe jemand kann mir einen Tipp geben. Danke.


----------

