diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index c0dfc7d64b..02b0718c8d 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -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 diff --git a/Marlin/Version.h b/Marlin/Version.h index b28f7eabc2..55664693f4 100644 --- a/Marlin/Version.h +++ b/Marlin/Version.h @@ -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. diff --git a/Marlin/src/inc/MarlinConfig.h b/Marlin/src/inc/MarlinConfig.h index 4058761b9f..807082d839 100644 --- a/Marlin/src/inc/MarlinConfig.h +++ b/Marlin/src/inc/MarlinConfig.h @@ -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 diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 339cd4087c..665a7306f8 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -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) diff --git a/Marlin/src/inc/Version.h b/Marlin/src/inc/Version.h index 0ef06de574..523ce48cca 100644 --- a/Marlin/src/inc/Version.h +++ b/Marlin/src/inc/Version.h @@ -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 /** diff --git a/Marlin/src/lcd/HD44780/marlinui_HD44780.cpp b/Marlin/src/lcd/HD44780/marlinui_HD44780.cpp index 0ab045bda3..ea73243250 100644 --- a/Marlin/src/lcd/HD44780/marlinui_HD44780.cpp +++ b/Marlin/src/lcd/HD44780/marlinui_HD44780.cpp @@ -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(" ")); diff --git a/Marlin/src/lcd/HD44780/marlinui_HD44780.h b/Marlin/src/lcd/HD44780/marlinui_HD44780.h index 6f6f5a6855..15f268f8d9 100644 --- a/Marlin/src/lcd/HD44780/marlinui_HD44780.h +++ b/Marlin/src/lcd/HD44780/marlinui_HD44780.h @@ -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. diff --git a/Marlin/src/lcd/lcdprint.cpp b/Marlin/src/lcd/lcdprint.cpp index ea833f53a1..475664f45a 100644 --- a/Marlin/src/lcd/lcdprint.cpp +++ b/Marlin/src/lcd/lcdprint.cpp @@ -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; diff --git a/Marlin/src/lcd/marlinui.h b/Marlin/src/lcd/marlinui.h index ad4be03900..53300a4400 100644 --- a/Marlin/src/lcd/marlinui.h +++ b/Marlin/src/lcd/marlinui.h @@ -105,7 +105,8 @@ typedef bool (*statusResetFunc_t)(); enum HD44780CharSet : uint8_t { CHARSET_MENU, CHARSET_INFO, - CHARSET_BOOT + CHARSET_BOOT, + CHARSET_BOOT_CUSTOM }; #endif diff --git a/Marlin/src/module/delta.cpp b/Marlin/src/module/delta.cpp index 18eff415a3..cfa134860b 100644 --- a/Marlin/src/module/delta.cpp +++ b/Marlin/src/module/delta.cpp @@ -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()); diff --git a/Marlin/src/module/ft_motion.cpp b/Marlin/src/module/ft_motion.cpp index f92caa7d72..e73c75b9c5 100644 --- a/Marlin/src/module/ft_motion.cpp +++ b/Marlin/src/module/ft_motion.cpp @@ -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) diff --git a/Marlin/src/module/motion.cpp b/Marlin/src/module/motion.cpp index cb0c1bf5d8..473d54cc38 100644 --- a/Marlin/src/module/motion.cpp +++ b/Marlin/src/module/motion.cpp @@ -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 diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp index 279ade34fb..8f11f4ec18 100644 --- a/Marlin/src/module/planner.cpp +++ b/Marlin/src/module/planner.cpp @@ -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; diff --git a/Marlin/src/module/planner.h b/Marlin/src/module/planner.h index 5f04a8185c..fd26b4340e 100644 --- a/Marlin/src/module/planner.h +++ b/Marlin/src/module/planner.h @@ -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); diff --git a/Marlin/src/module/stepper.cpp b/Marlin/src/module/stepper.cpp index d975e3ca81..c2674480d5 100644 --- a/Marlin/src/module/stepper.cpp +++ b/Marlin/src/module/stepper.cpp @@ -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) diff --git a/Marlin/src/module/stepper.h b/Marlin/src/module/stepper.h index cf0348ba59..82b41290bf 100644 --- a/Marlin/src/module/stepper.h +++ b/Marlin/src/module/stepper.h @@ -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) diff --git a/Marlin/src/pins/hc32f4/pins_CREALITY_ENDER2P_V24S4.h b/Marlin/src/pins/hc32f4/pins_CREALITY_ENDER2P_V24S4.h index 3eed3951d2..1d142d87cc 100644 --- a/Marlin/src/pins/hc32f4/pins_CREALITY_ENDER2P_V24S4.h +++ b/Marlin/src/pins/hc32f4/pins_CREALITY_ENDER2P_V24S4.h @@ -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 diff --git a/ini/native.ini b/ini/native.ini index bbb6724dae..28c558d39f 100644 --- a/ini/native.ini +++ b/ini/native.ini @@ -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