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.

Sorry to drag up an old forum post. I have been MIA for a while, but when I was catching up, I thought this was a really good topic. I think I am in a similar situation as Janbumer1 as we are using this software more on the Emulation side of real-world testing. When we get some new hires in, we’ve developed in-house training centered around Factory IO and the closer we come to real-world implementation, the more effective they become before they have to learn the logic in a frozen environment. (We have a lot of cranes and conveyor in -20*F, and learning in there is not fun)

Our company also utilizes a similar stacker crane design that Jan’s older project and I really like some of the feedback that he suggested.

· For Load on Forks, we utilize a similar retroreflective sensor as what he shows. We also have very similar checks to what he discussed. If the Forks are extended out, and the load is still on the crane… You got an issue. If you are already loaded, and go to a position for picking up another pallet… You got an issue. This is not required for entry level programming, but if you want to take your logic to the next level of usability (University level programming I think you called it) Error handling is the next step and HUGE thing in the real world.

· I really like the suggestion of detecting something in the racking. Our standard cranes are double deep, so we have 3 sensors on each side to do this. Pallet in Depth 1, Pallet in Depth 2, and Pallet in Depth 1 while delivering to Depth 2. (We have a slightly higher delivery position for delivering to depth 2 because of deflection on the fork units when sticking a one-ton pallet 2 meters out.) In the simulation world, the 3rd sensor wouldn’t be required. BUT, It would be REALLY nice to be able to attach our own sensors to the load handler, so the sensor’s location would be relative to the load handler(Move around with it), not to the world coordinates(Static in the environment). I am not sure of what engine you are running or if that makes sense or is easily doable. It would allow users to customize the standard elements where FactoryIO built the core element, but end users we able to customize.

· We have a separate PE that looks for the racking similar to what Jan said, in the real world with pallets stacked 40 meters high, the racking likes to move a little bit, and we will run a Rack Lean measurement. That allows us to dynamically make offsets to our data base. (We use a retro reflective sensor with reflectors in the racking to accomplish this)

· We do utilize a weight sensor on the load handler for a few reasons. Sometimes the user might manually lower the load handler too much with the forks out, and the forks will rest on the cross beam of the racking. They can continue to lower the load handler, but it will only cause slack in the lifting rope. IF the user then brought the forks back to the center, the load handler would free-fall until the lift rope was tight again, and potentially snap the rope. Our cranes also have a maximum weight they can handle, and we don’t want to allow the cranes to operate with an overloaded weight. So, if we say the load handler weighs for example 1,000LBS, if we are lowering and we see a weight of less than 500LBS, we throw an underload error. (Overrideable in the PLC if they’re going to do maintenance or something) Similar example, if the load handler weighs 1,000LBS, and we pick up a pallet that’s 2,500LBS that’s more than our 3,000LB limit. (1,000LB load handler, and 2,000LB MAX pallet)

· The height sensors are a neat idea to do on the crane, for our system, we do that at a conformity station prior to the crane and utilize pallet tracking on the conveyor feeding the cranes. But as I mentioned earlier, if Factory IO provided the updated Stacker Crane, and we could place our own sensors on the load handler, that would be super awesome.

(Keeping the topics I saw discussed separate)

I really like the ideal of the configurations that you have already implemented. (Different levels of student skills) I do agree that some updating to the configurations to modern standards is also best practice. It may cause some of the classes that people have developed to be updated, to match modern standards. BUT teaching modern technology to the students is the most important to me and will make it so they are the most prepared for the real world. (Which is what teaching was meant to be if you ask me)

I do like some of the subtle changes that Janbumer1 suggested for the Analog control of the crane. I personally never broke down the 0-10V signal like he was suggesting as if it was going through an actual 16bit +/-10V card, but I rather treated it as a field-bus communication, it was just funny the crane aisle was 10.5M long, and the signal was 0-10, so it was always just off. Our real-world cranes are upwards of 110M long, and 40M high, so having weird numbers in the database were just what it was. Haha. Also breaking out the Z axis into its own analog style control. Huge win. (It was ALWAYS a teaser to me that I couldn’t use the Stacker crane because of the Z control HAHA. SOOOO CLOSE)

I like the discussion on how to do the drive motion controls as I see a lot of drive manufacturers being split on this, and having offerings in both directions. For the SEW STACKER CRANE MoviKIT of their new MoviC family, the drives pretty much run the entirety of the motion control aspect. SUPER well thought out and designed for energy efficiency. Siemens is competing with SINUMERICS and SINAMICS… I am sure Rockwell has theirs I just haven’t gotten to play with it, and other manufacturers have their own standards. The cool thing about doing motion control at the drive level is the drive processor doesn’t have the entirety of the PLC overhead, so it’s doing process control at fractions of the time it takes the PLC to do it. For this, the current Analog is very close to what I have seen in the field. The only things I would add to what Jan wrote in his 1st table is a BOOL for each axis of RUN ENABLE as an output from the PLC to the model, as well as an ACC/DEC/SetpointSpeed setpoints for each AXIS. This would allow the crane to run rapidly to a position when empty, and run slower or different ramps when loaded. (If the value is not connected in the active configurations tab, then a default value would be run. To prevent errors on lack of parameterization)

I also agree with what both of you have said about the Velocity control. I would suggest that when expanding the stacker crane configurations, you would expand it as the 2 above options Analog – Velocity, and Analog – Positional. For the velocity control, I would suggest the addition of the Run Enable(BOOL), ACC/DEC/MAXSpeed setpoints for each AXIS. For some systems we have done before, when we go to a position, we output full speed and let the drive do the ACC ramp, then use a PID for positioning. Other systems, we control the entirety of the ACC ramp, DEC ramp, and positioning. (Depends on the application)

Since having multiple configurations shouldn’t be an issue, you could keep the current Analog (for backward compatibility), then have Analog – Velocity, and Analog – Positional with the new suggested IO. And I really like the idea you had about using the PLCOpen standard. Since it’s just about renaming the functionality you already have built in, might be an easy addition to the configurations.

(Keeping the topics I saw discussed separate)

I like the approach with the absolute encoders, you could do some really fast movements with something like that. On a lot of our absolute encoders, we have the ability to “0”/Home the encoders with an output from the PLC. We can manually move a lift to 1M above the ground floor, and set the encoder to a specific programmed value. Like Jan, I don’t like dealing with encoders going negative, so we move a lift to 1M, and “Home” the encoder to 10,000 pulses. That way if for whatever reason, we need to move JUST A LIL bit below the conveyor or racking, we won’t have that negative issue.

I’d suggest a “Home Setpoint” as a FLOAT or DINT or whatever you wanna do it as, and a BOOL of “Home” that when triggered loads the Home Setpoint into the offset for the encoder. (By default whatever orientation the element is placed in the model, that’s the 0 position.)

This functionality could really be rolled out to anything that has an encoder. The Cranes, the Vertical Lift, the Horizontal transfer I know you’re hopefully implementing, turn tables, ETC.

For some of our less precise turn tables, we have 2 prox sensors in each direction, quite similar to what Jan showed in the code with the encoder. We have a Slowdown and Stop prox, when we start the movement, we run speed FAST, then when we trigger the slowdown prox we will run speed SLOW, then as we slowly come up to the stop sensor it’s more accurate for stopping. Plus or minus close enough in pallet movements. Haha.