# Empfehlung für Messwerterfassungssystem mit >100 analogen Eingängen



## Devil120 (30 Juli 2020)

Moin, 

ich bin neu hier im Forum und auch neu in der Materie der Messwerterfassung. Etwas SPS-Programmiererfahrung besteht aber.

Es geht um Folgendes Projekt. Als Bachelorarbeit habe ich die Aufgabe, ein System für einen Prüfplatz zusammen zu stellen und eine erste Messwertaufnahme mit einem kleinen Anteil der später  benötigten Sensoren zu ermöglichen.

Das Ziel ist im Anschluss eine Messwertaufnahme von 10 S/s für folgende Sensorsignale zu gewährleisten:

- 100 x Thermoelement Typ K
- 4 x Druck
- 4 x Infrarot (Temperatur)
- 2 x Helligkeit
- 1 x Differenzdruck
- 1 x Luftfeuchte

(hatte mir überlegt, alle Sensoren (bis auf die Thermoelemente) als Strom-Ausführung zu nehmen (4..20mA)) 

Rahmenbedingungen:

- soll für eine spätere Implementierung in LabView geeignet sein (evtl. starte ich auch schon direkt in LabView, dass ist noch nicht ganz sicher)
- alle Daten mit dem selben Zeitstempel
- zentrale oder dezentrale Speicherung ist egal
- eine Kamera soll ebenfalls mitlaufen, die den selben Zeitstempel besitzt
- eine besonders große Messgenauigkeit ist nicht von Nöten
- eine spätere Einbindung von Aktoren soll möglich sein
- eine Messdatenaufbereitung (Visualisierung) muss grundsätzlich während der Messung möglich sein

Nun habe ich mich schon eine geraume Zeit durch das Internet gewühlt und nach geeigneten Messsystemen gesucht.

Näher in Betracht gezogen habe ich bisher Beckhoff (EtherCAT, TwinCAT3) und die NI eigene Hardware, bei der ich die Messwertaufnahme direkt in LabView realisieren würde.
Allerdings halten sich meine Erfahrungen mit LabView in Grenzen und ich weiß nicht, ob das den zeitlichen Rahmen für meine Arbeit sprengt. 

Meine Frage nun an euch - hat jemand schon einmal ein ähnliches System konzipiert und realisiert, sodass er mir einen Tipp bei der Auswahl geben kann? 
Gibt es eine Möglichkeit, die Messwertspeicherung in FUP zu programmieren, oder komme ich im Fall von Beckhoff nicht um C++ herum? 

Die Infos sind wahrscheinlich noch Lückenhaft, also haut bitte einfach raus, was ihr dazu noch wissen müsst.


Vielen Dank erst einmal im Voraus! 

Viele Grüße


----------



## wollvieh (30 Juli 2020)

Twincat3, CX20xx Controller, 200 Analogkanäle skalieren und via OPC UA oder ADS Labview zur Verfügung stellen, zusätzlich könntest Du die Werte  alle x Millisekunden in eine CSV Datei schreiben, klingt nach einer lösbaren Aufgabe.


----------



## oliver.tonn (30 Juli 2020)

Bezüglich der Anforderung "Alle Daten mit dem selben Zeitstempel" müsstest Du, wenn es denn Beckhoff werden soll, die Distributed Clock Funktionalität nutzen.


----------



## Devil120 (30 Juli 2020)

Vielen Dank für die Antworten! Das klingt schon mal gut. Mir gefällt die Beckhoff-HW auch ganz gut. Vor allem die einfache Erweiterbarkeit ist klasse.

Benötige ich den CX20XX-Controller oder könnte ich auch einen EK9000 nehmen und über ein Notebook oder einen IPC die Steuerung laufen lassen?


----------



## oliver.tonn (30 Juli 2020)

Ich sag mal nein, denn der hat einen Modbus-Anschluss. Wenn Du tatsächlich die SPS auf einem "großen" PC laufen lassen möchtest und nicht auf einer CX (z.B. CX20XX, CX51XX) dann solltest Du die EK1100 nehmen. Du musst dann nur darauf achten, dass Dein PC eine Netzwerkkarte mit Intel-Chipsatz hat.


----------



## Benjamin (30 Juli 2020)

Die Fragestellung kann schon noch weiter aufgemacht werden. Ist die Verwendung von Kompontenen aus der Industrie-Automation geplant? Dann ist der Weg über Beckhoff ein gangbarer. Alternativen gibt es natürlich hier auch viele:

 Siemens Simatic
 WAGO
 CoDeSys + kompatible Produkte (kann preislich attraktiv werden)
Wichtig ist es dann hier die Randbedingungen genau zu beachten. Wie schon angesprochen die Messrate (~ 10 / s) und Zeitstempelung. Bei der Zeitstempelung gibt es dann schnell unterschiede in der Auflösung. Reichen hier +- 50 ms oder muss das im us-Raster genau sein?

Wenn LabView schon angesprochen worden ist, dann ist die Fragestellung nach der Datenspeicherung in den SPS-Komponenten eher unwichtig. Diese werden dann eher nur als reine Eingabe-/Ausgabemodule betrachtet und die Programmierung hier reduziert sich auf ein Minimum.

Auf der anderen Seite des Spektrums gibt es dann die klassischen Messdatenerfassungen:

NI
HBM
Delphin > Mein Lieblingsprodukt für Messungen während einer IBN
...
Bei den "richtigen" Messdatenerfassungen sind die Möglichkeiten der Messdatenspeicherung, Analyse und Archivierung schon besser vorbereitet, wie dies bei einer SPS-Automatisierungslösung ist. Allerdings sind die angesprochenen > 100 Thermoelemente natürlich schon eine Ansage in der Anzahl der normalerweise recht teuren Module.

Allgemein kann man natürlich anmerken, dass die alten Grenzen zwischen SPS und Messdatenerfassung natürlich beständig schwinden.


----------



## Devil120 (30 Juli 2020)

Danke für deine Anregung. Die Genauigkeit der Messwerte ist nicht besonders wichtig, genauso die Genauigkeit des Zeitstempels. 50 ms reichen vollkommen aus.

Bei NI ist man alleine mit der Hardware schon bei über 11k€ + Software, weshalb ich da wahrscheinlich Schwierigkeiten bekomme, diese Summe zu rechtfertigen. Wenn das alles mit Beckhoff HW realisierbar ist, bin ich absolut zufrieden. 

Mein Plan wäre jetzt, dass ein Notebook oder ein IPC direkt in den Schaltschrank kommt und die Funktion des Controllers übernimmt. Die Daten können dann dort gespeichert und anschließend ausgewertet werden.


----------



## Blockmove (30 Juli 2020)

Mit Beckhoff-Hardware machst du schon mal nix verkehrt.
Du bist modular erweiterbar und Ethercat ist echtzeitfähig.
Bei den Themen Speicherung und Weiterverarbeitung bist du dann flexibel.


----------



## MasterOhh (30 Juli 2020)

Ich würde anstatt der CX20xx Reihe eher die CX51xx Reihe empfehlen. Eine CX5130 z.B. sollte mehr als ausreichend sein. Falls du dir unsicher bist, wende dich einfach mit deinem konkreten Anwendungsfall an den Beckhoff Vertrieb, der für deine Region zuständig ist.


----------



## Devil120 (14 September 2020)

Hallo Leute,
ich möchte gern eine kurze Rückmeldung geben und gleichzeitig nochmal zwei kurze Fragen stellen.

Habe jetzt ein System von Beckhoff hier stehen, was auch echt gut funktioniert. Es läuft soweit, dass die Messwerte alle 10 ms in eine CSV-Datei geschrieben werden.
Jedoch nur, wenn die CPU keine anderweitige Auslastung erfährt. So ergeben sich manchmal Sprünge von 11 ms bis ca. 17 ms zwischen den Messwerten, sodass ich nicht immer auf 100 Messwerte pro Sekunde komme. Es ist ein i7-6700TE, der Fehler liegt also eher in der Parametrierung bzw. in der Software.

- Hat jemand eine Ahnung wie ich ein festes (wirklich bombenfestes) Zeitintervall zum Schreiben setzen kann?

- Außerdem habe ich es erst einmal so realisiert, dass ich alle Messwerte mit 100 S/s in die CSV-Datei schreibe. Die Anforderung ist es, dass alle Messwerte mit min. 10 S/s erfasst werden und der Druck mit 100 S/s. Leider habe ich keine gute Idee gehabt, die Messwerte mit unterschiedlicher Frequenz aufzunehmen. Auch die CSV-Datei ist dann natürlich nicht einheitlich. 
Falls jemand von euch auch schon einmal ähnliches umgesetzt hat - wie habt ihr das gelöst?

Vielen herzlichen Dank und einen guten Start in die neue Woche


----------



## oliver.tonn (14 September 2020)

Devil120 schrieb:


> Hat jemand eine Ahnung wie ich ein festes (wirklich bombenfestes) Zeitintervall zum Schreiben setzen kann?


Ich behaupte mal gar nicht. Die Dateibefehle laufen im Hintergrund ab, immer wenn Zeit ist und nicht in Echtzeit und da hat man meine ich auch keinen Einfluss drauf.
Was Du machen könntest wäre die Daten in etwas größeren Portionen zwischen zu puffern und dann weg zu schreiben, denn die Zeit die der Schreibvorgang benötigt steigt nicht proportional mit der Datenmenge.


----------



## JesperMP (14 September 2020)

Wie wird der Zeitstempel erzeugt ? Durch den SPS, oder durch Windows ?


----------



## Devil120 (14 September 2020)

ich hole mir die Zeit aus Windows mit NT_GetTime

EDIT: Ich glaube, dass Windows das Problem ist. Wenn irgendwelche Prozesse mit höherer Priorität (außerhalb von TwinCAT) aufgerufen werden, wird der Ablauf kurz unterbrochen.
Ich müsste mir also eine Software zur Festsetzung der Prioritäten in Windows besorgen und es damit mal versuchen, oder?


----------



## Heinileini (14 September 2020)

Devil120 schrieb:


> Es läuft soweit, ... Jedoch nur, wenn die CPU keine anderweitige Auslastung erfährt. So ergeben sich manchmal Sprünge von 11 ms bis ca. 17 ms zwischen den Messwerten, sodass ich nicht immer auf 100 Messwerte pro Sekunde komme.
> - Hat jemand eine Ahnung wie ich ein festes (wirklich bombenfestes) Zeitintervall zum Schreiben setzen kann?
> 
> - Außerdem habe ich es erst einmal so realisiert, dass ich alle Messwerte mit 100 S/s in die CSV-Datei schreibe. Die Anforderung ist es, dass alle Messwerte mit min. 10 S/s erfasst werden und der Druck mit 100 S/s. Leider habe ich keine gute Idee gehabt, die Messwerte mit unterschiedlicher Frequenz aufzunehmen. Auch die CSV-Datei ist dann natürlich nicht einheitlich.


Zu Verschiebungen kommt es "automatisch" bei wechselnder Austastung der CPU. Am besten dafür sorgen, dass anderweitige Auslastungen im Rahmen bleiben. Halbwegs "bombenfest" geht nur, wenn keine anderweitigen Auslastungen auftreten und ausserdem genügend "Luft" bleibt, den schnellen Zyklus regelmässig vor Beginn des nächsten Zyklus zu beenden.

Du könntest ein Array mit den 10 Druckwerten (plus Zeitstempeln?) pro 100 ms auffüllen und die übrigen Messwerte alle 100 ms erfassen und alles zusammen alle 100 ms in die CSV-Datei schreiben.
Der Satzaufbau wäre dann in der CSV immer gleich - und die Belastung durch das Wegschreiben der Daten in die CSV verteilt sich dann hoffentlich so, dass der Jitter der alle 10 ms erfassten Druckwerte nicht zu gross wird.

Für den 100 ms Takt würde ich keine weitere Task nehmen, sondern die 10 ms Task und dort mitzählen und nur bei jedem 10. Aufruf das Wegschreiben des CSV-Datensatzes veranlassen.


----------



## PN/DP (14 September 2020)

Dein csv-Datei schreiben wird doch bestimmt in einzelnen Teilschritten (Schrittkette) ausgeführt - wie viele Task-Durchläufe braucht das Schreiben, welche Zykluszeit hat die Task?

Harald


----------



## JesperMP (14 September 2020)

Hat Twincat kein internen Uhr ?
Wenn es millisekunden-genau sein muss wurde Ich den Zeitstempel in den SPS auslesen, und Teil von die Daten sein die in den CSV geschrieben werden sollen.


----------



## JesperMP (14 September 2020)

Habe auch ein bisschen Angst wenn auf ein Flashspeicher jeden 10 ms geschrieben wird.
Wenn den Zeitstempel durch den SPS generiert wird, kann man ein Puffer von z.B. 100 Messungen in den SPS speichern. Und dann jeden Sekunde in die CSV schreiben.


----------



## Devil120 (14 September 2020)

PN/DP schrieb:


> Dein csv-Datei schreiben wird doch bestimmt in einzelnen Teilschritten (Schrittkette) ausgeführt - wie viele Task-Durchläufe braucht das Schreiben, welche Zykluszeit hat die Task?
> 
> Harald



Die Zykluszeit beträgt 1 ms. Anscheinend braucht es dann 10 Zyklen für einmal Schreiben. 
Es wird immer eine ganze Zeile aus Datum/Uhrzeit/Messwerte1... MesswertX geschrieben.



JesperMP schrieb:


> Hat Twincat kein internen Uhr ?
> Wenn es millisekunden-genau sein muss wurde Ich den Zeitstempel in den SPS auslesen, und Teil von die Daten sein die in den CSV geschrieben werden sollen.



Doch die gibt es. Aber bei Windows bekomme ich auch eine millisekunden-genaue Zeit und es ist halt dieselbe Zeit des Videosignals der Ethernet-Kamera. 



JesperMP schrieb:


> Habe auch ein bisschen Angst wenn auf ein Flashspeicher jeden 10 ms geschrieben wird.
> Wenn den Zeitstempel durch den SPS generiert wird, kann man ein Puffer von z.B. 100 Messungen in den SPS speichern. Und dann jeden Sekunde in die CSV schreiben.



Das mit dem Puffer muss ich mir nochmal genauer anschauen. Bin halt erstmal froh, dass es überhaupt so gut läuft  war ein ganz schöner Krampf für mich. Wenn ich durch den Puffer alles wieder umprogrammieren muss, wärs echt unschön :/

Vielen Dank erstmal für eure Anregungen.


----------



## oliver.tonn (14 September 2020)

Devil120 schrieb:


> Das mit dem Puffer muss ich mir nochmal genauer anschauen. Bin halt erstmal froh, dass es überhaupt so gut läuft  war ein ganz schöner Krampf für mich. Wenn ich durch den Puffer alles wieder umprogrammieren muss, wärs echt unschön


Wirst Du wohl nicht drum rum kommen, wie schon geschrieben laufen die Prozesse vom Dataihandling im Hintergrund und werden ausgeführt, wenn gerade mal Zeit war, aber halt nicht zyklisch im Echtzeitkontext.


----------



## Heinileini (14 September 2020)

Devil120 schrieb:


> Es wird immer eine ganze Zeile aus Datum/Uhrzeit/Messwerte1... MesswertX geschrieben.


Das war ja auch mein Ansatz aus #14, aber ...
alle 100 ms 1 Datensatz schreiben mit Datum+Uhrzeit und Messwert1 .. MesswertX und Druckwert0(Uhrzeit+0ms), Druckwert1(Uhrzeit+10ms) .. Druckwert9(Uhrzeit+90ms).

Kann es sein, dass Deine Zeitsprünge darauf zurückgehen, dass der Zeitwert "weit hergeholt" ist (aus Windows statt aus der SPS)?


----------



## Devil120 (14 September 2020)

Ok:/
kurz nochmal eine Verständnisfrage.
Wenn ich die Daten in einem Puffer zwischenspeichere und die CPU grade dann einen anderen Task priorisiert. Habe ich dann nicht genau den selben Zeitversatz, nur halt im Puffer, sodass ich aufs selbe hinaus komme? Oder hab ich da einen Denkfehler?
Dann wäre der einzige Vorteil, dass die Datei weniger geöffnet und geschlossen wird. Dem Flash-Speicher ist es doch egal, ob ich nun 100 Datensätze auf einmal oder nacheinander schreibe. Schreiben bleibt Schreiben, oder?

EDIT:



Heinileini schrieb:


> Das war ja auch mein Ansatz aus #14, aber ...





Heinileini schrieb:


> alle 100 ms 1 Datensatz schreiben mit Datum+Uhrzeit und Messwert1 .. MesswertX und Druckwert0(Uhrzeit+0ms), Druckwert1(Uhrzeit+10ms) .. Druckwert9(Uhrzeit+90ms).
> 
> Kann es sein, dass Deine Zeitsprünge darauf zurückgehen, dass der Zeitwert "weit hergeholt" ist (aus Windows statt aus der SPS)?




Ich hatte es vorher mit der SPS-Zeit programmiert. Da war es ganz genau so :/


----------



## Devil120 (14 September 2020)

Ich denke, ich weiß jetzt wo der Fehler liegt.. Ihr werdet mich für bekloppt halten.

Das Schreiben wird in einer Case-Funktion nach Beckhoff-Vorlage ausgeführt. 0 - 10 Steps + Fehlerstep/Wartestep 100. Je nach dem, ob er einen Fehler hat oder die Datei nicht schnell genug geschlossen wird, dreht er ne extra Runde. Da kommen die zusätzlichen Millisekunden her. Pro Step 1 ms, bei einer Zykluszeit von 1 ms. Hmmm


----------



## Heinileini (14 September 2020)

Du änderst Deinen Beitrag schneller, als ich die Änderungen aufschnappen kann.

Vorhin stand da noch "Ich hatte es vorher mit der SPS-Zeit programmiert. Da war es ganz genau so, nur um 2:XX h verschoben, da die Zeit dort absolut nicht passt."

Du hast vermutlich mit Ortszeit und Sommerzeit zu kämpfen, aber doch "nur" mit einer konstanten Abweichung zwischen Windows und SPS?
Wenn Du in Deiner 10-ms-Task eine eigene Zeitzählung machst, hinkt die dann umso mehr hinterher, je länger Du beide vergleichst? 
Andererseits dürfte es höchst unwahrscheinlich sein, dass sich die 10-ms-Task selbst in die Hacken tritt, wenn Deine "eigentliche" Zykluszeit bei 1 ms liegt.

Ich würde mich lieber auf die SPS-Zeit stützen und unterstellen, dass die 10-ms-Task auch tatsächlich alle 10 ms durchlaufen wird. 

Zu Deiner Verständnisfrage: 
Wird die Datei tatsächlich vor dem Schreiben eines jeden Satzes geöffnet und danach wieder geschlossen? Dann würde ich hier EinsparPotenzial für das Auslasten der CPU wittern.
Der LebensDauer des FlashSpeicher dürften die (zu) häufigen SchreibZugriffe nicht egal sein.
Da Du mit jedem Datensatz den jeweiligen Zeitstempel mit abspeicherst bzw. bis zum Schreiben pufferst, dürfte das Endergebnis sich nicht unterscheiden.
Als Zeitstempel nimmst Du doch die Zeit der MesswertErfassung und nicht die Zeit des Wegschreibens?!


----------



## Heinileini (14 September 2020)

Dieser Editor versucht schon wieder, mich reinzulegen. 
Erst zitiert der Beitrag sich selbst und dann legt er für eine Änderung einen weiteren Beitrag an. 

PS:
Ich würde beim Sammeln der Daten und beim Wegschreiben abwechselnd zwei Puffer belegen:
Daten in Puffer1 aufbereiten, während das BetriebsSystem damit beschäftigt ist, Puffer2 in die CSV-Datei zu schreiben und
Daten in Puffer2 aufbereiten, während das BetriebsSystem damit beschäftigt ist, Puffer1 in die CSV-Datei zu schreiben u.s.w..


----------



## Devil120 (14 September 2020)

Sorry für die Änderungen. Manchmal schreibe ich schneller als ich denken kann und dann fällt mir auf, dass da Blödsinn steht, der mit der Thematik nichts zu tun hat.

Der Zeitstempel bezieht sich auf den Zeitpunkt des Erfassens der Messwerte.
Zur Zeit öffne und schließe ich noch jedes Mal die Datei. Ist natürlich nicht sonderlich schön. Bin grade schon dabei das zu ändern.


----------



## JesperMP (14 September 2020)

Die Windows-Zeit kannst du vergessen. Es wird nie Synkron mit der SPS-Zeit.



Devil120 schrieb:


> Dann wäre der einzige Vorteil, dass die Datei weniger geöffnet und geschlossen wird.


Das macht definitiv die Schreibzeiten kürzer.



Devil120 schrieb:


> Dem Flash-Speicher ist es doch egal, ob ich nun 100 Datensätze auf einmal oder nacheinander schreibe. Schreiben bleibt Schreiben, oder?


Nicht egal. Das Flashspeicher hat ein begrenzten Anzahl schreibzyklen. 'industrielle' Flashspeicher sind besser als Konsum, aber lebt trotzdem nicht unendlich. 100-mal pro sekunde = 360000 pro Stunde = 8.6 M pro Tag, 260 M pro Monat = 3.1 G pro Jahr. Ich habe es erlebt das ein Siemens Panel das Flashspeicher durch zu viele Schreibzyklen defekt ging. Und das war leider von versehen das interne Speicher --> Panel = Schrott.


----------



## Devil120 (14 September 2020)

Ich sags mal so - Als Speicher dient eine externe SSD oder USB-Stick. Die Messdatenerfassung läuft, wenn es hoch kommt 2 Stunden am Tag. Da sehe ich eher kein Problem. 
Was ich aber grade merke ist, dass wenn ich den Ablauf leicht ändere, ich andere Zeitintervalle bekomme. Teilweise 9 ms, Teilweise 11 ms. Je nach Programmcode. Heißt für mich - ich muss den Programmcode so "anpassen", dass ich genau auf 10 ms komme. Irgendwie fühlt sich das nicht richtig an. Es muss doch eine Möglichkeit geben exakte Zeiten vorzugeben


----------



## JesperMP (14 September 2020)

oliver.tonn schrieb:


> Bezüglich der Anforderung "Alle Daten mit dem selben Zeitstempel" müsstest Du, wenn es denn Beckhoff werden soll, die Distributed Clock Funktionalität nutzen.


Verwendest du das ?


----------



## Heinileini (14 September 2020)

Devil120 schrieb:


> Was ich aber grade merke ist, dass wenn ich den Ablauf leicht ändere, ich andere Zeitintervalle bekomme. Teilweise 9 ms, Teilweise 11 ms. Je nach Programmcode. Heißt für mich - ich muss den Programmcode so "anpassen", dass ich genau auf 10 ms komme. Irgendwie fühlt sich das nicht richtig an. Es muss doch eine Möglichkeit geben exakte Zeiten vorzugeben


Dann würde ich doch empfehlen, 
1. Deine eigene Zeitzählung im 10-ms-Task zu basteln und
2. diese eigene Zeit mit der "realen" zu vergleichen, ob sich hier etwas eklatantes entwickelt, das auf einen HandlungsBedarf deutet.
3. die "realen" ms auf das nächst liegende Vielfache von 10 zu runden.


----------



## Devil120 (14 September 2020)

JesperMP schrieb:


> Verwendest du das ?


Habe grade mal geschaut und ich finde nicht mal die Einstellung dazu. Eine neue Baustelle für morgen..

Es ist auf jeden Fall im Moment so, dass ich paar Schreibvorgänge im Fehler-Step lande und das kann ja nicht Sinn der Sache sein. Schreiben tut er, aber halt nur mit Fehlern zwischendurch.
Schauen wa mal..


----------



## oliver.tonn (14 September 2020)

JesperMP schrieb:


> Verwendest du das ?


Nein, aber ich habe das EtherCAT Handbuch durchgelesen, wo auch die Funktionsweise der Distributed Clock beschrieben wird und bin der Meinung, dass diese Funktionalität den TE bei seinem Problem helfen kann.


----------



## oliver.tonn (14 September 2020)

Devil120 schrieb:


> Habe grade mal geschaut und ich finde nicht mal die Einstellung dazu. Eine neue Baustelle für morgen..


Schau Dir mal das EtherCAT Handbuch an, da ist Distributed Clock beschrieben.


Devil120 schrieb:


> Es ist auf jeden Fall im Moment so, dass ich paar Schreibvorgänge im Fehler-Step lande und das kann ja nicht Sinn der Sache sein. Schreiben tut er, aber halt nur mit Fehlern zwischendurch.
> Schauen wa mal..


Dann stell doch bitte Deinen Code über den Code Button hier rein, das sollte eigentlich nicht passieren.


----------



## Heinileini (14 September 2020)

Devil120 schrieb:


> Es ist auf jeden Fall im Moment so, dass ich paar Schreibvorgänge im Fehler-Step lande und das kann ja nicht Sinn der Sache sein. Schreiben tut er, aber halt nur mit Fehlern zwischendurch.


Welcher Art sind die auftretenden Fehler?
Was heisst mit Fehlern zwischendurch? Fehler nur dann, wenn das Schreiben gerade mal nicht gefordert ist, sondern nur beim Öffnen bzw. Schliessen der Datei?
Oder ist erkennbar, dass nicht alle Sätze geschrieben werden, also ab und zu ein Satz in der Reihenfolge fehlt?


----------



## JesperMP (14 September 2020)

JesperMP schrieb:
			
		

> Verwendest du das ?





oliver.tonn schrieb:


> Nein, aber ich habe das EtherCAT Handbuch durchgelesen, wo auch die Funktionsweise der Distributed Clock beschrieben wird und bin der Meinung, dass diese Funktionalität den TE bei seinem Problem helfen kann.


Die Frage war eigentlich an Devil120 gemeint.


----------



## Devil120 (15 September 2020)

Guten Morgen liebe Leute,
ich habe es eben geschafft, die Fehler auszumerzen!Ich habe nun eine Bombenfeste Schreibzeit von 4 ms, wobei die Datei nur einmal geöffnet wird. Yeah! 
Jetzt setze ich die Zykluszeit auf 2 ms und bastel mir noch 1/2 Wartesteps dazu, dass die 10 ms passen.
Vielen Dank für eure Anregungen. Ich wäre sonst nicht drauf gekommen, dass der Fehler evtl. ganz wo anders liegt.
Viele Grüße und einen schönen und sonnigen Tag


----------



## Devil120 (6 Oktober 2020)

Moin Leute,

ich muss mich nochmal an euch wenden. Und zwar geht es um den FB_CSVMemBufferWriter. Dieser besitzt einen Puffer zum Speichern von Strings mit einer Größe von 255 (-1). Dieser Puffer läuft bei mir allerdings mit einer größer werdenden Anzahl an Messwerten über, sodass dieser einen Fehler auswirft.

Nach kurzer Rücksprache mit Beckhoff kann ich mir selbst einen Puffer mit fast unbegrenzter Größe erstellen. Ich weiß allerdings nicht wie ich diesen deklarieren soll, noch wie der Aufruf des FBs auszusehen hat. Die Info-Seite ist dies bezüglich leider etwas spärlich. Vielleicht hat jemand von euch schonmal damit zu tun gehabt und kann mir weiterhelfen. Alle meine bisherigen Versuche einen Puffer zu erzeugen und einzusetzen sind bis jetzt fehlgeschlagen.

Hier nochmal die Info-Seite des FBs:
https://infosys.beckhoff.com/index....PlcLibUtilities_FB_CSVMemBufferWriter.htm&id=

Vielen Dank schon mal im Voraus!!


----------



## PN/DP (6 Oktober 2020)

```
aMyBuffer : ARRAY[0..999] OF BYTE;

MyFB_CSVMemBufferWriter(..., pBuffer:=ADR(aMyBuffer), cbBuffer:=SIZEOF(aMyBuffer) ...);
```

Harald


----------



## Devil120 (6 Oktober 2020)

Soo, ich hab hier mal einen Teil des Programmcodes gepostet. Der Rest ist für die Thematik irrelevant hoffe ich.
Ich habe jetzt ein Byte Array erstellt und es versucht mit in den aufruf des FB_CSVMemBuffer_Writers (fbWriter) zu integrieren. Den FB_FilePuts habe ich durch den FB_FileWrite ersetzt, da der FilePuts glaube ich nur Strings verarbeiten kann. Ich bekomme dennoch immer noch Fehler in meiner FOR-Schleife, bei der Abfrage des fbWriter.b0k. Der FB-Aufruf wird wahrscheinlich einfach nicht passen. Habe aber schon diverses ausprobiert, aber nichts scheint so wirklich richtig zu sein.. :/


```
PROGRAM Daten_schreiben(* 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
    // Rising edge starts program execution
    dateiname        : STRING      := 'DefaultDateiname';
    bWrite             : BOOL        := FALSE;
    sNetId            : T_AmsNetId  := '';    (* TwinCAT Netzwerkadresse *)
    sFileName        : T_MaxString := CONCAT(CONCAT('C:\Users\Administrator\Desktop\Messdatenerfassung_Ver0.7\',dateiname),'.csv');(* CSV destination file path and name *)
    sCSVLine        : T_MAXSTRING := '';(* Einzelne CSV Line (Zeile) *)
    sCSVField        : T_MAXSTRING := '';(* Einzelnes CSV-Feld (Spalte)*)
    bBusy            : BOOL;
    bError            : BOOL;
    nErrId            : UDINT;
    nZeile             : UDINT     := 1;(* Nummer der Zeile *)
    nSpalte            : UDINT     := 1;(* Nummer der Spalte *)
    hFile            : UINT        := 0;(* "File handle" der Zieldatei *)
    step            : DWORD     := 0;
    fbFileOpen        : FB_FileOpen;(* Öffnet die Datei *)
    fbFileClose        : FB_FileClose;(* Schließt die Datei *)
    fbFilePuts        : FB_FileWrite;(* Schreibe eine Zeile *)
    fbWriter        : FB_CSVMemBufferWriter;(* Helfer-Funktion zur Erzeugung von CSV-Daten-Bytes (einzelne Zeile)*)
    sCSVField_array : ARRAY [0..999] of BYTE;
    
    
    
        
    getTime            : NT_GetTime; (* Funktion zurm Holen der Windows-Systemzeit *)
    systemTime        : TIMESTRUCT; (* Variable zur Zwischenspeicherung der Systemzeit *)
    bGetTimeStart    : BOOL:=FALSE;
    bGetTimeBusy    : BOOL;
    bGetTimeERR        : BOOL;
    bGetTimeERRID    : UDINT;    
    
    .
    .
    .


        
    database        : ARRAY[1..MAX_CSV_SPALTEN, 1..MAX_CSV_ZEILEN ] OF STRING (MAX_CSV_FELD_LAENGE); (* Zwischespeicher für eine gesamte Messdatenerfassung zu einem einzelnen Zeitpunkt *)
                                    
    
END_VAR


_____________PROGRAMM________________________________________________________________________________________


.
.
.


            sCSVField_array := null; (* sCSVField_array "Löschen"*)
            fbWriter.eCmd := eEnumCmd_First;(* Schreibe ersten Feldwert *)


            IF nZeile <= MAX_CSV_ZEILEN THEN
            
                    FOR nSpalte := 1 TO ANZAHL_SPALTEN BY 1 DO
                    
                                        
                    sCSVField := STRING_TO_CSVFIELD(database[ nSpalte, nZeile ], TRUE ); //-------------
                    
                    (* Add new field to the record buffer *)
                                
                    
                    fbWriter(pBuffer := ADR(sCSVField_Array), cbBuffer := SIZEOF(sCSVField_Array), putValue := '', pValue := ADR(sCSVField), cbValue := SIZEOF(sCSVField),
                            bCRLF := ( nSpalte = ANZAHL_SPALTEN ) );(* bCRLF == TRUE => Neuer Datensatz (Zeile)*)
                    IF fbWriter.bOk THEN
                        fbWriter.eCmd := eEnumCmd_Next;(* Schreibe den nächsten Feldwert *)
                    ELSE(* Error *)
                        step := 100;
                        RETURN;
                    END_IF
                    
                    END_FOR(* FOR Spalte 0... Anzahl an Spalten *)
                    
                (* FB_FilePuts fügt eine s. g. carriage Return der geschriebenen Zeile zum Abschluss der Zeile hinzu.
                Dazu muss abgefragt werden, ob die letzten beiden Zeichen R und L sind und je nach dem nur durch ein L ersetzt werden,
                um einen doppelten Zeilenumbruch zu vermeiden. *)
            
                //IF RIGHT( sCSVField_array, 2 ) = '$R$L' THEN
                //sCSVLine := REPLACE( sCSVLine, '$L', 2, LEN( sCSVLine ) - 1 );
                //END_IF


            nZeile := nZeile + 1;(* Increment-Nummer der erzeugten Zeilen. Hier wird nur Zeilenweise geschrieben *)
            step := 4;
            ELSE 
            nZeile := 1;
            END_IF
            
        ELSE    (* Daten Schreiben stoppen *)
            step := 10; 
        END_IF
        
    4:    (* Schreibe eine einzelne Textzeile *)
    
        IF RUN AND bWrite  THEN
        fbFilePuts( bExecute := FALSE );
        fbFilePuts( sNetId := sNetId, hFile := hFile, pWriteBuff := ADR(sCSVField_array),cbWriteLen :=SIZEOF(sCSVField_array), bExecute := TRUE );
        step := 5;
        ELSE 
                step := 10;
        END_IF


    .
    .
    .
```



EDIT:

Habe jetzt die Eingänge des CSVMemBufferWriters für einen externen Puffer wieder auf 0 gesetzt und einfach für den Standardpuffer den BytePuffer angegeben. Als Input habe ich wieder den einzelnen Wert aus meiner Database (sCSVFeld) genommen:


```
.
.
.

 fbWriter(pBuffer := ADR(sCSVField_Array), cbBuffer := SIZEOF(sCSVField_Array), putValue := sCSVField '', pValue := 0, cbValue := 0 ,
                            bCRLF := ( nSpalte = ANZAHL_SPALTEN ) );(* bCRLF == TRUE => Neuer Datensatz (Zeile)*)
                    IF fbWriter.bOk THEN
                        fbWriter.eCmd := eEnumCmd_Next;(* Schreibe den nächsten Feldwert *)
                    ELSE(* Error *)
                        step := 100;
                        RETURN;
                    END_IF
.
.
.
```

Das läuft soweit! Nur schreibt er die erste Zeile linksbündig, manchmal die zweite Zeile nach rechts versetzt "in die Mitte" und die darauffolgenden Zeilen schön untereinander, rechtsbündig und abgeschnitten in die CSV-Datei. Da suche ich grade noch den Fehler bzw. die Stelle, wo das passiert.

EDIT2: Ich glaube er schreibt die Leerzeichen bis zu den Werten der 2. und darauffolgenden Zeilen mit. Diese Leerzeichen belegen komischerweise auch Speicherplatz. Im ByteArray fangen die Messdaten immer im 1. Byte an. Ich kanns mir grade noch nicht erklären, wo die Leerzeichen herkommen


----------



## Devil120 (6 Oktober 2020)

Also 
Wenn ich den Puffer mit 1000 Werten deklariere, dann schreibt er mir alle 1000 Bytes in die CSV. Jedes Byte, welches den Wert 0 hat, erzeugt ein Leerzeichen 
Hat jemand eine Idee, wie ich das löse? Dynamisches Array, oder Abfrage auf den Wert 0, aber wo?


----------



## Heinileini (6 Oktober 2020)

Devil120 schrieb:


> Ich glaube er schreibt die Leerzeichen bis zu den Werten der 2. und darauffolgenden Zeilen mit. Diese Leerzeichen belegen komischerweise auch Speicherplatz. Im ByteArray fangen die Messdaten immer im 1. Byte an. Ich kanns mir grade noch nicht erklären, wo die Leerzeichen herkommen


Die Leerzeichen sind nicht so leer, wie sie heissen.  Wenn Du sie nicht haben willst, musst Du sie vor dem Schreiben (oder nach dem Lesen) ersetzen durch "nichts", also einen Leerstring.
Wo sie herkommen? Formatierte Ausgaben, bei denen die VorlaufNullenUnterdrückung die unterdrückten Nullen durch Leerzeichen ersetzt, vermute ich mal.


----------



## Devil120 (6 Oktober 2020)

Danke dir erstmal 
Ich verstehe nur nicht, wieso er aus einer 0 im Byte (also NULL) ein Leerzeichen macht. Ich kann diese Nullen ja auch nicht durch "Nichts" ersetzen. Immer wieder was neues


----------



## PN/DP (6 Oktober 2020)

Devil120 schrieb:


> ```
> fbFilePuts( sNetId := sNetId, hFile := hFile, pWriteBuff := ADR(sCSVField_array),cbWriteLen :=[COLOR="#FF0000"]SIZEOF[/COLOR](sCSVField_array), bExecute := TRUE );
> ```
> 
> ...


Du hast einen Puffer sCSVField_array mit 1000 Bytes deklariert und danach (hoffentlich) Deine Strings *lückenlos* hintereinander in das Byte-Array kopiert. Dabei wird der Byte-Puffer allerhöchstwahrscheinlich nicht voll belegt, sondern es bleiben unbeschriebene Bytes am Ende übrig. Danach schreibst Du (mit fbWriter oder fbFilePuts oder beiden ) das komplette Array inklusive der nicht belegten Bytes am Ende in die Datei.
Du solltest nur den tatsächlich von Nutzdaten/Strings belegten Teil des Arrays in die Datei schreiben. Dazu musst Du Dir beim Zusammenbasteln merken wie lang der Gesamtstring ist, oder nachträglich die Gesamtlänge ermitteln (z.B. so), und bei fbWriter/fbFilePuts anstatt der gesamten Puffergröße cbBuffer := SIZEOF(sCSVField_Array) nur die tatsächliche Länge angeben.

Irgendwie finde ich in Deinem gezeigten Code nicht, wo Du in das Array sCSVField_Array schreibst und wie Du den laaangen String zusammenbastelst ...

Ein Byte mit dem Inhalt 0 ist nicht leer, sondern es steht der Wert 0 drin. Daß diese 0 für Dich wie ein Leerzeichen aussieht liegt an dem Programm, was Dir die 0 als Leerzeichen präsentiert.




Devil120 schrieb:


> ```
> sCSVField_array := [COLOR="#FF0000"]null; (* sCSVField_array "Löschen"*)[/COLOR]
> ```


Ich kenne Twincat nicht, doch ich vermute, auch dies tut nicht das was Du erwartest - ich glaube nicht daß dabei alle Bytes in dem Array auf 0 initialisiert/gelöscht werden. Ich hätte sogar erwartet, daß der ST-Compiler diese Anweisung anmeckert ("null" sollte nur an Pointer zugewiesen werden können) ...

Harald


----------



## Devil120 (7 Oktober 2020)

Moin Harald,

die Daten schreibe ich lückenlos in den BytePuffer (sCSVField_Array). Diese werden in der FOR-Schleife im Funktionsbausteinaufruf von fbWriter() hineingeschrieben (siehe Code).
Das mit der Abfrage der geschriebenen Bytes hatte ich gestern schon versucht, in dem ich abgefragt habe, ob die ein Byte nicht den Wert 0 trägt und dann einen Zähler hochzählen lassen habe. Das Ergebnis war aber sehr verwirrend, da immer nur das 1. Datenfeld geschrieben wurde, was sehr unlogisch ist. Ich versuche mit heute nochmal weiter daran.

Zum Schreiben verwende ich den FB_FileWrite. Der FB_FilePuts geht glaube ich nur für Strings die geschrieben werden sollen und nicht für Bytes.


```
....

                    FOR nSpalte := 1 TO ANZAHL_SPALTEN BY 1 DO
                    
                                        
                    sCSVField := STRING_TO_CSVFIELD(database[ nSpalte, nZeile ], TRUE ); 
                    
                    (* Add new field to the record buffer *)
                                
                    
                    fbWriter(pBuffer := ADR(sCSVField_Array), cbBuffer := SIZEOF(sCSVField_Array), putValue := sCSVField'', pValue := 0, cbValue := 0,
                            bCRLF := ( nSpalte = ANZAHL_SPALTEN ) );(* bCRLF == TRUE => Neuer Datensatz (Zeile)*)
                    IF fbWriter.bOk THEN
                        fbWriter.eCmd := eEnumCmd_Next;(* Schreibe den nächsten Feldwert *)
                    ELSE(* Error *)
                        step := 100;
                        RETURN;
                    END_IF
                    
                    END_FOR(* FOR Spalte 0... Anzahl an Spalten *)

                  .....
```

EDIT: Ich war wohl gestern zu verpeilt. Habe die WHILE-Schleife zum Zählen der geschriebenen Bytes integriert und jetzt läuft es astrein. Vielen herzlichen Dank!


----------



## Devil120 (7 Oktober 2020)

Es hört nicht auf... 

Jetzt habe ich folgendes sehr merkwürdiges Problem mit der CSV-Datei. Auf dem Beckhoff Rechner (Windows 10 Embedded) ist das Layout der CSV gut. Bedeutet - Kopfzeile, mit darauffolgenden Zeilen der Messwerte. 


```
Datum;Uhrzeit;...;Druck1/bar
2020-10-07;10:06:54,405;...;0,9906308
2020-10-07;10:06:54,415;...;0,9918516
2020-10-07;10:06:54,425;...;0,994293
2020-10-07;10:06:54,435;...;0,9955138
...
..
.
```

Packe ich die CSV-Datei auf meinen Laptop (Windows 10 Pro) wird diese so geöffnet:


```
Datum;Uhrzeit;...;Druck1/bar


2020-10-07;10:06:54,405;...;0,9906308


2020-10-07;10:06:54,415;...;0,9918516


2020-10-07;10:06:54,425;...;0,994293


2020-10-07;10:06:54,435;...;0,9955138


...

..

.
```

Verschiebe ich sie wieder auf den Embedded PC, passt es wieder. Liegt das an der Windows-Version, dass zusätzliche Leerzeilen eingefügt werden? Kann ich das irgendwie verhindern oder muss ich das bei dem Import in Matlab, Excel, etc. beachten und die Leerzeilen rausfiltern?

Meine Kollegen haben so etwas auch noch nicht gesehen...

Danke euch schonmal!!


----------



## Heinileini (7 Oktober 2020)

Das liegt vermutlich daran, dass unterschiedliche Programme die ZeilenEndeZeichen unterschiedlich auslegen.
Vermutlich steht in der Datei ein CR (hex 0D, dez 13) und ein LF (hex 0A, dez 10) und das eine Programm wertet beide Zeichen für sich als ZeilenSchaltung aus und das andere nur eines davon bzw. nur eine bestimmte Reihenfolge der beiden Zeichen (CR LF oder LF CR)?


----------



## Devil120 (7 Oktober 2020)

Also in beiden Fällen wird mit dem Editor als Textdatei geöffnet. Excel beispielsweise ließt die zusätzlichen Leerzeilen aber auch ein.
Wie kann ich mir die Binärdaten anzeigen lassen, sodass ich schauen kann was für ein Zeilenumbruch im Hintergrund drin steht?


----------



## PN/DP (7 Oktober 2020)

Das sieht so aus als ob Du als Zeilenschaltung am Zeilenende nicht die richtige CRLF-Zeichenfolge verwendest. Schau Dir mal Deine csv-Datei als Hex-Dump an.
Es muß so sein:
CRLF = 0x0D + 0x0A = '$0D' + '$0A' = '$R' + '$N' = '$R' + '$L'

Hast Du Dir schon mal dieses Beispiel angesehen?
Beispiel: Schreiben/lesen einer CSV-Datei

PS: Heinrich war schneller 

Harald


----------



## Devil120 (7 Oktober 2020)

@PN/DP alias Harald

Du bist Top! Vielen Dank!! Hatte am Ende der Zeile eine 13 und eine 10 drin stehen. Habe jetzt die 13 in eine 10 gewandelt und die zweite 10 in eine 0. War einfach ein Zeilenumbruch + ein Carriage Return. Ein Zeilenumbruch reicht ja 
Nochmals Danke!

EDIT: Stimmt, Heinileini war schneller, habe ich leider übersehen xD Auch dir vielen herzlichen Dank!!!


----------



## PN/DP (7 Oktober 2020)

Devil120 schrieb:


> Wie kann ich mir die Binärdaten anzeigen lassen, sodass ich schauen kann was für ein Zeilenumbruch im Hintergrund drin steht?


z.B. Notepad++ mit Hex Editor Plugin

Harald


----------



## PN/DP (7 Oktober 2020)

Devil120 schrieb:


> Hatte am Ende der Zeile eine 13 und eine 10 drin stehen. Habe jetzt die 13 in eine 10 gewandelt und die zweite 10 in eine 0. War einfach ein Zeilenumbruch + ein Carriage Return. Ein Zeilenumbruch reicht ja


Das war doch eigentlich richtig. "Internationaler Standard" seit Jahrzehnten, und so auch von Beckhoff empfohlen, ist die Bytefolge 0x0D + 0x0A (13 +10).
Wenn jetzt irgendein neumodisches Programm oder neumodisches Windows diese Zeichenfolge als 2 Zeilenschaltungen interpretiert, dann muß man sich bei dem Programmhersteller oder Microsoft beschweren... vorausgesetzt, bei Dir steht wirklich 0x0D 0x0A in dieser Reihenfolge drin. Oder wird in dem Windows 10 die csv-Datei irgendwie (unsichtbar?) "intelligent" markiert als ob sie einen bestimmten Zeichencode-Standard verwendet? Oder kann man die Interpretation von 0D 0A in Windows 10 irgendwo einstellen?

Harald


----------



## Devil120 (7 Oktober 2020)

Also - die letzten beiden Bytes (die nicht 0 sind) im Bytepuffer waren definitiv 13 und 10. Das Windows auf dem embedded PC mit dem dortigen Editor fand das ja auch gut. Kann natürlich sein, dass das aktuelle Windows 10 Pro da was anderes interpretiert oder sonst was passiert. Ich weiß es nicht. 
Mit nur der 10 am Ende können jedenfalls beide Windows-Versionen die Daten richtig im Editor darstellen.


----------



## Heinileini (7 Oktober 2020)

Kennt ihr den "EnterHaken"? So nannte mal ein Kollege den "KnickPfeil" auf der EnterTaste, der von oben nach unten verläuft und dann von rechts nach links.
Der senkrechte Teil symbolisiert die ZeilenSchaltung LF (Line Feed) und der waagerechte den WagenRücklauf CR (Carriage Return).
An den guten alten Fernschreibern gab's dafür getrennte Tasten, an etwas moderneren nur eine Taste, die aber beide Funktionen nacheinander auslöste.
Wenn CR und LF bzw. LF und CR direkt aufeinander folgen, ist die Wirkung im Endeffekt identisch. Und das ist der Ursprung allen Übels, das die beiden Zeichen CR und LF betrifft. 
Wer braucht schon einen WagenRücklauf ohne ZeilenSchaltung, wenn er nicht dieselbe Zeile mehrmals beschreiben will (bis sie unleserlich wird)?
Warum die EnterTaste dann ausgerechnet dem Zeichen CR zugeordnet wurde und nicht dem Zeichen LF? Ein Dilemma mit "historischen" Hintergrund und eine Umsetzung von BedienungsKomfort, die ins Chaos geführt hat. Neu ist das Problem wirklich nicht. Im Gegenteil, die Kollegen, die das Problem noch nicht kennen, sind zu neu!


----------



## PN/DP (7 Oktober 2020)

Bist Du ganz gaaanz sicher, daß Du nicht bei Dir die Reihenfolge 0D + 0A (13 + 10) vertauscht hast zu 0A + 0D (10 + 13)? Das kann ich nämlich mit Windows 10 nachvollziehen.

Ausgabe mit Windows Editor Notepad bzw. Excel:

```
Zeilenende   Windows 7 Pro/64   Windows 10 Pro/64         Windows Server 2016 /64   Excel 2010 /64
------------------------------------------------------------------------------------------------------------
[B]0D + 0A[/B]      Zeilenschaltung    Zeilenschaltung           Zeilenschaltung           Zeilenschaltung
nur 0A       ---                Zeilenschaltung           ---                       Zeilenschaltung
nur 0D       ---                Zeilenschaltung           ---                       Zeilenschaltung
0A + 0D      ---                mit Leerzeile dazwischen  ---                       mit Leerzeile dazwischen


--- = Ausgabe ohne Zeilenschaltung (alles in einer Zeile)
```
Da hat es wohl jemand gut gemeint  und das Verhalten von Windows 10 an das schon lange existierende Verhalten von Excel angepasst.

Unabhängig davon: Auf der Zielplattform wird die csv-Datei vermutlich nicht mit dem Windows Texteditor Notepad geöffnet, sondern mit Excel oder einer App mit eigener Einlese-Routine (z.B. in VB). Ob VB/VBA/VBS unter Windows 10 auch ihr verhalten geändert haben weiß ich nicht (ich glaube eher nicht). Auch die HTML-Spezifikation sieht die Verwendung des Zeichenpaars 0D + 0A als "Line break" vor. Deshalb würde ich mich an den jahrzehntealten und empfohlenen *Standard 0D+0A* halten.

Harald


----------

