Lasse Hedeby

Sharing my passion for automation, programming, IT and other geeky stuff

Simple CRC32 function for SIMATIC controllers.


FUNCTION "FCCRC32Calc" : DWord
  { S7_Optimized_Access := 'TRUE' }
  VERSION : 0.1
  VAR_INPUT
    data : Array[0..4095] of Byte;
    length : Int;
  END_VAR

  VAR_TEMP
    rem : DWord;
    i : Int;
    j : Int;
  END_VAR

  VAR CONSTANT
    POLYNOMIAL : DWord := 16#ABCDEF01;  // Chose your polynomial here
  END_VAR

  BEGIN
  #rem := 16#ffffffff;
  FOR #i := 0 TO (#length-1) DO
    #rem := #rem XOR #data[#i];
    FOR #j := 0 TO 7 DO
      IF #rem.%X31 THEN // if leftmost (most significant) bit is set
        #rem := SHR(IN := #rem, N := 1) XOR #POLYNOMIAL;
      ELSE
        #rem := SHR(IN := #rem, N := 1);
      END_IF;
    END_FOR;
  END_FOR;
  #FCCRC32Calc := #rem XOR 16#ffffffff;
END_FUNCTION

If the function does not work as expected, it’s often because the endianess is not correct between sender and receiver. Change the #rem.%X31 bit to #rem.%X0 to see if it helps.

March 28th, 2018

Posted In: Automation, Coding, S7-1200/1500, Security, SIMATIC, Structured Text (SCL/ST)

Tags: , , ,

A CAB printer can receive print jobs via Ethernet Raw using it’s own JScript syntax. Each command is separated by a line.

In order to do this from a SIMATIC S7-300 PLC, I’ve made an array of string (1-20) representing each line. Then the commands can be written directly in the code, and with CONCAT function dynamic data from tags can be added.

 //
 // Generate lines
 //
 statLines[1] := 'J';                                               // Line of JScript
 statLines[2] := 'S l1;0,0,50,53,85;ErrLbl';                        // Line of JScript
 statLines[3] := 'H 125,0,T,R0,B0';                                 // Line of JScript
 statLines[4] := 'O R,P';                                           // Line of JScript
 statLines[5] := 'T 0,7.77,0,3,4,u,k,q80;[J:c38.25]Production ID';  // Line of JScript
 statTempLine := 'B 8.16,12.58,0,datamatrix,1;';                    // 1. part of line of JScript
 statLines[6] := CONCAT(IN1:=statTempLine,IN2:=statProductCode);    // Combine 1. part with product code
 statLines[7] := 'T 36.16,8.02,0,3,4,u,k,q80;[J:c48.84]Error Code'; // Line of JScript
 statTempLine := 'T 37.6,14.64,0,3,6,k,q80;[J:c47.4]';              // 1. part of line of JScript
 statLines[8] := CONCAT(IN1:=statTempLine,IN2:=statErrorCode);      // Combine 1. part with error code
 statTempLine := 'T 0,46.47,0,3,4,k,q80;[J:c78.93]';                // Line of JScript
 statLines[9] := CONCAT(IN1:=statTempLine,IN2:=statProductCode);    // Combine 1. part with product code
 statLines[10] := 'G38.59,3.52,0;R:44.26,38.33,0.2,0.2';            // Line of JScript
 statLines[11] := 'A 1';                                            // Line of JScript 
 statLines[12] := '';
 statLines[13] := '';
 statLines[14] := '';
 statLines[15] := '';
 statLines[16] := '';
 statLines[17] := '';
 statLines[18] := '';
 statLines[19] := '';

Now each line can be combined into a array of characters and send as a TCP telegram using the T-Blocks (TCON, TSEND, TRCV etc.). What I do is to run a loop through each line and move each character to an array. At the end of each line I add a Carriage Return (0xD) to the array.

 // Convert all lines to array of charactors for telegram handling
 FOR statIndexLines := 1 TO 11 DO
   // Find length of string
   statStringLength := LEN(statLines[statIndexLines]);
 
   // Run through string and move every charactor to array
   IF (statStringLength <> 0) THEN
    FOR statIndexLength := 1 TO statStringLength DO
     // Get charactor from string and move to array of charactors
     statSendCharactors[statMemoryCharIndex] := STRING_TO_CHAR(MID(IN:=statLines[statIndexLines],L:=1,P:=statIndexLength));
 
     // Increment charactor index for every cycle
     statMemoryCharIndex := (statMemoryCharIndex + 1);
    END_FOR;
   END_IF;
 
   // Add Carriage Return after each line
   statSendCharactors[statMemoryCharIndex] := '$R';
   // Increment charactor index
   statMemoryCharIndex := (statMemoryCharIndex + 1); 
 END_FOR;

The block itself handles the TCP/IP communication and acts as client.

Attachements:

 

August 3rd, 2016

Posted In: Automation, IT, S7-300/400, SIMATIC, Structured Text (SCL/ST)

Tags: , , , , ,

Randomized storage is used if an item can be stored at any empty location in the warehouse. There is no specific location where the products have to be stored, but in practice this strategy usually means that when an item arrives at the warehouse it will be stored at the nearest appropriate warehouse location.
– http://www.fbk.eur.nl/OZ/LOGISTICA/stor.html

This storage strategy is often used in fully automated storges units, and it is also what I will address in this post.

Just to clarify – I have no experience what so ever in storage solutions. This is just a thought that has evolved to a challenge that I find interesting and now I am making my first draft on how to solve this task made for a S7-1200/1500 PLC in TIA Portal and SIMOTION SCOUT.

My initial idea was to use a multi dimensional array for data storage. This can be a 3 dimensional array or 2 dimensional array depending on the physical layout of the storage unit. But I would prefer to use 2 dimensional array of a struct to make it fast and to make it more user friendly for other programmers than the author.

 

Multi_Array_Def

The code for running through this array looks like this;


FOR #columns := 1 TO 12 DO
  FOR #rows := 1 TO 8 DO
    #data := "dGlobalStorage".gasItems[#columns,#rows];
  END_FOR;
END_FOR;

In this example I assume the same tool to be used for delivery and pickup on the storage crane/pick & place unit. So therefore we need to deliver an item before we can pick one up. We know the location where to pickup out item, so that location is our start point of finding the nearest free location. But because we have to deliver before pickup and we have a starting point we need to set up a set of rules to order the search pattern;

  1. Search in a straight line from start point to end point (linear interpolation).
  2. Search within the same row of the end point
  3. Search within the same column of the end point
  4. Search in a spiral (snail shell) starting in the end point

Now we know how many search patterns we need. I will make these as individual functions, and then call them in the main sequence. Then it is not that difficult to re-arrange the order if it makes sense. Example is that in a vertical storage it is faster to travel in horisontal lines because you have gravity on one axis, but in a vertical storage this is not an issue.

1. Search in a straight line from start point to end point (linear interpolation)

The search in a straight line from start to end can be done by linear interpolation. By knowing one axis (in this case the columns) we can interpolate the other axis (rows) with this equation;

    \[ y= y_0 + (y_1 - y_0) \frac{x-x_0}{x_1-x_0} \]

Where x_0,y_0 is starting point and x_1,y_1 is ending point.

1st-interpolation

 

So by using the above mentioned equation we can interpolate the y values based on the x value. Now by rounding the result using tie-breaking rounding rule (round half up) we get the integer part without fraction to be used in the array;

    \[ \begin{array}{c|c} x & y \\ \hline 1 & 1,00 \\ 2 & 1,67 \\ 3 & 2,33 \\ 4 & 3,00 \\ 5 & 3,67 \\ 6 & 4,33 \\ 7 & 5,00 \\ \end{array} \to \begin{array}{c|c} x & y \\ \hline 1 & 1 \\ 2 & 2 \\ 3 & 2 \\ 4 & 3 \\ 5 & 4 \\ 6 & 4 \\ 7 & 5 \\ \end{array} \]

By widening the span of the search window in every cycle until either the endpoint row and column is reached or a free location is found the search area can be expanded;

2nd_interpolation 3rd_interpolation
4th_interpolation 5th_interpolation

// Reset variables
#freeLocation.column := -1;
#freeLocation.row := -1;
#freeLocationFound := False;
#tempSearchCount := 0;
 
// Interpolate locations in the x-axis (columns)
IF (#startPoint.column < #endPoint.column) THEN
  FOR #tempColumn := #startPoint.column TO #endPoint.column DO
  //                   x - x0
  // y = y0 + (y1 - y0)-------
  //                   x1 - x0
  #tempIPOResult := INT_TO_REAL(#startPoint.row) +
                   (INT_TO_REAL(#endPoint.row) - INT_TO_REAL(#startPoint.row)) *
                   ((INT_TO_REAL(#tempColumn) - INT_TO_REAL(#startPoint.column)) / (INT_TO_REAL(#endPoint.column) - INT_TO_REAL(#startPoint.column)));

  #tempSearchCount := (#tempSearchCount + 1);

  #tempSearchLocations[#tempSearchCount].rowMin := DINT_TO_INT(ROUND(#tempIPOResult));
  #tempSearchLocations[#tempSearchCount].column := #tempColumn;
  #tempSearchLocations[#tempSearchCount].rowMax := #tempSearchLocations[#tempSearchCount].rowMin;
  END_FOR;
ELSE
  FOR #tempColumn := #startPoint.column TO #endPoint.column BY -1 DO
    //                   x - x0
    // y = y0 + (y1 - y0)-------
    //                   x1 - x0
    #tempIPOResult := INT_TO_REAL(#startPoint.row) +
                     (INT_TO_REAL(#endPoint.row) - INT_TO_REAL(#startPoint.row)) *
                     ((INT_TO_REAL(#tempColumn) - INT_TO_REAL(#startPoint.column)) / (INT_TO_REAL(#endPoint.column) - INT_TO_REAL(#startPoint.column)));

    #tempSearchCount := (#tempSearchCount + 1);

    #tempSearchLocations[#tempSearchCount].rowMin := DINT_TO_INT(ROUND(#tempIPOResult));
    #tempSearchLocations[#tempSearchCount].column := #tempColumn;
    #tempSearchLocations[#tempSearchCount].rowMax := #tempSearchLocations[#tempSearchCount].rowMin;
  END_FOR;
END_IF;

// Search loop
// A loop counter is added and checked against constant MAX_LOOPS to avoid
// an infinit loop causing cycle time exclusion
#tempSearchLoopCount := 0;
REPEAT
  // Loop counter
  #tempSearchLoopCount :=#tempSearchLoopCount + 1;
 
  // Loop thorugh all searches
  FOR #tempSearchIndex := 1 TO #tempSearchCount DO
    // Loop from rowMin to rowMax in a column based on the interpolation
    // 
    FOR #tempRow := #tempSearchLocations[#tempSearchIndex].rowMin TO #tempSearchLocations[#tempSearchIndex].rowMax DO
      // Debug: Add number to location for debugging
      IF (#storage.typeStorage[#tempRow,#tempSearchLocations[#tempSearchIndex].column].sgIdentification = '') THEN
        #storage.typeStorage[#tempRow,#tempSearchLocations[#tempSearchIndex].column].sgIdentification := INT_TO_STRING(#tempSearchLoopCount);
      END_IF;
 
      // Check if location is free
      IF NOT #storage.typeStorage[#tempRow,#tempSearchLocations[#tempSearchIndex].column].boItem THEN
        #freeLocation.column := #tempSearchLocations[#tempSearchIndex].column;
        #freeLocation.row := #tempRow;
        #freeLocationFound := True;
        RETURN;
      ELSE
        // Widen search span for next loop
        // Startpoint is left-down or right-down from end point
        IF
          ((#startPoint.column < #endPoint.column) AND (#startPoint.row < #endPoint.row)) OR
          ((#startPoint.column > #endPoint.column) AND (#startPoint.row < #endPoint.row))
        THEN
          // Only increment if it is below max row
          IF (#tempSearchLocations[#tempSearchIndex].rowMax < #endPoint.row) THEN
            #tempSearchLocations[#tempSearchIndex].rowMax := #tempSearchLocations[#tempSearchIndex].rowMax + 1;
          END_IF;
 
          // Only decrement if it is above min row
          IF (#tempSearchLocations[#tempSearchIndex].rowMin > #startPoint.row) THEN
            #tempSearchLocations[#tempSearchIndex].rowMin := #tempSearchLocations[#tempSearchIndex].rowMin - 1;
          END_IF;
        END_IF;
        // Startpoint is left-up or right-up from end point
        IF
          ((#startPoint.column < #endPoint.column) AND (#startPoint.row > #endPoint.row)) OR
          ((#startPoint.column > #endPoint.column) AND (#startPoint.row > #endPoint.row))
        THEN
          // Only increment if it is below max row
          IF (#tempSearchLocations[#tempSearchIndex].rowMax < #startPoint.row) THEN
            #tempSearchLocations[#tempSearchIndex].rowMax := #tempSearchLocations[#tempSearchIndex].rowMax + 1;
          END_IF;

          // Only decrement if it is above min row
          IF (#tempSearchLocations[#tempSearchIndex].rowMin > #endPoint.row) THEN
            #tempSearchLocations[#tempSearchIndex].rowMin := #tempSearchLocations[#tempSearchIndex].rowMin - 1;
          END_IF;
        END_IF;
      END_IF;
    END_FOR;
  END_FOR;
 
  // Check when to stop searching
  // Startpoint is left-down or right-down from end point
  IF
    ((#startPoint.column < #endPoint.column) AND (#startPoint.row < #endPoint.row)) OR
    ((#startPoint.column > #endPoint.column) AND (#startPoint.row < #endPoint.row))
  THEN
    IF ((#tempSearchLocations[1].rowMax > #endPoint.row) AND (#tempSearchLocations[#tempSearchCount].rowMin < #startPoint.row)) THEN
      EXIT;
    END_IF;
  END_IF;
 
  // Startpoint is left-up or right-up from end point
  IF
    ((#startPoint.column < #endPoint.column) AND (#startPoint.row > #endPoint.row)) OR
    ((#startPoint.column > #endPoint.column) AND (#startPoint.row > #endPoint.row))
  THEN
    IF (#tempSearchLocations[1].rowMin < #endPoint.row) AND (#tempSearchLocations[#tempSearchCount].rowMax > #startPoint.row) THEN
      EXIT;
    END_IF;
  END_IF;
UNTIL (#tempSearchLoopCount >= #MAX_LOOPS)
END_REPEAT;

2. Search within the same row of the end point

This is very simple. Just loop through all columns in the same row as the ending point, starting from the starting point column;

horizontal_search

 


// Reset output
#freeLocation.column := -1;
#freeLocation.row := -1;
#freeLocationFound := False;
 
IF #fullSearch THEN
  // Loop from starting point to endpoint
  FOR #tempRow := #minRow TO #maxRow DO
    // Debug: Add number to location for debugging
    IF (#storage.typeStorage[#tempRow, #endPoint.column].sgIdentification = '') THEN
      #storage.typeStorage[#tempRow, #endPoint.column].sgIdentification := INT_TO_STRING(1);
    END_IF;
 
    IF NOT #storage.typeStorage[#tempRow, #endPoint.column].boItem THEN
      #freeLocation.row := #tempRow;
      #freeLocation.column := #endPoint.column;
      #freeLocationFound := True;
      RETURN; // Stop search when found
    END_IF;
  END_FOR;
ELSE
  // If starting point is less than end point then increment search
  IF (#startPoint.row < #endPoint.row) THEN
    // Loop from starting point to endpoint
    FOR #tempRow := #startPoint.row TO #endPoint.row DO
      // Debug: Add number to location for debugging
      IF (#storage.typeStorage[#tempRow, #endPoint.column].sgIdentification = '') THEN
        #storage.typeStorage[#tempRow, #endPoint.column].sgIdentification := INT_TO_STRING(1);
      END_IF;
 
      IF NOT #storage.typeStorage[#tempRow, #endPoint.column].boItem THEN
        #freeLocation.row := #tempRow;
        #freeLocation.column := #endPoint.column;
        #freeLocationFound := True;
        RETURN; // Stop search when found
      END_IF;
    END_FOR;
  ELSE // Decrement search
    // Loop from starting point to endpoint
    FOR #tempRow := #startPoint.row TO #endPoint.row BY -1 DO
      // Debug: Add number to location for debugging
      IF (#storage.typeStorage[#tempRow, #endPoint.column].sgIdentification = '') THEN
      #storage.typeStorage[#tempRow, #endPoint.column].sgIdentification := INT_TO_STRING(1);
      END_IF;
 
      IF NOT #storage.typeStorage[#tempRow, #endPoint.column].boItem THEN
        #freeLocation.row := #tempRow;
        #freeLocation.column := #endPoint.column;
        #freeLocationFound := True;
        RETURN; // Stop search when found
      END_IF;
    END_FOR;
  END_IF;
END_IF;

3. Search within the same column of the end point

Same procedure as search within the same row;

vertical_search

 

// Reset output
#freeLocation.column := -1;
#freeLocation.row := -1;
#freeLocationFound := False;
 
// If full search
IF #fullSearch THEN
  // Loop in all columns
  FOR #tempColumn := #minColumn TO #maxColumn DO
    // Debug: Add number to location for debugging
    IF (#storage.typeStorage[#endPoint.row, #tempColumn].sgIdentification = '') THEN
      #storage.typeStorage[#endPoint.row, #tempColumn].sgIdentification := INT_TO_STRING(1);
    END_IF;
 
    IF NOT #storage.typeStorage[#endPoint.row, #tempColumn].boItem THEN
      #freeLocation.row := #endPoint.row;
      #freeLocation.column := #tempColumn;
      #freeLocationFound := True;
      RETURN; // Stop search when found
    END_IF;
  END_FOR;
ELSE
  // If starting point is less than end point then increment search
  IF (#startPoint.column < #endPoint.column) THEN
    // Loop from starting point to endpoint
    FOR #tempColumn := #startPoint.column TO #endPoint.column DO
      // Debug: Add number to location for debugging
      IF (#storage.typeStorage[#endPoint.row, #tempColumn].sgIdentification = '') THEN
        #storage.typeStorage[#endPoint.row, #tempColumn].sgIdentification := INT_TO_STRING(1);
      END_IF;
 
      IF NOT #storage.typeStorage[#endPoint.row, #tempColumn].boItem THEN
        #freeLocation.row := #endPoint.row;
        #freeLocation.column := #tempColumn;
        #freeLocationFound := True;
        RETURN; // Stop search when found
      END_IF;
    END_FOR;
  ELSE // Decrement search
    // Loop from starting point to endpoint
    FOR #tempColumn := #startPoint.column TO #endPoint.column BY -1 DO
      // Debug: Add number to location for debugging
      IF (#storage.typeStorage[#endPoint.row, #tempColumn].sgIdentification = '') THEN
        #storage.typeStorage[#endPoint.row, #tempColumn].sgIdentification := INT_TO_STRING(1);
      END_IF;
 
      IF NOT #storage.typeStorage[#endPoint.row, #tempColumn].boItem THEN
        #freeLocation.row := #endPoint.row;
        #freeLocation.column := #tempColumn;
        #freeLocationFound := True;
        RETURN; // Stop search when found
      END_IF;
    END_FOR;
  END_IF;
END_IF;

4. Search in a spiral (snail shell) starting in the ending point

This approach search in an outgoing spiral starting at the ending point like a snail shell;

spiral_search

 

// Reset
#freeLocation.column := -1;
#freeLocation.row := -1;
#freeLocationFound := False;
#tempDirection := #D_DOWN;
#tempCountDirectionChange := 0;
#tempAdd := 1;
#tempLoopCount := 0;
#tempRow := #endPoint.row;
#tempColumn := #endPoint.column;
 
// Spiral search loop
REPEAT
  // Loop counter (Only used for debugging)
  #tempLoopCount := (#tempLoopCount + 1);
 
  // Calculate addition and direction change
  IF (#tempCountDirectionChange = 2) THEN
    #tempAdd := (#tempAdd + 1);
    #tempCountDirectionChange := 0;
  END_IF;
 
  // Loop direction
  CASE #tempDirection OF
    #D_DOWN:
      #tempRow := (#tempRow - 1);
      // Count loops in this direction
      #tempDirectionCount := (#tempDirectionCount + 1);
      // Change direction
      IF (#tempDirectionCount = #tempAdd) THEN
        #tempCountDirectionChange := (#tempCountDirectionChange + 1);
        #tempDirectionCount := 0;
        #tempDirection := #D_LEFT;
      END_IF;
 
    #D_LEFT:
      #tempColumn := (#tempColumn - 1);
      // Count loops in this direction
      #tempDirectionCount := (#tempDirectionCount + 1);
      // Change direction
      IF (#tempDirectionCount = #tempAdd) THEN
        #tempCountDirectionChange := (#tempCountDirectionChange + 1);
        #tempDirectionCount := 0;
        #tempDirection := #D_UP;
      END_IF;
 
    #D_UP:
      #tempRow := (#tempRow + 1);
      // Count loops in this direction
      #tempDirectionCount := (#tempDirectionCount + 1);
      // Change direction
      IF (#tempDirectionCount = #tempAdd) THEN
        #tempCountDirectionChange := (#tempCountDirectionChange + 1);
        #tempDirectionCount := 0;
        #tempDirection := #D_RIGHT;
      END_IF;
 
    #D_RIGHT:
      #tempColumn := (#tempColumn + 1);
      // Count loops in this direction
      #tempDirectionCount := (#tempDirectionCount + 1);
      // Change direction
      IF (#tempDirectionCount = #tempAdd) THEN
        #tempCountDirectionChange := (#tempCountDirectionChange + 1);
        #tempDirectionCount := 0;
        #tempDirection := #D_DOWN;
      END_IF;
  END_CASE;
 
  // Check location
  IF
    (#tempRow <= #maxRow) AND
    (#tempRow >= 1) AND
    (#tempColumn <= #maxColumn) AND
    (#tempColumn >= 1)
  THEN
    // Debug: Add number to location for debugging
    IF (#storage.typeStorage[#tempRow, #tempColumn].sgIdentification = '') THEN
      #storage.typeStorage[#tempRow, #tempColumn].sgIdentification := INT_TO_STRING(#tempLoopCount);
    END_IF;
    // Check if location is free
    IF NOT #storage.typeStorage[#tempRow, #tempColumn].boItem THEN
      #freeLocation.row := #tempRow;
      #freeLocation.column := #tempColumn;
      #freeLocationFound := True;
      RETURN;
    END_IF;
  END_IF;
UNTIL (#tempLoopCount >= #MAX_LOOPS)
END_REPEAT;

By using these functions it is possible to make a prioritized search.

Attachments:

 

March 25th, 2016

Posted In: Automation, S7-1200/1500, SIMATIC, SIMOTION, Structured Text (SCL/ST)

Tags: , , , , ,

Next Page »