Stacker Crane upgrade discussion

Hi @brunovgr, @janbumer1, and @amjavi6,

Given how detailed our discussion about improving the Stacker Crane has become, I think it would be best to create a dedicated post for it. This will help keep all the information regarding this topic in one place, making our discussion easier to follow.

2 Likes

@brunovgr

Also, we’d love to get your input on the control of the stacker crane. What are your ideas? What would make it feel more realistic or practical from a PLC programming perspective? Your feedback would be very valuable at this stage.

The 0-10 V signal for these types of applications is awkward, because you run head on into floating point errors, measurement resolution (quantization) errors and measurement noise problems, so you’d be unable to accurately position an axis once a certain distance has been reached.

For example, if we take a stacker crane with an axis length of 100m, and you want to position it with an accuracy of 1 mm, you’d have to partition the 10V signal into 100’000 increments. This will result in a step size of 0.1 mV. With a standard 16 bit AI card you’d usually have 15 bits of resultion (1 bit for positive/negative), so you could only partition your 0-10V range into 31’768 steps, or 0.3 mV. Already you are unable to position your axis to a precision of 1 mm. Add noise and other transmission problems with analog signals to the mix, and a precise, repeatable positioning of an axis would be quite hard to do. The stacker crane in Factory I/O only has a movement distance of 10m, which would result in a 1 mV step size. But here we run into another resolution problem. We have no way of representing an exact 1 mV step using a 15 bit signal range. The closest we could get it to it is 0.91 mV or 1.22 mV. Your average quantization error would be 0.15 mV if I’m not mistaken.

What is usually done is that you would use a Bus for both the position or speed setpoint and then for the position feedback. There’s two different typical setups here:

  1. Drives with integrated positioning system
  2. Drives without integrated positioning system but with speed control

The first setup is the state of the art for applications like this I’d say. You use a Bus to tell the drive the desired target position and some additional parameters and then start the positioning, and the drive positions to the desired target position and then gives you a feedback once it has reached the position. Usually the drive also gives you the current position of the axis directly as a converted signal, like for example as an Integer representing Millimeters or tenths of Millimeters.
The second setup is a little older I’d say, where you have a drive where you can only tell it the target speed and desired ramp and then you’d have a separate position measurement system that tells you the current measured position via a Bus. You would then use the PLC to control the speed of the drive to reach the desired position. But here too you would, for example tell the drive the speed in rpm directly as an integer, and you would get the position as an integer representing, for example, millimeters.

Edit:
So for a concrete example for what I would have in mind, take the stacker crane as an example. For me these would be a minimum configs that makes sense with the current, fixed system in regards to which signals are offered by what:

Tag Controller I/O Type Description
X Target Position Output UInt32 Target position in mm along X-Axis (Horizontal Axis)
Y Target Position Output UInt32 Target position in mm along Y-Axis (Vertical Axis)
Z Target Position Output Int32 Target position in mm along Z-Axis (Fork)
X Current Position Input UInt32 Current position in mm along X-Axis (Horizontal Axis)
Y Current Position Input UInt32 Current position in mm along Y-Axis (Vertical Axis)
Z Current Position Input Int32 Current position in mm along Z-Axis (Fork)
X Axis in Position Input Bool X-Axis has reached target position
Y Axis in Position Input Bool Y-Axis has reached target position
Z Axis in Position Input Bool Z-Axis has reached target position
X Front Limit Input Bool NC, Crane at front limit (Safety)
X Rear Limit Input Bool NC, Crane at back limit (Safety)
Y Upper Limit Input Bool NC, Crane at upper limit (Safety)
Y Lower Limit Input Bool NC, Crane at lower limit (Safety)
Z Left Limit Input Bool NC, Fork at left limit (Safety)
Z Middle Positon Input Bool NC, Fork in middle position (Safety)
Z Right Limit Input Bool NC, Fork at right limit (Safety)
Fork Left Obstructed Input Bool Pallet or obstacle to in left direction of Fork
Fork Right Obstructed Input Bool Pallet or obstacle to in right direction of Fork
Fork Loaded Input Bool Load on Fork

Or, alternatively:

Tag Controller I/O Type Description
X Speed Setpoint Output Int32 Target speed in mm/s along X-Axis (Horizontal Axis)
Y Speed Setpoint Output Int32 Target speed in mm/s along Y-Axis (Vertical Axis)
Z Speed Setpoint Output Int32 Target speed in mm/s along Z-Axis (Fork)
X Maximum Acceleration Output UInt32 Maximum acceleration in mm/s² for X-Axis (Horizontal Axis)
Y Maximum Acceleration Output UInt32 Maximum acceleration in mm/s² for Y-Axis (Vertical Axis)
Z Maximum Acceleration Output UInt32 Maximum acceleration in mm/s² for Z-Axis (Fork)
X Current Position Input UInt32 Current position in mm along X-Axis (Horizontal Axis)
Y Current Position Input UInt32 Current position in mm along Y-Axis (Vertical Axis)
Z Current Position Input Int32 Current position in mm along Z-Axis (Fork)
X Axis Moving Input Bool X-Axis is moving (Actual speed not zero)
Y Axis Moving Input Bool Y-Axis is moving (Actual speed not zero)
Z Axis Moving Input Bool Z-Axis is moving (Actual speed not zero)
X Front Limit Input Bool NC, Crane at front limit (Safety)
X Rear Limit Input Bool NC, Crane at back limit (Safety)
Y Upper Limit Input Bool NC, Crane at upper limit (Safety)
Y Lower Limit Input Bool NC, Crane at lower limit (Safety)
Z Left Limit Input Bool NC, Fork at left limit (Safety)
Z Middle Positon Input Bool NC, Fork in middle position (Safety)
Z Right Limit Input Bool NC, Fork at right limit (Safety)
Fork Left Obstructed Input Bool Pallet or obstacle to in left direction of Fork
Fork Right Obstructed Input Bool Pallet or obstacle to in right direction of Fork
Fork Loaded Input Bool Load on Fork

I don’t know how this new assembly system will look like, but if it is what I think it is, then the all the Bool signals would come from actual sensors we put on things, rather than from the crane station itself.

Thank you for your detailed input, @janbumer1. Your suggestion aligns with several internal discussions we’ve had regarding machine configurations in Factory I/O.

I agree that using a 0-10 V signal in this case doesn’t make much sense, even without thinking about floating point issues. Analog signals for actuators are often retained for legacy or compatibility reasons - for instance, as setpoint references in VFDs - but their usage in modern applications is increasingly limited. Our current analog implementation originates from early versions of Factory I/O (in fact, it dates back over 20 years), where interfacing with PLCs required DAQ hardware and supported only on/off and voltage-based analog I/O. With the shift to Ethernet-based PLC communication, we’re no longer constrained by this and can support way more realistic data exchange patterns.

The use of drives, as you have mentioned, belongs to the realm of motion control. The idea of controlling such systems using higher-level abstractions is something we’ve considered. In fact, in the current version you can already specify a target cell using the Numerical configuration which emulates some kind of built-in controller responsible for moving the stacker crane.
The choice between addressing a specific cell or using real-world coordinates depends on how much of the motion logic is abstracted away from the PLC. In real systems, it’s becoming more common to embed motion control within PLCs, but in the case of stacker cranes, I think that this logic is often handled by a dedicated controller external to the PLC.

One potential direction is adopting an interface inspired by the PLCopen Motion Control standard. This would enable standardized command exchange between the PLC and the simulation for common motion functions such as Power, Stop, MoveAbsolute, MoveVelocity, and so on. This approach has two clear advantages:

  1. It provides a realistic way for students to practice simple axis control using an approach similar to IEC-compliant motion function blocks
  2. It introduces a common vocabulary for controlling machines with axis-based motion.

However, for machines like the stacker crane, relying exclusively on PLCopen-style motion commands may lead to unnecessarily complex PLC programs. A more flexible approach involves offering multiple modes of control (aka configurations):

  • A basic mode with predefined target cells (current implementation)
  • A position-based mode with mm-level granularity
  • An advanced mode based on motion control commands, similar to PLCopen function blocks.

Concerning the velocity control you described, this method does have limitations. Precision becomes increasingly difficult to achieve due to the communication delay between Factory I/O and the PLC. Accurate stopping at specific points requires predictive logic, which can add significant complexity. For this reason, we favor position control when accuracy is important.

Also, I really liked how you “positioned” the sensors. I was curious about the “Fork Loaded” input - what type of sensor are you thinking of there, and where would it be placed? Something like a photoelectric sensor or maybe something detecting weight?

2 Likes

Hey Bruno, thank you for your very detailed answer!

I understand now where you all were coming from with the 10 V signals. I like the three different methods you described there, especially the PLCOpen version.

One thing though, you brought up the example of using the digital encoded positions as something you’d use with a drive. Usually you wouldn’t put all these positions in a drive, as you would want to be able to dynamically adjust the positions sometimes or you want to in other ways change the position of a specific rack.

Usually we’d do the abstraction like this:

  1. Have a function that determines, which column, row, side (and depth for two-stroke cranes or even ACV cranes) a pallet should be loaded
  2. Row, column, depth and side get translated into actual setpoint values by means of a lookup table. Usually the y-axis values can depend on the x-axis values (as there could be sagging over the span of the whole length of the rack) and the z-values can depend on the y-values (as the racks could be not perfectly vertically aligned) and thus you’d usually have a big table that has the values for every single position of the rack. So for any combination of column, row and side/depth you’d get three values for x, y and z respectively
  3. Send those values to the drives in sequence (x and y can move at the same time, but z can only move once x and y are in position) and wait for confirmation that all 3 axis are in position

In regards to the speed control based approach, I agree. It’s one way to kind of position an axis if you don’t have a drive that has a built-in positioning system, but it usually doesn’t really allow you millimeter-level accuracy positioning. It does work for easier positioning, like for example a turntable! I regularly use just a simple drive and position turntables using just speed control where I have 2 or 3 different speeds I switch between and use the slowest speed for the final positioning. I can get positioning accuracy of 1 degrees with a good repeatability with this method. The key here is that you need to really use slow speeds for the final positioning and you need to allow for a certain delta to your target position. But it works just fine!

For the sensors, see the image attached as an example of a little older machine I worked on before.


B1 - Fork Left/Right Obstructed (is something in the rack in this position or also to detect the girder when positioning the y-axis to make sure your vertical position aligns with the position of the girder of the rack. Can also use cameras for this)
B2 - Sensor Left/Right to detect if the pallet is hanging over the fork
B3 - Diagonal Sensor (level, but diagonally across the fork) to detect if Fork is loaded. For example if we put down a pallet into the rack the state before we put it down has to be: logically the fork is loaded (we have some data for the pallet), the sensor for the fork says there is something there and the sensor for the rack says this rack position is empty. We then try to set down the pallet into the rack. Afterwards we check again if the fork is empty and if the position in the rack is now obstructed. If the fork is still not empty or the rack is not obstructed, something went wrong. Maybe our y-Axis was positioned wrong and we didn’t go down far enough to set the pallet down on the girder.
B4/6/7 - Sensors Left/Right to check the height class of the pallet. Sometimes, not all rows of a rack allow for the same height of load, because the customer knows that a certain percentage of their pallets will at maximum be a certain height one or height two or height three. If they make the rows of the racks less high, they can fit more rows into the same height of the total rack. But we usually check the pallet once we load it from the conveyors onto the crane to make sure the height class the pallet has according to the data we get from the warehouse system matches what we measure. If they do not match, something might be wrong. And we have to make sure we do not try to push a pallet that is too high into a rack row that is not high enough. If the warehouse system tells us to put a pallet into a row that is not of the correct size, something else might have gone wrong.
B5 - Sensor Left/Right to check if the load is hanging over. If the load get unbalanced during movement of the crane and starts to tip, we can detect this with these sensors and stop the crane before we rip apart the whole rack and cause further damage.

Just a remark. I think you mean PLCopen: https://plcopen.org/ instead of OpenPLC which already exists.

Yes, sorry. You are correct. It was quite late when I was writing the post and I got things mixed up.

I hope my explanations and the drawing helped though.

Great post @janbumer1!

The drawing you shared is especially helpful. It really clarifies where all the sensors should be placed and their specific purpose. This kind of insight is invaluable and not something that’s easy to find or figure out on our own. We’re definitely going to use it as a reference while working on the updated stacker crane.

Regarding your comments on speed control and the turntable. I imagine you’re using speed control as a way to achieve higher rotation speeds without relying on complex mechanical setups. In that case, am I right in thinking you’re using two separate sensors at each end - one to trigger deceleration and another for detecting the final position/rotation?

Thanks again for sharing all of this! :+1:

The drawing you shared is especially helpful. It really clarifies where all the sensors should be placed and their specific purpose. This kind of insight is invaluable and not something that’s easy to find or figure out on our own. We’re definitely going to use it as a reference while working on the updated stacker crane.

Keep in mind that this is just ONE way to do it and this is an older machine. But in principal it stays the same. One or two sensors accross the fork to detect if the fork is loaded, and then sensors on both sides to detect a pallet on the fork and check that the load is not hanging over the fork. Btw, we also have those sensors on our turntables! We have 3 to 4 sensors on each turntable:

  1. Gap free front
  2. Stop front
  3. Stop back
  4. Gap free back
    And then on the conveyors leading up to the turntables you have another gap sensor each.
    The gap sensors are to make sure that no pallet or obstruction is hanging over the turntable or the conveyors to make sure the turntables can turn freely without crashing or ripping loads apart.

Regarding your comments on speed control and the turntable. I imagine you’re using speed control as a way to achieve higher rotation speeds without relying on complex mechanical setups. In that case, am I right in thinking you’re using two separate sensors at each end - one to trigger deceleration and another for detecting the final position/rotation?

Not really, no. We do have sensors, but those are limit switches for safety purposes. Those are at the maximum turn angles (usually 5° and 360°) because our turntables have an internal cable chain, so they can’t turn more than 355° or they would rip of the cable. The positioning is entirely done with absolute encoders, see for example Sick AFM60. This way we can freely rotate our turntables into whatever position they need, including rotating CW or CCW on T-junctions so the pallets are always continuing their jurney facing forward. because sometimes barcodes are only on one side of the pallet, and all over the conveyors the barcode readers should always be on the same side.
Code to position a turntable would look something like this:

FUNCTION PositionTurntable : Void
VAR_INPUT
    iTgtPos: DINT;
    iEnable: BOOL;
END_VAR
VAR_OUTPUT
    oMoving : BOOL;
    oInPosition : BOOL;
    oDiff : DINT;
    oCurPos: DINT;
END_VAR;
VAR_TEMP
    diff : DINT;
    cur_pos : DINT;
    dir : SINT;
END_VAR;

VAR_CONSTANT
POS_WINDOW_1 : DINT := 1000; // incr
POS_WINDOW_2 : DINT := 200; // incr
POS_WINDOW_3 : DINT := 50; // incr
POS_SPEED_1 : UINT := 3000; // rpm
POS_SPEED_2 : UINT := 1000; // rpm
POS_SPEED_3 : UINT := 100; // rpm
POS_RAMP_1 : UINT := 1000; // ms
POS_RAMP_2 : UINT := 500; // ms
POS_RAMP_3 : UINT := 200; // ms
END_VAR

cur_pos := READ_ENCODER();
oCurPos := cur_pos;

diff = tgt_pos - cur_pos;
oDiff := diff;

dir = SIGN(diff);

IF ABS(diff) > POS_WINDOW_1 THEN
    MOVE_DRIVE(dir*POS_SPEED_1, POS_RAMP_1);
    oMoving := TRUE;
    oInPosition := FALSE;
ELSIF ABS(diff) > POS_WINDOW_2 THEN
    MOVE_DRIVE(dir*POS_SPEED_2, POS_RAMP_2);
    oMoving := TRUE;
    oInPosition := FALSE;
ELSIF ABS(diff) > POS_WINDOW_3 THEN
    MOVE_DRIVE(dir*POS_SPEED_3, POS_RAMP_3);
    oMoving := TRUE;
    oInPosition := FALSE;
ELSE
    MOVE_DRIVE(0, POS_RAMP_3);
    oMoving := FALSE;
    oInPosition := TRUE;
END_IF;
END_FUNCTION

@janbumer1 That makes sense. Positioning the turntable using absolute encoders really opens up a lot of flexibility, especially for scenarios where orientation matters, like barcode reading. We’d really like to offer a configuration for the turntable that implements this kind of functionality, but first we’ll need to look into how absolute encoders work and whether we can realistically simulate them. Most likely, we will need to keep velocities low.

Thanks for sharing the code as well. It’s super helpful :+1:

I mean, for the absolute encoders we usually set a starting value that is not 0 for the 5° angle (because we don’t want the value to underflow when the table for whatever reason turns past the 5° towards 0°. We also choose a resolution that is sufficiently large, but not so large that we get a value that overflows our DINT.
Usually we use the end position sensor as an initialisation sensor, so we slowly turn the turntable until the sensor is activated, and then turn it the other way until we lose the sensor again. The average between the values where the sensor turned on. Then we slowly turn the other way until the sensor turns off again and use that position to init the absolute encoder.
We then turn the table until it’s at the 355° position to check what the maximum value of our turntable is and make sure it’s within the value range of our variable.

Our encoder values are also not smoothly increasing when we turn the table sufficiently fast, so it would be realistic if values go missing. That’s also why we reduce the speed when we get close to the final position.