Marlin: [RFC] Implementing better manual moves

Created on 24 Oct 2018  Â·  15Comments  Â·  Source: MarlinFirmware/Marlin

I believe that one of the goals of the EXTENSIBLE_UI is to solve some of the hard problems related to making UIs so that the people writing UIs don't have to solve those problems.

One of these hard problems is implementing manual moves. Even though it appears to be a simple problem, it is made difficult by the fact that Marlin buffers moves and the printer does not move instantaneously to a commanded position.

In an UI, there are two ways to implement manual control:

  • Movement arrows, such as up/down/left/right/front/back
  • Numerical adjusters, setting position values for individual axis

The problem happens when the user is interacting with the controls. As they tap the buttons, the "desired" position is changed, but if the UI buffers a new move on each tap, then the actual position of the toolhead will lag the commanded positions by a delay which will get worse the more they tap. I believe the UltraLCD tries to get around this by delaying the start of movement, but this is still problematic. For example, turn the knob all the way to the maximum value, and as soon as the toolhead starts moving, turn it back to the half-way point. The toolhead will actually travel all the way to the maximum value before returning to the half-way point, even though the user decided they didn't want to go that far. The workaround required in UltraLCD is not only imperfect, it also requires SRAM for the counters and delays. The problem gets worse in a touch UI where you might be able to adjust multiple axis positions at once and implementing a similar scheme would require even more SRAM.

I would like to propose that this problem be solved another way. There could be a flag called "manual_movement" that would change the behavior of the stepper ISR so that it would ignore the planner buffer and simply step the motors in whatever direction was necessary to reach "destination" at that particular instant. It would also update the "current_position" corresponding to that step.

This would make implementing GUI controls quite easy. The GUI would simply set the "manual_movement" flag then load the new absolute positions into "destination" each time the user tapped. Or, it could add some value to "current_position" and store it to "destination". Since there is no buffering, the toolhead would never get ahead of the user.

I don't necessarily know enough to implement this, but I am curious whether it would be feasible. Even though this adds some complexity to the ISR, it does eliminate complexity and workarounds that would otherwise have to be done in UI code.

Thoughts?

All 15 comments

You are ignoring the acceleration ramp issues.. Movements can´t be fast without the planner in the middle...

But, there is an "easy" way to keep using the planner for manual control:

-Set (or get the current feedrate in mm/s). Lets call it F
-Implement a (touch) control with autorepeat (if kept pressed) every exactly, let´s say 100milliseconds for each direction. Let´s call T to the time between repeats
-Send a move command on the appropiate direction for each control of a lenght of F * T

That way, you let the planner join the movements and will keep control nearly realtime.

But you also need a way to update the manual move feedrate (realk slow, slow, ledium, fast)...

Implement a (touch) control with autorepeat (if kept pressed) every exactly, let´s say 100milliseconds for each direction. Let´s call T to the time between repeats

@ejtagle: Actually, I am already doing it this way in my GUI. But it is imperfect. The motion is stop and go, because of the deceleration and acceleration between the segments. Also, there is no current way in Marlin to determine exactly what the feedrate of a move will be, because there are so many limits that come into play (max accelerations, max feedrates, etc), so you end up having to make an educated guess. So, in short, the method works acceptably, but not perfectly. My point is that it could be better and also reduce complexity in the UI if it were implemented at a lower level. But if nothing else in Marlin changes, this is the method I will continue to use.

@marcio-ao : As explained above, it is unsafe to do movements without the planner (the planner is the one that enforces acceleration ramps and thus avoids step skipping issues)
If your movements are "stop and go" like, that is because the planner queue is becoming empty, or you are not queuing enough movements to let the planner merge them:

_There are ways to solve this_:

1) Queue smaller movements, perhaps splitting into 3 each movement will help the planner to merge succesive movements
2) Use the "count of queued elements" (planner.movesplanned() ) to know the number of queued movements. When the user presses a "move" button, add as much movements as needed to keep this value in the 3... 4 range (so, it will allow the planner to merge moves, but the queue will not be long enough to delay stop of movement when the user releases the "move" key)

I think that will be enough ;)

@ejtagle: Humm, interesting. I'll try that, although I do still feel that adds quite a bit of complexity for what ought to be a fairly simple thing.

Although if this problem gets solved effectively in the EXTENSIBLE_UI methods, the good news is nobody else will have to deal with this annoying issue ever again.

There's not much that can be done about a user changing their mind if they start a move too far and decide to go back. We don't have any clean way to strip moves out of the planner once they've been added. Users just have to take care and not start moves to points beyond their desired destination. The delay before starting a move was added specifically to deal with this kind of error. You have a short amount of time to spin the knob back the other way if you've spun it too far.

You have a short amount of time to spin the knob back the other way if you spun it too far.

I saw that this is what the Ultralcd code is doing. That technique seems effective on the rotary encoder because the user can dial in a precise location rather quickly. Touch interfaces, for all their advantages, aren't nearly as effective for entering a position precisely. You either need a keypad (ugh!) or as I chose to implement it, you increase or decrease one decimal place at a time. This is a far more delicate operation than just spinning a knob.

Bypassing the planner would bypass calculation of acceleration and deceleration — not desirable.

@thinkyhead: I see. I'll try implementing something like what @ejtagle is suggesting. Right now I am keeping only one segment in the queue at a time and this leads to a very stop and go motion. It's not horrible, but it ain't great either.

@thinkyhead: There's also the challenging problem of doing all this without requiring a lot of RAM. In my own UI implemenation, I jumped through a lot of hoops to try keeping data in a union so each UI page could reuse UI storage from other pages and hopefully not balloon the memory requirement.

With movement buttons, one typical approach is to use press-and-hold and slowly increase the speed of the value-changing in the pressed direction. Another is to have multiple arrows on a cross (see Printrun), which give 0.1, 1, and 10mm movement when tapped or held. And yet another is to have a reticule in the center of a circle or cross, which is dragged in the desired direction of motion, and the farther it is dragged from the center, the more rapidly it moves. Kind of like a jogging control in video applications.

If your movements are "stop and go" like, that is because the planner queue is becoming empty, or you are not queuing enough movements to let the planner merge them

@ejtagle: I implemented your technique and it works wonderfully. With the new code, if the destination is incremented faster than the toolhead can keep up, the planner merges adjacent segments and the motion is perfectly smooth. I can also limit the length of buffered segments to 0.1 sec, so that the UI seems to the user to be fully responsive to fast changes to the destination. This seems to work even better than the technique UltraLCD is using. Thank you once again for helping me solve what seemed to be a tough problem.

Another nice thing is that you can now begin a move in one axis, increment another axis, and the toolhead will move in a diagonal as it "chases" the changing destination. It's really nice! I feel that with three encoders I could make the printer into a 3D etch-a-sketch!

So can this be used by the standard Marlin UI? It would be nice to avoid the "out and back" movement if you overshoot the end position on a manual move.

@gloomyandy: Only if the UltraLCD is rewritten to use the methods in the EXTENSIBLE_UI. Currently this is not the case.

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

esenapaj picture esenapaj  Â·  3Comments

modem7 picture modem7  Â·  3Comments

StefanBruens picture StefanBruens  Â·  4Comments

jerryerry picture jerryerry  Â·  4Comments

Ciev picture Ciev  Â·  3Comments