TIA BCD-Code in Zahlenkette umwandeln

KarlMeier

Level-2
Beiträge
206
Reaktionspunkte
31
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo zusammen,
ich habe eine spezielle Frage. Ich habe bereits eine Lösung für mein Problem gefunden, aber ich denke, dass es eine wesentlich einfachere und auch schönere Lösung gibt.

Die Situation:
Zum Datenaustausch bekomme ich von einer PhoenixContact-Steuerung verschiedene Daten mittels BCD-Code übermittelt. Dabei bekomme ich einmal das BCD-Signal mittels 4 Bits eines Ausgangsmoduls. Zusätzlich bekomme ich ein Senden-Signal und zwar immer zu Beginn des Datenaustauschs, sowie zwischen jeder einzelnen Zahl. Insgesamt werden 15 Zahlen übertragen, welche sich folgendermaßen gruppieren. Zahl 1 + 2 geben die Programmnummer an, Zahl 3-11 geben die Kundennummer an, Zahl 12-15 ist das Gewicht.

Ich habe das Ganze nun folgendermaßen gelöst. Zunächst einmal hab ich einen Zähler gemacht, welcher mir das Senden-Signal zählt. Bei der ersten positiven Flanke weiß ich dass der Übertragungsvorgang beginnt. Beim Zählwert 2 weiß ich, dass die erste Zahl der Programmnummer gesendet wird, beim Zählwert 3 kommt die zweite Stelle der Programmnummer, bei Zählwert 4 kommt die erste Zahl der Kundennummer usw.
Und so läuft der Zähler durch bis 16 und speichert mir dabei immer den jeweiligen Zählwert in ein Array of Dint und setzt sich anschließend selbst zurück.
Den Wert der Zahl ermittle ich über SCL. Ich könnte auch in FUP den Gather-Baustein verwenden, der mir aus den einzelnen BCR-Bits eine Zahlenreihe erstellt. Das wäre jetzt Geschmacksache was die schönere Variante ist.

Nun aber zur Zahlenbildung. Ich muss ja die Zahlen in eine richtige Reihe bekommen. Dazu habe ich nun einfach in SCL die einzelnen Zahlen mit Faktor 10 multipliziert. Beispiel Programmnummer: Für die erste Stelle wurde mir 6 übertragen, für die zweite Stelle 3. Also multipliziere ich 6 mit 10 (Ergebnis 60) und addiere die zweite Stelle, also 3 dazu. Ergebnis: 63

Bei der Kundennummer hab ich es genauso gemacht. Die erste Zahl der Kundennummer multipliziere ich mit 100000000 (8-Nullen), die zweite Zahl der Kundennummer mit 10000000 (7-Nullen) usw. Am Ende addiere ich wieder die letzte Zahl und habe somit meine 9-stellige Kundennummer in einer Reihe stehen.

Beim 4-stelligen Gewicht mach ich es genauso.

Es funktioniert zwar erstmal alles so wie es soll, aber mir kommt die Lösung etwas unorthodox vor. Gibt es da nicht einen einfacheren bzw. schöneren Weg? Ich habe bereits nach alternativen Lösungswegen gesucht, habe aber nichts gefunden.

Beste Grüße
 
Ist zwischen den "Senden"-Pulsen irgendwann eine längere Pause? Daraus könntest Du ableiten, wann die erste BCD-Ziffer kommt.
Irgendwie ist Deine Beschreibung des Senden-Pulses etwas konfus. Wieso kommt die erste Ziffer erst beim zweiten Senden-Puls?? Musst Du vielleicht die BCD-Ziffer bei der fallenden Flanke des Senden-Pulses übernehmen? Gibt es eine Beschreibung der Ziffernübertragung vom Kommunikationspartner?

Für die BCD-zu-Dezimal-Wandlung gibt es fertige Anweisungen, siehe TIA-Hilfe "Explizite Konvertierung von DWORD (S7-1500): DWORD_BCD32_TO_DINT". Für Werte > 9.999.999 kann man die BCD-Decodierung auch kaskadieren, z.B. einmal 4 und einmal 5 BCD-Ziffern.
Wie groß kann die 9-stellige Kundennummer maximal werden? Ggf. muß Du für die dezimale Kundennummer LINT bzw. ULINT oder LWORD verwenden. Oder STRING ?

"Zu Fuss" konvertieren von BCD zu DINT kann man, indem man fortlaufend (z.B. Schleife) ein Zwischenergebnis mit 10 multipliziert und die nächste Ziffer addiert. Das hängt die nächste Ziffer als Einer-Ziffer an eine Dezimalzahl hintendran.

Harald
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Danke für die schnelle Antwort.
Der erste Sende-Impuls signalisiert, dass jetzt die Datenübertragung beginnt. Zwischen den einzelnen Impulsen sind dann etwa 0,75 Sekunden Pause. In ca. 10sek ist die Datenübertragung beendet und es folgt eine ca. 2-Minuten-Pause, bis die nächste Datenübertragung wieder gestartet wird.

Da die 9-stellige Kundennummer ja kein Zahlenwert im eigentlichen Sinne, ist könnte sie theoretisch 999.999.999 groß sein.
Das mit der Expliziten Konvertierung hab ich schon entdeckt. Bin da aber nicht wirklich weiter gekommen, weil ich nicht wusste was ich eingangsseitig für ein Format benötige. Ich vermute Word bzw. DWord. Aber wie krieg ich die einzelnen Zahlenwerte vernünftig in ein Word rein? Ein Array funktioniert ja nicht oder?
 
Da die 9-stellige Kundennummer ja kein Zahlenwert im eigentlichen Sinne, ist könnte sie theoretisch 999.999.999 groß sein.
Tja, da war ich kurz auf einem Irrweg. Für 9-stelligen Zahlenwert reicht DInt/UDInt.

Das mit der Expliziten Konvertierung hab ich schon entdeckt. Bin da aber nicht wirklich weiter gekommen, weil ich nicht wusste was ich eingangsseitig für ein Format benötige. Ich vermute Word bzw. DWord. Aber wie krieg ich die einzelnen Zahlenwerte vernünftig in ein Word rein? Ein Array funktioniert ja nicht oder?
Ja, die fertige BCD-Wandlung DWORD_BCD32_TO_DINT bringt hier nichts, weil es umständlicher ist, die einzelnen BCD-Ziffern nibbleweise in einem DWORD zu sammeln. Effizienter ist es, die einzelnen Ziffern gleich beim Empfang zu verarbeiten.

Hier ein Beispiel zum Verarbeiten aller 15 BCD-Ziffern und umwandeln in die 3 Werte ProgNummer, KundenNummer und Gewicht:
bcdziffer : UInt ; die empfangene Ziffer 0..9
count und count_old : UInt; Empfangszähler
zwischenwert, KundenNummer : UDInt;
ProgNummer, Gewicht : der gewünschte Datentyp z.B. UInt;
Code:
IF #count_old <> #count THEN //nur wenn sich der Empfangszähler erhöht (Flanke)
    #count_old := #count;

    CASE #count OF
        2,4,13: #zwischenwert := #bcdziffer;
        ELSE    #zwischenwert := #zwischenwert * 10 + #bcdziffer;
    END_CASE;

    CASE #count OF
        3:  #ProgNummer := UDINT_TO_UINT(#zwischenwert);
        12: #KundenNummer := #zwischenwert;
        16: #Gewicht := UDINT_TO_UINT(#zwischenwert);
    END_CASE;
END_IF;

Harald
 
Zuletzt bearbeitet:
Danke Harald,
ich denke der Code ist ein guter Ansatz. Allerdings passt der so noch nicht, was vielleicht dran liegt, dass ich mich zu unpräzise ausgedrückt hab.
Ich versuche es nochmal anders:

Eine Maschine übergibt ca. alle 3 Minuten einen Posten an eine nachgeschaltene Absortieranlage. Kurz vor der Postenübergabe wird mittles des BCD-Codes die Programmnummer, die Kundennummer und das Gewicht übergeben. Ein Senden-Signal kündigt den Datentransfer an. Danach folgen im 0,5-0,75 Sekunden-Takt die einzelnen BCD-Zahlen ohne Unterbrechung. Ein komplettes Datenpaket könnte zB so aussehen:
410015732650334

Das sind also die Zahlen, welche ich aktuell in ein Array of Dint speichere. Nun zerhacke ich die einzelnen Zahlen und füge sie zu einer Zahlenreihe zusammen. Zum Beispiel so:

41 - Programmnummer
001573265 - Kundennummer
033,4 - Gewicht

Um die jeweilige Zahl an ihren richtigen Platz in der Zahlenreihe zu bekommen habe ich jede Stelle entsprechend mit 10, 100, 1000 usw multipliziert. Somit habe ich am Ende alle Zahlen dort wo ich sie haben möchte und in richtiger Reihenfolge. Ich kann morgen mal den Code hier reinkopieren, dann wird es vielleicht noch deutlicher. Im großen und ganzen bin ich zufrieden, weil es funktioniert, aber ich denke, dass das ich das viel zu kompliziert gelöst habe.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Mein Code passt schon, jedenfalls zu dem was Du oben in #1 und #3 geschrieben hast (ich habe ihn allerdings nicht getestet). Du musst einfach nur immer da, wo Du die empfangene BCD-Ziffer in Dein Array speicherst, die Ziffer an meinen Code übergeben. Nach 15 Ziffern liegen Deine 3 Werte vor und Du kannst sie wegspeichern.
Deine Erklärungen sind allerdings nicht ganz fachgerecht. Was meinst Du mit "Zahlenreihe"? Willst Du vielleicht die Ziffern am Ende in einem String haben?
Genaugenommen erhältst Du auch nicht Zahlen sondern einzelne Ziffern und zu jeder Ziffer ein Übernahme-Signal/Puls.
Warum verwendest Du zum Speichern von 15 Ziffern ein Array of DINT?
Vermutlich verstehen wir Dich besser, wenn Du uns Deinen Code gezeigt hast.

Harald
 
Zuletzt bearbeitet:
Ja dann sollte dein Code funktionieren. Ich habe ihn als Vorlage verstanden um ihn entsprechend zu erweitern.

Möglicherweise ist die Bezeichnung „Zahlenreihe“ nicht fachgerecht. Ich meine damit die Aneinanderreihung von Zahlen in einer Dezimalschreibweise, also einer, zehner, hunderter, tausender usw., wobei die Kundennummer wie gesagt keine Zahl im eigentlichen Sinne ist, sondern eher ein String mit Zahlen. In welchem Format die Kundennummer am Ende vorliegt ist erstmal egal.
Das Array of Dint ist Platzverschwendung, ich weiß, aber ich hab es so gemacht, weil Tia gemeckert hat, als ich es mit Array of Int gemacht hab, weil sich das offenbar mit meiner SCL-Lösung in die Quere kommt.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Bei jeder Ziffer kommt ein Übernahmeimpuls mit. Dann soll das aktuell anliegende Bitmuster als Ziffer übernommen werden. Den Übernahmeimpuls gibt der TE auf einen Zähler und weiß daher, die wievielte Ziffer das ist.
 
Harald hat die Frage bereits richtig beantwortet. Nun wie versprochen die Bilder zu meiner ursprünglichen Frage bzw. meine Lösung.

Das ist der Signalaufbau meines Datenaustauschs:
DFE2FB17-B8D6-4DB8-959D-59CF292C17D4.jpeg

Nun wird im ersten Schritt mein Strobe-Signal gezählt, den Zähler muss ich jetzt nicht extra fotografieren. Mit jeder positiven Flanke des Strobe-Signals wird der Zählwert erhöht. Der Zählwert setzt sich dann selbst zurück wenn der letzte Impuls kommt oder wenn 3 Sekunden kein neuer Impuls kommt.

Den Zahlwert des BCD-Signals ermittle ich mit folgendem Gather-Baustein. Der Eingang ist hier ein Array of Bool, wobei die ersten 4 Bits mein BCD-Signal ist. Als Ergebnis bekomme ich dann den fertigen Zahlenwert.

43BDB8E1-A685-4162-9FA7-4C9CDC79B2CE.jpeg

Anschließend wird bei jedem Impuls der entsprechende Zahlenwert abgespeichert:
F3FEAD5E-F847-4B72-8C81-22BF4195135A.jpeg

Als letzten Schritte bilde ich dann aus den Einzelziffern meine entsprechenden „Zahlenreihen“:
D51B98F0-FF0E-4353-9C90-A74C45D049E0.jpeg
24FC763E-4FDC-4683-8020-1F00F4D45ACE.jpeg
 
Zuletzt bearbeitet:
Moin @KarlMeier
Ein paar Tipps zu Deinem Code:

Wenn man 4-Bit BCD-Signale geschickt durchdacht an Digitaleingänge anschließt, dann braucht man kein uneffizientes GATHER, dann ist das Umwandeln der 4 Bits in eine BCD-Ziffer (Zahlenwert) sehr einfach, z.B.
E0.0 BCD_1
E0.1 BCD_2
E0.2 BCD_4
E0.3 BCD_8
E0.4..E0.7 irgendwas anderes, z.B. einen weiteren BCD-Geber
E0.4 BCD2_1
E0.5 BCD2_2
E0.6 BCD2_4
E0.7 BCD2_8
Code:
#BCD1_Wert := %EB0 AND 16#0F;       // E0.0 .. E0.3
#BCD2_Wert := SHR(IN:=%EB0, N:=4);  // E0.4 .. E0.7

Welchen Datentyp hat Dein #Programmdaten.Programm? Dein TIA gibt da eine Warnung in den Zeilen 11 bis 13

Wenn man Konstanten für Gleitkomma-Operanden verwendet, dann sollte man sie auch in Gleitkomma-Schreibweise mit Dezimalpunkt schreiben. Also nicht 10 sondern 10.0 (z.B. Zeile 39).

Deine vielen Zeilen Code vom Netzwerk 4 kann man noch eine Stufe höher abstrahieren und durch meine paar Zeilen Code von Beitrag #4 ersetzen. Hier mein Code mit Deinen Variablennamen:
Code:
IF #Zählwert_old <> #Zählwert_Strobesignal THEN //nur wenn sich der Empfangszähler ändert (Flanke)
    #Zählwert_old := #Zählwert_Strobesignal;

    CASE #Zählwert_Strobesignal OF
        2,4,13: #Dezimalwert := #BCD_Signal_Ergebnis;
        ELSE    #Dezimalwert := #Dezimalwert * 10 + #BCD_Signal_Ergebnis;
    END_CASE;

    CASE #Zählwert_Strobesignal OF
        3:  #Programmdaten.Programm := UDINT_TO_UINT(#Dezimalwert);
        12: #Programmdaten.Kundennummer := #Dezimalwert;
        16: #Programmdaten.Gewicht_in_kg := UDINT_TO_REAL(#Dezimalwert) / 10.0;
    END_CASE;
END_IF;

Harald
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
Vielen lieben Dank Harald,
das war genau das was ich gesucht hab. Oft sieht man den Wald vor lauter Bäumen nicht. Ich hab noch ein paar Anpassungen gemacht und nun läuft es einwandfrei!

Die Meldung von Tia wegen den verschiedenen Datentypen entstand weil ich mit den Datentypen rumgespielt hab. Ursprünglich war es INT, dann wurde mir aber im Array der Wert zu groß und ich hab das Array in Dint geändert. Im PLC-Datentyp war es aber noch Int, darum hat das nicht gepasst. Da es nur eine Testprogrammierung war die trotzdem funktioniert hat, hat mich das nicht gestört. Mittlerweile ist aber alles bereinigt und ordentlich. Der Code wurde dank Deiner Hilfe massiv verkleinert, genau das hab ich gesucht.

Jetzt muss ich trotzdem nochmal blöd fragen, was genau passiert bei dieser Zuweisung:

#BCD1_Wert := %EB0 AND 16#0F;

Mir ist klar, dass ich das nur benötige, wenn ich zwei BCD-Zahlen gleichzeitig auswerte. Für einen BCD-Code würde die einfache Zuweisung ausreichen:
#BCD1_Wert := %EB0

Beim BCD2_Wert wird das Bitmuster um 4 Stellen verschoben, das hab ich auch kapiert, aber was genau bewirkt dieses AND 16#0F?
Es entspricht dem Wert 15 auch das kann ich logisch dem BCD-Code zuordnen, aber was genau passiert (bitte in Worten erklärt), wenn ich auf der rechten Seite der Zuweisung ein AND benutze???
 
Jetzt muss ich trotzdem nochmal blöd fragen, was genau passiert bei dieser Zuweisung:

#BCD1_Wert := %EB0 AND 16#0F;

Mir ist klar, dass ich das nur benötige, wenn ich zwei BCD-Zahlen gleichzeitig auswerte. Für einen BCD-Code würde die einfache Zuweisung ausreichen:
#BCD1_Wert := %EB0

Beim BCD2_Wert wird das Bitmuster um 4 Stellen verschoben, das hab ich auch kapiert, aber was genau bewirkt dieses AND 16#0F?
Es entspricht dem Wert 15 auch das kann ich logisch dem BCD-Code zuordnen, aber was genau passiert (bitte in Worten erklärt), wenn ich auf der rechten Seite der Zuweisung ein AND benutze???
Die 4 Bits (E0.4 bis E0.7) werden "ausmaskiert".
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Für einen BCD-Code würde die einfache Zuweisung ausreichen:
#BCD1_Wert := %EB0
Nein, nur wenn die Eingänge E0.4 bis E0.7 garantiert 0 sind, z.B. nicht beschaltet. Man wird aber in der Praxis die 4 Eingänge nicht frei lassen (Verschwendung), sondern mit anderen Signalen beschalten. Deshalb müssen die 4 Eingänge aus dem Wert für #BCD1_Wert entfernt (ausgeblendet) werden, damit sie den Wert nicht beeinflussen.

Das %EB0 AND 16#0F ist eine bitweise AND-Verknüpfung der Bitmuster des Bytes %EB0 mit dem Bitmuster von 16#0F.
Das AND 16#0F blendet die oberen 4 Bits aus (setzt sie im Byte auf 0, da wo in 16#0F die 0-Bits sind). Man sagt auch, die unteren 4 Bits werden maskiert (da wo in der Bitmaske 16#0F die 1-Bits sind).
Das SHR(IN:=%EB0, N:=4) schiebt die oberen 4 Bits (von E0.4 bis E0.7) nach Bit 0 bis 3. Weil dabei 0-Bits in die oben frei werdenden Stellen eingeschoben werden, kann man sich das anschließende AND 16#0F zum isolieren/maskieren der 4 Bits sparen.
Code:
  DI EB0    E0.7 6 5 4 3 2 1 0

#BCD1_Wert := %EB0 AND 16#0F;       // E0.0 .. E0.3 --> #BCD1_Wert
  Status EB0   ? ? ? ? 1 0 0 1  = 16#?9
  16#0F        0 0 0 0 1 1 1 1  = 16#0F
  AND ergibt   0 0 0 0 1 0 0 1  = 16#09 = BCD#9 = 9 dez


#BCD2_Wert := SHR(IN:=%EB0, N:=4);  // E0.4 .. E0.7 --> #BCD2_Wert
  Status EB0   1 0 0 1 ? ? ? ?  = 16##9?
  SHR 4 ergibt 0 0 0 0 1 0 0 1  = 16#09 = BCD#9 = 9 dez

Harald
 
Ahhhh jetzt fällt der Groschen!
Du sagst der Zuweisung also damit, dass er immer nur die ersten 4 Bits betrachten soll, der Rest kann somit niemals gezählt werden, weil die UND-Bedingung niemals erfüllt sein wird.

Für diese Lösung mag das gut funktionieren und auch logisch erscheinen. In der Praxis würde ich sowas ehrlich gesagt nicht machen. Da hätte ich ein wenig Bedenken, da es die Sache auf den ersten Blick ziemlich unübersichtlich macht.
Ich hab es jetzt anders gelöst, weil ich meine Eingänge ohnehin schon alle geordnet und vergeben habe. Ich nehme die 4 Bits und weiße sie direkt den ersten vier Bits einer INT-Variable zu. Funktioniert tadellos und empfinde ich als wesentlich übersichtlicher und logischer.
Trotzdem hochinteressant wie das mit dem „Maskieren“ funktioniert. Vielen Dank also für das neue Wissen!
 
Ich nehme die 4 Bits und weiße sie direkt den ersten vier Bits einer INT-Variable zu.
Das ist auf jeden Fall effizienter als das GATHER. Bits in einer INT/UINT-Variable manipulieren ist aber nicht ganz sauber. Zum Bits twiddlen gibt es WORD-Variablen ("Bitstrings").
Code:
#tempWord.%X0 := "BCD_1";
#tempWord.%X1 := "BCD_2";
#tempWord.%X2 := "BCD_4";
#tempWord.%X3 := "BCD_8";
#MyIntVar := WORD_TO_INT(#tempWord AND 16#0F);

Aber ob das nun übersichtlicher als #MyIntVar := WORD_TO_INT(%EB0 AND 16#0F); ist?
Der obige Code mit dem Einzelbits kopieren funktioniert allerdings auch für Fälle, wo die BCD-Signale beliebig verstreut an Digitaleingängen angeschlossen sind.

Harald
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
Genauso hab ich es eigentlich auch gemacht, nur mit dem Unterschied, dass die Lösung mit den Temps in Verbindung mit dem Wort und der Maske auf dem Weg zur INT-Variable alle möglich „Querschläger“ ausschließt, welche mir meine INT-Variable verfälschen könnten. :)
 
Zurück
Oben