# Merker lesen mit Libnodave



## Snoopy123123 (8 Juli 2009)

Habe folgendes problem: Ich möchte MD132 (Realwert) aus einer 3152dp lesen. SPS hängt via cp343 am ethernet. Die Verbindung via Libnodave steht auch. Ich kann auch werte aus DB's lesen jedoch nicht aus dem Merkerbereich.

Ich nutze Folgende Libnodave einstellungen.

DaveFlags
DaveDB ist 0
daveProtoISOTCP

Dazu folgender Delphi7 Code

procedure TForm1.NoDave1Read(Sender: TObject);
begin
nodave1.ReadBytes(DaveFlags,0,132,4);
Gauge1.Value:= NoDave1.GetFloat(132);
edit2.Text:= intToStr(noDave1.CycleTime);
edit1.Text:= FloatToStr(NoDave1.GetFloat(132));
end;

Wenn ich "nodave1.ReadBytes(DaveFlags,0,132,4);" auskommentiere dann startet die exe und edit2 wird sauber angezeigt. Nehm ich Read Bytes wieder in den Code dann hängt sich die Anwendung beim starten auf.

Weiss jemand was ich hier falsch mache ?!


----------



## MW (9 Juli 2009)

Snoopy123123 schrieb:


> procedure TForm1.NoDave1Read(Sender: TObject);
> begin
> nodave1.ReadBytes(DaveFlags,0,132,4);
> Gauge1.Value:= NoDave1.GetFloat(132);
> ...



Delphi ist nicht gerade mein Fall, aber schreib mal bei 

```
Gauge1.Value:= NoDave1.GetFloat(132);
```
 stat der 132 eine 0 rein.


----------



## Snoopy123123 (9 Juli 2009)

Nein dann funktioniert es auch nicht !

Sobal nodave1.ReadBytes(DaveFlags,0,132,4); drinsteht startet die anwendung nicht mehr. in Dave Read Bytes sollte ja ( Dave Area, DB, Start, Size) drinnstehen. Ich hab irgednwo gelesen das wenn auf den Merkerbereich zugegriffen wird in DB ein "0" drinstehen muss. Wenn ich DaveRead mit einem DaveDB aufrufe funktioniert es einwandfrei.

Ich bin Ratlos ?!?


----------



## Key (10 Juli 2009)

procedure TNoDave.DoReadBytes(Area: TNoDaveArea; DB, Start, Size: Integer; Buffer: Pointer);

wenn du den internen Buffer nutz dann ist buffer = null wenn nciht da deinen Puffer angeben in den geschrieben werden soll. Evtl. liegt es daran.

P.S.: kein plan von Delphi 
und DaveFlags muss 0x83 entsprechen.

Gruß Key


----------



## Snoopy123123 (10 Juli 2009)

Ich nutze den internen Buffer. Bei delphi kann mann die Angabe weglassen, dann ist der automatisch null [optionale uebergabevariable]. 

also daran liegts nicht.

noch jemand ne idee ???


----------



## Snoopy123123 (12 Juli 2009)

Wollte mich nochmal melden da ich imme noch nicht weiter gekommen bin. Weiss niemand was ich hier falsch mache ?


----------



## pm (13 Juli 2009)

hallo,

blöde frage, wenn es mit dem db klappt, warum machst du auf der sps nicht
einen transferbefehl von md132 in einen datenbausteinbereich?

mfg


----------



## poppycock (13 Juli 2009)

Hi,

ich kann pm nur zustimmen.

Für eine Kommunikation SPS<->HMI würde ich mir sowieso immer einen DB dafür anlegen.
Das ist übersichtlicher (auch in der selbstgeschriebenen Visu)!
Denn dann hast du alles, was du für die Visu brauchst, in einem DB, den du abfragen kannst. Aber auch in diesen DB kannst du Werte von der Visu schreiben und im SPS-Programm verwenden.

Gruß,
poppycock


----------



## Snoopy123123 (13 Juli 2009)

Es geht mir hier weniger darum wie finde ich einen Weg damit mein Programm läuft. Den habe ich schon lange gefunden. 

Ich wüsste gerne was ich mit Libnodave falsch mache und weshalb das mit dem Merkerbereich nicht funktioniert.

Ausserdem gibts auch fälle ich denen man das SPS Programm nicht anfassen darf weils schon en alter schinken ist und es ja ach so gut läuft


----------



## pm (13 Juli 2009)

hast du den beitrag schon gelesen?

http://sps-forum.de/showthread.php?t=16362

mache leider selber nichts mit delphi sondern vb.
merker auslesen habe ich auch noch nie gebraucht...


----------



## Snoopy123123 (13 Juli 2009)

Ja den hatte ich schon gelesen. 

Ich habe auch meine Codeschnippsel so wie afk geschrieben hat bei DB eine Null reingeschrieben, aber Libnodave mag meine MErker trotzdem nicht lesen :-(


----------



## Human (13 Juli 2009)

```
procedure TForm1.NoDave1Read(Sender: TObject);
begin
nodave1.ReadBytes(DaveFlags,0,132,4);
Gauge1.Value:= NoDave1.GetFloat(132);
edit2.Text:= intToStr(noDave1.CycleTime);
edit1.Text:= FloatToStr(NoDave1.GetFloat(132));
end;

Wenn ich "nodave1.ReadBytes(DaveFlags,0,132,4);" auskommentiere dann startet die exe und edit2 wird sauber angezeigt. Nehm ich Read Bytes wieder in den Code dann hängt sich die Anwendung beim starten auf.
```
 
Naja, hört sich für mich ganz logisch an!
Das OnRead-Ereignis wird bei jedem Lesen aus der SPS ausgelöst. Also es wird zirkulierend aufgerufen, deswegen hägt sich dein Programm auf!


----------



## Snoopy123123 (13 Juli 2009)

Ich dachte das sei der Sinn des On Read Ereignisses. Wenn ich eine Druckanzeige habe dann will doch nicht von Hand ein ereignis triggern um den einen Wert angezeigt zu bekommen. Was mich verwirrt das das mit Area: Dave Db einwandfrei funktioniert. Auch mit eingestellten on read Trigger mit 50ms.

Mein programm sich auf weil es aus dem On Read nicht mehr herauskommt. 

Nur weil ich etwas "zirkulierend" aufrufe hängt es sich doch nicht auf, dann würde es sich ja bei jedem Timerevent auch aufhängen, richtig ?


----------



## Human (13 Juli 2009)

In der Funktion ReadBytes wird das Ereignis OnRead aufgerufen und wenn du im OnRead wieder ReadBytes ausführst wird auch wieder das Ereignis OnRead auferufen usw!


----------



## Zottel (13 Juli 2009)

Was soll:

```
Gauge1.Value:= NoDave1.GetFloat(132);
edit2.Text:= intToStr(noDave1.CycleTime);
edit1.Text:= FloatToStr(NoDave1.GetFloat(132));
```
?
Wenn die Delphi-Unit nicht ganz anders funktioniert als der Rest der Bibliothek, sollte es heißen:

```
Gauge1.Value:= NoDave1.GetFloat();
edit2.Text:= intToStr(noDave1.CycleTime);
edit1.Text:= Gauge1.Value;
```
?
Erklärung:
GetFloat holt 4 byte aus dem Puffer und erhöht den Zeiger.
Es ist also keine Positionsangabe nötig. Sonst müßtest du mit GetFloatAt oder GetFloatFrom arbeiten.
Der Puffer enthält 4 Bytes, wenn du 4 gelesen hast.
Ein 2.Aufruf von GetFloat gibt zufällige Speicherinhalte zurück.


----------



## Snoopy123123 (13 Juli 2009)

Tatsache ! Jetzt funktionierts. Vielen Dank.

Das bedeutet aber das man ganz schöne klimmzüge machen muss wenn man aus verschiedenen bereichen DaveDB oder DaveFlags oder directe PEW lesen muss, will !!

Wie bewerkstelligt man das am besten. 

Mir fallen hier mehrere Möglichkeiten.

Mehrere NoDave Komponenten benutzen - nachteil belegt verbindungen zur SPS und nach ein paar (kommt auf die SPS an) ist schluss.

Ne Variable hochzählen und dave read ausführen ud dann nur die getbytes variablen ausführen die für diesen Bereich sind, aber wie stelle ich hier sicher das ich auch alle 100ms alle Datenbereiche einlese ?!?


----------



## Snoopy123123 (13 Juli 2009)

Hallo Zottel,

Verstehe ich es richtig: 

"Gauge1.Value:= NoDave1.GetFloat(); " zieht 4 Byte aus dem Buffer und zählt den Zeiger hoch

"Gauge1.Value:= NoDave1.GetFloat(132); " zieht 4 Byte aus dem Buffer beginnend ab Byte 132 und zählt den Zeiger hoch
Wenn also Dave Read Byte ab Byte 0 durchgeführt wurde bekomme ich MD132
und wenn Dave Read Byte ab Byte 100 durchgeführt wurde bekomme ich MD32

Hab ich das so richtig verstanden ?!


----------



## Snoopy123123 (13 Juli 2009)

Habe gerade mal 
"Gauge1.Value:= NoDave1.GetFloat(); "
eingegeben aber mein Delphi Compiler benötigt die Positionsangabe.


----------



## Zottel (13 Juli 2009)

Snoopy123123 schrieb:


> Habe gerade mal
> "Gauge1.Value:= NoDave1.GetFloat(); "
> eingegeben aber mein Delphi Compiler benötigt die Positionsangabe.



Möglicherweise sollte ich mich aus jeder Delphi-Diskussion raushalten...
Sehe gerade, daß TNoDave.GetFloat() intern daveGetFloatFrom() verwendet. Es wird daher kein Zeiger hochgezählt...

Um zu wissen, was die Adresse eigentlich bewirkt müßte ich erst die Funktion BufferAt verstehen...


Wieso schreibst du, daß es funktioniert?
Und dann wieder doch nicht?


----------



## Zottel (13 Juli 2009)

Snoopy123123 schrieb:


> Das bedeutet aber das man ganz schöne klimmzüge machen muss wenn man aus verschiedenen bereichen DaveDB oder DaveFlags oder directe PEW lesen muss, will !!


Zunächst mal ist PEWs lesen keine gute Idee. Die CPU muß dann zusätzliche Buszugriffe auf die Peripherie ausführen. Ist echt langsam.


Snoopy123123 schrieb:


> Wie bewerkstelligt man das am besten.


Lesen aus verstreuten Speicheradressen kann man bis zu einem gewissen Umfang mit:
davePrepareReadRequest
daveAddVarToReadRequest
daveExecReadRequest
in einer Abfrage zusammenfassen.
Grenzen setzen hier PDU-Länge und eventuell die CPU.
Diese Funktionen scheint die Komponente nicht zur Verfügung zu stellen...

Ansonsten ist es am günstigsten, verstreute Variablen im SPS-Programm in einen Block zu kopieren und diesen "am Stück" einzulesen.

Grundsätzlich gilt: Jeder read-request kostet Zeit, bis die CPU antwortet.

Das ist auch bei jeder anderen Kommunikationsbibliothek und bei jedem OPC-Server so, auch wenn einige OPC-Server was anderes vermuten lassen: Obwohl man für jede einzelne Variable einige Millisekunden als Aktualisierungsintervall einstellen kann, kann das am Ende nicht einmal annähernd eingehalten werden.


----------



## Question_mark (13 Juli 2009)

*Dat jeht soo ...*

Hallo,



			
				Snoopy123123 schrieb:
			
		

> Das bedeutet aber das man ganz schöne klimmzüge machen muss wenn man aus verschiedenen bereichen DaveDB oder DaveFlags oder directe PEW lesen muss, will !!
> 
> Wie bewerkstelligt man das am besten.



Am besten alles in der SPS in einen DB (möglichst zusammenhängend) rangieren und den DB mit LibohneDave in einem Rutsch auslesen. Unn fettisch ..

Gruß

Question_mark


----------



## Snoopy123123 (13 Juli 2009)

Zottel schrieb:


> Möglicherweise sollte ich mich aus jeder Delphi-Diskussion raushalten...
> Sehe gerade, daß TNoDave.GetFloat() intern daveGetFloatFrom() verwendet. Es wird daher kein Zeiger hochgezählt...
> 
> Um zu wissen, was die Adresse eigentlich bewirkt müßte ich erst die Funktion BufferAt verstehen...
> ...



Sorry meinte das es mit "Gauge1.Value:= NoDave1.GetFloat(132); " funktionert, aber ohne positionsangabe meckert Delphi und lässt mich nicht compilieren.


----------



## Question_mark (13 Juli 2009)

*Com Library vs OPC-Server*

Hallo,



			
				Zottel schrieb:
			
		

> und bei jedem OPC-Server so, auch wenn einige OPC-Server was anderes vermuten lassen: Obwohl man für jede einzelne Variable einige Millisekunden als Aktualisierungsintervall einstellen kann, kann das am Ende nicht einmal annähernd eingehalten werden.



Du kannst aber eine Kommunikationsbibliothek wie LibOhneDave oder AGLink nicht mit einem OPC-Server vergleichen. Das sind ganz unterschiedliche Ansätze ...
Bitte nicht Äpfel mit Birnen vergleichen. LibOhneDave oder AGLink machen Lese/Schreibzugriffe auf die SPS nur bei impliziten Funktionsaufrufen der Kommunikationsbibliothek durch den Anwender. Ein OPC-Server aktualisiert die OPC-Items in der vorgegebenen Aktualisierungsrate und hält die Daten in einem eigenen Cachespeicher. Dem OPC-Server kann ich eine Aktualisierungsrate vorschlagen. Und wenn diese erreicht werden kann, akzeptiert der OPC_Server meinen Vorschlag. Und antwortet anderenfalls auf meine Anfrage mit der bestmöglichen Aktualisierungsrate, die der OPC-Server seiner Meinung nach erreichen kann.
Also da gibt es eigentlich zwei unterschiedliche Cycle times :

1) Die Zeit für das Auslesen der OPC-Items von der SPS in den Cache des OPC-Servers
2) Das Zeitraster, mit dem der OPC-Server die Daten meinem OPC_Client zur Verfügung stellt.

Gruß

Question_mark


----------



## Zottel (14 Juli 2009)

Question_mark schrieb:


> Du kannst aber eine Kommunikationsbibliothek wie LibOhneDave oder AGLink nicht mit einem OPC-Server vergleichen.


Doch, insofern, daß der OPC-Server eben so eine Kommunikationsbibliothek (oder vergleichbare integrierte Funktionen) benötigt, um mit der Steuerung zu kommunizieren. Dabei sendet er dann genau dieselben Requests ab und wartet genauso lange auf die Antworten.
Der Hersteller des OPC-Servers mag Gehirnschmalz in die geschickte Zusammanfassung von Variablen investieren. Da hat er bei Siemens 2 Möglichkeiten:
1. Er kann Variablen in einem read request zusammenfassen.
2. Er kann, wenn MW 10 und MW24 benotigt werden, 16 Bytes ab MW10 lesen und die Variablen daraus extrahieren.

Aber wenn ich 40 Variablen aus 40 verschiedenen DBs angebe und 10ms Atualisierungszyklus einstelle, kann er das nicht im Traum einhalten. 
Er kann nur dieselben Daten aus dem Cache öfter an den Client senden, aber der Cache ist halt veraltet.

Es sind eben KEINE verschiedenen Ansätze. Der OPC-Server setzt sich sozusagen aus Kommunikationsbibliothek, Thread zur Aktualisierung, Cache
und OPC-Schnittstelle zusammen (ob diese Funktionen in einem Programm ohne sichtbare Schnittstellen integriert sind, spielt keine Rolle).


----------



## Question_mark (14 Juli 2009)

*Nicht doch Zottel ..*

Hallo,



			
				Question_mark schrieb:
			
		

> LibOhneDave oder AGLink machen Lese/Schreibzugriffe auf die SPS nur bei impliziten Funktionsaufrufen der Kommunikationsbibliothek durch den Anwender. Ein OPC-Server aktualisiert die OPC-Items in der vorgegebenen Aktualisierungsrate und hält die Daten in einem eigenen Cachespeicher.



@Zottel

Ich zitiere mich eigentlich ungern selber, aber ausser einem gemeinsamen Kommunikationstreiber sind es doch unterschiedliche Konzepte.

Gruß

Question_mark


----------



## Question_mark (14 Juli 2009)

*CycleTime*

Hallo,



			
				Zottel schrieb:
			
		

> Er kann nur dieselben Daten aus dem Cache öfter an den Client senden, aber der Cache ist halt veraltet.



Immerhin kann ich beim Request an den OPC-Server angeben, ob ich Daten aus dem Cache oder immer expliziet aus der SPS lesen möchte. Und wer glaubt, für eine besch..ene Visu alle Daten der SPS im 10ms Raster anzeigen zu müssen, hat sowieso irgendetwas nicht verstanden (damit habe ich jetzt aber nicht Dich persönlich gemeint).

Gruß

Question_mark


----------



## Human (14 Juli 2009)

Snoopy123123 schrieb:


> Das bedeutet aber das man ganz schöne klimmzüge machen muss wenn man aus verschiedenen bereichen DaveDB oder DaveFlags oder directe PEW lesen muss, will !!
> 
> Wie bewerkstelligt man das am besten.
> 
> ...


 
Ich mach das mit der NoDave-Komponente immer so, dass ich das integrierte lesen normal nur dazu verwende um meine Verbindung prüfen zu lassen, das auf 1 Byte irgendwo auf der SPS ansetze und den Interval auf 500ms setze.

Damit ich dann aber doch an meine Daten komme hab ich für jede Verbindung einen seperaten Thread in dem ich die Daten auslese, aufbereite und dann an mein Hauptprogramm übergebe, was den rießengroßen Vorteil hat, dass das nicht mein Hauptprogramm stört (macht der Interval auch nicht, da der auch in einem seperaten Thread ist) und vor allem Kann ich viele Werte auf einmal aus verschiedenen Speicherbereichen auslesen.


----------



## afk (14 Juli 2009)

Ich komme mal auf den Anfang zurück, um die ursprüngliche Frage zu beantworten:



Snoopy123123 schrieb:


> procedure TForm1.NoDave1Read(Sender: TObject);
> begin
> nodave1.ReadBytes(DaveFlags,0,132,4);
> Gauge1.Value:= NoDave1.GetFloat(132);
> ...


Der Fehler liegt, wie von Human schon erwähnt und wie du eigentlich bereits selbst erkannt hast, im Aufruf von DaveReadBytes innerhalb des OnRead-Events. 

Es gibt 2 Anwendungsvarianten der Komponente:


Die "*quick and dirty*" Variante für *Anfänger* und den schnellen Tests:
 In diesem Fall muß in den Properties _*Area*_, _*BufLen*_, _*BufOffs*_ und ggf. _*DBNumber*_ der Komponente eingestellt werden, was aus der SPS ausgelesen werden soll, und im Property _*Interval*_ wird das Leseintervall in Millisekunden bestimmt. Die Komponente erzeugt dann einen eigenen Thread, der das zyklische Lesen aus der SPS durchführt und nach jedem erfolgreichen Lesen den OnRead-Event auslöst. Im Event darf dann logischerweise nicht noch mal von der SPS, sondern nur noch aus dem internen Puffer der Komponente gelesen werden (mit den Get..._Methoden). Diese Methode funktioniert sogar innerhalb der Delphi-IDE, ohne daß das Programm schon laufen muß (daher "quick and dirty"), daher kann man so im Objektionspektor überprüfen, ob die Kommunikation mit den Einstellungen der Properties überhaupt funktioniert (der aktualisiert allerdings nicht zyklisch, sondern immer nur einmalig jeweils beim Selektieren der Komponente).


Die "*professionelle*" Variante für *richtige Anwendungen*:
Bei dieser Variante wird das Property _*Interval*_ auf 0 gesetzt, und die Properties _*Area*_, _*BufLen*_, _*BufOffs*_ und _*DBNumber*_ brauchen nicht eingestellt werden. Die Komponente erzeugt dann keinen Thread, der das zyklische Lesen aus der SPS übernimmt, die Komponente baut dann eigentlich "nur" noch die Verbindung zur SPS auf, sobald das Property _*Active*_ auf True gesetzt wird. Das zyklische Lesen von der SPS muß dann aus dem Programm heraus durch Aufruf der Methode _*ReadBytes(Area, DB, Start, Size[, Buffer])*_ ausgeführt werden (z.B. per Timer oder Thread). Ob dabei mit dem internen Buffer der Komponente oder mit Buffern gearbeitet wird, die vom Programm bereitgestellt werden, hängt vom Anwendungsfall (und vom Programmierer) ab. Der Vorteil dieser Variante ist, daß man sämtliche Freiheiten hat zu bestimmen, aus welchem Adressbereich der SPS gelesen werden soll, da man diese Information ja mit jedem Aufruf von _*ReadBytes*_ mitgibt.

In meinen eigenen Programmen (z.B. ein OPC-Server, soviel zum Thema "das sind zwei völlig unterschiedliche Konzepte" ) nutze ich ausschließlich die zweite Variante.


Gruß Axel


----------



## Zottel (14 Juli 2009)

Question_mark schrieb:


> Ich zitiere mich eigentlich ungern selber, aber ausser einem gemeinsamen Kommunikationstreiber sind es doch unterschiedliche Konzepte.


Es sind unterschiedliche Konzepte im Hinblick wie die Daten dem Client/der Anwendung übergeben werden.

Der Hauptzweck des Caches ist, daß mehrere Clients Daten erhalten können und diese dazu nur einmal von der Steuerung geholt werden.

Aber bei der Kommunikation mit der Steuerung ist es dieselbe Scheiße mit denselben Beschränkungen der Bandbreite (=Datenvolumen/Zeit).
Ich bestehe auf dieser Klarstellung, weil ich hin und wieder Anfragen von Usern bekomme, die mir erzählen, daß sie an einem OPC-Server geile Aktualisierungsraten einstellen können und das für beliebig viele Variablen und dann müsse das Ding doch soviel leistungsfähiger sein als Libnodave. Das ist wie den Tempomat auf 1000 stellen können, aber die Karre macht nun mal nicht mehr als 90.

Unter "unterschiedlichen Konzepten" würde ich im Hinblick auf die Kommunikation mit der Steuerung etwas verstehen wie:
Der OPC-Server bezöge seine Daten aus einem mit der CPU geteilten "shared memory" (Denkbar bei Soft-SPS oder wenn ein Wkindows-Rechner als Koprozessor in einer S7 liefe und mit dieser dual ported RAM teilte).


----------



## Snoopy123123 (14 Juli 2009)

Human schrieb:


> Ich mach das mit der NoDave-Komponente immer so, dass ich das integrierte lesen normal nur dazu verwende um meine Verbindung prüfen zu lassen, das auf 1 Byte irgendwo auf der SPS ansetze und den Interval auf 500ms setze.
> 
> Damit ich dann aber doch an meine Daten komme hab ich für jede Verbindung einen seperaten Thread in dem ich die Daten auslese, aufbereite und dann an mein Hauptprogramm übergebe, was den rießengroßen Vorteil hat, dass das nicht mein Hauptprogramm stört (macht der Interval auch nicht, da der auch in einem seperaten Thread ist) und vor allem Kann ich viele Werte auf einmal aus verschiedenen Speicherbereichen auslesen.



Super, vielen Dank ! Dieser hinweis hat mein Problem gelöst. Ich hab mich fälschlicherweise daran festgebissen das OnRead ereignis der Komponente benutzen zu müssen. Dachte das müsste so sein. Die Sache mit dem eigenen Tread funktioniert nun wunderbar ich ich kann in allen bereichen lesen und schreiben. 

Was ich noch nicht so ganz verstehe wie du das mit dem Verbindung prüfen machst. Kannst du das mal genauer erklären ? Oder gibt es in der Komponente schon eine art Watchdog ?


----------

