Merge branch 'bugfix-2.1.x' into EBAB_EBAP

This commit is contained in:
InsanityAutomation
2024-05-19 19:56:58 -04:00
18 changed files with 340 additions and 180 deletions
+1 -1
View File
@@ -1584,7 +1584,7 @@
#if HAS_MARLINUI_U8GLIB
//#define BOOT_MARLIN_LOGO_ANIMATED // Animated Marlin logo. Costs ~3260 (or ~940) bytes of flash.
#endif
#if ANY(HAS_MARLINUI_U8GLIB, TOUCH_UI_FTDI_EVE)
#if ANY(HAS_MARLINUI_U8GLIB, TOUCH_UI_FTDI_EVE, HAS_MARLINUI_HD44780)
//#define SHOW_CUSTOM_BOOTSCREEN // Show the bitmap in Marlin/_Bootscreen.h on startup.
#endif
#endif
+1 -1
View File
@@ -41,7 +41,7 @@
* here we define this default string as the date where the latest release
* version was tagged.
*/
//#define STRING_DISTRIBUTION_DATE "2024-05-16"
//#define STRING_DISTRIBUTION_DATE "2024-05-18"
/**
* Defines a generic printer name to be output to the LCD after booting Marlin.
+5 -2
View File
@@ -52,8 +52,6 @@
#include HAL_PATH(.., inc/Conditionals_type.h)
#include "Changes.h"
#include "SanityCheck.h"
#include HAL_PATH(.., inc/SanityCheck.h)
// Include all core headers
#include "../core/language.h"
@@ -65,3 +63,8 @@
#endif
#include "../core/multi_language.h"
#ifndef __MARLIN_DEPS__
#include "SanityCheck.h"
#include HAL_PATH(.., inc/SanityCheck.h)
#endif
+24 -2
View File
@@ -388,8 +388,8 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
/**
* Custom Boot and Status screens
*/
#if ENABLED(SHOW_CUSTOM_BOOTSCREEN) && NONE(HAS_MARLINUI_U8GLIB, TOUCH_UI_FTDI_EVE, IS_DWIN_MARLINUI)
#error "SHOW_CUSTOM_BOOTSCREEN requires Graphical LCD or TOUCH_UI_FTDI_EVE."
#if ENABLED(SHOW_CUSTOM_BOOTSCREEN) && NONE(HAS_MARLINUI_HD44780, HAS_MARLINUI_U8GLIB, TOUCH_UI_FTDI_EVE, IS_DWIN_MARLINUI)
#error "SHOW_CUSTOM_BOOTSCREEN requires Character LCD, Graphical LCD, or TOUCH_UI_FTDI_EVE."
#elif ENABLED(SHOW_CUSTOM_BOOTSCREEN) && DISABLED(SHOW_BOOTSCREEN)
#error "SHOW_CUSTOM_BOOTSCREEN requires SHOW_BOOTSCREEN."
#elif ENABLED(CUSTOM_STATUS_SCREEN_IMAGE) && !HAS_MARLINUI_U8GLIB
@@ -2703,6 +2703,28 @@ static_assert(NUM_SERVOS <= NUM_SERVO_PLUGS, "NUM_SERVOS (or some servo index) i
#undef IS_U8GLIB_SSD1306
#undef IS_EXTUI
/**
* Make sure LCD language settings are distinct
*/
#if NUM_LANGUAGES > 1
static_assert(strcmp(STRINGIFY(LCD_LANGUAGE_2), STRINGIFY(LCD_LANGUAGE)), "Error: LCD_LANGUAGE_2 (" STRINGIFY(LCD_LANGUAGE) ") cannot be the same as LCD_LANGUAGE.");
#endif
#if NUM_LANGUAGES > 2
static_assert(strcmp(STRINGIFY(LCD_LANGUAGE_3), STRINGIFY(LCD_LANGUAGE)), "Error: LCD_LANGUAGE_3 (" STRINGIFY(LCD_LANGUAGE) ") cannot be the same as LCD_LANGUAGE.");
static_assert(strcmp(STRINGIFY(LCD_LANGUAGE_3), STRINGIFY(LCD_LANGUAGE_2)), "Error: LCD_LANGUAGE_3 (" STRINGIFY(LCD_LANGUAGE) ") cannot be the same as LCD_LANGUAGE_2.");
#endif
#if NUM_LANGUAGES > 3
static_assert(strcmp(STRINGIFY(LCD_LANGUAGE_4), STRINGIFY(LCD_LANGUAGE)), "Error: LCD_LANGUAGE_4 (" STRINGIFY(LCD_LANGUAGE) ") cannot be the same as LCD_LANGUAGE.");
static_assert(strcmp(STRINGIFY(LCD_LANGUAGE_4), STRINGIFY(LCD_LANGUAGE_2)), "Error: LCD_LANGUAGE_4 (" STRINGIFY(LCD_LANGUAGE) ") cannot be the same as LCD_LANGUAGE_2.");
static_assert(strcmp(STRINGIFY(LCD_LANGUAGE_4), STRINGIFY(LCD_LANGUAGE_3)), "Error: LCD_LANGUAGE_4 (" STRINGIFY(LCD_LANGUAGE) ") cannot be the same as LCD_LANGUAGE_3.");
#endif
#if NUM_LANGUAGES > 4
static_assert(strcmp(STRINGIFY(LCD_LANGUAGE_5), STRINGIFY(LCD_LANGUAGE)), "Error: LCD_LANGUAGE_5 (" STRINGIFY(LCD_LANGUAGE) ") cannot be the same as LCD_LANGUAGE.");
static_assert(strcmp(STRINGIFY(LCD_LANGUAGE_5), STRINGIFY(LCD_LANGUAGE_2)), "Error: LCD_LANGUAGE_5 (" STRINGIFY(LCD_LANGUAGE) ") cannot be the same as LCD_LANGUAGE_2.");
static_assert(strcmp(STRINGIFY(LCD_LANGUAGE_5), STRINGIFY(LCD_LANGUAGE_3)), "Error: LCD_LANGUAGE_5 (" STRINGIFY(LCD_LANGUAGE) ") cannot be the same as LCD_LANGUAGE_3.");
static_assert(strcmp(STRINGIFY(LCD_LANGUAGE_5), STRINGIFY(LCD_LANGUAGE_4)), "Error: LCD_LANGUAGE_5 (" STRINGIFY(LCD_LANGUAGE) ") cannot be the same as LCD_LANGUAGE_4.");
#endif
#if ANY(TFT_GENERIC, MKS_TS35_V2_0, MKS_ROBIN_TFT24, MKS_ROBIN_TFT28, MKS_ROBIN_TFT32, MKS_ROBIN_TFT35, MKS_ROBIN_TFT43, MKS_ROBIN_TFT_V1_1R, \
TFT_TRONXY_X5SA, ANYCUBIC_TFT35, ANYCUBIC_TFT35, LONGER_LK_TFT28, ANET_ET4_TFT28, ANET_ET5_TFT35, BIQU_BX_TFT70, BTT_TFT35_SPI_V1_0)
#if NONE(TFT_COLOR_UI, TFT_CLASSIC_UI, TFT_LVGL_UI)
+1 -1
View File
@@ -42,7 +42,7 @@
* version was tagged.
*/
#ifndef STRING_DISTRIBUTION_DATE
#define STRING_DISTRIBUTION_DATE "2024-05-16"
#define STRING_DISTRIBUTION_DATE "2024-05-18"
#endif
/**
+59 -13
View File
@@ -331,15 +331,24 @@ void MarlinUI::set_custom_characters(const HD44780CharSet screen_charset/*=CHARS
#endif // HAS_MEDIA
#if ENABLED(SHOW_BOOTSCREEN)
// Set boot screen corner characters
if (screen_charset == CHARSET_BOOT) {
for (uint8_t i = 4; i--;)
createChar_P(i, corner[i]);
}
else
#endif
{ // Info Screen uses 5 special characters
switch (screen_charset) {
#if ENABLED(SHOW_BOOTSCREEN)
case CHARSET_BOOT: {
// Set boot screen corner characters
for (uint8_t i = 4; i--;) createChar_P(i, corner[i]);
} break;
#endif
#if ENABLED(SHOW_CUSTOM_BOOTSCREEN)
case CHARSET_BOOT_CUSTOM: {
for (uint8_t i = COUNT(customBootChars); i--;)
createChar_P(i, customBootChars[i]);
} break;
#endif
default: {
// Info Screen uses 5 special characters
createChar_P(LCD_STR_BEDTEMP[0], bedTemp);
createChar_P(LCD_STR_DEGREE[0], degree);
createChar_P(LCD_STR_THERMOMETER[0], thermometer);
@@ -361,7 +370,9 @@ void MarlinUI::set_custom_characters(const HD44780CharSet screen_charset/*=CHARS
createChar_P(LCD_STR_FOLDER[0], folder);
#endif
}
}
} break;
}
}
@@ -400,6 +411,42 @@ bool MarlinUI::detected() {
return TERN1(DETECT_I2C_LCD_DEVICE, lcd.LcdDetected() == 1);
}
#if ENABLED(SHOW_CUSTOM_BOOTSCREEN)
#ifndef CUSTOM_BOOTSCREEN_X
#define CUSTOM_BOOTSCREEN_X -1
#endif
#ifndef CUSTOM_BOOTSCREEN_Y
#define CUSTOM_BOOTSCREEN_Y ((LCD_HEIGHT - COUNT(custom_boot_lines)) / 2)
#endif
#ifndef CUSTOM_BOOTSCREEN_TIMEOUT
#define CUSTOM_BOOTSCREEN_TIMEOUT 2500
#endif
void MarlinUI::draw_custom_bootscreen(const uint8_t/*=0*/) {
set_custom_characters(CHARSET_BOOT_CUSTOM);
lcd.clear();
const int8_t sx = CUSTOM_BOOTSCREEN_X;
const uint8_t sy = CUSTOM_BOOTSCREEN_Y;
for (lcd_uint_t i = 0; i < COUNT(custom_boot_lines); ++i) {
PGM_P const pstr = (PGM_P)pgm_read_ptr(&custom_boot_lines[i]);
const uint8_t clen = utf8_strlen_P(pstr);
const lcd_uint_t x = sx >= 0 ? sx : (LCD_WIDTH - clen) / 2;
for (lcd_uint_t j = 0; j < clen; ++j) {
const lchar_t c = pgm_read_byte(&pstr[j]);
lcd_put_lchar(x + j, sy + i, c == '\x08' ? '\x00' : c);
}
}
}
// Shows the custom bootscreen and delays
void MarlinUI::show_custom_bootscreen() {
draw_custom_bootscreen();
safe_delay(CUSTOM_BOOTSCREEN_TIMEOUT);
}
#endif // SHOW_CUSTOM_BOOTSCREEN
#if HAS_SLOW_BUTTONS
uint8_t MarlinUI::read_slow_buttons() {
#if ENABLED(LCD_I2C_TYPE_MCP23017)
@@ -466,6 +513,8 @@ void MarlinUI::clear_lcd() { lcd.clear(); }
}
void MarlinUI::show_bootscreen() {
TERN_(SHOW_CUSTOM_BOOTSCREEN, show_custom_bootscreen());
set_custom_characters(CHARSET_BOOT);
lcd.clear();
@@ -660,9 +709,6 @@ FORCE_INLINE void _draw_bed_status(const bool blink) {
lcd_put_u8str(F("K"));
#else
lcd_put_u8str(cutter_power2str(cutter.unitPower));
#if CUTTER_UNIT_IS(PERCENT)
lcd_put_u8str(F("%"));
#endif
#endif
lcd_put_u8str(F(" "));
+14
View File
@@ -27,6 +27,20 @@
#include "../../inc/MarlinConfig.h"
#if ENABLED(SHOW_CUSTOM_BOOTSCREEN)
#include "../../../_Bootscreen.h"
#ifdef CUSTOM_BOOTSCREEN_Y
#define CUSTOM_BOOT_LAST COUNT(custom_boot_lines) + CUSTOM_BOOTSCREEN_Y
#else
#define CUSTOM_BOOT_LAST COUNT(custom_boot_lines)
#endif
static_assert(CUSTOM_BOOT_LAST <= LCD_HEIGHT, "custom_boot_lines (plus CUSTOM_BOOTSCREEN_Y) doesn't fit on the selected LCD.");
#endif
#if ENABLED(LCD_I2C_TYPE_PCF8575)
// NOTE: These are register-mapped pins on the PCF8575 controller, not Arduino pins.
+1 -1
View File
@@ -114,7 +114,7 @@ lcd_uint_t expand_u8str_P(char * const outstr, PGM_P const ptpl, const int8_t in
* Return the number of characters emitted
*/
lcd_uint_t lcd_put_u8str_P(PGM_P const ptpl, const int8_t ind, const char *cstr/*=nullptr*/, FSTR_P const fstr/*=nullptr*/, const lcd_uint_t maxlen/*=LCD_WIDTH*/) {
char estr[maxlen + 2];
char estr[maxlen * LANG_CHARSIZE + 2];
const lcd_uint_t outlen = expand_u8str_P(estr, ptpl, ind, cstr, fstr, maxlen);
lcd_put_u8str_max(estr, maxlen * (MENU_FONT_WIDTH));
return outlen;
+2 -1
View File
@@ -105,7 +105,8 @@ typedef bool (*statusResetFunc_t)();
enum HD44780CharSet : uint8_t {
CHARSET_MENU,
CHARSET_INFO,
CHARSET_BOOT
CHARSET_BOOT,
CHARSET_BOOT_CUSTOM
};
#endif
+1 -1
View File
@@ -242,7 +242,7 @@ void home_delta() {
#endif
// Move all carriages together linearly until an endstop is hit.
current_position.z = DIFF_TERN(USE_PROBE_FOR_Z_HOMING, delta_height + 10, probe.offset.z);
current_position.z = DIFF_TERN(HAS_BED_PROBE, delta_height + 10, probe.offset.z);
line_to_current_position(homing_feedrate(Z_AXIS));
planner.synchronize();
TERN_(HAS_DELTA_SENSORLESS_PROBING, endstops.report_states());
+2 -2
View File
@@ -762,7 +762,7 @@ void FTMotion::convertToSteps(const uint32_t idx) {
#if ENABLED(STEPS_ROUNDING)
#define TOSTEPS(A,B) int32_t(trajMod.A[idx] * planner.settings.axis_steps_per_mm[B] + (trajMod.A[idx] < 0.0f ? -0.5f : 0.5f))
const xyze_long_t steps_tar = LOGICAL_AXIS_ARRAY(
TOSTEPS(e, E_AXIS_N(current_block->extruder)), // May be eliminated if guaranteed positive.
TOSTEPS(e, E_AXIS_N(stepper.current_block->extruder)), // May be eliminated if guaranteed positive.
TOSTEPS(x, X_AXIS), TOSTEPS(y, Y_AXIS), TOSTEPS(z, Z_AXIS),
TOSTEPS(i, I_AXIS), TOSTEPS(j, J_AXIS), TOSTEPS(k, K_AXIS),
TOSTEPS(u, U_AXIS), TOSTEPS(v, V_AXIS), TOSTEPS(w, W_AXIS)
@@ -771,7 +771,7 @@ void FTMotion::convertToSteps(const uint32_t idx) {
#else
#define TOSTEPS(A,B) int32_t(trajMod.A[idx] * planner.settings.axis_steps_per_mm[B]) - steps.A
xyze_long_t delta = LOGICAL_AXIS_ARRAY(
TOSTEPS(e, E_AXIS_N(current_block->extruder)),
TOSTEPS(e, E_AXIS_N(stepper.current_block->extruder)),
TOSTEPS(x, X_AXIS), TOSTEPS(y, Y_AXIS), TOSTEPS(z, Z_AXIS),
TOSTEPS(i, I_AXIS), TOSTEPS(j, J_AXIS), TOSTEPS(k, K_AXIS),
TOSTEPS(u, U_AXIS), TOSTEPS(v, V_AXIS), TOSTEPS(w, W_AXIS)
+3 -4
View File
@@ -892,7 +892,7 @@ void restore_feedrate_and_scaling() {
#elif ENABLED(DELTA)
soft_endstop.min[axis] = base_min_pos(axis);
soft_endstop.max[axis] = (axis == Z_AXIS) ? DIFF_TERN(USE_PROBE_FOR_Z_HOMING, delta_height, probe.offset.z) : base_home_pos(axis);
soft_endstop.max[axis] = (axis == Z_AXIS) ? DIFF_TERN(HAS_BED_PROBE, delta_height, probe.offset.z) : base_max_pos(axis);
switch (axis) {
case X_AXIS:
@@ -1637,8 +1637,7 @@ void prepare_line_to_destination() {
SERIAL_ECHO_START();
msg.echoln();
msg.setf(GET_TEXT_F(MSG_HOME_FIRST), need);
ui.set_status(msg);
ui.status_printf(0, GET_TEXT_F(MSG_HOME_FIRST), need);
return true;
}
@@ -2467,7 +2466,7 @@ void set_axis_is_at_home(const AxisEnum axis) {
#if ANY(MORGAN_SCARA, AXEL_TPARA)
scara_set_axis_is_at_home(axis);
#elif ENABLED(DELTA)
current_position[axis] = (axis == Z_AXIS) ? DIFF_TERN(USE_PROBE_FOR_Z_HOMING, delta_height, probe.offset.z) : base_home_pos(axis);
current_position[axis] = (axis == Z_AXIS) ? DIFF_TERN(HAS_BED_PROBE, delta_height, probe.offset.z) : base_home_pos(axis);
#else
current_position[axis] = SUM_TERN(HAS_HOME_OFFSET, base_home_pos(axis), home_offset[axis]);
#endif
+204 -118
View File
@@ -128,6 +128,7 @@ Planner planner;
block_t Planner::block_buffer[BLOCK_BUFFER_SIZE];
volatile uint8_t Planner::block_buffer_head, // Index of the next block to be pushed
Planner::block_buffer_nonbusy, // Index of the first non-busy block
Planner::block_buffer_planned, // Index of the optimally planned block
Planner::block_buffer_tail; // Index of the busy block, if any
uint16_t Planner::cleaning_buffer_counter; // A counter to disable queuing of blocks
uint8_t Planner::delay_before_delivering; // Delay block delivery so initial blocks in an empty queue may merge
@@ -729,6 +730,8 @@ void Planner::init() {
#endif
#endif
#define MINIMAL_STEP_RATE 120
/**
* Get the current block for processing
* and mark the block as busy.
@@ -765,6 +768,10 @@ block_t* Planner::get_current_block() {
// As this block is busy, advance the nonbusy block pointer
block_buffer_nonbusy = next_block_index(block_buffer_tail);
// Push block_buffer_planned pointer, if encountered.
if (block_buffer_tail == block_buffer_planned)
block_buffer_planned = block_buffer_nonbusy;
// Return the block
return block;
}
@@ -777,29 +784,29 @@ block_t* Planner::get_current_block() {
/**
* Calculate trapezoid parameters, multiplying the entry- and exit-speeds
* by the provided factors. If entry_factor is 0 don't change the initial_rate.
* Assumes that the implied initial_rate and final_rate are no less than
* sqrt(block->acceleration_steps_per_s2 / 2). This is ensured through
* minimum_planner_speed_sqr / min_entry_speed_sqr though note there's one
* exception in recalculate_trapezoids().
* by the provided factors. Requires that initial_rate and final_rate are
* no less than sqrt(block->acceleration_steps_per_s2 / 2), which is ensured
* through minimum_planner_speed_sqr in _populate_block().
**
* ############ VERY IMPORTANT ############
* NOTE that the PRECONDITION to call this function is that the block is
* NOT BUSY and it is marked as RECALCULATE. That WARRANTIES the Stepper ISR
* is not and will not use the block while we modify it.
* is not and will not use the block while we modify it, so it is safe to
* alter its values.
*/
void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t entry_factor, const_float_t exit_factor) {
uint32_t initial_rate = LROUND(block->nominal_rate * entry_factor); // (steps per second)
if (initial_rate == 0) initial_rate = block->initial_rate;
NOMORE(initial_rate, block->nominal_rate);
uint32_t initial_rate = LROUND(block->nominal_rate * entry_factor),
final_rate = LROUND(block->nominal_rate * exit_factor); // (steps per second)
uint32_t final_rate = block->nominal_rate; // (steps per second)
if (exit_factor < 1.0f) final_rate *= exit_factor;
// Limit minimal step rate (Otherwise the timer will overflow.)
NOLESS(initial_rate, uint32_t(MINIMAL_STEP_RATE));
// Legacy check against supposed timer overflow. However Stepper::calc_timer_interval() already
// should protect against it. But removing this code produces judder in direction-switching
// moves. This is because the current discrete stepping math diverges from physical motion under
// constant acceleration when acceleration_steps_per_s2 is large compared to initial/final_rate.
NOLESS(initial_rate, uint32_t(MINIMAL_STEP_RATE)); // Enforce the minimum speed
NOLESS(final_rate, uint32_t(MINIMAL_STEP_RATE));
NOLESS(block->nominal_rate, (uint32_t)MINIMAL_STEP_RATE);
NOMORE(initial_rate, block->nominal_rate); // NOTE: The nominal rate may be less than MINIMAL_STEP_RATE!
NOMORE(final_rate, block->nominal_rate);
#if ANY(S_CURVE_ACCELERATION, LIN_ADVANCE)
// If we have some plateau time, the cruise rate will be the nominal rate
@@ -818,8 +825,9 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
const float half_inverse_accel = 0.5f * inverse_accel,
nominal_rate_sq = FLOAT_SQ(block->nominal_rate),
// Steps required for acceleration, deceleration to/from nominal rate
decelerate_steps_float = half_inverse_accel * (nominal_rate_sq - FLOAT_SQ(final_rate));
float accelerate_steps_float = half_inverse_accel * (nominal_rate_sq - FLOAT_SQ(initial_rate));
decelerate_steps_float = half_inverse_accel * (nominal_rate_sq - FLOAT_SQ(final_rate)),
accelerate_steps_float = half_inverse_accel * (nominal_rate_sq - FLOAT_SQ(initial_rate));
// Aims to fully reach nominal and final rates
accelerate_steps = CEIL(accelerate_steps_float);
decelerate_steps = CEIL(decelerate_steps_float);
@@ -934,16 +942,16 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
*
* Recalculates the motion plan according to the following basic guidelines:
*
* 1. Go over blocks sequentially in reverse order and maximize the entry junction speed:
* a. Entry speed should stay below/at the pre-computed maximum junction speed limit
* b. Aim for the maximum entry speed which is the one reverse-computed from its exit speed
* (next->entry_speed) if assuming maximum deceleration over the full block travel distance
* c. The last (newest appended) block uses safe_exit_speed exit speed (there's no 'next')
* 2. Go over blocks in chronological (forward) order and fix the exit junction speed:
* a. Exit speed (next->entry_speed) must be below/at the maximum exit speed forward-computed
* from its entry speed if assuming maximum acceleration over the full block travel distance
* b. Exit speed should stay above/at the pre-computed minimum junction speed limit
* 3. Convert entry / exit speeds (mm/s) into final/initial steps/s
* 1. Go over every feasible block sequentially in reverse order and calculate the junction speeds
* (i.e. current->entry_speed) such that:
* a. No junction speed exceeds the pre-computed maximum junction speed limit or nominal speeds of
* neighboring blocks.
* b. A block entry speed cannot exceed one reverse-computed from its exit speed (next->entry_speed)
* with a maximum allowable deceleration over the block travel distance.
* c. The last (or newest appended) block is planned from safe_exit_speed_sqr.
* 2. Go over every block in chronological (forward) order and dial down junction speed values if
* a. The exit speed exceeds the one forward-computed from its entry speed with the maximum allowable
* acceleration over the block travel distance.
*
* When these stages are complete, the planner will have maximized the velocity profiles throughout the all
* of the planner blocks, where every block is operating at its maximum allowable acceleration limits. In
@@ -951,22 +959,28 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
* are possible. If a new block is added to the buffer, the plan is recomputed according to the said
* guidelines for a new optimal plan.
*
* To increase computational efficiency of these guidelines:
* 1. We keep track of which blocks need calculation (block->flag.recalculate)
* 2. We stop the reverse pass on the first block whose entry_speed == max_entry_speed. As soon
* as that happens, there can be no further increases (ensured by the previous recalculate)
* 3. On the forward pass we skip through to the first block with a modified exit speed
* (next->entry_speed)
* 4. On the forward pass if we encounter a full acceleration block that limits its exit speed
* (next->entry_speed) we also update the maximum for that junction (next->max_entry_speed)
* so it's never updated again
* 5. We use speed squared (ex: entry_speed_sqr in mm^2/s^2) in acceleration limit computations
* 6. We don't recompute sqrt(entry_speed_sqr) if the block's entry speed didn't change
* To increase computational efficiency of these guidelines, a set of planner block pointers have been
* created to indicate stop-compute points for when the planner guidelines cannot logically make any further
* changes or improvements to the plan when in normal operation and new blocks are streamed and added to the
* planner buffer. For example, if a subset of sequential blocks in the planner have been planned and are
* bracketed by junction velocities at their maximums (or by the first planner block as well), no new block
* added to the planner buffer will alter the velocity profiles within them. So we no longer have to compute
* them. Or, if a set of sequential blocks from the first block in the planner (or a optimal stop-compute
* point) are all accelerating, they are all optimal and can not be altered by a new block added to the
* planner buffer, as this will only further increase the plan speed to chronological blocks until a maximum
* junction velocity is reached. However, if the operational conditions of the plan changes from infrequently
* used feed holds or feedrate overrides, the stop-compute pointers will be reset and the entire plan is
* recomputed as stated in the general guidelines.
*
* Planner buffer index mapping:
* - block_buffer_tail: Points to the beginning of the planner buffer. First to be executed or being executed.
* - block_buffer_head: Points to the buffer block after the last block in the buffer. Used to indicate whether
* the buffer is full or empty. As described for standard ring buffers, this block is always empty.
* - block_buffer_planned: Points to the first buffer block after the last optimally planned block for normal
* streaming operating conditions. Use for planning optimizations by avoiding recomputing parts of the
* planner buffer that don't change with the addition of a new block, as describe above. In addition,
* this block can never be less than block_buffer_tail and will always be pushed forward and maintain
* this requirement when encountered by the Planner::release_current_block() routine during a cycle.
*
* NOTE: Since the planner only computes on what's in the planner buffer, some motions with many short
* segments (e.g., complex curves) may seem to move slowly. This is because there simply isn't
@@ -989,8 +1003,7 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
*/
// The kernel called by recalculate() when scanning the plan from last to first entry.
// Returns true if it could increase the current block's entry speed.
bool Planner::reverse_pass_kernel(block_t * const current, const block_t * const next, const_float_t safe_exit_speed_sqr) {
void Planner::reverse_pass_kernel(block_t * const current, const block_t * const next, const_float_t safe_exit_speed_sqr) {
// We need to recalculate only for the last block added or if next->entry_speed_sqr changed.
if (!next || next->flag.recalculate) {
// And only if we're not already at max entry speed.
@@ -1008,135 +1021,194 @@ bool Planner::reverse_pass_kernel(block_t * const current, const block_t * const
// become BUSY just before being marked RECALCULATE, so check for that!
if (stepper.is_block_busy(current)) {
// Block became busy. Clear the RECALCULATE flag (no point in
// recalculating BUSY blocks).
// recalculating BUSY blocks). And don't set its speed, as it can't
// be updated at this time.
current->flag.recalculate = false;
}
else {
// Block is not BUSY so this is ahead of the Stepper ISR:
// Just Set the new entry speed.
current->entry_speed_sqr = new_entry_speed_sqr;
return true;
}
}
}
}
return false;
}
/**
* recalculate() needs to go over the current plan twice.
* Once in reverse and once forward. This implements the reverse pass that
* coarsely maximizes the entry speeds starting from last block.
* Requires there's at least one block with flag.recalculate in the buffer.
* Once in reverse and once forward. This implements the reverse pass.
*/
void Planner::reverse_pass(const_float_t safe_exit_speed_sqr) {
// Initialize block index to the last block in the planner buffer.
// This last block will have flag.recalculate set.
uint8_t block_index = prev_block_index(block_buffer_head);
// The ISR may change block_buffer_nonbusy so get a stable local copy.
uint8_t nonbusy_block_index = block_buffer_nonbusy;
// Read the index of the last buffer planned block.
// The ISR may change it so get a stable local copy.
uint8_t planned_block_index = block_buffer_planned;
// If there was a race condition and block_buffer_planned was incremented
// or was pointing at the head (queue empty) break loop now and avoid
// planning already consumed blocks
if (planned_block_index == block_buffer_head) return;
// Reverse Pass: Coarsely maximize all possible deceleration curves back-planning from the last
// block in buffer. Cease planning when the last optimal planned or tail pointer is reached.
// NOTE: Forward pass will later refine and correct the reverse pass to create an optimal plan.
const block_t *next = nullptr;
// Don't try to change the entry speed of the first non-busy block.
while (block_index != nonbusy_block_index) {
while (block_index != planned_block_index) {
// Perform the reverse pass
block_t *current = &block_buffer[block_index];
// Only process movement blocks
if (current->is_move()) {
// If no entry speed increase was possible we end the reverse pass.
if (!reverse_pass_kernel(current, next, safe_exit_speed_sqr)) return;
reverse_pass_kernel(current, next, safe_exit_speed_sqr);
next = current;
}
// Advance to the next
block_index = prev_block_index(block_index);
// The ISR could advance block_buffer_nonbusy while we were doing the reverse pass.
// The ISR could advance the block_buffer_planned while we were doing the reverse pass.
// We must try to avoid using an already consumed block as the last one - So follow
// changes to the pointer and make sure to limit the loop to the currently busy block
while (nonbusy_block_index != block_buffer_nonbusy) {
while (planned_block_index != block_buffer_planned) {
// If we reached the busy block or an already processed block, break the loop now
if (block_index == nonbusy_block_index) return;
if (block_index == planned_block_index) return;
// Advance the pointer, following the busy block
nonbusy_block_index = next_block_index(nonbusy_block_index);
planned_block_index = next_block_index(planned_block_index);
}
}
}
// The kernel called during the forward pass. Assumes current->flag.recalculate.
void Planner::forward_pass_kernel(const block_t * const previous, block_t * const current) {
// Check if the previous block is accelerating.
if (previous->entry_speed_sqr < current->entry_speed_sqr) {
// Compute the maximum achievable speed if the previous block was fully accelerating.
float new_exit_speed_sqr = max_allowable_speed_sqr(-previous->acceleration, previous->entry_speed_sqr, previous->millimeters);
// The kernel called by recalculate() when scanning the plan from first to last entry.
void Planner::forward_pass_kernel(const block_t * const previous, block_t * const current, const uint8_t block_index) {
// Check against previous speed only on current->entry_speed_sqr changes (or if first time).
if (current->flag.recalculate) {
// If the previous block is accelerating check if it's too short to complete the full speed
// change then adjust the entry speed accordingly. Entry speeds have already been maximized.
if (previous->entry_speed_sqr < current->entry_speed_sqr) {
float new_entry_speed_sqr = max_allowable_speed_sqr(-previous->acceleration, previous->entry_speed_sqr, previous->millimeters);
if (new_exit_speed_sqr < current->entry_speed_sqr) {
// Current entry speed limited by full acceleration from previous entry speed.
// If true, previous block is full-acceleration and we can move the planned pointer forward.
if (new_entry_speed_sqr < current->entry_speed_sqr) {
// Current entry speed limited by full acceleration from previous entry speed.
// Make sure entry speed not lower than minimum_planner_speed_sqr.
NOLESS(new_entry_speed_sqr, current->min_entry_speed_sqr);
current->entry_speed_sqr = new_entry_speed_sqr;
// Make sure entry speed not lower than minimum_planner_speed_sqr.
NOLESS(new_exit_speed_sqr, current->min_entry_speed_sqr);
current->entry_speed_sqr = new_exit_speed_sqr;
// Ensure we don't try updating entry_speed_sqr again.
current->max_entry_speed_sqr = new_exit_speed_sqr;
// Set optimal plan pointer.
block_buffer_planned = block_index;
}
else {
// Previous entry speed has been maximized.
block_buffer_planned = prev_block_index(block_index);
}
}
}
// The fully optimized entry speed is our new minimum speed.
current->min_entry_speed_sqr = current->entry_speed_sqr;
// Any block set at its maximum entry speed also creates an optimal plan up to this
// point in the buffer. When the plan is bracketed by either the beginning of the
// buffer and a maximum entry speed or two maximum entry speeds, every block in between
// cannot logically be further improved. Hence, we don't have to recompute them anymore.
if (current->entry_speed_sqr == current->max_entry_speed_sqr)
block_buffer_planned = block_index;
}
}
/**
* Do the forward pass and recalculate the trapezoid speed profiles for all blocks in the plan
* according to entry/exit speeds.
* recalculate() needs to go over the current plan twice.
* Once in reverse and once forward. This implements the forward pass.
*/
void Planner::forward_pass() {
// Forward Pass: Forward plan the acceleration curve from the planned pointer onward.
// Also scans for optimal plan breakpoints and appropriately updates the planned pointer.
// Begin at buffer planned pointer. Note that block_buffer_planned can be modified
// by the stepper ISR, so read it ONCE. It it guaranteed that block_buffer_planned
// will never lead head, so the loop is safe to execute. Also note that the forward
// pass will never modify the values at the tail.
uint8_t block_index = block_buffer_planned;
block_t *block;
const block_t * previous = nullptr;
while (block_index != block_buffer_head) {
// Perform the forward pass
block = &block_buffer[block_index];
// Only process movement blocks
if (block->is_move()) {
// If there's no previous block or the previous block is not
// BUSY (thus, modifiable) run the forward_pass_kernel. Otherwise,
// the previous block became BUSY, so assume the current block's
// entry speed can't be altered (since that would also require
// updating the exit speed of the previous block).
if (previous && !stepper.is_block_busy(previous))
forward_pass_kernel(previous, block, block_index);
previous = block;
}
// Advance to the previous
block_index = next_block_index(block_index);
}
}
/**
* Recalculate the trapezoid speed profiles for all blocks in the plan
* according to the entry_factor for each junction. Must be called by
* recalculate() after updating the blocks.
*/
void Planner::recalculate_trapezoids(const_float_t safe_exit_speed_sqr) {
// Start with the block that's about to execute or is executing.
// 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 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 non-SYNC block.
while (head_block_index != block_index) {
// Go back (head always point to the first free block)
const uint8_t prev_index = prev_block_index(head_block_index);
// Get the pointer to the block
block_t *prev = &block_buffer[prev_index];
// It the block is a move, we're done with this loop
if (prev->is_move()) break;
// Examine the previous block. This and all following are SYNC blocks
head_block_index = prev_index;
}
// Go from the tail (currently executed block) to the first block, without including it)
block_t *block = nullptr, *next = nullptr;
float next_entry_speed = 0.0f;
float current_entry_speed = 0.0f, next_entry_speed = 0.0f;
while (block_index != head_block_index) {
next = &block_buffer[block_index];
// Only process movement blocks
if (next->is_move()) {
// Check if the next block's entry speed changed
if (next->flag.recalculate) {
if (!block) {
// 'next' is the first move due to either being the first added move or due to the planner
// having completely fallen behind. Revert any reverse pass change.
next->entry_speed_sqr = next->min_entry_speed_sqr;
next_entry_speed = SQRT(next->min_entry_speed_sqr);
}
else {
// Try to fix exit speed which requires trapezoid recalculation
block->flag.recalculate = true;
next_entry_speed = SQRT(next->entry_speed_sqr);
// But there is an inherent race condition here, as the block may have
// become BUSY just before being marked RECALCULATE, so check for that!
if (stepper.is_block_busy(block)) {
// Block is BUSY so we can't change the exit speed. Revert any reverse pass change.
next->entry_speed_sqr = next->min_entry_speed_sqr;
if (!next->initial_rate) {
// 'next' was never calculated. Planner is falling behind so for maximum efficiency
// set next's stepping speed directly and forgo checking against min_entry_speed_sqr.
// calculate_trapezoid_for_block() can handle it, albeit sub-optimally.
next->initial_rate = block->final_rate;
}
// Note that at this point next_entry_speed is (still) 0.
}
else {
// Block is not BUSY: we won the race against the ISR or recalculate was already set
if (block) {
if (next->entry_speed_sqr != next->min_entry_speed_sqr)
forward_pass_kernel(block, next);
// If the next block is marked to RECALCULATE, also mark the previously-fetched one
if (next->flag.recalculate) block->flag.recalculate = true;
const float current_entry_speed = next_entry_speed;
next_entry_speed = SQRT(next->entry_speed_sqr);
// Recalculate if current block entry or exit junction speed has changed.
if (block->flag.recalculate) {
// But there is an inherent race condition here, as the block maybe
// became BUSY, just before it was marked as RECALCULATE, so check
// if that is the case!
if (!stepper.is_block_busy(block)) {
// Block is not BUSY, we won the race against the Stepper ISR:
// NOTE: Entry and exit factors always > 0 by all previous logic operations.
const float nomr = 1.0f / block->nominal_speed;
calculate_trapezoid_for_block(block, current_entry_speed * nomr, next_entry_speed * nomr);
}
@@ -1148,18 +1220,30 @@ void Planner::recalculate_trapezoids(const_float_t safe_exit_speed_sqr) {
}
block = next;
current_entry_speed = next_entry_speed;
}
block_index = next_block_index(block_index);
}
// Last/newest block in buffer. The above guarantees it's a move block.
if (block && block->flag.recalculate) {
const float current_entry_speed = next_entry_speed;
// Last/newest block in buffer. Always recalculated.
if (block) {
next_entry_speed = SQRT(safe_exit_speed_sqr);
const float nomr = 1.0f / block->nominal_speed;
calculate_trapezoid_for_block(block, current_entry_speed * nomr, next_entry_speed * nomr);
// 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;
// But there is an inherent race condition here, as the block maybe
// became BUSY, just before it was marked as RECALCULATE, so check
// if that is the case!
if (!stepper.is_block_busy(block)) {
// Block is not BUSY, we won the race against the Stepper ISR:
const float nomr = 1.0f / block->nominal_speed;
calculate_trapezoid_for_block(block, current_entry_speed * nomr, next_entry_speed * nomr);
}
// Reset block to ensure its trapezoid is computed - The stepper is free to use
// the block from now on.
@@ -1167,10 +1251,14 @@ void Planner::recalculate_trapezoids(const_float_t safe_exit_speed_sqr) {
}
}
// Requires there's at least one block with flag.recalculate in the buffer
void Planner::recalculate(const_float_t safe_exit_speed_sqr) {
reverse_pass(safe_exit_speed_sqr);
// The forward pass is done as part of recalculate_trapezoids()
// Initialize block index to the last block in the planner buffer.
const uint8_t block_index = prev_block_index(block_buffer_head);
// If there is just one block, no planning can be done. Avoid it!
if (block_index != block_buffer_planned) {
reverse_pass(safe_exit_speed_sqr);
forward_pass();
}
recalculate_trapezoids(safe_exit_speed_sqr);
}
@@ -1579,7 +1667,7 @@ void Planner::quick_stop() {
const bool was_enabled = stepper.suspend();
// Drop all queue entries
block_buffer_nonbusy = block_buffer_head = block_buffer_tail;
block_buffer_nonbusy = block_buffer_planned = block_buffer_head = block_buffer_tail;
// Restart the block delay for the first movement - As the queue was
// forced to empty, there's no risk the ISR will touch this.
@@ -2741,8 +2829,6 @@ bool Planner::_populate_block(
block->entry_speed_sqr = minimum_planner_speed_sqr;
// Set min entry speed. Rarely it could be higher than the previous nominal speed but that's ok.
block->min_entry_speed_sqr = minimum_planner_speed_sqr;
// Zero the initial_rate to indicate that calculate_trapezoid_for_block() hasn't been called yet.
block->initial_rate = 0;
block->flag.recalculate = true;
+6 -5
View File
@@ -238,7 +238,7 @@ typedef struct PlannerBlock {
#endif
// Settings for the trapezoid generator
uint32_t accelerate_before, // The index of the step event on which to start cruising
uint32_t accelerate_before, // The index of the step event where cruising starts
decelerate_start; // The index of the step event on which to start decelerating
#if ENABLED(S_CURVE_ACCELERATION)
@@ -261,7 +261,6 @@ typedef struct PlannerBlock {
final_adv_steps; // Advance steps for exit speed pressure
#endif
#define MINIMAL_STEP_RATE _MAX((STEPPER_TIMER_RATE / HAL_TIMER_TYPE_MAX), 1) // steps/s max. To prevent timer overflow, slowest is 1 step/s
uint32_t nominal_rate, // The nominal step rate for this block in step_events/sec
initial_rate, // The jerk-adjusted step rate at start of block
final_rate, // The minimal rate at exit
@@ -443,6 +442,7 @@ class Planner {
static block_t block_buffer[BLOCK_BUFFER_SIZE];
static volatile uint8_t block_buffer_head, // Index of the next block to be pushed
block_buffer_nonbusy, // Index of the first non busy block
block_buffer_planned, // Index of the optimally planned block
block_buffer_tail; // Index of the busy block, if any
static uint16_t cleaning_buffer_counter; // A counter to disable queuing of blocks
static uint8_t delay_before_delivering; // This counter delays delivery of blocks when queue becomes empty to allow the opportunity of merging blocks
@@ -804,7 +804,7 @@ class Planner {
FORCE_INLINE static uint8_t nonbusy_movesplanned() { return block_dec_mod(block_buffer_head, block_buffer_nonbusy); }
// Remove all blocks from the buffer
FORCE_INLINE static void clear_block_buffer() { block_buffer_nonbusy = block_buffer_head = block_buffer_tail = 0; }
FORCE_INLINE static void clear_block_buffer() { block_buffer_nonbusy = block_buffer_planned = block_buffer_head = block_buffer_tail = 0; }
// Check if movement queue is full
FORCE_INLINE static bool is_full() { return block_buffer_tail == next_block_index(block_buffer_head); }
@@ -1083,10 +1083,11 @@ class Planner {
static void calculate_trapezoid_for_block(block_t * const block, const_float_t entry_factor, const_float_t exit_factor);
static bool reverse_pass_kernel(block_t * const current, const block_t * const next, const_float_t safe_exit_speed_sqr);
static void forward_pass_kernel(const block_t * const previous, block_t * const current);
static void reverse_pass_kernel(block_t * const current, const block_t * const next, const_float_t safe_exit_speed_sqr);
static void forward_pass_kernel(const block_t * const previous, block_t * const current, uint8_t block_index);
static void reverse_pass(const_float_t safe_exit_speed_sqr);
static void forward_pass();
static void recalculate_trapezoids(const_float_t safe_exit_speed_sqr);
+10 -23
View File
@@ -58,10 +58,16 @@
*
* time ----->
*
* The trapezoid is the shape the speed curve over time. It starts at block->initial_rate, accelerates
* while step_events_completed < block->accelerate_before, then starts cruising at constant speed while
* step_events_completed < block->decelerate_start, then it decelerates until the trapezoid generator is reset.
* The slope of acceleration is calculated using v = u + at where t is the accumulated timer values of the steps so far.
* The speed over time graph forms a TRAPEZOID. The slope of acceleration is calculated by
* v = u + t
* where 't' is the accumulated timer values of the steps so far.
*
* The Stepper ISR dynamically executes acceleration, deceleration, and cruising according to the block parameters.
* - Start at block->initial_rate.
* - Accelerate while step_events_completed < block->accelerate_before.
* - Cruise while step_events_completed < block->decelerate_start.
* - Decelerate after that, until all steps are completed.
* - Reset the trapezoid generator.
*/
/**
@@ -2569,25 +2575,6 @@ hal_timer_t Stepper::block_phase_isr() {
// The timer interval is just the nominal value for the nominal speed
interval = ticks_nominal;
}
/**
* 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_before) {
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)
+2 -2
View File
@@ -391,8 +391,8 @@ class Stepper {
static xyze_long_t advance_dividend;
static uint32_t advance_divisor,
step_events_completed, // The number of step events executed in the current block
accelerate_before, // The point from where we need to stop acceleration
decelerate_start, // The point from where we need to start decelerating
accelerate_before, // The count at which to start cruising
decelerate_start, // The count at which to start decelerating
step_event_count; // The total event count for the current block
#if ANY(HAS_MULTI_EXTRUDER, MIXING_EXTRUDER)
@@ -188,7 +188,7 @@
* PC6 | 1 2 | PC7
* PA2 | 3 4 | PA3
* PB13 5 6 | PB14
* N/C | 7 8 | PB12
* PB15 | 7 8 | PB12
* GND | 9 10 | 5V
* ------
* EXP1
@@ -199,7 +199,7 @@
#define EXP1_04_PIN PA3
#define EXP1_05_PIN PB13
#define EXP1_06_PIN PB14
#define EXP1_07_PIN -1
#define EXP1_07_PIN PB15
#define EXP1_08_PIN PB12
#if ENABLED(CR10_STOCKDISPLAY) // LCD used for C2
+2 -1
View File
@@ -49,8 +49,9 @@ build_flags = ${env:linux_native.build_flags} -Werror
[simulator_common]
platform = native
framework =
build_flags = ${common.build_flags} -std=gnu++17 -D__PLAT_NATIVE_SIM__ -DU8G_HAL_LINKS
build_flags = ${common.build_flags} -std=gnu++17
-I/usr/include/SDL2 -IMarlin -IMarlin/src/HAL/NATIVE_SIM/u8g
-D__PLAT_NATIVE_SIM__ -DU8G_HAL_LINKS -DGLM_ENABLE_EXPERIMENTAL
build_src_flags = -Wall -Wno-expansion-to-defined -Wno-deprecated-declarations -Wcast-align
release_flags = -g0 -O3 -flto
debug_build_flags = -fstack-protector-strong -g -g3 -ggdb