Lasse Hedeby

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

Although SIMOTION does not support Modbus out-of-the-box, it does support TCP/IP socket communication. Siemens has not made a library for Modbus TCP/IP, so when I needed one, I had to make it myself.

What is Modbus and how does it works? I started reading the Protocol Specification and then the TCP/IP Implementation Guide from Modbus.org

Protocol Specification

This document describes the protocol and how it works, disregard of communication form. The Modbus protocol  is a simpel protocol data unit called PDU (nice right?) that consists of a function code and some data.

PDU

The additional fields is added when mapping the protocol on different networks or busses. In this case we use TCP/IP.

TCP/IP Implementation Guide

This document describes how to map the modbus protocol onto a TCP socket communication. When doing so an additional field is added called MBAP Header.

modbus_tcp_adu

The MBAP Header contains;

mbap_header

MODBUS TCP/IP Client for SIMOTION

The function block I made for SIMOTION only implements the functions codes 0x03 (Read Holding Registers) and 0x10 (Write Multiple Registers). Others I don’t need. I used the MB_CLIENT for S7-1200/1500 from Siemens as templates, hence the naming is similar and not following the programing style guide from PLCOpen.

Because we use TCP the function also requires the LCom Library for SIMOTION from Siemens. I could use the system functions for TCP but, it is just easier to use LCom since Siemens are maintaining it, and it works. It can be downloaded here:

https://support.industry.siemens.com/cs/ww/en/view/48955385

Also the help content provided in TIA Portal for the MB_CLIENT block can be used, as the parameters behave the same.

Attachments:

 

May 11th, 2016

Posted In: Automation, SIMOTION, 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: , , , , ,

I’ve made a random key generator for a S7-1200 PLC. It uses a random integer generator from Siemens to select the character position in a string of charactors and then add this particular character to a new string. This new string is the unique key.

The random integer generator reads the realtime clock in the CPU and uses the nanosecond data type for random integer generator.

This is not at truly random, unique safe generator that can be used in cryptography or other ways to secure anything. It can add an extra level of security but it can also be predicted.

// Reset in every cycle
#tempError  := False;
#tempStatus := #NO_JOB;

// Rising edge on execute
#statExecuteTrig   := #execute AND NOT #statExecuteMemory;
#statExecuteMemory := #execute;

// Generate key on rising edge
IF #statExecuteTrig THEN
  // Define list of charactors
  #tempCharactors := '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210-';
  #tempCharLength := LEN(#tempCharactors);

  // Charactor loop
  #tempString := '';
  #statRandomInt := 0;
  FOR #tempIndex := 0 TO (#length - 1) DO
    #tempRepeat := 0; // Prevent cycle time overflow
    REPEAT
      #tempRepeat := #tempRepeat + 1;
      // Generate a random number for charactor index
      #tempRandomInt := "LGF_RandomInt"(minValue := 1, maxValue := #tempCharLength);
    UNTIL (#statRandomInt <> #tempRandomInt) OR (#tempRepeat > 4) // Prevent dublicates next to each other
    END_REPEAT;
    // Add to string
    #tempString := CONCAT(IN1 := #tempString, IN2 := MID(IN := #tempCharactors, P := #tempRandomInt, L := 1));
    // Save last random int
    #statRandomInt := #tempRandomInt;
  END_FOR;

  // Write key and length
  #uniqueKey  := #tempString;
  #keyLength  := LEN(#tempString);
  #tempStatus := #JOB_COMPLETE;
END_IF;

// Reset if not execute
IF NOT #execute THEN
  #uniqueKey := '';
  #keyLength := 0;
END_IF;

// Update outputs
#error  := #tempError;
#status := #tempStatus;

Attachements:

 

February 20th, 2016

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

Tags: , , , ,

« Previous PageNext Page »