-
-
Notifications
You must be signed in to change notification settings - Fork 19.1k
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
Planner trapezoidal nominal_rate fix #26881
base: bugfix-2.1.x
Are you sure you want to change the base?
Planner trapezoidal nominal_rate fix #26881
Conversation
If the planner `entry_rate` or `final_rate` are larger thanthe `block->nominal_rate` then the trapezoid entry ramp continuously accelerates. Only happens if feed rate is less than MAXIMAL_STEP_RATE.
removed #define MINIMAL_STEP_RATE 120
Moved MINIMAL_STEP_RATE to this file.
Added calc for MINIMAL_STEP_RATE
fix minimal_step_rate calc
How did you discover this? I've seen some weird acceleration related to that 120 Hz rate in the past when I was capturing analyzer traces of movements (for real or in the simulator), but I don't remember whether those issues were fixed with some of other other step timing issues that @descipher and/or @tombrazier resolved a while back. |
I found it initially when using a puny laser that needed to move slowly to cut - it's easy to visually see over/under burn (related to step rate) with a laser. The code decelerates on entry from I'm still unsure what the function of |
Great work tracking this down. The comment in the code says
The comment goes back to Eric's original commit of the source code in 2011. Back then the minimum was 32Hz. Three days later he changed it to 120Hz in a commit with comment "Fixed lookup table bug." and it's been the same ever since. Along with this code there was also a line in a different place that limited The stepper timer has always been 2MHz for 16MHz ATmega2560 which, with a 16 bit timer, means timer/counter 1 will overflow at frequencies below 30.5Hz. So that's where 32Hz comes from. However, 120Hz is pretty slow for 3D printing equating to 1.5mm/s for X and Y on a 80 steps/mm printer. So I'm guessing this is why this has not been spotted and explored before. Anyway, it is not clear to me that there is any need for the |
On a related note, for the use case described with a laser that required really slow movement it might make sense to increase the microstepping level so that step rates don't get so low in the first place. Not a solution to the bug but likely a good idea if you're approaching the minimum rate the stepper interrupt can run at. |
Remove MINIMAL_STEP_RATE
Remove MINIMAL_STEP_RATE
Marlin/src/module/planner.cpp
Outdated
// Limit minimal step rate (Otherwise the timer will overflow.) | ||
NOLESS(initial_rate, uint32_t(MINIMAL_STEP_RATE)); | ||
NOLESS(final_rate, uint32_t(MINIMAL_STEP_RATE)); | ||
|
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.
@tombrazier in the latest diff MINMAL_STEP_RATE and these limits are removed entirely.
Are you confident that this is a correct thing to do, and that Otherwise the timer will overflow
is just an obsolete relic?
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 imagine we should have some testing on this in its final form before merging 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 believe there is likely a need to limit the minimum with AVR HAL hardware timers e.g. ATM2560 etc.
see https://github.com/MarlinFirmware/Marlin/blob/bugfix-2.1.x/Marlin/src/HAL/AVR/timers.h
Don't think you can remove them entirely.
regards @descipher
#define TEMP_TIMER_FREQUENCY (((F_CPU) + 0x2000) / 0x4000)
#define STEPPER_TIMER_RATE HAL_TIMER_RATE
#define STEPPER_TIMER_PRESCALE 8
#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000)
#define PULSE_TIMER_RATE STEPPER_TIMER_RATE
#define PULSE_TIMER_PRESCALE STEPPER_TIMER_PRESCALE
#define PULSE_TIMER_TICKS_PER_US STEPPER_TIMER_TICKS_PER_US
#define ENABLE_STEPPER_DRIVER_INTERRUPT() SBI(TIMSK1, OCIE1A)
#define DISABLE_STEPPER_DRIVER_INTERRUPT() CBI(TIMSK1, OCIE1A)
#define STEPPER_ISR_ENABLED() TEST(TIMSK1, OCIE1A)
#define ENABLE_TEMPERATURE_INTERRUPT() SBI(TIMSK0, OCIE0A)
#define DISABLE_TEMPERATURE_INTERRUPT() CBI(TIMSK0, OCIE0A)
#define TEMPERATURE_ISR_ENABLED() TEST(TIMSK0, OCIE0A)
FORCE_INLINE void HAL_timer_start(const uint8_t timer_num, const uint32_t) {
switch (timer_num) {
case MF_TIMER_STEP:
// waveform generation = 0100 = CTC
SET_WGM(1, CTC_OCRnA);
// output mode = 00 (disconnected)
SET_COMA(1, NORMAL);
// Set the timer pre-scaler
// Generally we use a divider of 8, resulting in a 2MHz timer
// frequency on a 16MHz MCU. If you are going to change this, be
// sure to regenerate speed_lookuptable.h with
// create_speed_lookuptable.py
SET_CS(1, PRESCALER_8); // CS 2 = 1/8 prescaler
// Init Stepper ISR to 122 Hz for quick starting
// (F_CPU) / (STEPPER_TIMER_PRESCALE) / frequency
OCR1A = 0x4000;
TCNT1 = 0;
break;
case MF_TIMER_TEMP:
// Use timer0 for temperature measurement
// Interleave temperature interrupt with millies interrupt
OCR0A = 128;
break;
}
}
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.
@tombrazier in the latest diff MINMAL_STEP_RATE and these limits are removed entirely.
Are you confident that this is a correct thing to do, and that
Otherwise the timer will overflow
is just an obsolete relic?
Not confident enough to merge it without testing. I am fairly sure it is redundant but it needs to be tried.
This feels very similar to something we fixed in the past, but perhaps it's a slightly different scenario. We might have fixed long pulses when starting from a standstill, versus yours transitioning form block to block. Here is the link to the old bug with a bunch of discussion for historical reference. It has a bunch of analyzer captures and charts in it which may appear somewhat similar to what you were seeing. |
It was the long pulses from standstill that we fixed. |
@GelidusResearch Could you elaborate? I think that it doesn't matter how low the step rates gets because in the stepper module it will not step any slower than 32 steps per second. That's built into |
Certainly, with the AVR and a period of 32Hz that sets OCR1A at ~62500, if there is any delay or latency in the ISR processing this could result in an overflow. We have no preemption capability to assert priority so if the processor is heavily loaded with other IRQ's this period value could be unstable. Of course we would need significant built up testing to see that potential issue. We have a window of ~500us before it overflows. I think that's what Eric may have encountered and thus he went to 120Hz for safety. So with the AVR 16bit timers @ 16Mhz and lower we may have issues near the 32Hz minimum for some users. |
@GelidusResearch Having given it more thought and reminded myself what the code does, I believe overflow is not a concern. The stepper interrupt timer gets reset to zero on compare match. It makes no difference how close it was to the maximum count of the timer when this happens. This must be the case for all MCUs because |
Removing the I have tested removal on a slow Laser and a 3D printer but that is no guarantee that it woks in all situations. |
Excellent point. All said, I now think the original implementation of this PR was about right. |
From the other discussion:
There is. I'll rephrase a comment I've made previously. Right now, the To see acceleration spikes (and maybe even lost steps) with this PR in this current state, just test with something that results in a low junction speed / a low |
I went ahead and added a commit applying suggestions from the discussion.
It will always be an integer. If HC32…
Teensy 3.1/3.2
SAMD21
I presume that the Is that all that the |
So we need #27035 before redefining |
@thinkyhead I mean if you type the division into a calculator, it won't be generally be an integer. If we take just the first case, for HC32, the step rate that the stepper will generate is 3125000 / 0xFFFF, which is 47.68444342717632. Truncating to an integer takes that to 47, which is not achievable. However I'm splitting hairs. A 1.4% difference in the speed used to calculate laser power is probably not a great problem. |
@tombrazier Based on your request I started PR #27089 but I kinda hesitate to recommend this option. Adding |
Of course, we shouldn't leave the branch in a known broken state, so although it's good to have 27089 as a separate PR that can be merged first (for clarity with the changes to follow) we should just decide when this PR and 27035 are "good enough" and I'll merge them all at the same time. Then we should do some testing soon after to make sure everything is stable and nothing is wonky, then continue repairs from there. If it ends up needing to be reverted for any reason, it will get reverted as a unit. |
Well, definitely.
If #27089 and #27035 are going to be treated as a unit they might as well stay a unit. And it would be nice to decouple this PR from them. So he're my proposal that to get this PR submitted now. Just add this: // Keep existing kludge against acceleration spikes until #27035
NOLESS(initial_rate, MIN(120, block->nominal_rate));
NOLESS(final_rate, MIN(120, block->nominal_rate)); to |
Please double-check the results of my merge. It was unclear whether to keep most of the stuff from #27013 or make it look more like the prior state of this PR. Maybe a hybrid is needed. |
const uint32_t initial_rate = _MAX((long int)MINIMAL_STEP_RATE, LROUND(block->nominal_rate * entry_factor)), // (steps per second) | ||
final_rate = _MAX((long int)MINIMAL_STEP_RATE, LROUND(block->nominal_rate * exit_factor)); |
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.
Just a style/readability comment: with the cast and the _MAX there's too much on each line.
Marlin/src/module/planner.cpp
Outdated
uint32_t initial_rate = LROUND(block->nominal_rate * entry_factor), | ||
final_rate = LROUND(block->nominal_rate * exit_factor); // (steps per second) | ||
const uint32_t initial_rate = _MAX(LROUND(block->nominal_rate * entry_factor), MINIMAL_STEP_RATE), | ||
final_rate = _MAX(LROUND(block->nominal_rate * exit_factor), MINIMAL_STEP_RATE); // (steps per second) | ||
|
||
// Limit minimal step rate (Otherwise the timer will overflow.) | ||
NOLESS(initial_rate, uint32_t(MINIMAL_STEP_RATE)); // Enforce the minimum speed | ||
NOLESS(final_rate, uint32_t(MINIMAL_STEP_RATE)); | ||
// Now ensure the nominal rate is above minimum | ||
NOLESS(block->nominal_rate, MINIMAL_STEP_RATE); | ||
|
||
// Assume these are handled elsewhere. Retained for debugging. | ||
//NOMORE(initial_rate, block->nominal_rate); // NOTE: The nominal rate may be less than MINIMAL_STEP_RATE! | ||
//NOMORE(final_rate, block->nominal_rate); | ||
|
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.
Following up on this PR: I think this version of the code is the most readable and easiest for the follow up PR #27035.
uint32_t initial_rate = LROUND(block->nominal_rate * entry_factor),
final_rate = LROUND(block->nominal_rate * exit_factor); // (steps per second)
// Limit minimal step rate (Otherwise the timer will overflow.)
NOLESS(initial_rate, MINIMAL_STEP_RATE);
NOLESS(final_rate, MINIMAL_STEP_RATE);
NOLESS(block->nominal_rate, MINIMAL_STEP_RATE);
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 like keeping the limiting code as a separate block for readability as well.
Fix nominal_rate range in planner
In
Planner::calculate_trapezoid_for_block()
,block->nominal_rate
could be lower thanMINIMAL_STEP_RATE
on entry. this produces invalid acceleration results as it attempts to match the exit rate from the previous block. Theinitial_rate
andfinal_rate
are NOLESS'ed to MINIMAL_STEP_RATE at entry. However,block->nominal_rate
isn't so you get some weird trapezoid outputs. The stepper fixes the worst case issues but the bad data causes the laser trap calculation to produce wrong results.This happens when the feed rate is set lower than MINIMAL_STEP_RATE . MINIMAL_STEP_RATE is set to 120 (a #define in the middle of planner.cpp) - I'm not sure how this number was derived? I've changed this value to be calculated which should give better results - unless there is some reason, other than isr counter overflow, for the slowest step rate to be limited?
Requirements
#define LASER_POWER_TRAP, #define LASER_FEATURE
Run this Gcode:
Planner output:
(ir = initial_rate, nr = nominal_rate, fr = final_rate)
=> ir:120 nr:45 fr:120
==> step acc:0->4294967293 decel: 270->266
==> ir:120 nr:45 fr:120
==> step acc:0->4294967293 decel: 270->266
==> ir:120 nr:45 fr:120
==> step acc:0->4294967293 decel: 271->267
==> ir:120 nr:45 fr:120
==> step acc:0->4294967293 decel: 271->267
==> ir:120 nr:45 fr:120
==> step acc:0->4294967293 decel: 270->266
==> ir:120 nr:45 fr:120
==> step acc:0->4294967293 decel: 270->266
==> ir:120 nr:45 fr:120
==> step acc:0->4294967293 decel: 271->267