-
-
Notifications
You must be signed in to change notification settings - Fork 19.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Alternative impl. of LCD-based manual movement #3110
Alternative impl. of LCD-based manual movement #3110
Conversation
4e1aa3c
to
b060b01
Compare
@@ -830,14 +831,13 @@ static void _lcd_move(const char* name, AxisEnum axis, int min, int max) { | |||
if (min_software_endstops) NOLESS(current_position[axis], min); | |||
if (max_software_endstops) NOMORE(current_position[axis], max); | |||
encoderPosition = 0; | |||
if (movesplanned() <= 3) | |||
line_to_current(axis); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here current_position
has already been set, but if the planner has some moves already the move is just dropped. This will cause the position to be out of sync until the next planner move. The solution is to set a move flag to add the move as soon as possible, then try once to do it immediately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the difference in your solution. The moves you can't do immediately are accumulated in current_position. We do not leave the move axis menu so the in formation about what axis to move is stored in 'currentMenu'. As long as we turn or klick the encoder, it does not make a difference if we enqueue the next move at the beginning of lcd_update()
, or a tiny bit later, when we are back in the move menu.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the encoder isn't actually clicked, it would seem to never add that last move, because lcd_return_to_status
ends up being called after a time-out.
The main thing I wanted to get in place and try was the manage_manual_move
function to see if there was some way to make manual moves interruptible. So if you change your mind and spin in the other direction there will only be a small move before it reverses direction – instead of having to wait for a bunch of queued moves to complete. But I have yet to work out the details.
b060b01
to
a947666
Compare
*/ | ||
inline void manage_manual_move() { | ||
if (manual_move_axis != (int8_t)NO_AXIS && !planner_is_full()) { | ||
#if ENABLED(DELTA) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!planner_is_full()
has much more latency than (movesplanned() <= 3`.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The stepper ISR code needs to be light and fast, but portions of the code that are written to run in the main loop()
should just aim for simplicity and saving bytes where possible.
Still, do you think it would help to FORCE_INLINE
the planner_is_full
function and move its definition to planner.cpp
? Apart from not being inline it shouldn't have any overhead compared to movesplanned()
. They both return a char
.
I see that making it FORCE_INLINE
saves 18 bytes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, sorry i have not been precise in what i said.
With planner_is_full() you have to wait up to 16 moves until the machine stops - with (movesplanned() <= 3) only 3.
3 because 1 or 2 does not give the planer the chance to optimize the accelerations - you'l have stops between the single moves.
Let's begin with turning the knob.
The first enqueued moves are all short - speed is low - until the buffer is full (or we reach an other limit (3)).
From there on the steps size will increase.
Finally the steps are long enough (take enough time) to turn the encoder back and forth without enqueuing new moves - only changing the size of the next move.
Finally you are able to send moves with a complete axis length.
Waiting for 16 full axis length moves, until the tool stops, is a bit long? Isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how any 16-move wait could occur. Both movesplanned() < 3
and planner_is_full()
have the same "problem." Both of them, if they are failing, must wait until some previously-added move finishes. In the first case, it will be a move that was added a couple moves ago. In the latter case, it will be the move that was added perhaps several moves ago, or perhaps just a couple of moves ago. In both cases, if the check fails, and then a move is added before we come back to our check in the next loop (not likely, since we're probably not actively printing), it will continue to fail until finally there is a single slot (or more) available in the buffer. The only difference I see is that movesplanned() < 3
will begin skipping moves sooner than planner_is_full
. The moves it ends up skipping are not guaranteed to be shorter, necessarily, since the encoder values still keep going up.
Anyway, we can probably test this scientifically and see what actually happens. I would be curious to know.
In any case, the current solution seems pretty reasonable. I was thinking of the idea of having the moves be delayed until the encoder stops for 1/4 second. But now I think it's not a good idea. It will tend to encourage much longer single moves, which would especially be a problem with the E axis. I think it's better if either (A) the movement happens quickly as a reaction to turning the knob, or (B) the movement waits until the knob is clicked.
a947666
to
86f25ad
Compare
The encoder wheel.. |
86f25ad
to
d8117ca
Compare
@jbrazio I like your idea also. As long as the wheel is being moved there should be no movement. Only when it pauses long enough. I think 1/2 second is probably pretty good. This PR lays the groundwork for just that kind of handling. |
Great ! My experience with the encoder wheel is when moving I tend to over/under shoot positions and this adds to the planner queue, the timeout to "accept" the user input will allow those corrections to happen without filling the planner with unintentional moves. |
a42d32d
to
91087ec
Compare
91087ec
to
c0734c1
Compare
71aca84
to
eb1d82e
Compare
@jbrazio See how this now works for you. It enforces a 1/2 second delay when using the I was hoping to modify it a bit more, so it splits up long moves into shorter ones (1-2mm for XY and E, 0.2mm for Z). But since the move options directly modify If "interruptible moves" works better than "delayed moves" we might opt for that – or make the behavior configurable to user-preference. It occurs to me there might be one more option, which is to still use the delayed move technique, but always stop and purge the current move and start a new one. This would allow moves in the opposite direction to be initiated with only the usual 0.5s delay. Stuff to experiment with later! |
2fbec39
to
56cbe86
Compare
56cbe86
to
da5de45
Compare
da5de45
to
a755744
Compare
1797b15
to
183c3e7
Compare
Patched for singletons. |
183c3e7
to
b6f5ea4
Compare
b6f5ea4
to
63f3abe
Compare
63f3abe
to
e2d592e
Compare
e2d592e
to
47b5c55
Compare
Recently we found that LCD-based movement can cause a stack overflow if the planner buffer gets full. This is because
plan_buffer_line()
callsidle()
in a loop. Sinceidle()
callslcd_update()
which in turn may callplan_buffer_line()
again, if you do a lot of manual moves with a slow axis (like Z), the stack quickly overflows due to nested calls toplan_buffer_line()
.The previous workaround was to skip calling
plan_buffer_line()
if the buffer had some moves already queued up. Unfortunately this may causecurrent_position
to be different from the last place moved, because any moves that can't be added are just dropped!This workaround changes the way that manual moves are applied, so moves won't be dropped, while at the same ensuring that
lcd_update()
will never callplan_buffer_line()
if it would result in recursion.The key is to simply set a flag that a manual move has been applied to one of the axes. Whenever
lcd_update()
is called (for example, fromidle()
) it will check to see if any manual move was applied but postponed. If so, and there's room in the planner, the move is added to the planner.A helpful feature of this change is that in the case of a long delay only the most recent destination set will be added to the planner. So if a single Z move takes a long time while there's a full planner buffer, fewer additional Z moves will end up being buffered.
This change also opens up the potential for interruptible manual moves, which could be achieved by setting a separate manual move destination, then adding smaller moves to the planner until the destination is reached. In this way, if the destination ends up being in the opposite direction from a previous destination, the direction change can happen more quickly.