️ Fix and improve Inline Laser Power (#22690)

This commit is contained in:
Mike La Spina
2022-07-06 07:46:39 -05:00
committed by Scott Lahteine
parent 5b6c46db29
commit d965303a7a
18 changed files with 851 additions and 715 deletions

View File

@ -128,8 +128,13 @@ uint8_t Planner::delay_before_delivering; // This counter delays delivery
planner_settings_t Planner::settings; // Initialized by settings.load()
#if ENABLED(LASER_POWER_INLINE)
/**
* Set up inline block variables
* Set laser_power_floor based on SPEED_POWER_MIN to pevent a zero power output state with LASER_POWER_TRAP
*/
#if ENABLED(LASER_FEATURE)
laser_state_t Planner::laser_inline; // Current state for blocks
const uint8_t laser_power_floor = cutter.pct_to_ocr(SPEED_POWER_MIN);
#endif
uint32_t Planner::max_acceleration_steps_per_s2[DISTINCT_AXES]; // (steps/s^2) Derived from mm_per_s2
@ -799,6 +804,7 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
if (plateau_steps < 0) {
const float accelerate_steps_float = CEIL(intersection_distance(initial_rate, final_rate, accel, block->step_event_count));
accelerate_steps = _MIN(uint32_t(_MAX(accelerate_steps_float, 0)), block->step_event_count);
decelerate_steps = block->step_event_count - accelerate_steps;
plateau_steps = 0;
#if ENABLED(S_CURVE_ACCELERATION)
@ -822,7 +828,7 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
// Store new block parameters
block->accelerate_until = accelerate_steps;
block->decelerate_after = accelerate_steps + plateau_steps;
block->decelerate_after = block->step_event_count - decelerate_steps;
block->initial_rate = initial_rate;
#if ENABLED(S_CURVE_ACCELERATION)
block->acceleration_time = acceleration_time;
@ -833,46 +839,52 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
#endif
block->final_rate = final_rate;
/**
* Laser trapezoid calculations
*
* Approximate the trapezoid with the laser, incrementing the power every `entry_per` while accelerating
* and decrementing it every `exit_power_per` while decelerating, thus ensuring power is related to feedrate.
*
* LASER_POWER_INLINE_TRAPEZOID_CONT doesn't need this as it continuously approximates
*
* Note this may behave unreliably when running with S_CURVE_ACCELERATION
*/
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
if (block->laser.power > 0) { // No need to care if power == 0
const uint8_t entry_power = block->laser.power * entry_factor; // Power on block entry
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
// Speedup power
const uint8_t entry_power_diff = block->laser.power - entry_power;
if (entry_power_diff) {
block->laser.entry_per = accelerate_steps / entry_power_diff;
block->laser.power_entry = entry_power;
#if ENABLED(LASER_POWER_TRAP)
/**
* Laser Trapezoid Calculations
*
* Approximate the trapezoid with the laser, incrementing the power every `trap_ramp_entry_incr` steps while accelerating,
* and decrementing the power every `trap_ramp_exit_decr` while decelerating, to keep power proportional to feedrate.
* Laser power trap will reduce the initial power to no less than the laser_power_floor value. Based on the number
* of calculated accel/decel steps the power is distributed over the trapezoid entry- and exit-ramp steps.
*
* trap_ramp_active_pwr - The active power is initially set at a reduced level factor of initial power / accel steps and
* will be additively incremented using a trap_ramp_entry_incr value for each accel step processed later in the stepper code.
* The trap_ramp_exit_decr value is calculated as power / decel steps and is also adjusted to no less than the power floor.
*
* If the power == 0 the inline mode variables need to be set to zero to prevent stepper processing. The method allows
* for simpler non-powered moves like G0 or G28.
*
* Laser Trap Power works for all Jerk and Curve modes; however Arc-based moves will have issues since the segments are
* usually too small.
*/
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
if (block->laser.power > 0) {
NOLESS(block->laser.power, laser_power_floor);
block->laser.trap_ramp_active_pwr = (block->laser.power - laser_power_floor) * (initial_rate / float(block->nominal_rate)) + laser_power_floor;
block->laser.trap_ramp_entry_incr = (block->laser.power - block->laser.trap_ramp_active_pwr) / accelerate_steps;
float laser_pwr = block->laser.power * (final_rate / float(block->nominal_rate));
NOLESS(laser_pwr, laser_power_floor);
block->laser.trap_ramp_exit_decr = (block->laser.power - laser_pwr) / decelerate_steps;
#if ENABLED(DEBUG_LASER_TRAP)
SERIAL_ECHO_MSG("lp:",block->laser.power);
SERIAL_ECHO_MSG("as:",accelerate_steps);
SERIAL_ECHO_MSG("ds:",decelerate_steps);
SERIAL_ECHO_MSG("p.trap:",block->laser.trap_ramp_active_pwr);
SERIAL_ECHO_MSG("p.incr:",block->laser.trap_ramp_entry_incr);
SERIAL_ECHO_MSG("p.decr:",block->laser.trap_ramp_exit_decr);
#endif
}
else {
block->laser.entry_per = 0;
block->laser.power_entry = block->laser.power;
block->laser.trap_ramp_active_pwr = 0;
block->laser.trap_ramp_entry_incr = 0;
block->laser.trap_ramp_exit_decr = 0;
}
// Slowdown power
const uint8_t exit_power = block->laser.power * exit_factor, // Power on block entry
exit_power_diff = block->laser.power - exit_power;
if (exit_power_diff) {
block->laser.exit_per = (block->step_event_count - block->decelerate_after) / exit_power_diff;
block->laser.power_exit = exit_power;
}
else {
block->laser.exit_per = 0;
block->laser.power_exit = block->laser.power;
}
#else
block->laser.power_entry = entry_power;
#endif
}
}
#endif
#endif // LASER_POWER_TRAP
}
/* PLANNER SPEED DEFINITION
@ -1130,10 +1142,9 @@ void Planner::recalculate_trapezoids() {
// The tail may be changed by the ISR so get a local copy.
uint8_t block_index = block_buffer_tail,
head_block_index = block_buffer_head;
// Since there could be non-move blocks in the head of the queue, and the
// Since there could be a sync block in the head of the queue, and the
// next loop must not recalculate the head block (as it needs to be
// specially handled), scan backwards to the first move block.
// specially handled), scan backwards to the first non-SYNC block.
while (head_block_index != block_index) {
// Go back (head always point to the first free block)
@ -1203,7 +1214,7 @@ void Planner::recalculate_trapezoids() {
// Last/newest block in buffer. Exit speed is set with MINIMUM_PLANNER_SPEED. Always recalculated.
if (next) {
// Mark the last block as RECALCULATE, to prevent the Stepper ISR running it.
// Mark the next(last) block as RECALCULATE, to prevent the Stepper ISR running it.
// As the last block is always recalculated here, there is a chance the block isn't
// marked as RECALCULATE yet. That's the reason for the following line.
block->flag.recalculate = true;
@ -1295,7 +1306,7 @@ void Planner::recalculate() {
#endif // HAS_FAN
/**
* Maintain fans, paste extruder pressure,
* Maintain fans, paste extruder pressure, spindle/laser power
*/
void Planner::check_axes_activity() {
@ -1359,7 +1370,7 @@ void Planner::check_axes_activity() {
}
else {
TERN_(HAS_CUTTER, cutter.refresh());
TERN_(HAS_CUTTER, if (cutter.cutter_mode == CUTTER_MODE_STANDARD) cutter.refresh());
#if HAS_TAIL_FAN_SPEED
FANS_LOOP(i) {
@ -1459,7 +1470,7 @@ void Planner::check_axes_activity() {
for (uint8_t b = block_buffer_tail; b != block_buffer_head; b = next_block_index(b)) {
const block_t * const block = &block_buffer[b];
if (NUM_AXIS_GANG(block->steps.x, || block->steps.y, || block->steps.z, || block->steps.i, || block->steps.j, || block->steps.k, || block->steps.u, || block->steps.v, || block->steps.w)) {
const float se = (float)block->steps.e / block->step_event_count * SQRT(block->nominal_speed_sqr); // mm/sec
const float se = (float)block->steps.e / block->step_event_count * SQRT(block->nominal_speed_sqr); // mm/sec;
NOLESS(high, se);
}
}
@ -1781,7 +1792,7 @@ void Planner::synchronize() { while (busy()) idle(); }
bool Planner::_buffer_steps(const xyze_long_t &target
OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float)
OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
, feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters/*=0.0*/
, feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters
) {
// Wait for the next available block
@ -1863,8 +1874,36 @@ bool Planner::_populate_block(
);
/* <-- add a slash to enable
#define _ALINE(A) " " STR_##A ":", target[_AXIS(A)], " (", int32_t(target[_AXIS(A)] - position[_AXIS(A)]), " steps)"
SERIAL_ECHOLNPGM(" _populate_block FR:", fr_mm_s, LOGICAL_AXIS_MAP(_ALINE));
SERIAL_ECHOLNPGM(
" _populate_block FR:", fr_mm_s,
" A:", target.a, " (", da, " steps)"
#if HAS_Y_AXIS
" B:", target.b, " (", db, " steps)"
#endif
#if HAS_Z_AXIS
" C:", target.c, " (", dc, " steps)"
#endif
#if HAS_I_AXIS
" " STR_I ":", target.i, " (", di, " steps)"
#endif
#if HAS_J_AXIS
" " STR_J ":", target.j, " (", dj, " steps)"
#endif
#if HAS_K_AXIS
" " STR_K ":", target.k, " (", dk, " steps)"
#endif
#if HAS_U_AXIS
" " STR_U ":", target.u, " (", du, " steps)"
#endif
#if HAS_V_AXIS
" " STR_V ":", target.v, " (", dv, " steps)"
#endif
#if HAS_W_AXIS
" " STR_W ":", target.w, " (", dw, " steps)"
#if HAS_EXTRUDERS
" E:", target.e, " (", de, " steps)"
#endif
);
//*/
#if EITHER(PREVENT_COLD_EXTRUSION, PREVENT_LENGTHY_EXTRUDE)
@ -1962,11 +2001,34 @@ bool Planner::_populate_block(
// Set direction bits
block->direction_bits = dm;
// Update block laser power
#if ENABLED(LASER_POWER_INLINE)
laser_inline.status.isPlanned = true;
block->laser.status = laser_inline.status;
block->laser.power = laser_inline.power;
/**
* Update block laser power
* For standard mode get the cutter.power value for processing, since it's
* only set by apply_power().
*/
#if HAS_CUTTER
switch (cutter.cutter_mode) {
default: break;
case CUTTER_MODE_STANDARD: block->cutter_power = cutter.power; break;
#if ENABLED(LASER_FEATURE)
/**
* For inline mode get the laser_inline variables, including power and status.
* Dynamic mode only needs to update if the feedrate has changed, since it's
* calculated from the current feedrate and power level.
*/
case CUTTER_MODE_CONTINUOUS:
block->laser.power = laser_inline.power;
block->laser.status = laser_inline.status;
break;
case CUTTER_MODE_DYNAMIC:
if (cutter.laser_feedrate_changed()) // Only process changes in rate
block->laser.power = laser_inline.power = cutter.calc_dynamic_power();
break;
#endif
}
#endif
// Number of steps for each axis
@ -2028,9 +2090,9 @@ bool Planner::_populate_block(
#endif
#elif ENABLED(MARKFORGED_XY)
steps_dist_mm.a = (da - db) * mm_per_step[A_AXIS];
steps_dist_mm.b = db * mm_per_step[B_AXIS];
steps_dist_mm.b = db * mm_per_step[B_AXIS];
#elif ENABLED(MARKFORGED_YX)
steps_dist_mm.a = da * mm_per_step[A_AXIS];
steps_dist_mm.a = da * mm_per_step[A_AXIS];
steps_dist_mm.b = (db - da) * mm_per_step[B_AXIS];
#else
XYZ_CODE(
@ -2076,21 +2138,12 @@ bool Planner::_populate_block(
block->millimeters = millimeters;
else {
/**
* Distance for interpretation of feedrate in accordance with LinuxCNC (the successor of
* NIST RS274NGC interpreter - version 3) and its default CANON_XYZ feed reference mode.
*
* Assume:
* - X, Y, Z are the primary linear axes;
* - U, V, W are secondary linear axes;
* - A, B, C are rotational axes.
*
* Then:
* - dX, dY, dZ are the displacements of the primary linear axes;
* - dU, dV, dW are the displacements of linear axes;
* - dA, dB, dC are the displacements of rotational axes.
*
* The time it takes to execute move command with feedrate F is t = D/F,
* where D is the total distance, calculated as follows:
* Distance for interpretation of feedrate in accordance with LinuxCNC (the successor of NIST
* RS274NGC interpreter - version 3) and its default CANON_XYZ feed reference mode.
* Assume that X, Y, Z are the primary linear axes and U, V, W are secondary linear axes and A, B, C are
* rotational axes. Then dX, dY, dZ are the displacements of the primary linear axes and dU, dV, dW are the displacements of linear axes and
* dA, dB, dC are the displacements of rotational axes.
* The time it takes to execute move command with feedrate F is t = D/F, where D is the total distance, calculated as follows:
* D^2 = dX^2 + dY^2 + dZ^2
* if D^2 == 0 (none of XYZ move but any secondary linear axes move, whether other axes are moved or not):
* D^2 = dU^2 + dV^2 + dW^2
@ -2099,9 +2152,8 @@ bool Planner::_populate_block(
*/
float distance_sqr = (
#if ENABLED(ARTICULATED_ROBOT_ARM)
// For articulated robots, interpreting feedrate like LinuxCNC would require inverse kinematics. As a workaround,
// assume that motors sit on a mutually-orthogonal axes and we can think of distance as magnitude of an n-vector
// in an n-dimensional Euclidian space.
// For articulated robots, interpreting feedrate like LinuxCNC would require inverse kinematics. As a workaround, pretend that motors sit on n mutually orthogonal
// axes and assume that we could think of distance as magnitude of an n-vector in an n-dimensional Euclidian space.
NUM_AXIS_GANG(
sq(steps_dist_mm.x), + sq(steps_dist_mm.y), + sq(steps_dist_mm.z),
+ sq(steps_dist_mm.i), + sq(steps_dist_mm.j), + sq(steps_dist_mm.k),
@ -2121,7 +2173,7 @@ bool Planner::_populate_block(
#elif CORE_IS_YZ
XYZ_GANG(sq(steps_dist_mm.x), + sq(steps_dist_mm.head.y), + sq(steps_dist_mm.head.z))
#else
XYZ_GANG(sq(steps_dist_mm.x), + sq(steps_dist_mm.y), + sq(steps_dist_mm.z))
XYZ_GANG(sq(steps_dist_mm.x), + sq(steps_dist_mm.y), + sq(steps_dist_mm.z))
#endif
);
@ -2154,9 +2206,9 @@ bool Planner::_populate_block(
/**
* At this point at least one of the axes has more steps than
* MIN_STEPS_PER_SEGMENT, ensuring the segment won't get dropped
* as zero-length. It's important to not apply corrections to blocks
* that would get dropped!
* MIN_STEPS_PER_SEGMENT, ensuring the segment won't get dropped as
* zero-length. It's important to not apply corrections
* to blocks that would get dropped!
*
* A correction function is permitted to add steps to an axis, it
* should *never* remove steps!
@ -2177,7 +2229,6 @@ bool Planner::_populate_block(
TERN_(MIXING_EXTRUDER, mixer.populate_block(block->b_color));
TERN_(HAS_CUTTER, block->cutter_power = cutter.power);
#if HAS_FAN
FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i];
@ -2192,9 +2243,15 @@ bool Planner::_populate_block(
#if ENABLED(AUTO_POWER_CONTROL)
if (NUM_AXIS_GANG(
block->steps.x, || block->steps.y, || block->steps.z,
|| block->steps.i, || block->steps.j, || block->steps.k,
|| block->steps.u, || block->steps.v, || block->steps.w
block->steps.x,
|| block->steps.y,
|| block->steps.z,
|| block->steps.i,
|| block->steps.j,
|| block->steps.k,
|| block->steps.u,
|| block->steps.v,
|| block->steps.w
)) powerManager.power_on();
#endif
@ -2428,7 +2485,7 @@ bool Planner::_populate_block(
if (speed_factor < 1.0f) {
current_speed *= speed_factor;
block->nominal_rate *= speed_factor;
block->nominal_speed_sqr *= sq(speed_factor);
block->nominal_speed_sqr = block->nominal_speed_sqr * sq(speed_factor);
}
// Compute and limit the acceleration rate for the trapezoid generator.
@ -2630,15 +2687,14 @@ bool Planner::_populate_block(
vmax_junction_sqr = sq(float(MINIMUM_PLANNER_SPEED));
}
else {
NOLESS(junction_cos_theta, -0.999999f); // Check for numerical round-off to avoid divide by zero.
// Convert delta vector to unit vector
xyze_float_t junction_unit_vec = unit_vec - prev_unit_vec;
normalize_junction_vector(junction_unit_vec);
const float junction_acceleration = limit_value_by_axis_maximum(block->acceleration, junction_unit_vec);
NOLESS(junction_cos_theta, -0.999999f); // Check for numerical round-off to avoid divide by zero.
const float sin_theta_d2 = SQRT(0.5f * (1.0f - junction_cos_theta)); // Trig half angle identity. Always positive.
const float junction_acceleration = limit_value_by_axis_maximum(block->acceleration, junction_unit_vec),
sin_theta_d2 = SQRT(0.5f * (1.0f - junction_cos_theta)); // Trig half angle identity. Always positive.
vmax_junction_sqr = junction_acceleration * junction_deviation_mm * sin_theta_d2 / (1.0f - sin_theta_d2);
@ -2889,21 +2945,19 @@ bool Planner::_populate_block(
/**
* Planner::buffer_sync_block
* Add a block to the buffer that just updates the position,
* or in case of LASER_SYNCHRONOUS_M106_M107 the fan PWM
* Add a block to the buffer that just updates the position
* @param sync_flag BLOCK_FLAG_SYNC_FANS & BLOCK_FLAG_LASER_PWR
* Supports LASER_SYNCHRONOUS_M106_M107 and LASER_POWER_SYNC power sync block buffer queueing.
*/
void Planner::buffer_sync_block(TERN_(LASER_SYNCHRONOUS_M106_M107, const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_POSITION*/)) {
#if DISABLED(LASER_SYNCHRONOUS_M106_M107)
constexpr BlockFlagBit sync_flag = BLOCK_BIT_SYNC_POSITION;
#endif
void Planner::buffer_sync_block(const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_POSITION*/) {
// Wait for the next available block
uint8_t next_buffer_head;
block_t * const block = get_next_free_block(next_buffer_head);
// Clear block
block->reset();
memset(block, 0, sizeof(block_t));
block->flag.apply(sync_flag);
block->position = position;
@ -2915,6 +2969,12 @@ void Planner::buffer_sync_block(TERN_(LASER_SYNCHRONOUS_M106_M107, const BlockFl
FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i];
#endif
/**
* M3-based power setting can be processed inline with a laser power sync block.
* During active moves cutter.power is processed immediately, otherwise on the next move.
*/
TERN_(LASER_POWER_SYNC, block->laser.power = cutter.power);
// If this is the first added movement, reload the delay, otherwise, cancel it.
if (block_buffer_head == block_buffer_tail) {
// If it was the first queued block, restart the 1st block delivery delay, to
@ -3052,8 +3112,8 @@ bool Planner::buffer_segment(const abce_pos_t &abce
if (!_buffer_steps(target
OPTARG(HAS_POSITION_FLOAT, target_float)
OPTARG(HAS_DIST_MM_ARG, cart_dist_mm)
, fr_mm_s, extruder, millimeters
)) return false;
, fr_mm_s, extruder, millimeters)
) return false;
stepper.wake_up();
return true;
@ -3099,7 +3159,7 @@ bool Planner::buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s, cons
inverse_kinematics(machine);
#if ENABLED(SCARA_FEEDRATE_SCALING)
// For SCARA scale the feed rate from mm/s to degrees/s
// For SCARA scale the feedrate from mm/s to degrees/s
// i.e., Complete the angular vector in the given time.
const float duration_recip = inv_duration ?: fr_mm_s / mm;
const xyz_pos_t diff = delta - position_float;
@ -3120,14 +3180,6 @@ bool Planner::buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s, cons
#if ENABLED(DIRECT_STEPPING)
/**
* @brief Add a direct stepping page block to the buffer
* and wake up the Stepper ISR to process it.
*
* @param page_idx Page index provided by G6 I<index>
* @param extruder The extruder to use in the move
* @param num_steps Number of steps to process in the ISR
*/
void Planner::buffer_page(const page_idx_t page_idx, const uint8_t extruder, const uint16_t num_steps) {
if (!last_page_step_rate) {
kill(GET_TEXT_F(MSG_BAD_PAGE_SPEED));
@ -3212,7 +3264,7 @@ void Planner::set_machine_position_mm(const abce_pos_t &abce) {
if (has_blocks_queued()) {
//previous_nominal_speed_sqr = 0.0; // Reset planner junction speeds. Assume start from rest.
//previous_speed.reset();
buffer_sync_block();
buffer_sync_block(BLOCK_BIT_SYNC_POSITION);
}
else {
#if ENABLED(BACKLASH_COMPENSATION)
@ -3225,12 +3277,6 @@ void Planner::set_machine_position_mm(const abce_pos_t &abce) {
}
}
/**
* @brief Set the Planner position in mm
* @details Set the Planner position from a native machine position in mm
*
* @param xyze A native (Cartesian) machine position
*/
void Planner::set_position_mm(const xyze_pos_t &xyze) {
xyze_pos_t machine = xyze;
TERN_(HAS_POSITION_MODIFIERS, apply_modifiers(machine, true));
@ -3259,20 +3305,14 @@ void Planner::set_position_mm(const xyze_pos_t &xyze) {
TERN_(IS_KINEMATIC, TERN_(HAS_EXTRUDERS, position_cart.e = e));
if (has_blocks_queued())
buffer_sync_block();
buffer_sync_block(BLOCK_BIT_SYNC_POSITION);
else
stepper.set_axis_position(E_AXIS, position.e);
}
#endif
/**
* @brief Recalculate the steps/s^2 acceleration rates, based on the mm/s^2
* @details Update planner movement factors after a change to certain settings:
* - max_acceleration_steps_per_s2 from settings max_acceleration_mm_per_s2 * axis_steps_per_mm (M201, M92)
* - acceleration_long_cutoff based on the largest max_acceleration_steps_per_s2 (M201)
* - max_e_jerk for all extruders based on junction_deviation_mm (M205 J)
*/
// Recalculate the steps/s^2 acceleration rates, based on the mm/s^2
void Planner::refresh_acceleration_rates() {
uint32_t highest_rate = 1;
LOOP_DISTINCT_AXES(i) {
@ -3285,8 +3325,8 @@ void Planner::refresh_acceleration_rates() {
}
/**
* @brief Recalculate 'position' and 'mm_per_step'.
* @details Required whenever settings.axis_steps_per_mm changes!
* Recalculate 'position' and 'mm_per_step'.
* Must be called whenever settings.axis_steps_per_mm changes!
*/
void Planner::refresh_positioning() {
LOOP_DISTINCT_AXES(i) mm_per_step[i] = 1.0f / settings.axis_steps_per_mm[i];

View File

@ -89,30 +89,6 @@
#define HAS_DIST_MM_ARG 1
#endif
#if ENABLED(LASER_POWER_INLINE)
typedef struct {
bool isPlanned:1;
bool isEnabled:1;
bool dir:1;
bool Reserved:6;
} power_status_t;
typedef struct {
power_status_t status; // See planner settings for meaning
uint8_t power; // Ditto; When in trapezoid mode this is nominal power
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
uint8_t power_entry; // Entry power for the laser
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
uint8_t power_exit; // Exit power for the laser
uint32_t entry_per, // Steps per power increment (to avoid floats in stepper calcs)
exit_per; // Steps per power decrement
#endif
#endif
} block_laser_t;
#endif
/**
* Planner block flags as boolean bit fields
*/
@ -132,14 +108,14 @@ enum BlockFlagBit {
BLOCK_BIT_SYNC_POSITION
// Direct stepping page
#if ENABLED(DIRECT_STEPPING)
, BLOCK_BIT_PAGE
#endif
OPTARG(DIRECT_STEPPING, BLOCK_BIT_PAGE)
// Sync the fan speeds from the block
#if ENABLED(LASER_SYNCHRONOUS_M106_M107)
, BLOCK_BIT_SYNC_FANS
#endif
OPTARG(LASER_SYNCHRONOUS_M106_M107, BLOCK_BIT_SYNC_FANS)
// Sync laser power from a queued block
OPTARG(LASER_POWER_SYNC, BLOCK_BIT_LASER_PWR)
};
/**
@ -165,6 +141,10 @@ typedef struct {
#if ENABLED(LASER_SYNCHRONOUS_M106_M107)
bool sync_fans:1;
#endif
#if ENABLED(LASER_POWER_SYNC)
bool sync_laser_pwr:1;
#endif
};
};
@ -176,9 +156,34 @@ typedef struct {
} block_flags_t;
#if ENABLED(LASER_FEATURE)
typedef struct {
bool isEnabled:1; // Set to engage the inline laser power output.
bool dir:1;
bool isPowered:1; // Set on any parsed G1, G2, G3, or G5 powered move, cleared on G0 and G28.
bool isSyncPower:1; // Set on a M3 sync based set laser power, used to determine active trap power
bool Reserved:4;
} power_status_t;
typedef struct {
power_status_t status; // See planner settings for meaning
uint8_t power; // Ditto; When in trapezoid mode this is nominal power
#if ENABLED(LASER_POWER_TRAP)
float trap_ramp_active_pwr; // Laser power level during active trapezoid smoothing
float trap_ramp_entry_incr; // Acceleration per step laser power increment (trap entry)
float trap_ramp_exit_decr; // Deceleration per step laser power decrement (trap exit)
#endif
} block_laser_t;
#endif
/**
* A single entry in the planner buffer, used to set up and
* track a coordinated linear motion for one or more axes.
* struct block_t
*
* A single entry in the planner buffer.
* Tracks linear movement over multiple axes.
*
* The "nominal" values are as-specified by G-code, and
* may never actually be reached due to acceleration limits.
@ -188,7 +193,8 @@ typedef struct block_t {
volatile block_flags_t flag; // Block flags
volatile bool is_fan_sync() { return TERN0(LASER_SYNCHRONOUS_M106_M107, flag.sync_fans); }
volatile bool is_sync() { return flag.sync_position || is_fan_sync(); }
volatile bool is_pwr_sync() { return TERN0(LASER_POWER_SYNC, flag.sync_laser_pwr); }
volatile bool is_sync() { return flag.sync_position || is_fan_sync() || is_pwr_sync(); }
volatile bool is_page() { return TERN0(DIRECT_STEPPING, flag.page); }
volatile bool is_move() { return !(is_sync() || is_page()); }
@ -270,12 +276,10 @@ typedef struct block_t {
xyze_pos_t start_position;
#endif
#if ENABLED(LASER_POWER_INLINE)
#if ENABLED(LASER_FEATURE)
block_laser_t laser;
#endif
void reset() { memset((char*)this, 0, sizeof(*this)); }
} block_t;
#if ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL, POWER_LOSS_RECOVERY)
@ -284,7 +288,7 @@ typedef struct block_t {
#define BLOCK_MOD(n) ((n)&(BLOCK_BUFFER_SIZE-1))
#if ENABLED(LASER_POWER_INLINE)
#if ENABLED(LASER_FEATURE)
typedef struct {
/**
* Laser status flags
@ -293,11 +297,10 @@ typedef struct block_t {
/**
* Laser power: 0 or 255 in case of PWM-less laser,
* or the OCR (oscillator count register) value;
*
* Using OCR instead of raw power, because it avoids
* floating point operations during the move loop.
*/
uint8_t power;
volatile uint8_t power;
} laser_state_t;
#endif
@ -399,7 +402,7 @@ class Planner {
static planner_settings_t settings;
#if ENABLED(LASER_POWER_INLINE)
#if ENABLED(LASER_FEATURE)
static laser_state_t laser_inline;
#endif
@ -784,12 +787,11 @@ class Planner {
/**
* Planner::buffer_sync_block
* Add a block to the buffer that just updates the position or in
* case of LASER_SYNCHRONOUS_M106_M107 the fan pwm
* Add a block to the buffer that just updates the position
* @param sync_flag sets a condition bit to process additional items
* such as sync fan pwm or sync M3/M4 laser power into a queued block
*/
static void buffer_sync_block(
TERN_(LASER_SYNCHRONOUS_M106_M107, const BlockFlagBit flag=BLOCK_BIT_SYNC_POSITION)
);
static void buffer_sync_block(const BlockFlagBit flag=BLOCK_BIT_SYNC_POSITION);
#if IS_KINEMATIC
private:

View File

@ -253,20 +253,6 @@ xyz_long_t Stepper::endstops_trigsteps;
xyze_long_t Stepper::count_position{0};
xyze_int8_t Stepper::count_direction{0};
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
Stepper::stepper_laser_t Stepper::laser_trap = {
.enabled = false,
.cur_power = 0,
.cruise_set = false,
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
.last_step_count = 0,
.acc_step_count = 0
#else
.till_update = 0
#endif
};
#endif
#define MINDIR(A) (count_direction[_AXIS(A)] < 0)
#define MAXDIR(A) (count_direction[_AXIS(A)] > 0)
@ -1964,7 +1950,6 @@ uint32_t Stepper::block_phase_isr() {
// If there is a current block
if (current_block) {
// If current block is finished, reset pointer and finalize state
if (step_events_completed >= step_event_count) {
#if ENABLED(DIRECT_STEPPING)
@ -2017,32 +2002,28 @@ uint32_t Stepper::block_phase_isr() {
else if (LA_steps) nextAdvanceISR = 0;
#endif
// Update laser - Accelerating
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
if (laser_trap.enabled) {
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
if (current_block->laser.entry_per) {
laser_trap.acc_step_count -= step_events_completed - laser_trap.last_step_count;
laser_trap.last_step_count = step_events_completed;
/*
* Adjust Laser Power - Accelerating
* isPowered - True when a move is powered.
* isEnabled - laser power is active.
* Laser power variables are calulated and stored in this block by the planner code.
*
* trap_ramp_active_pwr - the active power in this block across accel or decel trap steps.
* trap_ramp_entry_incr - holds the precalculated value to increase the current power per accel step.
*
* Apply the starting active power and then increase power per step by the trap_ramp_entry_incr value if positive.
*/
// Should be faster than a divide, since this should trip just once
if (laser_trap.acc_step_count < 0) {
while (laser_trap.acc_step_count < 0) {
laser_trap.acc_step_count += current_block->laser.entry_per;
if (laser_trap.cur_power < current_block->laser.power) laser_trap.cur_power++;
}
cutter.ocr_set_power(laser_trap.cur_power);
}
#if ENABLED(LASER_POWER_TRAP)
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
if (current_block->laser.trap_ramp_entry_incr > 0) {
cutter.apply_power(current_block->laser.trap_ramp_active_pwr);
current_block->laser.trap_ramp_active_pwr += current_block->laser.trap_ramp_entry_incr;
}
#else
if (laser_trap.till_update)
laser_trap.till_update--;
else {
laser_trap.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
laser_trap.cur_power = (current_block->laser.power * acc_step_rate) / current_block->nominal_rate;
cutter.ocr_set_power(laser_trap.cur_power); // Cycle efficiency is irrelevant it the last line was many cycles
}
#endif
}
// Not a powered move.
else cutter.apply_power(0);
}
#endif
}
@ -2066,7 +2047,6 @@ uint32_t Stepper::block_phase_isr() {
: current_block->final_rate;
}
#else
// Using the old trapezoidal control
step_rate = STEP_MULTIPLY(deceleration_time, current_block->acceleration_rate);
if (step_rate < acc_step_rate) { // Still decelerating?
@ -2094,37 +2074,25 @@ uint32_t Stepper::block_phase_isr() {
else if (LA_steps) nextAdvanceISR = 0;
#endif // LIN_ADVANCE
// Update laser - Decelerating
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
if (laser_trap.enabled) {
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
if (current_block->laser.exit_per) {
laser_trap.acc_step_count -= step_events_completed - laser_trap.last_step_count;
laser_trap.last_step_count = step_events_completed;
// Should be faster than a divide, since this should trip just once
if (laser_trap.acc_step_count < 0) {
while (laser_trap.acc_step_count < 0) {
laser_trap.acc_step_count += current_block->laser.exit_per;
if (laser_trap.cur_power > current_block->laser.power_exit) laser_trap.cur_power--;
}
cutter.ocr_set_power(laser_trap.cur_power);
}
/*
* Adjust Laser Power - Decelerating
* trap_ramp_entry_decr - holds the precalculated value to decrease the current power per decel step.
*/
#if ENABLED(LASER_POWER_TRAP)
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
if (current_block->laser.trap_ramp_exit_decr > 0) {
current_block->laser.trap_ramp_active_pwr -= current_block->laser.trap_ramp_exit_decr;
cutter.apply_power(current_block->laser.trap_ramp_active_pwr);
}
#else
if (laser_trap.till_update)
laser_trap.till_update--;
else {
laser_trap.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
laser_trap.cur_power = (current_block->laser.power * step_rate) / current_block->nominal_rate;
cutter.ocr_set_power(laser_trap.cur_power); // Cycle efficiency isn't relevant when the last line was many cycles
}
#endif
// Not a powered move.
else cutter.apply_power(0);
}
}
#endif
}
// Must be in cruise phase otherwise
else {
else { // Must be in cruise phase otherwise
#if ENABLED(LIN_ADVANCE)
// If there are any esteps, fire the next advance_isr "now"
@ -2139,24 +2107,50 @@ uint32_t Stepper::block_phase_isr() {
// The timer interval is just the nominal value for the nominal speed
interval = ticks_nominal;
// Update laser - Cruising
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
if (laser_trap.enabled) {
if (!laser_trap.cruise_set) {
laser_trap.cur_power = current_block->laser.power;
cutter.ocr_set_power(laser_trap.cur_power);
laser_trap.cruise_set = true;
}
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
laser_trap.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
#else
laser_trap.last_step_count = step_events_completed;
#endif
}
#endif
}
/* Adjust Laser Power - Cruise
* power - direct or floor adjusted active laser power.
*/
#if ENABLED(LASER_POWER_TRAP)
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
if (step_events_completed + 1 == accelerate_until) {
if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
if (current_block->laser.trap_ramp_entry_incr > 0) {
current_block->laser.trap_ramp_active_pwr = current_block->laser.power;
cutter.apply_power(current_block->laser.power);
}
}
// Not a powered move.
else cutter.apply_power(0);
}
}
#endif
}
#if ENABLED(LASER_FEATURE)
/*
* CUTTER_MODE_DYNAMIC is experimental and developing.
* Super-fast method to dynamically adjust the laser power OCR value based on the input feedrate in mm-per-minute.
* TODO: Set up Min/Max OCR offsets to allow tuning and scaling of various lasers.
* TODO: Integrate accel/decel +-rate into the dynamic laser power calc.
*/
if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC
&& planner.laser_inline.status.isPowered // isPowered flag set on any parsed G1, G2, G3, or G5 move; cleared on any others.
&& cutter.last_block_power != current_block->laser.power // Prevent constant update without change
) {
cutter.apply_power(current_block->laser.power);
cutter.last_block_power = current_block->laser.power;
}
#endif
}
else { // !current_block
#if ENABLED(LASER_FEATURE)
if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC) {
cutter.apply_power(0); // No movement in dynamic mode so turn Laser off
}
#endif
}
// If there is no current block at this point, attempt to pop one from the buffer
@ -2169,11 +2163,18 @@ uint32_t Stepper::block_phase_isr() {
// Sync block? Sync the stepper counts or fan speeds and return
while (current_block->is_sync()) {
if (current_block->is_fan_sync()) {
TERN_(LASER_SYNCHRONOUS_M106_M107, planner.sync_fan_speeds(current_block->fan_speed));
}
else
_set_position(current_block->position);
#if ENABLED(LASER_POWER_SYNC)
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
if (current_block->is_pwr_sync()) {
planner.laser_inline.status.isSyncPower = true;
cutter.apply_power(current_block->laser.power);
}
}
#endif
TERN_(LASER_SYNCHRONOUS_M106_M107, if (current_block->is_fan_sync()) planner.sync_fan_speeds(current_block->fan_speed));
if (!(current_block->is_fan_sync() || current_block->is_pwr_sync())) _set_position(current_block->position);
discard_current_block();
@ -2183,8 +2184,10 @@ uint32_t Stepper::block_phase_isr() {
}
// For non-inline cutter, grossly apply power
#if ENABLED(LASER_FEATURE) && DISABLED(LASER_POWER_INLINE)
cutter.apply_power(current_block->cutter_power);
#if HAS_CUTTER
if (cutter.cutter_mode == CUTTER_MODE_STANDARD) {
cutter.apply_power(current_block->cutter_power);
}
#endif
#if ENABLED(POWER_LOSS_RECOVERY)
@ -2357,36 +2360,22 @@ uint32_t Stepper::block_phase_isr() {
set_directions(current_block->direction_bits);
}
#if ENABLED(LASER_POWER_INLINE)
const power_status_t stat = current_block->laser.status;
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
laser_trap.enabled = stat.isPlanned && stat.isEnabled;
laser_trap.cur_power = current_block->laser.power_entry; // RESET STATE
laser_trap.cruise_set = false;
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
laser_trap.last_step_count = 0;
laser_trap.acc_step_count = current_block->laser.entry_per / 2;
#else
laser_trap.till_update = 0;
#endif
// Always have PWM in this case
if (stat.isPlanned) { // Planner controls the laser
cutter.ocr_set_power(
stat.isEnabled ? laser_trap.cur_power : 0 // ON with power or OFF
);
}
#else
if (stat.isPlanned) { // Planner controls the laser
#if ENABLED(SPINDLE_LASER_USE_PWM)
cutter.ocr_set_power(
stat.isEnabled ? current_block->laser.power : 0 // ON with power or OFF
);
#if ENABLED(LASER_FEATURE)
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) { // Planner controls the laser
if (planner.laser_inline.status.isSyncPower)
// If the previous block was a M3 sync power then skip the trap power init otherwise it will 0 the sync power.
planner.laser_inline.status.isSyncPower = false; // Clear the flag to process subsequent trap calc's.
else if (current_block->laser.status.isEnabled) {
#if ENABLED(LASER_POWER_TRAP)
TERN_(DEBUG_LASER_TRAP, SERIAL_ECHO_MSG("InitTrapPwr:",current_block->laser.trap_ramp_active_pwr));
cutter.apply_power(current_block->laser.status.isPowered ? current_block->laser.trap_ramp_active_pwr : 0);
#else
cutter.set_enabled(stat.isEnabled);
TERN_(DEBUG_CUTTER_POWER, SERIAL_ECHO_MSG("InlinePwr:",current_block->laser.power));
cutter.apply_power(current_block->laser.status.isPowered ? current_block->laser.power : 0);
#endif
}
#endif
#endif // LASER_POWER_INLINE
}
#endif // LASER_FEATURE
// If the endstop is already pressed, endstop interrupts won't invoke
// endstop_triggered and the move will grind. So check here for a
@ -2416,21 +2405,6 @@ uint32_t Stepper::block_phase_isr() {
// Calculate the initial timer interval
interval = calc_timer_interval(current_block->initial_rate, &steps_per_isr);
}
#if ENABLED(LASER_POWER_INLINE_CONTINUOUS)
else { // No new block found; so apply inline laser parameters
// This should mean ending file with 'M5 I' will stop the laser; thus the inline flag isn't needed
const power_status_t stat = planner.laser_inline.status;
if (stat.isPlanned) { // Planner controls the laser
#if ENABLED(SPINDLE_LASER_USE_PWM)
cutter.ocr_set_power(
stat.isEnabled ? planner.laser_inline.power : 0 // ON with power or OFF
);
#else
cutter.set_enabled(stat.isEnabled);
#endif
}
}
#endif
}
// Return the interval to wait

View File

@ -444,25 +444,6 @@ class Stepper {
// Current stepper motor directions (+1 or -1)
static xyze_int8_t count_direction;
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
typedef struct {
bool enabled; // Trapezoid needed flag (i.e., laser on, planner in control)
uint8_t cur_power; // Current laser power
bool cruise_set; // Power set up for cruising?
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
uint16_t till_update; // Countdown to the next update
#else
uint32_t last_step_count, // Step count from the last update
acc_step_count; // Bresenham counter for laser accel/decel
#endif
} stepper_laser_t;
static stepper_laser_t laser_trap;
#endif
public:
// Initialize stepper hardware
static void init();

View File

@ -1904,9 +1904,10 @@ void Temperature::task() {
#if ENABLED(LASER_COOLANT_FLOW_METER)
cooler.flowmeter_task(ms);
#if ENABLED(FLOWMETER_SAFETY)
if (cutter.enabled() && cooler.check_flow_too_low()) {
if (cooler.check_flow_too_low()) {
TERN_(HAS_DISPLAY, if (cutter.enabled()) ui.flow_fault());
cutter.disable();
TERN_(HAS_DISPLAY, ui.flow_fault());
cutter.cutter_mode = CUTTER_MODE_ERROR; // Immediately kill stepper inline power output
}
#endif
#endif