Messwerte von Start bis Ende der Messung nach Excel exportieren

Gizzl0r

Level-1
Beiträge
142
Reaktionspunkte
0
Zuviel Werbung?
-> Hier kostenlos registrieren
Guten Morgen,
ich versuche mich gerade daran 2 Variablen Werte, die sich während einer Messung verändern mit einem Zeitstempel in eine Excel Datei zu exportieren.
Dabei handelt es sich einmal um die Variable Widerstand und Druck.
Ich habe schon mal die Tutorials bzw. Hilfe Stellungen auf der Beckhoff Seite durchforstet und werde daraus nicht ganz schlau. Mit dem FB_FILEOPEN öffne bzw. erstelle ich eine Datei. Jedoch möchte ich das er für jede Messung eine Datei mit Zeitstempel und Datum erstellt. Wo er dann alles 2 ms die beiden Variablen reinschreibt. Das wie ich überflogen habe mit dem FB_FILEPUTS funktionieren soll. Wie kann ich jetzt aber meine beiden Variablen in einem String zusammenfassen?

Mit freundlichen Grüßen
Daniel
 
Der FB_FormatString ist Dein Freund. Mit dem kannst Du was auch immer in einem "Datensatz" zusammenfassen.
Wenn Du die Steuerzeichen (CR,LF) mit Concat anhängst kannst Du filewrite verwenden.
fileputs verwende ich nur für den Tabellenkopf.

Den Zeitstempel generierst Du aus dem Systemtimer. Sofern Du einen CX mit Flash-Memory hast, würde ich mir das gut überlegen mit der hohen Schreibfrequenz.
Ich habe das bei mir so gelöst, das ich die Werte im NovRAM sammle und jeweils mindestens 4kByte schreibe.
Sofern ein Datenverlust bei Netzausfall zu verschmerzen ist, kannst Du die Datensätze auch in einem Array of String sammeln und in einem Rutsch runterschreiben.
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
Hi,
ne die Messwerte sollen auf eine Externe Festplatte geschrieben werden die an der CX hängt. Wie kriege ich denn dann den String in die Excel Tabelle? Und wie erzeuge ich für jede Messung eine Excel Tabelle die als Dateiname z.b. 29_09_17_8_24 (dd_mm_yy_hh_min min) beinhaltet?
 
Und wie erzeuge ich für jede Messung eine Excel Tabelle die als Dateiname z.b. 29_09_17_8_24 (dd_mm_yy_hh_min min) beinhaltet?
Zur Klarstellung: vermutlich meinst Du keine Excel Tabelle sondern eine csv-Datei.
Den Dateiname wirst Du wohl selber zusammenstellen müssen, allerdings würde ich nicht so viele unnötige Trennzeichen in den Dateiname packen, und die Datumsteile anders sortieren und bei einstelligen Werten eine Vornull dazupacken, damit die Dateien in einem Ordner sinnvoll sortiert angezeigt werden können: yymmdd_hhmm.csv

Harald
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hi,
Danke schonmal für eure Hilfestellung.
@Chräshe
Wie wird dieser Puffer genau erstellt? Habe bis jetzt noch keine Erfahrung damit. Mir ist jetzt jedoch klar, wie dann die Lese/Schreibe Bausteine funktionieren. Ich bräuchte nur noch einen Anstoß wie ich diesen Puffer genau fülle.
Danke im Vorraus
 
Ein Puffer ist immer ein Array mit irgendwas. Im Falle einer lesbaren und excelimportierbaren .csv ein Array of byte oder ein String.
Ich hab grad ein älteres Testprogramm gefunden, mit dem ich bei einem CX9020 Werte gesammelt und gespeichert hab.
Typisch für mich: sparsam kommentiert.

Code:
(****************************************************************************************)
FUNCTION_BLOCK logger
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    systemzeit:FB_LocalSystemTime;
    mav_speed:FILTER_MAV_W;
    mav_strom:FILTER_MAV_W;
    mav_feuchte_rel:FILTER_MAV_W;
    mav_feuchte_abs:FILTER_MAV_W;
    mav_temp:FILTER_MAV_W;


    bastelstring:FB_FormatString;
    novram:FB_NovRamReadWriteEx;
    novram_to_buffer:FB_NovRamReadWriteEx;
    fbfileopen:FB_FileOpen;
    fbfileclose:FB_FileClose;
    fbfilewrite:FB_FileWrite;
    fbfileread:FB_FileRead;
    fbfileputs:FB_FilePuts;


    i_step:INT:=0;
    mav_n:UINT:=27;
    wsecold: WORD;
    dummy: BOOL;
    mav_rst: BOOL;
    wminold: WORD;

    zeitstempel:STRING(255);
    bwritenovram: BOOL;
    sverify: STRING(255);

    ncbread: UDINT;
    ncbwrite: UDINT;
    bbusy: BOOL;
    berror: BOOL;
    nerrid: UDINT;
    btest: BOOL;
    breadnovram: BOOL;
    cblen: UDINT:=37;
    w_divrest: INT;

    offcnt:UINT;

    crlf: STRING(5):='$R$N';
    aBuffer: ARRAY [1..5000] OF BYTE; (*bytepuffer für fileoperation*)
    b_readtobuffer: BOOL;
    bbusy1: BOOL;
    berror1: BOOL;
    nerrid1: UDINT;
    ncbread1: UDINT;
    sDateiname: STRING(150);
    s_kopf: STRING(255);
    openhandle: UINT;
    s_Datensatz_a: STRING(100);
    s_Datensatz_b: STRING(100);
    s_datensatz: STRING(255);

    cbwrite_vrfy: UDINT;
END_VAR

VAR_EXTERNAL

END_VAR




(* windows systemzeit auslesen und mit rtc synchronisieren *)
systemzeit(
    sNetID:= , (*default local*)
    bEnable:=TRUE ,
    dwCycle:=5 ,
    dwOpt:=1 ,
    tTimeout:= , (*default 5s*)
    bValid=> ,
    systemTime=> ,
    tzID=> );

(****************************************************************************************)
(*sekundenflanke *)

IF systemzeit.systemTime.wSecond <> wsecold THEN
(* hier alles rein, was zyklisch (3s) ausgeführt werden soll*)
    w_divrest:= systemzeit.systemTime.wSecond MOD 2;
    IF w_divrest = 0 THEN
        mav_speed(X:=DINT_TO_INT(speedsbm_stair) , N:=mav_n , RST:=mav_rst , Y=>);
        mav_strom(X:=Strom_presse , N:=mav_n , RST:=mav_rst , Y=> );
        mav_feuchte_rel(X:=rh_hmi , N:=mav_n , RST:=mav_rst , Y=> );
        mav_feuchte_abs(X:=dew_hmi , N:=mav_n , RST:=mav_rst , Y=> );
        mav_temp(X:=temp_hmi, N:=mav_n , RST:=mav_rst , Y=> );
END_IF;
END_IF;
wsecold:=systemzeit.systemTime.wSecond;
(****************************************************************************************)
mav_rst:=FALSE;
(*minutenflanke*)
IF wminold <> systemzeit.systemTime.wMinute THEN
(* hier alles rein, was minütlich ausgeführt werden soll*)
mav_rst:=TRUE;
(*datensatz zusammenbauen*)

    bastelstring(
        sFormat:='%2u.%2u.%4u;%2u:%2u;%5u;%5u;%5u;%5u;%5u;',
        arg1:=F_WORD(systemzeit.systemTime.wDay) ,
        arg2:=F_WORD(systemzeit.systemTime.wMonth) ,
        arg3:=F_WORD(systemzeit.systemTime.wYear) ,
        arg4:= F_WORD(systemzeit.systemTime.wHour),
        arg5:= F_WORD(systemzeit.systemTime.wMinute),
        arg6:=F_WORD(mav_speed.Y) ,
        arg7:=F_WORD(mav_strom.Y) ,
        arg8:=F_WORD(mav_feuchte_rel.Y),
        arg9:=F_WORD(mav_feuchte_abs.Y) ,
        arg10:=F_WORD(mav_temp.Y) ,
        bError=> ,
        nErrId=> ,
        sOut=>s_Datensatz_a);




    zeitstempel:= CONCAT(s_datensatz_a,crlf);     (*datensatz mit CRLF abschließen*)
    cblen:=LEN(zeitstempel);                (*datensatzlänge ermitteln*)
    bwritenovram:=TRUE;

s_kopf:='Datum;Zeit;speed;strom;feuchte_rel;feuchte_abs;Temperatur$N';
END_IF;
wminold:=systemzeit.systemTime.wMinute;
(****************************************************************************************)
(* NOVRAM speichern*)
novram(
    nDevId:= 1,
    bRead:=bwritenovram ,
    bWrite:=bwritenovram ,
    cbSrcLen:=cblen ,
    cbDestLen:=cblen ,
    pSrcAddr:=ADR(zeitstempel) ,
    pDestAddr:=ADR(sverify) ,
    nReadOffs:=noffset ,
    nWriteOffs:=noffset ,
    tTimeOut:=t#1s ,
    bBusy=>bbusy ,
    bError=>berror,
    nErrId=> nerrid,
    cbRead=>ncbread ,
    cbWrite=>ncbwrite );

IF bwritenovram AND NOT bbusy AND NOT berror AND NOT btest THEN
    IF zeitstempel = sverify THEN
        noffset:=noffset+cblen;
    END_IF;
    bwritenovram:=FALSE;
END_IF;
(***********************************************************************************************)

(***********************************)
IF noffset > 4900 THEN noffset:=128;END_IF;
IF noffset > 4500 THEN
    b_readtobuffer:=TRUE;
END_IF;

(************************************************************************************************)
novram_to_buffer(
    nDevId:= 1,
    bRead:=b_readtobuffer ,
    bWrite:= ,
    cbSrcLen:= ,
    cbDestLen:=(noffset-128) ,
    pSrcAddr:= ,
    pDestAddr:=ADR(aBuffer) ,
    nReadOffs:=128 ,
    nWriteOffs:= ,
    tTimeOut:=t#1s ,
    bBusy=>bbusy1 ,
    bError=>berror1 ,
    nErrId=>nerrid1 ,
    cbRead=>ncbread1 ,
    cbWrite=> );

IF b_readtobuffer AND NOT bbusy1 AND NOT berror1 AND ncbread1=noffset-128 AND i_step=0 THEN
    noffset:=128;
    b_readtobuffer:=FALSE;
    i_step:=10;
(*ELSIF b_readtobuffer AND NOT bbusy1 AND ( berror1 OR  ncbread1<>noffset-128) THEN
    b_readtobuffer:=FALSE;*)
END_IF;


(***********************************************************************************************)
(* state machine fileop *)
(***********************************)

CASE i_step OF

10: (************************************************************************************************************************)
(*dateiname zusammensetzen*)

bastelstring(
        sFormat:='Hard Disk\%2u%2u%4u.csv',
        arg1:=F_WORD(systemzeit.systemTime.wDay) ,
        arg2:=F_WORD(systemzeit.systemTime.wMonth) ,
        arg3:=F_WORD(systemzeit.systemTime.wYear) ,
        sOut=>sDateiname );

(*vorhandensein prüfen*)

fbfileopen(
    sNetId:= ,
    sPathName:=sDateiname ,
    nMode:=FOPEN_MODEREAD OR FOPEN_MODETEXT  ,
    ePath:= ,
    bExecute:=TRUE ,
    tTimeout:=t#1s ,
    bBusy=> ,
    bError=> ,
    nErrId=> ,
    hFile=> );

IF fbfileopen.bExecute AND NOT fbfileopen.bBusy THEN

    fbfileopen(
            sNetId:= ,
            sPathName:=sDateiname ,
            nMode:=FOPEN_MODEREAD OR FOPEN_MODETEXT  ,
            ePath:= ,
            bExecute:=FALSE ,
            tTimeout:=t#1s  );

    IF fbfileopen.bError AND  fbfileopen.nErrId=1804 THEN        (*file nicht vorhanden*)
         i_step:=20; (*sprung nach datei erstellen*)

    ELSIF NOT fbfileopen.bError THEN                            (*vorhanden und öffnen erfolgreich*)
        fbfileclose(
            sNetId:= ,
            hFile:=fbfileopen.hFile ,
            bExecute:=TRUE ,
            tTimeout:=t#500ms ,
            bBusy=> ,
            bError=> ,
            nErrId=> );
        IF fbfileclose.bExecute AND NOT fbfileclose.bBusy AND NOT fbfileclose.bError THEN
        fbfileclose(bExecute:=FALSE);
         i_step:=30; (*sprung nach datei zum schreiben öffnen*)
        END_IF;
    END_IF;
END_IF;

20: (********************************************************************************************************************)
(*file neu erstellen*)

fbfileopen(
    sNetId:= ,
    sPathName:=sDateiname ,
    nMode:= FOPEN_MODEWRITE OR FOPEN_MODETEXT ,
    ePath:= ,
    bExecute:=TRUE ,
    tTimeout:=t#1s ,
    bBusy=> ,
    bError=> ,
    nErrId=> ,
    hFile=>openhandle );

IF fbfileopen.bExecute AND NOT fbfileopen.bBusy THEN
    i_step:=21;
END_IF;

21:(*kopf schreiben*)
    fbfileopen(bExecute:= FALSE );
    fbfileputs(
    sNetId:= ,
    hFile:=openhandle ,
    sLine:=s_kopf ,
    bExecute:=TRUE ,
    tTimeout:= t#1s,
    bBusy=> ,
    bError=> ,
    nErrId=> );
IF fbfileputs.bExecute AND NOT fbfileputs.bBusy THEN
    i_step:=22;

END_IF;

22:(*datei schließen*)

fbfileputs(bExecute:=FALSE);
    fbfileclose(
    sNetId:= ,
    hFile:=openhandle ,
    bExecute:= TRUE,
    tTimeout:=t#500ms ,
    bBusy=> ,
    bError=> ,
    nErrId=> );

IF fbfileclose.bExecute AND NOT fbfileclose.bBusy (*AND NOT fbfileclose.bError *)THEN
        i_step:=23;
END_IF;

23:

fbfileclose(bExecute:=FALSE);
i_step:=30; (*sprung nach daten schreiben*)

30:(*datei zum schreiben (anhängen) öffnen*)

fbfileopen(
    sNetId:= ,
    sPathName:=sDateiname ,
    nMode:=FOPEN_MODEAPPEND OR FOPEN_MODEBINARY ,
    ePath:= ,
    bExecute:=TRUE ,
    tTimeout:=t#500ms ,
    bBusy=> ,
    bError=> ,
    nErrId=> ,
    hFile=>openhandle );
IF fbfileopen.bExecute AND NOT fbfileopen.bBusy THEN
    i_step:=31;
END_IF;

31:(*datei geöffnet; bytebuffer schreiben*)
fbfileopen(bExecute:= FALSE );
fbfilewrite(
    sNetId:= ,
    hFile:=openhandle ,
    pWriteBuff:=ADR(abuffer),
    cbWriteLen:=ncbread1 ,
    bExecute:= TRUE,
    tTimeout:=t#3s ,
    bBusy=> ,
    bError=> ,
    nErrId=> ,
    cbWrite=>cbwrite_vrfy );
IF fbfilewrite.bExecute AND NOT fbfilewrite.bBusy THEN
    i_step:=32;
END_IF;

32:
fbfilewrite(bExecute:=FALSE  );
fbfileclose(
    sNetId:= ,
    hFile:=openhandle ,
    bExecute:=TRUE ,
    tTimeout:=t#500ms ,
    bBusy=> ,
    bError=> ,
    nErrId=> );
IF fbfileclose.bExecute AND NOT fbfileclose.bBusy THEN
i_step:=33;
END_IF;

33:
fbfileclose(bExecute:=FALSE);

i_step:=0;
END_CASE;

In dem Code findest Du:

Das einsammeln von Werten und eine Mittelwertbildung,
Einmal pro Minute einen Mittelwert in einen formatierten String schreiben,
Den formatierten String in den Novram speichern,
ab einer Größe >4500byte den Novram-Puffer wieder in ein Array lesen und von dort auf die HDD schreiben (kritischer Moment, wollte ich noch umbauen, bedarfsdruck ist momentan aber gering ;) )
Täglich neue Datei,
Dateiname zusammenbasteln,
Tabellenkopf zusammenbasteln,
Datei auf vorhandensein prüfen,
wenn nicht, datei neu erstellen und kopfzeile schreiben,
wenn vorhanden dateensatz anhängen.
Kein Errorhandling.

Funktioniert so wie es hier steht. Die fehlenden Variablen sind global. Die überzähligen Variablen sind aus einer anderen Version und einfach nur nicht bereinigt.
Einmal Codeanalyse und Du solltest in der Lage sein, das nach Deinem Gusto umzubauen.
 
Zuletzt bearbeitet:
Danke @weißnix_
ich bin dein Code mal durchgegangen und versuche mir das seit heute morgen zu verinnerlichen.
Ich habe die Werte (GL_VAR.Widerstand und GL_VAR.KMZ_Anzeige) die sich mit dem Starten der Messung (GL_VAR.start_b) ändern. Nach erreichen der Nullposition wird GL_VAR.start_b auf 0 gesetzt und da soll auch die Messung bzw. befüllen des Array beendet werden und dann diese erfassten Daten in die .csv datei geschrieben werden.
Ich habe jetzt mal Testweise dein Array geändert: aBuffer: ARRAY [1..5000] OF REAL := [GL_VAR.Widerstand, GL_VAR.KMZ_Anzeige]; (*bytepuffer für fileoperation*)
Jedoch schreibt er keine Werte rein und erstellt auch keine Datei:

bastelstring(
sFormat:='D:\%2u%2u%4u.csv',
arg1:=F_WORD(systemzeit.systemTime.wDay) ,
arg2:=F_WORD(systemzeit.systemTime.wMonth) ,
arg3:=F_WORD(systemzeit.systemTime.wYear) ,
sOut=>sDateiname );


Ich muss zugeben das ich was Codesys angeht noch am Anfang stehe und mich freuen würde, wenn du mir da evtl. weiter helfen könntest.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Guten Tag,
so langsam macht sich Verzweiflung wegen meiner Unfähigkeit breit und ich würde euch nochmals um Hilfe bitten.
Ich habe es hinbekommen, die Datei auf der Festplatte zu erstellen. Jedoch kriege ich es nicht hin, die sich ändernden Messewerte (GL_VAR.Widerstand und GL_VAR.KMZ_Anzeige) zu Buffern nachdem die Taste GL_VAR.Start gedrückt wurde. Diese Daten sollen nach erfolgreicher Messung in die .csv datei exportiert werden.
Wie kriege ich meine REAL Messwerte fortlaufend in einen Buffer geschrieben?!?
Gibt es evtl. ein Tutorial? Das Testprogramm von Beckhoff, habe ich mir bereits angeschaut. Jedoch wird mir da nicht klar wie ich die Messwerte kontinuierlich erfasse nach drücken des Start Knopfes, geschweige denn diese Daten in die CSV zu bekommen......
Würde mich über jede Antwort freuen.
Danke Schön
 
Zum Puffern der Meßwerte brauchst Du ein Notizbuch/Logbuch, ganz allgemein eine Tabelle/Array für die Anzahl der mitzuschreibenden Meßwerte und eine Verwaltung, wie weit die Tabelle gefüllt ist.
Du legst Dir ein ARRAY OF REAL (oder ARRAY OF STRUCT) in der benötigten Größe an und einen Schreibzeiger (INT- oder UINT-Variable), wo Du Dir den Index merkst, welches der nächste freie Array-Platz ist (oder auf welchen Array-Platz zuletzt geschrieben wurde).
Bei jedem Aufzeichnungs-Ereignis schreibst Du den Meßwert an den durch den Schreibzeiger adressierten freien Platz und erhöhst den Schreibzeiger um 1 (setzt ihn auf den nächsten freien Platz). Nicht vergessen vor dem Schreiben zu prüfen, ob der Schreibzeiger noch einen Index innerhalb des Arrays adressiert!
Nachdem Du die Meßwerte in die CSV-Datei geschrieben hast, setzt Du den Schreibzeiger zurück auf den ersten Array-Platz/Eintrag.

Als Aufzeichnungs-Ereignis (Trigger) könntest Du einen Timer nehmen, oder bekommst Du vom Prozess ein Signal, wann der Meßwert gespeichert werden soll?

Harald
 
Guten Morgen,
habe mich mal hier im Forum an einem Code bedient. Kann mir anhand diesen Codes jemand erläutern, was ich machen muss um meine beiden Variablen nach dem Schritt (bWrite:=GL_VAR.start_b;) in einen Buffer zu Sammeln und um Sie dann in die .csv Datei zu exportieren?



Code:
FUNCTION_BLOCK WriteCSVVAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
bWrite : BOOL := FALSE;(* Rising edge starts program execution *)
sNetId : T_AmsNetId := ''; (* TwinCAT system network address *)
sFileName : T_MaxString := 'D:\Sensorauswertung.csv';(* CSV destination file path and name *)
sCSVLine : T_MaxString := '';(* Single CSV text line (row, record), we are using string as record buffer *)
sCSVField : T_MaxString := '';(* Single CSV field value (column, record field) *)
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) *)
MAX_CSV_COLUMNS :UINT :=1;
MAX_CSV_ROWS :UINT :=200;
MAX_CSV_FIELD_LENGTH :UINT:=250;
database : ARRAY[0..300, 0..1 ] OF STRING(250);
	Widerstand: STRING(255);
	Druck: STRING(255);
END_VAR


bWrite:=GL_VAR.start_b;
Widerstand := REAL_TO_STRING(GL_VAR.Widerstand);
Druck := REAL_TO_STRING(GL_VAR.KMZ_Anzeige);
CASE step OF


0: (* Button bWrite muss aktiviert werden *)
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( database[ nRow, nColumn ], 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 ));
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 := 0; (* Ready *)
END_IF
END_CASE
 
Zuviel Werbung?
-> Hier kostenlos registrieren
bwrite würde ich auf keinen Fall direkt an den Taster koppeln.

Code:
if GL_VAR.start_b and not btemp_start then bwrite:=true; end_if; (*bwrite mit der positiven Flanke am Taster setzen, reset von bwrite in der Schrittkette*)
btemp_start:=GL_VAR.start_b;

Der Code sammelt die beiden Variablen nicht in einem Puffer, sondern schreibt einen vorhandenen und gefüllten Puffer in eine Datei.
Vorher kommt noch der Schritt, Deine Variablen im Puffer zu sammeln. Das ergibt nur Sinn als z.B. Wertereihe. Sagen wir mal: 100 Werte ab Tasterdruck im 2 ms-Raster.
Dazu setzt Du die Zykluszeit für die Task auf 2 ms. Bei einem CX80xx oder 90xx würde ich in dieser schnellen Task nur das notwendigste Verpacken.

Code:
if GL_VAR.start_b and not btemp_start then b_sammeln:=true;  end_if; (*sammeln mit der positiven Flanke am Taster setzen, reset von  bsammeln in der Schrittkette*)
btemp_start:=GL_VAR.start_b;  (*Hilfsmerker für die Flanke*)

if not b_sammeln then 
                          row:=0; column:=0;
else
                          database[row,0]:=REAL_TO_STRING(GL_VAR.Widerstand);
                          database[row,1]:=REAL_TO_STRING(GL_VAR.KMZ_Anzeige);
                          row:=row+1;
end_if;

if row>=100 then b_sammeln:=false; end_if;

Ich hab es nicht getestet, der Code sollte aber ab Tastendruck 100 Wertepärchen im 2ms-Raster einsammeln.
Am Ende sollte also im Array von 0..99 jeweils ein Wert drinstehen. Jetzt ist erstmal die Voraussetzung für den Code in Deinem letzten Post geschaffen.
Das ergibt natürlich nur Sinn, wenn die Analogeingangskarten auch so schnell sind. Andernfalls wäre der Speichertakt auf die Aktualisierungszeit der Karten anzupassen.
Da wird dann wieder der Zeitstempel interessant.
Jetzt aber erstmal einen Schritt nach dem nächsten ;)

Möglicherweise hab ich die Aufgabe aber auch komplett mistvertanden :(

EDIT: Um wirklich immer aktuelle Werte von der Peripherie einzusammeln könntest Du z.B. das TXPDOToggle-Bit auswerten, welches durch eine Flanke aktualisierte Werte anzeigt.
 
Zuletzt bearbeitet:
@weißnix_
Wahnsinn. Du hast mir sowas von weiter geholfen!!! Danke.
Der schreibt jetzt die Daten in die DB und ich sehe sie auch :) Meine Frage ist jetzt sozusagen als AddOn zu der Geschichte. Der Wert GL_VAR.Widerstand hat ein Schwellenbereich. Das heißt, ich drücke mit einem Zylinder auf einen Prüfkörper mit 8.2kOhm Endwiderstand. Jetzt würde ich gerne das sobald der Widerstand sinkt von 8.2kOhm auf unter 5.5kOhm das er erst dann diese Datenin das Array aufnimmt. Weil er mir ja zur Zeit 3/4 des Array mit gleichen Inhalten füllt, was relativ unsinnig ist.
Zu deinem Beispiel zurück kommend welches du hier reingepostet hast. Wie würde da die Lösung aussehen? Weil in deinem Code ja auch der Variable Dateiname und der Zeitstempel bereits vorhanden sind.

Mit freundlichsten Grüßen
Daniel
 
Naja, jetzt hast Du einfach einen anderen Trigger für b_sammeln definiert.
Das solltest Du jetzt aber schaffen ;).

Außerdem:
Ich habe z.Zt. keine Lust, die dringend erforderliche Codeoptimierung in meinem Loggermodul durchzuführen. Wie groß ist also meine Lust, dieses Modul an Deine Aufgabe anzupassen?

Dieses Modul in dieser Form hilft Dir nur, wenn die Gefahr eines Datenverlusts während einer Langzeitmessung droht. In meinem Code wird bereits bei der Datenerfassung ein kompletter Datensatz formatiert. Das von Dir gefundene Codebeispiel formatiert ein Datenarray erst beim schreiben in's .csv-Format.
In meinem Code müßtest Du also das jetzt in #12/#13 gebildete Array umformatieren. In dem Codebeispiel von #11 sollte es direkt zu verwursten sein.
Hast Du denn jetzt die Bildung des Dateinamens im Griff?
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Habe jetzt nen Trigger für b_sammeln. Funktioniert auch Super.
Das mit dem Dateinamen habe ich jetzt auch hinbekommen :)

Vielen Danke nochmals. Hast mir ungemein weiter geholfen :D


EDIT: Wie kann ich den Speichertakt reduzieren? Das er z.B. nicht alle 2ms sondern alle 200ms einen Wert schreibt?
 
Zuletzt bearbeitet:
Das ist auch kein Problem. Allerdings mache ich das dann nicht mehr über die Taskzykluszeit.
Wir nehmen jetzt mal eine Taskzykluszeit von 10ms an.
Dann löse ich soetwas in etwa so:
Code:
var
cyclecount:int;
end_var

cyclecount:=cyclecount+1;
IF cyclecount=20 THEN   
    cyclecount:=0;
    (*hier jetzt den 200ms-Aufruf rein *)
end_if;

Das ganze kann man zu einem Aufrufverteiler ausbauen. Ich mach das meistens so, wenn ich z.B. Regler in einer Task habe und die Istwertaktualisierungszeit sehr viel größer als die Taskzykluszeit ist. Mehr als eine Task mache ich nur in seltenen Fällen.
Eine andere Variante wäre die Auswertung des Toggle-Bits der Analogkarte. Dann könnte aber u.U. die Erweiterung des Arrays auf 3 Spalten sinnvoll sein, um in der 3. Spalte den Zeitstempel unterzubringen.

Code:
var
xtoggle:bool;
xtoggleold:bool;
end_var

IF xtoggle xor xtoggleold THEN   

    (*hier jetzt den Aufruf rein *)
end_if;
xtoggleold:=xtoggle;
 
Zuletzt bearbeitet:
Code:
if GL_VAR.start_b and not btemp_start then b_sammeln:=true;  end_if; (*sammeln mit der positiven Flanke am Taster setzen, reset von  bsammeln in der Schrittkette*)
btemp_start:=GL_VAR.start_b;  (*Hilfsmerker für die Flanke*)

if not b_sammeln then 
                          row:=0; column:=0;
else
                          database[row,0]:=REAL_TO_STRING(GL_VAR.Widerstand);
                          database[row,1]:=REAL_TO_STRING(GL_VAR.KMZ_Anzeige);
                          row:=row+1;
end_if;

[COLOR="#FF0000"]if row>=100 then b_sammeln:=false; end_if;[/COLOR]
Den Index für den indizierten Zugriff auf ein Array prüft man nicht nach dem Zugriff sondern vorher.
Wenn "row" nicht noch woanders verwendet wird, dann reicht es, einfach die Programmzeile zu versetzen, z.B.:
Code:
if GL_VAR.start_b and not btemp_start then b_sammeln:=true;  end_if; (*sammeln mit der positiven Flanke am Taster setzen, reset von  bsammeln in der Schrittkette*)
btemp_start:=GL_VAR.start_b;  (*Hilfsmerker für die Flanke*)

[COLOR="#0000FF"]if row>=100 then b_sammeln:=false; end_if;[/COLOR]

if not b_sammeln then 
                          row:=0; column:=0;
else
                          database[row,0]:=REAL_TO_STRING(GL_VAR.Widerstand);
                          database[row,1]:=REAL_TO_STRING(GL_VAR.KMZ_Anzeige);
                          row:=row+1;
end_if;

Harald
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Das war keine Zugriffsprüfung sondern eine Abbruchbedingung.
Nachher kann auch vorher sein. So wie von mir plaziert prüft der Code nach der Berechnung vor dem Zugriff ;)

Im Prinzip hast Du aber Recht ;) , danke für die Ohrfeige.
 
Hi,
wie kann ich in dem Beispiel von oben den Inhalt des Arrays database wenn NOT b_sammeln einmalig leeren? Gibt es da ein Befehl im Twincat? z.b. clear Database [row,column];? :D
 
Also die gröbste Vorschlaghammer-Variante wäre mit MEMCPY o.ä. alles mit 0 überschreiben... bitte mit entsprechender Vorsicht anwenden...

Aber normalerweise geht man mit einer For-Schleife einmal alle Elemente ab, und initialisiert sie neu.
 
Zurück
Oben