Refactoring and lots of bug fixes. Updated homing cycle.
WARNING: There are still some bugs to be worked out. Please use caution if you test this firmware. - Feed holds work much better, but there are still some failure conditions that need to be worked out. This is the being worked on currently and a fix is planned to be pushed next. - Homing cycle refactoring: Slight adjustment of the homing cycle to allow for limit pins to be shared by different axes, as long as the shared limit pins are not homed on the same cycle. Also, removed the LOCATE_CYCLE portion of the homing cycle configuration. It was redundant. - Limit pin sharing: (See above). To clear up one or two limit pins for other IO, limit pins can now be shared. For example, the Z-limit can be shared with either X or Y limit pins, because it’s on a separate homing cycle. Hard limit will still work exactly as before. - Spindle pin output fixed. The pins weren’t getting initialized correctly. - Fixed a cycle issue where streaming was working almost like a single block mode. This was caused by a problem with the spindle_run() and coolant_run() commands and issuing an unintended planner buffer sync. - Refactored the cycle_start, feed_hold, and other runtime routines into the runtime command module, where they should be handled here only. These were redundant. - Moved some function calls around into more appropriate source code modules. - Fixed the reporting of spindle state.
This commit is contained in:
291
stepper.c
291
stepper.c
@ -27,18 +27,19 @@
|
||||
|
||||
|
||||
// Some useful constants.
|
||||
#define DT_SEGMENT (1.0/(ACCELERATION_TICKS_PER_SECOND*60.0)) // min/segment
|
||||
#define DT_SEGMENT (1.0/(ACCELERATION_TICKS_PER_SECOND*60.0)) // min/segment
|
||||
#define REQ_MM_INCREMENT_SCALAR 1.25
|
||||
#define RAMP_ACCEL 0
|
||||
#define RAMP_CRUISE 1
|
||||
#define RAMP_DECEL 2
|
||||
|
||||
// Define AMASS levels and cutoff frequencies. The highest level frequency bin starts at 0Hz and
|
||||
// ends at its cutoff frequency. The next lower level frequency bin starts at the next higher cutoff
|
||||
// frequency, and so on. The cutoff frequencies for each level must be considered carefully against
|
||||
// how much it over-drives the stepper ISR, the accuracy of the 16-bit timer, and the CPU overhead.
|
||||
// Level 0 (no AMASS, normal operation) frequency bin starts at the Level 1 cutoff frequency and
|
||||
// up to as fast as the CPU allows (over 30kHz in limited testing).
|
||||
// NOTE: AMASS uutoff frequency multiplied by ISR overdrive factor must not exceed maximum step frequency.
|
||||
// Define Adaptive Multi-Axis Step-Smoothing(AMASS) levels and cutoff frequencies. The highest level
|
||||
// frequency bin starts at 0Hz and ends at its cutoff frequency. The next lower level frequency bin
|
||||
// starts at the next higher cutoff frequency, and so on. The cutoff frequencies for each level must
|
||||
// be considered carefully against how much it over-drives the stepper ISR, the accuracy of the 16-bit
|
||||
// timer, and the CPU overhead. Level 0 (no AMASS, normal operation) frequency bin starts at the
|
||||
// Level 1 cutoff frequency and up to as fast as the CPU allows (over 30kHz in limited testing).
|
||||
// NOTE: AMASS cutoff frequency multiplied by ISR overdrive factor must not exceed maximum step frequency.
|
||||
// NOTE: Current settings are set to overdrive the ISR to no more than 16kHz, balancing CPU overhead
|
||||
// and timer accuracy. Do not alter these settings unless you know what you are doing.
|
||||
#define MAX_AMASS_LEVEL 3
|
||||
@ -121,14 +122,14 @@ typedef struct {
|
||||
uint8_t st_block_index; // Index of stepper common data block being prepped
|
||||
uint8_t flag_partial_block; // Flag indicating the last block completed. Time to load a new one.
|
||||
|
||||
float step_per_mm; // Current planner block step/millimeter conversion scalar
|
||||
float steps_remaining;
|
||||
|
||||
float mm_per_step;
|
||||
float minimum_mm;
|
||||
float step_per_mm; // Current planner block step/millimeter conversion scalar
|
||||
float req_mm_increment;
|
||||
float dt_remainder;
|
||||
|
||||
uint8_t ramp_type; // Current segment ramp state
|
||||
float mm_complete; // End of velocity profile from end of current planner block in (mm).
|
||||
// NOTE: This value must coincide with a step(no mantissa) when converted.
|
||||
float current_speed; // Current speed at the end of the segment buffer (mm/min)
|
||||
float maximum_speed; // Maximum speed of executing block. Not always nominal speed. (mm/min)
|
||||
float exit_speed; // Exit speed of executing block (mm/min)
|
||||
@ -202,7 +203,6 @@ void st_wake_up()
|
||||
#endif
|
||||
|
||||
// Enable Stepper Driver Interrupt
|
||||
TCCR1B = (TCCR1B & ~(0x07<<CS10)) | (1<<CS10); // Set prescaler
|
||||
TIMSK1 |= (1<<OCIE1A);
|
||||
}
|
||||
}
|
||||
@ -211,19 +211,20 @@ void st_wake_up()
|
||||
// Stepper shutdown
|
||||
void st_go_idle()
|
||||
{
|
||||
// Disable Timer1 Stepper Driver Interrupt. Allow Timer0 to finish. It will disable itself.
|
||||
TIMSK1 &= ~(1<<OCIE1A);
|
||||
// Disable Stepper Driver Interrupt. Allow Stepper Port Reset Interrupt to finish, if active.
|
||||
TIMSK1 &= ~(1<<OCIE1A); // Disable Timer1 interrupt
|
||||
TCCR1B = (TCCR1B & ~((1<<CS12) | (1<<CS11))) | (1<<CS10); // Reset clock to no prescaling.
|
||||
busy = false;
|
||||
|
||||
// Set stepper driver idle state, disabled or enabled, depending on settings and circumstances.
|
||||
bool pin_state = false;
|
||||
bool pin_state = false; // Keep enabled.
|
||||
if (((settings.stepper_idle_lock_time != 0xff) || bit_istrue(sys.execute,EXEC_ALARM)) && sys.state != STATE_HOMING) {
|
||||
// Force stepper dwell to lock axes for a defined amount of time to ensure the axes come to a complete
|
||||
// stop and not drift from residual inertial forces at the end of the last movement.
|
||||
delay_ms(settings.stepper_idle_lock_time);
|
||||
pin_state = true;
|
||||
pin_state = true; // Override. Disable steppers.
|
||||
}
|
||||
if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) { pin_state = !pin_state; }
|
||||
if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) { pin_state = !pin_state; } // Apply pin invert.
|
||||
if (pin_state) { STEPPERS_DISABLE_PORT |= (1<<STEPPERS_DISABLE_BIT); }
|
||||
else { STEPPERS_DISABLE_PORT &= ~(1<<STEPPERS_DISABLE_BIT); }
|
||||
}
|
||||
@ -250,10 +251,17 @@ void st_go_idle()
|
||||
non-dominant Bresenham axes step in the intermediate ISR tick, while the dominant axis is
|
||||
stepping every two ISR ticks, rather than every ISR tick in the traditional sense. At AMASS
|
||||
Level 2, we simply bit-shift again, so the non-dominant Bresenham axes can step within any
|
||||
of the four ISR ticks, and the dominant axis steps every four ISR ticks. And so on. This,
|
||||
in effect, removes the vast majority of the multi-axis aliasing issues with the Bresenham
|
||||
algorithm and does not significantly alter Grbl's performance, but in fact, more efficiently
|
||||
utilizes unused CPU cycles overall throughout all configurations.
|
||||
of the four ISR ticks, the dominant axis steps every four ISR ticks, and quadruple the
|
||||
stepper ISR frequency. And so on. This, in effect, virtually eliminates multi-axis aliasing
|
||||
issues with the Bresenham algorithm and does not significantly alter Grbl's performance, but
|
||||
in fact, more efficiently utilizes unused CPU cycles overall throughout all configurations.
|
||||
AMASS retains the Bresenham algorithm exactness by requiring that it always executes a full
|
||||
Bresenham step, regardless of AMASS Level. Meaning that for an AMASS Level 2, all four
|
||||
intermediate steps must be completed such that baseline Bresenham (Level 0) count is always
|
||||
retained. Similarly, AMASS Level 3 means all eight intermediate steps must be executed.
|
||||
Although the AMASS Levels are in reality arbitrary, where the baseline Bresenham counts can
|
||||
be multiplied by any integer value, multiplication by powers of two are simply used to ease
|
||||
CPU overhead with bitshift integer operations.
|
||||
This interrupt is simple and dumb by design. All the computational heavy-lifting, as in
|
||||
determining accelerations, is performed elsewhere. This interrupt pops pre-computed segments,
|
||||
defined as constant velocity over n number of steps, from the step segment buffer and then
|
||||
@ -265,6 +273,7 @@ void st_go_idle()
|
||||
NOTE: This interrupt must be as efficient as possible and complete before the next ISR tick,
|
||||
which for Grbl must be less than 33.3usec (@30kHz ISR rate). Oscilloscope measured time in
|
||||
ISR is 5usec typical and 25usec maximum, well below requirement.
|
||||
NOTE: This ISR expects at least one step to be executed per segment.
|
||||
*/
|
||||
// TODO: Replace direct updating of the int32 position counters in the ISR somehow. Perhaps use smaller
|
||||
// int8 variables and update position counters only when a segment completes. This can get complicated
|
||||
@ -279,15 +288,15 @@ ISR(TIMER1_COMPA_vect)
|
||||
|
||||
// Then pulse the stepping pins
|
||||
#ifdef STEP_PULSE_DELAY
|
||||
st.step_bits = (STEPPING_PORT & ~STEP_MASK) | st.step_outbits; // Store out_bits to prevent overwriting.
|
||||
st.step_bits = (STEP_PORT & ~STEP_MASK) | st.step_outbits; // Store out_bits to prevent overwriting.
|
||||
#else // Normal operation
|
||||
STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | st.step_outbits;
|
||||
STEP_PORT = (STEP_PORT & ~STEP_MASK) | st.step_outbits;
|
||||
#endif
|
||||
|
||||
// Enable step pulse reset timer so that The Stepper Port Reset Interrupt can reset the signal after
|
||||
// exactly settings.pulse_microseconds microseconds, independent of the main Timer1 prescaler.
|
||||
TCNT0 = st.step_pulse_time; // Reload Timer0 counter
|
||||
TCCR0B = (1<<CS21); // Begin Timer0. Full speed, 1/8 prescaler
|
||||
TCCR0B = (1<<CS01); // Begin Timer0. Full speed, 1/8 prescaler
|
||||
|
||||
busy = true;
|
||||
sei(); // Re-enable interrupts to allow Stepper Port Reset Interrupt to fire on-time.
|
||||
@ -299,10 +308,13 @@ ISR(TIMER1_COMPA_vect)
|
||||
if (segment_buffer_head != segment_buffer_tail) {
|
||||
// Initialize new step segment and load number of steps to execute
|
||||
st.exec_segment = &segment_buffer[segment_buffer_tail];
|
||||
// Initialize step segment timing per step and load number of steps to execute.
|
||||
|
||||
#ifndef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
|
||||
// With AMASS is disabled, set timer prescaler for segments with slow step frequencies (< 250Hz).
|
||||
TCCR1B = (TCCR1B & ~(0x07<<CS10)) | (st.exec_segment->prescaler<<CS10);
|
||||
#endif
|
||||
|
||||
// Initialize step segment timing per step and load number of steps to execute.
|
||||
OCR1A = st.exec_segment->cycles_per_tick;
|
||||
st.step_count = st.exec_segment->n_step; // NOTE: Can sometimes be zero when moving slow.
|
||||
// If the new segment starts a new planner block, initialize stepper variables and counters.
|
||||
@ -310,17 +322,22 @@ ISR(TIMER1_COMPA_vect)
|
||||
if ( st.exec_block_index != st.exec_segment->st_block_index ) {
|
||||
st.exec_block_index = st.exec_segment->st_block_index;
|
||||
st.exec_block = &st_block_buffer[st.exec_block_index];
|
||||
st.dir_outbits = st.exec_block->direction_bits ^ settings.dir_invert_mask;
|
||||
|
||||
// Initialize Bresenham line and distance counters
|
||||
st.counter_x = (st.exec_block->step_event_count >> 1);
|
||||
st.counter_y = st.counter_x;
|
||||
st.counter_z = st.counter_x;
|
||||
}
|
||||
|
||||
st.dir_outbits = st.exec_block->direction_bits ^ settings.dir_invert_mask;
|
||||
|
||||
#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
|
||||
// With AMASS enabled, adjust Bresenham axis increment counters according to AMASS level.
|
||||
st.steps[X_AXIS] = st.exec_block->steps[X_AXIS] >> st.exec_segment->amass_level;
|
||||
st.steps[Y_AXIS] = st.exec_block->steps[Y_AXIS] >> st.exec_segment->amass_level;
|
||||
st.steps[Z_AXIS] = st.exec_block->steps[Z_AXIS] >> st.exec_segment->amass_level;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
// Segment buffer empty. Shutdown.
|
||||
st_go_idle();
|
||||
@ -379,28 +396,27 @@ ISR(TIMER1_COMPA_vect)
|
||||
|
||||
st.step_outbits ^= settings.step_invert_mask; // Apply step port invert mask
|
||||
busy = false;
|
||||
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT;
|
||||
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISR
|
||||
}
|
||||
|
||||
|
||||
/* The Stepper Port Reset Interrupt: Timer0 OVF interrupt handles the falling edge of the step
|
||||
pulse. This should always trigger before the next Timer2 COMPA interrupt and independently
|
||||
finish, if Timer2 is disabled after completing a move. */
|
||||
|
||||
// This interrupt is set up by ISR_TIMER1_COMPAREA when it sets the motor port bits. It resets
|
||||
// the motor port after a short period (settings.pulse_microseconds) completing one step cycle.
|
||||
// NOTE: Interrupt collisions between the serial and stepper interrupts can cause delays by
|
||||
// a few microseconds, if they execute right before one another. Not a big deal, but can
|
||||
// cause issues at high step rates if another high frequency asynchronous interrupt is
|
||||
// added to Grbl.
|
||||
pulse. This should always trigger before the next Timer1 COMPA interrupt and independently
|
||||
finish, if Timer1 is disabled after completing a move.
|
||||
NOTE: Interrupt collisions between the serial and stepper interrupts can cause delays by
|
||||
a few microseconds, if they execute right before one another. Not a big deal, but can
|
||||
cause issues at high step rates if another high frequency asynchronous interrupt is
|
||||
added to Grbl.
|
||||
*/
|
||||
// This interrupt is enabled by ISR_TIMER1_COMPAREA when it sets the motor port bits to execute
|
||||
// a step. This ISR resets the motor port after a short period (settings.pulse_microseconds)
|
||||
// completing one step cycle.
|
||||
ISR(TIMER0_OVF_vect)
|
||||
{
|
||||
// Reset stepping pins (leave the direction pins)
|
||||
STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | (settings.step_invert_mask & STEP_MASK);
|
||||
STEP_PORT = (STEP_PORT & ~STEP_MASK) | (settings.step_invert_mask & STEP_MASK);
|
||||
TCCR0B = 0; // Disable Timer0 to prevent re-entering this interrupt when it's not needed.
|
||||
}
|
||||
|
||||
|
||||
#ifdef STEP_PULSE_DELAY
|
||||
// This interrupt is used only when STEP_PULSE_DELAY is enabled. Here, the step pulse is
|
||||
// initiated after the STEP_PULSE_DELAY time period has elapsed. The ISR TIMER2_OVF interrupt
|
||||
@ -409,7 +425,7 @@ ISR(TIMER0_OVF_vect)
|
||||
// st_wake_up() routine.
|
||||
ISR(TIMER0_COMPA_vect)
|
||||
{
|
||||
STEPPING_PORT = st.step_bits; // Begin step pulse.
|
||||
STEP_PORT = st.step_bits; // Begin step pulse.
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -417,6 +433,9 @@ ISR(TIMER0_OVF_vect)
|
||||
// Reset and clear stepper subsystem variables
|
||||
void st_reset()
|
||||
{
|
||||
// Initialize stepper driver idle state.
|
||||
st_go_idle();
|
||||
|
||||
memset(&prep, 0, sizeof(prep));
|
||||
memset(&st, 0, sizeof(st));
|
||||
st.exec_segment = NULL;
|
||||
@ -426,9 +445,6 @@ void st_reset()
|
||||
segment_buffer_head = 0; // empty = tail
|
||||
segment_next_head = 1;
|
||||
busy = false;
|
||||
|
||||
// Initialize stepper driver idle state.
|
||||
st_go_idle();
|
||||
}
|
||||
|
||||
|
||||
@ -436,8 +452,8 @@ void st_reset()
|
||||
void stepper_init()
|
||||
{
|
||||
// Configure step and direction interface pins
|
||||
STEPPING_DDR |= STEP_MASK;
|
||||
STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | settings.step_invert_mask;
|
||||
STEP_DDR |= STEP_MASK;
|
||||
STEP_PORT = (STEP_PORT & ~STEP_MASK) | settings.step_invert_mask;
|
||||
STEPPERS_DISABLE_DDR |= 1<<STEPPERS_DISABLE_BIT;
|
||||
DIRECTION_DDR |= DIRECTION_MASK;
|
||||
DIRECTION_PORT = (DIRECTION_PORT & ~DIRECTION_MASK) | settings.dir_invert_mask;
|
||||
@ -445,15 +461,13 @@ void stepper_init()
|
||||
// Configure Timer 1: Stepper Driver Interrupt
|
||||
TCCR1B &= ~(1<<WGM13); // waveform generation = 0100 = CTC
|
||||
TCCR1B |= (1<<WGM12);
|
||||
TCCR1A &= ~(1<<WGM11);
|
||||
TCCR1A &= ~(1<<WGM10);
|
||||
TCCR1A &= ~(3<<COM1A0); // output mode = 00 (disconnected)
|
||||
TCCR1A &= ~(3<<COM1B0);
|
||||
TCCR1B = (TCCR1B & ~(0x07<<CS10)) | (1<<CS10);
|
||||
// TIMSK1 &= ~(1<<OCIE1A); // Timer 1 disabled in st_go_idle().
|
||||
TCCR1A &= ~((1<<WGM11) | (1<<WGM10));
|
||||
TCCR1A &= ~((1<<COM1A1) | (1<<COM1A0) | (1<<COM1B1) | (1<<COM1B0)); // Disconnect OC1 output
|
||||
// TCCR1B = (TCCR1B & ~((1<<CS12) | (1<<CS11))) | (1<<CS10); // Set in st_go_idle().
|
||||
// TIMSK1 &= ~(1<<OCIE1A); // Set in st_go_idle().
|
||||
|
||||
// Configure Timer 0: Stepper Port Reset Interrupt
|
||||
TIMSK0 &= ~(1<<TOIE0);
|
||||
TIMSK0 &= ~((1<<OCIE0B) | (1<<OCIE0A) | (1<<TOIE0)); // Disconnect OC0 outputs and OVF interrupt.
|
||||
TCCR0A = 0; // Normal operation
|
||||
TCCR0B = 0; // Disable Timer0 until needed
|
||||
TIMSK0 |= (1<<TOIE0); // Enable Timer0 overflow interrupt
|
||||
@ -463,44 +477,6 @@ void stepper_init()
|
||||
}
|
||||
|
||||
|
||||
// Planner external interface to start stepper interrupt and execute the blocks in queue. Called
|
||||
// by the main program functions: planner auto-start and run-time command execution.
|
||||
void st_cycle_start()
|
||||
{
|
||||
if (sys.state == STATE_QUEUED) {
|
||||
sys.state = STATE_CYCLE;
|
||||
st_prep_buffer(); // Initialize step segment buffer before beginning cycle.
|
||||
st_wake_up();
|
||||
if (bit_isfalse(settings.flags,BITFLAG_AUTO_START)) { sys.auto_start = false; } // Reset auto start per settings.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Execute a feed hold with deceleration, only during cycle. Called by main program.
|
||||
void st_feed_hold()
|
||||
{
|
||||
if (sys.state == STATE_CYCLE) {
|
||||
sys.state = STATE_HOLD;
|
||||
st_update_plan_block_parameters();
|
||||
st_prep_buffer();
|
||||
sys.auto_start = false; // Disable planner auto start upon feed hold.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reinitializes the cycle plan and stepper system after a feed hold for a resume. Called by
|
||||
// runtime command execution in the main program, ensuring that the planner re-plans safely.
|
||||
// NOTE: Bresenham algorithm variables are still maintained through both the planner and stepper
|
||||
// cycle reinitializations. The stepper path should continue exactly as if nothing has happened.
|
||||
// Only the planner de/ac-celerations profiles and stepper rates have been updated.
|
||||
void st_cycle_reinitialize()
|
||||
{
|
||||
if (sys.state != STATE_QUEUED) {
|
||||
sys.state = STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Called by planner_recalculate() when the executing block is updated by the new plan.
|
||||
void st_update_plan_block_parameters()
|
||||
{
|
||||
@ -522,41 +498,29 @@ void st_update_plan_block_parameters()
|
||||
The number of steps "checked-out" from the planner buffer and the number of segments in
|
||||
the segment buffer is sized and computed such that no operation in the main program takes
|
||||
longer than the time it takes the stepper algorithm to empty it before refilling it.
|
||||
Currently, the segment buffer conservatively holds roughly up to 40-50 msec of steps.
|
||||
|
||||
NOTE: The segment buffer executes a computed number of steps over a configured segment
|
||||
execution time period, except at an end of a planner block where the segment execution
|
||||
gets truncated by the lack of travel distance. Since steps are integer values and to keep
|
||||
the distance traveled over the segment exact, a fractional step remaining after the last
|
||||
executed step in a segment is handled by allowing the stepper algorithm distance counters
|
||||
to tick to this fractional value without triggering a full step. So, when the next segment
|
||||
is loaded for execution, its first full step will already have the distance counters primed
|
||||
with the previous segment fractional step and will execute exactly on time according to
|
||||
the planner block velocity profile. This ensures the step phasing between segments are kept
|
||||
in sync and prevents artificially created accelerations between segments if they are not
|
||||
accounted for. This allows the stepper algorithm to run at very high step rates without
|
||||
losing steps.
|
||||
Currently, the segment buffer conservatively holds roughly up to 40-50 msec of steps.
|
||||
NOTE: Computation units are in steps, millimeters, and minutes.
|
||||
*/
|
||||
void st_prep_buffer()
|
||||
{
|
||||
while (segment_buffer_tail != segment_next_head) { // Check if we need to fill the buffer.
|
||||
if (sys.state == STATE_QUEUED) { return; } // Block until a motion state is issued
|
||||
|
||||
// Determine if we need to load a new planner block or if the block remainder is replanned.
|
||||
// Determine if we need to load a new planner block or if the block has been replanned.
|
||||
if (pl_block == NULL) {
|
||||
pl_block = plan_get_current_block(); // Query planner for a queued block
|
||||
if (pl_block == NULL) { return; } // No planner blocks. Exit.
|
||||
|
||||
// Check if the segment buffer completed the last planner block. If so, load the Bresenham
|
||||
// data for the block. If not, we are still mid-block and the velocity profile has changed.
|
||||
// data for the block. If not, we are still mid-block and the velocity profile was updated.
|
||||
if (prep.flag_partial_block) {
|
||||
prep.flag_partial_block = false; // Reset flag
|
||||
} else {
|
||||
// Increment stepper common data index
|
||||
// Increment stepper common data index to store new planner block data.
|
||||
if ( ++prep.st_block_index == (SEGMENT_BUFFER_SIZE-1) ) { prep.st_block_index = 0; }
|
||||
|
||||
// Prepare and copy Bresenham algorithm segment data from the new planner block, so that
|
||||
// when the segment buffer completes the planner block, it may be discarded immediately.
|
||||
// when the segment buffer completes the planner block, it may be discarded when the
|
||||
// segment buffer finishes the prepped block, but the stepper ISR is still executing it.
|
||||
st_prep_block = &st_block_buffer[prep.st_block_index];
|
||||
st_prep_block->direction_bits = pl_block->direction_bits;
|
||||
#ifndef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
|
||||
@ -565,6 +529,9 @@ void st_prep_buffer()
|
||||
st_prep_block->steps[Z_AXIS] = pl_block->steps[Z_AXIS];
|
||||
st_prep_block->step_event_count = pl_block->step_event_count;
|
||||
#else
|
||||
// With AMASS enabled, simply bit-shift multiply all Bresenham data by the max AMASS
|
||||
// level, such that we never divide beyond the original data anywhere in the algorithm.
|
||||
// If the original data is divided, we can lose a step from integer roundoff.
|
||||
st_prep_block->steps[X_AXIS] = pl_block->steps[X_AXIS] << MAX_AMASS_LEVEL;
|
||||
st_prep_block->steps[Y_AXIS] = pl_block->steps[Y_AXIS] << MAX_AMASS_LEVEL;
|
||||
st_prep_block->steps[Z_AXIS] = pl_block->steps[Z_AXIS] << MAX_AMASS_LEVEL;
|
||||
@ -574,8 +541,9 @@ void st_prep_buffer()
|
||||
// Initialize segment buffer data for generating the segments.
|
||||
prep.steps_remaining = pl_block->step_event_count;
|
||||
prep.step_per_mm = prep.steps_remaining/pl_block->millimeters;
|
||||
prep.mm_per_step = pl_block->millimeters/prep.steps_remaining;
|
||||
prep.minimum_mm = pl_block->millimeters-prep.mm_per_step;
|
||||
prep.req_mm_increment = REQ_MM_INCREMENT_SCALAR*pl_block->millimeters/prep.steps_remaining;
|
||||
|
||||
prep.dt_remainder = 0.0; // Reset for new planner block
|
||||
|
||||
if (sys.state == STATE_HOLD) {
|
||||
// Override planner block entry speed and enforce deceleration during feed hold.
|
||||
@ -593,18 +561,20 @@ void st_prep_buffer()
|
||||
*/
|
||||
prep.mm_complete = 0.0; // Default velocity profile complete at 0.0mm from end of block.
|
||||
float inv_2_accel = 0.5/pl_block->acceleration;
|
||||
if (sys.state == STATE_HOLD) {
|
||||
if (sys.state == STATE_HOLD) { // [Forced Deceleration to Zero Velocity]
|
||||
// Compute velocity profile parameters for a feed hold in-progress. This profile overrides
|
||||
// the planner block profile, enforcing a deceleration to zero speed.
|
||||
prep.ramp_type = RAMP_DECEL;
|
||||
float decel_dist = inv_2_accel*pl_block->entry_speed_sqr;
|
||||
if (decel_dist < pl_block->millimeters) {
|
||||
prep.exit_speed = 0.0;
|
||||
prep.mm_complete = pl_block->millimeters-decel_dist; // End of feed hold.
|
||||
} else {
|
||||
// Compute decelerate distance relative to end of block.
|
||||
float decel_dist = pl_block->millimeters - inv_2_accel*pl_block->entry_speed_sqr;
|
||||
if (decel_dist < 0.0) {
|
||||
// Deceleration through entire planner block. End of feed hold is not in this block.
|
||||
prep.exit_speed = sqrt(pl_block->entry_speed_sqr-2*pl_block->acceleration*pl_block->millimeters);
|
||||
} else {
|
||||
prep.mm_complete = decel_dist; // End of feed hold.
|
||||
prep.exit_speed = 0.0;
|
||||
}
|
||||
} else {
|
||||
} else { // [Normal Operation]
|
||||
// Compute or recompute velocity profile parameters of the prepped planner block.
|
||||
prep.ramp_type = RAMP_ACCEL; // Initialize as acceleration ramp.
|
||||
prep.accelerate_until = pl_block->millimeters;
|
||||
@ -640,7 +610,8 @@ void st_prep_buffer()
|
||||
// prep.decelerate_after = 0.0;
|
||||
prep.maximum_speed = prep.exit_speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Initialize new segment
|
||||
@ -649,6 +620,24 @@ void st_prep_buffer()
|
||||
// Set new segment to point to the current segment data block.
|
||||
prep_segment->st_block_index = prep.st_block_index;
|
||||
|
||||
|
||||
float mm_remaining = pl_block->millimeters;
|
||||
float minimum_mm = pl_block->millimeters-prep.req_mm_increment;
|
||||
if (minimum_mm < 0.0) { minimum_mm = 0.0; }
|
||||
if (sys.state == STATE_HOLD) {
|
||||
if (minimum_mm < prep.mm_complete) { // NOTE: Exit condition
|
||||
// Less than one step to decelerate to zero speed, but already very close. AMASS
|
||||
// requires full steps to execute. So, just bail.
|
||||
prep.current_speed = 0.0;
|
||||
prep.dt_remainder = 0.0;
|
||||
prep.steps_remaining = ceil(pl_block->millimeters * prep.step_per_mm);
|
||||
pl_block->millimeters = prep.steps_remaining/prep.step_per_mm; // Update with full steps.
|
||||
plan_cycle_reinitialize();
|
||||
sys.state = STATE_QUEUED;
|
||||
return; // Segment not generated, but current step data still retained.
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------------
|
||||
Compute the average velocity of this new segment by determining the total distance
|
||||
traveled over the segment time DT_SEGMENT. The following code first attempts to create
|
||||
@ -663,12 +652,11 @@ void st_prep_buffer()
|
||||
the end of planner block (typical) or mid-block at the end of a forced deceleration,
|
||||
such as from a feed hold.
|
||||
*/
|
||||
float dt_max = DT_SEGMENT; // Set maximum segment time
|
||||
float dt_max = DT_SEGMENT; // Maximum segment time
|
||||
float dt = 0.0; // Initialize segment time
|
||||
float mm_remaining = pl_block->millimeters;
|
||||
float time_var = dt_max; // Time worker variable
|
||||
float mm_var; // mm-Distance worker variable
|
||||
float speed_var; // Speed worker variable.
|
||||
float speed_var; // Speed worker variable
|
||||
do {
|
||||
switch (prep.ramp_type) {
|
||||
case RAMP_ACCEL:
|
||||
@ -716,7 +704,7 @@ void st_prep_buffer()
|
||||
dt += time_var; // Add computed ramp time to total segment time.
|
||||
if (dt < dt_max) { time_var = dt_max - dt; } // **Incomplete** At ramp junction.
|
||||
else {
|
||||
if (mm_remaining > prep.minimum_mm) { // Check for slow segments with zero steps.
|
||||
if (mm_remaining > minimum_mm) { // Check for very slow segments with zero steps.
|
||||
dt_max += DT_SEGMENT; // Increase segment time to ensure at least one step in segment.
|
||||
time_var = dt_max - dt;
|
||||
} else {
|
||||
@ -724,9 +712,10 @@ void st_prep_buffer()
|
||||
}
|
||||
}
|
||||
} while (mm_remaining > prep.mm_complete); // **Complete** Exit loop. Profile complete.
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------------
|
||||
Compute segment step rate, steps to execute, and step phase correction parameters.
|
||||
Compute segment step rate, steps to execute, and apply necessary rate corrections.
|
||||
NOTE: Steps are computed by direct scalar conversion of the millimeter distance
|
||||
remaining in the block, rather than incrementally tallying the steps executed per
|
||||
segment. This helps in removing floating point round-off issues of several additions.
|
||||
@ -735,15 +724,26 @@ void st_prep_buffer()
|
||||
Fortunately, this scenario is highly unlikely and unrealistic in CNC machines
|
||||
supported by Grbl (i.e. exceeding 10 meters axis travel at 200 step/mm).
|
||||
*/
|
||||
uint32_t cycles;
|
||||
float steps_remaining = prep.step_per_mm*mm_remaining;
|
||||
float steps_remaining = prep.step_per_mm*mm_remaining; // Convert mm_remaining to steps
|
||||
float n_steps_remaining = ceil(steps_remaining); // Round-up current steps remaining
|
||||
float last_n_steps_remaining = ceil(prep.steps_remaining); // Round-up last steps remaining
|
||||
prep_segment->n_step = last_n_steps_remaining-n_steps_remaining; // Compute number of steps to execute.
|
||||
|
||||
// Compute number of steps to execute and segment step phase correction.
|
||||
prep_segment->n_step = ceil(prep.steps_remaining)-ceil(steps_remaining);
|
||||
// Compute segment step rate. Since steps are integers and mm distances traveled are not,
|
||||
// the end of every segment can have a partial step of varying magnitudes that are not
|
||||
// executed, because the stepper ISR requires whole steps due to the AMASS algorithm. To
|
||||
// compensate, we track the time to execute the previous segment's partial step and simply
|
||||
// apply it with the partial step distance to the current segment, so that it minutely
|
||||
// adjusts the whole segment rate to keep step output exact. These rate adjustments are
|
||||
// typically very small and do not adversely effect performance, but ensures that Grbl
|
||||
// outputs the exact acceleration and velocity profiles as computed by the planner.
|
||||
dt += prep.dt_remainder; // Apply previous segment partial step execute time
|
||||
float inv_rate = dt/(last_n_steps_remaining - steps_remaining); // Compute adjusted step rate inverse
|
||||
prep.dt_remainder = (n_steps_remaining - steps_remaining)*inv_rate; // Update segment partial step time
|
||||
|
||||
// Compute CPU cycles per step for the prepped segment.
|
||||
uint32_t cycles = ceil( (TICKS_PER_MICROSECOND*1000000*60)*inv_rate ); // (cycles/step)
|
||||
|
||||
float inv_rate = dt/(prep.steps_remaining-steps_remaining);
|
||||
cycles = ceil( (TICKS_PER_MICROSECOND*1000000*60)*inv_rate ); // (cycles/step)
|
||||
|
||||
#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
|
||||
// Compute step timing and multi-axis smoothing level.
|
||||
// NOTE: AMASS overdrives the timer with each level, so only one prescalar is required.
|
||||
@ -777,32 +777,27 @@ void st_prep_buffer()
|
||||
|
||||
// Determine end of segment conditions. Setup initial conditions for next segment.
|
||||
if (mm_remaining > prep.mm_complete) {
|
||||
// Normal operation. Block incomplete. Distance remaining to be executed.
|
||||
prep.minimum_mm = prep.mm_per_step*floor(steps_remaining);
|
||||
// Normal operation. Block incomplete. Distance remaining in block to be executed.
|
||||
pl_block->millimeters = mm_remaining;
|
||||
prep.steps_remaining = steps_remaining;
|
||||
} else {
|
||||
// End of planner block or forced-termination. No more distance to be executed.
|
||||
if (mm_remaining > 0.0) { // At end of forced-termination.
|
||||
// NOTE: Currently only feed holds qualify for this scenario. May change with overrides.
|
||||
// Reset prep parameters for resuming and then bail.
|
||||
// NOTE: Currently only feed holds qualify for this scenario. May change with overrides.
|
||||
prep.current_speed = 0.0;
|
||||
prep.dt_remainder = 0.0;
|
||||
prep.steps_remaining = ceil(steps_remaining);
|
||||
prep.minimum_mm = prep.steps_remaining-prep.mm_per_step;
|
||||
pl_block->millimeters = prep.steps_remaining*prep.mm_per_step; // Update with full steps.
|
||||
plan_cycle_reinitialize();
|
||||
sys.state = STATE_QUEUED; // End cycle.
|
||||
pl_block->millimeters = prep.steps_remaining/prep.step_per_mm; // Update with full steps.
|
||||
plan_cycle_reinitialize();
|
||||
sys.state = STATE_QUEUED; // End cycle.
|
||||
|
||||
// TODO: Try to move QUEUED setting into cycle re-initialize.
|
||||
|
||||
} else { // End of planner block
|
||||
// The planner block is complete. All steps are set to be executed in the segment buffer.
|
||||
pl_block = NULL;
|
||||
plan_discard_current_block();
|
||||
|
||||
if (sys.state == STATE_HOLD) {
|
||||
if (prep.current_speed == 0.0) {
|
||||
// TODO: Check if the segment buffer gets initialized correctly.
|
||||
plan_cycle_reinitialize();
|
||||
sys.state = STATE_QUEUED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -810,6 +805,8 @@ void st_prep_buffer()
|
||||
segment_buffer_head = segment_next_head;
|
||||
if ( ++segment_next_head == SEGMENT_BUFFER_SIZE ) { segment_next_head = 0; }
|
||||
|
||||
if (sys.state == STATE_QUEUED) { return; } // Bail if hold completes
|
||||
|
||||
// int32_t blength = segment_buffer_head - segment_buffer_tail;
|
||||
// if (blength < 0) { blength += SEGMENT_BUFFER_SIZE; }
|
||||
// printInteger(blength);
|
||||
|
Reference in New Issue
Block a user