Closed thinkyhead closed 6 years ago
Maybe a 'split' parameter of '_buffer_line()' defaulting to 'true' and called with 'false' in the few cases where it matters is the way with the least changes.
Maybe a 'split' parameter of '_buffer_line()' defaulting to 'true' and called with 'false' in the few cases where it matters is the way with the least changes.
I would vote in favor of this implementation. And just for testing... It would be easy to turn all splitting off if issues show up in the future.
I'm just thinking out loud here, but what if instead if splitting the first move after a stop in half, we split it up a little more intelligently. Here's the trapezoid for a move:
| __ |
|/ \|
Within this trapezoid, there's the acceleration, constant velocity, and deceleration sections. The issue we're seeing is that if a second move is added after the first movement starts but before it finishes, the planner is unable to chain them together, resulting in the following:
| __ | __ |
|/ \|/ \|
Ideally, for a straight line, we would want these to be chained together to look like:
| ____ |
|/ \|
The issue with the proposed solution is that we're attempting to split up the first movement to allow the planner to chain them together. However, the planner still has the limitation of not being able to chain the first two movements together due to the way the recalculate()
function is designed and implemented. In order to allow the planner to be able to chain the first two commanded movements together, we need to put three movements in the buffer. The trick here is that the "split" first movement has to already be optimized since recalculate()
is not going to do it for us. In order to achieve this, I would like to propose the following.
When we look at the first movement after a stop, the acceleration and constant velocity (if applicable) sections of the movement can be safely executed without further processing. However, it's the deceleration section that is considered volatile since it could potentially be replaced with a constant velocity section in the event of two consecutive straight lines, or the deceleration curve reduced in the event of a change of direction less than 90 degrees. Therefore, I propose that we split the first movement into two sections, with the split being at the start of the deceleration section:
Original first move:
| __ |
|/ \|
Split first move:
| _ | |
|/ \|/\|
Pre-optimized split first move:
| __| |
|/ |\|
Unoptimized (split) first and second movements:
| __| | __ |
|/ |\|/ \|
Optimized (split) first and second movements:
| __|_|___ |
|/ | | \|
The trick here is that the first half of the first move wouldn't have any deceleration in its block, and the second half of the split move would start at the ending velocity of the first half. This is different than the current approaches since each segment ends up with an acceleration and deceleration section that need to be optimized by recalculate()
, which can't be done without a complete overhaul of recalculate()
.
By pre-optimizing these first two segments, we eliminate the need to even run recalculate()
until the third segment, which is how it already works. At that point, if the third segment (i.e. the second commanded movement) can be optimized with the end of the first commanded movement, then the optimization can take place as it currently does. Another benefit to this solution is that we don't have to play any games with the stepper ISR. So long as the second commanded movement is processed before the second segment starts (i.e. the deceleration portion of the first commanded movement), then the optimization can occur. Granted, if the second commanded movement arrives later than needed, there will be a stop between the two commanded movements, but I don't see how that can be solved since we can't really guess what the second commanded movement will be.
Unfortunately, I don't have to to attempt to implement this until next week, but I've been thinking about it all day, and wanted to get it out there for the smart folks in this group to chew on.
The trick here is that the first half of the first move wouldn't have any deceleration in its block, and the second half of the split move would start at the ending velocity of the first half. This is different than the current approaches since each segment ends up with an acceleration and deceleration section that need to be optimized by recalculate()
I agree. This path has a lot of merit. Let's all think about this. I don't think this will be very difficult to code up and debug.
Maybe a 'split' parameter of '_buffer_line()' defaulting to 'true' and called with 'false' in the few cases where it matters is the way with the least changes.
Too difficult. :-(
During probing do_blocking_move_to_z()
is used. That uses about any kind of move where the parameter had to be propagated thru.
Now looking how a 'global' would work.
The global works surprisingly well because it can be set/reset in bracket_probe_move()
(aka setup_for_endstop_or_probe_move() and clean_up_after_endstop_or_probe_move() )
planner.h
@@ -163,10 +163,11 @@ class Planner {
acceleration, // Normal acceleration mm/s^2 DEFAULT ACCELERATION for all printing moves. M204 SXXXX
retract_acceleration, // Retract acceleration mm/s^2 filament pull-back and push-forward while standing still in the other axes M204 TXXXX
travel_acceleration, // Travel acceleration mm/s^2 DEFAULT ACCELERATION for all NON printing moves. M204 MXXXX
max_jerk[XYZE], // The largest speed change requiring no acceleration
min_travel_feedrate_mm_s;
+ static bool split_first_move;
#if HAS_LEVELING
static bool leveling_active; // Flag that bed leveling is enabled
#if ABL_PLANAR
static matrix_3x3 bed_level_matrix; // Transform to compensate for bed level
planer.cpp
@@ -101,10 +101,12 @@ float Planner::max_feedrate_mm_s[XYZE_N], // Max speeds in mm per second
#if ENABLED(DISTINCT_E_FACTORS)
uint8_t Planner::last_extruder = 0; // Respond to extruder change
#endif
+bool Planner::split_first_move = true;
+
int16_t Planner::flow_percentage[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(100); // Extrusion factor for each extruder
float Planner::e_factor[EXTRUDERS], // The flow percentage and volumetric multiplier combine to scale E movement
Planner::filament_size[EXTRUDERS], // diameter of filament (in millimeters), typically around 1.75 or 2.85, 0 disables the volumetric calculations for the extruder
Planner::volumetric_area_nominal = CIRCLE_AREA((DEFAULT_NOMINAL_FILAMENT_DIA) * 0.5), // Nominal cross-sectional area
@@ -1442,12 +1444,12 @@ void Planner::_buffer_line(const float &a, const float &b, const float &c, const
// DRYRUN ignores all temperature constraints and assures that the extruder is instantly satisfied
if (DEBUGGING(DRYRUN))
position[E_AXIS] = target[E_AXIS];
- // Always split the first move into one longer and one shorter move
- if (!blocks_queued()) {
+ // Always split the first move into two (if not homing or probing)
+ if (!blocks_queued() && split_first_move) {
#define _BETWEEN(A) (position[A##_AXIS] + target[A##_AXIS]) >> 1
const int32_t between[XYZE] = { _BETWEEN(X), _BETWEEN(Y), _BETWEEN(Z), _BETWEEN(E) };
DISABLE_STEPPER_DRIVER_INTERRUPT();
_buffer_steps(between, fr_mm_s, extruder);
_buffer_steps(target, fr_mm_s, extruder);
motion.cpp
@@ -424,15 +424,17 @@ void bracket_probe_move(const bool before) {
if (before) {
saved_feedrate_mm_s = feedrate_mm_s;
saved_feedrate_percentage = feedrate_percentage;
feedrate_percentage = 100;
gcode.refresh_cmd_timeout();
+ planner.split_first_move = false;
}
else {
feedrate_mm_s = saved_feedrate_mm_s;
feedrate_percentage = saved_feedrate_percentage;
gcode.refresh_cmd_timeout();
+ planner.split_first_move = true;
}
}
void setup_for_endstop_or_probe_move() { bracket_probe_move(true); }
void clean_up_after_endstop_or_probe_move() { bracket_probe_move(false); }
Another idea is to simply have the stepper ISR throw away the rest of the blocks when it triggers an endstop or probe (which it should do anyway). There's a stepper variable (cleaning_buffer_counter
) that, when set, causes the stepper ISR to throw away blocks until it counts down to zero. This would cover all future endstop/probing situations.
void Stepper::endstop_triggered(AxisEnum axis) {
. . .
kill_current_block();
+ cleaning_buffer_counter = BLOCK_BUFFER_SIZE - 1;
}
@thinkyhead Ingenios. Compared to my attempt this is pointing directly to the target instead of "carpet bombing".
We'll see if the homing/probing issues come down to a normal level now. I hope they are just caused by the COPY() problem. But now we should be save here.
I'm just thinking out loud here, but what if instead if splitting the first move after a stop in half, we split it up a little more intelligently. Here's the trapezoid for a move:
| __ | |/ \|
There is another case where the plateau is not reached because the move is too short or the wanted speed is too high or the acceleration too low. In these cases the speed just ramps up and down. Splitting in the mid. for this type of moves is close to ideal. (and the calculation is sooo cheap.)
However, the planner still has the limitation of not being able to chain the first two movements together due to the way the recalculate() function is designed and implemented. In order to allow the planner to be able to chain the first two commanded movements together, we need to put three movements in the buffer. The trick here is that the "split" first movement has to already be optimized since recalculate() is not going to do it for us. In order to achieve this, I would like to propose the following.
My tests tell another story, especially when the plateau is reached in the first step all seems to be well.
Backward scan ("<") begins a bit late but is not needed at all here
If the "nominal_rate" is not reached things currently look like:
Even here the backward scan starts late, but fast enough to get a acceleration up to "nominal_rate".
But there are other interesting things
00:50:59.824 : echo:3:6;X 24000,24000,19596
00:50:59.827 : echo:4:6; 24000,24000,19596
final_rate != initial_rate
00:50:59.877 : echo:4:7;X 24000,24000,24000
00:50:59.877 : echo:5:7; 24000,24000,24000
buffer lines with continuous speeds at "nominal_rate" not marked with BLOCK_BIT_NOMINAL_LENGTH
Splitting in 3 parts makes no sense to me. Splitting at the start of the deceleration ramp makes no sense to me. Either nominal_rate is reached - and all is well with the current code, or the ideal split for a doubled move is about in the mid.
More interesting to me, is to match what the code does while recalculating with my expectations, or the other way around. I simply currently neither understand what's going on nor what should go on. :-(
Give me some days. A flu isn't helpful when wanting to think.
Testcode based on bugfix-2.0.x at https://github.com/AnHardt/Marlin/pull/80/commits/c4bdcd898709b9b3eb4a18055cab0f95683d6813 now https://github.com/AnHardt/Marlin/pull/80/commits/2a8d5c0f4af983ae624db3cd18f819416b93ab6b
I still don't completely understand but added some more debug code to recalculate()
.
The i tried to shift the index borders in reverse_pass()
a bit back and forward, shaked them and got
void Planner::reverse_pass() {
- if (movesplanned() > 3) {
+ if (movesplanned() > 2) {
- block_t* block[3] = { NULL, NULL, NULL };
+ block_t* block[2] = { NULL, NULL };
// Make a local copy of block_buffer_tail, because the interrupt can alter it
// Is a critical section REALLY needed for a single byte change?
//CRITICAL_SECTION_START;
uint8_t tail = block_buffer_tail;
//CRITICAL_SECTION_END
+ tail = BLOCK_MOD(tail+2);
- uint8_t b = BLOCK_MOD(block_buffer_head - 3);
+ uint8_t b = BLOCK_MOD(block_buffer_head);
while (b != tail) {
if (block[0] && TEST(block[0]->flag, BLOCK_BIT_START_FROM_FULL_HALT)) break;
b = prev_block_index(b);
- block[2] = block[1];
block[1] = block[0];
block[0] = &block_buffer[b];
- reverse_pass_kernel(block[1], block[2]);
+ reverse_pass_kernel(block[0], block[1]); // current, next
}
}
}
what matches my expectations.
Perfect for series of moves where the single buffer lines reach nominal_rate
and even so when the reverse_pass()
is really needed (nominal_rate
faster than can be reached in one block )
You can find the complete test code in https://github.com/AnHardt/Marlin/pull/80
Slow and even so when the reverse_pass() is really needed (nominal_rate faster than can be reached in one block ) Fast You can find the complete test code in AnHardt#80
I don't understand what Slow and Fast refer to? Its not the speed of the algorithm, right?
It's the value of F, in the g-code, what here makes the difference if 'nominal_rate' can be reached in one block or not. Accelerations are low in this example to fill the buffer even with lots of output on the serial.
After finally understanding what should go on and what was going on, her my hopefully a bit more readable and working version:
/**
* recalculate() needs to go over the current plan twice.
* Once in reverse and once forward. This implements the reverse pass.
*/
void Planner::reverse_pass() {
if (movesplanned() > 3) {
uint8_t endnr = BLOCK_MOD(block_buffer_tail + 2); // tail is running. tail+1 should not be altered because it's connected to the running block.
// tail+2 because the index is not already advanced when checked
uint8_t blocknr = prev_block_index(block_buffer_head);
block_t* current = &block_buffer[blocknr];
do {
block_t* next = current;
blocknr = prev_block_index(blocknr);
current = &block_buffer[blocknr];
if(TEST(current->flag, BLOCK_BIT_START_FROM_FULL_HALT)) //before of that every block is already optimized.
break;
reverse_pass_kernel(current, next);
} while (blocknr != endnr);
}
}
@thinkyhead I think there's an issue in 1.1.7 with the changes that were made to resolve this issue. I'm attempting to perform a print with several different parts, and I've noticed that at the end of some travel movements, it appears that the movement suddenly stops, resulting in a noticeable thud. I've got my acceleration values set to 800 all around and X/Y jerk set to 12. The changes in direction for normal printing movements are very smooth.
The issue seems to be worse as a function of the amount of change in direction at the end of the travel movement, with a full 180 degree stop and continue being the worst. This seems very similar to what I was experiencing during my experimentation in https://github.com/MarlinFirmware/Marlin/issues/8595#issuecomment-348074555.
Here's a video showing the issue. You can hear the thud just before three seconds in.
After a bunch of experimenting, I ended up reverting back to 1.1.6, and the issue is not there.
The major concern with this issue is that the immediate stop over-torques the stepper motor and was causing it to lose steps.
I've noticed that at the end of some travel movements, it appears that the movement suddenly stops, resulting in a noticeable thud.
Thanks for the video! I've been trying to discover the cause in issue #8808. The problem will go away if you disable SEGMENT_LEVELED_MOVES
.
I had SEGMENT_LEVELED_MOVES
disabled, but was using UBL.
is there a relation to ubl issue #8684 on bugfix-2.0.x? could be introduced by incorporating some changes from 2.0.x to 1.1.7 if so the changes between the two versions in that issue might give a hint...
I don't think UBL in itself can cause this type of motion issue. What I think is happening is that the segmented moves are exposing the issue, which is why the issue was seen when SEGMENT_LEVELED_MOVES or UBL are enabled. I suspect it would happen with bilinear leveling also.
In my case, I couldn't figure out why it wasn't happening during every travel movement, but my hypothesis above would explain it since movements with UBL (with SEGMENT_LEVELED_MOVES disabled) are only segmented if the movement crosses a grid boundary.
@tcm0116 I'm trying to narrow down the cause of the issue now. I'm about to test some points in time and see where this starts.
It's due to some specific change after the end of November, or maybe some poison combination of changes. I hope to be able to find the one or two responsible and get this patched tonight!
@thinkyhead sounds like a plan! I started does that path, but it was 3:30am and I needed to get to bed.
Revert this commit and it also fixes the issue: ec028bf747ff7fe99c4fac29f0df62491db2528c
@Bob-the-Kuhn I guess we need to have another look at #8735 "Initial Step Correction."
@tcm0116 Please test the bf1_reverting_8735 branch and see if it solves the issue.
@thinkyead it appears that reverting https://github.com/MarlinFirmware/Marlin/commit/ec028bf747ff7fe99c4fac29f0df62491db2528c resolves the issue for me.
@tcm0116 Good news! The issue that #8735 addresses is subtle and maybe even esoteric, so I think we can take our time with it.
It's just a shame that it made it into 1.1.7. Since it's pretty problematic, are we going to quickly release 1.1.8 or re-release 1.1.7 with that change reverted?
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.
Continuing from #8573 —