7a175bd2db
- **NON-FUNCTIONAL** - Contains an old draft of separating the stepper driver direct access to the planner buffer. This is designed to keep the stepper and planner modules independent and prevent overwriting or other complications. In this way, feedrate override should be able to be installed as well. - A number of planner optimizations are installed too. - Not sure where the bugs are. Either in the new planner optimizations, new stepper module updates, or in both. Or it just could be that the Arduino AVR is choking with the new things it has to do.
488 lines
26 KiB
C
488 lines
26 KiB
C
/*
|
|
planner.c - buffers movement commands and manages the acceleration profile plan
|
|
Part of Grbl
|
|
|
|
Copyright (c) 2011-2013 Sungeun K. Jeon
|
|
Copyright (c) 2009-2011 Simen Svale Skogsrud
|
|
Copyright (c) 2011 Jens Geisler
|
|
|
|
Grbl is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Grbl is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* The ring buffer implementation gleaned from the wiring_serial library by David A. Mellis. */
|
|
|
|
#include <inttypes.h>
|
|
#include <stdlib.h>
|
|
#include "planner.h"
|
|
#include "nuts_bolts.h"
|
|
#include "stepper.h"
|
|
#include "settings.h"
|
|
#include "config.h"
|
|
#include "protocol.h"
|
|
|
|
#define SOME_LARGE_VALUE 1.0E+38 // Used by rapids and acceleration maximization calculations. Just needs
|
|
// to be larger than any feasible (mm/min)^2 or mm/sec^2 value.
|
|
|
|
static plan_block_t block_buffer[BLOCK_BUFFER_SIZE]; // A ring buffer for motion instructions
|
|
static volatile uint8_t block_buffer_tail; // Index of the block to process now
|
|
static uint8_t block_buffer_head; // Index of the next block to be pushed
|
|
static uint8_t next_buffer_head; // Index of the next buffer head
|
|
static uint8_t block_buffer_planned; // Index of the optimally planned block
|
|
|
|
// Define planner variables
|
|
typedef struct {
|
|
int32_t position[N_AXIS]; // The planner position of the tool in absolute steps. Kept separate
|
|
// from g-code position for movements requiring multiple line motions,
|
|
// i.e. arcs, canned cycles, and backlash compensation.
|
|
float previous_unit_vec[N_AXIS]; // Unit vector of previous path line segment
|
|
float previous_nominal_speed_sqr; // Nominal speed of previous path line segment
|
|
} planner_t;
|
|
static planner_t pl;
|
|
|
|
|
|
// Returns the index of the next block in the ring buffer
|
|
// NOTE: Removed modulo (%) operator, which uses an expensive divide and multiplication.
|
|
static uint8_t next_block_index(uint8_t block_index)
|
|
{
|
|
block_index++;
|
|
if (block_index == BLOCK_BUFFER_SIZE) { block_index = 0; }
|
|
return(block_index);
|
|
}
|
|
|
|
|
|
// Returns the index of the previous block in the ring buffer
|
|
static uint8_t prev_block_index(uint8_t block_index)
|
|
{
|
|
if (block_index == 0) { block_index = BLOCK_BUFFER_SIZE; }
|
|
block_index--;
|
|
return(block_index);
|
|
}
|
|
|
|
|
|
/* PLANNER SPEED DEFINITION
|
|
+--------+ <- current->nominal_speed
|
|
/ \
|
|
current->entry_speed -> + \
|
|
| + <- next->entry_speed
|
|
+-------------+
|
|
time -->
|
|
|
|
Recalculates the motion plan according to the following algorithm:
|
|
|
|
1. Go over every block in reverse order and calculate a junction speed reduction (i.e. block_t.entry_speed)
|
|
so that:
|
|
a. The junction speed is equal to or less than the maximum junction speed limit
|
|
b. No speed reduction within one block requires faster deceleration than the acceleration limits.
|
|
c. The last (or newest appended) block is planned from a complete stop.
|
|
2. Go over every block in chronological (forward) order and dial down junction speed values if
|
|
a. The speed increase within one block would require faster acceleration than the acceleration limits.
|
|
|
|
When these stages are complete, all blocks have a junction entry speed that will allow all speed changes
|
|
to be performed using the overall limiting acceleration value, and where no junction speed is greater
|
|
than the max limit. In other words, it just computed the fastest possible velocity profile through all
|
|
buffered blocks, where the final buffered block is planned to come to a full stop when the buffer is fully
|
|
executed. Finally it will:
|
|
|
|
3. Convert the plan to data that the stepper algorithm needs. Only block trapezoids adjacent to a
|
|
a planner-modified junction speed with be updated, the others are assumed ok as is.
|
|
|
|
All planner computations(1)(2) are performed in floating point to minimize numerical round-off errors. Only
|
|
when planned values are converted to stepper rate parameters(3), these are integers. If another motion block
|
|
is added while executing, the planner will re-plan and update the stored optimal velocity profile as it goes.
|
|
|
|
Conceptually, the planner works like blowing up a balloon, where the balloon is the velocity profile. It's
|
|
constrained by the speeds at the beginning and end of the buffer, along with the maximum junction speeds and
|
|
nominal speeds of each block. Once a plan is computed, or balloon filled, this is the optimal velocity profile
|
|
through all of the motions in the buffer. Whenever a new block is added, this changes some of the limiting
|
|
conditions, or how the balloon is filled, so it has to be re-calculated to get the new optimal velocity profile.
|
|
|
|
Also, since the planner only computes on what's in the planner buffer, some motions with lots of short line
|
|
segments, like arcs, may seem to move slow. This is because there simply isn't enough combined distance traveled
|
|
in the entire buffer to accelerate up to the nominal speed and then decelerate to a stop at the end of the
|
|
buffer. There are a few simple solutions to this: (1) Maximize the machine acceleration. The planner will be
|
|
able to compute higher speed profiles within the same combined distance. (2) Increase line segment(s) distance.
|
|
The more combined distance the planner has to use, the faster it can go. (3) Increase the MINIMUM_PLANNER_SPEED.
|
|
Not recommended. This will change what speed the planner plans to at the end of the buffer. Can lead to lost
|
|
steps when coming to a stop. (4) [BEST] Increase the planner buffer size. The more combined distance, the
|
|
bigger the balloon, or faster it can go. But this is not possible for 328p Arduinos because its limited memory
|
|
is already maxed out. Future ARM versions should not have this issue, with look-ahead planner blocks numbering
|
|
up to a hundred or more.
|
|
|
|
NOTE: Since this function is constantly re-calculating for every new incoming block, it must be as efficient
|
|
as possible. For example, in situations like arc generation or complex curves, the short, rapid line segments
|
|
can execute faster than new blocks can be added, and the planner buffer will then starve and empty, leading
|
|
to weird hiccup-like jerky motions.
|
|
*/
|
|
static void planner_recalculate()
|
|
{
|
|
// Last/newest block in buffer. Exit speed is set with MINIMUM_PLANNER_SPEED. Always recalculated.
|
|
uint8_t block_index = block_buffer_head;
|
|
plan_block_t *current = &block_buffer[block_index]; // Set as last/newest block in buffer
|
|
|
|
// Ping the stepper algorithm to check if we can alter the parameters of the currently executing
|
|
// block. If not, skip it and work on the next block.
|
|
// TODO: Need to look into if there are conditions where this fails.
|
|
uint8_t block_buffer_safe = next_block_index( block_buffer_tail );
|
|
|
|
// TODO: Need to recompute buffer tail millimeters based on how much is completed.
|
|
|
|
if (block_buffer_safe == next_buffer_head) { // Only one safe block in buffer to operate on
|
|
|
|
block_buffer_planned = block_buffer_safe;
|
|
// calculate_trapezoid_for_block(current, 0.0, MINIMUM_PLANNER_SPEED*MINIMUM_PLANNER_SPEED);
|
|
|
|
} else {
|
|
|
|
// TODO: need to account for the two block condition better. If the currently executing block
|
|
// is not safe, do we wait until its done? Can we treat the buffer head differently?
|
|
|
|
// Calculate trapezoid for the last/newest block.
|
|
current->entry_speed_sqr = min( current->max_entry_speed_sqr,
|
|
MINIMUM_PLANNER_SPEED*MINIMUM_PLANNER_SPEED + 2*current->acceleration*current->millimeters);
|
|
// calculate_trapezoid_for_block(current, current->entry_speed_sqr, MINIMUM_PLANNER_SPEED*MINIMUM_PLANNER_SPEED);
|
|
|
|
|
|
// Reverse Pass: Back plan the deceleration curve from the last block in buffer. Cease
|
|
// planning when: (1) the last optimal planned pointer is reached. (2) the safe block
|
|
// pointer is reached, whereby the planned pointer is updated.
|
|
float entry_speed_sqr;
|
|
plan_block_t *next;
|
|
block_index = prev_block_index(block_index);
|
|
while (block_index != block_buffer_planned) {
|
|
next = current;
|
|
current = &block_buffer[block_index];
|
|
|
|
// Exit loop and update planned pointer when the tail/safe block is reached.
|
|
if (block_index == block_buffer_safe) {
|
|
block_buffer_planned = block_buffer_safe;
|
|
break;
|
|
}
|
|
|
|
// Crudely maximize deceleration curve from the end of the non-optimally planned buffer to
|
|
// the optimal plan pointer. Forward pass will adjust and finish optimizing the plan.
|
|
if (current->entry_speed_sqr != current->max_entry_speed_sqr) {
|
|
entry_speed_sqr = next->entry_speed_sqr + 2*current->acceleration*current->millimeters;
|
|
if (entry_speed_sqr < current->max_entry_speed_sqr) {
|
|
current->entry_speed_sqr = entry_speed_sqr;
|
|
} else {
|
|
current->entry_speed_sqr = current->max_entry_speed_sqr;
|
|
}
|
|
}
|
|
block_index = prev_block_index(block_index);
|
|
}
|
|
|
|
// Forward Pass: Forward plan the acceleration curve from the planned pointer onward.
|
|
// Also scans for optimal plan breakpoints and appropriately updates the planned pointer.
|
|
block_index = block_buffer_planned; // Begin at buffer planned pointer
|
|
next = &block_buffer[prev_block_index(block_buffer_planned)]; // Set up for while loop
|
|
while (block_index != next_buffer_head) {
|
|
current = next;
|
|
next = &block_buffer[block_index];
|
|
|
|
// Any acceleration detected in the forward pass automatically moves the optimal planned
|
|
// pointer forward, since everything before this is all optimal. In other words, nothing
|
|
// can improve the plan from the buffer tail to the planned pointer by logic.
|
|
if (current->entry_speed_sqr < next->entry_speed_sqr) {
|
|
block_buffer_planned = block_index;
|
|
entry_speed_sqr = current->entry_speed_sqr + 2*current->acceleration*current->millimeters;
|
|
if (entry_speed_sqr < next->entry_speed_sqr) {
|
|
next->entry_speed_sqr = entry_speed_sqr; // Always <= max_entry_speed_sqr. Backward pass set this.
|
|
}
|
|
}
|
|
|
|
// Any block set at its maximum entry speed also creates an optimal plan up to this
|
|
// point in the buffer. The optimally planned pointer is updated.
|
|
if (next->entry_speed_sqr == next->max_entry_speed_sqr) {
|
|
block_buffer_planned = block_index;
|
|
}
|
|
|
|
// Automatically recalculate trapezoid for all buffer blocks from last plan's optimal planned
|
|
// pointer to the end of the buffer, except the last block.
|
|
// calculate_trapezoid_for_block(current, current->entry_speed_sqr, next->entry_speed_sqr);
|
|
block_index = next_block_index( block_index );
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void plan_init()
|
|
{
|
|
block_buffer_head = 0;
|
|
block_buffer_tail = block_buffer_head;
|
|
next_buffer_head = next_block_index(block_buffer_head);
|
|
block_buffer_planned = block_buffer_head;
|
|
memset(&pl, 0, sizeof(pl)); // Clear planner struct
|
|
}
|
|
|
|
|
|
void plan_discard_current_block()
|
|
{
|
|
if (block_buffer_head != block_buffer_tail) {
|
|
block_buffer_tail = next_block_index( block_buffer_tail );
|
|
}
|
|
}
|
|
|
|
|
|
plan_block_t *plan_get_current_block()
|
|
{
|
|
if (block_buffer_head == block_buffer_tail) { return(NULL); }
|
|
return(&block_buffer[block_buffer_tail]);
|
|
}
|
|
|
|
|
|
plan_block_t *plan_get_block_by_index(uint8_t block_index)
|
|
{
|
|
if (block_buffer_head == block_index) { return(NULL); }
|
|
return(&block_buffer[block_index]);
|
|
}
|
|
|
|
|
|
// Returns the availability status of the block ring buffer. True, if full.
|
|
uint8_t plan_check_full_buffer()
|
|
{
|
|
if (block_buffer_tail == next_buffer_head) { return(true); }
|
|
return(false);
|
|
}
|
|
|
|
|
|
// Block until all buffered steps are executed or in a cycle state. Works with feed hold
|
|
// during a synchronize call, if it should happen. Also, waits for clean cycle end.
|
|
void plan_synchronize()
|
|
{
|
|
while (plan_get_current_block() || sys.state == STATE_CYCLE) {
|
|
protocol_execute_runtime(); // Check and execute run-time commands
|
|
if (sys.abort) { return; } // Check for system abort
|
|
}
|
|
}
|
|
|
|
|
|
// Add a new linear movement to the buffer. target[N_AXIS] is the signed, absolute target position
|
|
// in millimeters. Feed rate specifies the speed of the motion. If feed rate is inverted, the feed
|
|
// rate is taken to mean "frequency" and would complete the operation in 1/feed_rate minutes.
|
|
// All position data passed to the planner must be in terms of machine position to keep the planner
|
|
// independent of any coordinate system changes and offsets, which are handled by the g-code parser.
|
|
// NOTE: Assumes buffer is available. Buffer checks are handled at a higher level by motion_control.
|
|
// In other words, the buffer head is never equal to the buffer tail. Also the feed rate input value
|
|
// is used in three ways: as a normal feed rate if invert_feed_rate is false, as inverse time if
|
|
// invert_feed_rate is true, or as seek/rapids rate if the feed_rate value is negative (and
|
|
// invert_feed_rate always false).
|
|
void plan_buffer_line(float *target, float feed_rate, uint8_t invert_feed_rate)
|
|
{
|
|
// Prepare and initialize new block
|
|
plan_block_t *block = &block_buffer[block_buffer_head];
|
|
block->step_event_count = 0;
|
|
block->millimeters = 0;
|
|
block->direction_bits = 0;
|
|
block->acceleration = SOME_LARGE_VALUE; // Scaled down to maximum acceleration later
|
|
|
|
// Compute and store initial move distance data.
|
|
int32_t target_steps[N_AXIS];
|
|
float unit_vec[N_AXIS], delta_mm;
|
|
uint8_t idx;
|
|
for (idx=0; idx<N_AXIS; idx++) {
|
|
// Calculate target position in absolute steps. This conversion should be consistent throughout.
|
|
target_steps[idx] = lround(target[idx]*settings.steps_per_mm[idx]);
|
|
|
|
// Number of steps for each axis and determine max step events
|
|
block->steps[idx] = labs(target_steps[idx]-pl.position[idx]);
|
|
block->step_event_count = max(block->step_event_count, block->steps[idx]);
|
|
|
|
// Compute individual axes distance for move and prep unit vector calculations.
|
|
// NOTE: Computes true distance from converted step values.
|
|
delta_mm = (target_steps[idx] - pl.position[idx])/settings.steps_per_mm[idx];
|
|
unit_vec[idx] = delta_mm; // Store unit vector numerator. Denominator computed later.
|
|
|
|
// Set direction bits. Bit enabled always means direction is negative.
|
|
if (delta_mm < 0 ) { block->direction_bits |= get_direction_mask(idx); }
|
|
|
|
// Incrementally compute total move distance by Euclidean norm. First add square of each term.
|
|
block->millimeters += delta_mm*delta_mm;
|
|
}
|
|
block->millimeters = sqrt(block->millimeters); // Complete millimeters calculation with sqrt()
|
|
|
|
// Bail if this is a zero-length block
|
|
if (block->step_event_count == 0) { return; }
|
|
|
|
// Adjust feed_rate value to mm/min depending on type of rate input (normal, inverse time, or rapids)
|
|
// TODO: Need to distinguish a rapids vs feed move for overrides. Some flag of some sort.
|
|
if (feed_rate < 0) { feed_rate = SOME_LARGE_VALUE; } // Scaled down to absolute max/rapids rate later
|
|
else if (invert_feed_rate) { feed_rate = block->millimeters/feed_rate; }
|
|
|
|
// Calculate the unit vector of the line move and the block maximum feed rate and acceleration limited
|
|
// by the maximum possible values. Block rapids rates are computed or feed rates are scaled down so
|
|
// they don't exceed the maximum axes velocities. The block acceleration is maximized based on direction
|
|
// and axes properties as well.
|
|
// NOTE: This calculation assumes all axes are orthogonal (Cartesian) and works with ABC-axes,
|
|
// if they are also orthogonal/independent. Operates on the absolute value of the unit vector.
|
|
float inverse_unit_vec_value;
|
|
float inverse_millimeters = 1.0/block->millimeters; // Inverse millimeters to remove multiple float divides
|
|
float junction_cos_theta = 0;
|
|
for (idx=0; idx<N_AXIS; idx++) {
|
|
if (unit_vec[idx] != 0) { // Avoid divide by zero.
|
|
unit_vec[idx] *= inverse_millimeters; // Complete unit vector calculation
|
|
inverse_unit_vec_value = abs(1.0/unit_vec[idx]); // Inverse to remove multiple float divides.
|
|
|
|
// Check and limit feed rate against max individual axis velocities and accelerations
|
|
feed_rate = min(feed_rate,settings.max_velocity[idx]*inverse_unit_vec_value);
|
|
block->acceleration = min(block->acceleration,settings.acceleration[idx]*inverse_unit_vec_value);
|
|
|
|
// Incrementally compute cosine of angle between previous and current path. Cos(theta) of the junction
|
|
// between the current move and the previous move is simply the dot product of the two unit vectors,
|
|
// where prev_unit_vec is negative. Used later to compute maximum junction speed.
|
|
junction_cos_theta -= pl.previous_unit_vec[idx] * unit_vec[idx];
|
|
}
|
|
}
|
|
|
|
/* Compute maximum allowable entry speed at junction by centripetal acceleration approximation.
|
|
Let a circle be tangent to both previous and current path line segments, where the junction
|
|
deviation is defined as the distance from the junction to the closest edge of the circle,
|
|
colinear with the circle center. The circular segment joining the two paths represents the
|
|
path of centripetal acceleration. Solve for max velocity based on max acceleration about the
|
|
radius of the circle, defined indirectly by junction deviation. This may be also viewed as
|
|
path width or max_jerk in the previous grbl version. This approach does not actually deviate
|
|
from path, but used as a robust way to compute cornering speeds, as it takes into account the
|
|
nonlinearities of both the junction angle and junction velocity.
|
|
NOTE: If the junction deviation value is finite, Grbl executes the motions in an exact path
|
|
mode (G61). If the junction deviation value is zero, Grbl will execute the motion in an exact
|
|
stop mode (G61.1) manner. In the future, if continuous mode (G64) is desired, the math here
|
|
is exactly the same. Instead of motioning all the way to junction point, the machine will
|
|
just follow the arc circle defined here. The Arduino doesn't have the CPU cycles to perform
|
|
a continuous mode path, but ARM-based microcontrollers most certainly do.
|
|
*/
|
|
// TODO: Acceleration need to be limited by the minimum of the two junctions.
|
|
// TODO: Need to setup a method to handle zero junction speeds when starting from rest.
|
|
if (block_buffer_head == block_buffer_tail) {
|
|
block->max_entry_speed_sqr = MINIMUM_PLANNER_SPEED*MINIMUM_PLANNER_SPEED;
|
|
} else {
|
|
// NOTE: Computed without any expensive trig, sin() or acos(), by trig half angle identity of cos(theta).
|
|
float sin_theta_d2 = sqrt(0.5*(1.0-junction_cos_theta)); // Trig half angle identity. Always positive.
|
|
block->max_entry_speed_sqr = (block->acceleration * settings.junction_deviation * sin_theta_d2)/(1.0-sin_theta_d2);
|
|
}
|
|
|
|
// Store block nominal speed and rate
|
|
block->nominal_speed_sqr = feed_rate*feed_rate; // (mm/min). Always > 0
|
|
// block->nominal_rate = ceil(feed_rate*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
|
|
//
|
|
// // Compute and store acceleration and distance traveled per step event.
|
|
// block->rate_delta = ceil(block->acceleration*
|
|
// ((INV_TIME_MULTIPLIER/(60.0*60.0))/(ISR_TICKS_PER_SECOND*ACCELERATION_TICKS_PER_SECOND))); // (mult*mm/isr_tic/accel_tic)
|
|
// block->d_next = ceil((block->millimeters*INV_TIME_MULTIPLIER)/block->step_event_count); // (mult*mm/step)
|
|
|
|
// Update previous path unit_vector and nominal speed (squared)
|
|
memcpy(pl.previous_unit_vec, unit_vec, sizeof(unit_vec)); // pl.previous_unit_vec[] = unit_vec[]
|
|
pl.previous_nominal_speed_sqr = block->nominal_speed_sqr;
|
|
|
|
// Update planner position
|
|
memcpy(pl.position, target_steps, sizeof(target_steps)); // pl.position[] = target_steps[]
|
|
|
|
planner_recalculate();
|
|
|
|
// Update buffer head and next buffer head indices.
|
|
// NOTE: The buffer head update is atomic since it's one byte. Performed after the new plan
|
|
// calculations to help prevent overwriting scenarios with adding a new block to a low buffer.
|
|
block_buffer_head = next_buffer_head;
|
|
next_buffer_head = next_block_index(block_buffer_head);
|
|
}
|
|
|
|
|
|
// Reset the planner position vectors. Called by the system abort/initialization routine.
|
|
void plan_sync_position()
|
|
{
|
|
uint8_t idx;
|
|
for (idx=0; idx<N_AXIS; idx++) {
|
|
pl.position[idx] = sys.position[idx];
|
|
}
|
|
}
|
|
|
|
|
|
/* STEPPER VELOCITY PROFILE DEFINITION
|
|
less than nominal rate-> +
|
|
+--------+ <- nominal_rate /|\
|
|
/ \ / | \
|
|
initial_rate -> + \ / | + <- next->initial_rate
|
|
| + <- next->initial_rate / | |
|
|
+-------------+ initial_rate -> +----+--+
|
|
time --> ^ ^ ^ ^
|
|
| | | |
|
|
decelerate distance decelerate distance
|
|
|
|
Calculates trapezoid parameters for stepper algorithm. Each block velocity profiles can be
|
|
described as either a trapezoidal or a triangular shape. The trapezoid occurs when the block
|
|
reaches the nominal speed of the block and cruises for a period of time. A triangle occurs
|
|
when the nominal speed is not reached within the block. Some other special cases exist,
|
|
such as pure ac/de-celeration velocity profiles from beginning to end or a trapezoid that
|
|
has no deceleration period when the next block resumes acceleration.
|
|
|
|
The following function determines the type of velocity profile and stores the minimum required
|
|
information for the stepper algorithm to execute the calculated profiles. In this case, only
|
|
the new initial rate and n_steps until deceleration are computed, since the stepper algorithm
|
|
already handles acceleration and cruising and just needs to know when to start decelerating.
|
|
*/
|
|
int32_t calculate_trapezoid_for_block(uint8_t block_index)
|
|
{
|
|
plan_block_t *current_block = &block_buffer[block_index];
|
|
|
|
// Determine current block exit speed
|
|
float exit_speed_sqr;
|
|
uint8_t next_index = next_block_index(block_index);
|
|
plan_block_t *next_block = plan_get_block_by_index(next_index);
|
|
if (next_block == NULL) { exit_speed_sqr = 0; } // End of planner buffer. Zero speed.
|
|
else { exit_speed_sqr = next_block->entry_speed_sqr; } // Entry speed of next block
|
|
|
|
// First determine intersection distance (in steps) from the exit point for a triangular profile.
|
|
// Computes: steps_intersect = steps/mm * ( distance/2 + (v_entry^2-v_exit^2)/(4*acceleration) )
|
|
float intersect_distance = 0.5*( current_block->millimeters + (current_block->entry_speed_sqr-exit_speed_sqr)/(2*current_block->acceleration) );
|
|
|
|
// Check if this is a pure acceleration block by a intersection distance less than zero. Also
|
|
// prevents signed and unsigned integer conversion errors.
|
|
if (intersect_distance > 0 ) {
|
|
float decelerate_distance;
|
|
// Determine deceleration distance (in steps) from nominal speed to exit speed for a trapezoidal profile.
|
|
// Value is never negative. Nominal speed is always greater than or equal to the exit speed.
|
|
// Computes: steps_decelerate = steps/mm * ( (v_nominal^2 - v_exit^2)/(2*acceleration) )
|
|
decelerate_distance = (current_block->nominal_speed_sqr - exit_speed_sqr)/(2*current_block->acceleration);
|
|
|
|
// The lesser of the two triangle and trapezoid distances always defines the velocity profile.
|
|
if (decelerate_distance > intersect_distance) { decelerate_distance = intersect_distance; }
|
|
|
|
// Finally, check if this is a pure deceleration block.
|
|
if (decelerate_distance > current_block->millimeters) { decelerate_distance = current_block->millimeters; }
|
|
|
|
return(ceil(((current_block->millimeters-decelerate_distance)*current_block->step_event_count)/ current_block->millimeters));
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
|
|
// Re-initialize buffer plan with a partially completed block, assumed to exist at the buffer tail.
|
|
// Called after a steppers have come to a complete stop for a feed hold and the cycle is stopped.
|
|
void plan_cycle_reinitialize(int32_t step_events_remaining)
|
|
{
|
|
plan_block_t *block = &block_buffer[block_buffer_tail]; // Point to partially completed block
|
|
|
|
// Only remaining millimeters and step_event_count need to be updated for planner recalculate.
|
|
// Other variables (step_x, step_y, step_z, rate_delta, etc.) all need to remain the same to
|
|
// ensure the original planned motion is resumed exactly.
|
|
block->millimeters = (block->millimeters*step_events_remaining)/block->step_event_count;
|
|
block->step_event_count = step_events_remaining;
|
|
|
|
// Re-plan from a complete stop. Reset planner entry speeds and buffer planned pointer.
|
|
block->entry_speed_sqr = 0.0;
|
|
block->max_entry_speed_sqr = MINIMUM_PLANNER_SPEED*MINIMUM_PLANNER_SPEED;
|
|
block_buffer_planned = block_buffer_tail;
|
|
planner_recalculate();
|
|
}
|