TIA Kommunikation Char Array zu Int wandeln

RogerSchw85

Level-2
Beiträge
629
Reaktionspunkte
54
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo zusammen

Ich habe eine TCP Kommunikation zu einer S71515-F. Diese umfasst 8192 Bytes. In diesen 8192 befinden sich viele Integeres die per ";" getrennt sind. Aktuell suche ich im Array von ";" bis ";", kopiere die einzelnen Chars in ein String und wandle den String zu einem Integer. Dies frisst unglaublich viel Zykluszeit.

Gibt es eine bessere Möglichkeit das zu bewerkstelligen?

WSTRING geht nicht da der TCP Partner kein Unicode versenden kann.

Vielen Dank schon mal für die Hilfe
 
Gibt es eine Möglichkeit alle INTs gleich lang zu machen ? Also z.B durch führende Nullstellen. Dann wäre das Suchen hinfällig. Musst du alles in einem Zyklus verarbeiten ? Kannst du z.B nur 800 Bytes pro Zyklus machen aber dagür über mehrere Zyklen laufen lassen ? Kannst du mal deinen hetzigen Code posten ?
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich habe vergessen zu erwähnen das auch bits, reals und Strings übertragen werden. Jedoch ja man könnte alle Variablen auf eine feste länge machen.

Ich muss alle Variablen in einem Zyklus abarbeiten, es sind Variablen die Datenbank Einträge und abfragen auslösen im Produktionsprozess

Code:
(********************************************************* Sucht den Anfang des #Empfangen Strings, decodiert    *
* Decodiert diesen in Bits, int und Real                *
*                                                       *
* Die erste Zahl vom Abschnitt bool, int und Real ist   *
* die anzahl der Variablen die der Abschnitt hat        *
*                                                       *
* Erstellt am: 24.12.2018 R. Schweingruber              *
* Letzte Aenderung:                                     *
********************************************************)


IF (NOT #Empfangen) THEN
    RETURN;
END_IF;


// Falls der erste Eintrag kein S ist -> Stoerung
IF (#Puffer[0] <> 'S') THEN
    RETURN;
END_IF;




// Falls der letzte Eintrag kein E ist -> Stoerung
IF (#Puffer[8191] <> 'E') THEN
    RETURN;
END_IF;


#index := 2;


// Laenge Bereich auslesen
FOR #i := #index TO 8190 DO
    IF (#Puffer[#index + 1] <> ';') THEN
        #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#index]));
        #index += 1;
    ELSE
        #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#index]));
        #index += 1;
        EXIT;
    END_IF;
END_FOR;
STRG_VAL(IN := #zahl,
         FORMAT := W#16#0000,
         P := 1,
         OUT => #anzahlBereich);


#index += 1;
#zahl := '';


// Bool auswerten
FOR #i := 0 TO (#anzahlBereich - 1) DO
    #Daten."Bool"[#i] := false;
    IF (#Puffer[#index] = '1') THEN
        #Daten."Bool"[#i] := true;
    END_IF;
    
    #index += 2;
END_FOR;


#zahl := '';


// Laenge bereich auslesen
FOR #i := #index TO 8190 DO
    IF (#Puffer[#index + 1] <> ';') THEN
        #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#index]));
        #index += 1;
    ELSE
        #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#index]));
        #index += 1;
        EXIT;
    END_IF;
END_FOR;
STRG_VAL(IN := #zahl,
         FORMAT := W#16#0000,
         P := 1,
         OUT => #anzahlBereich);


#zahl := '';
#index += 1;


// Integer auswerten
FOR #i := 0 TO (#anzahlBereich - 1) DO
    // suchen bis +1
    #zahl := '';
    FOR #j := #index TO 8190 DO
        IF (#Puffer[#index + 1] <> ';') THEN
            #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#index]));
            #index += 1;
        ELSE
            #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#index]));
            #index += 1;
            EXIT;
        END_IF;
    END_FOR;
    
    // zahl gefunden
    STRG_VAL(IN := #zahl,
             FORMAT := W#16#0000,
             P := 1,
             OUT => #Daten."Int"[#i]);
END_FOR;


#zahl := '';


// Laenge bereich auslesen
FOR #i := #index TO 8190 DO
    IF (#Puffer[#index + 1] <> ';') THEN
        #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#index]));
        #index += 1;
    ELSE
        #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#index]));
        #index += 1;
        EXIT;
    END_IF;
END_FOR;
STRG_VAL(IN := #zahl,
         FORMAT := W#16#0000,
         P := 1,
         OUT => #anzahlBereich);


#zahl := '';
#index += 1;


// Real auswerten
FOR #i := 0 TO (#anzahlBereich - 1) DO
    // suchen bis +1
    #zahl := '';
    FOR #j := #index TO 8190 DO
        IF (#Puffer[#j + 1] <> ';') THEN
            #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#j]));
            #index += 1;
        ELSE
            #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#j]));
            #index += 1;
            EXIT;
        END_IF;
    END_FOR;
    
    // zahl gefunden
    STRG_VAL(IN := #zahl,
             FORMAT := W#16#0000,
             P := 1,
             OUT => #Daten."Real"[#i]);
END_FOR;


#zahl := '';


// Laenge bereich auslesen
FOR #i := #index TO 8190 DO
    IF (#Puffer[#index + 1] <> ';') THEN
        #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#index]));
        #index += 1;
    ELSE
        #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#index]));
        #index += 1;
        EXIT;
    END_IF;
END_FOR;
STRG_VAL(IN := #zahl,
         FORMAT := W#16#0000,
         P := 1,
         OUT => #anzahlBereich);


#zahl := '';


// Strings auswerten
FOR #i := 0 TO (#anzahlBereich - 1) DO
    // suchen bis +1
    #zahl := '';
    FOR #j := #index TO 8190 DO
        IF (#Puffer[#j + 1] <> ';') THEN
            #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#j]));
            #index += 1;
        ELSE
            #zahl := CONCAT(IN1 := #zahl, IN2 := CHAR_TO_STRING(IN := #Puffer[#j]));
            #index += 1;
            EXIT;
        END_IF;
    END_FOR;
    
    // Zahl gefunden
    #Daten."String"[#i] := #zahl;
END_FOR;
 
Was ist der Partner für ein Gerät? Kannst du es programmieren? Kannst du die Daten nicht binär zur SPS übertragen, dann bräuchtest du nichts umzuwandeln.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Du könntest die Konvertierung zu Strings und von String nach Integer entfernen und selber vornehmen, das würde vermutlich schon einiges bringen.

Kurz hingeschrieben nur für Integer:
Code:
temp_di := 0;
n := 0;
FOR i := 0 TO 8000 DO
  ci := CHAR_TO_INT(Puffer[i]);
  is_digit := (ci >= CHAR_TO_INT('0') AND ci <= CHAR_TO_INT('9'));
  is_semi := c = ';';
  IF NOT (is_digit OR is_semi) THEN
    EXIT;
  END_IF;
  IF is_semi THEN
    "Daten".Int[n] := temp_di;
    temp_di := 0;
    n := n + 1;
  ELSE
    temp_di := temp_di * 10;
    temp_di := temp_di + (ci - INT_TO_DINT(CHAR_TO_INT('0')));
  END_IF;
END_FOR;
 
Zuletzt bearbeitet:
Das hat schon mal extrem geholfen!

Ich habe es nun noch ein wenig angepasst damit auch negative Zahlen verarbeitet werden können:

Code:
// Integer auswertenFOR #i := 0 TO (#anzahlBereich - 1) DO
    // suchen bis +1
    #zahl := '';
    #is_negativ := false;
    FOR #j := #index TO 8190 DO
        #ci := CHAR_TO_INT(#Puffer[#j]);
        #is_digit := (#ci >= CHAR_TO_INT('0') AND #ci <= CHAR_TO_INT('9'));
        #is_semi := #Puffer[#j] = ';';
        IF NOT #is_negativ THEN
            #is_negativ := #Puffer[#j] = '-';
        END_IF;
        IF #is_semi THEN
            #Daten."Int"[#i] := #temp_di;
            IF (#is_negativ) THEN
                #Daten."Int"[#i] := 0 - #Daten."Int"[#i];
            END_IF;
            #temp_di := 0;
            EXIT;
        ELSE
            IF (#is_digit) THEN
                #temp_di := #temp_di * 10;
                #temp_di := #temp_di + (#ci - INT_TO_DINT(CHAR_TO_INT('0')));
            END_IF;
        END_IF;
        #index += 1;
    END_FOR;
END_FOR;

Hast du auch eine ähnliche Lösung für Integer in ein Char Array zu kopieren? weil auch da mache ich es im Moment über Strings...
 
Ich würde an deiner Stelle in der Schleife nur einmal zu Beginn über den Arrayindex auf Puffer[j] zugreifen um diese in eine Temp-Variable zu kopieren, so wie ich es auch gemacht habe, und dann anschließend nur noch mit dieser Variable arbeiten. Ein ganz schlauer Compiler kann das vermutlich so optimieren und merkt sich die Adressberechnung über Zeiger + Index und verwendet sie dann wieder. Ich glaube aber nicht, dass TIA das macht, darum kann man dem Ganzen über die Temp-Variable etwas unter die Arme greifen.

In die andere Richtung kann man vermutlich nicht mehr ganz so viel herausholen, aber ein Versuch ist es wert. Ich würde das folgendermaßen machen:
Code:
FUNCTION "IntArrToChArr" : VOID

VAR_TEMP
    temp_di : DINT;
    i : INT;
    j : INT;
    start : INT;
    stopp : INT;
    n : INT;
    pos : INT;
END_VAR

BEGIN

pos := 0;
FOR i := 0 TO 10 DO // für 10 die Anzahl der Werte im Int-Array einsetzen
    temp_di := "DB_WERTE".WERTE[i];
    IF temp_di < 0 THEN
        "DB_CSV".carr[pos] := '-';
        pos := pos + 1;
        temp_di := temp_di * -1;
    END_IF;
    n := getNumDigits(temp_di);
    start := pos + n - 1;
    stopp := pos;
    FOR j := start TO stopp BY -1 DO
        "DB_CSV".carr[j] := INT_TO_CHAR(DINT_TO_INT(temp_di MOD 10) + CHAR_TO_INT('0'));
        temp_di := temp_di / 10;
    END_FOR;
    pos := start + 1;
    "DB_CSV".carr[pos] := ';';
    pos := pos + 1;
END_FOR;

END_FUNCTION

getNumDigits ist eine Hilfsfunktion die einem vorab sagt, wie viele Stellen die Zahl benötigt. Wenn man das nicht vorher feststellt, müsste man erst in ein Temp-Char Array speichern und anschließend die Reihenfolge ändern. Da ist die Funktion vermutlich schneller.
Code:
FUNCTION "getNumDigits" : INT
VAR_INPUT
    IN : DINT;
END_VAR

VAR_TEMP
    n : INT;
END_VAR

BEGIN

IF IN <= 9 THEN
    n := 1;
ELSIF IN <= 99 THEN
    n := 2;
ELSIF IN <= 999 THEN
    n := 3;
ELSIF IN <= 9999 THEN
    n := 4;
ELSIF IN <= 99999 THEN
    n := 5;
ELSIF IN <= 999999 THEN
    n := 6;
ELSIF IN <= 9999999 THEN
    n := 7;
ELSIF IN <= 99999999 THEN
    n := 8;
ELSIF IN <= 999999999 THEN
    n := 9;
ELSE
    n := 10;
END_IF;
"getNumDigits" := n;
END_FUNCTION
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich würde an deiner Stelle in der Schleife nur einmal zu Beginn über den Arrayindex auf Puffer[j] zugreifen um diese in eine Temp-Variable zu kopieren, so wie ich es auch gemacht habe, . . .
Du hast aber bei der Prüfung auf SemiKolon daneben gegriffen und das ASCII-Zeichen mit der nicht definierten Variablen c verglichen.
Das hat der TE "repariert" und dabei das eingeführt, was Du bemäkelst.
Die Notwendigkeit für 2 ineinander gewurschtelte FOR-Schleifen kann ich auch nicht nachvollziehen, resultiert aber mit Sicherheit daraus, dass Is_negative nur vor der inneren ForSchleife "genullt" wird und dies auch nochmal in der Gegend passieren müsste, wo das Exit steht. Exit und äussere Schleife entfallen dann.
 
Du hast aber bei der Prüfung auf SemiKolon daneben gegriffen und das ASCII-Zeichen mit der nicht definierten Variablen c verglichen.
Das hat der TE "repariert" und dabei das eingeführt, was Du bemäkelst.
Da hast du recht, ich hatte das auch leider nicht getestet sondern nur hingeschrieben. Und mir was das CHAR_TO_INT zu viel Schreibarbeit, darum habe ich einfach eine Integer-Variable ci ergänzt. Das muss man selber beim programmieren nicht unbedingt machen, weil CHAR_TO_INT keine Rechenleistung kostet sondern direkt durch den Zahlenwert ersetzt wird, manchmal macht es das aber übersichtlicher.

Wenn er auch noch Real-Zahlen verarbeiten möchte, muss er auch anders vorgehen. Zur Zeit kann ein "-" auch mitten in der Zahl stehen, was ich erst mal nicht erlauben würde. Man müsste sich dazu eigentlich einen kleinen Parser schreiben, der sich auch die Zustände merkt.
Ich glaube es gibt oder gab auf den Siemens-Seiten auch mal einen FAQ mit entsprechendem Code um Daten im Json-Format in der SPS zu verarbeiten, vielleicht lässt sich daraus etwas wiederverwenden, zumindest wenn dort halbwegs laufzeitoptimiert programmiert wurde.
 
Du hast aber bei der Prüfung auf SemiKolon daneben gegriffen und das ASCII-Zeichen mit der nicht definierten Variablen c verglichen.
Das hat der TE "repariert" und dabei das eingeführt, was Du bemäkelst.
Die Notwendigkeit für 2 ineinander gewurschtelte FOR-Schleifen kann ich auch nicht nachvollziehen, resultiert aber mit Sicherheit daraus, dass Is_negative nur vor der inneren ForSchleife "genullt" wird und dies auch nochmal in der Gegend passieren müsste, wo das Exit steht. Exit und äussere Schleife entfallen dann.

Du hast recht die äussere FOR Schleife könnte man weglassen. Jedoch stört sie mich überhaupt nicht. Auch finde ich den Code gut lesbar so.

Zur Zeit kann ein "-" auch mitten in der Zahl stehen, was ich erst mal nicht erlauben würde. Man müsste sich dazu eigentlich einen kleinen Parser schreiben, der sich auch die Zustände merkt.

Das "-" kommt nur am Anfang, deshalb funktioniert es so. Real sind im Moment nicht wichtig, da nur etwa 10 Reals übertragen werden.

Die beiden Maßnahmen haben mir die SPS markant entlastet. Ich spare mindestens 10ms Zykluszeit, was etwa 30% ist.

Was ich nicht verstehe ist, warum du über DINT (temp_di) gehst und dort keine Ints hast?
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Was ich nicht verstehe ist, warum du über DINT (temp_di) gehst und dort keine Ints hast?
Weil das die "allgemeinere" Form ist. Wenn's für DINT funktioniert und schnell genug ist, dann ist es auch für den INT-ZahlenBereich OK und lediglich "überqualifiziert".
Ob INT oder DINT im Endeffekt schneller ist, das steht in den Sternen. Man stellt sich zwar vor, dass die Bearbeitung von 16 Bit (INT) schneller sein muss als die Bearbeitung von 32 Bit (DINT), aber es kann auch genau umgekehrt sein oder völlig gleich.
Das müsste man am besten nachmessen, wenn man auf eine möglichst schnelle Bearbeitung angewiesen ist.
 
Zuletzt bearbeitet:
Zurück
Oben