# Double Variable mit Wago 750-881 verarbeiten



## eloee (5 September 2013)

Hallo liebes Forum,

habe schon viel bei euch mitgelesen, was mir immer sehr geholfen hat.
Nun habe ich jedoch ein Problem, für das ich bisher keine Lösung gefunden habe.

Ich versuche mit einer Wago 750-881 über Modbus TCP einen Siemens Sentron PAC3200 Stromzähler / Multifunktionsmessgerät auszulesen.
Hierbei werden die meisten Werte im Format "REAL" bereitgestellt, welche ich Problemlos empfangen und verarbeiten kann.

Lediglich die Energiezähler (kWh) werden im Format "DOUBLE" übertragen, welches die Wago SPS, meines Wissens nach, nicht von Haus aus verarbeiten kann.
Habe nun schon mit dem Oscat "REAL2" Typ experimentiert, jedoch bisher erfolglos :-(
Habt ihr noch eine Idee wie ich an diesen Wert rankomme ? Wenn ein paar Kommastellen flöten gehen, wäre das nicht schlimm ;-)

Vielen Dank im Vorraus schonmal,
Gruß eloee


----------



## D.K (14 April 2016)

Hallo eloee,

Ich versuche ebenfalls eine Kommunikation zwischen Wago 750-881 und Sentron PAC3200 herzustellen.
Allerdings gelingt mir das nicht.

Beschreibe doch mal bitte wie du den Controller bzw. Multifunktionsmessgerät konfiguriert, bzw. programmiert hast.

Vielen danke für deine Mühe.

Gruß


----------



## eloee (15 April 2016)

Hallo D.K.,

mittlerweile setze ich das ganze mit einem PFC200 in e!Cockpit um, da geht das deutlich einfacher da der PFC200 LREAL Variablen unterstützt.
Habe mal mein altes 750-881 Projekt rausgesucht, die LREAL Problematik hatte ich damals nicht lösen können, die Kommunikation ansich war aber kein Problem.
Habe die Bibliothek WagoLibModbus_IP_01.lib verwendet, genauer gesagt den Baustein ETHERNET_MODBUSMASTER_TCP.




sendbuffer und recievebuffer sind jeweils ARRAY[0..99] OF WORD.
Mit diesem Aufruf habe ich im Empfangsbuffer dann an folgenden Stellen die Werte:
- 0, 1 Spannung L1
- 2, 3 Spannung L2
- 4, 5 Spannung L3
- 12, 13 Strom L1
- 14, 15 Strom L2
- 16, 17 Strom L3
- 24, 25 Leistung L1
- 26, 27 Leistung L2
- 28, 29 Leistung L3
- 64, 65 Leistung gesamt

Der Zählerstand ist weiter hinten in den Registern, musst also noch einen zweiten Aufruf des Modbusmaster mit anderem Register bzw. wREAD_ADRESS von 801 starten, dann liegen die Werte im Empfangsbuffer an Stelle 0-3. 

Strom, Spannung, Leistung werden als DWORD übertragen, du musst die zwei Wörter also noch zu einem DWORD zusammenfügen, habe dazu mal einen Baustein geschrieben. Falls dus brauchst gib Bescheid, kann aber auch sein das es das bereits irgendwo in einer Lib gibt.

Schöne Grüße
Eloee


----------



## paparadox (16 April 2016)

Moin Eloee,



> Strom, Spannung, Leistung werden als DWORD übertragen, du musst die zwei Wörter also noch zu einem DWORD zusammenfügen, habe dazu mal einen Baustein geschrieben. Falls dus brauchst gib Bescheid, kann aber auch sein das es das bereits irgendwo in einer Lib gibt.



wäre super wenn du den Code hier zur Verfügung stellen könntest. 

Gruß
PPX


----------



## eloee (18 April 2016)

Klar hab den FC Code mal angehängt ... nicht besonders schön aber hat damals glaube funktioniert ;-)

```
FUNCTION_BLOCK Util_Convert_2_Word_to_div
VAR_INPUT
    in_word0: WORD;    (*Word 1*)
    in_word1: WORD;    (*Word 2*)
END_VAR
VAR_OUTPUT
    out_real: REAL;
    out_int: INT;
END_VAR
VAR
    temp_dword: DWORD;
END_VAR



(*Umkopieren der Werte Word zu Doppelwort*)
temp_dword.0 := in_word1.0;
temp_dword.1 := in_word1.1;
temp_dword.2 := in_word1.2;
temp_dword.3 := in_word1.3;
temp_dword.4 := in_word1.4;
temp_dword.5 := in_word1.5;
temp_dword.6 := in_word1.6;
temp_dword.7 := in_word1.7;
temp_dword.8 := in_word1.8;
temp_dword.9 := in_word1.9;
temp_dword.10 := in_word1.10;
temp_dword.11 := in_word1.11;
temp_dword.12 := in_word1.12;
temp_dword.13 := in_word1.13;
temp_dword.14 := in_word1.14;
temp_dword.15 := in_word1.15;


temp_dword.16 := in_word0.0;
temp_dword.17 := in_word0.1;
temp_dword.18 := in_word0.2;
temp_dword.19 := in_word0.3;
temp_dword.20 := in_word0.4;
temp_dword.21 := in_word0.5;
temp_dword.22 := in_word0.6;
temp_dword.23 := in_word0.7;
temp_dword.24 := in_word0.8;
temp_dword.25 := in_word0.9;
temp_dword.26 := in_word0.10;
temp_dword.27 := in_word0.11;
temp_dword.28 := in_word0.12;
temp_dword.29 := in_word0.13;
temp_dword.30 := in_word0.14;
temp_dword.31 := in_word0.15;






out_real := DW_TO_REAL(temp_dword);
out_int := REAL_TO_INT(out_real);
```

Schöne Grüße
Eloee


----------



## m.beeken (19 April 2016)

Moin,
die Energiezähler (kWh) gibt es aber auch als 32 bit Werte ab Register 2801!




Schöne Grüße
Michael


----------



## PN/DP (19 April 2016)

eloee schrieb:


> ```
> (*Umkopieren der Werte Word zu Doppelwort*)
> temp_dword.0 := in_word1.0;
> temp_dword.1 := in_word1.1;
> ...


Statt 32 Bits einzeln zu kopieren kann man 2 WORD auch gleich zu DWORD zusammenbasteln:

```
temp_dword := SHL(WORD_TO_DWORD(in_word0), 16) OR WORD_TO_DWORD(in_word1);
```

Harald


----------



## D.K (25 April 2016)

Hallo zusammen,

Ich muss das Thema nochmal aufgreifen.

Vielen Dank erstmal für die bisherige Hilfe. Die Kommunikation funktioniert und ist stabil. 
Allerdings habe ich Probleme bei der Auswertung der Werte/Worte.
ich komme einfach nicht auf die Spannungswerte die mir im Display angezeigt werden. 

```
word30:=SHL (BYTE_TO_WORD(pac3200.readdata[0].bWert2),8) + BYTE_TO_WORD(pac3200.readdata[0].bWert1);
word31:=SHL (BYTE_TO_WORD(pac3200.readdata[0].bWert3),8) + BYTE_TO_WORD(pac3200.readdata[0].bWert4);
word32:=SHL (word30,16) + word31;
word33:=DWORD_TO_REAL(word32);
word34:=REAL_TO_INT(word33);
word35:=TRUNC(word33);
```

die Umwandlung von Dword_to_real ergibt für mich kein sinnvollen Wert.

Habe ich einen Denkfehler ? Oder seht ihr einen Fehler ?

Vielen Dank im Vorraus.

Gruss 
D.K


----------



## PN/DP (25 April 2016)

Warum vertauschst Du in einem Word die Bytes (H-Byte/L-Byte) und im anderen nicht?

Harald


----------



## D.K (25 April 2016)

Hallo Harald,

Danke für die Antwort.
Das war ein Fehler.

Ich  tausche die Bytes weil, wenn ich mich nicht täusche, Das PAC3200 das  kleinerwertige Byte als erstes sendet. Also Intel Format bzw. Little  Endian Format.
Oder täusche ich mich da ?

Ich glaube mein  Problem ist die Umwandlung. Wenn ich mir mit Modbus Poll den Datenstrom  als Float anzeigen lasse stimmt der Wert.

Gruss 
D.K.


----------



## eloee (26 April 2016)

Hast auf jeden Fall Recht das da was mit der Reihenfolge nicht stimmt, saß damals auch relativ lange dran und habe mir dann eben meinen "Hässlichen" Baustein gebastelt.
Nimm doch mal deine Empfangswörter und hau sie da drauf, müsste eigentlich funktionieren.

warum nimmst du die Byte Indizes erst ab "1" ? Ist dein Array erst ab 1 und nicht ab 0 ? Falls ja dann versuch mal die Variante:
ansonsten einfach jeweils die Indizes mal um 1 addieren, ich glaube du darfst nicht schon die Bytes vertauschen sondern dann erst die Wörter.


```
word30:=SHL (BYTE_TO_WORD(pac3200.readdata[0].bWert0), 8) + BYTE_TO_WORD(pac3200.readdata[0].bWert1);
word31:=SHL (BYTE_TO_WORD(pac3200.readdata[0].bWert2), 8) + BYTE_TO_WORD(pac3200.readdata[0].bWert3);
word32:=SHL (word30,16) + word31;
word33:=DWORD_TO_REAL(word32);
word34:=REAL_TO_INT(word33);
word35:=TRUNC(word33);
```

Gruß Eloee


----------



## PN/DP (26 April 2016)

D.K schrieb:


> Ich  tausche die Bytes weil, wenn ich mich nicht täusche, Das PAC3200 das  kleinerwertige Byte als erstes sendet. Also Intel Format bzw. Little  Endian Format.
> Oder täusche ich mich da ?
> 
> Ich glaube mein  Problem ist die Umwandlung.


Modbus-Words sind immer Big Endian.

Umwandlung Bytefolge zu REAL:
(1) Weil Codesys diese eigenartige Datentyp-Vermischung von DWORD = UDINT betreibt, kann man das Bitmuster eines DWORD ohne Konvertierung anscheinend nur per Pointer auf eine REAL-Variable übertragen oder über ein AT-Konstrukt.
(2) Nimm zum Zusammenfügen der BYTE zu WORD und WORD zu DWORD besser "OR", denn das Addieren von BYTE/WORD/DWORD mit "+" ist auch nicht ganz sauber, auch wenn Codesys das so zuläßt.

Versuche mal folgendes:

```
VAR
  wLo : WORD;
  wHi : WORD;
  dwVar : DWORD;
  rVar : REAL;
  pt_REAL : POINTER TO REAL;
END_VAR

wLo := SHL(BYTE_TO_WORD(pac3200.readdata[0].bWert4),8) OR BYTE_TO_WORD(pac3200.readdata[0].bWert3);
wHi := SHL(BYTE_TO_WORD(pac3200.readdata[0].bWert2),8) OR BYTE_TO_WORD(pac3200.readdata[0].bWert1);
dwVar := SHL(WORD_TO_DWORD(wLo),16) OR WORD_TO_DWORD(wHi);
pt_REAL := ADR(dwVar);
rVar : = pt_REAL^;
```

Test: die Modbus-Bytefolge (hex 16#) 44 9A 50 00 muß dwVar = DW#16#449A5000 ergeben und den REAL-Wert rVar = 1234.5 

Harald


----------



## D.K (26 April 2016)

Vielen Dank für die Antwort Harald.

Dadurch ist das Problem gelöst.
Für vielleicht nachfolgende User die das gleiche Problem haben nur eine kleine Ergänzung von Haralds Code:


```
dwVar := SHL(WORD_TO_DWORD(wLo),16) OR WORD_TO_DWORD(wHi);
```
In meinem Fall muss das wLo mit wHi getauscht werden. Bestimmt nur ein Tippfehler.


Aber Punkt 1 von Harald ist denke Ich der Knackpunkt.
Meine Frage dazu wäre jetzt noch. Du sprichst von "ohne Konvertierung". Welche Konvertierung meinst du ?
Das heisst wenn Ich das DWORD vorher Konvertiere muss ich nicht zwingend den Pointer benutzen ?

D.K


----------



## PN/DP (26 April 2016)

D.K schrieb:


> ```
> dwVar := SHL(WORD_TO_DWORD(wLo),16) OR WORD_TO_DWORD(wHi);
> ```
> In meinem Fall muss das wLo mit wHi getauscht werden. Bestimmt nur ein Tippfehler.


Das war leider kein Tippfehler, das war ein Denkfehler.  Ich dachte, man müßte beim Zusammensetzen des DWORD die Big-Endian-zu-Little-Endian-Konvertierung beachten, was aber nicht der Fall ist. Erst wenn man auf Speichereinheiten zugreift muß man es beachten.
Beim Lesen des REAL vom Modbus-Telegramm muß man die Big-Endian-zu-Little-Endian-Konvertierung bei der Zuordnung der 4 Einzelbyte zu den 2 Words vornehmen.

Ich korrigiere meinen Programmvorschlag wie folgt:

```
VAR
  wHi : WORD;
  wLo : WORD;
  dwVar : DWORD;
  rVar : REAL;
  pt_REAL : POINTER TO REAL;
END_VAR

wHi := SHL(BYTE_TO_WORD(pac3200.readdata[0].bWert2),8) OR BYTE_TO_WORD(pac3200.readdata[0].bWert1);
wLo := SHL(BYTE_TO_WORD(pac3200.readdata[0].bWert4),8) OR BYTE_TO_WORD(pac3200.readdata[0].bWert3);
dwVar := SHL(WORD_TO_DWORD(wHi),16) OR WORD_TO_DWORD(wLo);
pt_REAL := ADR(dwVar);
rVar : = pt_REAL^;
```




D.K schrieb:


> Aber Punkt 1 von Harald ist denke Ich der Knackpunkt.
> Meine Frage dazu wäre jetzt noch. Du sprichst von "ohne Konvertierung". Welche Konvertierung meinst du ?


Das Problem ist: beim Speichern des DWORD in einen REAL darf der Wert des DWORD nicht konvertiert/verändert werden, weil es schon einen REAL enthält, Codesys tut es aber, weil es DWORD = UDINT gleichsetzt, was nach meiner Meinung ein Designfehler ist, der aber nun anscheinend nicht mehr beseitigt werden kann (oder nicht soll), weil es bereits tausende Programme gibt wo dieses dokumentierte Verhalten dummerweise genutzt wird.

Empfehlung nach IEC 61131-3

UDINT ist ein Datentyp = unsigned double INT = Ganzzahl 0 bis 4294967295
Nach IEC 61131-3 gehört UDINT zur Gruppe der generischen Datentypen ANY_NUM, Untergruppe ANY_INT. ANY_INT kann man in Ganzzahl-Rechenoperationen direkt verwenden.
DWORD ist eine Bitfolge (Bitstring) von 32 Bit, die 4 Byte Speichergröße belegt. Egal was für ein Datentyp in dem DWORD abgelegt ist, es wird nicht interpretiert. Codesys behandelt aber: DWORD = UDINT
Nach IEC 61131-3 gehört DWORD zur Gruppe der generischen Datentypen ANY_BIT. Mit ANY_BIT kann man nicht rechnen, nur vergleichen, ob der Wert 0 oder <> 0 ist. Wenn man mit ANY_BIT rechnen will, dann müsste man immer erst den ANY_BIT in einen ANY_NUM typecasten (übernehmen, z.B. WORD_TO_INT), was lästig ist und zum nachdenken und verstehen zwingt. Und vielleicht deshalb von Codesys für die Bequemlichkeit der Programmierer ANY_BIT = unsigned ANY_INT als gleichwertig festgelegt wurde? 

Warum darf das DWORD nicht konvertiert werden?

In dem PAC3200 gibt es REAL-Variablen, sagen wir mal in unserer Variable ist der Wert 1234.5 (DW#16#449A5000 in Hex-Ansicht).
Für die Übertragung per Modbus wird der REAL einfach in 2 Word (Register) je 16 Bit zerschnitten und "Big Endian" ins Modbus-Telegramm gelegt: 16#44 16#9A + 16#50 16#00
Der Wago-Baustein empfängt das Modbus-Telegramm (oder zerlegt in 2 Register-Abfragen?) und liefert die Bytefolge: 16#44 16#9A 16#50 16#00
Hier bin ich aber nicht sicher. Es scheint, als ob der Wago-Baustein schon die Bytes je Word vertauscht und tatsächlich 16#9A 16#44 16#00 16#50 liefert und man deshalb die Bytes in der Reihenfolge 2 1 4 3 zusammensetzen muß. Das kann man aber einfach testen. Ich habe allerdings kein Wago und kein Codesys.
Im Programm muß nun die Bytefolge zu einem DWORD zusammengesetzt und in eine REAL-Variable gespeichert werden. Die Bytes müssen so aus dem Empfangspuffer entnommen und zum DWORD zusammengesetzt werden, daß sich der DWORD-Wert DW#16#449A5000 ergibt.
Dieser DWORD-Bitstring DW#16#449A5000 ist die Hex-Ansicht des ursprünglichen REAL-Wertes 1234.5 und braucht also nur noch ohne irgendwelche Änderungen in eine REAL-Variable gespeichert werden. Und hier ist die Stelle, wo das Codesys-Verhalten (DWORD = UDINT) verfälschend zuschlägt: Codesys interpretiert den DWORD-Wert DW#16#449A5000 als UDINT dezimal 1150963712 und konvertiert den Wert bei DWORD_TO_REAL zum REAL-Wert 1150963710.0 (1.15096371e+9), was nicht das ist was wir wollen.
Ein Kopieren des Bitstrings von DWORD unverändert in eine REAL-Variable ist meines Wissens in Codesys nur per Pointer möglich, oder man muß eine DWORD-Variable und eine REAL-Variable per AT-Konstrukt auf die selbe Speicheradresse legen und kann dann DWORD hineinspeichern und REAL herauslesen und umgekehrt.

Harald


----------



## D.K (27 April 2016)

Vielen Dank für die Antwort.
Die Eigenart von Codesys kannte Ich nicht.

Gruss D.K.


----------

