Step 7 12 Stellige BCD Zahl in DINT wandeln

Maagic7

Level-2
Beiträge
429
Reaktionspunkte
221
Zuviel Werbung?
-> Hier kostenlos registrieren
Das ist ein ziemlich 'geiler' Code für ne S7-300, um 12-stellige BCD Zahlen in einen DINT zu verwandeln.
Sowas kann nur einem (P)C-Programierer eingefallen sein.

Hat da jemand was SPS taugliches zur Hand?

Wozu man das braucht? Ganz einfach! Es gibt 'sehr intelligente' Geräte, die über den Bus Werte als unendliche Lange
BCD Zahlen schicken statt als DINT! Wem so etwas bescheuertes einfällt, weis ich leider auch nicht!
Mein Gefühl sagt mir (P)C-Programmierer

Code:
VAR_INPUT
    BcdChars : ARRAY[1..6] OF BYTE;
END_VAR

VAR_OUTPUT
    diValue : DINT;
END_VAR

VAR
    HexString: ARRAY[0..15] OF STRING;
    BcdStr : STRING;
END_VAR

BEGIN

    HexString[0] := '0';
    HexString[1] := '1';
    HexString[2] := '2';
    HexString[3] := '3';
    HexString[4] := '4';
    HexString[5] := '5';
    HexString[6] := '6';
    HexString[7] := '7';
    HexString[8] := '8';
    HexString[9] := '9';
    HexString[10] := 'A';
    HexString[11] := 'B';
    HexString[12] := 'C';
    HexString[13] := 'D';
    HexString[14] := 'E';
    HexString[15] := 'F';


        BcdStr := '';
        BcdStr := CONCAT(IN1 := BcdStr, IN2 := HexString[BYTE_TO_INT(BcdChars[6]) / 16]);
        BcdStr := CONCAT(IN1 := BcdStr, IN2 := HexString[BYTE_TO_INT(BcdChars[6]) MOD 16]);
        BcdStr := CONCAT(IN1 := BcdStr, IN2 := HexString[BYTE_TO_INT(BcdChars[5]) / 16]);
        BcdStr := CONCAT(IN1 := BcdStr, IN2 := HexString[BYTE_TO_INT(BcdChars[5]) MOD 16]);
        BcdStr := CONCAT(IN1 := BcdStr, IN2 := HexString[BYTE_TO_INT(BcdChars[4]) / 16]);
        BcdStr := CONCAT(IN1 := BcdStr, IN2 := HexString[BYTE_TO_INT(BcdChars[4]) MOD 16]);
        BcdStr := CONCAT(IN1 := BcdStr, IN2 := HexString[BYTE_TO_INT(BcdChars[3]) / 16]);
        BcdStr := CONCAT(IN1 := BcdStr, IN2 := HexString[BYTE_TO_INT(BcdChars[3]) MOD 16]);
        BcdStr := CONCAT(IN1 := BcdStr, IN2 := HexString[BYTE_TO_INT(BcdChars[2]) / 16]);
        BcdStr := CONCAT(IN1 := BcdStr, IN2 := HexString[BYTE_TO_INT(BcdChars[2]) MOD 16]);
        BcdStr := CONCAT(IN1 := BcdStr, IN2 := HexString[BYTE_TO_INT(BcdChars[1]) / 16]);
        BcdStr := CONCAT(IN1 := BcdStr, IN2 := HexString[BYTE_TO_INT(BcdChars[1]) MOD 16]);
        IF (LEN(BcdStr) > 1) AND (LEFT(IN := BcdStr, L := 1) = 'F') THEN
            BcdStr := DELETE(IN := BcdStr, L := 1, P := 1);
            WHILE (LEN(BcdStr) > 1) AND (LEFT(IN := BcdStr, L := 1) = '0') DO
                BcdStr := DELETE(IN := BcdStr, L := 1, P := 1);
            END_WHILE;
            BcdStr := CONCAT(IN1 := '-', IN2 := BcdStr);
        END_IF;
        diValue:=STRING_TO_DINT(BcdStr);
END
 
Ach, der furchtbare Code funktioniert nicht..

Wozu man das braucht? Ganz einfach! Es gibt 'sehr intelligente' Geräte, die über den Bus Werte als unendliche Lange
BCD Zahlen schicken statt als DINT! Wem so etwas bescheuertes einfällt, weis ich leider auch nicht!
So bescheuert ist das gar nicht. Bei BCD-Zahlen kann ein Mensch notfalls fast im Klartext mitlesen welche Zahlen da übertragen werden.

Wie ist denn die Dokumentation des 12-stelligen BCD-Strings bzw. der 6 Byte BcdChars? Die Ziffern kommen in einem 6 Byte langen little Endian (quasi von hinten nach vorne) und müssen Vornullen enthalten, weil ein DINT höchstens 10 Stellen haben kann. Ich vermute, das 6. Byte kann/darf nur die Werte 16#00 für eine positive Zahl und 16#F0 für eine negative Zahl enthalten. Positive Zahlen werden von dem Code wahrscheinlich nicht umgewandelt (Fehler!), weil da immer ein String mit 12 Zeichen zusammengebastelt wird, am Ende das STRING_TO_DINT aber nur Strings mit max 11 Zeichen frist (10 Ziffern + Vorzeichen). Bei negativen Zahlen werden die Vornullen aus dem String entfernt, bei positiven Zahlen nicht. Eigentlich muß nur genau eine Vornull entfernt werden - das hat der Programmierer aber nicht erkannt.

Das Programm ist durch die Wahl des Umwegs über Stringverarbeitung extrem uneffizient programmiert, und auch logisch leicht "lala", z.B. werden von negativen Zahlen die Vornullen entfernt, von positiven Zahlen aber nicht, oder nachdem ein String mit immer 12 Zeichen zusammengebastelt wurde, wird gefragt, ob die Länge > 1 ist :ROFLMAO:

Wenn man den SCL-Code so ändert daß man gleich die einzelnen Zeichen verarbeitet anstatt mit Strings zu hantieren, sinkt die Programmgröße schon mal auf weniger als ein Fünftel (17%, 464 Byte anstatt 2722 Byte habe ich erreicht :cool: ) (Ganz zu schweigen vom möglichen Wegfall der String-Verarbeitungs-FCs CONCAT, LEN, LEFT, DELETE, aber vielleicht werden die im Projekt schon verwendet?).

In AWL könnte man noch effizienter programmieren als der SCL-Compiler und käme vielleicht auf nur noch 15% der originalen Programmgröße. Den Code versteht dann aber niemand "on the fly".

Harald
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Doku hab ich dazu gar nicht!
Das ist ein Ausschnitt aus einem original Codesys Programm, welches so 1:1 auf S7-SCL übertragen wurde.

Das mit den den BCDChars() ist ein Fehler von mir: das sind natürlich HEX-Werte.
Im original ist das gar nicht dokumentiert was das macht! Das muss man einfach erkennen!

Der original Code funktioniert korrekt und ich hab das auch mit dem Originalcode in Funktion.
Zum besseren Verständnis hab ich das etwas angepasst und nur den wichtigen
Teil aus dem original Kontext kopiert.

Der original Code hat um 14 KB und ist in mererlei Hinsicht problematisch für S7. Das verschlingt mal knapp
100kB in Summe an Programm und Datenspeicher nur um ein Gerät auszulesen.
Auf ner Beckhoff anscheinend kein Problem. Auf ner S7 schon.
Bei mehreren Geräten auslesen sprengt das allein schon den Speicher der S7.
Ich hab schon mal die Stringlängen auf die notwendige Länge reduziert. Das brint schon einiges.

Wie man das effektiv auf einer S7 erledigt, da fällt mir bestimmt noch was ein!
Schnell mal was eintippen und geht, war leider nicht!

Wenn es aber jemand schon zufällig parat hat, kann ich mich um andere Dinge kümmern.
 
OK! Ich hege Verständnis für den Programmier!
Nachdem ich die erste Version des eigen Versuchs übersetzt bekommen hab.

Code:
FUNCTION m7_BCD_TO_DINT : DINT

VAR_INPUT
    inBCDs : ARRAY[1..6] OF BYTE;
END_VAR

VAR_TEMP
    // temporäre Variablen
    I      : DINT;   // Schleifenvariable
    tmpDI  : DINT;   // unser DINT Wert
    dBCD   : DINT;
    Err    : BOOL;   // Fehler (ungüpltige BCD-Ziffer)
    negative: BOOL; // BCD-Zahl ist negativ
    dFakt  : DINT;  // Faktor für 1er, 10er, 100er ...

    BCDhi  : WORD;
    BCDlo  : WORD;
END_VAR

    Err := FALSE;
    negative := FALSE;
    tmpDI := 0;
    dFAKT := 1;  // Faktor für (1er, 10er),(100er, 1000er) ...
   
    negative := (inBCDs[6] AND B#16#F0) = B#16#F0;
    
    FOR I:=1 TO 6  DO
        
        BCDhi := BYTE_TO_WORD( SHR(IN:=inBCDs[I], N:=4) );  // höherwertige BCD Ziffer
        BCDlo := BYTE_TO_WORD( inBCDs[I] AND B#16#F);        // niederwertigere BCD Ziffer

        // Achtung BCD-Ziffern müssen bei S7 unbedingt auf korrekt 0..9 geprüft werden,
        // da bei inkorrekten Werten die CPU in STOP geht
        IF WORD_TO_INT(BCDhi) > 9 THEN
            BCDhi := 0;                 // bei I=6 ist das dann das Vorzeichen 'F'   
            Err := (I<>6) OR NOT negative ;  // Bei negativ und höchster Ziffer ist das kein Fehler
        END_IF;
  
        IF WORD_TO_INT(BCDlo) > 9 THEN
            BCDlo := 0;     
            Err := TRUE;
        END_IF;

        dBCD := + INT_TO_DINT(BCD_TO_INT(BCDhi)*10) + INT_TO_DINT(BCD_TO_INT(BCDhi));
        tmpDi := tmpDi + dBCD * dFakt;        
        dFakt := dFakt *100;        // ShiftLeft BCD 2 Digits
 
    END_FOR;
    
    IF negative THEN tmpDI := -tmpDI; END_IF;

    m7_BCD_TO_DINT := tmpDI;
 
    OK := NOT Err;
    
END_FUNCTION
 
Das ist ein Ausschnitt aus einem original Codesys Programm, welches so 1:1 auf S7-SCL übertragen wurde.
Ah, das passiert öfter, daß ST-Code und SCL-Code nicht exakt gleich funktioniert. Die Norm 61131-3 läßt viele Auslegungen und eigene Erweiterungen zu.

Das mit den den BCDChars() ist ein Fehler von mir: das sind natürlich HEX-Werte.
Kein Fehler, es ist doch eine BCD-Bytefolge. Damit die Umwandlung zu DINT funktioniert, muß die Bytefolge 1 Vorzeichen-Nibble (0 oder F) + 11 BCD-Nibbles (0..9) enthalten, wobei das Nibble nach dem Vorzeichen 0 sein muß.

Sieht der Code so mehr SPS-like aus?
Code:
TITLE ='BCD12_DINT'
[COLOR="#008000"]//wandelt BCD-Bytefolge mit 10 Ziffern + Vorzeichen in DINT[/COLOR]
AUTHOR : 'PN/DP'
VERSION : '1.0'

VAR_INPUT
  BcdChars : ARRAY[1..6] OF BYTE;
END_VAR
VAR_OUTPUT
  diValue : DINT;
END_VAR
VAR_TEMP
  BcdStr : STRING[12];
  cBcdStr AT BcdStr : STRUCT
    MaxLen : BYTE;
    ActLen : BYTE;
    TxtC : ARRAY[1..12] OF CHAR;
  END_STRUCT;
  wBcdStr AT BcdStr : STRUCT
    LenHeader : Word;
    TxtW : ARRAY[1..6] OF WORD;
  END_STRUCT;
  i : INT;
  c : BYTE;
END_VAR

  [COLOR="#008000"]//Byte-Array BcdChars zu ASCII-String entpacken[/COLOR]
  wBcdStr.LenHeader := INT_TO_WORD(12 * 256 + 12); [COLOR="#008000"]//MaxLen + ActLen sind jeweils 12[/COLOR]
  FOR i := 1 TO 6 DO
    c := BcdChars[7-i];
    wBcdStr.TxtW[i] := (INT_TO_WORD( BYTE_TO_INT(c) * 16 ) OR c) AND 16#0F0F OR 16#3030;
  END_FOR;

  [COLOR="#008000"]//Vorzeichen[/COLOR]
  IF cBcdStr.TxtC[1] = '?' THEN  [COLOR="#008000"]//BcdChars[6] war Fx (F0?) --> negative Zahl[/COLOR]
    cBcdStr.TxtC[1] := '-';
  END_IF;

  [COLOR="#008000"]//eine Vornull entfernen, weil STRING_TO_DINT nur Strings mit max 11 Zeichen verarbeitet[/COLOR]
  IF cBcdStr.TxtC[2] = '0' THEN
    FOR i := 2 TO 11 DO
      cBcdStr.TxtC[i] := cBcdStr.TxtC[i + 1];
    END_FOR;
    cBcdStr.ActLen := 11;
  END_IF;

  [COLOR="#008000"]//Umwandeln String in DINT[/COLOR]
  diValue := STRING_TO_DINT(BcdStr);
Harald
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Dein Code rechnet falsch wegen einem Tippfehler:
Code:
        dBCD := + INT_TO_DINT(BCD_TO_INT(BCDhi)*10) + INT_TO_DINT(BCD_TO_INT(BCD[COLOR="#FF0000"][U]hi[/U][/COLOR]));
Normalerweise hätte ich auch versucht, die 6 Zeichen direkt in DINT umzuwandeln, wenn man sowieso jedes Zeichen einzeln anfasst (irgendwo hier im Forum gibt es da auch einen Code von mir, ich finde ihn nur nicht wieder). Gestern Abend hatte ich jedoch keine Lust mehr das Programm komplett neu zu entwerfen, ich wollte es nur effizienter schreiben.

Harald
 
Für S7 ist das eine echt elegante Lösung!

Ich denk ich hab es gelöst, muss nur noch testen. Ich brauch einen Code der sowohl auf S7 als auch auf Codesys funktioniert.
Das Problem ist, dass keine 2 unterschiedlichen Codes für S7 und CodeSys gepflegt werden sollen.
Änderungen werden nur in den Originalcode übernommen, wenn das 1:1 auf beiden System läuft.

Nur so bringt das was für andere, die das auch benötigen. Momentan braucht man eine 319er CPU damit das mit dem
original Code läuft.

Ich hab die original Stringversion und unsere Versionen mal getrennt übersetzt:

original StringVersion: 2626 Byte
PN/DP String : 464 Byte
meine Rechenversion: 550 Byte

hier noch deine Version als fertige Function
Code:
FUNCTION BCD12_DINT : DINT
TITLE ='BCD12_DINT'
//wandelt BCD-Bytefolge mit 10 Ziffern + Vorzeichen in DINT
AUTHOR : 'PN/DP'
VERSION : '1.0'

VAR_INPUT
  BcdChars : ARRAY[1..6] OF BYTE;
END_VAR
VAR_OUTPUT
  diValue : DINT;
END_VAR
VAR_TEMP
  BcdStr : STRING[12];
  cBcdStr AT BcdStr : STRUCT
    MaxLen : BYTE;
    ActLen : BYTE;
    TxtC : ARRAY[1..12] OF CHAR;
  END_STRUCT;
  wBcdStr AT BcdStr : STRUCT
    LenHeader : Word;
    TxtW : ARRAY[1..6] OF WORD;
  END_STRUCT;
  i : INT;
  c : BYTE;
END_VAR

    //Byte-Array BcdChars zu ASCII-String entpacken
    wBcdStr.LenHeader := INT_TO_WORD(12 * 256 + 12); //MaxLen + ActLen sind jeweils 12
    FOR i := 1 TO 6 DO
        c := BcdChars[7-i];
        wBcdStr.TxtW[i] := (INT_TO_WORD( BYTE_TO_INT(c) * 16 ) OR c) AND 16#0F0F OR 16#3030;
    END_FOR;
    
    //Vorzeichen
    IF cBcdStr.TxtC[1] = 'F' THEN  //BcdChars[6] war Fx (F0?) --> negative Zahl
        cBcdStr.TxtC[1] := '-';
    END_IF;
    
    //eine Vornull entfernen, weil STRING_TO_DINT nur Strings mit max 11 Zeichen verarbeitet
    IF cBcdStr.TxtC[2] = '0' THEN
    FOR i := 2 TO 11 DO
        cBcdStr.TxtC[i] := cBcdStr.TxtC[i + 1];
    END_FOR;
    
    cBcdStr.ActLen := 11;
    END_IF;
    
    //Umwandeln String in DINT
    BCD12_DINT := STRNG_DI(BcdStr);
  
END_FUNCTION
 
Sieht gelöst aus! Ist aber ohne den Umweg über den BCD-String defintiv aufwändiger
zu programmieren und Testen.

Hier die Version mit kompletter Fehlerabfrage auf gültige BCD und DINT Wertebereich.
Mit Testbaustein zum generieren des BCD_ARRAY.
Hat jetzt 656 Byte statt vorher 2626.

Code:
FUNCTION CreateTestBCD : VOID

// Bausteinparameter
VAR_INPUT
    BCD_1 : BYTE;
    BCD_2 : BYTE;
    BCD_3 : BYTE;
    BCD_4 : BYTE;
    BCD_5 : BYTE;
    BCD_6 : BYTE;
END_VAR

VAR_OUTPUT
    BCDs : ARRAY [1..6] OF BYTE; // Ausgangsparameter

END_VAR



BEGIN    // Anweisungsteil

    BCDs[1] := BCD_1;
    BCDs[2] := BCD_2;
    BCDs[3] := BCD_3;
    BCDs[4] := BCD_4;
    BCDs[5] := BCD_5;
    BCDs[6] := BCD_6;
   
END_FUNCTION

Das ist die fertige Konvertierungsfunktion (das müsste so auch auf CodeSys laufen)

Code:
// {SetOKFlag := 'y' }

FUNCTION m7_BCD_TO_DINT : DINT


VAR_INPUT
    inBCDs : ARRAY[1..6] OF BYTE;  // 1:Lo..6:Hi; Array with the BCD digits (with sign max 12 digits)
END_VAR

VAR_OUTPUT

END_VAR

VAR_TEMP
    I      : DINT;   // LOOP COUNTER
    tmpDI  : DINT;   // Our DINT value
    diBCD  : DINT;   // Value of a 2-digit BCD value
    Err    : BOOL;   // Error (BCD-digit not valid; NOT{0..9})
    negative: BOOL;  // BCD is negative
    dFact  : DINT;   // Factor for BCD_TO_INT (1_digit, 10_digit, 100_digit ...)

    BCDhi  : WORD;   // Value of hi BCD digit of a BYTE
    BCDlo  : WORD;   // Value of lo BCD digit of a BYTE
END_VAR


    Err := FALSE;
    negative := FALSE;
    tmpDI := 0;
    dFact := 1;  // Faktor für (1er, 10er),(100er, 1000er) ...
   
    // A BCD can contain a maximum of 10 digits and a sign to fit into a DINT
     I := 0;
    REPEAT 
        I := I + 1;   // inBCDs[1..6]

        BCDhi := BYTE_TO_WORD( SHR(IN:=inBCDs[I], N:=4) );   // Value of hi BCD digit
        BCDlo := BYTE_TO_WORD( inBCDs[I] AND B#16#F);        // Value of lo BCD digit
        
        // Caution BCD digits must be checked for correct range {0..9}
        // PLCs may stop if BCD contains invalid digits
    
        IF WORD_TO_INT(BCDhi) > 9 THEN          // IF BCD_DIGIT IS NOT VALID
            IF BCDhi = B#16#F THEN
                negative := TRUE;
            ELSE
                Err := TRUE;                    // invalid BCD digit
            END_IF;
            BCDhi := 0;                                   
        END_IF;
  
        IF WORD_TO_INT(BCDlo) > 9 THEN          // IF BCD_DIGIT IS NOT VALID
            IF BCDlo = B#16#F THEN
                negative := TRUE;
                BCDHi := 0;                     // ignore BCDhi if BCDlo is the sign '-'
            ELSE
                Err := TRUE;                    // invalid BCD digit
            END_IF;
            BCDlo := 0;     
        END_IF;
 
        // calculate the DINT value of the 2 BCD digits only up to 10 digits max
        // digit 11..12 is the '-' at 10 digit long BCDs
        IF I<= 5 THEN
            diBCD := INT_TO_DINT(BCD_TO_INT(BCDhi)*10) + INT_TO_DINT(BCD_TO_INT(BCDlo));
  
            IF I = 5 THEN           // Check overflow if 10digit BCD
                IF diBCD > 21 THEN  // 21-47-48-36-47 = DW#16#7FFFFFFF; maxDINT
                    Err := TRUE;
                ELSIF diBCD = 21 THEN
                    IF tmpDI > 47483647 THEN ERR := TRUE; END_IF;
                END_IF;
            END_IF;         

            tmpDi := tmpDi + diBCD * dFact;  // add the value of the 2 digits in the right position of the DINT   
            dFact := dFact *100;             // 100 it's ShiftLeftBCD(myBCD,2_digits) as DINT
        END_IF; // dFact = {1, 100, 10.000, 1.000.000, 100.000.000}

    UNTIL negative OR Err OR (I = 6)
    END_REPEAT;
   
    IF negative THEN        // add the negative sign to the DINT
        tmpDI := -tmpDI;
    END_IF;                

    IF Err THEN
       m7_BCD_TO_DINT := -2147483648;  // DW#16#80000000
    ELSE
       m7_BCD_TO_DINT := tmpDI;
    END_IF;
    
    OK := NOT Err;      //  OK-Bit = TRUE if BCD is converted without an Error! If OK is FALSE, the returned DINT is not valid!
    
END_FUNCTION
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich brauch einen Code der sowohl auf S7 als auch auf Codesys funktioniert.
Das Problem ist, dass keine 2 unterschiedlichen Codes für S7 und CodeSys gepflegt werden sollen.
Änderungen werden nur in den Originalcode übernommen, wenn das 1:1 auf beiden System läuft.
Hmm, das wird schwierig, weil es in Codesys-ST das AT nicht so gibt wie in SCL, und es andererseits in SCL keine UNION gibt. (von den Pointer-Möglichkeiten von ST ganz abgesehen) Da würden einige effiziente Lösungswege wegfallen.

Mir schwebt da eine Weiterentwicklung meiner Lösung mit direkter Konvertierung in DINT unter Wegfall der externen STRING_TO_DINT-Function vor, doch ohne AT wird das wieder ineffizienter als ich möchte... hmm, mal sehen.

Harald
 
Mir schwebt da eine Weiterentwicklung meiner Lösung mit direkter Konvertierung in DINT unter Wegfall der externen STRING_TO_DINT-Function vor
Hier nun die neue Version, ergibt 686 Byte Programmcode. Allerding hatte ich den Aufwand für die Sonderbehandlung der letzten Ziffer arg unterschätzt, der macht hier fast ein Drittel (200 Byte) des gesamten Programms aus. Für das Entpacken der BCD-Nibbles zu Bytes habe ich auch eine alternative Lösung ohne Word-Array, die braucht kein AT und ist unabhängig vom Endian, müsste so auch in Codesys-ST funktionieren, ist aber 38 Byte länger.
Code:
FUNCTION BCDL_TO_DINT : DINT
TITLE ='BCDL_TO_DINT'
[COLOR="#008000"]//wandelt little-Endian BCD-Bytefolge mit 10 Ziffern + Vorzeichen in DINT
//Vorzeichen-Byte darf nur 16#F0 (für negativ) oder 16#00 sein
//Spezialfälle: -2147483648 ist zulässig, -0 ist zulässig und ergibt 0[/COLOR]
AUTHOR : 'PN/DP'
VERSION : '1.2'

VAR_INPUT
  BcdChars : ARRAY[1..6] OF BYTE; [COLOR="#008000"]// |90|78|56|34|12|V0| für -2'147'483'648 .. +2'147'483'647[/COLOR]
END_VAR
VAR
  Ziffern : ARRAY[0..9] OF BYTE;
  wZiffern AT Ziffern : ARRAY[0..4] OF WORD;
  diValue : DINT;
  i : DINT;
  c : BYTE;
  v : BYTE;
  z : INT;
  isneg : BOOL;
  error : BOOL;
END_VAR

  [COLOR="#008000"]// Vorzeichen-Byte darf nur 16#F0 (für negativ) oder 16#00 sein[/COLOR]
  v := BcdChars[6];
  isneg := v = 16#F0;
  error := v <> 16#00 AND NOT isneg;

  IF NOT error THEN

    [COLOR="#008000"]// 5 Bytes ---> 10 Ziffern entpacken[/COLOR]
    FOR i := 0 TO 4 DO
      c := BcdChars[5-i];
      wZiffern[i] := (INT_TO_WORD( BYTE_TO_INT(c) * 16 ) OR c) AND 16#0F0F;
[COLOR="#008000"]      // Alternative ohne AT-Word-Array (38 Byte länger):
      // Ziffern[i+i] := INT_TO_BYTE(BYTE_TO_INT(c) / 16);
      // Ziffern[i+i+1] := c AND 16#F;[/COLOR]
    END_FOR;

    [COLOR="#008000"]// 9 Ziffern in DINT wandeln[/COLOR]
    diValue := 0;
    FOR i := 0 TO 8 DO
      z := BYTE_TO_INT(Ziffern[i]);
      IF z > 9 THEN  [COLOR="#008000"]//nur Ziffern 0..9 zulässig[/COLOR]
        error := TRUE;
        EXIT;  [COLOR="#008000"]//Konvertierung abbrechen[/COLOR]
      ELSE
        diValue := diValue * 10 + z;
      END_IF;
    END_FOR;

  END_IF;  [COLOR="#008000"]//NOT error[/COLOR]
  IF NOT error THEN

[COLOR="#008000"]    // 10. Ziffer Spezial-Behandlung
    // wenn hier diValue
    //  > 214748364 ---> error (Wert wird auf jeden Fall zu groß)
    //  = 214748364 geht nur noch 0..7, und 8 nur wenn negativ
    //  < 214748364 geht mit allen Ziffern[/COLOR]
    z := BYTE_TO_INT(Ziffern[9]);
    IF z > 9 OR diValue > 214748364 OR (diValue = 214748364 AND z = 9) THEN
      error := TRUE;

    ELSIF diValue = 214748364 AND z = 8 THEN
      IF isneg THEN      [COLOR="#008000"]//Spezialfall -2147483648 (16#80000000)[/COLOR]
        diValue := -2147483648;
        isneg := FALSE;  [COLOR="#008000"]//weiter unten nicht nochmal negieren![/COLOR]
      ELSE
        error := TRUE;
      END_IF;

    ELSE  [COLOR="#008000"]// < 214748364 OR (= 214748364 AND z = 0..7) geht![/COLOR]
      diValue := diValue * 10 + z;
    END_IF;

    IF isneg THEN  [COLOR="#008000"]//falls negatives Vorzeichen[/COLOR]
      diValue := -diValue;
    END_IF;

  END_IF;  [COLOR="#008000"]//NOT error[/COLOR]

  [COLOR="#008000"]// Fehler aufgetreten? --> 0 zurückgeben[/COLOR]
  IF error THEN
    diValue := 0;          [COLOR="#008000"]//ggf. weitere Signalisierung? OK-Flag?[/COLOR]
  END_IF;

  BCDL_TO_DINT := diValue; [COLOR="#008000"]//Rückgabe DINT-Wert oder 0 bei Fehler[/COLOR]

  OK := OK AND NOT error;  [COLOR="#008000"]//OK ist ENO: Wert ist nur gültig wenn OK = TRUE[/COLOR]

END_FUNCTION

Harald
 
Erfolgsmeldung!
Hab die ganze Software jetzt online in die Anlage eingespielt läuft!

Der Ursprungscode war
Programmcode: gute 60kB / Instanzdaten ca. 100kB

Das hab ich jetzt durch entfernen der ganzen Stringoperationen eingedampft
Programmcode 33kb Instanzdaten 27kB


Da würde noch mehr gehen!
Ich hab aber wieder Platz genug für mein eigentliches Steuerungsporgramm,
somit wird das erst mal auf Eis gelegt!
 
Zuviel Werbung?
-> Hier kostenlos registrieren
( Die 6-Byte-BCD-Bytefolge kommt in little-Endian (das niederwertigste Byte zuerst, das Vorzeichen zuletzt). Für verständlichere Darstellung drehe ich im Folgenden die Reihenfolge und füge '-' in die Bytefolge ein. )

Sieht gelöst aus! Ist aber ohne den Umweg über den BCD-String defintiv aufwändiger
zu programmieren und Testen.

Hier die Version mit kompletter Fehlerabfrage auf gültige BCD und DINT Wertebereich.
Testergebnisse dazu:

Bei manchen Bytefolgen rechnet Dein Programm recht eigenwillig, so daß BCD-Formatfehler nicht erkannt werden z.B.
00-12-34-56-78-9F ergibt L#0
00-12-34-5F-78-90 ergibt L#-7890
AB-CD-EF-5F-78-90 ergibt L#-7890

Dein Programm akzeptiert den Wert F an jeder Stelle der Bytefolge und wertet es dann als Vorzeichen '-', und stoppt die Konvertierung am ersten F von hinten und ignoriert die Ziffern davor --> die dürfen dann jeden beliebigen Wert haben. Daß (ausschließlich!) negative BCD-Bytefolgen eine dynamische Länge haben können/dürfen, hast Du nirgends erwähnt...

99-01-01-01-01-01 ergibt L#101010101
99-12-34-56-78-90 ergibt L#1234567890
9F-12-34-56-78-90 ergibt L#-1234567890
F9-12-34-56-78-90 ergibt L#-1234567890
xF-12-34-56-78-90 ergibt L#-1234567890
Was im ersten Byte steht wird weitgehend ignoriert solange es Ziffern 0..9 sind oder F. Wenn die zweite/rechte Ziffer F ist dann darf die erste/linke Ziffer jeden beliebigen Wert haben

F0-21-47-48-36-48 wird als Fehler abgewiesen, obwohl das ein zulässiger DINT-Wert L#-2147483648 wäre


Meine Programm-Varianten (z.B. #14) akzeptieren (nach dem Drehen) nur Bytefolgen nach diesem Muster:
F0-99-99-99-99-99
00-99-99-99-99-99

F0 bzw 00 müssen F0 oder 00 sein, 9 steht für 0..9 an der Stelle


Code:
        // Caution BCD digits must be checked for correct range {0..9}
        [COLOR="#FF0000"]// PLCs may stop if BCD contains invalid digits[/COLOR]
    
        [COLOR="#0000FF"]IF WORD_TO_INT(BCDhi) > 9 THEN[/COLOR]          // IF BCD_DIGIT IS NOT VALID
            IF BCDhi = B#16#F THEN
                negative := TRUE;
            ELSE
                Err := TRUE;                    // invalid BCD digit
            END_IF;
            BCDhi := 0;                                   
        END_IF;
  
        [COLOR="#0000FF"]IF WORD_TO_INT(BCDlo) > 9 THEN[/COLOR]          // IF BCD_DIGIT IS NOT VALID
            IF BCDlo = B#16#F THEN
                negative := TRUE;
                BCDHi := 0;                     // ignore BCDhi if BCDlo is the sign '-'
            ELSE
                Err := TRUE;                    // invalid BCD digit
            END_IF;
            BCDlo := 0;     
        END_IF;
 
        // calculate the DINT value of the 2 BCD digits only up to 10 digits max
        // digit 11..12 is the '-' at 10 digit long BCDs
        IF I<= 5 THEN
            diBCD := INT_TO_DINT([COLOR="#FF0000"]BCD_TO_INT[/COLOR](BCDhi)*10) + INT_TO_DINT([COLOR="#FF0000"]BCD_TO_INT[/COLOR](BCDlo));
Du hast vorher schon Werte > 9 ausgeschlossen, der Wert kann nur noch 0..9 sein, das "gefährliche" BCD_TO_INT ist überflüssig.

Harald
 
Danke erst mal für den Umfangreichen Test. Irgendwann ist man da selbst Betriebsblind.

Ein weiters Danke noch dafür dass du die Tragweite eines evtl Fehlers in
einem solchen Programm direkt erkennst und das weitergibst.
Das Zeug läuft wenn es im Einsatz ist im Kraftwerks- und
Energieerzeugungsbereich. Und da gehören Fehler auf keinen Fall hin!!!

--------------------------------------------------------------------
Testergebnisse dazu:

Bei manchen Bytefolgen rechnet Dein Programm recht eigenwillig, so daß BCD-Formatfehler nicht erkannt werden z.B.
00-12-34-56-78-9F ergibt L#
00-12-34-5F-78-90 ergibt L#-7890
AB-CD-EF-5F-78-90 ergibt L#-7890

Das war druchaus absicht, dass ich nach dem '-' die Zeichen einfach ignoriert hab!
Eigentlich ist das eine ungültige Eingabe.
Das Problem: im orignal Datenstrom sind die BCD-Kolonnen nicht immer gleich lang.
D.h. das '-' ist nicht immer vorne es kann auch F0-12-34 oder F0-01 vorkommen.
Theoretisch ist das '-' aber immer die linke Ziffer im Byte.
Beim ersten '-' muss man defintiv abbrechen:
- Entweder es ist das '-'
- oder es ist eine ungültige Ziffer.


Dein Programm akzeptiert den Wert F an jeder Stelle der Bytefolge und wertet es dann als Vorzeichen '-', und stoppt die Konvertierung am ersten F von hinten und ignoriert die Ziffern davor
--> die dürfen dann jeden beliebigen Wert haben. Daß (ausschließlich!) negative BCD-Bytefolgen eine dynamische Länge haben können/dürfen, hast Du nirgends erwähnt...

// ich hab das nochmals in meinem Testprogramm laufen lassen! Das hatte ich übersehen!!
// den Grund warum das keinen Fehler gibt muss ich noch kontrollieren!
99-01-01-01-01-01 ergibt L#101010101 // das sollte eigentlich eine Fehler gebe!!!
99-12-34-56-78-90 ergibt L#1234567890 // das auch
9F-12-34-56-78-90 ergibt L#-1234567890
F9-12-34-56-78-90 ergibt L#-1234567890
xF-12-34-56-78-90 ergibt L#-1234567890 // das gäbe keinen Fehler, da nach dem '-' abgebrochen wird!

Was im ersten Byte steht wird weitgehend ignoriert solange es Ziffern 0..9 sind oder F.
Wenn die zweite/rechte Ziffer F ist dann darf die erste/linke Ziffer jeden beliebigen Wert haben // ja das ist so, da die linke dann ehr nicht mehr relevant ist!


F0-21-47-48-36-48 wird als Fehler abgewiesen, obwohl das ein zulässiger DINT-Wert L#-2147483648 wäre
// ja, das war absicht und das hab ich bei mir in der Endversion auch entsprechend dokumentiert.
// das war eine gute Methode um weg vom ENO eine schnelle Fehleranzeige da reinzubirngen.
// da es sich um Messwerte handelt kann ein L#-2147483648 nich auftreten, dass allein wäre schon ein Fehler!

BCD_TO_INT muss entfallen, das war das Ziel, da BCD_TO_INT in CodeSys evtl. gar nicht funktioniert!!!
 
Zurück
Oben