- Probe index (optional - defaults to 0
+ */
+ inline void gcode_M43() {
+
+ if (parser.seen('T')) { // must be first or else its "S" and "E" parameters will execute endstop or servo test
+ toggle_pins();
+ return;
+ }
+
+ // Enable or disable endstop monitoring
+ if (parser.seen('E')) {
+ endstop_monitor_flag = parser.value_bool();
+ SERIAL_PROTOCOLPGM("endstop monitor ");
+ serialprintPGM(endstop_monitor_flag ? PSTR("en") : PSTR("dis"));
+ SERIAL_PROTOCOLLNPGM("abled");
+ return;
+ }
+
+ if (parser.seen('S')) {
+ servo_probe_test();
+ return;
+ }
+
+ // Get the range of pins to test or watch
+ const pin_t first_pin = parser.byteval('P'),
+ last_pin = parser.seenval('P') ? first_pin : NUM_DIGITAL_PINS - 1;
+
+ if (first_pin > last_pin) return;
+
+ const bool ignore_protection = parser.boolval('I');
+
+ // Watch until click, M108, or reset
+ if (parser.boolval('W')) {
+ SERIAL_PROTOCOLLNPGM("Watching pins");
+ byte pin_state[last_pin - first_pin + 1];
+ for (pin_t pin = first_pin; pin <= last_pin; pin++) {
+ if (pin_is_protected(pin) && !ignore_protection) continue;
+ pinMode(pin, INPUT_PULLUP);
+ delay(1);
+ /*
+ if (IS_ANALOG(pin))
+ pin_state[pin - first_pin] = analogRead(pin - analogInputToDigitalPin(0)); // int16_t pin_state[...]
+ else
+ //*/
+ pin_state[pin - first_pin] = digitalRead(pin);
+ }
+
+ #if HAS_RESUME_CONTINUE
+ wait_for_user = true;
+ KEEPALIVE_STATE(PAUSED_FOR_USER);
+ #endif
+
+ for (;;) {
+ for (pin_t pin = first_pin; pin <= last_pin; pin++) {
+ if (pin_is_protected(pin) && !ignore_protection) continue;
+ const byte val =
+ /*
+ IS_ANALOG(pin)
+ ? analogRead(pin - analogInputToDigitalPin(0)) : // int16_t val
+ :
+ //*/
+ digitalRead(pin);
+ if (val != pin_state[pin - first_pin]) {
+ report_pin_state_extended(pin, ignore_protection, false);
+ pin_state[pin - first_pin] = val;
+ }
+ }
+
+ #if HAS_RESUME_CONTINUE
+ if (!wait_for_user) {
+ KEEPALIVE_STATE(IN_HANDLER);
+ break;
+ }
+ #endif
+
+ safe_delay(200);
+ }
+ return;
+ }
+
+ // Report current state of selected pin(s)
+ for (pin_t pin = first_pin; pin <= last_pin; pin++)
+ report_pin_state_extended(pin, ignore_protection, true);
+ }
+
+#endif // PINS_DEBUGGING
+
+#if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST)
+
+ /**
+ * M48: Z probe repeatability measurement function.
+ *
+ * Usage:
+ * M48
+ * P = Number of sampled points (4-50, default 10)
+ * X = Sample X position
+ * Y = Sample Y position
+ * V = Verbose level (0-4, default=1)
+ * E = Engage Z probe for each reading
+ * L = Number of legs of movement before probe
+ * S = Schizoid (Or Star if you prefer)
+ *
+ * This function requires the machine to be homed before invocation.
+ */
+ inline void gcode_M48() {
+
+ if (axis_unhomed_error()) return;
+
+ const int8_t verbose_level = parser.byteval('V', 1);
+ if (!WITHIN(verbose_level, 0, 4)) {
+ SERIAL_PROTOCOLLNPGM("?(V)erbose level is implausible (0-4).");
+ return;
+ }
+
+ if (verbose_level > 0)
+ SERIAL_PROTOCOLLNPGM("M48 Z-Probe Repeatability Test");
+
+ const int8_t n_samples = parser.byteval('P', 10);
+ if (!WITHIN(n_samples, 4, 50)) {
+ SERIAL_PROTOCOLLNPGM("?Sample size not plausible (4-50).");
+ return;
+ }
+
+ const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE;
+
+ float X_current = current_position[X_AXIS],
+ Y_current = current_position[Y_AXIS];
+
+ const float X_probe_location = parser.linearval('X', X_current + X_PROBE_OFFSET_FROM_EXTRUDER),
+ Y_probe_location = parser.linearval('Y', Y_current + Y_PROBE_OFFSET_FROM_EXTRUDER);
+
+ if (!position_is_reachable_by_probe(X_probe_location, Y_probe_location)) {
+ SERIAL_PROTOCOLLNPGM("? (X,Y) out of bounds.");
+ return;
+ }
+
+ bool seen_L = parser.seen('L');
+ uint8_t n_legs = seen_L ? parser.value_byte() : 0;
+ if (n_legs > 15) {
+ SERIAL_PROTOCOLLNPGM("?Number of legs in movement not plausible (0-15).");
+ return;
+ }
+ if (n_legs == 1) n_legs = 2;
+
+ const bool schizoid_flag = parser.boolval('S');
+ if (schizoid_flag && !seen_L) n_legs = 7;
+
+ /**
+ * Now get everything to the specified probe point So we can safely do a
+ * probe to get us close to the bed. If the Z-Axis is far from the bed,
+ * we don't want to use that as a starting point for each probe.
+ */
+ if (verbose_level > 2)
+ SERIAL_PROTOCOLLNPGM("Positioning the probe...");
+
+ // Disable bed level correction in M48 because we want the raw data when we probe
+
+ #if HAS_LEVELING
+ const bool was_enabled = planner.leveling_active;
+ set_bed_leveling_enabled(false);
+ #endif
+
+ setup_for_endstop_or_probe_move();
+
+ double mean = 0.0, sigma = 0.0, min = 99999.9, max = -99999.9, sample_set[n_samples];
+
+ // Move to the first point, deploy, and probe
+ const float t = probe_pt(X_probe_location, Y_probe_location, raise_after, verbose_level);
+ bool probing_good = !isnan(t);
+
+ if (probing_good) {
+ randomSeed(millis());
+
+ for (uint8_t n = 0; n < n_samples; n++) {
+ if (n_legs) {
+ const int dir = (random(0, 10) > 5.0) ? -1 : 1; // clockwise or counter clockwise
+ float angle = random(0.0, 360.0);
+ const float radius = random(
+ #if ENABLED(DELTA)
+ 0.1250000000 * (DELTA_PRINTABLE_RADIUS),
+ 0.3333333333 * (DELTA_PRINTABLE_RADIUS)
+ #else
+ 5.0, 0.125 * min(X_BED_SIZE, Y_BED_SIZE)
+ #endif
+ );
+
+ if (verbose_level > 3) {
+ SERIAL_ECHOPAIR("Starting radius: ", radius);
+ SERIAL_ECHOPAIR(" angle: ", angle);
+ SERIAL_ECHOPGM(" Direction: ");
+ if (dir > 0) SERIAL_ECHOPGM("Counter-");
+ SERIAL_ECHOLNPGM("Clockwise");
+ }
+
+ for (uint8_t l = 0; l < n_legs - 1; l++) {
+ double delta_angle;
+
+ if (schizoid_flag)
+ // The points of a 5 point star are 72 degrees apart. We need to
+ // skip a point and go to the next one on the star.
+ delta_angle = dir * 2.0 * 72.0;
+
+ else
+ // If we do this line, we are just trying to move further
+ // around the circle.
+ delta_angle = dir * (float) random(25, 45);
+
+ angle += delta_angle;
+
+ while (angle > 360.0) // We probably do not need to keep the angle between 0 and 2*PI, but the
+ angle -= 360.0; // Arduino documentation says the trig functions should not be given values
+ while (angle < 0.0) // outside of this range. It looks like they behave correctly with
+ angle += 360.0; // numbers outside of the range, but just to be safe we clamp them.
+
+ X_current = X_probe_location - (X_PROBE_OFFSET_FROM_EXTRUDER) + cos(RADIANS(angle)) * radius;
+ Y_current = Y_probe_location - (Y_PROBE_OFFSET_FROM_EXTRUDER) + sin(RADIANS(angle)) * radius;
+
+ #if DISABLED(DELTA)
+ X_current = constrain(X_current, X_MIN_POS, X_MAX_POS);
+ Y_current = constrain(Y_current, Y_MIN_POS, Y_MAX_POS);
+ #else
+ // If we have gone out too far, we can do a simple fix and scale the numbers
+ // back in closer to the origin.
+ while (!position_is_reachable_by_probe(X_current, Y_current)) {
+ X_current *= 0.8;
+ Y_current *= 0.8;
+ if (verbose_level > 3) {
+ SERIAL_ECHOPAIR("Pulling point towards center:", X_current);
+ SERIAL_ECHOLNPAIR(", ", Y_current);
+ }
+ }
+ #endif
+ if (verbose_level > 3) {
+ SERIAL_PROTOCOLPGM("Going to:");
+ SERIAL_ECHOPAIR(" X", X_current);
+ SERIAL_ECHOPAIR(" Y", Y_current);
+ SERIAL_ECHOLNPAIR(" Z", current_position[Z_AXIS]);
+ }
+ do_blocking_move_to_xy(X_current, Y_current);
+ } // n_legs loop
+ } // n_legs
+
+ // Probe a single point
+ sample_set[n] = probe_pt(X_probe_location, Y_probe_location, raise_after);
+
+ // Break the loop if the probe fails
+ probing_good = !isnan(sample_set[n]);
+ if (!probing_good) break;
+
+ /**
+ * Get the current mean for the data points we have so far
+ */
+ double sum = 0.0;
+ for (uint8_t j = 0; j <= n; j++) sum += sample_set[j];
+ mean = sum / (n + 1);
+
+ NOMORE(min, sample_set[n]);
+ NOLESS(max, sample_set[n]);
+
+ /**
+ * Now, use that mean to calculate the standard deviation for the
+ * data points we have so far
+ */
+ sum = 0.0;
+ for (uint8_t j = 0; j <= n; j++)
+ sum += sq(sample_set[j] - mean);
+
+ sigma = SQRT(sum / (n + 1));
+ if (verbose_level > 0) {
+ if (verbose_level > 1) {
+ SERIAL_PROTOCOL(n + 1);
+ SERIAL_PROTOCOLPGM(" of ");
+ SERIAL_PROTOCOL((int)n_samples);
+ SERIAL_PROTOCOLPGM(": z: ");
+ SERIAL_PROTOCOL_F(sample_set[n], 3);
+ if (verbose_level > 2) {
+ SERIAL_PROTOCOLPGM(" mean: ");
+ SERIAL_PROTOCOL_F(mean, 4);
+ SERIAL_PROTOCOLPGM(" sigma: ");
+ SERIAL_PROTOCOL_F(sigma, 6);
+ SERIAL_PROTOCOLPGM(" min: ");
+ SERIAL_PROTOCOL_F(min, 3);
+ SERIAL_PROTOCOLPGM(" max: ");
+ SERIAL_PROTOCOL_F(max, 3);
+ SERIAL_PROTOCOLPGM(" range: ");
+ SERIAL_PROTOCOL_F(max-min, 3);
+ }
+ SERIAL_EOL();
+ }
+ }
+
+ } // n_samples loop
+ }
+
+ STOW_PROBE();
+
+ if (probing_good) {
+ SERIAL_PROTOCOLLNPGM("Finished!");
+
+ if (verbose_level > 0) {
+ SERIAL_PROTOCOLPGM("Mean: ");
+ SERIAL_PROTOCOL_F(mean, 6);
+ SERIAL_PROTOCOLPGM(" Min: ");
+ SERIAL_PROTOCOL_F(min, 3);
+ SERIAL_PROTOCOLPGM(" Max: ");
+ SERIAL_PROTOCOL_F(max, 3);
+ SERIAL_PROTOCOLPGM(" Range: ");
+ SERIAL_PROTOCOL_F(max-min, 3);
+ SERIAL_EOL();
+ }
+
+ SERIAL_PROTOCOLPGM("Standard Deviation: ");
+ SERIAL_PROTOCOL_F(sigma, 6);
+ SERIAL_EOL();
+ SERIAL_EOL();
+ }
+
+ clean_up_after_endstop_or_probe_move();
+
+ // Re-enable bed level correction if it had been on
+ #if HAS_LEVELING
+ set_bed_leveling_enabled(was_enabled);
+ #endif
+
+ #if Z_AFTER_PROBING
+ move_z_after_probing();
+ #endif
+
+ report_current_position();
+ }
+
+#endif // Z_MIN_PROBE_REPEATABILITY_TEST
+
+#if ENABLED(G26_MESH_VALIDATION)
+
+ inline void gcode_M49() {
+ g26_debug_flag ^= true;
+ SERIAL_PROTOCOLPGM("G26 Debug ");
+ serialprintPGM(g26_debug_flag ? PSTR("on.\n") : PSTR("off.\n"));
+ }
+
+#endif // G26_MESH_VALIDATION
+
+#if ENABLED(ULTRA_LCD) && ENABLED(LCD_SET_PROGRESS_MANUALLY)
+ /**
+ * M73: Set percentage complete (for display on LCD)
+ *
+ * Example:
+ * M73 P25 ; Set progress to 25%
+ *
+ * Notes:
+ * This has no effect during an SD print job
+ */
+ inline void gcode_M73() {
+ if (!IS_SD_PRINTING && parser.seen('P')) {
+ progress_bar_percent = parser.value_byte();
+ NOMORE(progress_bar_percent, 100);
+ }
+ }
+#endif // ULTRA_LCD && LCD_SET_PROGRESS_MANUALLY
+
+/**
+ * M75: Start print timer
+ */
+inline void gcode_M75() { print_job_timer.start(); }
+
+/**
+ * M76: Pause print timer
+ */
+inline void gcode_M76() { print_job_timer.pause(); }
+
+/**
+ * M77: Stop print timer
+ */
+inline void gcode_M77() { print_job_timer.stop(); }
+
+#if ENABLED(PRINTCOUNTER)
+ /**
+ * M78: Show print statistics
+ */
+ inline void gcode_M78() {
+ // "M78 S78" will reset the statistics
+ if (parser.intval('S') == 78)
+ print_job_timer.initStats();
+ else
+ print_job_timer.showStats();
+ }
+#endif
+
+/**
+ * M104: Set hot end temperature
+ */
+inline void gcode_M104() {
+ if (get_target_extruder_from_command(104)) return;
+ if (DEBUGGING(DRYRUN)) return;
+
+ #if ENABLED(SINGLENOZZLE)
+ if (target_extruder != active_extruder) return;
+ #endif
+
+ if (parser.seenval('S')) {
+ const int16_t temp = parser.value_celsius();
+ thermalManager.setTargetHotend(temp, target_extruder);
+
+ #if ENABLED(DUAL_X_CARRIAGE)
+ if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && target_extruder == 0)
+ thermalManager.setTargetHotend(temp ? temp + duplicate_extruder_temp_offset : 0, 1);
+ #endif
+
+ #if ENABLED(PRINTJOB_TIMER_AUTOSTART)
+ /**
+ * Stop the timer at the end of print. Start is managed by 'heat and wait' M109.
+ * We use half EXTRUDE_MINTEMP here to allow nozzles to be put into hot
+ * standby mode, for instance in a dual extruder setup, without affecting
+ * the running print timer.
+ */
+ if (parser.value_celsius() <= (EXTRUDE_MINTEMP) / 2) {
+ print_job_timer.stop();
+ LCD_MESSAGEPGM(WELCOME_MSG);
+ }
+ #endif
+
+ #if ENABLED(ULTRA_LCD)
+ if (parser.value_celsius() > thermalManager.degHotend(target_extruder))
+ lcd_status_printf_P(0, PSTR("E%i %s"), target_extruder + 1, MSG_HEATING);
+ #endif
+ }
+
+ #if ENABLED(AUTOTEMP)
+ planner.autotemp_M104_M109();
+ #endif
+}
+
+/**
+ * M105: Read hot end and bed temperature
+ */
+inline void gcode_M105() {
+ if (get_target_extruder_from_command(105)) return;
+
+ #if HAS_TEMP_SENSOR
+ SERIAL_PROTOCOLPGM(MSG_OK);
+ thermalManager.print_heaterstates();
+ #else // !HAS_TEMP_HOTEND && !HAS_TEMP_BED
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_NO_THERMISTORS);
+ #endif
+
+ SERIAL_EOL();
+}
+
+#if ENABLED(AUTO_REPORT_TEMPERATURES)
+
+ /**
+ * M155: Set temperature auto-report interval. M155 S
+ */
+ inline void gcode_M155() {
+ if (parser.seenval('S'))
+ thermalManager.set_auto_report_interval(parser.value_byte());
+ }
+
+#endif // AUTO_REPORT_TEMPERATURES
+
+#if FAN_COUNT > 0
+
+ /**
+ * M106: Set Fan Speed
+ *
+ * S Speed between 0-255
+ * P Fan index, if more than one fan
+ *
+ * With EXTRA_FAN_SPEED enabled:
+ *
+ * T Restore/Use/Set Temporary Speed:
+ * 1 = Restore previous speed after T2
+ * 2 = Use temporary speed set with T3-255
+ * 3-255 = Set the speed for use with T2
+ */
+ inline void gcode_M106() {
+ const uint8_t p = parser.byteval('P');
+ if (p < FAN_COUNT) {
+ #if ENABLED(EXTRA_FAN_SPEED)
+ const int16_t t = parser.intval('T');
+ if (t > 0) {
+ switch (t) {
+ case 1:
+ fanSpeeds[p] = old_fanSpeeds[p];
+ break;
+ case 2:
+ old_fanSpeeds[p] = fanSpeeds[p];
+ fanSpeeds[p] = new_fanSpeeds[p];
+ break;
+ default:
+ new_fanSpeeds[p] = min(t, 255);
+ break;
+ }
+ return;
+ }
+ #endif // EXTRA_FAN_SPEED
+ const uint16_t s = parser.ushortval('S', 255);
+ fanSpeeds[p] = min(s, 255);
+ }
+ }
+
+ /**
+ * M107: Fan Off
+ */
+ inline void gcode_M107() {
+ const uint16_t p = parser.ushortval('P');
+ if (p < FAN_COUNT) fanSpeeds[p] = 0;
+ }
+
+#endif // FAN_COUNT > 0
+
+#if DISABLED(EMERGENCY_PARSER)
+
+ /**
+ * M108: Stop the waiting for heaters in M109, M190, M303. Does not affect the target temperature.
+ */
+ inline void gcode_M108() { wait_for_heatup = false; }
+
+
+ /**
+ * M112: Emergency Stop
+ */
+ inline void gcode_M112() { kill(PSTR(MSG_KILLED)); }
+
+
+ /**
+ * M410: Quickstop - Abort all planned moves
+ *
+ * This will stop the carriages mid-move, so most likely they
+ * will be out of sync with the stepper position after this.
+ */
+ inline void gcode_M410() { quickstop_stepper(); }
+
+#endif
+
+/**
+ * M109: Sxxx Wait for extruder(s) to reach temperature. Waits only when heating.
+ * Rxxx Wait for extruder(s) to reach temperature. Waits when heating and cooling.
+ */
+
+#ifndef MIN_COOLING_SLOPE_DEG
+ #define MIN_COOLING_SLOPE_DEG 1.50
+#endif
+#ifndef MIN_COOLING_SLOPE_TIME
+ #define MIN_COOLING_SLOPE_TIME 60
+#endif
+
+inline void gcode_M109() {
+
+ if (get_target_extruder_from_command(109)) return;
+ if (DEBUGGING(DRYRUN)) return;
+
+ #if ENABLED(SINGLENOZZLE)
+ if (target_extruder != active_extruder) return;
+ #endif
+
+ const bool no_wait_for_cooling = parser.seenval('S');
+ if (no_wait_for_cooling || parser.seenval('R')) {
+ const int16_t temp = parser.value_celsius();
+ thermalManager.setTargetHotend(temp, target_extruder);
+
+ #if ENABLED(DUAL_X_CARRIAGE)
+ if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && target_extruder == 0)
+ thermalManager.setTargetHotend(temp ? temp + duplicate_extruder_temp_offset : 0, 1);
+ #endif
+
+ #if ENABLED(PRINTJOB_TIMER_AUTOSTART)
+ /**
+ * Use half EXTRUDE_MINTEMP to allow nozzles to be put into hot
+ * standby mode, (e.g., in a dual extruder setup) without affecting
+ * the running print timer.
+ */
+ if (parser.value_celsius() <= (EXTRUDE_MINTEMP) / 2) {
+ print_job_timer.stop();
+ LCD_MESSAGEPGM(WELCOME_MSG);
+ }
+ else
+ print_job_timer.start();
+ #endif
+
+ #if ENABLED(ULTRA_LCD)
+ if (thermalManager.isHeatingHotend(target_extruder))
+ lcd_status_printf_P(0, PSTR("E%i %s"), target_extruder + 1, MSG_HEATING);
+ #endif
+ }
+ else return;
+
+ #if ENABLED(AUTOTEMP)
+ planner.autotemp_M104_M109();
+ #endif
+
+ #if TEMP_RESIDENCY_TIME > 0
+ millis_t residency_start_ms = 0;
+ // Loop until the temperature has stabilized
+ #define TEMP_CONDITIONS (!residency_start_ms || PENDING(now, residency_start_ms + (TEMP_RESIDENCY_TIME) * 1000UL))
+ #else
+ // Loop until the temperature is very close target
+ #define TEMP_CONDITIONS (wants_to_cool ? thermalManager.isCoolingHotend(target_extruder) : thermalManager.isHeatingHotend(target_extruder))
+ #endif
+
+ float target_temp = -1.0, old_temp = 9999.0;
+ bool wants_to_cool = false;
+ wait_for_heatup = true;
+ millis_t now, next_temp_ms = 0, next_cool_check_ms = 0;
+
+ #if DISABLED(BUSY_WHILE_HEATING)
+ KEEPALIVE_STATE(NOT_BUSY);
+ #endif
+
+ #if ENABLED(PRINTER_EVENT_LEDS)
+ const float start_temp = thermalManager.degHotend(target_extruder);
+ uint8_t old_blue = 0;
+ #endif
+
+ do {
+ // Target temperature might be changed during the loop
+ if (target_temp != thermalManager.degTargetHotend(target_extruder)) {
+ wants_to_cool = thermalManager.isCoolingHotend(target_extruder);
+ target_temp = thermalManager.degTargetHotend(target_extruder);
+
+ // Exit if S, continue if S, R, or R
+ if (no_wait_for_cooling && wants_to_cool) break;
+ }
+
+ now = millis();
+ if (ELAPSED(now, next_temp_ms)) { //Print temp & remaining time every 1s while waiting
+ next_temp_ms = now + 1000UL;
+ thermalManager.print_heaterstates();
+ #if TEMP_RESIDENCY_TIME > 0
+ SERIAL_PROTOCOLPGM(" W:");
+ if (residency_start_ms)
+ SERIAL_PROTOCOL(long((((TEMP_RESIDENCY_TIME) * 1000UL) - (now - residency_start_ms)) / 1000UL));
+ else
+ SERIAL_PROTOCOLCHAR('?');
+ #endif
+ SERIAL_EOL();
+ }
+
+ idle();
+ reset_stepper_timeout(); // Keep steppers powered
+
+ const float temp = thermalManager.degHotend(target_extruder);
+
+ #if ENABLED(PRINTER_EVENT_LEDS)
+ // Gradually change LED strip from violet to red as nozzle heats up
+ if (!wants_to_cool) {
+ const uint8_t blue = map(constrain(temp, start_temp, target_temp), start_temp, target_temp, 255, 0);
+ if (blue != old_blue) {
+ old_blue = blue;
+ leds.set_color(
+ MakeLEDColor(255, 0, blue, 0, pixels.getBrightness())
+ #if ENABLED(NEOPIXEL_IS_SEQUENTIAL)
+ , true
+ #endif
+ );
+ }
+ }
+ #endif
+
+ #if TEMP_RESIDENCY_TIME > 0
+
+ const float temp_diff = FABS(target_temp - temp);
+
+ if (!residency_start_ms) {
+ // Start the TEMP_RESIDENCY_TIME timer when we reach target temp for the first time.
+ if (temp_diff < TEMP_WINDOW) residency_start_ms = now;
+ }
+ else if (temp_diff > TEMP_HYSTERESIS) {
+ // Restart the timer whenever the temperature falls outside the hysteresis.
+ residency_start_ms = now;
+ }
+
+ #endif
+
+ // Prevent a wait-forever situation if R is misused i.e. M109 R0
+ if (wants_to_cool) {
+ // break after MIN_COOLING_SLOPE_TIME seconds
+ // if the temperature did not drop at least MIN_COOLING_SLOPE_DEG
+ if (!next_cool_check_ms || ELAPSED(now, next_cool_check_ms)) {
+ if (old_temp - temp < MIN_COOLING_SLOPE_DEG) break;
+ next_cool_check_ms = now + 1000UL * MIN_COOLING_SLOPE_TIME;
+ old_temp = temp;
+ }
+ }
+
+ } while (wait_for_heatup && TEMP_CONDITIONS);
+
+ if (wait_for_heatup) {
+ LCD_MESSAGEPGM(MSG_HEATING_COMPLETE);
+ #if ENABLED(PRINTER_EVENT_LEDS)
+ leds.set_white();
+ #endif
+ }
+
+ #if DISABLED(BUSY_WHILE_HEATING)
+ KEEPALIVE_STATE(IN_HANDLER);
+ #endif
+}
+
+#if HAS_TEMP_BED
+
+ #ifndef MIN_COOLING_SLOPE_DEG_BED
+ #define MIN_COOLING_SLOPE_DEG_BED 1.50
+ #endif
+ #ifndef MIN_COOLING_SLOPE_TIME_BED
+ #define MIN_COOLING_SLOPE_TIME_BED 60
+ #endif
+
+ /**
+ * M190: Sxxx Wait for bed current temp to reach target temp. Waits only when heating
+ * Rxxx Wait for bed current temp to reach target temp. Waits when heating and cooling
+ */
+ inline void gcode_M190() {
+ if (DEBUGGING(DRYRUN)) return;
+
+ LCD_MESSAGEPGM(MSG_BED_HEATING);
+ const bool no_wait_for_cooling = parser.seenval('S');
+ if (no_wait_for_cooling || parser.seenval('R')) {
+ thermalManager.setTargetBed(parser.value_celsius());
+ #if ENABLED(PRINTJOB_TIMER_AUTOSTART)
+ if (parser.value_celsius() > BED_MINTEMP)
+ print_job_timer.start();
+ #endif
+ }
+ else return;
+
+ #if TEMP_BED_RESIDENCY_TIME > 0
+ millis_t residency_start_ms = 0;
+ // Loop until the temperature has stabilized
+ #define TEMP_BED_CONDITIONS (!residency_start_ms || PENDING(now, residency_start_ms + (TEMP_BED_RESIDENCY_TIME) * 1000UL))
+ #else
+ // Loop until the temperature is very close target
+ #define TEMP_BED_CONDITIONS (wants_to_cool ? thermalManager.isCoolingBed() : thermalManager.isHeatingBed())
+ #endif
+
+ float target_temp = -1.0, old_temp = 9999.0;
+ bool wants_to_cool = false;
+ wait_for_heatup = true;
+ millis_t now, next_temp_ms = 0, next_cool_check_ms = 0;
+
+ #if DISABLED(BUSY_WHILE_HEATING)
+ KEEPALIVE_STATE(NOT_BUSY);
+ #endif
+
+ target_extruder = active_extruder; // for print_heaterstates
+
+ #if ENABLED(PRINTER_EVENT_LEDS)
+ const float start_temp = thermalManager.degBed();
+ uint8_t old_red = 127;
+ #endif
+
+ do {
+ // Target temperature might be changed during the loop
+ if (target_temp != thermalManager.degTargetBed()) {
+ wants_to_cool = thermalManager.isCoolingBed();
+ target_temp = thermalManager.degTargetBed();
+
+ // Exit if S, continue if S, R, or R
+ if (no_wait_for_cooling && wants_to_cool) break;
+ }
+
+ now = millis();
+ if (ELAPSED(now, next_temp_ms)) { //Print Temp Reading every 1 second while heating up.
+ next_temp_ms = now + 1000UL;
+ thermalManager.print_heaterstates();
+ #if TEMP_BED_RESIDENCY_TIME > 0
+ SERIAL_PROTOCOLPGM(" W:");
+ if (residency_start_ms)
+ SERIAL_PROTOCOL(long((((TEMP_BED_RESIDENCY_TIME) * 1000UL) - (now - residency_start_ms)) / 1000UL));
+ else
+ SERIAL_PROTOCOLCHAR('?');
+ #endif
+ SERIAL_EOL();
+ }
+
+ idle();
+ reset_stepper_timeout(); // Keep steppers powered
+
+ const float temp = thermalManager.degBed();
+
+ #if ENABLED(PRINTER_EVENT_LEDS)
+ // Gradually change LED strip from blue to violet as bed heats up
+ if (!wants_to_cool) {
+ const uint8_t red = map(constrain(temp, start_temp, target_temp), start_temp, target_temp, 0, 255);
+ if (red != old_red) {
+ old_red = red;
+ leds.set_color(
+ MakeLEDColor(red, 0, 255, 0, pixels.getBrightness())
+ #if ENABLED(NEOPIXEL_IS_SEQUENTIAL)
+ , true
+ #endif
+ );
+ }
+ }
+ #endif
+
+ #if TEMP_BED_RESIDENCY_TIME > 0
+
+ const float temp_diff = FABS(target_temp - temp);
+
+ if (!residency_start_ms) {
+ // Start the TEMP_BED_RESIDENCY_TIME timer when we reach target temp for the first time.
+ if (temp_diff < TEMP_BED_WINDOW) residency_start_ms = now;
+ }
+ else if (temp_diff > TEMP_BED_HYSTERESIS) {
+ // Restart the timer whenever the temperature falls outside the hysteresis.
+ residency_start_ms = now;
+ }
+
+ #endif // TEMP_BED_RESIDENCY_TIME > 0
+
+ // Prevent a wait-forever situation if R is misused i.e. M190 R0
+ if (wants_to_cool) {
+ // Break after MIN_COOLING_SLOPE_TIME_BED seconds
+ // if the temperature did not drop at least MIN_COOLING_SLOPE_DEG_BED
+ if (!next_cool_check_ms || ELAPSED(now, next_cool_check_ms)) {
+ if (old_temp - temp < MIN_COOLING_SLOPE_DEG_BED) break;
+ next_cool_check_ms = now + 1000UL * MIN_COOLING_SLOPE_TIME_BED;
+ old_temp = temp;
+ }
+ }
+
+ } while (wait_for_heatup && TEMP_BED_CONDITIONS);
+
+ if (wait_for_heatup) LCD_MESSAGEPGM(MSG_BED_DONE);
+ #if DISABLED(BUSY_WHILE_HEATING)
+ KEEPALIVE_STATE(IN_HANDLER);
+ #endif
+ }
+
+#endif // HAS_TEMP_BED
+
+/**
+ * M110: Set Current Line Number
+ */
+inline void gcode_M110() {
+ if (parser.seenval('N')) gcode_LastN = parser.value_long();
+}
+
+/**
+ * M111: Set the debug level
+ */
+inline void gcode_M111() {
+ if (parser.seen('S')) marlin_debug_flags = parser.byteval('S');
+
+ const static char str_debug_1[] PROGMEM = MSG_DEBUG_ECHO,
+ str_debug_2[] PROGMEM = MSG_DEBUG_INFO,
+ str_debug_4[] PROGMEM = MSG_DEBUG_ERRORS,
+ str_debug_8[] PROGMEM = MSG_DEBUG_DRYRUN,
+ str_debug_16[] PROGMEM = MSG_DEBUG_COMMUNICATION
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ , str_debug_32[] PROGMEM = MSG_DEBUG_LEVELING
+ #endif
+ ;
+
+ const static char* const debug_strings[] PROGMEM = {
+ str_debug_1, str_debug_2, str_debug_4, str_debug_8, str_debug_16
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ , str_debug_32
+ #endif
+ };
+
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM(MSG_DEBUG_PREFIX);
+ if (marlin_debug_flags) {
+ uint8_t comma = 0;
+ for (uint8_t i = 0; i < COUNT(debug_strings); i++) {
+ if (TEST(marlin_debug_flags, i)) {
+ if (comma++) SERIAL_CHAR(',');
+ serialprintPGM((char*)pgm_read_ptr(&debug_strings[i]));
+ }
+ }
+ }
+ else {
+ SERIAL_ECHOPGM(MSG_DEBUG_OFF);
+ }
+ SERIAL_EOL();
+}
+
+#if ENABLED(HOST_KEEPALIVE_FEATURE)
+
+ /**
+ * M113: Get or set Host Keepalive interval (0 to disable)
+ *
+ * S Optional. Set the keepalive interval.
+ */
+ inline void gcode_M113() {
+ if (parser.seenval('S')) {
+ host_keepalive_interval = parser.value_byte();
+ NOMORE(host_keepalive_interval, 60);
+ }
+ else {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR("M113 S", (unsigned long)host_keepalive_interval);
+ }
+ }
+
+#endif
+
+#if ENABLED(BARICUDA)
+
+ #if HAS_HEATER_1
+ /**
+ * M126: Heater 1 valve open
+ */
+ inline void gcode_M126() { baricuda_valve_pressure = parser.byteval('S', 255); }
+ /**
+ * M127: Heater 1 valve close
+ */
+ inline void gcode_M127() { baricuda_valve_pressure = 0; }
+ #endif
+
+ #if HAS_HEATER_2
+ /**
+ * M128: Heater 2 valve open
+ */
+ inline void gcode_M128() { baricuda_e_to_p_pressure = parser.byteval('S', 255); }
+ /**
+ * M129: Heater 2 valve close
+ */
+ inline void gcode_M129() { baricuda_e_to_p_pressure = 0; }
+ #endif
+
+#endif // BARICUDA
+
+/**
+ * M140: Set bed temperature
+ */
+inline void gcode_M140() {
+ if (DEBUGGING(DRYRUN)) return;
+ if (parser.seenval('S')) thermalManager.setTargetBed(parser.value_celsius());
+}
+
+#if ENABLED(ULTIPANEL)
+
+ /**
+ * M145: Set the heatup state for a material in the LCD menu
+ *
+ * S (0=PLA, 1=ABS)
+ * H
+ * B
+ * F
+ */
+ inline void gcode_M145() {
+ const uint8_t material = (uint8_t)parser.intval('S');
+ if (material >= COUNT(lcd_preheat_hotend_temp)) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_MATERIAL_INDEX);
+ }
+ else {
+ int v;
+ if (parser.seenval('H')) {
+ v = parser.value_int();
+ lcd_preheat_hotend_temp[material] = constrain(v, EXTRUDE_MINTEMP, HEATER_0_MAXTEMP - 15);
+ }
+ if (parser.seenval('F')) {
+ v = parser.value_int();
+ lcd_preheat_fan_speed[material] = constrain(v, 0, 255);
+ }
+ #if TEMP_SENSOR_BED != 0
+ if (parser.seenval('B')) {
+ v = parser.value_int();
+ lcd_preheat_bed_temp[material] = constrain(v, BED_MINTEMP, BED_MAXTEMP - 15);
+ }
+ #endif
+ }
+ }
+
+#endif // ULTIPANEL
+
+#if ENABLED(TEMPERATURE_UNITS_SUPPORT)
+ /**
+ * M149: Set temperature units
+ */
+ inline void gcode_M149() {
+ if (parser.seenval('C')) parser.set_input_temp_units(TEMPUNIT_C);
+ else if (parser.seenval('K')) parser.set_input_temp_units(TEMPUNIT_K);
+ else if (parser.seenval('F')) parser.set_input_temp_units(TEMPUNIT_F);
+ }
+#endif
+
+#if HAS_POWER_SWITCH
+
+ /**
+ * M80 : Turn on the Power Supply
+ * M80 S : Report the current state and exit
+ */
+ inline void gcode_M80() {
+
+ // S: Report the current power supply state and exit
+ if (parser.seen('S')) {
+ serialprintPGM(powersupply_on ? PSTR("PS:1\n") : PSTR("PS:0\n"));
+ return;
+ }
+
+ PSU_ON();
+
+ /**
+ * If you have a switch on suicide pin, this is useful
+ * if you want to start another print with suicide feature after
+ * a print without suicide...
+ */
+ #if HAS_SUICIDE
+ OUT_WRITE(SUICIDE_PIN, HIGH);
+ #endif
+
+ #if DISABLED(AUTO_POWER_CONTROL)
+ delay(100); // Wait for power to settle
+ restore_stepper_drivers();
+ #endif
+
+ #if ENABLED(ULTIPANEL)
+ LCD_MESSAGEPGM(WELCOME_MSG);
+ #endif
+ }
+
+#endif // HAS_POWER_SWITCH
+
+/**
+ * M81: Turn off Power, including Power Supply, if there is one.
+ *
+ * This code should ALWAYS be available for EMERGENCY SHUTDOWN!
+ */
+inline void gcode_M81() {
+ thermalManager.disable_all_heaters();
+ stepper.finish_and_disable();
+
+ #if FAN_COUNT > 0
+ for (uint8_t i = 0; i < FAN_COUNT; i++) fanSpeeds[i] = 0;
+ #if ENABLED(PROBING_FANS_OFF)
+ fans_paused = false;
+ ZERO(paused_fanSpeeds);
+ #endif
+ #endif
+
+ safe_delay(1000); // Wait 1 second before switching off
+
+ #if HAS_SUICIDE
+ stepper.synchronize();
+ suicide();
+ #elif HAS_POWER_SWITCH
+ PSU_OFF();
+ #endif
+
+ #if ENABLED(ULTIPANEL)
+ LCD_MESSAGEPGM(MACHINE_NAME " " MSG_OFF ".");
+ #endif
+}
+
+/**
+ * M82: Set E codes absolute (default)
+ */
+inline void gcode_M82() { axis_relative_modes[E_AXIS] = false; }
+
+/**
+ * M83: Set E codes relative while in Absolute Coordinates (G90) mode
+ */
+inline void gcode_M83() { axis_relative_modes[E_AXIS] = true; }
+
+/**
+ * M18, M84: Disable stepper motors
+ */
+inline void gcode_M18_M84() {
+ if (parser.seenval('S')) {
+ stepper_inactive_time = parser.value_millis_from_seconds();
+ }
+ else {
+ bool all_axis = !(parser.seen('X') || parser.seen('Y') || parser.seen('Z') || parser.seen('E'));
+ if (all_axis) {
+ stepper.finish_and_disable();
+ }
+ else {
+ stepper.synchronize();
+ if (parser.seen('X')) disable_X();
+ if (parser.seen('Y')) disable_Y();
+ if (parser.seen('Z')) disable_Z();
+ #if E0_ENABLE_PIN != X_ENABLE_PIN && E1_ENABLE_PIN != Y_ENABLE_PIN // Only disable on boards that have separate ENABLE_PINS
+ if (parser.seen('E')) disable_e_steppers();
+ #endif
+ }
+
+ #if ENABLED(AUTO_BED_LEVELING_UBL) && ENABLED(ULTIPANEL) // Only needed with an LCD
+ if (ubl.lcd_map_control) ubl.lcd_map_control = defer_return_to_status = false;
+ #endif
+ }
+}
+
+/**
+ * M85: Set inactivity shutdown timer with parameter S. To disable set zero (default)
+ */
+inline void gcode_M85() {
+ if (parser.seen('S')) max_inactive_time = parser.value_millis_from_seconds();
+}
+
+/**
+ * Multi-stepper support for M92, M201, M203
+ */
+#if ENABLED(DISTINCT_E_FACTORS)
+ #define GET_TARGET_EXTRUDER(CMD) if (get_target_extruder_from_command(CMD)) return
+ #define TARGET_EXTRUDER target_extruder
+#else
+ #define GET_TARGET_EXTRUDER(CMD) NOOP
+ #define TARGET_EXTRUDER 0
+#endif
+
+/**
+ * M92: Set axis steps-per-unit for one or more axes, X, Y, Z, and E.
+ * (Follows the same syntax as G92)
+ *
+ * With multiple extruders use T to specify which one.
+ */
+inline void gcode_M92() {
+
+ GET_TARGET_EXTRUDER(92);
+
+ LOOP_XYZE(i) {
+ if (parser.seen(axis_codes[i])) {
+ if (i == E_AXIS) {
+ const float value = parser.value_per_axis_unit((AxisEnum)(E_AXIS + TARGET_EXTRUDER));
+ if (value < 20.0) {
+ float factor = planner.axis_steps_per_mm[E_AXIS + TARGET_EXTRUDER] / value; // increase e constants if M92 E14 is given for netfab.
+ planner.max_jerk[E_AXIS] *= factor;
+ planner.max_feedrate_mm_s[E_AXIS + TARGET_EXTRUDER] *= factor;
+ planner.max_acceleration_steps_per_s2[E_AXIS + TARGET_EXTRUDER] *= factor;
+ }
+ planner.axis_steps_per_mm[E_AXIS + TARGET_EXTRUDER] = value;
+ }
+ else {
+ planner.axis_steps_per_mm[i] = parser.value_per_axis_unit((AxisEnum)i);
+ }
+ }
+ }
+ planner.refresh_positioning();
+}
+
+/**
+ * Output the current position to serial
+ */
+void report_current_position() {
+ SERIAL_PROTOCOLPGM("X:");
+ SERIAL_PROTOCOL(LOGICAL_X_POSITION(current_position[X_AXIS]));
+ SERIAL_PROTOCOLPGM(" Y:");
+ SERIAL_PROTOCOL(LOGICAL_Y_POSITION(current_position[Y_AXIS]));
+ SERIAL_PROTOCOLPGM(" Z:");
+ SERIAL_PROTOCOL(LOGICAL_Z_POSITION(current_position[Z_AXIS]));
+ SERIAL_PROTOCOLPGM(" E:");
+ SERIAL_PROTOCOL(current_position[E_AXIS]);
+
+ stepper.report_positions();
+
+ #if IS_SCARA
+ SERIAL_PROTOCOLPAIR("SCARA Theta:", stepper.get_axis_position_degrees(A_AXIS));
+ SERIAL_PROTOCOLLNPAIR(" Psi+Theta:", stepper.get_axis_position_degrees(B_AXIS));
+ SERIAL_EOL();
+ #endif
+}
+
+#ifdef M114_DETAIL
+
+ void report_xyze(const float pos[], const uint8_t n = 4, const uint8_t precision = 3) {
+ char str[12];
+ for (uint8_t i = 0; i < n; i++) {
+ SERIAL_CHAR(' ');
+ SERIAL_CHAR(axis_codes[i]);
+ SERIAL_CHAR(':');
+ SERIAL_PROTOCOL(dtostrf(pos[i], 8, precision, str));
+ }
+ SERIAL_EOL();
+ }
+
+ inline void report_xyz(const float pos[]) { report_xyze(pos, 3); }
+
+ void report_current_position_detail() {
+
+ stepper.synchronize();
+
+ SERIAL_PROTOCOLPGM("\nLogical:");
+ const float logical[XYZ] = {
+ LOGICAL_X_POSITION(current_position[X_AXIS]),
+ LOGICAL_Y_POSITION(current_position[Y_AXIS]),
+ LOGICAL_Z_POSITION(current_position[Z_AXIS])
+ };
+ report_xyz(logical);
+
+ SERIAL_PROTOCOLPGM("Raw: ");
+ report_xyz(current_position);
+
+ float leveled[XYZ] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] };
+
+ #if PLANNER_LEVELING
+ SERIAL_PROTOCOLPGM("Leveled:");
+ planner.apply_leveling(leveled);
+ report_xyz(leveled);
+
+ SERIAL_PROTOCOLPGM("UnLevel:");
+ float unleveled[XYZ] = { leveled[X_AXIS], leveled[Y_AXIS], leveled[Z_AXIS] };
+ planner.unapply_leveling(unleveled);
+ report_xyz(unleveled);
+ #endif
+
+ #if IS_KINEMATIC
+ #if IS_SCARA
+ SERIAL_PROTOCOLPGM("ScaraK: ");
+ #else
+ SERIAL_PROTOCOLPGM("DeltaK: ");
+ #endif
+ inverse_kinematics(leveled); // writes delta[]
+ report_xyz(delta);
+ #endif
+
+ SERIAL_PROTOCOLPGM("Stepper:");
+ LOOP_XYZE(i) {
+ SERIAL_CHAR(' ');
+ SERIAL_CHAR(axis_codes[i]);
+ SERIAL_CHAR(':');
+ SERIAL_PROTOCOL(stepper.position((AxisEnum)i));
+ }
+ SERIAL_EOL();
+
+ #if IS_SCARA
+ const float deg[XYZ] = {
+ stepper.get_axis_position_degrees(A_AXIS),
+ stepper.get_axis_position_degrees(B_AXIS)
+ };
+ SERIAL_PROTOCOLPGM("Degrees:");
+ report_xyze(deg, 2);
+ #endif
+
+ SERIAL_PROTOCOLPGM("FromStp:");
+ get_cartesian_from_steppers(); // writes cartes[XYZ] (with forward kinematics)
+ const float from_steppers[XYZE] = { cartes[X_AXIS], cartes[Y_AXIS], cartes[Z_AXIS], stepper.get_axis_position_mm(E_AXIS) };
+ report_xyze(from_steppers);
+
+ const float diff[XYZE] = {
+ from_steppers[X_AXIS] - leveled[X_AXIS],
+ from_steppers[Y_AXIS] - leveled[Y_AXIS],
+ from_steppers[Z_AXIS] - leveled[Z_AXIS],
+ from_steppers[E_AXIS] - current_position[E_AXIS]
+ };
+ SERIAL_PROTOCOLPGM("Differ: ");
+ report_xyze(diff);
+ }
+#endif // M114_DETAIL
+
+/**
+ * M114: Report current position to host
+ */
+inline void gcode_M114() {
+
+ #ifdef M114_DETAIL
+ if (parser.seen('D')) {
+ report_current_position_detail();
+ return;
+ }
+ #endif
+
+ stepper.synchronize();
+ report_current_position();
+}
+
+/**
+ * M115: Capabilities string
+ */
+
+#if ENABLED(EXTENDED_CAPABILITIES_REPORT)
+ static void cap_line(const char * const name, bool ena=false) {
+ SERIAL_PROTOCOLPGM("Cap:");
+ serialprintPGM(name);
+ SERIAL_PROTOCOLPGM(":");
+ SERIAL_PROTOCOLLN(int(ena ? 1 : 0));
+ }
+#endif
+
+inline void gcode_M115() {
+ SERIAL_PROTOCOLLNPGM(MSG_M115_REPORT);
+
+ #if ENABLED(EXTENDED_CAPABILITIES_REPORT)
+
+ // SERIAL_XON_XOFF
+ cap_line(PSTR("SERIAL_XON_XOFF")
+ #if ENABLED(SERIAL_XON_XOFF)
+ , true
+ #endif
+ );
+
+ // EEPROM (M500, M501)
+ cap_line(PSTR("EEPROM")
+ #if ENABLED(EEPROM_SETTINGS)
+ , true
+ #endif
+ );
+
+ // Volumetric Extrusion (M200)
+ cap_line(PSTR("VOLUMETRIC")
+ #if DISABLED(NO_VOLUMETRICS)
+ , true
+ #endif
+ );
+
+ // AUTOREPORT_TEMP (M155)
+ cap_line(PSTR("AUTOREPORT_TEMP")
+ #if ENABLED(AUTO_REPORT_TEMPERATURES)
+ , true
+ #endif
+ );
+
+ // PROGRESS (M530 S L, M531 , M532 X L)
+ cap_line(PSTR("PROGRESS"));
+
+ // Print Job timer M75, M76, M77
+ cap_line(PSTR("PRINT_JOB"), true);
+
+ // AUTOLEVEL (G29)
+ cap_line(PSTR("AUTOLEVEL")
+ #if HAS_AUTOLEVEL
+ , true
+ #endif
+ );
+
+ // Z_PROBE (G30)
+ cap_line(PSTR("Z_PROBE")
+ #if HAS_BED_PROBE
+ , true
+ #endif
+ );
+
+ // MESH_REPORT (M420 V)
+ cap_line(PSTR("LEVELING_DATA")
+ #if HAS_LEVELING
+ , true
+ #endif
+ );
+
+ // BUILD_PERCENT (M73)
+ cap_line(PSTR("BUILD_PERCENT")
+ #if ENABLED(LCD_SET_PROGRESS_MANUALLY)
+ , true
+ #endif
+ );
+
+ // SOFTWARE_POWER (M80, M81)
+ cap_line(PSTR("SOFTWARE_POWER")
+ #if HAS_POWER_SWITCH
+ , true
+ #endif
+ );
+
+ // CASE LIGHTS (M355)
+ cap_line(PSTR("TOGGLE_LIGHTS")
+ #if HAS_CASE_LIGHT
+ , true
+ #endif
+ );
+ cap_line(PSTR("CASE_LIGHT_BRIGHTNESS")
+ #if HAS_CASE_LIGHT
+ , USEABLE_HARDWARE_PWM(CASE_LIGHT_PIN)
+ #endif
+ );
+
+ // EMERGENCY_PARSER (M108, M112, M410)
+ cap_line(PSTR("EMERGENCY_PARSER")
+ #if ENABLED(EMERGENCY_PARSER)
+ , true
+ #endif
+ );
+
+ // AUTOREPORT_SD_STATUS (M27 extension)
+ cap_line(PSTR("AUTOREPORT_SD_STATUS")
+ #if ENABLED(AUTO_REPORT_SD_STATUS)
+ , true
+ #endif
+ );
+
+ #endif // EXTENDED_CAPABILITIES_REPORT
+}
+
+/**
+ * M117: Set LCD Status Message
+ */
+inline void gcode_M117() { lcd_setstatus(parser.string_arg); }
+
+/**
+ * M118: Display a message in the host console.
+ *
+ * A1 Append '// ' for an action command, as in OctoPrint
+ * E1 Have the host 'echo:' the text
+ */
+inline void gcode_M118() {
+ if (parser.seenval('E') && parser.value_bool()) SERIAL_ECHO_START();
+ if (parser.seenval('A') && parser.value_bool()) SERIAL_ECHOPGM("// ");
+ SERIAL_ECHOLN(parser.string_arg);
+}
+
+/**
+ * M119: Output endstop states to serial output
+ */
+inline void gcode_M119() { endstops.M119(); }
+
+/**
+ * M120: Enable endstops and set non-homing endstop state to "enabled"
+ */
+inline void gcode_M120() { endstops.enable_globally(true); }
+
+/**
+ * M121: Disable endstops and set non-homing endstop state to "disabled"
+ */
+inline void gcode_M121() { endstops.enable_globally(false); }
+
+#if ENABLED(PARK_HEAD_ON_PAUSE)
+
+ /**
+ * M125: Store current position and move to filament change position.
+ * Called on pause (by M25) to prevent material leaking onto the
+ * object. On resume (M24) the head will be moved back and the
+ * print will resume.
+ *
+ * If Marlin is compiled without SD Card support, M125 can be
+ * used directly to pause the print and move to park position,
+ * resuming with a button click or M108.
+ *
+ * L = override retract length
+ * X = override X
+ * Y = override Y
+ * Z = override Z raise
+ */
+ inline void gcode_M125() {
+
+ // Initial retract before move to filament change position
+ const float retract = -FABS(parser.seen('L') ? parser.value_axis_units(E_AXIS) : 0
+ #ifdef PAUSE_PARK_RETRACT_LENGTH
+ + (PAUSE_PARK_RETRACT_LENGTH)
+ #endif
+ );
+
+ point_t park_point = NOZZLE_PARK_POINT;
+
+ // Move XY axes to filament change position or given position
+ if (parser.seenval('X')) park_point.x = parser.linearval('X');
+ if (parser.seenval('Y')) park_point.y = parser.linearval('Y');
+
+ // Lift Z axis
+ if (parser.seenval('Z')) park_point.z = parser.linearval('Z');
+
+ #if HOTENDS > 1 && DISABLED(DUAL_X_CARRIAGE) && DISABLED(DELTA)
+ park_point.x += (active_extruder ? hotend_offset[X_AXIS][active_extruder] : 0);
+ park_point.y += (active_extruder ? hotend_offset[Y_AXIS][active_extruder] : 0);
+ #endif
+
+ #if DISABLED(SDSUPPORT)
+ const bool job_running = print_job_timer.isRunning();
+ #endif
+
+ if (pause_print(retract, park_point)) {
+ #if DISABLED(SDSUPPORT)
+ // Wait for lcd click or M108
+ wait_for_filament_reload();
+
+ // Return to print position and continue
+ resume_print();
+
+ if (job_running) print_job_timer.start();
+ #endif
+ }
+ }
+
+#endif // PARK_HEAD_ON_PAUSE
+
+#if HAS_COLOR_LEDS
+
+ /**
+ * M150: Set Status LED Color - Use R-U-B-W for R-G-B-W
+ * and Brightness - Use P (for NEOPIXEL only)
+ *
+ * Always sets all 3 or 4 components. If a component is left out, set to 0.
+ * If brightness is left out, no value changed
+ *
+ * Examples:
+ *
+ * M150 R255 ; Turn LED red
+ * M150 R255 U127 ; Turn LED orange (PWM only)
+ * M150 ; Turn LED off
+ * M150 R U B ; Turn LED white
+ * M150 W ; Turn LED white using a white LED
+ * M150 P127 ; Set LED 50% brightness
+ * M150 P ; Set LED full brightness
+ */
+ inline void gcode_M150() {
+ leds.set_color(MakeLEDColor(
+ parser.seen('R') ? (parser.has_value() ? parser.value_byte() : 255) : 0,
+ parser.seen('U') ? (parser.has_value() ? parser.value_byte() : 255) : 0,
+ parser.seen('B') ? (parser.has_value() ? parser.value_byte() : 255) : 0,
+ parser.seen('W') ? (parser.has_value() ? parser.value_byte() : 255) : 0,
+ parser.seen('P') ? (parser.has_value() ? parser.value_byte() : 255) : pixels.getBrightness()
+ ));
+ }
+
+#endif // HAS_COLOR_LEDS
+
+#if DISABLED(NO_VOLUMETRICS)
+
+ /**
+ * M200: Set filament diameter and set E axis units to cubic units
+ *
+ * T - Optional extruder number. Current extruder if omitted.
+ * D - Diameter of the filament. Use "D0" to switch back to linear units on the E axis.
+ */
+ inline void gcode_M200() {
+
+ if (get_target_extruder_from_command(200)) return;
+
+ if (parser.seen('D')) {
+ // setting any extruder filament size disables volumetric on the assumption that
+ // slicers either generate in extruder values as cubic mm or as as filament feeds
+ // for all extruders
+ if ( (parser.volumetric_enabled = (parser.value_linear_units() != 0.0)) )
+ planner.set_filament_size(target_extruder, parser.value_linear_units());
+ }
+ planner.calculate_volumetric_multipliers();
+ }
+
+#endif // !NO_VOLUMETRICS
+
+/**
+ * M201: Set max acceleration in units/s^2 for print moves (M201 X1000 Y1000)
+ *
+ * With multiple extruders use T to specify which one.
+ */
+inline void gcode_M201() {
+
+ GET_TARGET_EXTRUDER(201);
+
+ LOOP_XYZE(i) {
+ if (parser.seen(axis_codes[i])) {
+ const uint8_t a = i + (i == E_AXIS ? TARGET_EXTRUDER : 0);
+ planner.max_acceleration_mm_per_s2[a] = parser.value_axis_units((AxisEnum)a);
+ }
+ }
+ // steps per sq second need to be updated to agree with the units per sq second (as they are what is used in the planner)
+ planner.reset_acceleration_rates();
+}
+
+#if 0 // Not used for Sprinter/grbl gen6
+ inline void gcode_M202() {
+ LOOP_XYZE(i) {
+ if (parser.seen(axis_codes[i])) axis_travel_steps_per_sqr_second[i] = parser.value_axis_units((AxisEnum)i) * planner.axis_steps_per_mm[i];
+ }
+ }
+#endif
+
+
+/**
+ * M203: Set maximum feedrate that your machine can sustain (M203 X200 Y200 Z300 E10000) in units/sec
+ *
+ * With multiple extruders use T to specify which one.
+ */
+inline void gcode_M203() {
+
+ GET_TARGET_EXTRUDER(203);
+
+ LOOP_XYZE(i)
+ if (parser.seen(axis_codes[i])) {
+ const uint8_t a = i + (i == E_AXIS ? TARGET_EXTRUDER : 0);
+ planner.max_feedrate_mm_s[a] = parser.value_axis_units((AxisEnum)a);
+ }
+}
+
+/**
+ * M204: Set Accelerations in units/sec^2 (M204 P1200 R3000 T3000)
+ *
+ * P = Printing moves
+ * R = Retract only (no X, Y, Z) moves
+ * T = Travel (non printing) moves
+ */
+inline void gcode_M204() {
+ bool report = true;
+ if (parser.seenval('S')) { // Kept for legacy compatibility. Should NOT BE USED for new developments.
+ planner.travel_acceleration = planner.acceleration = parser.value_linear_units();
+ report = false;
+ }
+ if (parser.seenval('P')) {
+ planner.acceleration = parser.value_linear_units();
+ report = false;
+ }
+ if (parser.seenval('R')) {
+ planner.retract_acceleration = parser.value_linear_units();
+ report = false;
+ }
+ if (parser.seenval('T')) {
+ planner.travel_acceleration = parser.value_linear_units();
+ report = false;
+ }
+ if (report) {
+ SERIAL_ECHOPAIR("Acceleration: P", planner.acceleration);
+ SERIAL_ECHOPAIR(" R", planner.retract_acceleration);
+ SERIAL_ECHOLNPAIR(" T", planner.travel_acceleration);
+ }
+}
+
+/**
+ * M205: Set Advanced Settings
+ *
+ * S = Min Feed Rate (units/s)
+ * T = Min Travel Feed Rate (units/s)
+ * B = Min Segment Time (µs)
+ * X = Max X Jerk (units/sec^2)
+ * Y = Max Y Jerk (units/sec^2)
+ * Z = Max Z Jerk (units/sec^2)
+ * E = Max E Jerk (units/sec^2)
+ */
+inline void gcode_M205() {
+ if (parser.seen('S')) planner.min_feedrate_mm_s = parser.value_linear_units();
+ if (parser.seen('T')) planner.min_travel_feedrate_mm_s = parser.value_linear_units();
+ if (parser.seen('B')) planner.min_segment_time_us = parser.value_ulong();
+ if (parser.seen('X')) planner.max_jerk[X_AXIS] = parser.value_linear_units();
+ if (parser.seen('Y')) planner.max_jerk[Y_AXIS] = parser.value_linear_units();
+ if (parser.seen('Z')) {
+ planner.max_jerk[Z_AXIS] = parser.value_linear_units();
+ #if HAS_MESH
+ if (planner.max_jerk[Z_AXIS] <= 0.1)
+ SERIAL_ECHOLNPGM("WARNING! Low Z Jerk may lead to unwanted pauses.");
+ #endif
+ }
+ if (parser.seen('E')) planner.max_jerk[E_AXIS] = parser.value_linear_units();
+}
+
+#if HAS_M206_COMMAND
+
+ /**
+ * M206: Set Additional Homing Offset (X Y Z). SCARA aliases T=X, P=Y
+ *
+ * *** @thinkyhead: I recommend deprecating M206 for SCARA in favor of M665.
+ * *** M206 for SCARA will remain enabled in 1.1.x for compatibility.
+ * *** In the next 1.2 release, it will simply be disabled by default.
+ */
+ inline void gcode_M206() {
+ LOOP_XYZ(i)
+ if (parser.seen(axis_codes[i]))
+ set_home_offset((AxisEnum)i, parser.value_linear_units());
+
+ #if ENABLED(MORGAN_SCARA)
+ if (parser.seen('T')) set_home_offset(A_AXIS, parser.value_float()); // Theta
+ if (parser.seen('P')) set_home_offset(B_AXIS, parser.value_float()); // Psi
+ #endif
+
+ report_current_position();
+ }
+
+#endif // HAS_M206_COMMAND
+
+#if ENABLED(DELTA)
+ /**
+ * M665: Set delta configurations
+ *
+ * H = delta height
+ * L = diagonal rod
+ * R = delta radius
+ * S = segments per second
+ * B = delta calibration radius
+ * X = Alpha (Tower 1) angle trim
+ * Y = Beta (Tower 2) angle trim
+ * Z = Rotate A and B by this angle
+ */
+ inline void gcode_M665() {
+ if (parser.seen('H')) delta_height = parser.value_linear_units();
+ if (parser.seen('L')) delta_diagonal_rod = parser.value_linear_units();
+ if (parser.seen('R')) delta_radius = parser.value_linear_units();
+ if (parser.seen('S')) delta_segments_per_second = parser.value_float();
+ if (parser.seen('B')) delta_calibration_radius = parser.value_float();
+ if (parser.seen('X')) delta_tower_angle_trim[A_AXIS] = parser.value_float();
+ if (parser.seen('Y')) delta_tower_angle_trim[B_AXIS] = parser.value_float();
+ if (parser.seen('Z')) delta_tower_angle_trim[C_AXIS] = parser.value_float();
+ recalc_delta_settings();
+ }
+ /**
+ * M666: Set delta endstop adjustment
+ */
+ inline void gcode_M666() {
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) {
+ SERIAL_ECHOLNPGM(">>> gcode_M666");
+ }
+ #endif
+ LOOP_XYZ(i) {
+ if (parser.seen(axis_codes[i])) {
+ if (parser.value_linear_units() * Z_HOME_DIR <= 0)
+ delta_endstop_adj[i] = parser.value_linear_units();
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) {
+ SERIAL_ECHOPAIR("delta_endstop_adj[", axis_codes[i]);
+ SERIAL_ECHOLNPAIR("] = ", delta_endstop_adj[i]);
+ }
+ #endif
+ }
+ }
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) {
+ SERIAL_ECHOLNPGM("<<< gcode_M666");
+ }
+ #endif
+ }
+
+#elif IS_SCARA
+
+ /**
+ * M665: Set SCARA settings
+ *
+ * Parameters:
+ *
+ * S[segments-per-second] - Segments-per-second
+ * P[theta-psi-offset] - Theta-Psi offset, added to the shoulder (A/X) angle
+ * T[theta-offset] - Theta offset, added to the elbow (B/Y) angle
+ *
+ * A, P, and X are all aliases for the shoulder angle
+ * B, T, and Y are all aliases for the elbow angle
+ */
+ inline void gcode_M665() {
+ if (parser.seen('S')) delta_segments_per_second = parser.value_float();
+
+ const bool hasA = parser.seen('A'), hasP = parser.seen('P'), hasX = parser.seen('X');
+ const uint8_t sumAPX = hasA + hasP + hasX;
+ if (sumAPX == 1)
+ home_offset[A_AXIS] = parser.value_float();
+ else if (sumAPX > 1) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM("Only one of A, P, or X is allowed.");
+ return;
+ }
+
+ const bool hasB = parser.seen('B'), hasT = parser.seen('T'), hasY = parser.seen('Y');
+ const uint8_t sumBTY = hasB + hasT + hasY;
+ if (sumBTY == 1)
+ home_offset[B_AXIS] = parser.value_float();
+ else if (sumBTY > 1) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM("Only one of B, T, or Y is allowed.");
+ return;
+ }
+ }
+
+#elif ENABLED(X_DUAL_ENDSTOPS) || ENABLED(Y_DUAL_ENDSTOPS) || ENABLED(Z_DUAL_ENDSTOPS)
+
+ /**
+ * M666: Set Dual Endstops offsets for X, Y, and/or Z.
+ * With no parameters report current offsets.
+ */
+ inline void gcode_M666() {
+ bool report = true;
+ #if ENABLED(X_DUAL_ENDSTOPS)
+ if (parser.seenval('X')) {
+ endstops.x_endstop_adj = parser.value_linear_units();
+ report = false;
+ }
+ #endif
+ #if ENABLED(Y_DUAL_ENDSTOPS)
+ if (parser.seenval('Y')) {
+ endstops.y_endstop_adj = parser.value_linear_units();
+ report = false;
+ }
+ #endif
+ #if ENABLED(Z_DUAL_ENDSTOPS)
+ if (parser.seenval('Z')) {
+ endstops.z_endstop_adj = parser.value_linear_units();
+ report = false;
+ }
+ #endif
+ if (report) {
+ SERIAL_ECHOPGM("Dual Endstop Adjustment (mm): ");
+ #if ENABLED(X_DUAL_ENDSTOPS)
+ SERIAL_ECHOPAIR(" X", endstops.x_endstop_adj);
+ #endif
+ #if ENABLED(Y_DUAL_ENDSTOPS)
+ SERIAL_ECHOPAIR(" Y", endstops.y_endstop_adj);
+ #endif
+ #if ENABLED(Z_DUAL_ENDSTOPS)
+ SERIAL_ECHOPAIR(" Z", endstops.z_endstop_adj);
+ #endif
+ SERIAL_EOL();
+ }
+ }
+
+#endif // X_DUAL_ENDSTOPS || Y_DUAL_ENDSTOPS || Z_DUAL_ENDSTOPS
+
+#if ENABLED(FWRETRACT)
+
+ /**
+ * M207: Set firmware retraction values
+ *
+ * S[+units] retract_length
+ * W[+units] swap_retract_length (multi-extruder)
+ * F[units/min] retract_feedrate_mm_s
+ * Z[units] retract_zlift
+ */
+ inline void gcode_M207() {
+ if (parser.seen('S')) fwretract.retract_length = parser.value_axis_units(E_AXIS);
+ if (parser.seen('F')) fwretract.retract_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS));
+ if (parser.seen('Z')) fwretract.retract_zlift = parser.value_linear_units();
+ if (parser.seen('W')) fwretract.swap_retract_length = parser.value_axis_units(E_AXIS);
+ }
+
+ /**
+ * M208: Set firmware un-retraction values
+ *
+ * S[+units] retract_recover_length (in addition to M207 S*)
+ * W[+units] swap_retract_recover_length (multi-extruder)
+ * F[units/min] retract_recover_feedrate_mm_s
+ * R[units/min] swap_retract_recover_feedrate_mm_s
+ */
+ inline void gcode_M208() {
+ if (parser.seen('S')) fwretract.retract_recover_length = parser.value_axis_units(E_AXIS);
+ if (parser.seen('F')) fwretract.retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS));
+ if (parser.seen('R')) fwretract.swap_retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS));
+ if (parser.seen('W')) fwretract.swap_retract_recover_length = parser.value_axis_units(E_AXIS);
+ }
+
+ /**
+ * M209: Enable automatic retract (M209 S1)
+ * For slicers that don't support G10/11, reversed extrude-only
+ * moves will be classified as retraction.
+ */
+ inline void gcode_M209() {
+ if (MIN_AUTORETRACT <= MAX_AUTORETRACT) {
+ if (parser.seen('S')) {
+ fwretract.autoretract_enabled = parser.value_bool();
+ for (uint8_t i = 0; i < EXTRUDERS; i++) fwretract.retracted[i] = false;
+ }
+ }
+ }
+
+#endif // FWRETRACT
+
+/**
+ * M211: Enable, Disable, and/or Report software endstops
+ *
+ * Usage: M211 S1 to enable, M211 S0 to disable, M211 alone for report
+ */
+inline void gcode_M211() {
+ SERIAL_ECHO_START();
+ #if HAS_SOFTWARE_ENDSTOPS
+ if (parser.seen('S')) soft_endstops_enabled = parser.value_bool();
+ SERIAL_ECHOPGM(MSG_SOFT_ENDSTOPS);
+ serialprintPGM(soft_endstops_enabled ? PSTR(MSG_ON) : PSTR(MSG_OFF));
+ #else
+ SERIAL_ECHOPGM(MSG_SOFT_ENDSTOPS);
+ SERIAL_ECHOPGM(MSG_OFF);
+ #endif
+ SERIAL_ECHOPGM(MSG_SOFT_MIN);
+ SERIAL_ECHOPAIR( MSG_X, LOGICAL_X_POSITION(soft_endstop_min[X_AXIS]));
+ SERIAL_ECHOPAIR(" " MSG_Y, LOGICAL_Y_POSITION(soft_endstop_min[Y_AXIS]));
+ SERIAL_ECHOPAIR(" " MSG_Z, LOGICAL_Z_POSITION(soft_endstop_min[Z_AXIS]));
+ SERIAL_ECHOPGM(MSG_SOFT_MAX);
+ SERIAL_ECHOPAIR( MSG_X, LOGICAL_X_POSITION(soft_endstop_max[X_AXIS]));
+ SERIAL_ECHOPAIR(" " MSG_Y, LOGICAL_Y_POSITION(soft_endstop_max[Y_AXIS]));
+ SERIAL_ECHOLNPAIR(" " MSG_Z, LOGICAL_Z_POSITION(soft_endstop_max[Z_AXIS]));
+}
+
+#if HOTENDS > 1
+
+ /**
+ * M218 - Set/get hotend offset (in linear units)
+ *
+ * T
+ * X
+ * Y
+ * Z - Available with DUAL_X_CARRIAGE and SWITCHING_NOZZLE
+ */
+ inline void gcode_M218() {
+ if (get_target_extruder_from_command(218) || target_extruder == 0) return;
+
+ bool report = true;
+ if (parser.seenval('X')) {
+ hotend_offset[X_AXIS][target_extruder] = parser.value_linear_units();
+ report = false;
+ }
+ if (parser.seenval('Y')) {
+ hotend_offset[Y_AXIS][target_extruder] = parser.value_linear_units();
+ report = false;
+ }
+
+ #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(SWITCHING_NOZZLE) || ENABLED(PARKING_EXTRUDER)
+ if (parser.seenval('Z')) {
+ hotend_offset[Z_AXIS][target_extruder] = parser.value_linear_units();
+ report = false;
+ }
+ #endif
+
+ if (report) {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM(MSG_HOTEND_OFFSET);
+ HOTEND_LOOP() {
+ SERIAL_CHAR(' ');
+ SERIAL_ECHO(hotend_offset[X_AXIS][e]);
+ SERIAL_CHAR(',');
+ SERIAL_ECHO(hotend_offset[Y_AXIS][e]);
+ #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(SWITCHING_NOZZLE) || ENABLED(PARKING_EXTRUDER)
+ SERIAL_CHAR(',');
+ SERIAL_ECHO(hotend_offset[Z_AXIS][e]);
+ #endif
+ }
+ SERIAL_EOL();
+ }
+
+ #if ENABLED(DELTA)
+ if (target_extruder == active_extruder)
+ do_blocking_move_to_xy(current_position[X_AXIS], current_position[Y_AXIS], planner.max_feedrate_mm_s[X_AXIS]);
+ #endif
+ }
+
+#endif // HOTENDS > 1
+
+/**
+ * M220: Set speed percentage factor, aka "Feed Rate" (M220 S95)
+ */
+inline void gcode_M220() {
+ if (parser.seenval('S')) feedrate_percentage = parser.value_int();
+}
+
+/**
+ * M221: Set extrusion percentage (M221 T0 S95)
+ */
+inline void gcode_M221() {
+ if (get_target_extruder_from_command(221)) return;
+ if (parser.seenval('S')) {
+ planner.flow_percentage[target_extruder] = parser.value_int();
+ planner.refresh_e_factor(target_extruder);
+ }
+}
+
+/**
+ * M226: Wait until the specified pin reaches the state required (M226 P S)
+ */
+inline void gcode_M226() {
+ if (parser.seen('P')) {
+ const int pin = parser.value_int(),
+ pin_state = parser.intval('S', -1); // required pin state - default is inverted
+
+ if (WITHIN(pin_state, -1, 1) && pin > -1 && !pin_is_protected(pin)) {
+
+ int target = LOW;
+
+ stepper.synchronize();
+
+ pinMode(pin, INPUT);
+ switch (pin_state) {
+ case 1:
+ target = HIGH;
+ break;
+ case 0:
+ target = LOW;
+ break;
+ case -1:
+ target = !digitalRead(pin);
+ break;
+ }
+
+ while (digitalRead(pin) != target) idle();
+
+ } // pin_state -1 0 1 && pin > -1
+ } // parser.seen('P')
+}
+
+#if ENABLED(EXPERIMENTAL_I2CBUS)
+
+ /**
+ * M260: Send data to a I2C slave device
+ *
+ * This is a PoC, the formating and arguments for the GCODE will
+ * change to be more compatible, the current proposal is:
+ *
+ * M260 A ; Sets the I2C slave address the data will be sent to
+ *
+ * M260 B
+ * M260 B
+ * M260 B
+ *
+ * M260 S1 ; Send the buffered data and reset the buffer
+ * M260 R1 ; Reset the buffer without sending data
+ *
+ */
+ inline void gcode_M260() {
+ // Set the target address
+ if (parser.seen('A')) i2c.address(parser.value_byte());
+
+ // Add a new byte to the buffer
+ if (parser.seen('B')) i2c.addbyte(parser.value_byte());
+
+ // Flush the buffer to the bus
+ if (parser.seen('S')) i2c.send();
+
+ // Reset and rewind the buffer
+ else if (parser.seen('R')) i2c.reset();
+ }
+
+ /**
+ * M261: Request X bytes from I2C slave device
+ *
+ * Usage: M261 A B
+ */
+ inline void gcode_M261() {
+ if (parser.seen('A')) i2c.address(parser.value_byte());
+
+ uint8_t bytes = parser.byteval('B', 1);
+
+ if (i2c.addr && bytes && bytes <= TWIBUS_BUFFER_SIZE) {
+ i2c.relay(bytes);
+ }
+ else {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM("Bad i2c request");
+ }
+ }
+
+#endif // EXPERIMENTAL_I2CBUS
+
+#if HAS_SERVOS
+
+ /**
+ * M280: Get or set servo position. P [S]
+ */
+ inline void gcode_M280() {
+ if (!parser.seen('P')) return;
+ const int servo_index = parser.value_int();
+ if (WITHIN(servo_index, 0, NUM_SERVOS - 1)) {
+ if (parser.seen('S'))
+ MOVE_SERVO(servo_index, parser.value_int());
+ else {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPAIR(" Servo ", servo_index);
+ SERIAL_ECHOLNPAIR(": ", servo[servo_index].read());
+ }
+ }
+ else {
+ SERIAL_ERROR_START();
+ SERIAL_ECHOPAIR("Servo ", servo_index);
+ SERIAL_ECHOLNPGM(" out of range");
+ }
+ }
+
+#endif // HAS_SERVOS
+
+#if ENABLED(BABYSTEPPING)
+
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ FORCE_INLINE void mod_zprobe_zoffset(const float &offs) {
+ zprobe_zoffset += offs;
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR(MSG_PROBE_Z_OFFSET ": ", zprobe_zoffset);
+ }
+ #endif
+
+ /**
+ * M290: Babystepping
+ */
+ inline void gcode_M290() {
+ #if ENABLED(BABYSTEP_XY)
+ for (uint8_t a = X_AXIS; a <= Z_AXIS; a++)
+ if (parser.seenval(axis_codes[a]) || (a == Z_AXIS && parser.seenval('S'))) {
+ const float offs = constrain(parser.value_axis_units((AxisEnum)a), -2, 2);
+ thermalManager.babystep_axis((AxisEnum)a, offs * planner.axis_steps_per_mm[a]);
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ if (a == Z_AXIS && (!parser.seen('P') || parser.value_bool())) mod_zprobe_zoffset(offs);
+ #endif
+ }
+ #else
+ if (parser.seenval('Z') || parser.seenval('S')) {
+ const float offs = constrain(parser.value_axis_units(Z_AXIS), -2, 2);
+ thermalManager.babystep_axis(Z_AXIS, offs * planner.axis_steps_per_mm[Z_AXIS]);
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ if (!parser.seen('P') || parser.value_bool()) mod_zprobe_zoffset(offs);
+ #endif
+ }
+ #endif
+ }
+
+#endif // BABYSTEPPING
+
+#if HAS_BUZZER
+
+ /**
+ * M300: Play beep sound S P
+ */
+ inline void gcode_M300() {
+ uint16_t const frequency = parser.ushortval('S', 260);
+ uint16_t duration = parser.ushortval('P', 1000);
+
+ // Limits the tone duration to 0-5 seconds.
+ NOMORE(duration, 5000);
+
+ BUZZ(duration, frequency);
+ }
+
+#endif // HAS_BUZZER
+
+#if ENABLED(PIDTEMP)
+
+ /**
+ * M301: Set PID parameters P I D (and optionally C, L)
+ *
+ * P[float] Kp term
+ * I[float] Ki term (unscaled)
+ * D[float] Kd term (unscaled)
+ *
+ * With PID_EXTRUSION_SCALING:
+ *
+ * C[float] Kc term
+ * L[float] LPQ length
+ */
+ inline void gcode_M301() {
+
+ // multi-extruder PID patch: M301 updates or prints a single extruder's PID values
+ // default behaviour (omitting E parameter) is to update for extruder 0 only
+ const uint8_t e = parser.byteval('E'); // extruder being updated
+
+ if (e < HOTENDS) { // catch bad input value
+ if (parser.seen('P')) PID_PARAM(Kp, e) = parser.value_float();
+ if (parser.seen('I')) PID_PARAM(Ki, e) = scalePID_i(parser.value_float());
+ if (parser.seen('D')) PID_PARAM(Kd, e) = scalePID_d(parser.value_float());
+ #if ENABLED(PID_EXTRUSION_SCALING)
+ if (parser.seen('C')) PID_PARAM(Kc, e) = parser.value_float();
+ if (parser.seen('L')) lpq_len = parser.value_float();
+ NOMORE(lpq_len, LPQ_MAX_LEN);
+ #endif
+
+ thermalManager.updatePID();
+ SERIAL_ECHO_START();
+ #if ENABLED(PID_PARAMS_PER_HOTEND)
+ SERIAL_ECHOPAIR(" e:", e); // specify extruder in serial output
+ #endif // PID_PARAMS_PER_HOTEND
+ SERIAL_ECHOPAIR(" p:", PID_PARAM(Kp, e));
+ SERIAL_ECHOPAIR(" i:", unscalePID_i(PID_PARAM(Ki, e)));
+ SERIAL_ECHOPAIR(" d:", unscalePID_d(PID_PARAM(Kd, e)));
+ #if ENABLED(PID_EXTRUSION_SCALING)
+ //Kc does not have scaling applied above, or in resetting defaults
+ SERIAL_ECHOPAIR(" c:", PID_PARAM(Kc, e));
+ #endif
+ SERIAL_EOL();
+ }
+ else {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_INVALID_EXTRUDER);
+ }
+ }
+
+#endif // PIDTEMP
+
+#if ENABLED(PIDTEMPBED)
+
+ inline void gcode_M304() {
+ if (parser.seen('P')) thermalManager.bedKp = parser.value_float();
+ if (parser.seen('I')) thermalManager.bedKi = scalePID_i(parser.value_float());
+ if (parser.seen('D')) thermalManager.bedKd = scalePID_d(parser.value_float());
+
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPAIR(" p:", thermalManager.bedKp);
+ SERIAL_ECHOPAIR(" i:", unscalePID_i(thermalManager.bedKi));
+ SERIAL_ECHOLNPAIR(" d:", unscalePID_d(thermalManager.bedKd));
+ }
+
+#endif // PIDTEMPBED
+
+#if defined(CHDK) || HAS_PHOTOGRAPH
+
+ /**
+ * M240: Trigger a camera by emulating a Canon RC-1
+ * See http://www.doc-diy.net/photo/rc-1_hacked/
+ */
+ inline void gcode_M240() {
+ #ifdef CHDK
+
+ OUT_WRITE(CHDK, HIGH);
+ chdkHigh = millis();
+ chdkActive = true;
+
+ #elif HAS_PHOTOGRAPH
+
+ const uint8_t NUM_PULSES = 16;
+ const float PULSE_LENGTH = 0.01524;
+ for (int i = 0; i < NUM_PULSES; i++) {
+ WRITE(PHOTOGRAPH_PIN, HIGH);
+ _delay_ms(PULSE_LENGTH);
+ WRITE(PHOTOGRAPH_PIN, LOW);
+ _delay_ms(PULSE_LENGTH);
+ }
+ delay(7.33);
+ for (int i = 0; i < NUM_PULSES; i++) {
+ WRITE(PHOTOGRAPH_PIN, HIGH);
+ _delay_ms(PULSE_LENGTH);
+ WRITE(PHOTOGRAPH_PIN, LOW);
+ _delay_ms(PULSE_LENGTH);
+ }
+
+ #endif // !CHDK && HAS_PHOTOGRAPH
+ }
+
+#endif // CHDK || PHOTOGRAPH_PIN
+
+#if HAS_LCD_CONTRAST
+
+ /**
+ * M250: Read and optionally set the LCD contrast
+ */
+ inline void gcode_M250() {
+ if (parser.seen('C')) set_lcd_contrast(parser.value_int());
+ SERIAL_PROTOCOLPGM("lcd contrast value: ");
+ SERIAL_PROTOCOL(lcd_contrast);
+ SERIAL_EOL();
+ }
+
+#endif // HAS_LCD_CONTRAST
+
+#if ENABLED(PREVENT_COLD_EXTRUSION)
+
+ /**
+ * M302: Allow cold extrudes, or set the minimum extrude temperature
+ *
+ * S sets the minimum extrude temperature
+ * P enables (1) or disables (0) cold extrusion
+ *
+ * Examples:
+ *
+ * M302 ; report current cold extrusion state
+ * M302 P0 ; enable cold extrusion checking
+ * M302 P1 ; disables cold extrusion checking
+ * M302 S0 ; always allow extrusion (disables checking)
+ * M302 S170 ; only allow extrusion above 170
+ * M302 S170 P1 ; set min extrude temp to 170 but leave disabled
+ */
+ inline void gcode_M302() {
+ const bool seen_S = parser.seen('S');
+ if (seen_S) {
+ thermalManager.extrude_min_temp = parser.value_celsius();
+ thermalManager.allow_cold_extrude = (thermalManager.extrude_min_temp == 0);
+ }
+
+ if (parser.seen('P'))
+ thermalManager.allow_cold_extrude = (thermalManager.extrude_min_temp == 0) || parser.value_bool();
+ else if (!seen_S) {
+ // Report current state
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPAIR("Cold extrudes are ", (thermalManager.allow_cold_extrude ? "en" : "dis"));
+ SERIAL_ECHOPAIR("abled (min temp ", thermalManager.extrude_min_temp);
+ SERIAL_ECHOLNPGM("C)");
+ }
+ }
+
+#endif // PREVENT_COLD_EXTRUSION
+
+/**
+ * M303: PID relay autotune
+ *
+ * S sets the target temperature. (default 150C / 70C)
+ * E (-1 for the bed) (default 0)
+ * C
+ * U with a non-zero value will apply the result to current settings
+ */
+inline void gcode_M303() {
+ #if HAS_PID_HEATING
+ const int e = parser.intval('E'), c = parser.intval('C', 5);
+ const bool u = parser.boolval('U');
+
+ int16_t temp = parser.celsiusval('S', e < 0 ? 70 : 150);
+
+ if (WITHIN(e, 0, HOTENDS - 1))
+ target_extruder = e;
+
+ #if DISABLED(BUSY_WHILE_HEATING)
+ KEEPALIVE_STATE(NOT_BUSY);
+ #endif
+
+ thermalManager.PID_autotune(temp, e, c, u);
+
+ #if DISABLED(BUSY_WHILE_HEATING)
+ KEEPALIVE_STATE(IN_HANDLER);
+ #endif
+ #else
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_M303_DISABLED);
+ #endif
+}
+
+#if ENABLED(MORGAN_SCARA)
+
+ bool SCARA_move_to_cal(const uint8_t delta_a, const uint8_t delta_b) {
+ if (IsRunning()) {
+ forward_kinematics_SCARA(delta_a, delta_b);
+ destination[X_AXIS] = cartes[X_AXIS];
+ destination[Y_AXIS] = cartes[Y_AXIS];
+ destination[Z_AXIS] = current_position[Z_AXIS];
+ prepare_move_to_destination();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * M360: SCARA calibration: Move to cal-position ThetaA (0 deg calibration)
+ */
+ inline bool gcode_M360() {
+ SERIAL_ECHOLNPGM(" Cal: Theta 0");
+ return SCARA_move_to_cal(0, 120);
+ }
+
+ /**
+ * M361: SCARA calibration: Move to cal-position ThetaB (90 deg calibration - steps per degree)
+ */
+ inline bool gcode_M361() {
+ SERIAL_ECHOLNPGM(" Cal: Theta 90");
+ return SCARA_move_to_cal(90, 130);
+ }
+
+ /**
+ * M362: SCARA calibration: Move to cal-position PsiA (0 deg calibration)
+ */
+ inline bool gcode_M362() {
+ SERIAL_ECHOLNPGM(" Cal: Psi 0");
+ return SCARA_move_to_cal(60, 180);
+ }
+
+ /**
+ * M363: SCARA calibration: Move to cal-position PsiB (90 deg calibration - steps per degree)
+ */
+ inline bool gcode_M363() {
+ SERIAL_ECHOLNPGM(" Cal: Psi 90");
+ return SCARA_move_to_cal(50, 90);
+ }
+
+ /**
+ * M364: SCARA calibration: Move to cal-position PsiC (90 deg to Theta calibration position)
+ */
+ inline bool gcode_M364() {
+ SERIAL_ECHOLNPGM(" Cal: Theta-Psi 90");
+ return SCARA_move_to_cal(45, 135);
+ }
+
+#endif // SCARA
+
+#if ENABLED(EXT_SOLENOID)
+
+ void enable_solenoid(const uint8_t num) {
+ switch (num) {
+ case 0:
+ OUT_WRITE(SOL0_PIN, HIGH);
+ break;
+ #if HAS_SOLENOID_1 && EXTRUDERS > 1
+ case 1:
+ OUT_WRITE(SOL1_PIN, HIGH);
+ break;
+ #endif
+ #if HAS_SOLENOID_2 && EXTRUDERS > 2
+ case 2:
+ OUT_WRITE(SOL2_PIN, HIGH);
+ break;
+ #endif
+ #if HAS_SOLENOID_3 && EXTRUDERS > 3
+ case 3:
+ OUT_WRITE(SOL3_PIN, HIGH);
+ break;
+ #endif
+ #if HAS_SOLENOID_4 && EXTRUDERS > 4
+ case 4:
+ OUT_WRITE(SOL4_PIN, HIGH);
+ break;
+ #endif
+ default:
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPGM(MSG_INVALID_SOLENOID);
+ break;
+ }
+ }
+
+ void enable_solenoid_on_active_extruder() { enable_solenoid(active_extruder); }
+
+ void disable_all_solenoids() {
+ OUT_WRITE(SOL0_PIN, LOW);
+ #if HAS_SOLENOID_1 && EXTRUDERS > 1
+ OUT_WRITE(SOL1_PIN, LOW);
+ #endif
+ #if HAS_SOLENOID_2 && EXTRUDERS > 2
+ OUT_WRITE(SOL2_PIN, LOW);
+ #endif
+ #if HAS_SOLENOID_3 && EXTRUDERS > 3
+ OUT_WRITE(SOL3_PIN, LOW);
+ #endif
+ #if HAS_SOLENOID_4 && EXTRUDERS > 4
+ OUT_WRITE(SOL4_PIN, LOW);
+ #endif
+ }
+
+ /**
+ * M380: Enable solenoid on the active extruder
+ */
+ inline void gcode_M380() { enable_solenoid_on_active_extruder(); }
+
+ /**
+ * M381: Disable all solenoids
+ */
+ inline void gcode_M381() { disable_all_solenoids(); }
+
+#endif // EXT_SOLENOID
+
+/**
+ * M400: Finish all moves
+ */
+inline void gcode_M400() { stepper.synchronize(); }
+
+#if HAS_BED_PROBE
+
+ /**
+ * M401: Deploy and activate the Z probe
+ */
+ inline void gcode_M401() {
+ DEPLOY_PROBE();
+ report_current_position();
+ }
+
+ /**
+ * M402: Deactivate and stow the Z probe
+ */
+ inline void gcode_M402() {
+ STOW_PROBE();
+ #if Z_AFTER_PROBING
+ move_z_after_probing();
+ #endif
+ report_current_position();
+ }
+
+#endif // HAS_BED_PROBE
+
+#if ENABLED(FILAMENT_WIDTH_SENSOR)
+
+ /**
+ * M404: Display or set (in current units) the nominal filament width (3mm, 1.75mm ) W<3.0>
+ */
+ inline void gcode_M404() {
+ if (parser.seen('W')) {
+ filament_width_nominal = parser.value_linear_units();
+ planner.volumetric_area_nominal = CIRCLE_AREA(filament_width_nominal * 0.5);
+ }
+ else {
+ SERIAL_PROTOCOLPGM("Filament dia (nominal mm):");
+ SERIAL_PROTOCOLLN(filament_width_nominal);
+ }
+ }
+
+ /**
+ * M405: Turn on filament sensor for control
+ */
+ inline void gcode_M405() {
+ // This is technically a linear measurement, but since it's quantized to centimeters and is a different
+ // unit than everything else, it uses parser.value_byte() instead of parser.value_linear_units().
+ if (parser.seen('D')) {
+ meas_delay_cm = parser.value_byte();
+ NOMORE(meas_delay_cm, MAX_MEASUREMENT_DELAY);
+ }
+
+ if (filwidth_delay_index[1] == -1) { // Initialize the ring buffer if not done since startup
+ const int8_t temp_ratio = thermalManager.widthFil_to_size_ratio();
+
+ for (uint8_t i = 0; i < COUNT(measurement_delay); ++i)
+ measurement_delay[i] = temp_ratio;
+
+ filwidth_delay_index[0] = filwidth_delay_index[1] = 0;
+ }
+
+ filament_sensor = true;
+ }
+
+ /**
+ * M406: Turn off filament sensor for control
+ */
+ inline void gcode_M406() {
+ filament_sensor = false;
+ planner.calculate_volumetric_multipliers(); // Restore correct 'volumetric_multiplier' value
+ }
+
+ /**
+ * M407: Get measured filament diameter on serial output
+ */
+ inline void gcode_M407() {
+ SERIAL_PROTOCOLPGM("Filament dia (measured mm):");
+ SERIAL_PROTOCOLLN(filament_width_meas);
+ }
+
+#endif // FILAMENT_WIDTH_SENSOR
+
+void quickstop_stepper() {
+ stepper.quick_stop();
+ stepper.synchronize();
+ set_current_from_steppers_for_axis(ALL_AXES);
+ SYNC_PLAN_POSITION_KINEMATIC();
+}
+
+#if HAS_LEVELING
+ /**
+ * M420: Enable/Disable Bed Leveling and/or set the Z fade height.
+ *
+ * S[bool] Turns leveling on or off
+ * Z[height] Sets the Z fade height (0 or none to disable)
+ * V[bool] Verbose - Print the leveling grid
+ *
+ * With AUTO_BED_LEVELING_UBL only:
+ *
+ * L[index] Load UBL mesh from index (0 is default)
+ */
+ inline void gcode_M420() {
+
+ const float oldpos[] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] };
+
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+
+ // L to load a mesh from the EEPROM
+ if (parser.seen('L')) {
+
+ #if ENABLED(EEPROM_SETTINGS)
+ const int8_t storage_slot = parser.has_value() ? parser.value_int() : ubl.storage_slot;
+ const int16_t a = settings.calc_num_meshes();
+
+ if (!a) {
+ SERIAL_PROTOCOLLNPGM("?EEPROM storage not available.");
+ return;
+ }
+
+ if (!WITHIN(storage_slot, 0, a - 1)) {
+ SERIAL_PROTOCOLLNPGM("?Invalid storage slot.");
+ SERIAL_PROTOCOLLNPAIR("?Use 0 to ", a - 1);
+ return;
+ }
+
+ settings.load_mesh(storage_slot);
+ ubl.storage_slot = storage_slot;
+
+ #else
+
+ SERIAL_PROTOCOLLNPGM("?EEPROM storage not available.");
+ return;
+
+ #endif
+ }
+
+ // L to load a mesh from the EEPROM
+ if (parser.seen('L') || parser.seen('V')) {
+ ubl.display_map(0); // Currently only supports one map type
+ SERIAL_ECHOLNPAIR("ubl.mesh_is_valid = ", ubl.mesh_is_valid());
+ SERIAL_ECHOLNPAIR("ubl.storage_slot = ", ubl.storage_slot);
+ }
+
+ #endif // AUTO_BED_LEVELING_UBL
+
+ // V to print the matrix or mesh
+ if (parser.seen('V')) {
+ #if ABL_PLANAR
+ planner.bed_level_matrix.debug(PSTR("Bed Level Correction Matrix:"));
+ #else
+ if (leveling_is_valid()) {
+ #if ENABLED(AUTO_BED_LEVELING_BILINEAR)
+ print_bilinear_leveling_grid();
+ #if ENABLED(ABL_BILINEAR_SUBDIVISION)
+ print_bilinear_leveling_grid_virt();
+ #endif
+ #elif ENABLED(MESH_BED_LEVELING)
+ SERIAL_ECHOLNPGM("Mesh Bed Level data:");
+ mbl.report_mesh();
+ #endif
+ }
+ #endif
+ }
+
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ if (parser.seen('Z')) set_z_fade_height(parser.value_linear_units(), false);
+ #endif
+
+ bool to_enable = false;
+ if (parser.seen('S')) {
+ to_enable = parser.value_bool();
+ set_bed_leveling_enabled(to_enable);
+ }
+
+ const bool new_status = planner.leveling_active;
+
+ if (to_enable && !new_status) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_M420_FAILED);
+ }
+
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR("Bed Leveling ", new_status ? MSG_ON : MSG_OFF);
+
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM("Fade Height ");
+ if (planner.z_fade_height > 0.0)
+ SERIAL_ECHOLN(planner.z_fade_height);
+ else
+ SERIAL_ECHOLNPGM(MSG_OFF);
+ #endif
+
+ // Report change in position
+ if (memcmp(oldpos, current_position, sizeof(oldpos)))
+ report_current_position();
+ }
+#endif
+
+#if ENABLED(MESH_BED_LEVELING)
+
+ /**
+ * M421: Set a single Mesh Bed Leveling Z coordinate
+ *
+ * Usage:
+ * M421 X Y Z
+ * M421 X Y Q
+ * M421 I J Z
+ * M421 I J Q
+ */
+ inline void gcode_M421() {
+ const bool hasX = parser.seen('X'), hasI = parser.seen('I');
+ const int8_t ix = hasI ? parser.value_int() : hasX ? mbl.probe_index_x(parser.value_linear_units()) : -1;
+ const bool hasY = parser.seen('Y'), hasJ = parser.seen('J');
+ const int8_t iy = hasJ ? parser.value_int() : hasY ? mbl.probe_index_y(parser.value_linear_units()) : -1;
+ const bool hasZ = parser.seen('Z'), hasQ = !hasZ && parser.seen('Q');
+
+ if (int(hasI && hasJ) + int(hasX && hasY) != 1 || !(hasZ || hasQ)) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_M421_PARAMETERS);
+ }
+ else if (ix < 0 || iy < 0) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_MESH_XY);
+ }
+ else
+ mbl.set_z(ix, iy, parser.value_linear_units() + (hasQ ? mbl.z_values[ix][iy] : 0));
+ }
+
+#elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
+
+ /**
+ * M421: Set a single Mesh Bed Leveling Z coordinate
+ *
+ * Usage:
+ * M421 I J Z
+ * M421 I J Q
+ */
+ inline void gcode_M421() {
+ int8_t ix = parser.intval('I', -1), iy = parser.intval('J', -1);
+ const bool hasI = ix >= 0,
+ hasJ = iy >= 0,
+ hasZ = parser.seen('Z'),
+ hasQ = !hasZ && parser.seen('Q');
+
+ if (!hasI || !hasJ || !(hasZ || hasQ)) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_M421_PARAMETERS);
+ }
+ else if (!WITHIN(ix, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1)) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_MESH_XY);
+ }
+ else {
+ z_values[ix][iy] = parser.value_linear_units() + (hasQ ? z_values[ix][iy] : 0);
+ #if ENABLED(ABL_BILINEAR_SUBDIVISION)
+ bed_level_virt_interpolate();
+ #endif
+ }
+ }
+
+#elif ENABLED(AUTO_BED_LEVELING_UBL)
+
+ /**
+ * M421: Set a single Mesh Bed Leveling Z coordinate
+ *
+ * Usage:
+ * M421 I J Z
+ * M421 I J Q
+ * M421 C Z
+ * M421 C Q
+ */
+ inline void gcode_M421() {
+ int8_t ix = parser.intval('I', -1), iy = parser.intval('J', -1);
+ const bool hasI = ix >= 0,
+ hasJ = iy >= 0,
+ hasC = parser.seen('C'),
+ hasZ = parser.seen('Z'),
+ hasQ = !hasZ && parser.seen('Q');
+
+ if (hasC) {
+ const mesh_index_pair location = ubl.find_closest_mesh_point_of_type(REAL, current_position[X_AXIS], current_position[Y_AXIS], USE_NOZZLE_AS_REFERENCE, NULL);
+ ix = location.x_index;
+ iy = location.y_index;
+ }
+
+ if (int(hasC) + int(hasI && hasJ) != 1 || !(hasZ || hasQ)) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_M421_PARAMETERS);
+ }
+ else if (!WITHIN(ix, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1)) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_MESH_XY);
+ }
+ else
+ ubl.z_values[ix][iy] = parser.value_linear_units() + (hasQ ? ubl.z_values[ix][iy] : 0);
+ }
+
+#endif // AUTO_BED_LEVELING_UBL
+
+#if HAS_M206_COMMAND
+
+ /**
+ * M428: Set home_offset based on the distance between the
+ * current_position and the nearest "reference point."
+ * If an axis is past center its endstop position
+ * is the reference-point. Otherwise it uses 0. This allows
+ * the Z offset to be set near the bed when using a max endstop.
+ *
+ * M428 can't be used more than 2cm away from 0 or an endstop.
+ *
+ * Use M206 to set these values directly.
+ */
+ inline void gcode_M428() {
+ if (axis_unhomed_error()) return;
+
+ float diff[XYZ];
+ LOOP_XYZ(i) {
+ diff[i] = base_home_pos((AxisEnum)i) - current_position[i];
+ if (!WITHIN(diff[i], -20, 20) && home_dir((AxisEnum)i) > 0)
+ diff[i] = -current_position[i];
+ if (!WITHIN(diff[i], -20, 20)) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_M428_TOO_FAR);
+ LCD_ALERTMESSAGEPGM("Err: Too far!");
+ BUZZ(200, 40);
+ return;
+ }
+ }
+
+ LOOP_XYZ(i) set_home_offset((AxisEnum)i, diff[i]);
+ report_current_position();
+ LCD_MESSAGEPGM(MSG_HOME_OFFSETS_APPLIED);
+ BUZZ(100, 659);
+ BUZZ(100, 698);
+ }
+
+#endif // HAS_M206_COMMAND
+
+/**
+ * M500: Store settings in EEPROM
+ */
+inline void gcode_M500() {
+ (void)settings.save();
+}
+
+/**
+ * M501: Read settings from EEPROM
+ */
+inline void gcode_M501() {
+ (void)settings.load();
+}
+
+/**
+ * M502: Revert to default settings
+ */
+inline void gcode_M502() {
+ (void)settings.reset();
+}
+
+#if DISABLED(DISABLE_M503)
+ /**
+ * M503: print settings currently in memory
+ */
+ inline void gcode_M503() {
+ (void)settings.report(parser.seen('S') && !parser.value_bool());
+ }
+#endif
+
+#if ENABLED(EEPROM_SETTINGS)
+ /**
+ * M504: Validate EEPROM Contents
+ */
+ inline void gcode_M504() {
+ if (settings.validate()) {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPGM("EEPROM OK");
+ }
+ }
+#endif
+
+#if ENABLED(ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED)
+
+ /**
+ * M540: Set whether SD card print should abort on endstop hit (M540 S<0|1>)
+ */
+ inline void gcode_M540() {
+ if (parser.seen('S')) stepper.abort_on_endstop_hit = parser.value_bool();
+ }
+
+#endif // ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED
+
+#if HAS_BED_PROBE
+
+ inline void gcode_M851() {
+ if (parser.seenval('Z')) {
+ const float value = parser.value_linear_units();
+ if (WITHIN(value, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX))
+ zprobe_zoffset = value;
+ else {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM("?Z out of range (" STRINGIFY(Z_PROBE_OFFSET_RANGE_MIN) " to " STRINGIFY(Z_PROBE_OFFSET_RANGE_MAX) ")");
+ }
+ return;
+ }
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM(MSG_PROBE_Z_OFFSET);
+ SERIAL_ECHOLNPAIR(": ", zprobe_zoffset);
+ }
+
+#endif // HAS_BED_PROBE
+
+#if ENABLED(SKEW_CORRECTION_GCODE)
+
+ /**
+ * M852: Get or set the machine skew factors. Reports current values with no arguments.
+ *
+ * S[xy_factor] - Alias for 'I'
+ * I[xy_factor] - New XY skew factor
+ * J[xz_factor] - New XZ skew factor
+ * K[yz_factor] - New YZ skew factor
+ */
+ inline void gcode_M852() {
+ uint8_t ijk = 0, badval = 0, setval = 0;
+
+ if (parser.seen('I') || parser.seen('S')) {
+ ++ijk;
+ const float value = parser.value_linear_units();
+ if (WITHIN(value, SKEW_FACTOR_MIN, SKEW_FACTOR_MAX)) {
+ if (planner.xy_skew_factor != value) {
+ planner.xy_skew_factor = value;
+ ++setval;
+ }
+ }
+ else
+ ++badval;
+ }
+
+ #if ENABLED(SKEW_CORRECTION_FOR_Z)
+
+ if (parser.seen('J')) {
+ ++ijk;
+ const float value = parser.value_linear_units();
+ if (WITHIN(value, SKEW_FACTOR_MIN, SKEW_FACTOR_MAX)) {
+ if (planner.xz_skew_factor != value) {
+ planner.xz_skew_factor = value;
+ ++setval;
+ }
+ }
+ else
+ ++badval;
+ }
+
+ if (parser.seen('K')) {
+ ++ijk;
+ const float value = parser.value_linear_units();
+ if (WITHIN(value, SKEW_FACTOR_MIN, SKEW_FACTOR_MAX)) {
+ if (planner.yz_skew_factor != value) {
+ planner.yz_skew_factor = value;
+ ++setval;
+ }
+ }
+ else
+ ++badval;
+ }
+
+ #endif
+
+ if (badval)
+ SERIAL_ECHOLNPGM(MSG_SKEW_MIN " " STRINGIFY(SKEW_FACTOR_MIN) " " MSG_SKEW_MAX " " STRINGIFY(SKEW_FACTOR_MAX));
+
+ // When skew is changed the current position changes
+ if (setval) {
+ set_current_from_steppers_for_axis(ALL_AXES);
+ SYNC_PLAN_POSITION_KINEMATIC();
+ report_current_position();
+ }
+
+ if (!ijk) {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM(MSG_SKEW_FACTOR " XY: ");
+ SERIAL_ECHO_F(planner.xy_skew_factor, 6);
+ SERIAL_EOL();
+ #if ENABLED(SKEW_CORRECTION_FOR_Z)
+ SERIAL_ECHOPAIR(" XZ: ", planner.xz_skew_factor);
+ SERIAL_ECHOLNPAIR(" YZ: ", planner.yz_skew_factor);
+ #else
+ SERIAL_EOL();
+ #endif
+ }
+ }
+
+#endif // SKEW_CORRECTION_GCODE
+
+#if ENABLED(ADVANCED_PAUSE_FEATURE)
+
+ /**
+ * M600: Pause for filament change
+ *
+ * E[distance] - Retract the filament this far
+ * Z[distance] - Move the Z axis by this distance
+ * X[position] - Move to this X position, with Y
+ * Y[position] - Move to this Y position, with X
+ * U[distance] - Retract distance for removal (manual reload)
+ * L[distance] - Extrude distance for insertion (manual reload)
+ * B[count] - Number of times to beep, -1 for indefinite (if equipped with a buzzer)
+ * T[toolhead] - Select extruder for filament change
+ *
+ * Default values are used for omitted arguments.
+ */
+ inline void gcode_M600() {
+ point_t park_point = NOZZLE_PARK_POINT;
+
+ if (get_target_extruder_from_command(600)) return;
+
+ // Show initial message
+ #if ENABLED(ULTIPANEL)
+ lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_INIT, ADVANCED_PAUSE_MODE_PAUSE_PRINT, target_extruder);
+ #endif
+
+ #if ENABLED(HOME_BEFORE_FILAMENT_CHANGE)
+ // Don't allow filament change without homing first
+ if (axis_unhomed_error()) home_all_axes();
+ #endif
+
+ #if EXTRUDERS > 1
+ // Change toolhead if specified
+ uint8_t active_extruder_before_filament_change = active_extruder;
+ if (active_extruder != target_extruder)
+ tool_change(target_extruder, 0, true);
+ #endif
+
+ // Initial retract before move to filament change position
+ const float retract = -FABS(parser.seen('E') ? parser.value_axis_units(E_AXIS) : 0
+ #ifdef PAUSE_PARK_RETRACT_LENGTH
+ + (PAUSE_PARK_RETRACT_LENGTH)
+ #endif
+ );
+
+ // Lift Z axis
+ if (parser.seenval('Z')) park_point.z = parser.linearval('Z');
+
+ // Move XY axes to filament change position or given position
+ if (parser.seenval('X')) park_point.x = parser.linearval('X');
+ if (parser.seenval('Y')) park_point.y = parser.linearval('Y');
+
+ #if HOTENDS > 1 && DISABLED(DUAL_X_CARRIAGE) && DISABLED(DELTA)
+ park_point.x += (active_extruder ? hotend_offset[X_AXIS][active_extruder] : 0);
+ park_point.y += (active_extruder ? hotend_offset[Y_AXIS][active_extruder] : 0);
+ #endif
+
+ // Unload filament
+ const float unload_length = -FABS(parser.seen('U') ? parser.value_axis_units(E_AXIS) :
+ filament_change_unload_length[active_extruder]);
+
+ // Load filament
+ const float load_length = FABS(parser.seen('L') ? parser.value_axis_units(E_AXIS) :
+ filament_change_load_length[active_extruder]);
+
+ const int beep_count = parser.intval('B',
+ #ifdef FILAMENT_CHANGE_ALERT_BEEPS
+ FILAMENT_CHANGE_ALERT_BEEPS
+ #else
+ -1
+ #endif
+ );
+
+ const bool job_running = print_job_timer.isRunning();
+
+ if (pause_print(retract, park_point, unload_length, true)) {
+ wait_for_filament_reload(beep_count);
+ resume_print(load_length, ADVANCED_PAUSE_EXTRUDE_LENGTH, beep_count);
+ }
+
+ #if EXTRUDERS > 1
+ // Restore toolhead if it was changed
+ if (active_extruder_before_filament_change != active_extruder)
+ tool_change(active_extruder_before_filament_change, 0, true);
+ #endif
+
+ // Resume the print job timer if it was running
+ if (job_running) print_job_timer.start();
+ }
+
+ /**
+ * M603: Configure filament change
+ *
+ * T[toolhead] - Select extruder to configure, active extruder if not specified
+ * U[distance] - Retract distance for removal, for the specified extruder
+ * L[distance] - Extrude distance for insertion, for the specified extruder
+ *
+ */
+ inline void gcode_M603() {
+
+ if (get_target_extruder_from_command(603)) return;
+
+ // Unload length
+ if (parser.seen('U')) {
+ filament_change_unload_length[target_extruder] = FABS(parser.value_axis_units(E_AXIS));
+ #if ENABLED(PREVENT_LENGTHY_EXTRUDE)
+ NOMORE(filament_change_unload_length[target_extruder], EXTRUDE_MAXLENGTH);
+ #endif
+ }
+
+ // Load length
+ if (parser.seen('L')) {
+ filament_change_load_length[target_extruder] = FABS(parser.value_axis_units(E_AXIS));
+ #if ENABLED(PREVENT_LENGTHY_EXTRUDE)
+ NOMORE(filament_change_load_length[target_extruder], EXTRUDE_MAXLENGTH);
+ #endif
+ }
+ }
+
+#endif // ADVANCED_PAUSE_FEATURE
+
+#if ENABLED(MK2_MULTIPLEXER)
+
+ inline void select_multiplexed_stepper(const uint8_t e) {
+ stepper.synchronize();
+ disable_e_steppers();
+ WRITE(E_MUX0_PIN, TEST(e, 0) ? HIGH : LOW);
+ WRITE(E_MUX1_PIN, TEST(e, 1) ? HIGH : LOW);
+ WRITE(E_MUX2_PIN, TEST(e, 2) ? HIGH : LOW);
+ safe_delay(100);
+ }
+
+#endif // MK2_MULTIPLEXER
+
+#if ENABLED(DUAL_X_CARRIAGE)
+
+ /**
+ * M605: Set dual x-carriage movement mode
+ *
+ * M605 S0: Full control mode. The slicer has full control over x-carriage movement
+ * M605 S1: Auto-park mode. The inactive head will auto park/unpark without slicer involvement
+ * M605 S2 [Xnnn] [Rmmm]: Duplication mode. The second extruder will duplicate the first with nnn
+ * units x-offset and an optional differential hotend temperature of
+ * mmm degrees. E.g., with "M605 S2 X100 R2" the second extruder will duplicate
+ * the first with a spacing of 100mm in the x direction and 2 degrees hotter.
+ *
+ * Note: the X axis should be homed after changing dual x-carriage mode.
+ */
+ inline void gcode_M605() {
+ stepper.synchronize();
+ if (parser.seen('S')) dual_x_carriage_mode = (DualXMode)parser.value_byte();
+ switch (dual_x_carriage_mode) {
+ case DXC_FULL_CONTROL_MODE:
+ case DXC_AUTO_PARK_MODE:
+ break;
+ case DXC_DUPLICATION_MODE:
+ if (parser.seen('X')) duplicate_extruder_x_offset = max(parser.value_linear_units(), X2_MIN_POS - x_home_pos(0));
+ if (parser.seen('R')) duplicate_extruder_temp_offset = parser.value_celsius_diff();
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM(MSG_HOTEND_OFFSET);
+ SERIAL_CHAR(' ');
+ SERIAL_ECHO(hotend_offset[X_AXIS][0]);
+ SERIAL_CHAR(',');
+ SERIAL_ECHO(hotend_offset[Y_AXIS][0]);
+ SERIAL_CHAR(' ');
+ SERIAL_ECHO(duplicate_extruder_x_offset);
+ SERIAL_CHAR(',');
+ SERIAL_ECHOLN(hotend_offset[Y_AXIS][1]);
+ break;
+ default:
+ dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE;
+ break;
+ }
+ active_extruder_parked = false;
+ extruder_duplication_enabled = false;
+ delayed_move_time = 0;
+ }
+
+#elif ENABLED(DUAL_NOZZLE_DUPLICATION_MODE)
+
+ inline void gcode_M605() {
+ stepper.synchronize();
+ extruder_duplication_enabled = parser.intval('S') == (int)DXC_DUPLICATION_MODE;
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR(MSG_DUPLICATION_MODE, extruder_duplication_enabled ? MSG_ON : MSG_OFF);
+ }
+
+#endif // DUAL_NOZZLE_DUPLICATION_MODE
+
+#if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+
+ /**
+ * M701: Load filament
+ *
+ * T[extruder] - Optional extruder number. Current extruder if omitted.
+ * Z[distance] - Move the Z axis by this distance
+ * L[distance] - Extrude distance for insertion (positive value) (manual reload)
+ *
+ * Default values are used for omitted arguments.
+ */
+ inline void gcode_M701() {
+ point_t park_point = NOZZLE_PARK_POINT;
+
+ #if ENABLED(NO_MOTION_BEFORE_HOMING)
+ // Only raise Z if the machine is homed
+ if (axis_unhomed_error()) park_point.z = 0;
+ #endif
+
+ if (get_target_extruder_from_command(701)) return;
+
+ // Z axis lift
+ if (parser.seenval('Z')) park_point.z = parser.linearval('Z');
+
+ // Load filament
+ const float load_length = FABS(parser.seen('L') ? parser.value_axis_units(E_AXIS) :
+ filament_change_load_length[target_extruder]);
+
+ // Show initial "wait for load" message
+ #if ENABLED(ULTIPANEL)
+ lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_LOAD, ADVANCED_PAUSE_MODE_LOAD_FILAMENT, target_extruder);
+ #endif
+
+ #if EXTRUDERS > 1
+ // Change toolhead if specified
+ uint8_t active_extruder_before_filament_change = active_extruder;
+ if (active_extruder != target_extruder)
+ tool_change(target_extruder, 0, true);
+ #endif
+
+ // Lift Z axis
+ if (park_point.z > 0)
+ do_blocking_move_to_z(min(current_position[Z_AXIS] + park_point.z, Z_MAX_POS), NOZZLE_PARK_Z_FEEDRATE);
+
+ load_filament(load_length, ADVANCED_PAUSE_EXTRUDE_LENGTH, FILAMENT_CHANGE_ALERT_BEEPS, true,
+ thermalManager.wait_for_heating(target_extruder), ADVANCED_PAUSE_MODE_LOAD_FILAMENT);
+
+ // Restore Z axis
+ if (park_point.z > 0)
+ do_blocking_move_to_z(max(current_position[Z_AXIS] - park_point.z, Z_MIN_POS), NOZZLE_PARK_Z_FEEDRATE);
+
+ #if EXTRUDERS > 1
+ // Restore toolhead if it was changed
+ if (active_extruder_before_filament_change != active_extruder)
+ tool_change(active_extruder_before_filament_change, 0, true);
+ #endif
+
+ // Show status screen
+ #if ENABLED(ULTIPANEL)
+ lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_STATUS);
+ #endif
+ }
+
+ /**
+ * M702: Unload filament
+ *
+ * T[extruder] - Optional extruder number. If omitted, current extruder
+ * (or ALL extruders with FILAMENT_UNLOAD_ALL_EXTRUDERS).
+ * Z[distance] - Move the Z axis by this distance
+ * U[distance] - Retract distance for removal (manual reload)
+ *
+ * Default values are used for omitted arguments.
+ */
+ inline void gcode_M702() {
+ point_t park_point = NOZZLE_PARK_POINT;
+
+ #if ENABLED(NO_MOTION_BEFORE_HOMING)
+ // Only raise Z if the machine is homed
+ if (axis_unhomed_error()) park_point.z = 0;
+ #endif
+
+ if (get_target_extruder_from_command(702)) return;
+
+ // Z axis lift
+ if (parser.seenval('Z')) park_point.z = parser.linearval('Z');
+
+ // Show initial message
+ #if ENABLED(ULTIPANEL)
+ lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_UNLOAD, ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, target_extruder);
+ #endif
+
+ #if EXTRUDERS > 1
+ // Change toolhead if specified
+ uint8_t active_extruder_before_filament_change = active_extruder;
+ if (active_extruder != target_extruder)
+ tool_change(target_extruder, 0, true);
+ #endif
+
+ // Lift Z axis
+ if (park_point.z > 0)
+ do_blocking_move_to_z(min(current_position[Z_AXIS] + park_point.z, Z_MAX_POS), NOZZLE_PARK_Z_FEEDRATE);
+
+ // Unload filament
+ #if EXTRUDERS > 1 && ENABLED(FILAMENT_UNLOAD_ALL_EXTRUDERS)
+ if (!parser.seenval('T')) {
+ HOTEND_LOOP() {
+ if (e != active_extruder) tool_change(e, 0, true);
+ unload_filament(-filament_change_unload_length[e], true, ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT);
+ }
+ }
+ else
+ #endif
+ {
+ // Unload length
+ const float unload_length = -FABS(parser.seen('U') ? parser.value_axis_units(E_AXIS) :
+ filament_change_unload_length[target_extruder]);
+
+ unload_filament(unload_length, true, ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT);
+ }
+
+ // Restore Z axis
+ if (park_point.z > 0)
+ do_blocking_move_to_z(max(current_position[Z_AXIS] - park_point.z, Z_MIN_POS), NOZZLE_PARK_Z_FEEDRATE);
+
+ #if EXTRUDERS > 1
+ // Restore toolhead if it was changed
+ if (active_extruder_before_filament_change != active_extruder)
+ tool_change(active_extruder_before_filament_change, 0, true);
+ #endif
+
+ // Show status screen
+ #if ENABLED(ULTIPANEL)
+ lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_STATUS);
+ #endif
+ }
+
+#endif // FILAMENT_LOAD_UNLOAD_GCODES
+
+#if ENABLED(LIN_ADVANCE)
+ /**
+ * M900: Get or Set Linear Advance K-factor
+ *
+ * K Set advance K factor
+ */
+ inline void gcode_M900() {
+ if (parser.seenval('K')) {
+ const float newK = parser.floatval('K');
+ if (WITHIN(newK, 0, 10)) {
+ stepper.synchronize();
+ planner.extruder_advance_K = newK;
+ }
+ else
+ SERIAL_PROTOCOLLNPGM("?K value out of range (0-10).");
+ }
+ else {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR("Advance K=", planner.extruder_advance_K);
+ }
+ }
+#endif // LIN_ADVANCE
+
+#if HAS_TRINAMIC
+ #if ENABLED(TMC_DEBUG)
+ inline void gcode_M122() {
+ if (parser.seen('S'))
+ tmc_set_report_status(parser.value_bool());
+ else
+ tmc_report_all();
+ }
+ #endif // TMC_DEBUG
+
+ /**
+ * M906: Set motor current in milliamps using axis codes X, Y, Z, E
+ * Report driver currents when no axis specified
+ */
+ inline void gcode_M906() {
+ #define TMC_SAY_CURRENT(Q) tmc_get_current(stepper##Q, TMC_##Q)
+ #define TMC_SET_CURRENT(Q) tmc_set_current(stepper##Q, TMC_##Q, value)
+
+ bool report = true;
+ const uint8_t index = parser.byteval('I');
+ LOOP_XYZE(i) if (uint16_t value = parser.intval(axis_codes[i])) {
+ report = false;
+ switch (i) {
+ case X_AXIS:
+ #if X_IS_TRINAMIC
+ if (index == 0) TMC_SET_CURRENT(X);
+ #endif
+ #if X2_IS_TRINAMIC
+ if (index == 1) TMC_SET_CURRENT(X2);
+ #endif
+ break;
+ case Y_AXIS:
+ #if Y_IS_TRINAMIC
+ if (index == 0) TMC_SET_CURRENT(Y);
+ #endif
+ #if Y2_IS_TRINAMIC
+ if (index == 1) TMC_SET_CURRENT(Y2);
+ #endif
+ break;
+ case Z_AXIS:
+ #if Z_IS_TRINAMIC
+ if (index == 0) TMC_SET_CURRENT(Z);
+ #endif
+ #if Z2_IS_TRINAMIC
+ if (index == 1) TMC_SET_CURRENT(Z2);
+ #endif
+ break;
+ case E_AXIS: {
+ if (get_target_extruder_from_command(906)) return;
+ switch (target_extruder) {
+ #if E0_IS_TRINAMIC
+ case 0: TMC_SET_CURRENT(E0); break;
+ #endif
+ #if E1_IS_TRINAMIC
+ case 1: TMC_SET_CURRENT(E1); break;
+ #endif
+ #if E2_IS_TRINAMIC
+ case 2: TMC_SET_CURRENT(E2); break;
+ #endif
+ #if E3_IS_TRINAMIC
+ case 3: TMC_SET_CURRENT(E3); break;
+ #endif
+ #if E4_IS_TRINAMIC
+ case 4: TMC_SET_CURRENT(E4); break;
+ #endif
+ }
+ } break;
+ }
+ }
+
+ if (report) LOOP_XYZE(i) switch (i) {
+ case X_AXIS:
+ #if X_IS_TRINAMIC
+ TMC_SAY_CURRENT(X);
+ #endif
+ #if X2_IS_TRINAMIC
+ TMC_SAY_CURRENT(X2);
+ #endif
+ break;
+ case Y_AXIS:
+ #if Y_IS_TRINAMIC
+ TMC_SAY_CURRENT(Y);
+ #endif
+ #if Y2_IS_TRINAMIC
+ TMC_SAY_CURRENT(Y2);
+ #endif
+ break;
+ case Z_AXIS:
+ #if Z_IS_TRINAMIC
+ TMC_SAY_CURRENT(Z);
+ #endif
+ #if Z2_IS_TRINAMIC
+ TMC_SAY_CURRENT(Z2);
+ #endif
+ break;
+ case E_AXIS:
+ #if E0_IS_TRINAMIC
+ TMC_SAY_CURRENT(E0);
+ #endif
+ #if E1_IS_TRINAMIC
+ TMC_SAY_CURRENT(E1);
+ #endif
+ #if E2_IS_TRINAMIC
+ TMC_SAY_CURRENT(E2);
+ #endif
+ #if E3_IS_TRINAMIC
+ TMC_SAY_CURRENT(E3);
+ #endif
+ #if E4_IS_TRINAMIC
+ TMC_SAY_CURRENT(E4);
+ #endif
+ break;
+ }
+ }
+
+ /**
+ * M911: Report TMC stepper driver overtemperature pre-warn flag
+ * The flag is held by the library and persist until manually cleared by M912
+ */
+ inline void gcode_M911() {
+ #if ENABLED(X_IS_TMC2130) || (ENABLED(X_IS_TMC2208) && PIN_EXISTS(X_SERIAL_RX)) || ENABLED(IS_TRAMS)
+ tmc_report_otpw(stepperX, TMC_X);
+ #endif
+ #if ENABLED(Y_IS_TMC2130) || (ENABLED(Y_IS_TMC2208) && PIN_EXISTS(Y_SERIAL_RX)) || ENABLED(IS_TRAMS)
+ tmc_report_otpw(stepperY, TMC_Y);
+ #endif
+ #if ENABLED(Z_IS_TMC2130) || (ENABLED(Z_IS_TMC2208) && PIN_EXISTS(Z_SERIAL_RX)) || ENABLED(IS_TRAMS)
+ tmc_report_otpw(stepperZ, TMC_Z);
+ #endif
+ #if ENABLED(E0_IS_TMC2130) || (ENABLED(E0_IS_TMC2208) && PIN_EXISTS(E0_SERIAL_RX)) || ENABLED(IS_TRAMS)
+ tmc_report_otpw(stepperE0, TMC_E0);
+ #endif
+ }
+
+ /**
+ * M912: Clear TMC stepper driver overtemperature pre-warn flag held by the library
+ */
+ inline void gcode_M912() {
+ const bool clearX = parser.seen(axis_codes[X_AXIS]), clearY = parser.seen(axis_codes[Y_AXIS]), clearZ = parser.seen(axis_codes[Z_AXIS]), clearE = parser.seen(axis_codes[E_AXIS]),
+ clearAll = (!clearX && !clearY && !clearZ && !clearE) || (clearX && clearY && clearZ && clearE);
+ #if ENABLED(X_IS_TMC2130) || ENABLED(IS_TRAMS) || (ENABLED(X_IS_TMC2208) && PIN_EXISTS(X_SERIAL_RX))
+ if (clearX || clearAll) tmc_clear_otpw(stepperX, TMC_X);
+ #endif
+ #if ENABLED(X2_IS_TMC2130) || (ENABLED(X2_IS_TMC2208) && PIN_EXISTS(X_SERIAL_RX))
+ if (clearX || clearAll) tmc_clear_otpw(stepperX, TMC_X);
+ #endif
+
+ #if ENABLED(Y_IS_TMC2130) || (ENABLED(Y_IS_TMC2208) && PIN_EXISTS(Y_SERIAL_RX))
+ if (clearY || clearAll) tmc_clear_otpw(stepperY, TMC_Y);
+ #endif
+
+ #if ENABLED(Z_IS_TMC2130) || (ENABLED(Z_IS_TMC2208) && PIN_EXISTS(Z_SERIAL_RX))
+ if (clearZ || clearAll) tmc_clear_otpw(stepperZ, TMC_Z);
+ #endif
+
+ #if ENABLED(E0_IS_TMC2130) || (ENABLED(E0_IS_TMC2208) && PIN_EXISTS(E0_SERIAL_RX))
+ if (clearE || clearAll) tmc_clear_otpw(stepperE0, TMC_E0);
+ #endif
+ }
+
+ /**
+ * M913: Set HYBRID_THRESHOLD speed.
+ */
+ #if ENABLED(HYBRID_THRESHOLD)
+ inline void gcode_M913() {
+ #define TMC_SAY_PWMTHRS(P,Q) tmc_get_pwmthrs(stepper##Q, TMC_##Q, planner.axis_steps_per_mm[P##_AXIS])
+ #define TMC_SET_PWMTHRS(P,Q) tmc_set_pwmthrs(stepper##Q, TMC_##Q, value, planner.axis_steps_per_mm[P##_AXIS])
+ #define TMC_SAY_PWMTHRS_E(E) do{ const uint8_t extruder = E; tmc_get_pwmthrs(stepperE##E, TMC_E##E, planner.axis_steps_per_mm[E_AXIS_N]); }while(0)
+ #define TMC_SET_PWMTHRS_E(E) do{ const uint8_t extruder = E; tmc_set_pwmthrs(stepperE##E, TMC_E##E, value, planner.axis_steps_per_mm[E_AXIS_N]); }while(0)
+
+ bool report = true;
+ const uint8_t index = parser.byteval('I');
+ LOOP_XYZE(i) if (int32_t value = parser.longval(axis_codes[i])) {
+ report = false;
+ switch (i) {
+ case X_AXIS:
+ #if X_IS_TRINAMIC
+ if (index == 0) TMC_SET_PWMTHRS(X,X);
+ #endif
+ #if X2_IS_TRINAMIC
+ if (index == 1) TMC_SET_PWMTHRS(X,X2);
+ #endif
+ break;
+ case Y_AXIS:
+ #if Y_IS_TRINAMIC
+ if (index == 0) TMC_SET_PWMTHRS(Y,Y);
+ #endif
+ #if Y2_IS_TRINAMIC
+ if (index == 1) TMC_SET_PWMTHRS(Y,Y2);
+ #endif
+ break;
+ case Z_AXIS:
+ #if Z_IS_TRINAMIC
+ if (index == 0) TMC_SET_PWMTHRS(Z,Z);
+ #endif
+ #if Z2_IS_TRINAMIC
+ if (index == 1) TMC_SET_PWMTHRS(Z,Z2);
+ #endif
+ break;
+ case E_AXIS: {
+ if (get_target_extruder_from_command(913)) return;
+ switch (target_extruder) {
+ #if E0_IS_TRINAMIC
+ case 0: TMC_SET_PWMTHRS_E(0); break;
+ #endif
+ #if E_STEPPERS > 1 && E1_IS_TRINAMIC
+ case 1: TMC_SET_PWMTHRS_E(1); break;
+ #endif
+ #if E_STEPPERS > 2 && E2_IS_TRINAMIC
+ case 2: TMC_SET_PWMTHRS_E(2); break;
+ #endif
+ #if E_STEPPERS > 3 && E3_IS_TRINAMIC
+ case 3: TMC_SET_PWMTHRS_E(3); break;
+ #endif
+ #if E_STEPPERS > 4 && E4_IS_TRINAMIC
+ case 4: TMC_SET_PWMTHRS_E(4); break;
+ #endif
+ }
+ } break;
+ }
+ }
+
+ if (report) LOOP_XYZE(i) switch (i) {
+ case X_AXIS:
+ #if X_IS_TRINAMIC
+ TMC_SAY_PWMTHRS(X,X);
+ #endif
+ #if X2_IS_TRINAMIC
+ TMC_SAY_PWMTHRS(X,X2);
+ #endif
+ break;
+ case Y_AXIS:
+ #if Y_IS_TRINAMIC
+ TMC_SAY_PWMTHRS(Y,Y);
+ #endif
+ #if Y2_IS_TRINAMIC
+ TMC_SAY_PWMTHRS(Y,Y2);
+ #endif
+ break;
+ case Z_AXIS:
+ #if Z_IS_TRINAMIC
+ TMC_SAY_PWMTHRS(Z,Z);
+ #endif
+ #if Z2_IS_TRINAMIC
+ TMC_SAY_PWMTHRS(Z,Z2);
+ #endif
+ break;
+ case E_AXIS:
+ #if E0_IS_TRINAMIC
+ TMC_SAY_PWMTHRS_E(0);
+ #endif
+ #if E_STEPPERS > 1 && E1_IS_TRINAMIC
+ TMC_SAY_PWMTHRS_E(1);
+ #endif
+ #if E_STEPPERS > 2 && E2_IS_TRINAMIC
+ TMC_SAY_PWMTHRS_E(2);
+ #endif
+ #if E_STEPPERS > 3 && E3_IS_TRINAMIC
+ TMC_SAY_PWMTHRS_E(3);
+ #endif
+ #if E_STEPPERS > 4 && E4_IS_TRINAMIC
+ TMC_SAY_PWMTHRS_E(4);
+ #endif
+ break;
+ }
+ }
+ #endif // HYBRID_THRESHOLD
+
+ /**
+ * M914: Set SENSORLESS_HOMING sensitivity.
+ */
+ #if ENABLED(SENSORLESS_HOMING)
+ inline void gcode_M914() {
+ #define TMC_SAY_SGT(Q) tmc_get_sgt(stepper##Q, TMC_##Q)
+ #define TMC_SET_SGT(Q) tmc_set_sgt(stepper##Q, TMC_##Q, value)
+
+ bool report = true;
+ const uint8_t index = parser.byteval('I');
+ LOOP_XYZ(i) if (parser.seen(axis_codes[i])) {
+ const int8_t value = (int8_t)constrain(parser.value_int(), -63, 64);
+ report = false;
+ switch (i) {
+ case X_AXIS:
+ #if ENABLED(X_IS_TMC2130) || ENABLED(IS_TRAMS)
+ if (index == 0) TMC_SET_SGT(X);
+ #endif
+ #if ENABLED(X2_IS_TMC2130)
+ if (index == 1) TMC_SET_SGT(X2);
+ #endif
+ break;
+ case Y_AXIS:
+ #if ENABLED(Y_IS_TMC2130) || ENABLED(IS_TRAMS)
+ if (index == 0) TMC_SET_SGT(Y);
+ #endif
+ #if ENABLED(Y2_IS_TMC2130)
+ if (index == 1) TMC_SET_SGT(Y2);
+ #endif
+ break;
+ case Z_AXIS:
+ #if ENABLED(Z_IS_TMC2130) || ENABLED(IS_TRAMS)
+ if (index == 0) TMC_SET_SGT(Z);
+ #endif
+ #if ENABLED(Z2_IS_TMC2130)
+ if (index == 1) TMC_SET_SGT(Z2);
+ #endif
+ break;
+ }
+ }
+
+ if (report) LOOP_XYZ(i) switch (i) {
+ case X_AXIS:
+ #if ENABLED(X_IS_TMC2130) || ENABLED(IS_TRAMS)
+ TMC_SAY_SGT(X);
+ #endif
+ #if ENABLED(X2_IS_TMC2130)
+ TMC_SAY_SGT(X2);
+ #endif
+ break;
+ case Y_AXIS:
+ #if ENABLED(Y_IS_TMC2130) || ENABLED(IS_TRAMS)
+ TMC_SAY_SGT(Y);
+ #endif
+ #if ENABLED(Y2_IS_TMC2130)
+ TMC_SAY_SGT(Y2);
+ #endif
+ break;
+ case Z_AXIS:
+ #if ENABLED(Z_IS_TMC2130) || ENABLED(IS_TRAMS)
+ TMC_SAY_SGT(Z);
+ #endif
+ #if ENABLED(Z2_IS_TMC2130)
+ TMC_SAY_SGT(Z2);
+ #endif
+ break;
+ }
+ }
+ #endif // SENSORLESS_HOMING
+
+ /**
+ * TMC Z axis calibration routine
+ */
+ #if ENABLED(TMC_Z_CALIBRATION)
+ inline void gcode_M915() {
+ const uint16_t _rms = parser.seenval('S') ? parser.value_int() : CALIBRATION_CURRENT,
+ _z = parser.seenval('Z') ? parser.value_linear_units() : CALIBRATION_EXTRA_HEIGHT;
+
+ if (!axis_known_position[Z_AXIS]) {
+ SERIAL_ECHOLNPGM("\nPlease home Z axis first");
+ return;
+ }
+
+ #if Z_IS_TRINAMIC
+ const uint16_t Z_current_1 = stepperZ.getCurrent();
+ stepperZ.setCurrent(_rms, R_SENSE, HOLD_MULTIPLIER);
+ #endif
+ #if Z2_IS_TRINAMIC
+ const uint16_t Z2_current_1 = stepperZ2.getCurrent();
+ stepperZ2.setCurrent(_rms, R_SENSE, HOLD_MULTIPLIER);
+ #endif
+
+ SERIAL_ECHOPAIR("\nCalibration current: Z", _rms);
+
+ soft_endstops_enabled = false;
+
+ do_blocking_move_to_z(Z_MAX_POS+_z);
+
+ #if Z_IS_TRINAMIC
+ stepperZ.setCurrent(Z_current_1, R_SENSE, HOLD_MULTIPLIER);
+ #endif
+ #if Z2_IS_TRINAMIC
+ stepperZ2.setCurrent(Z2_current_1, R_SENSE, HOLD_MULTIPLIER);
+ #endif
+
+ do_blocking_move_to_z(Z_MAX_POS);
+ soft_endstops_enabled = true;
+
+ SERIAL_ECHOLNPGM("\nHoming Z due to lost steps");
+ enqueue_and_echo_commands_P(PSTR("G28 Z"));
+ }
+ #endif
+
+#endif // HAS_TRINAMIC
+
+/**
+ * M907: Set digital trimpot motor current using axis codes X, Y, Z, E, B, S
+ */
+inline void gcode_M907() {
+ #if HAS_DIGIPOTSS
+
+ LOOP_XYZE(i) if (parser.seen(axis_codes[i])) stepper.digipot_current(i, parser.value_int());
+ if (parser.seen('B')) stepper.digipot_current(4, parser.value_int());
+ if (parser.seen('S')) for (uint8_t i = 0; i <= 4; i++) stepper.digipot_current(i, parser.value_int());
+
+ #elif HAS_MOTOR_CURRENT_PWM
+
+ #if PIN_EXISTS(MOTOR_CURRENT_PWM_XY)
+ if (parser.seen('X')) stepper.digipot_current(0, parser.value_int());
+ #endif
+ #if PIN_EXISTS(MOTOR_CURRENT_PWM_Z)
+ if (parser.seen('Z')) stepper.digipot_current(1, parser.value_int());
+ #endif
+ #if PIN_EXISTS(MOTOR_CURRENT_PWM_E)
+ if (parser.seen('E')) stepper.digipot_current(2, parser.value_int());
+ #endif
+
+ #endif
+
+ #if ENABLED(DIGIPOT_I2C)
+ // this one uses actual amps in floating point
+ LOOP_XYZE(i) if (parser.seen(axis_codes[i])) digipot_i2c_set_current(i, parser.value_float());
+ // for each additional extruder (named B,C,D,E..., channels 4,5,6,7...)
+ for (uint8_t i = NUM_AXIS; i < DIGIPOT_I2C_NUM_CHANNELS; i++) if (parser.seen('B' + i - (NUM_AXIS))) digipot_i2c_set_current(i, parser.value_float());
+ #endif
+
+ #if ENABLED(DAC_STEPPER_CURRENT)
+ if (parser.seen('S')) {
+ const float dac_percent = parser.value_float();
+ for (uint8_t i = 0; i <= 4; i++) dac_current_percent(i, dac_percent);
+ }
+ LOOP_XYZE(i) if (parser.seen(axis_codes[i])) dac_current_percent(i, parser.value_float());
+ #endif
+}
+
+#if HAS_DIGIPOTSS || ENABLED(DAC_STEPPER_CURRENT)
+
+ /**
+ * M908: Control digital trimpot directly (M908 P S)
+ */
+ inline void gcode_M908() {
+ #if HAS_DIGIPOTSS
+ stepper.digitalPotWrite(
+ parser.intval('P'),
+ parser.intval('S')
+ );
+ #endif
+ #ifdef DAC_STEPPER_CURRENT
+ dac_current_raw(
+ parser.byteval('P', -1),
+ parser.ushortval('S', 0)
+ );
+ #endif
+ }
+
+ #if ENABLED(DAC_STEPPER_CURRENT) // As with Printrbot RevF
+
+ inline void gcode_M909() { dac_print_values(); }
+
+ inline void gcode_M910() { dac_commit_eeprom(); }
+
+ #endif
+
+#endif // HAS_DIGIPOTSS || DAC_STEPPER_CURRENT
+
+#if HAS_MICROSTEPS
+
+ // M350 Set microstepping mode. Warning: Steps per unit remains unchanged. S code sets stepping mode for all drivers.
+ inline void gcode_M350() {
+ if (parser.seen('S')) for (int i = 0; i <= 4; i++) stepper.microstep_mode(i, parser.value_byte());
+ LOOP_XYZE(i) if (parser.seen(axis_codes[i])) stepper.microstep_mode(i, parser.value_byte());
+ if (parser.seen('B')) stepper.microstep_mode(4, parser.value_byte());
+ stepper.microstep_readings();
+ }
+
+ /**
+ * M351: Toggle MS1 MS2 pins directly with axis codes X Y Z E B
+ * S# determines MS1 or MS2, X# sets the pin high/low.
+ */
+ inline void gcode_M351() {
+ if (parser.seenval('S')) switch (parser.value_byte()) {
+ case 1:
+ LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.microstep_ms(i, parser.value_byte(), -1);
+ if (parser.seenval('B')) stepper.microstep_ms(4, parser.value_byte(), -1);
+ break;
+ case 2:
+ LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.microstep_ms(i, -1, parser.value_byte());
+ if (parser.seenval('B')) stepper.microstep_ms(4, -1, parser.value_byte());
+ break;
+ }
+ stepper.microstep_readings();
+ }
+
+#endif // HAS_MICROSTEPS
+
+#if HAS_CASE_LIGHT
+
+ #ifndef INVERT_CASE_LIGHT
+ #define INVERT_CASE_LIGHT false
+ #endif
+ uint8_t case_light_brightness; // LCD routine wants INT
+ bool case_light_on;
+
+ #if ENABLED(CASE_LIGHT_USE_NEOPIXEL)
+ LEDColor case_light_color =
+ #ifdef CASE_LIGHT_NEOPIXEL_COLOR
+ CASE_LIGHT_NEOPIXEL_COLOR
+ #else
+ { 255, 255, 255, 255 }
+ #endif
+ ;
+ #endif
+
+ void update_case_light() {
+ const uint8_t i = case_light_on ? case_light_brightness : 0, n10ct = INVERT_CASE_LIGHT ? 255 - i : i;
+
+ #if ENABLED(CASE_LIGHT_USE_NEOPIXEL)
+
+ leds.set_color(
+ MakeLEDColor(case_light_color.r, case_light_color.g, case_light_color.b, case_light_color.w, n10ct),
+ false
+ );
+
+ #else // !CASE_LIGHT_USE_NEOPIXEL
+
+ SET_OUTPUT(CASE_LIGHT_PIN);
+ if (USEABLE_HARDWARE_PWM(CASE_LIGHT_PIN))
+ analogWrite(CASE_LIGHT_PIN, n10ct);
+ else {
+ const bool s = case_light_on ? !INVERT_CASE_LIGHT : INVERT_CASE_LIGHT;
+ WRITE(CASE_LIGHT_PIN, s ? HIGH : LOW);
+ }
+
+ #endif // !CASE_LIGHT_USE_NEOPIXEL
+ }
+#endif // HAS_CASE_LIGHT
+
+/**
+ * M355: Turn case light on/off and set brightness
+ *
+ * P Set case light brightness (PWM pin required - ignored otherwise)
+ *
+ * S Set case light on/off
+ *
+ * When S turns on the light on a PWM pin then the current brightness level is used/restored
+ *
+ * M355 P200 S0 turns off the light & sets the brightness level
+ * M355 S1 turns on the light with a brightness of 200 (assuming a PWM pin)
+ */
+inline void gcode_M355() {
+ #if HAS_CASE_LIGHT
+ uint8_t args = 0;
+ if (parser.seenval('P')) ++args, case_light_brightness = parser.value_byte();
+ if (parser.seenval('S')) ++args, case_light_on = parser.value_bool();
+ if (args) update_case_light();
+
+ // always report case light status
+ SERIAL_ECHO_START();
+ if (!case_light_on) {
+ SERIAL_ECHOLNPGM("Case light: off");
+ }
+ else {
+ if (!USEABLE_HARDWARE_PWM(CASE_LIGHT_PIN)) SERIAL_ECHOLNPGM("Case light: on");
+ else SERIAL_ECHOLNPAIR("Case light: ", (int)case_light_brightness);
+ }
+
+ #else
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_M355_NONE);
+ #endif // HAS_CASE_LIGHT
+}
+
+#if ENABLED(MIXING_EXTRUDER)
+
+ /**
+ * M163: Set a single mix factor for a mixing extruder
+ * This is called "weight" by some systems.
+ *
+ * S[index] The channel index to set
+ * P[float] The mix value
+ *
+ */
+ inline void gcode_M163() {
+ const int mix_index = parser.intval('S');
+ if (mix_index < MIXING_STEPPERS) {
+ float mix_value = parser.floatval('P');
+ NOLESS(mix_value, 0.0);
+ mixing_factor[mix_index] = RECIPROCAL(mix_value);
+ }
+ }
+
+ #if MIXING_VIRTUAL_TOOLS > 1
+
+ /**
+ * M164: Store the current mix factors as a virtual tool.
+ *
+ * S[index] The virtual tool to store
+ *
+ */
+ inline void gcode_M164() {
+ const int tool_index = parser.intval('S');
+ if (tool_index < MIXING_VIRTUAL_TOOLS) {
+ normalize_mix();
+ for (uint8_t i = 0; i < MIXING_STEPPERS; i++)
+ mixing_virtual_tool_mix[tool_index][i] = mixing_factor[i];
+ }
+ }
+
+ #endif
+
+ #if ENABLED(DIRECT_MIXING_IN_G1)
+ /**
+ * M165: Set multiple mix factors for a mixing extruder.
+ * Factors that are left out will be set to 0.
+ * All factors together must add up to 1.0.
+ *
+ * A[factor] Mix factor for extruder stepper 1
+ * B[factor] Mix factor for extruder stepper 2
+ * C[factor] Mix factor for extruder stepper 3
+ * D[factor] Mix factor for extruder stepper 4
+ * H[factor] Mix factor for extruder stepper 5
+ * I[factor] Mix factor for extruder stepper 6
+ *
+ */
+ inline void gcode_M165() { gcode_get_mix(); }
+ #endif
+
+#endif // MIXING_EXTRUDER
+
+/**
+ * M999: Restart after being stopped
+ *
+ * Default behaviour is to flush the serial buffer and request
+ * a resend to the host starting on the last N line received.
+ *
+ * Sending "M999 S1" will resume printing without flushing the
+ * existing command buffer.
+ *
+ */
+inline void gcode_M999() {
+ Running = true;
+ lcd_reset_alert_level();
+
+ if (parser.boolval('S')) return;
+
+ // gcode_LastN = Stopped_gcode_LastN;
+ flush_and_request_resend();
+}
+
+#if ENABLED(SWITCHING_EXTRUDER)
+ #if EXTRUDERS > 3
+ #define REQ_ANGLES 4
+ #define _SERVO_NR (e < 2 ? SWITCHING_EXTRUDER_SERVO_NR : SWITCHING_EXTRUDER_E23_SERVO_NR)
+ #else
+ #define REQ_ANGLES 2
+ #define _SERVO_NR SWITCHING_EXTRUDER_SERVO_NR
+ #endif
+ inline void move_extruder_servo(const uint8_t e) {
+ constexpr int16_t angles[] = SWITCHING_EXTRUDER_SERVO_ANGLES;
+ static_assert(COUNT(angles) == REQ_ANGLES, "SWITCHING_EXTRUDER_SERVO_ANGLES needs " STRINGIFY(REQ_ANGLES) " angles.");
+ stepper.synchronize();
+ #if EXTRUDERS & 1
+ if (e < EXTRUDERS - 1)
+ #endif
+ {
+ MOVE_SERVO(_SERVO_NR, angles[e]);
+ safe_delay(500);
+ }
+ }
+#endif // SWITCHING_EXTRUDER
+
+#if ENABLED(SWITCHING_NOZZLE)
+ inline void move_nozzle_servo(const uint8_t e) {
+ const int16_t angles[2] = SWITCHING_NOZZLE_SERVO_ANGLES;
+ stepper.synchronize();
+ MOVE_SERVO(SWITCHING_NOZZLE_SERVO_NR, angles[e]);
+ safe_delay(500);
+ }
+#endif
+
+inline void invalid_extruder_error(const uint8_t e) {
+ SERIAL_ECHO_START();
+ SERIAL_CHAR('T');
+ SERIAL_ECHO_F(e, DEC);
+ SERIAL_CHAR(' ');
+ SERIAL_ECHOLNPGM(MSG_INVALID_EXTRUDER);
+}
+
+#if ENABLED(PARKING_EXTRUDER)
+
+ #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT)
+ #define PE_MAGNET_ON_STATE !PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE
+ #else
+ #define PE_MAGNET_ON_STATE PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE
+ #endif
+
+ void pe_set_magnet(const uint8_t extruder_num, const uint8_t state) {
+ switch (extruder_num) {
+ case 1: OUT_WRITE(SOL1_PIN, state); break;
+ default: OUT_WRITE(SOL0_PIN, state); break;
+ }
+ #if PARKING_EXTRUDER_SOLENOIDS_DELAY > 0
+ dwell(PARKING_EXTRUDER_SOLENOIDS_DELAY);
+ #endif
+ }
+
+ inline void pe_activate_magnet(const uint8_t extruder_num) { pe_set_magnet(extruder_num, PE_MAGNET_ON_STATE); }
+ inline void pe_deactivate_magnet(const uint8_t extruder_num) { pe_set_magnet(extruder_num, !PE_MAGNET_ON_STATE); }
+
+#endif // PARKING_EXTRUDER
+
+#if HAS_FANMUX
+
+ void fanmux_switch(const uint8_t e) {
+ WRITE(FANMUX0_PIN, TEST(e, 0) ? HIGH : LOW);
+ #if PIN_EXISTS(FANMUX1)
+ WRITE(FANMUX1_PIN, TEST(e, 1) ? HIGH : LOW);
+ #if PIN_EXISTS(FANMUX2)
+ WRITE(FANMUX2, TEST(e, 2) ? HIGH : LOW);
+ #endif
+ #endif
+ }
+
+ FORCE_INLINE void fanmux_init(void) {
+ SET_OUTPUT(FANMUX0_PIN);
+ #if PIN_EXISTS(FANMUX1)
+ SET_OUTPUT(FANMUX1_PIN);
+ #if PIN_EXISTS(FANMUX2)
+ SET_OUTPUT(FANMUX2_PIN);
+ #endif
+ #endif
+ fanmux_switch(0);
+ }
+
+#endif // HAS_FANMUX
+
+/**
+ * Perform a tool-change, which may result in moving the
+ * previous tool out of the way and the new tool into place.
+ */
+void tool_change(const uint8_t tmp_extruder, const float fr_mm_s/*=0.0*/, bool no_move/*=false*/) {
+ #if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1
+
+ if (tmp_extruder >= MIXING_VIRTUAL_TOOLS)
+ return invalid_extruder_error(tmp_extruder);
+
+ // T0-Tnnn: Switch virtual tool by changing the mix
+ for (uint8_t j = 0; j < MIXING_STEPPERS; j++)
+ mixing_factor[j] = mixing_virtual_tool_mix[tmp_extruder][j];
+
+ #else // !MIXING_EXTRUDER || MIXING_VIRTUAL_TOOLS <= 1
+
+ if (tmp_extruder >= EXTRUDERS)
+ return invalid_extruder_error(tmp_extruder);
+
+ #if HOTENDS > 1
+
+ const float old_feedrate_mm_s = fr_mm_s > 0.0 ? fr_mm_s : feedrate_mm_s;
+
+ feedrate_mm_s = fr_mm_s > 0.0 ? fr_mm_s : XY_PROBE_FEEDRATE_MM_S;
+
+ if (tmp_extruder != active_extruder) {
+ if (!no_move && axis_unhomed_error()) {
+ no_move = true;
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("No move on toolchange");
+ #endif
+ }
+
+ // Save current position to destination, for use later
+ set_destination_from_current();
+
+ #if ENABLED(DUAL_X_CARRIAGE)
+
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) {
+ SERIAL_ECHOPGM("Dual X Carriage Mode ");
+ switch (dual_x_carriage_mode) {
+ case DXC_FULL_CONTROL_MODE: SERIAL_ECHOLNPGM("DXC_FULL_CONTROL_MODE"); break;
+ case DXC_AUTO_PARK_MODE: SERIAL_ECHOLNPGM("DXC_AUTO_PARK_MODE"); break;
+ case DXC_DUPLICATION_MODE: SERIAL_ECHOLNPGM("DXC_DUPLICATION_MODE"); break;
+ }
+ }
+ #endif
+
+ const float xhome = x_home_pos(active_extruder);
+ if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE
+ && IsRunning()
+ && (delayed_move_time || current_position[X_AXIS] != xhome)
+ ) {
+ float raised_z = current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT;
+ #if ENABLED(MAX_SOFTWARE_ENDSTOPS)
+ NOMORE(raised_z, soft_endstop_max[Z_AXIS]);
+ #endif
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) {
+ SERIAL_ECHOLNPAIR("Raise to ", raised_z);
+ SERIAL_ECHOLNPAIR("MoveX to ", xhome);
+ SERIAL_ECHOLNPAIR("Lower to ", current_position[Z_AXIS]);
+ }
+ #endif
+ // Park old head: 1) raise 2) move to park position 3) lower
+ for (uint8_t i = 0; i < 3; i++)
+ planner.buffer_line(
+ i == 0 ? current_position[X_AXIS] : xhome,
+ current_position[Y_AXIS],
+ i == 2 ? current_position[Z_AXIS] : raised_z,
+ current_position[E_AXIS],
+ planner.max_feedrate_mm_s[i == 1 ? X_AXIS : Z_AXIS],
+ active_extruder
+ );
+ stepper.synchronize();
+ }
+
+ // Apply Y & Z extruder offset (X offset is used as home pos with Dual X)
+ current_position[Y_AXIS] -= hotend_offset[Y_AXIS][active_extruder] - hotend_offset[Y_AXIS][tmp_extruder];
+ current_position[Z_AXIS] -= hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder];
+
+ // Activate the new extruder ahead of calling set_axis_is_at_home!
+ active_extruder = tmp_extruder;
+
+ // This function resets the max/min values - the current position may be overwritten below.
+ set_axis_is_at_home(X_AXIS);
+
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) DEBUG_POS("New Extruder", current_position);
+ #endif
+
+ // Only when auto-parking are carriages safe to move
+ if (dual_x_carriage_mode != DXC_AUTO_PARK_MODE) no_move = true;
+
+ switch (dual_x_carriage_mode) {
+ case DXC_FULL_CONTROL_MODE:
+ // New current position is the position of the activated extruder
+ current_position[X_AXIS] = inactive_extruder_x_pos;
+ // Save the inactive extruder's position (from the old current_position)
+ inactive_extruder_x_pos = destination[X_AXIS];
+ break;
+ case DXC_AUTO_PARK_MODE:
+ // record raised toolhead position for use by unpark
+ COPY(raised_parked_position, current_position);
+ raised_parked_position[Z_AXIS] += TOOLCHANGE_UNPARK_ZLIFT;
+ #if ENABLED(MAX_SOFTWARE_ENDSTOPS)
+ NOMORE(raised_parked_position[Z_AXIS], soft_endstop_max[Z_AXIS]);
+ #endif
+ active_extruder_parked = true;
+ delayed_move_time = 0;
+ break;
+ case DXC_DUPLICATION_MODE:
+ // If the new extruder is the left one, set it "parked"
+ // This triggers the second extruder to move into the duplication position
+ active_extruder_parked = (active_extruder == 0);
+
+ if (active_extruder_parked)
+ current_position[X_AXIS] = inactive_extruder_x_pos;
+ else
+ current_position[X_AXIS] = destination[X_AXIS] + duplicate_extruder_x_offset;
+ inactive_extruder_x_pos = destination[X_AXIS];
+ extruder_duplication_enabled = false;
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) {
+ SERIAL_ECHOLNPAIR("Set inactive_extruder_x_pos=", inactive_extruder_x_pos);
+ SERIAL_ECHOLNPGM("Clear extruder_duplication_enabled");
+ }
+ #endif
+ break;
+ }
+
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) {
+ SERIAL_ECHOLNPAIR("Active extruder parked: ", active_extruder_parked ? "yes" : "no");
+ DEBUG_POS("New extruder (parked)", current_position);
+ }
+ #endif
+
+ // No extra case for HAS_ABL in DUAL_X_CARRIAGE. Does that mean they don't work together?
+
+ #else // !DUAL_X_CARRIAGE
+
+ #if ENABLED(PARKING_EXTRUDER) // Dual Parking extruder
+ const float z_diff = hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder];
+ float z_raise = PARKING_EXTRUDER_SECURITY_RAISE;
+ if (!no_move) {
+
+ const float parkingposx[] = PARKING_EXTRUDER_PARKING_X,
+ midpos = (parkingposx[0] + parkingposx[1]) * 0.5 + hotend_offset[X_AXIS][active_extruder],
+ grabpos = parkingposx[tmp_extruder] + hotend_offset[X_AXIS][active_extruder]
+ + (tmp_extruder == 0 ? -(PARKING_EXTRUDER_GRAB_DISTANCE) : PARKING_EXTRUDER_GRAB_DISTANCE);
+ /**
+ * Steps:
+ * 1. Raise Z-Axis to give enough clearance
+ * 2. Move to park position of old extruder
+ * 3. Disengage magnetic field, wait for delay
+ * 4. Move near new extruder
+ * 5. Engage magnetic field for new extruder
+ * 6. Move to parking incl. offset of new extruder
+ * 7. Lower Z-Axis
+ */
+
+ // STEP 1
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ SERIAL_ECHOLNPGM("Starting Autopark");
+ if (DEBUGGING(LEVELING)) DEBUG_POS("current position:", current_position);
+ #endif
+ current_position[Z_AXIS] += z_raise;
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ SERIAL_ECHOLNPGM("(1) Raise Z-Axis ");
+ if (DEBUGGING(LEVELING)) DEBUG_POS("Moving to Raised Z-Position", current_position);
+ #endif
+ planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[Z_AXIS], active_extruder);
+ stepper.synchronize();
+
+ // STEP 2
+ current_position[X_AXIS] = parkingposx[active_extruder] + hotend_offset[X_AXIS][active_extruder];
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ SERIAL_ECHOLNPAIR("(2) Park extruder ", active_extruder);
+ if (DEBUGGING(LEVELING)) DEBUG_POS("Moving ParkPos", current_position);
+ #endif
+ planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder);
+ stepper.synchronize();
+
+ // STEP 3
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ SERIAL_ECHOLNPGM("(3) Disengage magnet ");
+ #endif
+ pe_deactivate_magnet(active_extruder);
+
+ // STEP 4
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ SERIAL_ECHOLNPGM("(4) Move to position near new extruder");
+ #endif
+ current_position[X_AXIS] += (active_extruder == 0 ? 10 : -10); // move 10mm away from parked extruder
+
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) DEBUG_POS("Moving away from parked extruder", current_position);
+ #endif
+ planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder);
+ stepper.synchronize();
+
+ // STEP 5
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ SERIAL_ECHOLNPGM("(5) Engage magnetic field");
+ #endif
+
+ #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT)
+ pe_activate_magnet(active_extruder); //just save power for inverted magnets
+ #endif
+ pe_activate_magnet(tmp_extruder);
+
+ // STEP 6
+ current_position[X_AXIS] = grabpos + (tmp_extruder == 0 ? (+10) : (-10));
+ planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder);
+ current_position[X_AXIS] = grabpos;
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ SERIAL_ECHOLNPAIR("(6) Unpark extruder ", tmp_extruder);
+ if (DEBUGGING(LEVELING)) DEBUG_POS("Move UnparkPos", current_position);
+ #endif
+ planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS]/2, active_extruder);
+ stepper.synchronize();
+
+ // Step 7
+ current_position[X_AXIS] = midpos - hotend_offset[X_AXIS][tmp_extruder];
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ SERIAL_ECHOLNPGM("(7) Move midway between hotends");
+ if (DEBUGGING(LEVELING)) DEBUG_POS("Move midway to new extruder", current_position);
+ #endif
+ planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder);
+ stepper.synchronize();
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ SERIAL_ECHOLNPGM("Autopark done.");
+ #endif
+ }
+ else { // nomove == true
+ // Only engage magnetic field for new extruder
+ pe_activate_magnet(tmp_extruder);
+ #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT)
+ pe_activate_magnet(active_extruder); // Just save power for inverted magnets
+ #endif
+ }
+ current_position[Z_AXIS] -= hotend_offset[Z_AXIS][tmp_extruder] - hotend_offset[Z_AXIS][active_extruder]; // Apply Zoffset
+
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) DEBUG_POS("Applying Z-offset", current_position);
+ #endif
+
+ #endif // dualParking extruder
+
+ #if ENABLED(SWITCHING_NOZZLE)
+ #define DONT_SWITCH (SWITCHING_EXTRUDER_SERVO_NR == SWITCHING_NOZZLE_SERVO_NR)
+ // <0 if the new nozzle is higher, >0 if lower. A bigger raise when lower.
+ const float z_diff = hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder],
+ z_raise = 0.3 + (z_diff > 0.0 ? z_diff : 0.0);
+
+ // Always raise by some amount
+ current_position[Z_AXIS] += z_raise;
+ planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[Z_AXIS], active_extruder);
+ move_nozzle_servo(tmp_extruder);
+ #endif
+
+ /**
+ * Set current_position to the position of the new nozzle.
+ * Offsets are based on linear distance, so we need to get
+ * the resulting position in coordinate space.
+ *
+ * - With grid or 3-point leveling, offset XYZ by a tilted vector
+ * - With mesh leveling, update Z for the new position
+ * - Otherwise, just use the raw linear distance
+ *
+ * Software endstops are altered here too. Consider a case where:
+ * E0 at X=0 ... E1 at X=10
+ * When we switch to E1 now X=10, but E1 can't move left.
+ * To express this we apply the change in XY to the software endstops.
+ * E1 can move farther right than E0, so the right limit is extended.
+ *
+ * Note that we don't adjust the Z software endstops. Why not?
+ * Consider a case where Z=0 (here) and switching to E1 makes Z=1
+ * because the bed is 1mm lower at the new position. As long as
+ * the first nozzle is out of the way, the carriage should be
+ * allowed to move 1mm lower. This technically "breaks" the
+ * Z software endstop. But this is technically correct (and
+ * there is no viable alternative).
+ */
+ #if ABL_PLANAR
+ // Offset extruder, make sure to apply the bed level rotation matrix
+ vector_3 tmp_offset_vec = vector_3(hotend_offset[X_AXIS][tmp_extruder],
+ hotend_offset[Y_AXIS][tmp_extruder],
+ 0),
+ act_offset_vec = vector_3(hotend_offset[X_AXIS][active_extruder],
+ hotend_offset[Y_AXIS][active_extruder],
+ 0),
+ offset_vec = tmp_offset_vec - act_offset_vec;
+
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) {
+ tmp_offset_vec.debug(PSTR("tmp_offset_vec"));
+ act_offset_vec.debug(PSTR("act_offset_vec"));
+ offset_vec.debug(PSTR("offset_vec (BEFORE)"));
+ }
+ #endif
+
+ offset_vec.apply_rotation(planner.bed_level_matrix.transpose(planner.bed_level_matrix));
+
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) offset_vec.debug(PSTR("offset_vec (AFTER)"));
+ #endif
+
+ // Adjustments to the current position
+ const float xydiff[2] = { offset_vec.x, offset_vec.y };
+ current_position[Z_AXIS] += offset_vec.z;
+
+ #else // !ABL_PLANAR
+
+ const float xydiff[2] = {
+ hotend_offset[X_AXIS][tmp_extruder] - hotend_offset[X_AXIS][active_extruder],
+ hotend_offset[Y_AXIS][tmp_extruder] - hotend_offset[Y_AXIS][active_extruder]
+ };
+
+ #if HAS_MESH && PLANNER_LEVELING
+
+ if (planner.leveling_active) {
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) SERIAL_ECHOPAIR("Z before: ", current_position[Z_AXIS]);
+ #endif
+ float x2 = current_position[X_AXIS] + xydiff[X_AXIS],
+ y2 = current_position[Y_AXIS] + xydiff[Y_AXIS],
+ z1 = current_position[Z_AXIS], z2 = z1;
+ planner.apply_leveling(current_position[X_AXIS], current_position[Y_AXIS], z1);
+ planner.apply_leveling(x2, y2, z2);
+ current_position[Z_AXIS] += z2 - z1;
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING))
+ SERIAL_ECHOLNPAIR(" after: ", current_position[Z_AXIS]);
+ #endif
+ }
+
+ #endif // HAS_MESH && PLANNER_LEVELING
+
+ #endif // !HAS_ABL
+
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) {
+ SERIAL_ECHOPAIR("Offset Tool XY by { ", xydiff[X_AXIS]);
+ SERIAL_ECHOPAIR(", ", xydiff[Y_AXIS]);
+ SERIAL_ECHOLNPGM(" }");
+ }
+ #endif
+
+ // The newly-selected extruder XY is actually at...
+ current_position[X_AXIS] += xydiff[X_AXIS];
+ current_position[Y_AXIS] += xydiff[Y_AXIS];
+
+ // Set the new active extruder
+ active_extruder = tmp_extruder;
+
+ #endif // !DUAL_X_CARRIAGE
+
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) DEBUG_POS("Sync After Toolchange", current_position);
+ #endif
+
+ // Tell the planner the new "current position"
+ SYNC_PLAN_POSITION_KINEMATIC();
+
+ #if ENABLED(DELTA)
+ //LOOP_XYZ(i) update_software_endstops(i); // or modify the constrain function
+ // Do a small lift to avoid the workpiece in the move back (below)
+ const bool safe_to_move = current_position[Z_AXIS] < delta_clip_start_height - 1;
+ if (!no_move && IsRunning() && safe_to_move) {
+ ++current_position[Z_AXIS];
+ planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[Z_AXIS], active_extruder);
+ }
+ #else
+ constexpr bool safe_to_move = true;
+ #endif
+
+ #if ENABLED(SWITCHING_NOZZLE)
+ destination[Z_AXIS] += z_diff; // Include the Z restore with the "move back"
+ #endif
+
+ // Move to the "old position" (move the extruder into place)
+ if (safe_to_move && !no_move && IsRunning()) {
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) DEBUG_POS("Move back", destination);
+ #endif
+ // Move back to the original (or tweaked) position
+ do_blocking_move_to(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS]);
+ }
+ #if ENABLED(SWITCHING_NOZZLE)
+ else {
+ // Move back down. (Including when the new tool is higher.)
+ do_blocking_move_to_z(destination[Z_AXIS], planner.max_feedrate_mm_s[Z_AXIS]);
+ }
+ #endif
+ } // (tmp_extruder != active_extruder)
+
+ stepper.synchronize();
+
+ #if ENABLED(EXT_SOLENOID) && !ENABLED(PARKING_EXTRUDER)
+ disable_all_solenoids();
+ enable_solenoid_on_active_extruder();
+ #endif // EXT_SOLENOID
+
+ feedrate_mm_s = old_feedrate_mm_s;
+
+ #else // HOTENDS <= 1
+
+ UNUSED(fr_mm_s);
+ UNUSED(no_move);
+
+ #if ENABLED(MK2_MULTIPLEXER)
+ if (tmp_extruder >= E_STEPPERS)
+ return invalid_extruder_error(tmp_extruder);
+
+ select_multiplexed_stepper(tmp_extruder);
+ #endif
+
+ // Set the new active extruder
+ active_extruder = tmp_extruder;
+
+ #endif // HOTENDS <= 1
+
+ #if ENABLED(SWITCHING_EXTRUDER) && !DONT_SWITCH
+ stepper.synchronize();
+ move_extruder_servo(active_extruder);
+ #endif
+
+ #if HAS_FANMUX
+ fanmux_switch(active_extruder);
+ #endif
+
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR(MSG_ACTIVE_EXTRUDER, (int)active_extruder);
+
+ #endif // !MIXING_EXTRUDER || MIXING_VIRTUAL_TOOLS <= 1
+}
+
+/**
+ * T0-T3: Switch tool, usually switching extruders
+ *
+ * F[units/min] Set the movement feedrate
+ * S1 Don't move the tool in XY after change
+ */
+inline void gcode_T(const uint8_t tmp_extruder) {
+
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) {
+ SERIAL_ECHOPAIR(">>> gcode_T(", tmp_extruder);
+ SERIAL_CHAR(')');
+ SERIAL_EOL();
+ DEBUG_POS("BEFORE", current_position);
+ }
+ #endif
+
+ #if HOTENDS == 1 || (ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1)
+
+ tool_change(tmp_extruder);
+
+ #elif HOTENDS > 1
+
+ tool_change(
+ tmp_extruder,
+ MMM_TO_MMS(parser.linearval('F')),
+ (tmp_extruder == active_extruder) || parser.boolval('S')
+ );
+
+ #endif
+
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) {
+ DEBUG_POS("AFTER", current_position);
+ SERIAL_ECHOLNPGM("<<< gcode_T");
+ }
+ #endif
+}
+
+/**
+ * Process the parsed command and dispatch it to its handler
+ */
+void process_parsed_command() {
+ KEEPALIVE_STATE(IN_HANDLER);
+
+ // Handle a known G, M, or T
+ switch (parser.command_letter) {
+ case 'G': switch (parser.codenum) {
+
+ case 0: case 1: gcode_G0_G1( // G0: Fast Move, G1: Linear Move
+ #if IS_SCARA
+ parser.codenum == 0
+ #endif
+ ); break;
+
+ #if ENABLED(ARC_SUPPORT) && DISABLED(SCARA)
+ case 2: case 3: gcode_G2_G3(parser.codenum == 2); break; // G2: CW ARC, G3: CCW ARC
+ #endif
+
+ case 4: gcode_G4(); break; // G4: Dwell
+
+ #if ENABLED(BEZIER_CURVE_SUPPORT)
+ case 5: gcode_G5(); break; // G5: Cubic B_spline
+ #endif
+
+ #if ENABLED(FWRETRACT)
+ case 10: gcode_G10(); break; // G10: Retract
+ case 11: gcode_G11(); break; // G11: Prime
+ #endif
+
+ #if ENABLED(NOZZLE_CLEAN_FEATURE)
+ case 12: gcode_G12(); break; // G12: Clean Nozzle
+ #endif
+
+ #if ENABLED(CNC_WORKSPACE_PLANES)
+ case 17: gcode_G17(); break; // G17: Select Plane XY
+ case 18: gcode_G18(); break; // G18: Select Plane ZX
+ case 19: gcode_G19(); break; // G19: Select Plane YZ
+ #endif
+
+ #if ENABLED(INCH_MODE_SUPPORT)
+ case 20: gcode_G20(); break; // G20: Inch Units
+ case 21: gcode_G21(); break; // G21: Millimeter Units
+ #endif
+
+ #if ENABLED(G26_MESH_VALIDATION)
+ case 26: gcode_G26(); break; // G26: Mesh Validation Pattern
+ #endif
+
+ #if ENABLED(NOZZLE_PARK_FEATURE)
+ case 27: gcode_G27(); break; // G27: Park Nozzle
+ #endif
+
+ case 28: gcode_G28(false); break; // G28: Home one or more axes
+
+ #if HAS_LEVELING
+ case 29: gcode_G29(); break; // G29: Detailed Z probe
+ #endif
+
+ #if HAS_BED_PROBE
+ case 30: gcode_G30(); break; // G30: Single Z probe
+ #endif
+
+ #if ENABLED(Z_PROBE_SLED)
+ case 31: gcode_G31(); break; // G31: Dock sled
+ case 32: gcode_G32(); break; // G32: Undock sled
+ #endif
+
+ #if ENABLED(DELTA_AUTO_CALIBRATION)
+ case 33: gcode_G33(); break; // G33: Delta Auto-Calibration
+ #endif
+
+ #if ENABLED(G38_PROBE_TARGET)
+ case 38:
+ if (parser.subcode == 2 || parser.subcode == 3)
+ gcode_G38(parser.subcode == 2); // G38.2, G38.3: Probe towards object
+ break;
+ #endif
+
+ #if HAS_MESH
+ case 42: gcode_G42(); break; // G42: Move to mesh point
+ #endif
+
+ case 90: relative_mode = false; break; // G90: Absolute coordinates
+ case 91: relative_mode = true; break; // G91: Relative coordinates
+
+ case 92: gcode_G92(); break; // G92: Set Position
+
+ #if ENABLED(DEBUG_GCODE_PARSER)
+ case 800: parser.debug(); break; // G800: GCode Parser Test for G
+ #endif
+ }
+ break;
+
+ case 'M': switch (parser.codenum) {
+ #if HAS_RESUME_CONTINUE
+ case 0: case 1: gcode_M0_M1(); break; // M0: Unconditional stop, M1: Conditional stop
+ #endif
+
+ #if ENABLED(SPINDLE_LASER_ENABLE)
+ case 3: gcode_M3_M4(true); break; // M3: Laser/CW-Spindle Power
+ case 4: gcode_M3_M4(false); break; // M4: Laser/CCW-Spindle Power
+ case 5: gcode_M5(); break; // M5: Laser/Spindle OFF
+ #endif
+ #if(ENABLED(FAN_AS_LASER))
+ case 3: gcode_M3(); break; // M3: Laser Power On
+ case 5: gcode_M5(); break; // M5: Laser OFF
+ #endif
+ case 17: gcode_M17(); break; // M17: Enable all steppers
+
+ #if ENABLED(SDSUPPORT)
+ case 20: gcode_M20(); break; // M20: List SD Card
+ case 21: gcode_M21(); break; // M21: Init SD Card
+ case 22: gcode_M22(); break; // M22: Release SD Card
+ case 23: gcode_M23(); break; // M23: Select File
+ case 24: gcode_M24(); break; // M24: Start SD Print
+ case 25: gcode_M25(); break; // M25: Pause SD Print
+ case 26: gcode_M26(); break; // M26: Set SD Index
+ case 27: gcode_M27(); break; // M27: Get SD Status
+ case 28: gcode_M28(); break; // M28: Start SD Write
+ case 29: gcode_M29(); break; // M29: Stop SD Write
+ case 30: gcode_M30(); break; // M30: Delete File
+ case 32: gcode_M32(); break; // M32: Select file, Start SD Print
+ #if ENABLED(LONG_FILENAME_HOST_SUPPORT)
+ case 33: gcode_M33(); break; // M33: Report longname path
+ #endif
+ #if ENABLED(SDCARD_SORT_ALPHA) && ENABLED(SDSORT_GCODE)
+ case 34: gcode_M34(); break; // M34: Set SD card sorting options
+ #endif
+ case 928: gcode_M928(); break; // M928: Start SD write
+ #endif // SDSUPPORT
+
+ case 31: gcode_M31(); break; // M31: Report print job elapsed time
+
+ case 42: gcode_M42(); break; // M42: Change pin state
+ #if ENABLED(PINS_DEBUGGING)
+ case 43: gcode_M43(); break; // M43: Read/monitor pin and endstop states
+ #endif
+
+ #if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST)
+ case 48: gcode_M48(); break; // M48: Z probe repeatability test
+ #endif
+ #if ENABLED(G26_MESH_VALIDATION)
+ case 49: gcode_M49(); break; // M49: Toggle the G26 Debug Flag
+ #endif
+
+ #if ENABLED(ULTRA_LCD) && ENABLED(LCD_SET_PROGRESS_MANUALLY)
+ case 73: gcode_M73(); break; // M73: Set Print Progress %
+ #endif
+ case 75: gcode_M75(); break; // M75: Start Print Job Timer
+ case 76: gcode_M76(); break; // M76: Pause Print Job Timer
+ case 77: gcode_M77(); break; // M77: Stop Print Job Timer
+ #if ENABLED(PRINTCOUNTER)
+ case 78: gcode_M78(); break; // M78: Report Print Statistics
+ #endif
+
+ #if ENABLED(M100_FREE_MEMORY_WATCHER)
+ case 100: gcode_M100(); break; // M100: Free Memory Report
+ #endif
+
+ case 104: gcode_M104(); break; // M104: Set Hotend Temperature
+ case 110: gcode_M110(); break; // M110: Set Current Line Number
+ case 111: gcode_M111(); break; // M111: Set Debug Flags
+
+ #if DISABLED(EMERGENCY_PARSER)
+ case 108: gcode_M108(); break; // M108: Cancel Waiting
+ case 112: gcode_M112(); break; // M112: Emergency Stop
+ case 410: gcode_M410(); break; // M410: Quickstop. Abort all planned moves
+ #endif
+
+ #if ENABLED(HOST_KEEPALIVE_FEATURE)
+ case 113: gcode_M113(); break; // M113: Set Host Keepalive Interval
+ #endif
+
+ case 140: gcode_M140(); break; // M140: Set Bed Temperature
+
+ case 105: gcode_M105(); KEEPALIVE_STATE(NOT_BUSY); return; // M105: Report Temperatures (and say "ok")
+
+ #if ENABLED(AUTO_REPORT_TEMPERATURES)
+ case 155: gcode_M155(); break; // M155: Set Temperature Auto-report Interval
+ #endif
+
+ case 109: gcode_M109(); break; // M109: Set Hotend Temperature. Wait for target.
+
+ #if HAS_TEMP_BED
+ case 190: gcode_M190(); break; // M190: Set Bed Temperature. Wait for target.
+ #endif
+
+ #if FAN_COUNT > 0
+ case 106: gcode_M106(); break; // M106: Set Fan Speed
+ case 107: gcode_M107(); break; // M107: Fan Off
+ #endif
+
+ #if ENABLED(PARK_HEAD_ON_PAUSE)
+ case 125: gcode_M125(); break; // M125: Park (for Filament Change)
+ #endif
+
+ #if ENABLED(BARICUDA)
+ #if HAS_HEATER_1
+ case 126: gcode_M126(); break; // M126: Valve 1 Open
+ case 127: gcode_M127(); break; // M127: Valve 1 Closed
+ #endif
+ #if HAS_HEATER_2
+ case 128: gcode_M128(); break; // M128: Valve 2 Open
+ case 129: gcode_M129(); break; // M129: Valve 2 Closed
+ #endif
+ #endif
+
+ #if HAS_POWER_SWITCH
+ case 80: gcode_M80(); break; // M80: Turn on Power Supply
+ #endif
+ case 81: gcode_M81(); break; // M81: Turn off Power and Power Supply
+
+ case 82: gcode_M82(); break; // M82: Disable Relative E-Axis
+ case 83: gcode_M83(); break; // M83: Set Relative E-Axis
+ case 18: case 84: gcode_M18_M84(); break; // M18/M84: Disable Steppers / Set Timeout
+ case 85: gcode_M85(); break; // M85: Set inactivity stepper shutdown timeout
+ case 92: gcode_M92(); break; // M92: Set steps-per-unit
+ case 114: gcode_M114(); break; // M114: Report Current Position
+ case 115: gcode_M115(); break; // M115: Capabilities Report
+ case 117: gcode_M117(); break; // M117: Set LCD message text
+ case 118: gcode_M118(); break; // M118: Print a message in the host console
+ case 119: gcode_M119(); break; // M119: Report Endstop states
+ case 120: gcode_M120(); break; // M120: Enable Endstops
+ case 121: gcode_M121(); break; // M121: Disable Endstops
+
+ #if ENABLED(ULTIPANEL)
+ case 145: gcode_M145(); break; // M145: Set material heatup parameters
+ #endif
+
+ #if ENABLED(TEMPERATURE_UNITS_SUPPORT)
+ case 149: gcode_M149(); break; // M149: Set Temperature Units, C F K
+ #endif
+
+ #if HAS_COLOR_LEDS
+ case 150: gcode_M150(); break; // M150: Set Status LED Color
+ #endif
+
+ #if ENABLED(MIXING_EXTRUDER)
+ case 163: gcode_M163(); break; // M163: Set Mixing Component
+ #if MIXING_VIRTUAL_TOOLS > 1
+ case 164: gcode_M164(); break; // M164: Save Current Mix
+ #endif
+ #if ENABLED(DIRECT_MIXING_IN_G1)
+ case 165: gcode_M165(); break; // M165: Set Multiple Mixing Components
+ #endif
+ #endif
+
+ #if DISABLED(NO_VOLUMETRICS)
+ case 200: gcode_M200(); break; // M200: Set Filament Diameter, Volumetric Extrusion
+ #endif
+
+ case 201: gcode_M201(); break; // M201: Set Max Printing Acceleration (units/sec^2)
+ #if 0
+ case 202: gcode_M202(); break; // M202: Not used for Sprinter/grbl gen6
+ #endif
+ case 203: gcode_M203(); break; // M203: Set Max Feedrate (units/sec)
+ case 204: gcode_M204(); break; // M204: Set Acceleration
+ case 205: gcode_M205(); break; // M205: Set Advanced settings
+
+ #if HAS_M206_COMMAND
+ case 206: gcode_M206(); break; // M206: Set Home Offsets
+ case 428: gcode_M428(); break; // M428: Set Home Offsets based on current position
+ #endif
+
+ #if ENABLED(FWRETRACT)
+ case 207: gcode_M207(); break; // M207: Set Retract Length, Feedrate, Z lift
+ case 208: gcode_M208(); break; // M208: Set Additional Prime Length and Feedrate
+ case 209:
+ if (MIN_AUTORETRACT <= MAX_AUTORETRACT) gcode_M209(); // M209: Turn Auto-Retract on/off
+ break;
+ #endif
+
+ case 211: gcode_M211(); break; // M211: Enable/Disable/Report Software Endstops
+
+ #if HOTENDS > 1
+ case 218: gcode_M218(); break; // M218: Set Tool Offset
+ #endif
+
+ case 220: gcode_M220(); break; // M220: Set Feedrate Percentage
+ case 221: gcode_M221(); break; // M221: Set Flow Percentage
+ case 226: gcode_M226(); break; // M226: Wait for Pin State
+
+ #if defined(CHDK) || HAS_PHOTOGRAPH
+ case 240: gcode_M240(); break; // M240: Trigger Camera
+ #endif
+
+ #if HAS_LCD_CONTRAST
+ case 250: gcode_M250(); break; // M250: Set LCD Contrast
+ #endif
+
+ #if ENABLED(EXPERIMENTAL_I2CBUS)
+ case 260: gcode_M260(); break; // M260: Send Data to i2c slave
+ case 261: gcode_M261(); break; // M261: Request Data from i2c slave
+ #endif
+
+ #if HAS_SERVOS
+ case 280: gcode_M280(); break; // M280: Set Servo Position
+ #endif
+
+ #if ENABLED(BABYSTEPPING)
+ case 290: gcode_M290(); break; // M290: Babystepping
+ #endif
+
+ #if HAS_BUZZER
+ case 300: gcode_M300(); break; // M300: Add Tone/Buzz to Queue
+ #endif
+
+ #if ENABLED(PIDTEMP)
+ case 301: gcode_M301(); break; // M301: Set Hotend PID parameters
+ #endif
+
+ #if ENABLED(PREVENT_COLD_EXTRUSION)
+ case 302: gcode_M302(); break; // M302: Set Minimum Extrusion Temp
+ #endif
+
+ case 303: gcode_M303(); break; // M303: PID Autotune
+
+ #if ENABLED(PIDTEMPBED)
+ case 304: gcode_M304(); break; // M304: Set Bed PID parameters
+ #endif
+
+ #if HAS_MICROSTEPS
+ case 350: gcode_M350(); break; // M350: Set microstepping mode. Warning: Steps per unit remains unchanged. S code sets stepping mode for all drivers.
+ case 351: gcode_M351(); break; // M351: Toggle MS1 MS2 pins directly, S# determines MS1 or MS2, X# sets the pin high/low.
+ #endif
+
+ case 355: gcode_M355(); break; // M355: Set Case Light brightness
+
+ #if ENABLED(MORGAN_SCARA)
+ case 360: if (gcode_M360()) return; break; // M360: SCARA Theta pos1
+ case 361: if (gcode_M361()) return; break; // M361: SCARA Theta pos2
+ case 362: if (gcode_M362()) return; break; // M362: SCARA Psi pos1
+ case 363: if (gcode_M363()) return; break; // M363: SCARA Psi pos2
+ case 364: if (gcode_M364()) return; break; // M364: SCARA Psi pos3 (90 deg to Theta)
+ #endif
+
+ case 400: gcode_M400(); break; // M400: Synchronize. Wait for moves to finish.
+
+ #if HAS_BED_PROBE
+ case 401: gcode_M401(); break; // M401: Deploy Probe
+ case 402: gcode_M402(); break; // M402: Stow Probe
+ #endif
+
+ #if ENABLED(FILAMENT_WIDTH_SENSOR)
+ case 404: gcode_M404(); break; // M404: Set/Report Nominal Filament Width
+ case 405: gcode_M405(); break; // M405: Enable Filament Width Sensor
+ case 406: gcode_M406(); break; // M406: Disable Filament Width Sensor
+ case 407: gcode_M407(); break; // M407: Report Measured Filament Width
+ #endif
+
+ #if HAS_LEVELING
+ case 420: gcode_M420(); break; // M420: Set Bed Leveling Enabled / Fade
+ #endif
+
+ #if HAS_MESH
+ case 421: gcode_M421(); break; // M421: Set a Mesh Z value
+ #endif
+
+ case 500: gcode_M500(); break; // M500: Store Settings in EEPROM
+ case 501: gcode_M501(); break; // M501: Read Settings from EEPROM
+ case 502: gcode_M502(); break; // M502: Revert Settings to defaults
+ #if DISABLED(DISABLE_M503)
+ case 503: gcode_M503(); break; // M503: Report Settings (in SRAM)
+ #endif
+ #if ENABLED(EEPROM_SETTINGS)
+ case 504: gcode_M504(); break; // M504: Validate EEPROM
+ #endif
+
+ #if ENABLED(ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED)
+ case 540: gcode_M540(); break; // M540: Set Abort on Endstop Hit for SD Printing
+ #endif
+
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ case 600: gcode_M600(); break; // M600: Pause for Filament Change
+ case 603: gcode_M603(); break; // M603: Configure Filament Change
+ #endif
+
+ #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(DUAL_NOZZLE_DUPLICATION_MODE)
+ case 605: gcode_M605(); break; // M605: Set Dual X Carriage movement mode
+ #endif
+
+ #if ENABLED(DELTA)
+ case 665: gcode_M665(); break; // M665: Delta Configuration
+ #endif
+ #if ENABLED(DELTA) || ENABLED(X_DUAL_ENDSTOPS) || ENABLED(Y_DUAL_ENDSTOPS) || ENABLED(Z_DUAL_ENDSTOPS)
+ case 666: gcode_M666(); break; // M666: DELTA/Dual Endstop Adjustment
+ #endif
+
+ #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ case 701: gcode_M701(); break; // M701: Load Filament
+ case 702: gcode_M702(); break; // M702: Unload Filament
+ #endif
+
+ #if ENABLED(DEBUG_GCODE_PARSER)
+ case 800: parser.debug(); break; // M800: GCode Parser Test for M
+ #endif
+
+ #if HAS_BED_PROBE
+ case 851: gcode_M851(); break; // M851: Set Z Probe Z Offset
+ #endif
+
+ #if ENABLED(SKEW_CORRECTION_GCODE)
+ case 852: gcode_M852(); break; // M852: Set Skew factors
+ #endif
+
+ #if ENABLED(I2C_POSITION_ENCODERS)
+ case 860: gcode_M860(); break; // M860: Report encoder module position
+ case 861: gcode_M861(); break; // M861: Report encoder module status
+ case 862: gcode_M862(); break; // M862: Perform axis test
+ case 863: gcode_M863(); break; // M863: Calibrate steps/mm
+ case 864: gcode_M864(); break; // M864: Change module address
+ case 865: gcode_M865(); break; // M865: Check module firmware version
+ case 866: gcode_M866(); break; // M866: Report axis error count
+ case 867: gcode_M867(); break; // M867: Toggle error correction
+ case 868: gcode_M868(); break; // M868: Set error correction threshold
+ case 869: gcode_M869(); break; // M869: Report axis error
+ #endif
+
+ #if ENABLED(LIN_ADVANCE)
+ case 900: gcode_M900(); break; // M900: Set Linear Advance K factor
+ #endif
+
+ case 907: gcode_M907(); break; // M907: Set Digital Trimpot Motor Current using axis codes.
+
+ #if HAS_DIGIPOTSS || ENABLED(DAC_STEPPER_CURRENT)
+ case 908: gcode_M908(); break; // M908: Direct Control Digital Trimpot
+ #if ENABLED(DAC_STEPPER_CURRENT)
+ case 909: gcode_M909(); break; // M909: Print Digipot/DAC current value (As with Printrbot RevF)
+ case 910: gcode_M910(); break; // M910: Commit Digipot/DAC value to External EEPROM (As with Printrbot RevF)
+ #endif
+ #endif
+
+ #if ENABLED(HAVE_TMC2130) || ENABLED(HAVE_TMC2208)
+ #if ENABLED(TMC_DEBUG)
+ case 122: gcode_M122(); break; // M122: Debug TMC steppers
+ #endif
+ case 906: gcode_M906(); break; // M906: Set motor current in milliamps using axis codes X, Y, Z, E
+ case 911: gcode_M911(); break; // M911: Report TMC prewarn triggered flags
+ case 912: gcode_M912(); break; // M911: Clear TMC prewarn triggered flags
+ #if ENABLED(HYBRID_THRESHOLD)
+ case 913: gcode_M913(); break; // M913: Set HYBRID_THRESHOLD speed.
+ #endif
+ #if ENABLED(SENSORLESS_HOMING)
+ case 914: gcode_M914(); break; // M914: Set SENSORLESS_HOMING sensitivity.
+ #endif
+ #if ENABLED(TMC_Z_CALIBRATION)
+ case 915: gcode_M915(); break; // M915: TMC Z axis calibration routine
+ #endif
+ #endif
+
+ case 999: gcode_M999(); break; // M999: Restart after being Stopped
+ }
+ break;
+
+ case 'T': gcode_T(parser.codenum); break; // T: Tool Select
+
+ default: parser.unknown_command_error();
+ }
+
+ KEEPALIVE_STATE(NOT_BUSY);
+ ok_to_send();
+}
+
+void process_next_command() {
+ char * const current_command = command_queue[cmd_queue_index_r];
+
+ if (DEBUGGING(ECHO)) {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLN(current_command);
+ #if ENABLED(M100_FREE_MEMORY_WATCHER)
+ SERIAL_ECHOPAIR("slot:", cmd_queue_index_r);
+ M100_dump_routine(" Command Queue:", (const char*)command_queue, (const char*)(command_queue + sizeof(command_queue)));
+ #endif
+ }
+
+ reset_stepper_timeout(); // Keep steppers powered
+
+ // Parse the next command in the queue
+ parser.parse(current_command);
+ process_parsed_command();
+}
+
+/**
+ * Send a "Resend: nnn" message to the host to
+ * indicate that a command needs to be re-sent.
+ */
+void flush_and_request_resend() {
+ //char command_queue[cmd_queue_index_r][100]="Resend:";
+ SERIAL_FLUSH();
+ SERIAL_PROTOCOLPGM(MSG_RESEND);
+ SERIAL_PROTOCOLLN(gcode_LastN + 1);
+ ok_to_send();
+}
+
+/**
+ * Send an "ok" message to the host, indicating
+ * that a command was successfully processed.
+ *
+ * If ADVANCED_OK is enabled also include:
+ * N Line number of the command, if any
+ * P Planner space remaining
+ * B Block queue space remaining
+ */
+void ok_to_send() {
+ if (!send_ok[cmd_queue_index_r]) return;
+ SERIAL_PROTOCOLPGM(MSG_OK);
+ #if ENABLED(ADVANCED_OK)
+ char* p = command_queue[cmd_queue_index_r];
+ if (*p == 'N') {
+ SERIAL_PROTOCOL(' ');
+ SERIAL_ECHO(*p++);
+ while (NUMERIC_SIGNED(*p))
+ SERIAL_ECHO(*p++);
+ }
+ SERIAL_PROTOCOLPGM(" P"); SERIAL_PROTOCOL(int(BLOCK_BUFFER_SIZE - planner.movesplanned() - 1));
+ SERIAL_PROTOCOLPGM(" B"); SERIAL_PROTOCOL(BUFSIZE - commands_in_queue);
+ #endif
+ SERIAL_EOL();
+}
+
+#if HAS_SOFTWARE_ENDSTOPS
+
+ /**
+ * Constrain the given coordinates to the software endstops.
+ *
+ * For DELTA/SCARA the XY constraint is based on the smallest
+ * radius within the set software endstops.
+ */
+ void clamp_to_software_endstops(float target[XYZ]) {
+ if (!soft_endstops_enabled) return;
+ #if IS_KINEMATIC
+ const float dist_2 = HYPOT2(target[X_AXIS], target[Y_AXIS]);
+ if (dist_2 > soft_endstop_radius_2) {
+ const float ratio = soft_endstop_radius / SQRT(dist_2); // 200 / 300 = 0.66
+ target[X_AXIS] *= ratio;
+ target[Y_AXIS] *= ratio;
+ }
+ #else
+ #if ENABLED(MIN_SOFTWARE_ENDSTOP_X)
+ NOLESS(target[X_AXIS], soft_endstop_min[X_AXIS]);
+ #endif
+ #if ENABLED(MIN_SOFTWARE_ENDSTOP_Y)
+ NOLESS(target[Y_AXIS], soft_endstop_min[Y_AXIS]);
+ #endif
+ #if ENABLED(MAX_SOFTWARE_ENDSTOP_X)
+ NOMORE(target[X_AXIS], soft_endstop_max[X_AXIS]);
+ #endif
+ #if ENABLED(MAX_SOFTWARE_ENDSTOP_Y)
+ NOMORE(target[Y_AXIS], soft_endstop_max[Y_AXIS]);
+ #endif
+ #endif
+ #if ENABLED(MIN_SOFTWARE_ENDSTOP_Z)
+ NOLESS(target[Z_AXIS], soft_endstop_min[Z_AXIS]);
+ #endif
+ #if ENABLED(MAX_SOFTWARE_ENDSTOP_Z)
+ NOMORE(target[Z_AXIS], soft_endstop_max[Z_AXIS]);
+ #endif
+ }
+
+#endif
+
+#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
+
+ // Get the Z adjustment for non-linear bed leveling
+ float bilinear_z_offset(const float raw[XYZ]) {
+
+ static float z1, d2, z3, d4, L, D, ratio_x, ratio_y,
+ last_x = -999.999, last_y = -999.999;
+
+ // Whole units for the grid line indices. Constrained within bounds.
+ static int8_t gridx, gridy, nextx, nexty,
+ last_gridx = -99, last_gridy = -99;
+
+ // XY relative to the probed area
+ const float rx = raw[X_AXIS] - bilinear_start[X_AXIS],
+ ry = raw[Y_AXIS] - bilinear_start[Y_AXIS];
+
+ #if ENABLED(EXTRAPOLATE_BEYOND_GRID)
+ // Keep using the last grid box
+ #define FAR_EDGE_OR_BOX 2
+ #else
+ // Just use the grid far edge
+ #define FAR_EDGE_OR_BOX 1
+ #endif
+
+ if (last_x != rx) {
+ last_x = rx;
+ ratio_x = rx * ABL_BG_FACTOR(X_AXIS);
+ const float gx = constrain(FLOOR(ratio_x), 0, ABL_BG_POINTS_X - FAR_EDGE_OR_BOX);
+ ratio_x -= gx; // Subtract whole to get the ratio within the grid box
+
+ #if DISABLED(EXTRAPOLATE_BEYOND_GRID)
+ // Beyond the grid maintain height at grid edges
+ NOLESS(ratio_x, 0); // Never < 0.0. (> 1.0 is ok when nextx==gridx.)
+ #endif
+
+ gridx = gx;
+ nextx = min(gridx + 1, ABL_BG_POINTS_X - 1);
+ }
+
+ if (last_y != ry || last_gridx != gridx) {
+
+ if (last_y != ry) {
+ last_y = ry;
+ ratio_y = ry * ABL_BG_FACTOR(Y_AXIS);
+ const float gy = constrain(FLOOR(ratio_y), 0, ABL_BG_POINTS_Y - FAR_EDGE_OR_BOX);
+ ratio_y -= gy;
+
+ #if DISABLED(EXTRAPOLATE_BEYOND_GRID)
+ // Beyond the grid maintain height at grid edges
+ NOLESS(ratio_y, 0); // Never < 0.0. (> 1.0 is ok when nexty==gridy.)
+ #endif
+
+ gridy = gy;
+ nexty = min(gridy + 1, ABL_BG_POINTS_Y - 1);
+ }
+
+ if (last_gridx != gridx || last_gridy != gridy) {
+ last_gridx = gridx;
+ last_gridy = gridy;
+ // Z at the box corners
+ z1 = ABL_BG_GRID(gridx, gridy); // left-front
+ d2 = ABL_BG_GRID(gridx, nexty) - z1; // left-back (delta)
+ z3 = ABL_BG_GRID(nextx, gridy); // right-front
+ d4 = ABL_BG_GRID(nextx, nexty) - z3; // right-back (delta)
+ }
+
+ // Bilinear interpolate. Needed since ry or gridx has changed.
+ L = z1 + d2 * ratio_y; // Linear interp. LF -> LB
+ const float R = z3 + d4 * ratio_y; // Linear interp. RF -> RB
+
+ D = R - L;
+ }
+
+ const float offset = L + ratio_x * D; // the offset almost always changes
+
+ /*
+ static float last_offset = 0;
+ if (FABS(last_offset - offset) > 0.2) {
+ SERIAL_ECHOPGM("Sudden Shift at ");
+ SERIAL_ECHOPAIR("x=", rx);
+ SERIAL_ECHOPAIR(" / ", bilinear_grid_spacing[X_AXIS]);
+ SERIAL_ECHOLNPAIR(" -> gridx=", gridx);
+ SERIAL_ECHOPAIR(" y=", ry);
+ SERIAL_ECHOPAIR(" / ", bilinear_grid_spacing[Y_AXIS]);
+ SERIAL_ECHOLNPAIR(" -> gridy=", gridy);
+ SERIAL_ECHOPAIR(" ratio_x=", ratio_x);
+ SERIAL_ECHOLNPAIR(" ratio_y=", ratio_y);
+ SERIAL_ECHOPAIR(" z1=", z1);
+ SERIAL_ECHOPAIR(" z2=", z2);
+ SERIAL_ECHOPAIR(" z3=", z3);
+ SERIAL_ECHOLNPAIR(" z4=", z4);
+ SERIAL_ECHOPAIR(" L=", L);
+ SERIAL_ECHOPAIR(" R=", R);
+ SERIAL_ECHOLNPAIR(" offset=", offset);
+ }
+ last_offset = offset;
+ //*/
+
+ return offset;
+ }
+
+#endif // AUTO_BED_LEVELING_BILINEAR
+
+#if ENABLED(DELTA)
+
+ /**
+ * Recalculate factors used for delta kinematics whenever
+ * settings have been changed (e.g., by M665).
+ */
+ void recalc_delta_settings() {
+ const float trt[ABC] = DELTA_RADIUS_TRIM_TOWER,
+ drt[ABC] = DELTA_DIAGONAL_ROD_TRIM_TOWER;
+ delta_tower[A_AXIS][X_AXIS] = cos(RADIANS(210 + delta_tower_angle_trim[A_AXIS])) * (delta_radius + trt[A_AXIS]); // front left tower
+ delta_tower[A_AXIS][Y_AXIS] = sin(RADIANS(210 + delta_tower_angle_trim[A_AXIS])) * (delta_radius + trt[A_AXIS]);
+ delta_tower[B_AXIS][X_AXIS] = cos(RADIANS(330 + delta_tower_angle_trim[B_AXIS])) * (delta_radius + trt[B_AXIS]); // front right tower
+ delta_tower[B_AXIS][Y_AXIS] = sin(RADIANS(330 + delta_tower_angle_trim[B_AXIS])) * (delta_radius + trt[B_AXIS]);
+ delta_tower[C_AXIS][X_AXIS] = cos(RADIANS( 90 + delta_tower_angle_trim[C_AXIS])) * (delta_radius + trt[C_AXIS]); // back middle tower
+ delta_tower[C_AXIS][Y_AXIS] = sin(RADIANS( 90 + delta_tower_angle_trim[C_AXIS])) * (delta_radius + trt[C_AXIS]);
+ delta_diagonal_rod_2_tower[A_AXIS] = sq(delta_diagonal_rod + drt[A_AXIS]);
+ delta_diagonal_rod_2_tower[B_AXIS] = sq(delta_diagonal_rod + drt[B_AXIS]);
+ delta_diagonal_rod_2_tower[C_AXIS] = sq(delta_diagonal_rod + drt[C_AXIS]);
+ update_software_endstops(Z_AXIS);
+ axis_homed[X_AXIS] = axis_homed[Y_AXIS] = axis_homed[Z_AXIS] = false;
+ }
+
+ #if ENABLED(DELTA_FAST_SQRT)
+ /**
+ * Fast inverse sqrt from Quake III Arena
+ * See: https://en.wikipedia.org/wiki/Fast_inverse_square_root
+ */
+ float Q_rsqrt(const float number) {
+ long i;
+ float x2, y;
+ const float threehalfs = 1.5f;
+ x2 = number * 0.5f;
+ y = number;
+ i = * ( long * ) &y; // evil floating point bit level hacking
+ i = 0x5F3759DF - ( i >> 1 ); // what the f***?
+ y = * ( float * ) &i;
+ y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
+ // y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
+ return y;
+ }
+
+ #endif
+
+ /**
+ * Delta Inverse Kinematics
+ *
+ * Calculate the tower positions for a given machine
+ * position, storing the result in the delta[] array.
+ *
+ * This is an expensive calculation, requiring 3 square
+ * roots per segmented linear move, and strains the limits
+ * of a Mega2560 with a Graphical Display.
+ *
+ * Suggested optimizations include:
+ *
+ * - Disable the home_offset (M206) and/or position_shift (G92)
+ * features to remove up to 12 float additions.
+ *
+ * - Use a fast-inverse-sqrt function and add the reciprocal.
+ * (see above)
+ */
+
+ #define DELTA_DEBUG(VAR) do { \
+ SERIAL_ECHOPAIR("cartesian X:", VAR[X_AXIS]); \
+ SERIAL_ECHOPAIR(" Y:", VAR[Y_AXIS]); \
+ SERIAL_ECHOLNPAIR(" Z:", VAR[Z_AXIS]); \
+ SERIAL_ECHOPAIR("delta A:", delta[A_AXIS]); \
+ SERIAL_ECHOPAIR(" B:", delta[B_AXIS]); \
+ SERIAL_ECHOLNPAIR(" C:", delta[C_AXIS]); \
+ }while(0)
+
+ void inverse_kinematics(const float raw[XYZ]) {
+ #if HOTENDS > 1
+ // Delta hotend offsets must be applied in Cartesian space with no "spoofing"
+ const float pos[XYZ] = {
+ raw[X_AXIS] - hotend_offset[X_AXIS][active_extruder],
+ raw[Y_AXIS] - hotend_offset[Y_AXIS][active_extruder],
+ raw[Z_AXIS]
+ };
+ DELTA_IK(pos);
+ //DELTA_DEBUG(pos);
+ #else
+ DELTA_IK(raw);
+ //DELTA_DEBUG(raw);
+ #endif
+ }
+
+ /**
+ * Calculate the highest Z position where the
+ * effector has the full range of XY motion.
+ */
+ float delta_safe_distance_from_top() {
+ float cartesian[XYZ] = { 0, 0, 0 };
+ inverse_kinematics(cartesian);
+ const float centered_extent = delta[A_AXIS];
+ cartesian[Y_AXIS] = DELTA_PRINTABLE_RADIUS;
+ inverse_kinematics(cartesian);
+ return FABS(centered_extent - delta[A_AXIS]);
+ }
+
+ /**
+ * Delta Forward Kinematics
+ *
+ * See the Wikipedia article "Trilateration"
+ * https://en.wikipedia.org/wiki/Trilateration
+ *
+ * Establish a new coordinate system in the plane of the
+ * three carriage points. This system has its origin at
+ * tower1, with tower2 on the X axis. Tower3 is in the X-Y
+ * plane with a Z component of zero.
+ * We will define unit vectors in this coordinate system
+ * in our original coordinate system. Then when we calculate
+ * the Xnew, Ynew and Znew values, we can translate back into
+ * the original system by moving along those unit vectors
+ * by the corresponding values.
+ *
+ * Variable names matched to Marlin, c-version, and avoid the
+ * use of any vector library.
+ *
+ * by Andreas Hardtung 2016-06-07
+ * based on a Java function from "Delta Robot Kinematics V3"
+ * by Steve Graves
+ *
+ * The result is stored in the cartes[] array.
+ */
+ void forward_kinematics_DELTA(float z1, float z2, float z3) {
+ // Create a vector in old coordinates along x axis of new coordinate
+ const float p12[] = {
+ delta_tower[B_AXIS][X_AXIS] - delta_tower[A_AXIS][X_AXIS],
+ delta_tower[B_AXIS][Y_AXIS] - delta_tower[A_AXIS][Y_AXIS],
+ z2 - z1
+ },
+
+ // Get the Magnitude of vector.
+ d = SQRT(sq(p12[0]) + sq(p12[1]) + sq(p12[2])),
+
+ // Create unit vector by dividing by magnitude.
+ ex[3] = { p12[0] / d, p12[1] / d, p12[2] / d },
+
+ // Get the vector from the origin of the new system to the third point.
+ p13[3] = {
+ delta_tower[C_AXIS][X_AXIS] - delta_tower[A_AXIS][X_AXIS],
+ delta_tower[C_AXIS][Y_AXIS] - delta_tower[A_AXIS][Y_AXIS],
+ z3 - z1
+ },
+
+ // Use the dot product to find the component of this vector on the X axis.
+ i = ex[0] * p13[0] + ex[1] * p13[1] + ex[2] * p13[2],
+
+ // Create a vector along the x axis that represents the x component of p13.
+ iex[] = { ex[0] * i, ex[1] * i, ex[2] * i };
+
+ // Subtract the X component from the original vector leaving only Y. We use the
+ // variable that will be the unit vector after we scale it.
+ float ey[3] = { p13[0] - iex[0], p13[1] - iex[1], p13[2] - iex[2] };
+
+ // The magnitude of Y component
+ const float j = SQRT(sq(ey[0]) + sq(ey[1]) + sq(ey[2]));
+
+ // Convert to a unit vector
+ ey[0] /= j; ey[1] /= j; ey[2] /= j;
+
+ // The cross product of the unit x and y is the unit z
+ // float[] ez = vectorCrossProd(ex, ey);
+ const float ez[3] = {
+ ex[1] * ey[2] - ex[2] * ey[1],
+ ex[2] * ey[0] - ex[0] * ey[2],
+ ex[0] * ey[1] - ex[1] * ey[0]
+ },
+ // We now have the d, i and j values defined in Wikipedia.
+ // Plug them into the equations defined in Wikipedia for Xnew, Ynew and Znew
+ Xnew = (delta_diagonal_rod_2_tower[A_AXIS] - delta_diagonal_rod_2_tower[B_AXIS] + sq(d)) / (d * 2),
+ Ynew = ((delta_diagonal_rod_2_tower[A_AXIS] - delta_diagonal_rod_2_tower[C_AXIS] + HYPOT2(i, j)) / 2 - i * Xnew) / j,
+ Znew = SQRT(delta_diagonal_rod_2_tower[A_AXIS] - HYPOT2(Xnew, Ynew));
+
+ // Start from the origin of the old coordinates and add vectors in the
+ // old coords that represent the Xnew, Ynew and Znew to find the point
+ // in the old system.
+ cartes[X_AXIS] = delta_tower[A_AXIS][X_AXIS] + ex[0] * Xnew + ey[0] * Ynew - ez[0] * Znew;
+ cartes[Y_AXIS] = delta_tower[A_AXIS][Y_AXIS] + ex[1] * Xnew + ey[1] * Ynew - ez[1] * Znew;
+ cartes[Z_AXIS] = z1 + ex[2] * Xnew + ey[2] * Ynew - ez[2] * Znew;
+ }
+
+ void forward_kinematics_DELTA(float point[ABC]) {
+ forward_kinematics_DELTA(point[A_AXIS], point[B_AXIS], point[C_AXIS]);
+ }
+
+#endif // DELTA
+
+/**
+ * Get the stepper positions in the cartes[] array.
+ * Forward kinematics are applied for DELTA and SCARA.
+ *
+ * The result is in the current coordinate space with
+ * leveling applied. The coordinates need to be run through
+ * unapply_leveling to obtain machine coordinates suitable
+ * for current_position, etc.
+ */
+void get_cartesian_from_steppers() {
+ #if ENABLED(DELTA)
+ forward_kinematics_DELTA(
+ stepper.get_axis_position_mm(A_AXIS),
+ stepper.get_axis_position_mm(B_AXIS),
+ stepper.get_axis_position_mm(C_AXIS)
+ );
+ #else
+ #if IS_SCARA
+ forward_kinematics_SCARA(
+ stepper.get_axis_position_degrees(A_AXIS),
+ stepper.get_axis_position_degrees(B_AXIS)
+ );
+ #else
+ cartes[X_AXIS] = stepper.get_axis_position_mm(X_AXIS);
+ cartes[Y_AXIS] = stepper.get_axis_position_mm(Y_AXIS);
+ #endif
+ cartes[Z_AXIS] = stepper.get_axis_position_mm(Z_AXIS);
+ #endif
+}
+
+/**
+ * Set the current_position for an axis based on
+ * the stepper positions, removing any leveling that
+ * may have been applied.
+ *
+ * To prevent small shifts in axis position always call
+ * SYNC_PLAN_POSITION_KINEMATIC after updating axes with this.
+ *
+ * To keep hosts in sync, always call report_current_position
+ * after updating the current_position.
+ */
+void set_current_from_steppers_for_axis(const AxisEnum axis) {
+ get_cartesian_from_steppers();
+ #if PLANNER_LEVELING
+ planner.unapply_leveling(cartes);
+ #endif
+ if (axis == ALL_AXES)
+ COPY(current_position, cartes);
+ else
+ current_position[axis] = cartes[axis];
+}
+
+#if IS_CARTESIAN
+#if ENABLED(SEGMENT_LEVELED_MOVES)
+
+ /**
+ * Prepare a segmented move on a CARTESIAN setup.
+ *
+ * This calls planner.buffer_line several times, adding
+ * small incremental moves. This allows the planner to
+ * apply more detailed bed leveling to the full move.
+ */
+ inline void segmented_line_to_destination(const float &fr_mm_s, const float segment_size=LEVELED_SEGMENT_LENGTH) {
+
+ const float xdiff = destination[X_AXIS] - current_position[X_AXIS],
+ ydiff = destination[Y_AXIS] - current_position[Y_AXIS];
+
+ // If the move is only in Z/E don't split up the move
+ if (!xdiff && !ydiff) {
+ planner.buffer_line_kinematic(destination, fr_mm_s, active_extruder);
+ return;
+ }
+
+ // Remaining cartesian distances
+ const float zdiff = destination[Z_AXIS] - current_position[Z_AXIS],
+ ediff = destination[E_AXIS] - current_position[E_AXIS];
+
+ // Get the linear distance in XYZ
+ // If the move is very short, check the E move distance
+ // No E move either? Game over.
+ float cartesian_mm = SQRT(sq(xdiff) + sq(ydiff) + sq(zdiff));
+ if (UNEAR_ZERO(cartesian_mm)) cartesian_mm = FABS(ediff);
+ if (UNEAR_ZERO(cartesian_mm)) return;
+
+ // The length divided by the segment size
+ // At least one segment is required
+ uint16_t segments = cartesian_mm / segment_size;
+ NOLESS(segments, 1);
+
+ // The approximate length of each segment
+ const float inv_segments = 1.0 / float(segments),
+ cartesian_segment_mm = cartesian_mm * inv_segments,
+ segment_distance[XYZE] = {
+ xdiff * inv_segments,
+ ydiff * inv_segments,
+ zdiff * inv_segments,
+ ediff * inv_segments
+ };
+
+ // SERIAL_ECHOPAIR("mm=", cartesian_mm);
+ // SERIAL_ECHOLNPAIR(" segments=", segments);
+ // SERIAL_ECHOLNPAIR(" segment_mm=", cartesian_segment_mm);
+
+ // Get the raw current position as starting point
+ float raw[XYZE];
+ COPY(raw, current_position);
+
+ // Calculate and execute the segments
+ while (--segments) {
+ static millis_t next_idle_ms = millis() + 200UL;
+ thermalManager.manage_heater(); // This returns immediately if not really needed.
+ if (ELAPSED(millis(), next_idle_ms)) {
+ next_idle_ms = millis() + 200UL;
+ idle();
+ }
+ LOOP_XYZE(i) raw[i] += segment_distance[i];
+ planner.buffer_line_kinematic(raw, fr_mm_s, active_extruder, cartesian_segment_mm);
+ }
+
+ // Since segment_distance is only approximate,
+ // the final move must be to the exact destination.
+ planner.buffer_line_kinematic(destination, fr_mm_s, active_extruder, cartesian_segment_mm);
+ }
+
+#elif ENABLED(MESH_BED_LEVELING)
+
+ /**
+ * Prepare a mesh-leveled linear move in a Cartesian setup,
+ * splitting the move where it crosses mesh borders.
+ */
+ void mesh_line_to_destination(const float fr_mm_s, uint8_t x_splits=0xFF, uint8_t y_splits=0xFF) {
+ // Get current and destination cells for this line
+ int cx1 = mbl.cell_index_x(current_position[X_AXIS]),
+ cy1 = mbl.cell_index_y(current_position[Y_AXIS]),
+ cx2 = mbl.cell_index_x(destination[X_AXIS]),
+ cy2 = mbl.cell_index_y(destination[Y_AXIS]);
+ NOMORE(cx1, GRID_MAX_POINTS_X - 2);
+ NOMORE(cy1, GRID_MAX_POINTS_Y - 2);
+ NOMORE(cx2, GRID_MAX_POINTS_X - 2);
+ NOMORE(cy2, GRID_MAX_POINTS_Y - 2);
+
+ // Start and end in the same cell? No split needed.
+ if (cx1 == cx2 && cy1 == cy2) {
+ buffer_line_to_destination(fr_mm_s);
+ set_current_from_destination();
+ return;
+ }
+
+ #define MBL_SEGMENT_END(A) (current_position[A ##_AXIS] + (destination[A ##_AXIS] - current_position[A ##_AXIS]) * normalized_dist)
+
+ float normalized_dist, end[XYZE];
+ const int8_t gcx = max(cx1, cx2), gcy = max(cy1, cy2);
+
+ // Crosses on the X and not already split on this X?
+ // The x_splits flags are insurance against rounding errors.
+ if (cx2 != cx1 && TEST(x_splits, gcx)) {
+ // Split on the X grid line
+ CBI(x_splits, gcx);
+ COPY(end, destination);
+ destination[X_AXIS] = mbl.index_to_xpos[gcx];
+ normalized_dist = (destination[X_AXIS] - current_position[X_AXIS]) / (end[X_AXIS] - current_position[X_AXIS]);
+ destination[Y_AXIS] = MBL_SEGMENT_END(Y);
+ }
+ // Crosses on the Y and not already split on this Y?
+ else if (cy2 != cy1 && TEST(y_splits, gcy)) {
+ // Split on the Y grid line
+ CBI(y_splits, gcy);
+ COPY(end, destination);
+ destination[Y_AXIS] = mbl.index_to_ypos[gcy];
+ normalized_dist = (destination[Y_AXIS] - current_position[Y_AXIS]) / (end[Y_AXIS] - current_position[Y_AXIS]);
+ destination[X_AXIS] = MBL_SEGMENT_END(X);
+ }
+ else {
+ // Must already have been split on these border(s)
+ buffer_line_to_destination(fr_mm_s);
+ set_current_from_destination();
+ return;
+ }
+
+ destination[Z_AXIS] = MBL_SEGMENT_END(Z);
+ destination[E_AXIS] = MBL_SEGMENT_END(E);
+
+ // Do the split and look for more borders
+ mesh_line_to_destination(fr_mm_s, x_splits, y_splits);
+
+ // Restore destination from stack
+ COPY(destination, end);
+ mesh_line_to_destination(fr_mm_s, x_splits, y_splits);
+ }
+
+#elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
+
+ #define CELL_INDEX(A,V) ((V - bilinear_start[A##_AXIS]) * ABL_BG_FACTOR(A##_AXIS))
+
+ /**
+ * Prepare a bilinear-leveled linear move on Cartesian,
+ * splitting the move where it crosses grid borders.
+ */
+ void bilinear_line_to_destination(const float fr_mm_s, uint16_t x_splits=0xFFFF, uint16_t y_splits=0xFFFF) {
+ // Get current and destination cells for this line
+ int cx1 = CELL_INDEX(X, current_position[X_AXIS]),
+ cy1 = CELL_INDEX(Y, current_position[Y_AXIS]),
+ cx2 = CELL_INDEX(X, destination[X_AXIS]),
+ cy2 = CELL_INDEX(Y, destination[Y_AXIS]);
+ cx1 = constrain(cx1, 0, ABL_BG_POINTS_X - 2);
+ cy1 = constrain(cy1, 0, ABL_BG_POINTS_Y - 2);
+ cx2 = constrain(cx2, 0, ABL_BG_POINTS_X - 2);
+ cy2 = constrain(cy2, 0, ABL_BG_POINTS_Y - 2);
+
+ // Start and end in the same cell? No split needed.
+ if (cx1 == cx2 && cy1 == cy2) {
+ buffer_line_to_destination(fr_mm_s);
+ set_current_from_destination();
+ return;
+ }
+
+ #define LINE_SEGMENT_END(A) (current_position[A ##_AXIS] + (destination[A ##_AXIS] - current_position[A ##_AXIS]) * normalized_dist)
+
+ float normalized_dist, end[XYZE];
+ const int8_t gcx = max(cx1, cx2), gcy = max(cy1, cy2);
+
+ // Crosses on the X and not already split on this X?
+ // The x_splits flags are insurance against rounding errors.
+ if (cx2 != cx1 && TEST(x_splits, gcx)) {
+ // Split on the X grid line
+ CBI(x_splits, gcx);
+ COPY(end, destination);
+ destination[X_AXIS] = bilinear_start[X_AXIS] + ABL_BG_SPACING(X_AXIS) * gcx;
+ normalized_dist = (destination[X_AXIS] - current_position[X_AXIS]) / (end[X_AXIS] - current_position[X_AXIS]);
+ destination[Y_AXIS] = LINE_SEGMENT_END(Y);
+ }
+ // Crosses on the Y and not already split on this Y?
+ else if (cy2 != cy1 && TEST(y_splits, gcy)) {
+ // Split on the Y grid line
+ CBI(y_splits, gcy);
+ COPY(end, destination);
+ destination[Y_AXIS] = bilinear_start[Y_AXIS] + ABL_BG_SPACING(Y_AXIS) * gcy;
+ normalized_dist = (destination[Y_AXIS] - current_position[Y_AXIS]) / (end[Y_AXIS] - current_position[Y_AXIS]);
+ destination[X_AXIS] = LINE_SEGMENT_END(X);
+ }
+ else {
+ // Must already have been split on these border(s)
+ buffer_line_to_destination(fr_mm_s);
+ set_current_from_destination();
+ return;
+ }
+
+ destination[Z_AXIS] = LINE_SEGMENT_END(Z);
+ destination[E_AXIS] = LINE_SEGMENT_END(E);
+
+ // Do the split and look for more borders
+ bilinear_line_to_destination(fr_mm_s, x_splits, y_splits);
+
+ // Restore destination from stack
+ COPY(destination, end);
+ bilinear_line_to_destination(fr_mm_s, x_splits, y_splits);
+ }
+
+#endif // AUTO_BED_LEVELING_BILINEAR
+#endif // IS_CARTESIAN
+
+#if !UBL_SEGMENTED
+#if IS_KINEMATIC
+
+ /**
+ * Prepare a linear move in a DELTA or SCARA setup.
+ *
+ * This calls planner.buffer_line several times, adding
+ * small incremental moves for DELTA or SCARA.
+ *
+ * For Unified Bed Leveling (Delta or Segmented Cartesian)
+ * the ubl.prepare_segmented_line_to method replaces this.
+ */
+ inline bool prepare_kinematic_move_to(const float (&rtarget)[XYZE]) {
+
+ // Get the top feedrate of the move in the XY plane
+ const float _feedrate_mm_s = MMS_SCALED(feedrate_mm_s);
+
+ const float xdiff = rtarget[X_AXIS] - current_position[X_AXIS],
+ ydiff = rtarget[Y_AXIS] - current_position[Y_AXIS];
+
+ // If the move is only in Z/E don't split up the move
+ if (!xdiff && !ydiff) {
+ planner.buffer_line_kinematic(rtarget, _feedrate_mm_s, active_extruder);
+ return false; // caller will update current_position
+ }
+
+ // Fail if attempting move outside printable radius
+ if (!position_is_reachable(rtarget[X_AXIS], rtarget[Y_AXIS])) return true;
+
+ // Remaining cartesian distances
+ const float zdiff = rtarget[Z_AXIS] - current_position[Z_AXIS],
+ ediff = rtarget[E_AXIS] - current_position[E_AXIS];
+
+ // Get the linear distance in XYZ
+ // If the move is very short, check the E move distance
+ // No E move either? Game over.
+ float cartesian_mm = SQRT(sq(xdiff) + sq(ydiff) + sq(zdiff));
+ if (UNEAR_ZERO(cartesian_mm)) cartesian_mm = FABS(ediff);
+ if (UNEAR_ZERO(cartesian_mm)) return true;
+
+ // Minimum number of seconds to move the given distance
+ const float seconds = cartesian_mm / _feedrate_mm_s;
+
+ // The number of segments-per-second times the duration
+ // gives the number of segments
+ uint16_t segments = delta_segments_per_second * seconds;
+
+ // For SCARA minimum segment size is 0.25mm
+ #if IS_SCARA
+ NOMORE(segments, cartesian_mm * 4);
+ #endif
+
+ // At least one segment is required
+ NOLESS(segments, 1);
+
+ // The approximate length of each segment
+ const float inv_segments = 1.0 / float(segments),
+ cartesian_segment_mm = cartesian_mm * inv_segments,
+ segment_distance[XYZE] = {
+ xdiff * inv_segments,
+ ydiff * inv_segments,
+ zdiff * inv_segments,
+ ediff * inv_segments
+ };
+
+ // SERIAL_ECHOPAIR("mm=", cartesian_mm);
+ // SERIAL_ECHOPAIR(" seconds=", seconds);
+ // SERIAL_ECHOLNPAIR(" segments=", segments);
+ // SERIAL_ECHOLNPAIR(" segment_mm=", cartesian_segment_mm);
+
+ // Get the current position as starting point
+ float raw[XYZE];
+ COPY(raw, current_position);
+
+
+ // Calculate and execute the segments
+ while (--segments) {
+
+ static millis_t next_idle_ms = millis() + 200UL;
+ thermalManager.manage_heater(); // This returns immediately if not really needed.
+ if (ELAPSED(millis(), next_idle_ms)) {
+ next_idle_ms = millis() + 200UL;
+ idle();
+ }
+
+ LOOP_XYZE(i) raw[i] += segment_distance[i];
+ #if ENABLED(DELTA) && HOTENDS < 2
+ DELTA_IK(raw); // Delta can inline its kinematics
+ #else
+ inverse_kinematics(raw);
+ #endif
+
+ ADJUST_DELTA(raw); // Adjust Z if bed leveling is enabled
+
+ planner.buffer_line(delta[A_AXIS], delta[B_AXIS], delta[C_AXIS], raw[E_AXIS], _feedrate_mm_s, active_extruder, cartesian_segment_mm);
+ }
+
+ // Ensure last segment arrives at target location.
+ planner.buffer_line_kinematic(rtarget, _feedrate_mm_s, active_extruder, cartesian_segment_mm);
+
+ return false; // caller will update current_position
+ }
+
+#else // !IS_KINEMATIC
+
+ /**
+ * Prepare a linear move in a Cartesian setup.
+ *
+ * When a mesh-based leveling system is active, moves are segmented
+ * according to the configuration of the leveling system.
+ *
+ * Returns true if current_position[] was set to destination[]
+ */
+ inline bool prepare_move_to_destination_cartesian() {
+ #if HAS_MESH
+ if (planner.leveling_active && planner.leveling_active_at_z(destination[Z_AXIS])) {
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+ ubl.line_to_destination_cartesian(MMS_SCALED(feedrate_mm_s), active_extruder); // UBL's motion routine needs to know about
+ return true; // all moves, including Z-only moves.
+ #elif ENABLED(SEGMENT_LEVELED_MOVES)
+ segmented_line_to_destination(MMS_SCALED(feedrate_mm_s));
+ return false; // caller will update current_position
+ #else
+ /**
+ * For MBL and ABL-BILINEAR only segment moves when X or Y are involved.
+ * Otherwise fall through to do a direct single move.
+ */
+ if (current_position[X_AXIS] != destination[X_AXIS] || current_position[Y_AXIS] != destination[Y_AXIS]) {
+ #if ENABLED(MESH_BED_LEVELING)
+ mesh_line_to_destination(MMS_SCALED(feedrate_mm_s));
+ #elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
+ bilinear_line_to_destination(MMS_SCALED(feedrate_mm_s));
+ #endif
+ return true;
+ }
+ #endif
+ }
+ #endif // HAS_MESH
+
+ buffer_line_to_destination(MMS_SCALED(feedrate_mm_s));
+ return false; // caller will update current_position
+ }
+
+#endif // !IS_KINEMATIC
+#endif // !UBL_SEGMENTED
+
+#if ENABLED(DUAL_X_CARRIAGE)
+
+ /**
+ * Unpark the carriage, if needed
+ */
+ inline bool dual_x_carriage_unpark() {
+ if (active_extruder_parked)
+ switch (dual_x_carriage_mode) {
+
+ case DXC_FULL_CONTROL_MODE: break;
+
+ case DXC_AUTO_PARK_MODE:
+ if (current_position[E_AXIS] == destination[E_AXIS]) {
+ // This is a travel move (with no extrusion)
+ // Skip it, but keep track of the current position
+ // (so it can be used as the start of the next non-travel move)
+ if (delayed_move_time != 0xFFFFFFFFUL) {
+ set_current_from_destination();
+ NOLESS(raised_parked_position[Z_AXIS], destination[Z_AXIS]);
+ delayed_move_time = millis();
+ return true;
+ }
+ }
+ // unpark extruder: 1) raise, 2) move into starting XY position, 3) lower
+ for (uint8_t i = 0; i < 3; i++)
+ planner.buffer_line(
+ i == 0 ? raised_parked_position[X_AXIS] : current_position[X_AXIS],
+ i == 0 ? raised_parked_position[Y_AXIS] : current_position[Y_AXIS],
+ i == 2 ? current_position[Z_AXIS] : raised_parked_position[Z_AXIS],
+ current_position[E_AXIS],
+ i == 1 ? PLANNER_XY_FEEDRATE() : planner.max_feedrate_mm_s[Z_AXIS],
+ active_extruder
+ );
+ delayed_move_time = 0;
+ active_extruder_parked = false;
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Clear active_extruder_parked");
+ #endif
+ break;
+
+ case DXC_DUPLICATION_MODE:
+ if (active_extruder == 0) {
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) {
+ SERIAL_ECHOPAIR("Set planner X", inactive_extruder_x_pos);
+ SERIAL_ECHOLNPAIR(" ... Line to X", current_position[X_AXIS] + duplicate_extruder_x_offset);
+ }
+ #endif
+ // move duplicate extruder into correct duplication position.
+ planner.set_position_mm(
+ inactive_extruder_x_pos,
+ current_position[Y_AXIS],
+ current_position[Z_AXIS],
+ current_position[E_AXIS]
+ );
+ planner.buffer_line(
+ current_position[X_AXIS] + duplicate_extruder_x_offset,
+ current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS],
+ planner.max_feedrate_mm_s[X_AXIS], 1
+ );
+ SYNC_PLAN_POSITION_KINEMATIC();
+ stepper.synchronize();
+ extruder_duplication_enabled = true;
+ active_extruder_parked = false;
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Set extruder_duplication_enabled\nClear active_extruder_parked");
+ #endif
+ }
+ else {
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Active extruder not 0");
+ #endif
+ }
+ break;
+ }
+ return false;
+ }
+
+#endif // DUAL_X_CARRIAGE
+
+/**
+ * Prepare a single move and get ready for the next one
+ *
+ * This may result in several calls to planner.buffer_line to
+ * do smaller moves for DELTA, SCARA, mesh moves, etc.
+ *
+ * Make sure current_position[E] and destination[E] are good
+ * before calling or cold/lengthy extrusion may get missed.
+ */
+void prepare_move_to_destination() {
+ clamp_to_software_endstops(destination);
+
+ #if ENABLED(PREVENT_COLD_EXTRUSION) || ENABLED(PREVENT_LENGTHY_EXTRUDE)
+
+ if (!DEBUGGING(DRYRUN)) {
+ if (destination[E_AXIS] != current_position[E_AXIS]) {
+ #if ENABLED(PREVENT_COLD_EXTRUSION)
+ if (thermalManager.tooColdToExtrude(active_extruder)) {
+ current_position[E_AXIS] = destination[E_AXIS]; // Behave as if the move really took place, but ignore E part
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPGM(MSG_ERR_COLD_EXTRUDE_STOP);
+ }
+ #endif // PREVENT_COLD_EXTRUSION
+ #if ENABLED(PREVENT_LENGTHY_EXTRUDE)
+ if (FABS(destination[E_AXIS] - current_position[E_AXIS]) * planner.e_factor[active_extruder] > (EXTRUDE_MAXLENGTH)) {
+ current_position[E_AXIS] = destination[E_AXIS]; // Behave as if the move really took place, but ignore E part
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPGM(MSG_ERR_LONG_EXTRUDE_STOP);
+ }
+ #endif // PREVENT_LENGTHY_EXTRUDE
+ }
+ }
+
+ #endif
+
+ #if ENABLED(DUAL_X_CARRIAGE)
+ if (dual_x_carriage_unpark()) return;
+ #endif
+
+ if (
+ #if UBL_SEGMENTED
+ ubl.prepare_segmented_line_to(destination, MMS_SCALED(feedrate_mm_s))
+ #elif IS_KINEMATIC
+ prepare_kinematic_move_to(destination)
+ #else
+ prepare_move_to_destination_cartesian()
+ #endif
+ ) return;
+
+ set_current_from_destination();
+}
+
+#if ENABLED(ARC_SUPPORT)
+
+ #if N_ARC_CORRECTION < 1
+ #undef N_ARC_CORRECTION
+ #define N_ARC_CORRECTION 1
+ #endif
+
+ /**
+ * Plan an arc in 2 dimensions
+ *
+ * The arc is approximated by generating many small linear segments.
+ * The length of each segment is configured in MM_PER_ARC_SEGMENT (Default 1mm)
+ * Arcs should only be made relatively large (over 5mm), as larger arcs with
+ * larger segments will tend to be more efficient. Your slicer should have
+ * options for G2/G3 arc generation. In future these options may be GCode tunable.
+ */
+ void plan_arc(
+ const float (&cart)[XYZE], // Destination position
+ const float (&offset)[2], // Center of rotation relative to current_position
+ const bool clockwise // Clockwise?
+ ) {
+ #if ENABLED(CNC_WORKSPACE_PLANES)
+ AxisEnum p_axis, q_axis, l_axis;
+ switch (workspace_plane) {
+ default:
+ case PLANE_XY: p_axis = X_AXIS; q_axis = Y_AXIS; l_axis = Z_AXIS; break;
+ case PLANE_ZX: p_axis = Z_AXIS; q_axis = X_AXIS; l_axis = Y_AXIS; break;
+ case PLANE_YZ: p_axis = Y_AXIS; q_axis = Z_AXIS; l_axis = X_AXIS; break;
+ }
+ #else
+ constexpr AxisEnum p_axis = X_AXIS, q_axis = Y_AXIS, l_axis = Z_AXIS;
+ #endif
+
+ // Radius vector from center to current location
+ float r_P = -offset[0], r_Q = -offset[1];
+
+ const float radius = HYPOT(r_P, r_Q),
+ center_P = current_position[p_axis] - r_P,
+ center_Q = current_position[q_axis] - r_Q,
+ rt_X = cart[p_axis] - center_P,
+ rt_Y = cart[q_axis] - center_Q,
+ linear_travel = cart[l_axis] - current_position[l_axis],
+ extruder_travel = cart[E_AXIS] - current_position[E_AXIS];
+
+ // CCW angle of rotation between position and target from the circle center. Only one atan2() trig computation required.
+ float angular_travel = ATAN2(r_P * rt_Y - r_Q * rt_X, r_P * rt_X + r_Q * rt_Y);
+ if (angular_travel < 0) angular_travel += RADIANS(360);
+ if (clockwise) angular_travel -= RADIANS(360);
+
+ // Make a circle if the angular rotation is 0 and the target is current position
+ if (angular_travel == 0 && current_position[p_axis] == cart[p_axis] && current_position[q_axis] == cart[q_axis])
+ angular_travel = RADIANS(360);
+
+ const float flat_mm = radius * angular_travel,
+ mm_of_travel = linear_travel ? HYPOT(flat_mm, linear_travel) : FABS(flat_mm);
+ if (mm_of_travel < 0.001) return;
+
+ uint16_t segments = FLOOR(mm_of_travel / (MM_PER_ARC_SEGMENT));
+ NOLESS(segments, 1);
+
+ /**
+ * Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ * and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ * r_T = [cos(phi) -sin(phi);
+ * sin(phi) cos(phi)] * r ;
+ *
+ * For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ * defined from the circle center to the initial position. Each line segment is formed by successive
+ * vector rotations. This requires only two cos() and sin() computations to form the rotation
+ * matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ * all double numbers are single precision on the Arduino. (True double precision will not have
+ * round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ * tool precision in some cases. Therefore, arc path correction is implemented.
+ *
+ * Small angle approximation may be used to reduce computation overhead further. This approximation
+ * holds for everything, but very small circles and large MM_PER_ARC_SEGMENT values. In other words,
+ * theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ * to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ * numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ * issue for CNC machines with the single precision Arduino calculations.
+ *
+ * This approximation also allows plan_arc to immediately insert a line segment into the planner
+ * without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ * a correction, the planner should have caught up to the lag caused by the initial plan_arc overhead.
+ * This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ float raw[XYZE];
+ const float theta_per_segment = angular_travel / segments,
+ linear_per_segment = linear_travel / segments,
+ extruder_per_segment = extruder_travel / segments,
+ sin_T = theta_per_segment,
+ cos_T = 1 - 0.5 * sq(theta_per_segment); // Small angle approximation
+
+ // Initialize the linear axis
+ raw[l_axis] = current_position[l_axis];
+
+ // Initialize the extruder axis
+ raw[E_AXIS] = current_position[E_AXIS];
+
+ const float fr_mm_s = MMS_SCALED(feedrate_mm_s);
+
+ millis_t next_idle_ms = millis() + 200UL;
+
+ #if N_ARC_CORRECTION > 1
+ int8_t arc_recalc_count = N_ARC_CORRECTION;
+ #endif
+
+ for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times
+
+ thermalManager.manage_heater();
+ if (ELAPSED(millis(), next_idle_ms)) {
+ next_idle_ms = millis() + 200UL;
+ idle();
+ }
+
+ #if N_ARC_CORRECTION > 1
+ if (--arc_recalc_count) {
+ // Apply vector rotation matrix to previous r_P / 1
+ const float r_new_Y = r_P * sin_T + r_Q * cos_T;
+ r_P = r_P * cos_T - r_Q * sin_T;
+ r_Q = r_new_Y;
+ }
+ else
+ #endif
+ {
+ #if N_ARC_CORRECTION > 1
+ arc_recalc_count = N_ARC_CORRECTION;
+ #endif
+
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ // To reduce stuttering, the sin and cos could be computed at different times.
+ // For now, compute both at the same time.
+ const float cos_Ti = cos(i * theta_per_segment), sin_Ti = sin(i * theta_per_segment);
+ r_P = -offset[0] * cos_Ti + offset[1] * sin_Ti;
+ r_Q = -offset[0] * sin_Ti - offset[1] * cos_Ti;
+ }
+
+ // Update raw location
+ raw[p_axis] = center_P + r_P;
+ raw[q_axis] = center_Q + r_Q;
+ raw[l_axis] += linear_per_segment;
+ raw[E_AXIS] += extruder_per_segment;
+
+ clamp_to_software_endstops(raw);
+
+ planner.buffer_line_kinematic(raw, fr_mm_s, active_extruder);
+ }
+
+ // Ensure last segment arrives at target location.
+ planner.buffer_line_kinematic(cart, fr_mm_s, active_extruder);
+
+ // As far as the parser is concerned, the position is now == target. In reality the
+ // motion control system might still be processing the action and the real tool position
+ // in any intermediate location.
+ set_current_from_destination();
+ } // plan_arc
+
+#endif // ARC_SUPPORT
+
+#if ENABLED(BEZIER_CURVE_SUPPORT)
+
+ void plan_cubic_move(const float (&offset)[4]) {
+ cubic_b_spline(current_position, destination, offset, MMS_SCALED(feedrate_mm_s), active_extruder);
+
+ // As far as the parser is concerned, the position is now == destination. In reality the
+ // motion control system might still be processing the action and the real tool position
+ // in any intermediate location.
+ set_current_from_destination();
+ }
+
+#endif // BEZIER_CURVE_SUPPORT
+
+#if ENABLED(USE_CONTROLLER_FAN)
+
+ void controllerFan() {
+ static millis_t lastMotorOn = 0, // Last time a motor was turned on
+ nextMotorCheck = 0; // Last time the state was checked
+ const millis_t ms = millis();
+ if (ELAPSED(ms, nextMotorCheck)) {
+ nextMotorCheck = ms + 2500UL; // Not a time critical function, so only check every 2.5s
+ if (X_ENABLE_READ == X_ENABLE_ON || Y_ENABLE_READ == Y_ENABLE_ON || Z_ENABLE_READ == Z_ENABLE_ON || thermalManager.soft_pwm_amount_bed > 0
+ || E0_ENABLE_READ == E_ENABLE_ON // If any of the drivers are enabled...
+ #if E_STEPPERS > 1
+ || E1_ENABLE_READ == E_ENABLE_ON
+ #if HAS_X2_ENABLE
+ || X2_ENABLE_READ == X_ENABLE_ON
+ #endif
+ #if E_STEPPERS > 2
+ || E2_ENABLE_READ == E_ENABLE_ON
+ #if E_STEPPERS > 3
+ || E3_ENABLE_READ == E_ENABLE_ON
+ #if E_STEPPERS > 4
+ || E4_ENABLE_READ == E_ENABLE_ON
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #endif // E_STEPPERS > 1
+ ) {
+ lastMotorOn = ms; //... set time to NOW so the fan will turn on
+ }
+
+ // Fan off if no steppers have been enabled for CONTROLLERFAN_SECS seconds
+ const uint8_t speed = (lastMotorOn && PENDING(ms, lastMotorOn + (CONTROLLERFAN_SECS) * 1000UL)) ? CONTROLLERFAN_SPEED : 0;
+ controllerFanSpeed = speed;
+
+ // allows digital or PWM fan output to be used (see M42 handling)
+ WRITE(CONTROLLER_FAN_PIN, speed);
+ analogWrite(CONTROLLER_FAN_PIN, speed);
+ }
+ }
+
+#endif // USE_CONTROLLER_FAN
+
+#if ENABLED(MORGAN_SCARA)
+
+ /**
+ * Morgan SCARA Forward Kinematics. Results in cartes[].
+ * Maths and first version by QHARLEY.
+ * Integrated into Marlin and slightly restructured by Joachim Cerny.
+ */
+ void forward_kinematics_SCARA(const float &a, const float &b) {
+
+ float a_sin = sin(RADIANS(a)) * L1,
+ a_cos = cos(RADIANS(a)) * L1,
+ b_sin = sin(RADIANS(b)) * L2,
+ b_cos = cos(RADIANS(b)) * L2;
+
+ cartes[X_AXIS] = a_cos + b_cos + SCARA_OFFSET_X; //theta
+ cartes[Y_AXIS] = a_sin + b_sin + SCARA_OFFSET_Y; //theta+phi
+
+ /*
+ SERIAL_ECHOPAIR("SCARA FK Angle a=", a);
+ SERIAL_ECHOPAIR(" b=", b);
+ SERIAL_ECHOPAIR(" a_sin=", a_sin);
+ SERIAL_ECHOPAIR(" a_cos=", a_cos);
+ SERIAL_ECHOPAIR(" b_sin=", b_sin);
+ SERIAL_ECHOLNPAIR(" b_cos=", b_cos);
+ SERIAL_ECHOPAIR(" cartes[X_AXIS]=", cartes[X_AXIS]);
+ SERIAL_ECHOLNPAIR(" cartes[Y_AXIS]=", cartes[Y_AXIS]);
+ //*/
+ }
+
+ /**
+ * Morgan SCARA Inverse Kinematics. Results in delta[].
+ *
+ * See http://forums.reprap.org/read.php?185,283327
+ *
+ * Maths and first version by QHARLEY.
+ * Integrated into Marlin and slightly restructured by Joachim Cerny.
+ */
+ void inverse_kinematics(const float raw[XYZ]) {
+
+ static float C2, S2, SK1, SK2, THETA, PSI;
+
+ float sx = raw[X_AXIS] - SCARA_OFFSET_X, // Translate SCARA to standard X Y
+ sy = raw[Y_AXIS] - SCARA_OFFSET_Y; // With scaling factor.
+
+ if (L1 == L2)
+ C2 = HYPOT2(sx, sy) / L1_2_2 - 1;
+ else
+ C2 = (HYPOT2(sx, sy) - (L1_2 + L2_2)) / (2.0 * L1 * L2);
+
+ S2 = SQRT(1 - sq(C2));
+
+ // Unrotated Arm1 plus rotated Arm2 gives the distance from Center to End
+ SK1 = L1 + L2 * C2;
+
+ // Rotated Arm2 gives the distance from Arm1 to Arm2
+ SK2 = L2 * S2;
+
+ // Angle of Arm1 is the difference between Center-to-End angle and the Center-to-Elbow
+ THETA = ATAN2(SK1, SK2) - ATAN2(sx, sy);
+
+ // Angle of Arm2
+ PSI = ATAN2(S2, C2);
+
+ delta[A_AXIS] = DEGREES(THETA); // theta is support arm angle
+ delta[B_AXIS] = DEGREES(THETA + PSI); // equal to sub arm angle (inverted motor)
+ delta[C_AXIS] = raw[Z_AXIS];
+
+ /*
+ DEBUG_POS("SCARA IK", raw);
+ DEBUG_POS("SCARA IK", delta);
+ SERIAL_ECHOPAIR(" SCARA (x,y) ", sx);
+ SERIAL_ECHOPAIR(",", sy);
+ SERIAL_ECHOPAIR(" C2=", C2);
+ SERIAL_ECHOPAIR(" S2=", S2);
+ SERIAL_ECHOPAIR(" Theta=", THETA);
+ SERIAL_ECHOLNPAIR(" Phi=", PHI);
+ //*/
+ }
+
+#endif // MORGAN_SCARA
+
+#if ENABLED(TEMP_STAT_LEDS)
+
+ static bool red_led = false;
+ static millis_t next_status_led_update_ms = 0;
+
+ void handle_status_leds(void) {
+ if (ELAPSED(millis(), next_status_led_update_ms)) {
+ next_status_led_update_ms += 500; // Update every 0.5s
+ float max_temp = 0.0;
+ #if HAS_TEMP_BED
+ max_temp = MAX3(max_temp, thermalManager.degTargetBed(), thermalManager.degBed());
+ #endif
+ HOTEND_LOOP()
+ max_temp = MAX3(max_temp, thermalManager.degHotend(e), thermalManager.degTargetHotend(e));
+ const bool new_led = (max_temp > 55.0) ? true : (max_temp < 54.0) ? false : red_led;
+ if (new_led != red_led) {
+ red_led = new_led;
+ #if PIN_EXISTS(STAT_LED_RED)
+ WRITE(STAT_LED_RED_PIN, new_led ? HIGH : LOW);
+ #if PIN_EXISTS(STAT_LED_BLUE)
+ WRITE(STAT_LED_BLUE_PIN, new_led ? LOW : HIGH);
+ #endif
+ #else
+ WRITE(STAT_LED_BLUE_PIN, new_led ? HIGH : LOW);
+ #endif
+ }
+ }
+ }
+
+#endif
+
+void enable_all_steppers() {
+ #if ENABLED(AUTO_POWER_CONTROL)
+ powerManager.power_on();
+ #endif
+ enable_X();
+ enable_Y();
+ enable_Z();
+ enable_E0();
+ enable_E1();
+ enable_E2();
+ enable_E3();
+ enable_E4();
+}
+
+void disable_e_stepper(const uint8_t e) {
+ switch (e) {
+ case 0: disable_E0(); break;
+ case 1: disable_E1(); break;
+ case 2: disable_E2(); break;
+ case 3: disable_E3(); break;
+ case 4: disable_E4(); break;
+ }
+}
+
+void disable_e_steppers() {
+ disable_E0();
+ disable_E1();
+ disable_E2();
+ disable_E3();
+ disable_E4();
+}
+
+void disable_all_steppers() {
+ disable_X();
+ disable_Y();
+ disable_Z();
+ disable_e_steppers();
+}
+
+/**
+ * Manage several activities:
+ * - Check for Filament Runout
+ * - Keep the command buffer full
+ * - Check for maximum inactive time between commands
+ * - Check for maximum inactive time between stepper commands
+ * - Check if pin CHDK needs to go LOW
+ * - Check for KILL button held down
+ * - Check for HOME button held down
+ * - Check if cooling fan needs to be switched on
+ * - Check if an idle but hot extruder needs filament extruded (EXTRUDER_RUNOUT_PREVENT)
+ */
+void manage_inactivity(bool ignore_stepper_queue/*=false*/) {
+
+ #if ENABLED(FILAMENT_RUNOUT_SENSOR)
+ runout.run();
+ #endif
+
+ if (commands_in_queue < BUFSIZE) get_available_commands();
+
+ const millis_t ms = millis();
+
+ if (max_inactive_time && ELAPSED(ms, previous_move_ms + max_inactive_time)) {
+ SERIAL_ERROR_START();
+ SERIAL_ECHOLNPAIR(MSG_KILL_INACTIVE_TIME, parser.command_ptr);
+ kill(PSTR(MSG_KILLED));
+ }
+
+ // Prevent steppers timing-out in the middle of M600
+ #if ENABLED(ADVANCED_PAUSE_FEATURE) && ENABLED(PAUSE_PARK_NO_STEPPER_TIMEOUT)
+ #define MOVE_AWAY_TEST !did_pause_print
+ #else
+ #define MOVE_AWAY_TEST true
+ #endif
+
+ if (stepper_inactive_time) {
+ if (planner.has_blocks_queued())
+ previous_move_ms = ms; // reset_stepper_timeout to keep steppers powered
+ else if (MOVE_AWAY_TEST && !ignore_stepper_queue && ELAPSED(ms, previous_move_ms + stepper_inactive_time)) {
+ #if ENABLED(DISABLE_INACTIVE_X)
+ disable_X();
+ #endif
+ #if ENABLED(DISABLE_INACTIVE_Y)
+ disable_Y();
+ #endif
+ #if ENABLED(DISABLE_INACTIVE_Z)
+ disable_Z();
+ #endif
+ #if ENABLED(DISABLE_INACTIVE_E)
+ disable_e_steppers();
+ #endif
+ #if ENABLED(AUTO_BED_LEVELING_UBL) && ENABLED(ULTIPANEL) // Only needed with an LCD
+ if (ubl.lcd_map_control) ubl.lcd_map_control = defer_return_to_status = false;
+ #endif
+ }
+ }
+
+ #ifdef CHDK // Check if pin should be set to LOW after M240 set it to HIGH
+ if (chdkActive && ELAPSED(ms, chdkHigh + CHDK_DELAY)) {
+ chdkActive = false;
+ WRITE(CHDK, LOW);
+ }
+ #endif
+
+ #if HAS_KILL
+
+ // Check if the kill button was pressed and wait just in case it was an accidental
+ // key kill key press
+ // -------------------------------------------------------------------------------
+ static int killCount = 0; // make the inactivity button a bit less responsive
+ const int KILL_DELAY = 750;
+ if (!READ(KILL_PIN))
+ killCount++;
+ else if (killCount > 0)
+ killCount--;
+
+ // Exceeded threshold and we can confirm that it was not accidental
+ // KILL the machine
+ // ----------------------------------------------------------------
+ if (killCount >= KILL_DELAY) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_KILL_BUTTON);
+ kill(PSTR(MSG_KILLED));
+ }
+ #endif
+
+ #if HAS_HOME
+ // Check to see if we have to home, use poor man's debouncer
+ // ---------------------------------------------------------
+ static int homeDebounceCount = 0; // poor man's debouncing count
+ const int HOME_DEBOUNCE_DELAY = 2500;
+ if (!IS_SD_PRINTING && !READ(HOME_PIN)) {
+ if (!homeDebounceCount) {
+ enqueue_and_echo_commands_P(PSTR("G28"));
+ LCD_MESSAGEPGM(MSG_AUTO_HOME);
+ }
+ if (homeDebounceCount < HOME_DEBOUNCE_DELAY)
+ homeDebounceCount++;
+ else
+ homeDebounceCount = 0;
+ }
+ #endif
+
+ #if ENABLED(USE_CONTROLLER_FAN)
+ controllerFan(); // Check if fan should be turned on to cool stepper drivers down
+ #endif
+
+ #if ENABLED(AUTO_POWER_CONTROL)
+ powerManager.check();
+ #endif
+
+ #if ENABLED(EXTRUDER_RUNOUT_PREVENT)
+ if (thermalManager.degHotend(active_extruder) > EXTRUDER_RUNOUT_MINTEMP
+ && ELAPSED(ms, previous_move_ms + (EXTRUDER_RUNOUT_SECONDS) * 1000UL)
+ && !planner.has_blocks_queued()
+ ) {
+ #if ENABLED(SWITCHING_EXTRUDER)
+ const bool oldstatus = E0_ENABLE_READ;
+ enable_E0();
+ #else // !SWITCHING_EXTRUDER
+ bool oldstatus;
+ switch (active_extruder) {
+ default: oldstatus = E0_ENABLE_READ; enable_E0(); break;
+ #if E_STEPPERS > 1
+ case 1: oldstatus = E1_ENABLE_READ; enable_E1(); break;
+ #if E_STEPPERS > 2
+ case 2: oldstatus = E2_ENABLE_READ; enable_E2(); break;
+ #if E_STEPPERS > 3
+ case 3: oldstatus = E3_ENABLE_READ; enable_E3(); break;
+ #if E_STEPPERS > 4
+ case 4: oldstatus = E4_ENABLE_READ; enable_E4(); break;
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #endif // E_STEPPERS > 1
+ }
+ #endif // !SWITCHING_EXTRUDER
+
+ const float olde = current_position[E_AXIS];
+ current_position[E_AXIS] += EXTRUDER_RUNOUT_EXTRUDE;
+ planner.buffer_line_kinematic(current_position, MMM_TO_MMS(EXTRUDER_RUNOUT_SPEED), active_extruder);
+ current_position[E_AXIS] = olde;
+ planner.set_e_position_mm(olde);
+ stepper.synchronize();
+ #if ENABLED(SWITCHING_EXTRUDER)
+ E0_ENABLE_WRITE(oldstatus);
+ #else
+ switch (active_extruder) {
+ case 0: E0_ENABLE_WRITE(oldstatus); break;
+ #if E_STEPPERS > 1
+ case 1: E1_ENABLE_WRITE(oldstatus); break;
+ #if E_STEPPERS > 2
+ case 2: E2_ENABLE_WRITE(oldstatus); break;
+ #if E_STEPPERS > 3
+ case 3: E3_ENABLE_WRITE(oldstatus); break;
+ #if E_STEPPERS > 4
+ case 4: E4_ENABLE_WRITE(oldstatus); break;
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #endif // E_STEPPERS > 1
+ }
+ #endif // !SWITCHING_EXTRUDER
+
+ previous_move_ms = ms; // reset_stepper_timeout to keep steppers powered
+ }
+ #endif // EXTRUDER_RUNOUT_PREVENT
+
+ #if ENABLED(DUAL_X_CARRIAGE)
+ // handle delayed move timeout
+ if (delayed_move_time && ELAPSED(ms, delayed_move_time + 1000UL) && IsRunning()) {
+ // travel moves have been received so enact them
+ delayed_move_time = 0xFFFFFFFFUL; // force moves to be done
+ set_destination_from_current();
+ prepare_move_to_destination();
+ }
+ #endif
+
+ #if ENABLED(TEMP_STAT_LEDS)
+ handle_status_leds();
+ #endif
+
+ #if ENABLED(MONITOR_DRIVER_STATUS)
+ monitor_tmc_driver();
+ #endif
+
+ planner.check_axes_activity();
+}
+
+/**
+ * Standard idle routine keeps the machine alive
+ */
+void idle(
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ bool no_stepper_sleep/*=false*/
+ #endif
+) {
+ #if ENABLED(MAX7219_DEBUG)
+ Max7219_idle_tasks();
+ #endif // MAX7219_DEBUG
+
+ lcd_update();
+
+ host_keepalive();
+
+ manage_inactivity(
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ no_stepper_sleep
+ #endif
+ );
+
+ thermalManager.manage_heater();
+
+ #if ENABLED(PRINTCOUNTER)
+ print_job_timer.tick();
+ #endif
+
+ #if HAS_BUZZER && DISABLED(LCD_USE_I2C_BUZZER)
+ buzzer.tick();
+ #endif
+
+ #if ENABLED(I2C_POSITION_ENCODERS)
+ static millis_t i2cpem_next_update_ms;
+ if (planner.has_blocks_queued() && ELAPSED(millis(), i2cpem_next_update_ms)) {
+ I2CPEM.update();
+ i2cpem_next_update_ms = millis() + I2CPE_MIN_UPD_TIME_MS;
+ }
+ #endif
+
+ #if HAS_AUTO_REPORTING
+ if (!suspend_auto_report) {
+ #if ENABLED(AUTO_REPORT_TEMPERATURES)
+ thermalManager.auto_report_temperatures();
+ #endif
+ #if ENABLED(AUTO_REPORT_SD_STATUS)
+ card.auto_report_sd_status();
+ #endif
+ }
+ #endif
+}
+
+/**
+ * Kill all activity and lock the machine.
+ * After this the machine will need to be reset.
+ */
+void kill(const char* lcd_msg) {
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_KILLED);
+
+ thermalManager.disable_all_heaters();
+ disable_all_steppers();
+
+ #if ENABLED(ULTRA_LCD)
+ kill_screen(lcd_msg);
+ #else
+ UNUSED(lcd_msg);
+ #endif
+
+ _delay_ms(600); // Wait a short time (allows messages to get out before shutting down.
+ cli(); // Stop interrupts
+
+ _delay_ms(250); //Wait to ensure all interrupts routines stopped
+ thermalManager.disable_all_heaters(); //turn off heaters again
+
+ #ifdef ACTION_ON_KILL
+ SERIAL_ECHOLNPGM("//action:" ACTION_ON_KILL);
+ #endif
+
+ #if HAS_POWER_SWITCH
+ PSU_OFF();
+ #endif
+
+ suicide();
+ while (1) {
+ #if ENABLED(USE_WATCHDOG)
+ watchdog_reset();
+ #endif
+ } // Wait for reset
+}
+
+/**
+ * Turn off heaters and stop the print in progress
+ * After a stop the machine may be resumed with M999
+ */
+void stop() {
+ thermalManager.disable_all_heaters(); // 'unpause' taken care of in here
+
+ #if ENABLED(PROBING_FANS_OFF)
+ if (fans_paused) fans_pause(false); // put things back the way they were
+ #endif
+
+ if (IsRunning()) {
+ Stopped_gcode_LastN = gcode_LastN; // Save last g_code for restart
+ SERIAL_ERROR_START();
+ SERIAL_ERRORLNPGM(MSG_ERR_STOPPED);
+ LCD_MESSAGEPGM(MSG_STOPPED);
+ safe_delay(350); // allow enough time for messages to get out before stopping
+ Running = false;
+ }
+}
+
+/**
+ * Marlin entry-point: Set up before the program loop
+ * - Set up the kill pin, filament runout, power hold
+ * - Start the serial port
+ * - Print startup messages and diagnostics
+ * - Get EEPROM or default settings
+ * - Initialize managers for:
+ * • temperature
+ * • planner
+ * • watchdog
+ * • stepper
+ * • photo pin
+ * • servos
+ * • LCD controller
+ * • Digipot I2C
+ * • Z probe sled
+ * • status LEDs
+ */
+void setup() {
+
+ #if ENABLED(MAX7219_DEBUG)
+ Max7219_init();
+ #endif
+
+ #if ENABLED(DISABLE_JTAG)
+ // Disable JTAG on AT90USB chips to free up pins for IO
+ MCUCR = 0x80;
+ MCUCR = 0x80;
+ #endif
+
+ #if ENABLED(FILAMENT_RUNOUT_SENSOR)
+ runout.setup();
+ #endif
+
+ setup_killpin();
+
+ setup_powerhold();
+
+ #if HAS_STEPPER_RESET
+ disableStepperDrivers();
+ #endif
+
+ MYSERIAL0.begin(BAUDRATE);
+ SERIAL_PROTOCOLLNPGM("start");
+ SERIAL_ECHO_START();
+
+ // Prepare communication for TMC drivers
+ #if ENABLED(HAVE_TMC2130)
+ tmc_init_cs_pins();
+ #endif
+ #if ENABLED(HAVE_TMC2208)
+ tmc2208_serial_begin();
+ #endif
+
+ // Check startup - does nothing if bootloader sets MCUSR to 0
+ byte mcu = MCUSR;
+ if (mcu & 1) SERIAL_ECHOLNPGM(MSG_POWERUP);
+ if (mcu & 2) SERIAL_ECHOLNPGM(MSG_EXTERNAL_RESET);
+ if (mcu & 4) SERIAL_ECHOLNPGM(MSG_BROWNOUT_RESET);
+ if (mcu & 8) SERIAL_ECHOLNPGM(MSG_WATCHDOG_RESET);
+ if (mcu & 32) SERIAL_ECHOLNPGM(MSG_SOFTWARE_RESET);
+ MCUSR = 0;
+
+ SERIAL_ECHOPGM(MSG_MARLIN);
+ SERIAL_CHAR(' ');
+ SERIAL_ECHOLNPGM(SHORT_BUILD_VERSION);
+ SERIAL_EOL();
+
+ #if defined(STRING_DISTRIBUTION_DATE) && defined(STRING_CONFIG_H_AUTHOR)
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM(MSG_CONFIGURATION_VER);
+ SERIAL_ECHOPGM(STRING_DISTRIBUTION_DATE);
+ SERIAL_ECHOLNPGM(MSG_AUTHOR STRING_CONFIG_H_AUTHOR);
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPGM("Compiled: " __DATE__);
+ #endif
+
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPAIR(MSG_FREE_MEMORY, freeMemory());
+ SERIAL_ECHOLNPAIR(MSG_PLANNER_BUFFER_BYTES, (int)sizeof(block_t)*BLOCK_BUFFER_SIZE);
+
+ // Send "ok" after commands by default
+ for (int8_t i = 0; i < BUFSIZE; i++) send_ok[i] = true;
+
+ // Load data from EEPROM if available (or use defaults)
+ // This also updates variables in the planner, elsewhere
+ (void)settings.load();
+
+ #if HAS_M206_COMMAND
+ // Initialize current position based on home_offset
+ COPY(current_position, home_offset);
+ #else
+ ZERO(current_position);
+ #endif
+
+ // Vital to init stepper/planner equivalent for current_position
+ SYNC_PLAN_POSITION_KINEMATIC();
+
+ thermalManager.init(); // Initialize temperature loop
+
+ print_job_timer.init(); // Initial setup of print job timer
+
+ stepper.init(); // Initialize stepper, this enables interrupts!
+
+ servo_init(); // Initialize all servos, stow servo probe
+
+ #if HAS_PHOTOGRAPH
+ OUT_WRITE(PHOTOGRAPH_PIN, LOW);
+ #endif
+
+ #if HAS_CASE_LIGHT
+ case_light_on = CASE_LIGHT_DEFAULT_ON;
+ case_light_brightness = CASE_LIGHT_DEFAULT_BRIGHTNESS;
+ update_case_light();
+ #endif
+
+ #if ENABLED(SPINDLE_LASER_ENABLE)
+ OUT_WRITE(SPINDLE_LASER_ENABLE_PIN, !SPINDLE_LASER_ENABLE_INVERT); // init spindle to off
+ #if SPINDLE_DIR_CHANGE
+ OUT_WRITE(SPINDLE_DIR_PIN, SPINDLE_INVERT_DIR ? 255 : 0); // init rotation to clockwise (M3)
+ #endif
+ #if ENABLED(SPINDLE_LASER_PWM)
+ SET_OUTPUT(SPINDLE_LASER_PWM_PIN);
+ analogWrite(SPINDLE_LASER_PWM_PIN, SPINDLE_LASER_PWM_INVERT ? 255 : 0); // set to lowest speed
+ #endif
+ #endif
+
+ #if HAS_BED_PROBE
+ endstops.enable_z_probe(false);
+ #endif
+
+ #if ENABLED(USE_CONTROLLER_FAN)
+ SET_OUTPUT(CONTROLLER_FAN_PIN); //Set pin used for driver cooling fan
+ #endif
+
+ #if HAS_STEPPER_RESET
+ enableStepperDrivers();
+ #endif
+
+ #if ENABLED(DIGIPOT_I2C)
+ digipot_i2c_init();
+ #endif
+
+ #if ENABLED(DAC_STEPPER_CURRENT)
+ dac_init();
+ #endif
+
+ #if (ENABLED(Z_PROBE_SLED) || ENABLED(SOLENOID_PROBE)) && HAS_SOLENOID_1
+ OUT_WRITE(SOL1_PIN, LOW); // turn it off
+ #endif
+
+ #if HAS_HOME
+ SET_INPUT_PULLUP(HOME_PIN);
+ #endif
+
+ #if PIN_EXISTS(STAT_LED_RED)
+ OUT_WRITE(STAT_LED_RED_PIN, LOW); // turn it off
+ #endif
+
+ #if PIN_EXISTS(STAT_LED_BLUE)
+ OUT_WRITE(STAT_LED_BLUE_PIN, LOW); // turn it off
+ #endif
+
+ #if HAS_COLOR_LEDS
+ leds.setup();
+ #endif
+
+ #if ENABLED(RGB_LED) || ENABLED(RGBW_LED)
+ SET_OUTPUT(RGB_LED_R_PIN);
+ SET_OUTPUT(RGB_LED_G_PIN);
+ SET_OUTPUT(RGB_LED_B_PIN);
+ #if ENABLED(RGBW_LED)
+ SET_OUTPUT(RGB_LED_W_PIN);
+ #endif
+ #endif
+
+ #if ENABLED(MK2_MULTIPLEXER)
+ SET_OUTPUT(E_MUX0_PIN);
+ SET_OUTPUT(E_MUX1_PIN);
+ SET_OUTPUT(E_MUX2_PIN);
+ #endif
+
+ #if HAS_FANMUX
+ fanmux_init();
+ #endif
+
+ lcd_init();
+ LCD_MESSAGEPGM(WELCOME_MSG);
+
+ #if ENABLED(SHOW_BOOTSCREEN)
+ lcd_bootscreen();
+ #endif
+
+ #if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1
+ // Virtual Tools 0, 1, 2, 3 = Filament 1, 2, 3, 4, etc.
+ for (uint8_t t = 0; t < MIXING_VIRTUAL_TOOLS && t < MIXING_STEPPERS; t++)
+ for (uint8_t i = 0; i < MIXING_STEPPERS; i++)
+ mixing_virtual_tool_mix[t][i] = (t == i) ? 1.0 : 0.0;
+
+ // Remaining virtual tools are 100% filament 1
+ #if MIXING_STEPPERS < MIXING_VIRTUAL_TOOLS
+ for (uint8_t t = MIXING_STEPPERS; t < MIXING_VIRTUAL_TOOLS; t++)
+ for (uint8_t i = 0; i < MIXING_STEPPERS; i++)
+ mixing_virtual_tool_mix[t][i] = (i == 0) ? 1.0 : 0.0;
+ #endif
+
+ // Initialize mixing to tool 0 color
+ for (uint8_t i = 0; i < MIXING_STEPPERS; i++)
+ mixing_factor[i] = mixing_virtual_tool_mix[0][i];
+ #endif
+
+ #if ENABLED(BLTOUCH)
+ // Make sure any BLTouch error condition is cleared
+ bltouch_command(BLTOUCH_RESET);
+ set_bltouch_deployed(true);
+ set_bltouch_deployed(false);
+ #endif
+
+ #if ENABLED(I2C_POSITION_ENCODERS)
+ I2CPEM.init();
+ #endif
+
+ #if ENABLED(EXPERIMENTAL_I2CBUS) && I2C_SLAVE_ADDRESS > 0
+ i2c.onReceive(i2c_on_receive);
+ i2c.onRequest(i2c_on_request);
+ #endif
+
+ #if ENABLED(ENDSTOP_INTERRUPTS_FEATURE)
+ setup_endstop_interrupts();
+ #endif
+
+ #if ENABLED(SWITCHING_EXTRUDER) && !DONT_SWITCH
+ move_extruder_servo(0); // Initialize extruder servo
+ #endif
+
+ #if ENABLED(SWITCHING_NOZZLE)
+ move_nozzle_servo(0); // Initialize nozzle servo
+ #endif
+
+ #if ENABLED(PARKING_EXTRUDER)
+ #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT)
+ pe_activate_magnet(0);
+ pe_activate_magnet(1);
+ #else
+ pe_deactivate_magnet(0);
+ pe_deactivate_magnet(1);
+ #endif
+ #endif
+
+ #if ENABLED(USE_WATCHDOG)
+ watchdog_init();
+ #endif
+}
+
+/**
+ * The main Marlin program loop
+ *
+ * - Save or log commands to SD
+ * - Process available commands (if not saving)
+ * - Call heater manager
+ * - Call inactivity manager
+ * - Call endstop manager
+ * - Call LCD update
+ */
+void loop() {
+ if (commands_in_queue < BUFSIZE) get_available_commands();
+
+ #if ENABLED(SDSUPPORT)
+ card.checkautostart(false);
+ #endif
+
+ if (commands_in_queue) {
+
+ #if ENABLED(SDSUPPORT)
+
+ if (card.saving) {
+ char* command = command_queue[cmd_queue_index_r];
+ if (strstr_P(command, PSTR("M29"))) {
+ // M29 closes the file
+ card.closefile();
+ SERIAL_PROTOCOLLNPGM(MSG_FILE_SAVED);
+
+ #if !(defined(__AVR__) && defined(USBCON))
+ #if ENABLED(SERIAL_STATS_DROPPED_RX)
+ SERIAL_ECHOLNPAIR("Dropped bytes: ", customizedSerial.dropped());
+ #endif
+
+ #if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
+ SERIAL_ECHOLNPAIR("Max RX Queue Size: ", customizedSerial.rxMaxEnqueued());
+ #endif
+ #endif // !(__AVR__ && USBCON)
+
+ ok_to_send();
+ }
+ else {
+ // Write the string from the read buffer to SD
+ card.write_command(command);
+ if (card.logging)
+ process_next_command(); // The card is saving because it's logging
+ else
+ ok_to_send();
+ }
+ }
+ else
+ process_next_command();
+
+ #else
+
+ process_next_command();
+
+ #endif // SDSUPPORT
+
+ // The queue may be reset by a command handler or by code invoked by idle() within a handler
+ if (commands_in_queue) {
+ --commands_in_queue;
+ if (++cmd_queue_index_r >= BUFSIZE) cmd_queue_index_r = 0;
+ }
+ }
+ endstops.report_state();
+ idle();
+}
diff --git a/SanityCheck.h b/SanityCheck.h
new file mode 100644
index 0000000000..32b3c4c09c
--- /dev/null
+++ b/SanityCheck.h
@@ -0,0 +1,1707 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/**
+ * SanityCheck.h
+ *
+ * Test configuration values for errors at compile-time.
+ */
+
+#ifndef _SANITYCHECK_H_
+#define _SANITYCHECK_H_
+
+/**
+ * Require gcc 4.7 or newer (first included with Arduino 1.6.8) for C++11 features.
+ */
+#if __cplusplus < 201103L
+ #error "Marlin requires C++11 support (gcc >= 4.7, Arduino IDE >= 1.6.8). Please upgrade your toolchain."
+#endif
+
+/**
+ * We try our best to include sanity checks for all changed configuration
+ * directives because users have a tendency to use outdated config files with
+ * the bleeding-edge source code, but sometimes this is not enough. This check
+ * forces a minimum config file revision. Otherwise Marlin will not build.
+ */
+#if !defined(CONFIGURATION_H_VERSION) || CONFIGURATION_H_VERSION < REQUIRED_CONFIGURATION_H_VERSION
+ #error "You are using an old Configuration.h file, update it before building Marlin."
+#endif
+
+#if !defined(CONFIGURATION_ADV_H_VERSION) || CONFIGURATION_ADV_H_VERSION < REQUIRED_CONFIGURATION_ADV_H_VERSION
+ #error "You are using an old Configuration_adv.h file, update it before building Marlin."
+#endif
+
+/**
+ * Warnings for old configurations
+ */
+#if !defined(X_BED_SIZE) || !defined(Y_BED_SIZE)
+ #error "X_BED_SIZE and Y_BED_SIZE are now required! Please update your configuration."
+#elif WATCH_TEMP_PERIOD > 500
+ #error "WATCH_TEMP_PERIOD now uses seconds instead of milliseconds."
+#elif DISABLED(THERMAL_PROTECTION_HOTENDS) && (defined(WATCH_TEMP_PERIOD) || defined(THERMAL_PROTECTION_PERIOD))
+ #error "Thermal Runaway Protection for hotends is now enabled with THERMAL_PROTECTION_HOTENDS."
+#elif DISABLED(THERMAL_PROTECTION_BED) && defined(THERMAL_PROTECTION_BED_PERIOD)
+ #error "Thermal Runaway Protection for the bed is now enabled with THERMAL_PROTECTION_BED."
+#elif (CORE_IS_XZ || CORE_IS_YZ) && ENABLED(Z_LATE_ENABLE)
+ #error "Z_LATE_ENABLE can't be used with COREXZ, COREZX, COREYZ, or COREZY."
+#elif defined(X_HOME_RETRACT_MM)
+ #error "[XYZ]_HOME_RETRACT_MM settings have been renamed [XYZ]_HOME_BUMP_MM."
+#elif defined(SDCARDDETECTINVERTED)
+ #error "SDCARDDETECTINVERTED is now SD_DETECT_INVERTED. Please update your configuration."
+#elif defined(BTENABLED)
+ #error "BTENABLED is now BLUETOOTH. Please update your configuration."
+#elif defined(CUSTOM_MENDEL_NAME)
+ #error "CUSTOM_MENDEL_NAME is now CUSTOM_MACHINE_NAME. Please update your configuration."
+#elif defined(HAS_AUTOMATIC_VERSIONING)
+ #error "HAS_AUTOMATIC_VERSIONING is now USE_AUTOMATIC_VERSIONING. Please update your configuration."
+#elif defined(SDSLOW)
+ #error "SDSLOW deprecated. Set SPI_SPEED to SPI_HALF_SPEED instead."
+#elif defined(SDEXTRASLOW)
+ #error "SDEXTRASLOW deprecated. Set SPI_SPEED to SPI_QUARTER_SPEED instead."
+#elif defined(FILAMENT_SENSOR)
+ #error "FILAMENT_SENSOR is now FILAMENT_WIDTH_SENSOR. Please update your configuration."
+#elif defined(ENDSTOPPULLUP_FIL_RUNOUT)
+ #error "ENDSTOPPULLUP_FIL_RUNOUT is now FIL_RUNOUT_PULLUP. Please update your configuration."
+#elif defined(DISABLE_MAX_ENDSTOPS) || defined(DISABLE_MIN_ENDSTOPS)
+ #error "DISABLE_MAX_ENDSTOPS and DISABLE_MIN_ENDSTOPS deprecated. Use individual USE_*_PLUG options instead."
+#elif defined(LANGUAGE_INCLUDE)
+ #error "LANGUAGE_INCLUDE has been replaced by LCD_LANGUAGE. Please update your configuration."
+#elif defined(EXTRUDER_OFFSET_X) || defined(EXTRUDER_OFFSET_Y)
+ #error "EXTRUDER_OFFSET_[XY] is deprecated. Use HOTEND_OFFSET_[XY] instead."
+#elif defined(PID_PARAMS_PER_EXTRUDER)
+ #error "PID_PARAMS_PER_EXTRUDER is deprecated. Use PID_PARAMS_PER_HOTEND instead."
+#elif defined(EXTRUDER_WATTS) || defined(BED_WATTS)
+ #error "EXTRUDER_WATTS and BED_WATTS are deprecated. Remove them from your configuration."
+#elif defined(SERVO_ENDSTOP_ANGLES)
+ #error "SERVO_ENDSTOP_ANGLES is deprecated. Use Z_SERVO_ANGLES instead."
+#elif defined(X_ENDSTOP_SERVO_NR) || defined(Y_ENDSTOP_SERVO_NR)
+ #error "X_ENDSTOP_SERVO_NR and Y_ENDSTOP_SERVO_NR are deprecated and should be removed."
+#elif defined(DEFAULT_XYJERK)
+ #error "DEFAULT_XYJERK is deprecated. Use DEFAULT_XJERK and DEFAULT_YJERK instead."
+#elif defined(XY_TRAVEL_SPEED)
+ #error "XY_TRAVEL_SPEED is deprecated. Use XY_PROBE_SPEED instead."
+#elif defined(PROBE_SERVO_DEACTIVATION_DELAY)
+ #error "PROBE_SERVO_DEACTIVATION_DELAY is deprecated. Use DEACTIVATE_SERVOS_AFTER_MOVE instead."
+#elif defined(SERVO_DEACTIVATION_DELAY)
+ #error "SERVO_DEACTIVATION_DELAY is deprecated. Use SERVO_DELAY instead."
+#elif ENABLED(FILAMENTCHANGEENABLE)
+ #error "FILAMENTCHANGEENABLE is now ADVANCED_PAUSE_FEATURE. Please update your configuration."
+#elif ENABLED(FILAMENT_CHANGE_FEATURE)
+ #error "FILAMENT_CHANGE_FEATURE is now ADVANCED_PAUSE_FEATURE. Please update your configuration."
+#elif defined(FILAMENT_CHANGE_X_POS) || defined(FILAMENT_CHANGE_Y_POS)
+ #error "FILAMENT_CHANGE_[XY]_POS is now set with NOZZLE_PARK_POINT. Please update your configuration."
+#elif defined(FILAMENT_CHANGE_Z_ADD)
+ #error "FILAMENT_CHANGE_Z_ADD is now set with NOZZLE_PARK_POINT. Please update your configuration."
+#elif defined(FILAMENT_CHANGE_XY_FEEDRATE)
+ #error "FILAMENT_CHANGE_XY_FEEDRATE is now NOZZLE_PARK_XY_FEEDRATE. Please update your configuration."
+#elif defined(FILAMENT_CHANGE_Z_FEEDRATE)
+ #error "FILAMENT_CHANGE_Z_FEEDRATE is now NOZZLE_PARK_Z_FEEDRATE. Please update your configuration."
+#elif defined(PAUSE_PARK_X_POS) || defined(PAUSE_PARK_Y_POS)
+ #error "PAUSE_PARK_[XY]_POS is now set with NOZZLE_PARK_POINT. Please update your configuration."
+#elif defined(PAUSE_PARK_Z_ADD)
+ #error "PAUSE_PARK_Z_ADD is now set with NOZZLE_PARK_POINT. Please update your configuration."
+#elif defined(PAUSE_PARK_XY_FEEDRATE)
+ #error "PAUSE_PARK_XY_FEEDRATE is now NOZZLE_PARK_XY_FEEDRATE. Please update your configuration."
+#elif defined(PAUSE_PARK_Z_FEEDRATE)
+ #error "PAUSE_PARK_Z_FEEDRATE is now NOZZLE_PARK_Z_FEEDRATE. Please update your configuration."
+#elif defined(FILAMENT_CHANGE_RETRACT_FEEDRATE)
+ #error "FILAMENT_CHANGE_RETRACT_FEEDRATE is now PAUSE_PARK_RETRACT_FEEDRATE. Please update your configuration."
+#elif defined(FILAMENT_CHANGE_RETRACT_LENGTH)
+ #error "FILAMENT_CHANGE_RETRACT_LENGTH is now PAUSE_PARK_RETRACT_LENGTH. Please update your configuration."
+#elif defined(FILAMENT_CHANGE_EXTRUDE_FEEDRATE)
+ #error "FILAMENT_CHANGE_EXTRUDE_FEEDRATE is now ADVANCED_PAUSE_EXTRUDE_FEEDRATE. Please update your configuration."
+#elif defined(FILAMENT_CHANGE_EXTRUDE_LENGTH)
+ #error "FILAMENT_CHANGE_EXTRUDE_LENGTH is now ADVANCED_PAUSE_EXTRUDE_LENGTH. Please update your configuration."
+#elif defined(FILAMENT_CHANGE_NOZZLE_TIMEOUT)
+ #error "FILAMENT_CHANGE_NOZZLE_TIMEOUT is now PAUSE_PARK_NOZZLE_TIMEOUT. Please update your configuration."
+#elif defined(FILAMENT_CHANGE_NUMBER_OF_ALERT_BEEPS)
+ #error "FILAMENT_CHANGE_NUMBER_OF_ALERT_BEEPS is now FILAMENT_CHANGE_ALERT_BEEPS. Please update your configuration."
+#elif ENABLED(FILAMENT_CHANGE_NO_STEPPER_TIMEOUT)
+ #error "FILAMENT_CHANGE_NO_STEPPER_TIMEOUT is now PAUSE_PARK_NO_STEPPER_TIMEOUT. Please update your configuration."
+#elif defined(PLA_PREHEAT_HOTEND_TEMP)
+ #error "PLA_PREHEAT_HOTEND_TEMP is now PREHEAT_1_TEMP_HOTEND. Please update your configuration."
+#elif defined(PLA_PREHEAT_HPB_TEMP)
+ #error "PLA_PREHEAT_HPB_TEMP is now PREHEAT_1_TEMP_BED. Please update your configuration."
+#elif defined(PLA_PREHEAT_FAN_SPEED)
+ #error "PLA_PREHEAT_FAN_SPEED is now PREHEAT_1_FAN_SPEED. Please update your configuration."
+#elif defined(ABS_PREHEAT_HOTEND_TEMP)
+ #error "ABS_PREHEAT_HOTEND_TEMP is now PREHEAT_2_TEMP_HOTEND. Please update your configuration."
+#elif defined(ABS_PREHEAT_HPB_TEMP)
+ #error "ABS_PREHEAT_HPB_TEMP is now PREHEAT_2_TEMP_BED. Please update your configuration."
+#elif defined(ABS_PREHEAT_FAN_SPEED)
+ #error "ABS_PREHEAT_FAN_SPEED is now PREHEAT_2_FAN_SPEED. Please update your configuration."
+#elif defined(ENDSTOPS_ONLY_FOR_HOMING)
+ #error "ENDSTOPS_ONLY_FOR_HOMING is deprecated. Use (disable) ENDSTOPS_ALWAYS_ON_DEFAULT instead."
+#elif defined(HOMING_FEEDRATE)
+ #error "HOMING_FEEDRATE is deprecated. Set individual rates with HOMING_FEEDRATE_(XY|Z|E) instead."
+#elif defined(MANUAL_HOME_POSITIONS)
+ #error "MANUAL_HOME_POSITIONS is deprecated. Set MANUAL_[XYZ]_HOME_POS as-needed instead."
+#elif defined(PID_ADD_EXTRUSION_RATE)
+ #error "PID_ADD_EXTRUSION_RATE is now PID_EXTRUSION_SCALING and is DISABLED by default. Are you sure you want to use this option? Please update your configuration."
+#elif defined(Z_RAISE_BEFORE_HOMING)
+ #error "Z_RAISE_BEFORE_HOMING is now Z_HOMING_HEIGHT. Please update your configuration."
+#elif defined(MIN_Z_HEIGHT_FOR_HOMING)
+ #error "MIN_Z_HEIGHT_FOR_HOMING is now Z_HOMING_HEIGHT. Please update your configuration."
+#elif defined(Z_RAISE_BEFORE_PROBING) || defined(Z_RAISE_AFTER_PROBING)
+ #error "Z_RAISE_(BEFORE|AFTER)_PROBING are deprecated. Use Z_CLEARANCE_DEPLOY_PROBE and Z_AFTER_PROBING instead."
+#elif defined(Z_RAISE_PROBE_DEPLOY_STOW) || defined(Z_RAISE_BETWEEN_PROBINGS)
+ #error "Z_RAISE_PROBE_DEPLOY_STOW and Z_RAISE_BETWEEN_PROBINGS are now Z_CLEARANCE_DEPLOY_PROBE and Z_CLEARANCE_BETWEEN_PROBES. Please update your configuration."
+#elif defined(Z_PROBE_DEPLOY_HEIGHT) || defined(Z_PROBE_TRAVEL_HEIGHT)
+ #error "Z_PROBE_DEPLOY_HEIGHT and Z_PROBE_TRAVEL_HEIGHT are now Z_CLEARANCE_DEPLOY_PROBE and Z_CLEARANCE_BETWEEN_PROBES. Please update your configuration."
+#elif defined(MANUAL_BED_LEVELING)
+ #error "MANUAL_BED_LEVELING is now LCD_BED_LEVELING. Please update your configuration."
+#elif defined(MESH_HOME_SEARCH_Z)
+ #error "MESH_HOME_SEARCH_Z is now LCD_PROBE_Z_RANGE. Please update your configuration."
+#elif defined(MANUAL_PROBE_Z_RANGE)
+ #error "MANUAL_PROBE_Z_RANGE is now LCD_PROBE_Z_RANGE. Please update your configuration."
+#elif !defined(MIN_STEPS_PER_SEGMENT)
+ #error Please replace "const int dropsegments" with "#define MIN_STEPS_PER_SEGMENT" (and increase by 1) in Configuration_adv.h.
+#elif defined(PREVENT_DANGEROUS_EXTRUDE)
+ #error "PREVENT_DANGEROUS_EXTRUDE is now PREVENT_COLD_EXTRUSION. Please update your configuration."
+#elif defined(SCARA)
+ #error "SCARA is now MORGAN_SCARA. Please update your configuration."
+#elif defined(ENABLE_AUTO_BED_LEVELING)
+ #error "ENABLE_AUTO_BED_LEVELING is deprecated. Specify AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_BILINEAR, or AUTO_BED_LEVELING_3POINT."
+#elif defined(AUTO_BED_LEVELING_FEATURE)
+ #error "AUTO_BED_LEVELING_FEATURE is deprecated. Specify AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_BILINEAR, or AUTO_BED_LEVELING_3POINT."
+#elif defined(ABL_GRID_POINTS)
+ #error "ABL_GRID_POINTS is now GRID_MAX_POINTS_X and GRID_MAX_POINTS_Y. Please update your configuration."
+#elif defined(ABL_GRID_POINTS_X) || defined(ABL_GRID_POINTS_Y)
+ #error "ABL_GRID_POINTS_[XY] is now GRID_MAX_POINTS_[XY]. Please update your configuration."
+#elif defined(ABL_GRID_MAX_POINTS_X) || defined(ABL_GRID_MAX_POINTS_Y)
+ #error "ABL_GRID_MAX_POINTS_[XY] is now GRID_MAX_POINTS_[XY]. Please update your configuration."
+#elif defined(MESH_NUM_X_POINTS) || defined(MESH_NUM_Y_POINTS)
+ #error "MESH_NUM_[XY]_POINTS is now GRID_MAX_POINTS_[XY]. Please update your configuration."
+#elif defined(UBL_MESH_NUM_X_POINTS) || defined(UBL_MESH_NUM_Y_POINTS)
+ #error "UBL_MESH_NUM_[XY]_POINTS is now GRID_MAX_POINTS_[XY]. Please update your configuration."
+#elif defined(UBL_G26_MESH_VALIDATION)
+ #error "UBL_G26_MESH_VALIDATION is now G26_MESH_VALIDATION. Please update your configuration."
+#elif defined(UBL_MESH_EDIT_ENABLED)
+ #error "UBL_MESH_EDIT_ENABLED is now G26_MESH_VALIDATION. Please update your configuration."
+#elif defined(UBL_MESH_EDITING)
+ #error "UBL_MESH_EDITING is now G26_MESH_VALIDATION. Please update your configuration."
+#elif defined(BLTOUCH_HEATERS_OFF)
+ #error "BLTOUCH_HEATERS_OFF is now PROBING_HEATERS_OFF. Please update your configuration."
+#elif defined(BEEPER)
+ #error "BEEPER is now BEEPER_PIN. Please update your pins definitions."
+#elif defined(SDCARDDETECT)
+ #error "SDCARDDETECT is now SD_DETECT_PIN. Please update your pins definitions."
+#elif defined(STAT_LED_RED) || defined(STAT_LED_BLUE)
+ #error "STAT_LED_RED/STAT_LED_BLUE are now STAT_LED_RED_PIN/STAT_LED_BLUE_PIN. Please update your pins definitions."
+#elif defined(LCD_PIN_BL)
+ #error "LCD_PIN_BL is now LCD_BACKLIGHT_PIN. Please update your pins definitions."
+#elif defined(LCD_PIN_RESET)
+ #error "LCD_PIN_RESET is now LCD_RESET_PIN. Please update your pins definitions."
+#elif defined(EXTRUDER_0_AUTO_FAN_PIN) || defined(EXTRUDER_1_AUTO_FAN_PIN) || defined(EXTRUDER_2_AUTO_FAN_PIN) || defined(EXTRUDER_3_AUTO_FAN_PIN)
+ #error "EXTRUDER_[0123]_AUTO_FAN_PIN is now E[0123]_AUTO_FAN_PIN. Please update your Configuration_adv.h."
+#elif defined(min_software_endstops) || defined(max_software_endstops)
+ #error "(min|max)_software_endstops are now (MIN|MAX)_SOFTWARE_ENDSTOPS. Please update your configuration."
+#elif ENABLED(Z_PROBE_SLED) && defined(SLED_PIN)
+ #error "Replace SLED_PIN with SOL1_PIN (applies to both Z_PROBE_SLED and SOLENOID_PROBE)."
+#elif defined(CONTROLLERFAN_PIN)
+ #error "CONTROLLERFAN_PIN is now CONTROLLER_FAN_PIN, enabled with USE_CONTROLLER_FAN. Please update your Configuration_adv.h."
+#elif defined(MIN_RETRACT)
+ #error "MIN_RETRACT is now MIN_AUTORETRACT and MAX_AUTORETRACT. Please update your Configuration_adv.h."
+#elif defined(ADVANCE)
+ #error "ADVANCE was removed in Marlin 1.1.6. Please use LIN_ADVANCE."
+#elif defined(LIN_ADVANCE_E_D_RATIO)
+ #error "LIN_ADVANCE (1.5) no longer uses LIN_ADVANCE_E_D_RATIO. Check your configuration."
+#elif defined(NEOPIXEL_RGBW_LED)
+ #error "NEOPIXEL_RGBW_LED is now NEOPIXEL_LED. Please update your configuration."
+#elif ENABLED(DELTA) && defined(DELTA_PROBEABLE_RADIUS)
+ #error "Remove DELTA_PROBEABLE_RADIUS and use MIN_PROBE_EDGE to inset the probe area instead."
+#elif defined(UBL_MESH_INSET)
+ #error "UBL_MESH_INSET is now just MESH_INSET. Please update your configuration."
+#elif defined(UBL_MESH_MIN_X) || defined(UBL_MESH_MIN_Y) || defined(UBL_MESH_MAX_X) || defined(UBL_MESH_MAX_Y)
+ #error "UBL_MESH_(MIN|MAX)_[XY] is now just MESH_(MIN|MAX)_[XY]. Please update your configuration."
+#elif defined(ABL_PROBE_PT_1_X) || defined(ABL_PROBE_PT_1_Y) || defined(ABL_PROBE_PT_2_X) || defined(ABL_PROBE_PT_2_Y) || defined(ABL_PROBE_PT_3_X) || defined(ABL_PROBE_PT_3_Y)
+ #error "ABL_PROBE_PT_[123]_[XY] is now PROBE_PT_[123]_[XY]. Please update your configuration."
+#elif defined(UBL_PROBE_PT_1_X) || defined(UBL_PROBE_PT_1_Y) || defined(UBL_PROBE_PT_2_X) || defined(UBL_PROBE_PT_2_Y) || defined(UBL_PROBE_PT_3_X) || defined(UBL_PROBE_PT_3_Y)
+ #error "UBL_PROBE_PT_[123]_[XY] is now PROBE_PT_[123]_[XY]. Please update your configuration."
+#elif defined(ENABLE_MESH_EDIT_GFX_OVERLAY)
+ #error "ENABLE_MESH_EDIT_GFX_OVERLAY is now MESH_EDIT_GFX_OVERLAY. Please update your configuration."
+#elif defined(BABYSTEP_ZPROBE_GFX_REVERSE)
+ #error "BABYSTEP_ZPROBE_GFX_REVERSE is now set by OVERLAY_GFX_REVERSE. Please update your configurations."
+#elif defined(UBL_GRANULAR_SEGMENTATION_FOR_CARTESIAN)
+ #error "UBL_GRANULAR_SEGMENTATION_FOR_CARTESIAN is now SEGMENT_LEVELED_MOVES. Please update your configuration."
+#elif HAS_PID_HEATING && (defined(K1) || !defined(PID_K1))
+ #error "K1 is now PID_K1. Please update your configuration."
+#elif defined(PROBE_DOUBLE_TOUCH)
+ #error "PROBE_DOUBLE_TOUCH is now MULTIPLE_PROBING. Please update your configuration."
+#elif defined(ANET_KEYPAD_LCD)
+ #error "ANET_KEYPAD_LCD is now ZONESTAR_LCD. Please update your configuration."
+#elif defined(LCD_I2C_SAINSMART_YWROBOT)
+ #error "LCD_I2C_SAINSMART_YWROBOT is now LCD_SAINSMART_I2C_(1602|2004). Please update your configuration."
+#elif defined(MEASURED_LOWER_LIMIT) || defined(MEASURED_UPPER_LIMIT)
+ #error "MEASURED_(UPPER|LOWER)_LIMIT is now FILWIDTH_ERROR_MARGIN. Please update your configuration."
+#elif defined(HAVE_TMCDRIVER)
+ #error "HAVE_TMCDRIVER is now HAVE_TMC26X. Please update your Configuration_adv.h."
+#elif defined(X_IS_TMC) || defined(X2_IS_TMC) || defined(Y_IS_TMC) || defined(Y2_IS_TMC) || defined(Z_IS_TMC) || defined(Z2_IS_TMC) \
+ || defined(E0_IS_TMC) || defined(E1_IS_TMC) || defined(E2_IS_TMC) || defined(E3_IS_TMC) || defined(E4_IS_TMC)
+ #error "[AXIS]_IS_TMC is now [AXIS]_IS_TMC26X. Please update your Configuration_adv.h."
+#elif defined(AUTOMATIC_CURRENT_CONTROL)
+ #error "AUTOMATIC_CURRENT_CONTROL is now MONITOR_DRIVER_STATUS. Please update your configuration."
+#endif
+
+/**
+ * Marlin release, version and default string
+ */
+#ifndef SHORT_BUILD_VERSION
+ #error "SHORT_BUILD_VERSION must be specified."
+#elif !defined(DETAILED_BUILD_VERSION)
+ #error "BUILD_VERSION must be specified."
+#elif !defined(STRING_DISTRIBUTION_DATE)
+ #error "STRING_DISTRIBUTION_DATE must be specified."
+#elif !defined(PROTOCOL_VERSION)
+ #error "PROTOCOL_VERSION must be specified."
+#elif !defined(MACHINE_NAME)
+ #error "MACHINE_NAME must be specified."
+#elif !defined(SOURCE_CODE_URL)
+ #error "SOURCE_CODE_URL must be specified."
+#elif !defined(DEFAULT_MACHINE_UUID)
+ #error "DEFAULT_MACHINE_UUID must be specified."
+#elif !defined(WEBSITE_URL)
+ #error "WEBSITE_URL must be specified."
+#endif
+
+/**
+ * Serial
+ */
+#if !(defined(__AVR__) && defined(USBCON))
+ #if ENABLED(SERIAL_XON_XOFF) && RX_BUFFER_SIZE < 1024
+ #error "SERIAL_XON_XOFF requires RX_BUFFER_SIZE >= 1024 for reliable transfers without drops."
+ #elif RX_BUFFER_SIZE && (RX_BUFFER_SIZE < 2 || !IS_POWER_OF_2(RX_BUFFER_SIZE))
+ #error "RX_BUFFER_SIZE must be a power of 2 greater than 1."
+ #elif TX_BUFFER_SIZE && (TX_BUFFER_SIZE < 2 || TX_BUFFER_SIZE > 256 || !IS_POWER_OF_2(TX_BUFFER_SIZE))
+ #error "TX_BUFFER_SIZE must be 0, a power of 2 greater than 1, and no greater than 256."
+ #elif ENABLED(BLUETOOTH)
+ #error "BLUETOOTH is only supported with AT90USB."
+ #endif
+#elif ENABLED(SERIAL_XON_XOFF) || ENABLED(SERIAL_STATS_MAX_RX_QUEUED) || ENABLED(SERIAL_STATS_DROPPED_RX)
+ #error "SERIAL_XON_XOFF and SERIAL_STATS_* features not supported on USB-native AVR devices."
+#endif
+
+#if SERIAL_PORT > 7
+ #error "Set SERIAL_PORT to the port on your board. Usually this is 0."
+#endif
+
+/**
+ * Dual Stepper Drivers
+ */
+#if ENABLED(X_DUAL_STEPPER_DRIVERS) && ENABLED(DUAL_X_CARRIAGE)
+ #error "DUAL_X_CARRIAGE is not compatible with X_DUAL_STEPPER_DRIVERS."
+#elif ENABLED(X_DUAL_STEPPER_DRIVERS) && (!HAS_X2_ENABLE || !HAS_X2_STEP || !HAS_X2_DIR)
+ #error "X_DUAL_STEPPER_DRIVERS requires X2 pins (and an extra E plug)."
+#elif ENABLED(Y_DUAL_STEPPER_DRIVERS) && (!HAS_Y2_ENABLE || !HAS_Y2_STEP || !HAS_Y2_DIR)
+ #error "Y_DUAL_STEPPER_DRIVERS requires Y2 pins (and an extra E plug)."
+#elif ENABLED(Z_DUAL_STEPPER_DRIVERS) && (!HAS_Z2_ENABLE || !HAS_Z2_STEP || !HAS_Z2_DIR)
+ #error "Z_DUAL_STEPPER_DRIVERS requires Z2 pins (and an extra E plug)."
+#endif
+
+/**
+ * Validate that the bed size fits
+ */
+static_assert(X_MAX_LENGTH >= X_BED_SIZE && Y_MAX_LENGTH >= Y_BED_SIZE,
+ "Movement bounds ([XY]_MIN_POS, [XY]_MAX_POS) are too narrow to contain [XY]_BED_SIZE.");
+
+/**
+ * Granular software endstops (Marlin >= 1.1.7)
+ */
+#if ENABLED(MIN_SOFTWARE_ENDSTOPS) && DISABLED(MIN_SOFTWARE_ENDSTOP_Z)
+ #if IS_KINEMATIC
+ #error "MIN_SOFTWARE_ENDSTOPS on DELTA/SCARA also requires MIN_SOFTWARE_ENDSTOP_Z."
+ #elif DISABLED(MIN_SOFTWARE_ENDSTOP_X) && DISABLED(MIN_SOFTWARE_ENDSTOP_Y)
+ #error "MIN_SOFTWARE_ENDSTOPS requires at least one of the MIN_SOFTWARE_ENDSTOP_[XYZ] options."
+ #endif
+#endif
+
+#if ENABLED(MAX_SOFTWARE_ENDSTOPS) && DISABLED(MAX_SOFTWARE_ENDSTOP_Z)
+ #if IS_KINEMATIC
+ #error "MAX_SOFTWARE_ENDSTOPS on DELTA/SCARA also requires MAX_SOFTWARE_ENDSTOP_Z."
+ #elif DISABLED(MAX_SOFTWARE_ENDSTOP_X) && DISABLED(MAX_SOFTWARE_ENDSTOP_Y)
+ #error "MAX_SOFTWARE_ENDSTOPS requires at least one of the MAX_SOFTWARE_ENDSTOP_[XYZ] options."
+ #endif
+#endif
+
+/**
+ * Progress Bar
+ */
+#if ENABLED(LCD_PROGRESS_BAR)
+ #if DISABLED(SDSUPPORT) && DISABLED(LCD_SET_PROGRESS_MANUALLY)
+ #error "LCD_PROGRESS_BAR requires SDSUPPORT or LCD_SET_PROGRESS_MANUALLY."
+ #elif DISABLED(ULTRA_LCD)
+ #error "LCD_PROGRESS_BAR requires a character LCD."
+ #elif ENABLED(DOGLCD)
+ #error "LCD_PROGRESS_BAR does not apply to graphical displays."
+ #elif ENABLED(FILAMENT_LCD_DISPLAY)
+ #error "LCD_PROGRESS_BAR and FILAMENT_LCD_DISPLAY are not fully compatible. Comment out this line to use both."
+ #endif
+#elif ENABLED(LCD_SET_PROGRESS_MANUALLY) && DISABLED(DOGLCD)
+ #error "LCD_SET_PROGRESS_MANUALLY requires LCD_PROGRESS_BAR or Graphical LCD."
+#endif
+
+/**
+ * Custom Boot and Status screens
+ */
+#if DISABLED(DOGLCD) && (ENABLED(SHOW_CUSTOM_BOOTSCREEN) || ENABLED(CUSTOM_STATUS_SCREEN_IMAGE))
+ #error "Graphical LCD is required for SHOW_CUSTOM_BOOTSCREEN and CUSTOM_STATUS_SCREEN_IMAGE."
+#endif
+
+/**
+ * SD File Sorting
+ */
+#if ENABLED(SDCARD_SORT_ALPHA)
+ #if SDSORT_LIMIT > 256
+ #error "SDSORT_LIMIT must be 256 or smaller."
+ #elif SDSORT_LIMIT < 10
+ #error "SDSORT_LIMIT should be greater than 9 to be useful."
+ #elif DISABLED(SDSORT_USES_RAM)
+ #if ENABLED(SDSORT_DYNAMIC_RAM)
+ #error "SDSORT_DYNAMIC_RAM requires SDSORT_USES_RAM (which reads the directory into RAM)."
+ #elif ENABLED(SDSORT_CACHE_NAMES)
+ #error "SDSORT_CACHE_NAMES requires SDSORT_USES_RAM (which reads the directory into RAM)."
+ #endif
+ #endif
+
+ #if ENABLED(SDSORT_CACHE_NAMES) && DISABLED(SDSORT_DYNAMIC_RAM)
+ #if SDSORT_CACHE_VFATS < 2
+ #error "SDSORT_CACHE_VFATS must be 2 or greater!"
+ #elif SDSORT_CACHE_VFATS > MAX_VFAT_ENTRIES
+ #undef SDSORT_CACHE_VFATS
+ #define SDSORT_CACHE_VFATS MAX_VFAT_ENTRIES
+ #warning "SDSORT_CACHE_VFATS was reduced to MAX_VFAT_ENTRIES!"
+ #endif
+ #endif
+#endif
+
+/**
+ * I2C Position Encoders
+ */
+#if ENABLED(I2C_POSITION_ENCODERS)
+ #if DISABLED(BABYSTEPPING) || DISABLED(BABYSTEP_XY)
+ #error "I2C_POSITION_ENCODERS requires BABYSTEPPING and BABYSTEP_XY."
+ #elif !WITHIN(I2CPE_ENCODER_CNT, 1, 5)
+ #error "I2CPE_ENCODER_CNT must be between 1 and 5."
+ #endif
+#endif
+
+/**
+ * Babystepping
+ */
+#if ENABLED(BABYSTEPPING)
+ #if ENABLED(SCARA)
+ #error "BABYSTEPPING is not implemented for SCARA yet."
+ #elif ENABLED(DELTA) && ENABLED(BABYSTEP_XY)
+ #error "BABYSTEPPING only implemented for Z axis on deltabots."
+ #elif ENABLED(BABYSTEP_ZPROBE_OFFSET) && ENABLED(MESH_BED_LEVELING)
+ #error "MESH_BED_LEVELING and BABYSTEP_ZPROBE_OFFSET is not a valid combination"
+ #elif ENABLED(BABYSTEP_ZPROBE_OFFSET) && !HAS_BED_PROBE
+ #error "BABYSTEP_ZPROBE_OFFSET requires a probe."
+ #elif ENABLED(BABYSTEP_ZPROBE_GFX_OVERLAY) && !ENABLED(DOGLCD)
+ #error "BABYSTEP_ZPROBE_GFX_OVERLAY requires a DOGLCD."
+ #elif ENABLED(BABYSTEP_ZPROBE_GFX_OVERLAY) && !ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ #error "BABYSTEP_ZPROBE_GFX_OVERLAY requires a BABYSTEP_ZPROBE_OFFSET."
+ #endif
+#endif
+
+/**
+ * Filament Runout needs one or more pins and either SD Support or Auto print start detection
+ */
+#if ENABLED(FILAMENT_RUNOUT_SENSOR)
+ #if !PIN_EXISTS(FIL_RUNOUT)
+ #error "FILAMENT_RUNOUT_SENSOR requires FIL_RUNOUT_PIN."
+ #elif NUM_RUNOUT_SENSORS > E_STEPPERS
+ #error "NUM_RUNOUT_SENSORS cannot exceed the number of E steppers."
+ #elif NUM_RUNOUT_SENSORS > 1 && !PIN_EXISTS(FIL_RUNOUT2)
+ #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 1 requires FIL_RUNOUT2_PIN."
+ #elif NUM_RUNOUT_SENSORS > 2 && !PIN_EXISTS(FIL_RUNOUT3)
+ #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 2 requires FIL_RUNOUT3_PIN."
+ #elif NUM_RUNOUT_SENSORS > 3 && !PIN_EXISTS(FIL_RUNOUT4)
+ #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 3 requires FIL_RUNOUT4_PIN."
+ #elif NUM_RUNOUT_SENSORS > 4 && !PIN_EXISTS(FIL_RUNOUT5)
+ #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 4 requires FIL_RUNOUT5_PIN."
+ #elif DISABLED(SDSUPPORT) && DISABLED(PRINTJOB_TIMER_AUTOSTART)
+ #error "FILAMENT_RUNOUT_SENSOR requires SDSUPPORT or PRINTJOB_TIMER_AUTOSTART."
+ #elif DISABLED(ADVANCED_PAUSE_FEATURE)
+ static_assert(NULL == strstr(FILAMENT_RUNOUT_SCRIPT, "M600"), "ADVANCED_PAUSE_FEATURE is required to use M600 with FILAMENT_RUNOUT_SENSOR.");
+ #endif
+#endif
+
+/**
+ * Advanced Pause
+ */
+#if ENABLED(ADVANCED_PAUSE_FEATURE)
+ #if !HAS_RESUME_CONTINUE
+ #error "ADVANCED_PAUSE_FEATURE currently requires an LCD controller or EMERGENCY_PARSER."
+ #elif ENABLED(EXTRUDER_RUNOUT_PREVENT)
+ #error "EXTRUDER_RUNOUT_PREVENT is incompatible with ADVANCED_PAUSE_FEATURE."
+ #elif ENABLED(PARK_HEAD_ON_PAUSE) && DISABLED(SDSUPPORT) && DISABLED(NEWPANEL) && DISABLED(EMERGENCY_PARSER)
+ #error "PARK_HEAD_ON_PAUSE requires SDSUPPORT, EMERGENCY_PARSER, or an LCD controller."
+ #elif ENABLED(HOME_BEFORE_FILAMENT_CHANGE) && DISABLED(PAUSE_PARK_NO_STEPPER_TIMEOUT)
+ #error "HOME_BEFORE_FILAMENT_CHANGE requires PAUSE_PARK_NO_STEPPER_TIMEOUT."
+ #elif DISABLED(NOZZLE_PARK_FEATURE)
+ #error "ADVANCED_PAUSE_FEATURE requires NOZZLE_PARK_FEATURE."
+ #elif ENABLED(PREVENT_LENGTHY_EXTRUDE) && FILAMENT_CHANGE_UNLOAD_LENGTH > EXTRUDE_MAXLENGTH
+ #error "FILAMENT_CHANGE_UNLOAD_LENGTH must be less than or equal to EXTRUDE_MAXLENGTH."
+ #elif ENABLED(PREVENT_LENGTHY_EXTRUDE) && FILAMENT_CHANGE_LOAD_LENGTH > EXTRUDE_MAXLENGTH
+ #error "FILAMENT_CHANGE_LOAD_LENGTH must be less than or equal to EXTRUDE_MAXLENGTH."
+ #endif
+#endif
+
+/**
+ * Individual axis homing is useless for DELTAS
+ */
+#if ENABLED(INDIVIDUAL_AXIS_HOMING_MENU) && ENABLED(DELTA)
+ #error "INDIVIDUAL_AXIS_HOMING_MENU is incompatible with DELTA kinematics."
+#endif
+
+/**
+ * Options only for EXTRUDERS > 1
+ */
+#if EXTRUDERS > 1
+
+ #if EXTRUDERS > 5
+ #error "Marlin supports a maximum of 5 EXTRUDERS."
+ #endif
+
+ #if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
+ #error "EXTRUDERS must be 1 with TEMP_SENSOR_1_AS_REDUNDANT."
+ #endif
+
+ #if ENABLED(HEATERS_PARALLEL)
+ #error "EXTRUDERS must be 1 with HEATERS_PARALLEL."
+ #endif
+
+#elif ENABLED(MK2_MULTIPLEXER)
+ #error "MK2_MULTIPLEXER requires 2 or more EXTRUDERS."
+#elif ENABLED(SINGLENOZZLE)
+ #error "SINGLENOZZLE requires 2 or more EXTRUDERS."
+#endif
+
+/**
+ * Sanity checking for the Průša MK2 Multiplexer
+ */
+#ifdef SNMM
+ #error "SNMM is now MK2_MULTIPLEXER. Please update your configuration."
+#endif
+
+/**
+ * A Dual Nozzle carriage with switching servo
+ */
+#if ENABLED(SWITCHING_NOZZLE)
+ #if ENABLED(DUAL_X_CARRIAGE)
+ #error "SWITCHING_NOZZLE and DUAL_X_CARRIAGE are incompatible."
+ #elif ENABLED(SINGLENOZZLE)
+ #error "SWITCHING_NOZZLE and SINGLENOZZLE are incompatible."
+ #elif EXTRUDERS != 2
+ #error "SWITCHING_NOZZLE requires exactly 2 EXTRUDERS."
+ #elif NUM_SERVOS < 1
+ #error "SWITCHING_NOZZLE requires NUM_SERVOS >= 1."
+ #endif
+#endif
+
+/**
+ * Single Stepper Dual Extruder with switching servo
+ */
+#if ENABLED(SWITCHING_EXTRUDER) && NUM_SERVOS < 1
+ #error "SWITCHING_EXTRUDER requires NUM_SERVOS >= 1."
+#endif
+
+/**
+ * Mixing Extruder requirements
+ */
+#if ENABLED(MIXING_EXTRUDER)
+ #if EXTRUDERS > 1
+ #error "MIXING_EXTRUDER currently only supports one extruder."
+ #elif MIXING_STEPPERS < 2
+ #error "You must set MIXING_STEPPERS >= 2 for a mixing extruder."
+ #elif ENABLED(FILAMENT_SENSOR)
+ #error "MIXING_EXTRUDER is incompatible with FILAMENT_SENSOR. Comment out this line to use it anyway."
+ #elif ENABLED(SWITCHING_EXTRUDER)
+ #error "Please select either MIXING_EXTRUDER or SWITCHING_EXTRUDER, not both."
+ #elif ENABLED(SINGLENOZZLE)
+ #error "MIXING_EXTRUDER is incompatible with SINGLENOZZLE."
+ #elif ENABLED(LIN_ADVANCE)
+ #error "MIXING_EXTRUDER is incompatible with LIN_ADVANCE."
+ #endif
+#endif
+
+/**
+ * Linear Advance 1.5 - Check K value range
+ */
+#if ENABLED(LIN_ADVANCE)
+ static_assert(
+ WITHIN(LIN_ADVANCE_K, 0, 10),
+ "LIN_ADVANCE_K must be a value from 0 to 10 (Changed in LIN_ADVANCE v1.5, Marlin 1.1.9)."
+ );
+#endif
+
+/**
+ * Parking Extruder requirements
+ */
+#if ENABLED(PARKING_EXTRUDER)
+ #if ENABLED(DUAL_X_CARRIAGE)
+ #error "PARKING_EXTRUDER and DUAL_X_CARRIAGE are incompatible."
+ #elif ENABLED(SINGLENOZZLE)
+ #error "PARKING_EXTRUDER and SINGLENOZZLE are incompatible."
+ #elif ENABLED(EXT_SOLENOID)
+ #error "PARKING_EXTRUDER and EXT_SOLENOID are incompatible. (Pins are used twice.)"
+ #elif EXTRUDERS != 2
+ #error "PARKING_EXTRUDER requires exactly 2 EXTRUDERS."
+ #elif !PIN_EXISTS(SOL0) || !PIN_EXISTS(SOL1)
+ #error "PARKING_EXTRUDER requires SOL0_PIN and SOL1_PIN."
+ #elif !defined(PARKING_EXTRUDER_PARKING_X)
+ #error "PARKING_EXTRUDER requires PARKING_EXTRUDER_PARKING_X."
+ #elif !defined(PARKING_EXTRUDER_SECURITY_RAISE)
+ #error "PARKING_EXTRUDER requires PARKING_EXTRUDER_SECURITY_RAISE."
+ #elif PARKING_EXTRUDER_SECURITY_RAISE < 0
+ #error "PARKING_EXTRUDER_SECURITY_RAISE must be 0 or higher."
+ #elif !defined(PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE) || !WITHIN(PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE, LOW, HIGH)
+ #error "PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE must be defined as HIGH or LOW."
+ #elif !defined(PARKING_EXTRUDER_SOLENOIDS_DELAY) || !WITHIN(PARKING_EXTRUDER_SOLENOIDS_DELAY, 0, 2000)
+ #error "PARKING_EXTRUDER_SOLENOIDS_DELAY must be between 0 and 2000 (ms)."
+ #endif
+#endif
+
+/**
+ * Part-Cooling Fan Multiplexer requirements
+ */
+#if PIN_EXISTS(FANMUX1)
+ #if !HAS_FANMUX
+ #error "FANMUX0_PIN must be set before FANMUX1_PIN can be set."
+ #endif
+#elif PIN_EXISTS(FANMUX2)
+ #error "FANMUX0_PIN and FANMUX1_PIN must be set before FANMUX2_PIN can be set."
+#endif
+
+/**
+ * Limited number of servos
+ */
+#if NUM_SERVOS > 4
+ #error "The maximum number of SERVOS in Marlin is 4."
+#endif
+
+/**
+ * Servo deactivation depends on servo endstops, switching nozzle, or switching extruder
+ */
+#if ENABLED(DEACTIVATE_SERVOS_AFTER_MOVE) && !HAS_Z_SERVO_ENDSTOP && !defined(SWITCHING_NOZZLE_SERVO_NR) && !defined(SWITCHING_EXTRUDER_SERVO_NR)
+ #error "Z_ENDSTOP_SERVO_NR, switching nozzle, or switching extruder is required for DEACTIVATE_SERVOS_AFTER_MOVE."
+#endif
+
+/**
+ * Required LCD language
+ */
+#if DISABLED(DOGLCD) && ENABLED(ULTRA_LCD) && !defined(DISPLAY_CHARSET_HD44780)
+ #error "You must set DISPLAY_CHARSET_HD44780 to JAPANESE, WESTERN or CYRILLIC for your LCD controller."
+#endif
+
+/**
+ * Bed Heating Options - PID vs Limit Switching
+ */
+#if ENABLED(PIDTEMPBED) && ENABLED(BED_LIMIT_SWITCHING)
+ #error "To use BED_LIMIT_SWITCHING you must disable PIDTEMPBED."
+#endif
+
+/**
+ * Kinematics
+ */
+
+/**
+ * Allow only one kinematic type to be defined
+ */
+#if 1 < 0 \
+ + ENABLED(DELTA) \
+ + ENABLED(MORGAN_SCARA) \
+ + ENABLED(MAKERARM_SCARA) \
+ + ENABLED(COREXY) \
+ + ENABLED(COREXZ) \
+ + ENABLED(COREYZ) \
+ + ENABLED(COREYX) \
+ + ENABLED(COREZX) \
+ + ENABLED(COREZY)
+ #error "Please enable only one of DELTA, MORGAN_SCARA, MAKERARM_SCARA, COREXY, COREYX, COREXZ, COREZX, COREYZ, or COREZY."
+#endif
+
+/**
+ * Delta requirements
+ */
+#if ENABLED(DELTA)
+ #if DISABLED(USE_XMAX_PLUG) && DISABLED(USE_YMAX_PLUG) && DISABLED(USE_ZMAX_PLUG)
+ #error "You probably want to use Max Endstops for DELTA!"
+ #elif ENABLED(ENABLE_LEVELING_FADE_HEIGHT) && DISABLED(AUTO_BED_LEVELING_BILINEAR) && !UBL_SEGMENTED
+ #error "ENABLE_LEVELING_FADE_HEIGHT on DELTA requires AUTO_BED_LEVELING_BILINEAR or AUTO_BED_LEVELING_UBL."
+ #elif ENABLED(DELTA_AUTO_CALIBRATION) && !(HAS_BED_PROBE || ENABLED(ULTIPANEL))
+ #error "DELTA_AUTO_CALIBRATION requires a probe or LCD Controller."
+ #elif ABL_GRID
+ #if (GRID_MAX_POINTS_X & 1) == 0 || (GRID_MAX_POINTS_Y & 1) == 0
+ #error "DELTA requires GRID_MAX_POINTS_X and GRID_MAX_POINTS_Y to be odd numbers."
+ #elif GRID_MAX_POINTS_X < 3
+ #error "DELTA requires GRID_MAX_POINTS_X and GRID_MAX_POINTS_Y to be 3 or higher."
+ #endif
+ #endif
+#endif
+
+/**
+ * Probes
+ */
+
+/**
+ * Allow only one probe option to be defined
+ */
+#if 1 < 0 \
+ + ENABLED(PROBE_MANUALLY) \
+ + ENABLED(FIX_MOUNTED_PROBE) \
+ + (HAS_Z_SERVO_ENDSTOP && DISABLED(BLTOUCH)) \
+ + ENABLED(BLTOUCH) \
+ + ENABLED(SOLENOID_PROBE) \
+ + ENABLED(Z_PROBE_ALLEN_KEY) \
+ + ENABLED(Z_PROBE_SLED)
+ #error "Please enable only one probe option: PROBE_MANUALLY, FIX_MOUNTED_PROBE, BLTOUCH, SOLENOID_PROBE, Z_PROBE_ALLEN_KEY, Z_PROBE_SLED, or Z Servo."
+#endif
+
+#if HAS_BED_PROBE
+
+ /**
+ * Z_PROBE_SLED is incompatible with DELTA
+ */
+ #if ENABLED(Z_PROBE_SLED) && ENABLED(DELTA)
+ #error "You cannot use Z_PROBE_SLED with DELTA."
+ #endif
+
+ /**
+ * SOLENOID_PROBE requirements
+ */
+ #if ENABLED(SOLENOID_PROBE)
+ #if ENABLED(EXT_SOLENOID)
+ #error "SOLENOID_PROBE is incompatible with EXT_SOLENOID."
+ #elif !HAS_SOLENOID_1
+ #error "SOLENOID_PROBE requires SOL1_PIN. It can be added to your Configuration.h."
+ #endif
+ #endif
+
+ /**
+ * NUM_SERVOS is required for a Z servo probe
+ */
+ #if HAS_Z_SERVO_ENDSTOP
+ #ifndef NUM_SERVOS
+ #error "You must set NUM_SERVOS for a Z servo probe (Z_ENDSTOP_SERVO_NR)."
+ #elif Z_ENDSTOP_SERVO_NR >= NUM_SERVOS
+ #error "Z_ENDSTOP_SERVO_NR must be smaller than NUM_SERVOS."
+ #endif
+ #endif
+
+ /**
+ * Require pin options and pins to be defined
+ */
+ #if ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN)
+ #if ENABLED(Z_MIN_PROBE_ENDSTOP)
+ #error "Enable only one option: Z_MIN_PROBE_ENDSTOP or Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN."
+ #elif DISABLED(USE_ZMIN_PLUG)
+ #error "Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN requires USE_ZMIN_PLUG to be enabled."
+ #elif !HAS_Z_MIN
+ #error "Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN requires the Z_MIN_PIN to be defined."
+ #elif ENABLED(Z_MIN_PROBE_ENDSTOP_INVERTING) != ENABLED(Z_MIN_ENDSTOP_INVERTING)
+ #error "Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN requires Z_MIN_ENDSTOP_INVERTING to match Z_MIN_PROBE_ENDSTOP_INVERTING."
+ #endif
+ #elif ENABLED(Z_MIN_PROBE_ENDSTOP)
+ #if !HAS_Z_MIN_PROBE_PIN
+ #error "Z_MIN_PROBE_ENDSTOP requires the Z_MIN_PROBE_PIN to be defined."
+ #endif
+ #else
+ #error "You must enable either Z_MIN_PROBE_ENDSTOP or Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN to use a probe."
+ #endif
+
+ /**
+ * Make sure Z raise values are set
+ */
+ #ifndef Z_CLEARANCE_DEPLOY_PROBE
+ #error "You must define Z_CLEARANCE_DEPLOY_PROBE in your configuration."
+ #elif !defined(Z_CLEARANCE_BETWEEN_PROBES)
+ #error "You must define Z_CLEARANCE_BETWEEN_PROBES in your configuration."
+ #elif Z_CLEARANCE_DEPLOY_PROBE < 0
+ #error "Probes need Z_CLEARANCE_DEPLOY_PROBE >= 0."
+ #elif Z_CLEARANCE_BETWEEN_PROBES < 0
+ #error "Probes need Z_CLEARANCE_BETWEEN_PROBES >= 0."
+ #elif Z_AFTER_PROBING < 0
+ #error "Probes need Z_AFTER_PROBING >= 0."
+ #endif
+
+ #if MULTIPLE_PROBING && MULTIPLE_PROBING < 2
+ #error "MULTIPLE_PROBING must be >= 2."
+ #endif
+
+#else
+
+ /**
+ * Require some kind of probe for bed leveling and probe testing
+ */
+ #if OLDSCHOOL_ABL && !PROBE_SELECTED
+ #error "Auto Bed Leveling requires one of these: PROBE_MANUALLY, FIX_MOUNTED_PROBE, BLTOUCH, SOLENOID_PROBE, Z_PROBE_ALLEN_KEY, Z_PROBE_SLED, or a Z Servo."
+ #endif
+
+ #if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST)
+ #error "Z_MIN_PROBE_REPEATABILITY_TEST requires a probe: FIX_MOUNTED_PROBE, BLTOUCH, SOLENOID_PROBE, Z_PROBE_ALLEN_KEY, Z_PROBE_SLED, or Z Servo."
+ #endif
+
+#endif
+
+/**
+ * Allow only one bed leveling option to be defined
+ */
+#if 1 < 0 \
+ + ENABLED(AUTO_BED_LEVELING_LINEAR) \
+ + ENABLED(AUTO_BED_LEVELING_3POINT) \
+ + ENABLED(AUTO_BED_LEVELING_BILINEAR) \
+ + ENABLED(AUTO_BED_LEVELING_UBL) \
+ + ENABLED(MESH_BED_LEVELING)
+ #error "Select only one of: MESH_BED_LEVELING, AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_3POINT, AUTO_BED_LEVELING_BILINEAR or AUTO_BED_LEVELING_UBL."
+#endif
+
+/**
+ * Bed Leveling Requirements
+ */
+
+#if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(AUTO_BED_LEVELING_3POINT)
+ static_assert(WITHIN(PROBE_PT_1_X, MIN_PROBE_X, MAX_PROBE_X), "PROBE_PT_1_X is outside the probe region.");
+ static_assert(WITHIN(PROBE_PT_2_X, MIN_PROBE_X, MAX_PROBE_X), "PROBE_PT_2_X is outside the probe region.");
+ static_assert(WITHIN(PROBE_PT_3_X, MIN_PROBE_X, MAX_PROBE_X), "PROBE_PT_3_X is outside the probe region.");
+ static_assert(WITHIN(PROBE_PT_1_Y, MIN_PROBE_Y, MAX_PROBE_Y), "PROBE_PT_1_Y is outside the probe region.");
+ static_assert(WITHIN(PROBE_PT_2_Y, MIN_PROBE_Y, MAX_PROBE_Y), "PROBE_PT_2_Y is outside the probe region.");
+ static_assert(WITHIN(PROBE_PT_3_Y, MIN_PROBE_Y, MAX_PROBE_Y), "PROBE_PT_3_Y is outside the probe region.");
+#endif
+
+#if ENABLED(AUTO_BED_LEVELING_UBL)
+
+ /**
+ * Unified Bed Leveling
+ */
+
+ // Hide PROBE_MANUALLY from the rest of the code
+ #undef PROBE_MANUALLY
+
+ #if IS_SCARA
+ #error "AUTO_BED_LEVELING_UBL does not yet support SCARA printers."
+ #elif DISABLED(EEPROM_SETTINGS)
+ #error "AUTO_BED_LEVELING_UBL requires EEPROM_SETTINGS. Please update your configuration."
+ #elif !WITHIN(GRID_MAX_POINTS_X, 3, 15) || !WITHIN(GRID_MAX_POINTS_Y, 3, 15)
+ #error "GRID_MAX_POINTS_[XY] must be a whole number between 3 and 15."
+ #elif DISABLED(RESTORE_LEVELING_AFTER_G28)
+ #error "AUTO_BED_LEVELING_UBL (<=1.1.8) always has RESTORE_LEVELING_AFTER_G28 enabled. To keep this behavior, #define RESTORE_LEVELING_AFTER_G28. To keep it disabled comment out this line in SanityCheck.h."
+ #endif
+
+#elif OLDSCHOOL_ABL
+
+ /**
+ * Auto Bed Leveling
+ */
+
+ /**
+ * Delta and SCARA have limited bed leveling options
+ */
+ #if IS_SCARA && DISABLED(AUTO_BED_LEVELING_BILINEAR)
+ #error "SCARA machines can only use the AUTO_BED_LEVELING_BILINEAR leveling option."
+ #endif
+
+ /**
+ * Check auto bed leveling probe points
+ */
+ #if ABL_GRID
+
+ static_assert(LEFT_PROBE_BED_POSITION < RIGHT_PROBE_BED_POSITION, "LEFT_PROBE_BED_POSITION must be less than RIGHT_PROBE_BED_POSITION.");
+ static_assert(FRONT_PROBE_BED_POSITION < BACK_PROBE_BED_POSITION, "FRONT_PROBE_BED_POSITION must be less than BACK_PROBE_BED_POSITION.");
+ static_assert(LEFT_PROBE_BED_POSITION >= MIN_PROBE_X, "LEFT_PROBE_BED_POSITION is outside the probe region.");
+ static_assert(RIGHT_PROBE_BED_POSITION <= MAX_PROBE_X, "RIGHT_PROBE_BED_POSITION is outside the probe region.");
+ static_assert(FRONT_PROBE_BED_POSITION >= MIN_PROBE_Y, "FRONT_PROBE_BED_POSITION is outside the probe region.");
+ static_assert(BACK_PROBE_BED_POSITION <= MAX_PROBE_Y, "BACK_PROBE_BED_POSITION is outside the probe region.");
+
+ #endif // AUTO_BED_LEVELING_3POINT
+
+#elif ENABLED(MESH_BED_LEVELING)
+
+ // Hide PROBE_MANUALLY from the rest of the code
+ #undef PROBE_MANUALLY
+
+ /**
+ * Mesh Bed Leveling
+ */
+
+ #if ENABLED(DELTA)
+ #error "MESH_BED_LEVELING is not compatible with DELTA printers."
+ #elif GRID_MAX_POINTS_X > 9 || GRID_MAX_POINTS_Y > 9
+ #error "GRID_MAX_POINTS_X and GRID_MAX_POINTS_Y must be less than 10 for MBL."
+ #endif
+
+#endif
+
+#if HAS_MESH
+ static_assert(DEFAULT_ZJERK > 0.1, "Low DEFAULT_ZJERK values are incompatible with mesh-based leveling.");
+#elif ENABLED(G26_MESH_VALIDATION)
+ #error "G26_MESH_VALIDATION requires MESH_BED_LEVELING, AUTO_BED_LEVELING_BILINEAR, or AUTO_BED_LEVELING_UBL."
+#endif
+
+#if ENABLED(MESH_EDIT_GFX_OVERLAY) && (DISABLED(AUTO_BED_LEVELING_UBL) || DISABLED(DOGLCD))
+ #error "MESH_EDIT_GFX_OVERLAY requires AUTO_BED_LEVELING_UBL and a Graphical LCD."
+#endif
+
+/**
+ * LCD_BED_LEVELING requirements
+ */
+#if ENABLED(LCD_BED_LEVELING)
+ #if DISABLED(ULTIPANEL)
+ #error "LCD_BED_LEVELING requires an LCD controller."
+ #elif !(ENABLED(MESH_BED_LEVELING) || (OLDSCHOOL_ABL && ENABLED(PROBE_MANUALLY)))
+ #error "LCD_BED_LEVELING requires MESH_BED_LEVELING or ABL with PROBE_MANUALLY."
+ #endif
+#endif
+
+/**
+ * Homing
+ */
+#if X_HOME_BUMP_MM < 0 || Y_HOME_BUMP_MM < 0 || Z_HOME_BUMP_MM < 0
+ #error "[XYZ]_HOME_BUMP_MM must be greater than or equal to 0."
+#endif
+
+#if ENABLED(CODEPENDENT_XY_HOMING)
+ #if ENABLED(QUICK_HOME)
+ #error "QUICK_HOME is incompatible with CODEPENDENT_XY_HOMING."
+ #elif IS_KINEMATIC
+ #error "CODEPENDENT_XY_HOMING requires a Cartesian setup."
+ #endif
+#endif
+
+/**
+ * Make sure Z_SAFE_HOMING point is reachable
+ */
+#if ENABLED(Z_SAFE_HOMING)
+ #if HAS_BED_PROBE
+ #if !WITHIN(Z_SAFE_HOMING_X_POINT, MIN_PROBE_X, MAX_PROBE_X)
+ #error "Z_SAFE_HOMING_X_POINT is outside the probe region."
+ #elif !WITHIN(Z_SAFE_HOMING_Y_POINT, MIN_PROBE_Y, MAX_PROBE_Y)
+ #error "Z_SAFE_HOMING_Y_POINT is outside the probe region."
+ #endif
+ #elif !WITHIN(Z_SAFE_HOMING_X_POINT, X_MIN_POS, X_MAX_POS)
+ #error "Z_SAFE_HOMING_X_POINT can't be reached by the nozzle."
+ #elif !WITHIN(Z_SAFE_HOMING_Y_POINT, Y_MIN_POS, Y_MAX_POS)
+ #error "Z_SAFE_HOMING_Y_POINT can't be reached by the nozzle."
+ #endif
+#endif // Z_SAFE_HOMING
+
+/**
+ * Make sure DISABLE_[XYZ] compatible with selected homing options
+ */
+#if ENABLED(DISABLE_X) || ENABLED(DISABLE_Y) || ENABLED(DISABLE_Z)
+ #if ENABLED(HOME_AFTER_DEACTIVATE) || ENABLED(Z_SAFE_HOMING)
+ #error "DISABLE_[XYZ] is not compatible with HOME_AFTER_DEACTIVATE or Z_SAFE_HOMING."
+ #endif
+#endif // DISABLE_[XYZ]
+
+/**
+ * Filament Width Sensor
+ */
+#if ENABLED(FILAMENT_WIDTH_SENSOR)
+ #if !HAS_FILAMENT_WIDTH_SENSOR
+ #error "FILAMENT_WIDTH_SENSOR requires a FILWIDTH_PIN to be defined."
+ #elif ENABLED(NO_VOLUMETRICS)
+ #error "FILAMENT_WIDTH_SENSOR requires NO_VOLUMETRICS to be disabled."
+ #endif
+#endif
+
+/**
+ * ULTIPANEL encoder
+ */
+#if ENABLED(ULTIPANEL) && DISABLED(NEWPANEL) && DISABLED(SR_LCD_2W_NL) && !defined(SHIFT_CLK)
+ #error "ULTIPANEL requires some kind of encoder."
+#endif
+
+#if ENCODER_PULSES_PER_STEP < 0
+ #error "ENCODER_PULSES_PER_STEP should not be negative, use REVERSE_MENU_DIRECTION instead."
+#endif
+
+/**
+ * SAV_3DGLCD display options
+ */
+#if ENABLED(U8GLIB_SSD1306) && ENABLED(U8GLIB_SH1106)
+ #error "Only enable one SAV_3DGLCD display type: U8GLIB_SSD1306 or U8GLIB_SH1106."
+#endif
+
+/**
+ * Allen Key
+ * Deploying the Allen Key probe uses big moves in z direction. Too dangerous for an unhomed z-axis.
+ */
+#if ENABLED(Z_PROBE_ALLEN_KEY) && (Z_HOME_DIR < 0) && ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN)
+ #error "You can't home to a z min endstop with a Z_PROBE_ALLEN_KEY"
+#endif
+
+/**
+ * Dual X Carriage requirements
+ */
+#if ENABLED(DUAL_X_CARRIAGE)
+ #if EXTRUDERS == 1
+ #error "DUAL_X_CARRIAGE requires 2 (or more) extruders."
+ #elif CORE_IS_XY || CORE_IS_XZ
+ #error "DUAL_X_CARRIAGE cannot be used with COREXY, COREYX, COREXZ, or COREZX."
+ #elif !HAS_X2_ENABLE || !HAS_X2_STEP || !HAS_X2_DIR
+ #error "DUAL_X_CARRIAGE requires X2 stepper pins to be defined."
+ #elif !HAS_X_MAX
+ #error "DUAL_X_CARRIAGE requires USE_XMAX_PLUG and an X Max Endstop."
+ #elif !defined(X2_HOME_POS) || !defined(X2_MIN_POS) || !defined(X2_MAX_POS)
+ #error "DUAL_X_CARRIAGE requires X2_HOME_POS, X2_MIN_POS, and X2_MAX_POS."
+ #elif X_HOME_DIR != -1 || X2_HOME_DIR != 1
+ #error "DUAL_X_CARRIAGE requires X_HOME_DIR -1 and X2_HOME_DIR 1."
+ #endif
+#endif // DUAL_X_CARRIAGE
+
+/**
+ * Make sure auto fan pins don't conflict with the fan pin
+ */
+#if HAS_AUTO_FAN
+ #if HAS_FAN0
+ #if E0_AUTO_FAN_PIN == FAN_PIN
+ #error "You cannot set E0_AUTO_FAN_PIN equal to FAN_PIN."
+ #elif E1_AUTO_FAN_PIN == FAN_PIN
+ #error "You cannot set E1_AUTO_FAN_PIN equal to FAN_PIN."
+ #elif E2_AUTO_FAN_PIN == FAN_PIN
+ #error "You cannot set E2_AUTO_FAN_PIN equal to FAN_PIN."
+ #elif E3_AUTO_FAN_PIN == FAN_PIN
+ #error "You cannot set E3_AUTO_FAN_PIN equal to FAN_PIN."
+ #endif
+ #endif
+#endif
+
+#if HAS_FAN0 && CONTROLLER_FAN_PIN == FAN_PIN
+ #error "You cannot set CONTROLLER_FAN_PIN equal to FAN_PIN."
+#endif
+
+#if ENABLED(USE_CONTROLLER_FAN)
+ #if !HAS_CONTROLLER_FAN
+ #error "USE_CONTROLLER_FAN requires a CONTROLLER_FAN_PIN. Define in Configuration_adv.h."
+ #elif E0_AUTO_FAN_PIN == CONTROLLER_FAN_PIN
+ #error "You cannot set E0_AUTO_FAN_PIN equal to CONTROLLER_FAN_PIN."
+ #elif E1_AUTO_FAN_PIN == CONTROLLER_FAN_PIN
+ #error "You cannot set E1_AUTO_FAN_PIN equal to CONTROLLER_FAN_PIN."
+ #elif E2_AUTO_FAN_PIN == CONTROLLER_FAN_PIN
+ #error "You cannot set E2_AUTO_FAN_PIN equal to CONTROLLER_FAN_PIN."
+ #elif E3_AUTO_FAN_PIN == CONTROLLER_FAN_PIN
+ #error "You cannot set E3_AUTO_FAN_PIN equal to CONTROLLER_FAN_PIN."
+ #endif
+#endif
+
+/**
+ * Test Heater, Temp Sensor, and Extruder Pins; Sensor Type must also be set.
+ */
+#if !HAS_HEATER_0
+ #error "HEATER_0_PIN not defined for this board."
+#elif !PIN_EXISTS(TEMP_0) && !(defined(MAX6675_SS) && MAX6675_SS >= 0)
+ #error "TEMP_0_PIN not defined for this board."
+#elif ((defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)) && (!PIN_EXISTS(E0_STEP) || !PIN_EXISTS(E0_DIR)))
+ #error "E0_STEP_PIN or E0_DIR_PIN not defined for this board."
+#elif ( !(defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)) && (!PIN_EXISTS(E0_STEP) || !PIN_EXISTS(E0_DIR) || !PIN_EXISTS(E0_ENABLE)))
+ #error "E0_STEP_PIN, E0_DIR_PIN, or E0_ENABLE_PIN not defined for this board."
+#elif TEMP_SENSOR_0 == 0
+ #error "TEMP_SENSOR_0 is required."
+#endif
+
+// Pins are required for heaters
+#if ENABLED(HEATER_0_USES_MAX6675) && !(defined(MAX6675_SS) && MAX6675_SS >= 0)
+ #error "MAX6675_SS (required for TEMP_SENSOR_0) not defined for this board."
+#elif (HOTENDS > 1 || ENABLED(HEATERS_PARALLEL)) && !HAS_HEATER_1
+ #error "HEATER_1_PIN not defined for this board."
+#endif
+
+#if HOTENDS > 1
+ #if TEMP_SENSOR_1 == 0
+ #error "TEMP_SENSOR_1 is required with 2 or more HOTENDS."
+ #elif !PIN_EXISTS(TEMP_1)
+ #error "TEMP_1_PIN not defined for this board."
+ #endif
+ #if HOTENDS > 2
+ #if TEMP_SENSOR_2 == 0
+ #error "TEMP_SENSOR_2 is required with 3 or more HOTENDS."
+ #elif !HAS_HEATER_2
+ #error "HEATER_2_PIN not defined for this board."
+ #elif !PIN_EXISTS(TEMP_2)
+ #error "TEMP_2_PIN not defined for this board."
+ #endif
+ #if HOTENDS > 3
+ #if TEMP_SENSOR_3 == 0
+ #error "TEMP_SENSOR_3 is required with 4 or more HOTENDS."
+ #elif !HAS_HEATER_3
+ #error "HEATER_3_PIN not defined for this board."
+ #elif !PIN_EXISTS(TEMP_3)
+ #error "TEMP_3_PIN not defined for this board."
+ #endif
+ #if HOTENDS > 4
+ #if TEMP_SENSOR_4 == 0
+ #error "TEMP_SENSOR_4 is required with 5 HOTENDS."
+ #elif !HAS_HEATER_4
+ #error "HEATER_4_PIN not defined for this board."
+ #elif !PIN_EXISTS(TEMP_4)
+ #error "TEMP_4_PIN not defined for this board."
+ #endif
+ #elif TEMP_SENSOR_4 != 0
+ #error "TEMP_SENSOR_4 shouldn't be set with only 4 HOTENDS."
+ #endif
+ #elif TEMP_SENSOR_3 != 0
+ #error "TEMP_SENSOR_3 shouldn't be set with only 3 HOTENDS."
+ #elif TEMP_SENSOR_4 != 0
+ #error "TEMP_SENSOR_4 shouldn't be set with only 3 HOTENDS."
+ #endif
+ #elif TEMP_SENSOR_2 != 0
+ #error "TEMP_SENSOR_2 shouldn't be set with only 2 HOTENDS."
+ #elif TEMP_SENSOR_3 != 0
+ #error "TEMP_SENSOR_3 shouldn't be set with only 2 HOTENDS."
+ #elif TEMP_SENSOR_4 != 0
+ #error "TEMP_SENSOR_4 shouldn't be set with only 2 HOTENDS."
+ #endif
+#elif TEMP_SENSOR_1 != 0 && DISABLED(TEMP_SENSOR_1_AS_REDUNDANT)
+ #error "TEMP_SENSOR_1 shouldn't be set with only 1 HOTEND."
+#elif TEMP_SENSOR_2 != 0
+ #error "TEMP_SENSOR_2 shouldn't be set with only 1 HOTEND."
+#elif TEMP_SENSOR_3 != 0
+ #error "TEMP_SENSOR_3 shouldn't be set with only 1 HOTEND."
+#elif TEMP_SENSOR_4 != 0
+ #error "TEMP_SENSOR_4 shouldn't be set with only 1 HOTEND."
+#endif
+
+#if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT) && TEMP_SENSOR_1 == 0
+ #error "TEMP_SENSOR_1 is required with TEMP_SENSOR_1_AS_REDUNDANT."
+#endif
+
+/**
+ * Temperature status LEDs
+ */
+#if ENABLED(TEMP_STAT_LEDS) && !PIN_EXISTS(STAT_LED_RED) && !PIN_EXISTS(STAT_LED_BLUE)
+ #error "TEMP_STAT_LEDS requires STAT_LED_RED_PIN or STAT_LED_BLUE_PIN, preferably both."
+#endif
+
+/**
+ * Basic 2-nozzle duplication mode
+ */
+#if ENABLED(DUAL_NOZZLE_DUPLICATION_MODE)
+ #if HOTENDS != 2
+ #error "DUAL_NOZZLE_DUPLICATION_MODE requires exactly 2 hotends."
+ #elif ENABLED(DUAL_X_CARRIAGE)
+ #error "DUAL_NOZZLE_DUPLICATION_MODE is incompatible with DUAL_X_CARRIAGE."
+ #elif ENABLED(SINGLENOZZLE)
+ #error "DUAL_NOZZLE_DUPLICATION_MODE is incompatible with SINGLENOZZLE."
+ #elif ENABLED(MIXING_EXTRUDER)
+ #error "DUAL_NOZZLE_DUPLICATION_MODE is incompatible with MIXING_EXTRUDER."
+ #elif ENABLED(SWITCHING_EXTRUDER)
+ #error "DUAL_NOZZLE_DUPLICATION_MODE is incompatible with SWITCHING_EXTRUDER."
+ #endif
+#endif
+
+/**
+ * Test Extruder Stepper Pins
+ */
+#if DISABLED(MK2_MULTIPLEXER) // MK2_MULTIPLEXER uses E0 stepper only
+ #if E_STEPPERS > 4
+ #if !PIN_EXISTS(E4_STEP) || !PIN_EXISTS(E4_DIR) || !PIN_EXISTS(E4_ENABLE)
+ #error "E4_STEP_PIN, E4_DIR_PIN, or E4_ENABLE_PIN not defined for this board."
+ #endif
+ #elif E_STEPPERS > 3
+ #if !PIN_EXISTS(E3_STEP) || !PIN_EXISTS(E3_DIR) || !PIN_EXISTS(E3_ENABLE)
+ #error "E3_STEP_PIN, E3_DIR_PIN, or E3_ENABLE_PIN not defined for this board."
+ #endif
+ #elif E_STEPPERS > 2
+ #if !PIN_EXISTS(E2_STEP) || !PIN_EXISTS(E2_DIR) || !PIN_EXISTS(E2_ENABLE)
+ #error "E2_STEP_PIN, E2_DIR_PIN, or E2_ENABLE_PIN not defined for this board."
+ #endif
+ #elif E_STEPPERS > 1
+ #if !PIN_EXISTS(E1_STEP) || !PIN_EXISTS(E1_DIR) || !PIN_EXISTS(E1_ENABLE)
+ #error "E1_STEP_PIN, E1_DIR_PIN, or E1_ENABLE_PIN not defined for this board."
+ #endif
+ #endif
+#endif
+/**
+ * Endstop Tests
+ */
+
+#define _PLUG_UNUSED_TEST(AXIS,PLUG) (DISABLED(USE_##PLUG##MIN_PLUG) && DISABLED(USE_##PLUG##MAX_PLUG) && !(ENABLED(AXIS##_DUAL_ENDSTOPS) && WITHIN(AXIS##2_USE_ENDSTOP, _##PLUG##MAX_, _##PLUG##MIN_)))
+#define _AXIS_PLUG_UNUSED_TEST(AXIS) (_PLUG_UNUSED_TEST(AXIS,X) && _PLUG_UNUSED_TEST(AXIS,Y) && _PLUG_UNUSED_TEST(AXIS,Z))
+
+// At least 3 endstop plugs must be used
+#if _AXIS_PLUG_UNUSED_TEST(X)
+ #error "You must enable USE_XMIN_PLUG or USE_XMAX_PLUG."
+#endif
+#if _AXIS_PLUG_UNUSED_TEST(Y)
+ #error "You must enable USE_YMIN_PLUG or USE_YMAX_PLUG."
+#endif
+#if _AXIS_PLUG_UNUSED_TEST(Z)
+ #error "You must enable USE_ZMIN_PLUG or USE_ZMAX_PLUG."
+#endif
+
+// Delta and Cartesian use 3 homing endstops
+#if !IS_SCARA
+ #if X_HOME_DIR < 0 && DISABLED(USE_XMIN_PLUG)
+ #error "Enable USE_XMIN_PLUG when homing X to MIN."
+ #elif X_HOME_DIR > 0 && DISABLED(USE_XMAX_PLUG)
+ #error "Enable USE_XMAX_PLUG when homing X to MAX."
+ #elif Y_HOME_DIR < 0 && DISABLED(USE_YMIN_PLUG)
+ #error "Enable USE_YMIN_PLUG when homing Y to MIN."
+ #elif Y_HOME_DIR > 0 && DISABLED(USE_YMAX_PLUG)
+ #error "Enable USE_YMAX_PLUG when homing Y to MAX."
+ #endif
+#endif
+#if Z_HOME_DIR < 0 && DISABLED(USE_ZMIN_PLUG)
+ #error "Enable USE_ZMIN_PLUG when homing Z to MIN."
+#elif Z_HOME_DIR > 0 && DISABLED(USE_ZMAX_PLUG)
+ #error "Enable USE_ZMAX_PLUG when homing Z to MAX."
+#endif
+
+// Dual endstops requirements
+#if ENABLED(X_DUAL_ENDSTOPS)
+ #if !X2_USE_ENDSTOP
+ #error "You must set X2_USE_ENDSTOP with X_DUAL_ENDSTOPS."
+ #elif X2_USE_ENDSTOP == _X_MIN_ && DISABLED(USE_XMIN_PLUG)
+ #error "USE_XMIN_PLUG is required when X2_USE_ENDSTOP is _X_MIN_."
+ #elif X2_USE_ENDSTOP == _X_MAX_ && DISABLED(USE_XMAX_PLUG)
+ #error "USE_XMAX_PLUG is required when X2_USE_ENDSTOP is _X_MAX_."
+ #elif X2_USE_ENDSTOP == _Y_MIN_ && DISABLED(USE_YMIN_PLUG)
+ #error "USE_YMIN_PLUG is required when X2_USE_ENDSTOP is _Y_MIN_."
+ #elif X2_USE_ENDSTOP == _Y_MAX_ && DISABLED(USE_YMAX_PLUG)
+ #error "USE_YMAX_PLUG is required when X2_USE_ENDSTOP is _Y_MAX_."
+ #elif X2_USE_ENDSTOP == _Z_MIN_ && DISABLED(USE_ZMIN_PLUG)
+ #error "USE_ZMIN_PLUG is required when X2_USE_ENDSTOP is _Z_MIN_."
+ #elif X2_USE_ENDSTOP == _Z_MAX_ && DISABLED(USE_ZMAX_PLUG)
+ #error "USE_ZMAX_PLUG is required when X2_USE_ENDSTOP is _Z_MAX_."
+ #elif !HAS_X2_MIN && !HAS_X2_MAX
+ #error "X2_USE_ENDSTOP has been assigned to a nonexistent endstop!"
+ #elif ENABLED(DELTA)
+ #error "X_DUAL_ENDSTOPS is not compatible with DELTA."
+ #endif
+#endif
+#if ENABLED(Y_DUAL_ENDSTOPS)
+ #if !Y2_USE_ENDSTOP
+ #error "You must set Y2_USE_ENDSTOP with Y_DUAL_ENDSTOPS."
+ #elif Y2_USE_ENDSTOP == _X_MIN_ && DISABLED(USE_XMIN_PLUG)
+ #error "USE_XMIN_PLUG is required when Y2_USE_ENDSTOP is _X_MIN_."
+ #elif Y2_USE_ENDSTOP == _X_MAX_ && DISABLED(USE_XMAX_PLUG)
+ #error "USE_XMAX_PLUG is required when Y2_USE_ENDSTOP is _X_MAX_."
+ #elif Y2_USE_ENDSTOP == _Y_MIN_ && DISABLED(USE_YMIN_PLUG)
+ #error "USE_YMIN_PLUG is required when Y2_USE_ENDSTOP is _Y_MIN_."
+ #elif Y2_USE_ENDSTOP == _Y_MAX_ && DISABLED(USE_YMAX_PLUG)
+ #error "USE_YMAX_PLUG is required when Y2_USE_ENDSTOP is _Y_MAX_."
+ #elif Y2_USE_ENDSTOP == _Z_MIN_ && DISABLED(USE_ZMIN_PLUG)
+ #error "USE_ZMIN_PLUG is required when Y2_USE_ENDSTOP is _Z_MIN_."
+ #elif Y2_USE_ENDSTOP == _Z_MAX_ && DISABLED(USE_ZMAX_PLUG)
+ #error "USE_ZMAX_PLUG is required when Y2_USE_ENDSTOP is _Z_MAX_."
+ #elif !HAS_Y2_MIN && !HAS_Y2_MAX
+ #error "Y2_USE_ENDSTOP has been assigned to a nonexistent endstop!"
+ #elif ENABLED(DELTA)
+ #error "Y_DUAL_ENDSTOPS is not compatible with DELTA."
+ #endif
+#endif
+#if ENABLED(Z_DUAL_ENDSTOPS)
+ #if !Z2_USE_ENDSTOP
+ #error "You must set Z2_USE_ENDSTOP with Z_DUAL_ENDSTOPS."
+ #elif Z2_USE_ENDSTOP == _X_MIN_ && DISABLED(USE_XMIN_PLUG)
+ #error "USE_XMIN_PLUG is required when Z2_USE_ENDSTOP is _X_MIN_."
+ #elif Z2_USE_ENDSTOP == _X_MAX_ && DISABLED(USE_XMAX_PLUG)
+ #error "USE_XMAX_PLUG is required when Z2_USE_ENDSTOP is _X_MAX_."
+ #elif Z2_USE_ENDSTOP == _Y_MIN_ && DISABLED(USE_YMIN_PLUG)
+ #error "USE_YMIN_PLUG is required when Z2_USE_ENDSTOP is _Y_MIN_."
+ #elif Z2_USE_ENDSTOP == _Y_MAX_ && DISABLED(USE_YMAX_PLUG)
+ #error "USE_YMAX_PLUG is required when Z2_USE_ENDSTOP is _Y_MAX_."
+ #elif Z2_USE_ENDSTOP == _Z_MIN_ && DISABLED(USE_ZMIN_PLUG)
+ #error "USE_ZMIN_PLUG is required when Z2_USE_ENDSTOP is _Z_MIN_."
+ #elif Z2_USE_ENDSTOP == _Z_MAX_ && DISABLED(USE_ZMAX_PLUG)
+ #error "USE_ZMAX_PLUG is required when Z2_USE_ENDSTOP is _Z_MAX_."
+ #elif !HAS_Z2_MIN && !HAS_Z2_MAX
+ #error "Z2_USE_ENDSTOP has been assigned to a nonexistent endstop!"
+ #elif ENABLED(DELTA)
+ #error "Z_DUAL_ENDSTOPS is not compatible with DELTA."
+ #endif
+#endif
+
+/**
+ * emergency-command parser
+ */
+#if ENABLED(EMERGENCY_PARSER) && defined(__AVR__) && defined(USBCON)
+ #error "EMERGENCY_PARSER does not work on boards with AT90USB processors (USBCON)."
+#endif
+
+/**
+ * I2C bus
+ */
+#if ENABLED(EXPERIMENTAL_I2CBUS) && I2C_SLAVE_ADDRESS > 0
+ #if I2C_SLAVE_ADDRESS < 8
+ #error "I2C_SLAVE_ADDRESS can't be less than 8. (Addresses 0 - 7 are reserved.)"
+ #elif I2C_SLAVE_ADDRESS > 127
+ #error "I2C_SLAVE_ADDRESS can't be over 127. (Only 7 bits allowed.)"
+ #endif
+#endif
+
+/**
+ * G38 Probe Target
+ */
+#if ENABLED(G38_PROBE_TARGET)
+ #if !HAS_BED_PROBE
+ #error "G38_PROBE_TARGET requires a bed probe."
+ #elif !IS_CARTESIAN
+ #error "G38_PROBE_TARGET requires a Cartesian machine."
+ #endif
+#endif
+
+/**
+ * RGB_LED Requirements
+ */
+#define _RGB_TEST (PIN_EXISTS(RGB_LED_R) && PIN_EXISTS(RGB_LED_G) && PIN_EXISTS(RGB_LED_B))
+#if ENABLED(RGB_LED)
+ #if !_RGB_TEST
+ #error "RGB_LED requires RGB_LED_R_PIN, RGB_LED_G_PIN, and RGB_LED_B_PIN."
+ #elif ENABLED(RGBW_LED)
+ #error "Please enable only one of RGB_LED and RGBW_LED."
+ #endif
+#elif ENABLED(RGBW_LED)
+ #if !(_RGB_TEST && PIN_EXISTS(RGB_LED_W))
+ #error "RGBW_LED requires RGB_LED_R_PIN, RGB_LED_G_PIN, RGB_LED_B_PIN, and RGB_LED_W_PIN."
+ #endif
+#elif ENABLED(NEOPIXEL_LED)
+ #if !(PIN_EXISTS(NEOPIXEL) && NEOPIXEL_PIXELS > 0)
+ #error "NEOPIXEL_LED requires NEOPIXEL_PIN and NEOPIXEL_PIXELS."
+ #endif
+#elif ENABLED(PRINTER_EVENT_LEDS) && DISABLED(BLINKM) && DISABLED(PCA9632) && DISABLED(NEOPIXEL_LED)
+ #error "PRINTER_EVENT_LEDS requires BLINKM, PCA9632, RGB_LED, RGBW_LED or NEOPIXEL_LED."
+#endif
+
+/**
+ * Auto Fan check for PWM pins
+ */
+#if HAS_AUTO_FAN && EXTRUDER_AUTO_FAN_SPEED != 255
+ #define AF_ERR_SUFF "_AUTO_FAN_PIN is not a PWM pin. Set EXTRUDER_AUTO_FAN_SPEED to 255."
+ #if HAS_AUTO_FAN_0
+ static_assert(GET_TIMER(E0_AUTO_FAN_PIN), "E0" AF_ERR_SUFF);
+ #elif HAS_AUTO_FAN_1
+ static_assert(GET_TIMER(E1_AUTO_FAN_PIN), "E1" AF_ERR_SUFF);
+ #elif HAS_AUTO_FAN_2
+ static_assert(GET_TIMER(E2_AUTO_FAN_PIN), "E2" AF_ERR_SUFF);
+ #elif HAS_AUTO_FAN_3
+ static_assert(GET_TIMER(E3_AUTO_FAN_PIN), "E3" AF_ERR_SUFF);
+ #endif
+#endif
+
+/**
+ * Make sure only one display is enabled
+ *
+ * Note: BQ_LCD_SMART_CONTROLLER => REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER
+ * REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER => REPRAP_DISCOUNT_SMART_CONTROLLER
+ * SAV_3DGLCD => U8GLIB_SH1106 => ULTIMAKERCONTROLLER
+ * MKS_12864OLED => U8GLIB_SH1106 => ULTIMAKERCONTROLLER
+ * MKS_12864OLED_SSD1306 => U8GLIB_SSD1306 => ULTIMAKERCONTROLLER
+ * miniVIKI => ULTIMAKERCONTROLLER
+ * VIKI2 => ULTIMAKERCONTROLLER
+ * ELB_FULL_GRAPHIC_CONTROLLER => ULTIMAKERCONTROLLER
+ * PANEL_ONE => ULTIMAKERCONTROLLER
+ */
+#if 1 < 0 \
+ + ( ENABLED(ULTIMAKERCONTROLLER) \
+ && DISABLED(SAV_3DGLCD) \
+ && DISABLED(miniVIKI) \
+ && DISABLED(VIKI2) \
+ && DISABLED(ELB_FULL_GRAPHIC_CONTROLLER) \
+ && DISABLED(PANEL_ONE) \
+ && DISABLED(MKS_12864OLED) \
+ && DISABLED(MKS_12864OLED_SSD1306) ) \
+ + ( ENABLED(REPRAP_DISCOUNT_SMART_CONTROLLER) \
+ && DISABLED(REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER) \
+ && DISABLED(LCD_FOR_MELZI) \
+ && DISABLED(MAKEBOARD_MINI_2_LINE_DISPLAY_1602) \
+ && DISABLED(MKS_12864OLED) \
+ && DISABLED(MKS_12864OLED_SSD1306) ) \
+ + (ENABLED(REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER) && DISABLED(BQ_LCD_SMART_CONTROLLER)) \
+ + ENABLED(LCD_FOR_MELZI) \
+ + ENABLED(MKS_12864OLED) \
+ + ENABLED(MKS_12864OLED_SSD1306) \
+ + ENABLED(MAKEBOARD_MINI_2_LINE_DISPLAY_1602) \
+ + ENABLED(CARTESIO_UI) \
+ + ENABLED(PANEL_ONE) \
+ + ENABLED(MAKRPANEL) \
+ + ENABLED(REPRAPWORLD_GRAPHICAL_LCD) \
+ + ENABLED(VIKI2) \
+ + ENABLED(miniVIKI) \
+ + ENABLED(ELB_FULL_GRAPHIC_CONTROLLER) \
+ + ENABLED(G3D_PANEL) \
+ + (ENABLED(MINIPANEL) && DISABLED(MKS_MINI_12864)) \
+ + ENABLED(MKS_MINI_12864) \
+ + (ENABLED(REPRAPWORLD_KEYPAD) && DISABLED(CARTESIO_UI) && DISABLED(ZONESTAR_LCD)) \
+ + ENABLED(RIGIDBOT_PANEL) \
+ + ENABLED(RA_CONTROL_PANEL) \
+ + ENABLED(LCD_SAINSMART_I2C_1602) \
+ + ENABLED(LCD_SAINSMART_I2C_2004) \
+ + ENABLED(LCM1602) \
+ + ENABLED(LCD_I2C_PANELOLU2) \
+ + ENABLED(LCD_I2C_VIKI) \
+ + (ENABLED(U8GLIB_SSD1306) && DISABLED(OLED_PANEL_TINYBOY2) && DISABLED(MKS_12864OLED_SSD1306)) \
+ + ENABLED(SAV_3DLCD) \
+ + ENABLED(BQ_LCD_SMART_CONTROLLER) \
+ + ENABLED(SAV_3DGLCD) \
+ + ENABLED(OLED_PANEL_TINYBOY2) \
+ + ENABLED(ZONESTAR_LCD) \
+ + ENABLED(ULTI_CONTROLLER)
+ #error "Please select no more than one LCD controller option."
+#endif
+
+/**
+ * Make sure HAVE_TMC26X is warranted
+ */
+#if ENABLED(HAVE_TMC26X) && !( \
+ ENABLED( X_IS_TMC26X ) \
+ || ENABLED( X2_IS_TMC26X ) \
+ || ENABLED( Y_IS_TMC26X ) \
+ || ENABLED( Y2_IS_TMC26X ) \
+ || ENABLED( Z_IS_TMC26X ) \
+ || ENABLED( Z2_IS_TMC26X ) \
+ || ENABLED( E0_IS_TMC26X ) \
+ || ENABLED( E1_IS_TMC26X ) \
+ || ENABLED( E2_IS_TMC26X ) \
+ || ENABLED( E3_IS_TMC26X ) \
+ || ENABLED( E4_IS_TMC26X ) \
+ )
+ #error "HAVE_TMC26X requires at least one TMC26X stepper to be set."
+#endif
+
+/**
+ * Make sure HAVE_TMC2130 is warranted
+ */
+#if ENABLED(HAVE_TMC2130)
+ #if !( ENABLED( X_IS_TMC2130 ) \
+ || ENABLED( X2_IS_TMC2130 ) \
+ || ENABLED( Y_IS_TMC2130 ) \
+ || ENABLED( Y2_IS_TMC2130 ) \
+ || ENABLED( Z_IS_TMC2130 ) \
+ || ENABLED( Z2_IS_TMC2130 ) \
+ || ENABLED( E0_IS_TMC2130 ) \
+ || ENABLED( E1_IS_TMC2130 ) \
+ || ENABLED( E2_IS_TMC2130 ) \
+ || ENABLED( E3_IS_TMC2130 ) \
+ || ENABLED( E4_IS_TMC2130 ) )
+ #error "HAVE_TMC2130 requires at least one TMC2130 stepper to be set."
+ #elif ENABLED(HYBRID_THRESHOLD) && DISABLED(STEALTHCHOP)
+ #error "Enable STEALTHCHOP to use HYBRID_THRESHOLD."
+ #endif
+
+ #if ENABLED(X_IS_TMC2130) && !PIN_EXISTS(X_CS)
+ #error "X_CS_PIN is required for X_IS_TMC2130. Define X_CS_PIN in Configuration_adv.h."
+ #elif ENABLED(X2_IS_TMC2130) && !PIN_EXISTS(X2_CS)
+ #error "X2_CS_PIN is required for X2_IS_TMC2130. Define X2_CS_PIN in Configuration_adv.h."
+ #elif ENABLED(Y_IS_TMC2130) && !PIN_EXISTS(Y_CS)
+ #error "Y_CS_PIN is required for Y_IS_TMC2130. Define Y_CS_PIN in Configuration_adv.h."
+ #elif ENABLED(Y2_IS_TMC2130) && !PIN_EXISTS(Y2_CS)
+ #error "Y2_CS_PIN is required for Y2_IS_TMC2130. Define Y2_CS_PIN in Configuration_adv.h."
+ #elif ENABLED(Z_IS_TMC2130) && !PIN_EXISTS(Z_CS)
+ #error "Z_CS_PIN is required for Z_IS_TMC2130. Define Z_CS_PIN in Configuration_adv.h."
+ #elif ENABLED(Z2_IS_TMC2130) && !PIN_EXISTS(Z2_CS)
+ #error "Z2_CS_PIN is required for Z2_IS_TMC2130. Define Z2_CS_PIN in Configuration_adv.h."
+ #elif ENABLED(E0_IS_TMC2130) && !PIN_EXISTS(E0_CS)
+ #error "E0_CS_PIN is required for E0_IS_TMC2130. Define E0_CS_PIN in Configuration_adv.h."
+ #elif ENABLED(E1_IS_TMC2130) && !PIN_EXISTS(E1_CS)
+ #error "E1_CS_PIN is required for E1_IS_TMC2130. Define E1_CS_PIN in Configuration_adv.h."
+ #elif ENABLED(E2_IS_TMC2130) && !PIN_EXISTS(E2_CS)
+ #error "E2_CS_PIN is required for E2_IS_TMC2130. Define E2_CS_PIN in Configuration_adv.h."
+ #elif ENABLED(E3_IS_TMC2130) && !PIN_EXISTS(E3_CS)
+ #error "E3_CS_PIN is required for E3_IS_TMC2130. Define E3_CS_PIN in Configuration_adv.h."
+ #elif ENABLED(E4_IS_TMC2130) && !PIN_EXISTS(E4_CS)
+ #error "E4_CS_PIN is required for E4_IS_TMC2130. Define E4_CS_PIN in Configuration_adv.h."
+ #endif
+
+ // Require STEALTHCHOP for SENSORLESS_HOMING on DELTA as the transition from spreadCycle to stealthChop
+ // is necessary in order to reset the stallGuard indication between the initial movement of all three
+ // towers to +Z and the individual homing of each tower. This restriction can be removed once a means of
+ // clearing the stallGuard activated status is found.
+ #if ENABLED(SENSORLESS_HOMING) && ENABLED(DELTA) && !ENABLED(STEALTHCHOP)
+ #error "SENSORLESS_HOMING on DELTA currently requires STEALTHCHOP."
+ #endif
+
+ // Sensorless homing is required for both combined steppers in an H-bot
+ #if CORE_IS_XY && X_SENSORLESS != Y_SENSORLESS
+ #error "CoreXY requires both X and Y to use sensorless homing if either does."
+ #elif CORE_IS_XZ && X_SENSORLESS != Z_SENSORLESS
+ #error "CoreXZ requires both X and Z to use sensorless homing if either does."
+ #elif CORE_IS_YZ && Y_SENSORLESS != Z_SENSORLESS
+ #error "CoreYZ requires both Y and Z to use sensorless homing if either does."
+ #endif
+
+#elif ENABLED(SENSORLESS_HOMING)
+
+ #error "SENSORLESS_HOMING requires TMC2130 stepper drivers."
+
+#endif
+
+/**
+ * Make sure HAVE_TMC2208 is warranted
+ */
+#if ENABLED(HAVE_TMC2208) && !( \
+ ENABLED( X_IS_TMC2208 ) \
+ || ENABLED( X2_IS_TMC2208 ) \
+ || ENABLED( Y_IS_TMC2208 ) \
+ || ENABLED( Y2_IS_TMC2208 ) \
+ || ENABLED( Z_IS_TMC2208 ) \
+ || ENABLED( Z2_IS_TMC2208 ) \
+ || ENABLED( E0_IS_TMC2208 ) \
+ || ENABLED( E1_IS_TMC2208 ) \
+ || ENABLED( E2_IS_TMC2208 ) \
+ || ENABLED( E3_IS_TMC2208 ) )
+ #error "HAVE_TMC2208 requires at least one TMC2208 stepper to be set."
+#endif
+
+/**
+ * TMC2208 software UART and ENDSTOP_INTERRUPTS both use pin change interrupts (PCI)
+ */
+#if ENABLED(HAVE_TMC2208) && ENABLED(ENDSTOP_INTERRUPTS_FEATURE) && !( \
+ defined(X_HARDWARE_SERIAL ) \
+ || defined(X2_HARDWARE_SERIAL) \
+ || defined(Y_HARDWARE_SERIAL ) \
+ || defined(Y2_HARDWARE_SERIAL) \
+ || defined(Z_HARDWARE_SERIAL ) \
+ || defined(Z2_HARDWARE_SERIAL) \
+ || defined(E0_HARDWARE_SERIAL) \
+ || defined(E1_HARDWARE_SERIAL) \
+ || defined(E2_HARDWARE_SERIAL) \
+ || defined(E3_HARDWARE_SERIAL) \
+ || defined(E4_HARDWARE_SERIAL) )
+ #error "select hardware UART for TMC2208 to use both TMC2208 and ENDSTOP_INTERRUPTS_FEATURE."
+#endif
+
+#if ENABLED(HYBRID_THRESHOLD) && DISABLED(STEALTHCHOP)
+ #error "Enable STEALTHCHOP to use HYBRID_THRESHOLD."
+#endif
+
+#if ENABLED(TMC_Z_CALIBRATION) && !Z_IS_TRINAMIC && !Z2_IS_TRINAMIC
+ #error "TMC_Z_CALIBRATION requires at least one TMC driver on Z axis"
+#endif
+
+/**
+ * Make sure HAVE_L6470DRIVER is warranted
+ */
+#if ENABLED(HAVE_L6470DRIVER) && !( \
+ ENABLED( X_IS_L6470 ) \
+ || ENABLED( X2_IS_L6470 ) \
+ || ENABLED( Y_IS_L6470 ) \
+ || ENABLED( Y2_IS_L6470 ) \
+ || ENABLED( Z_IS_L6470 ) \
+ || ENABLED( Z2_IS_L6470 ) \
+ || ENABLED( E0_IS_L6470 ) \
+ || ENABLED( E1_IS_L6470 ) \
+ || ENABLED( E2_IS_L6470 ) \
+ || ENABLED( E3_IS_L6470 ) \
+ || ENABLED( E4_IS_L6470 ) \
+ )
+ #error "HAVE_L6470DRIVER requires at least one L6470 stepper to be set."
+#endif
+
+/**
+ * Check that each axis has only one driver selected
+ */
+#if 1 < 0 \
+ + ENABLED(X_IS_TMC26X) \
+ + ENABLED(X_IS_TMC2130) \
+ + ENABLED(X_IS_TMC2208) \
+ + ENABLED(X_IS_L6470)
+ #error "Please enable only one stepper driver for the X axis."
+#endif
+#if 1 < 0 \
+ + ENABLED(X2_IS_TMC26X) \
+ + ENABLED(X2_IS_TMC2130) \
+ + ENABLED(X2_IS_TMC2208) \
+ + ENABLED(X2_IS_L6470)
+ #error "Please enable only one stepper driver for the X2 axis."
+#endif
+#if 1 < 0 \
+ + ENABLED(Y_IS_TMC26X) \
+ + ENABLED(Y_IS_TMC2130) \
+ + ENABLED(Y_IS_TMC2208) \
+ + ENABLED(Y_IS_L6470)
+ #error "Please enable only one stepper driver for the Y axis."
+#endif
+#if 1 < 0 \
+ + ENABLED(Y2_IS_TMC26X) \
+ + ENABLED(Y2_IS_TMC2130) \
+ + ENABLED(Y2_IS_TMC2208) \
+ + ENABLED(Y2_IS_L6470)
+ #error "Please enable only one stepper driver for the Y2 axis."
+#endif
+#if 1 < 0 \
+ + ENABLED(Z_IS_TMC26X) \
+ + ENABLED(Z_IS_TMC2130) \
+ + ENABLED(Z_IS_TMC2208) \
+ + ENABLED(Z_IS_L6470)
+ #error "Please enable only one stepper driver for the Z axis."
+#endif
+#if 1 < 0 \
+ + ENABLED(Z2_IS_TMC26X) \
+ + ENABLED(Z2_IS_TMC2130) \
+ + ENABLED(Z2_IS_TMC2208) \
+ + ENABLED(Z2_IS_L6470)
+ #error "Please enable only one stepper driver for the Z2 axis."
+#endif
+#if 1 < 0 \
+ + ENABLED(E0_IS_TMC26X) \
+ + ENABLED(E0_IS_TMC2130) \
+ + ENABLED(E0_IS_TMC2208) \
+ + ENABLED(E0_IS_L6470)
+ #error "Please enable only one stepper driver for the E0 axis."
+#endif
+#if 1 < 0 \
+ + ENABLED(E1_IS_TMC26X) \
+ + ENABLED(E1_IS_TMC2130) \
+ + ENABLED(E1_IS_TMC2208) \
+ + ENABLED(E1_IS_L6470)
+ #error "Please enable only one stepper driver for the E1 axis."
+#endif
+#if 1 < 0 \
+ + ENABLED(E2_IS_TMC26X) \
+ + ENABLED(E2_IS_TMC2130) \
+ + ENABLED(E2_IS_TMC2208) \
+ + ENABLED(E2_IS_L6470)
+ #error "Please enable only one stepper driver for the E2 axis."
+#endif
+#if 1 < 0 \
+ + ENABLED(E3_IS_TMC26X) \
+ + ENABLED(E3_IS_TMC2130) \
+ + ENABLED(E3_IS_TMC2208) \
+ + ENABLED(E3_IS_L6470)
+ #error "Please enable only one stepper driver for the E3 axis."
+#endif
+#if 1 < 0 \
+ + ENABLED(E4_IS_TMC26X) \
+ + ENABLED(E4_IS_TMC2130) \
+ + ENABLED(E4_IS_TMC2208) \
+ + ENABLED(E4_IS_L6470)
+ #error "Please enable only one stepper driver for the E4 axis."
+#endif
+
+/**
+ * Digipot requirement
+ */
+#if ENABLED(DIGIPOT_MCP4018)
+ #if !defined(DIGIPOTS_I2C_SDA_X) || !defined(DIGIPOTS_I2C_SDA_Y) || !defined(DIGIPOTS_I2C_SDA_Z) \
+ || !defined(DIGIPOTS_I2C_SDA_E0) || !defined(DIGIPOTS_I2C_SDA_E1)
+ #error "DIGIPOT_MCP4018 requires DIGIPOTS_I2C_SDA_* pins to be defined."
+ #endif
+#endif
+
+/**
+ * Require 4 or more elements in per-axis initializers
+ */
+constexpr float sanity_arr_1[] = DEFAULT_AXIS_STEPS_PER_UNIT,
+ sanity_arr_2[] = DEFAULT_MAX_FEEDRATE,
+ sanity_arr_3[] = DEFAULT_MAX_ACCELERATION;
+static_assert(COUNT(sanity_arr_1) >= XYZE, "DEFAULT_AXIS_STEPS_PER_UNIT requires 4 (or more) elements.");
+static_assert(COUNT(sanity_arr_2) >= XYZE, "DEFAULT_MAX_FEEDRATE requires 4 (or more) elements.");
+static_assert(COUNT(sanity_arr_3) >= XYZE, "DEFAULT_MAX_ACCELERATION requires 4 (or more) elements.");
+static_assert(COUNT(sanity_arr_1) <= XYZE_N, "DEFAULT_AXIS_STEPS_PER_UNIT has too many elements.");
+static_assert(COUNT(sanity_arr_2) <= XYZE_N, "DEFAULT_MAX_FEEDRATE has too many elements.");
+static_assert(COUNT(sanity_arr_3) <= XYZE_N, "DEFAULT_MAX_ACCELERATION has too many elements.");
+
+/**
+ * Sanity checks for Spindle / Laser
+ */
+#if ENABLED(SPINDLE_LASER_ENABLE)
+ #if !PIN_EXISTS(SPINDLE_LASER_ENABLE)
+ //#error "SPINDLE_LASER_ENABLE requires SPINDLE_LASER_ENABLE_PIN."
+ #elif SPINDLE_DIR_CHANGE && !PIN_EXISTS(SPINDLE_DIR)
+ #error "SPINDLE_DIR_PIN not defined."
+ #elif ENABLED(SPINDLE_LASER_PWM) && PIN_EXISTS(SPINDLE_LASER_PWM)
+ #if !(WITHIN(SPINDLE_LASER_PWM_PIN, 2, 13) || WITHIN(SPINDLE_LASER_PWM_PIN, 44, 46))
+ #error "SPINDLE_LASER_PWM_PIN not assigned to a PWM pin."
+ #elif SPINDLE_LASER_POWERUP_DELAY < 1
+ #error "SPINDLE_LASER_POWERUP_DELAY must be greater than 0."
+ #elif SPINDLE_LASER_POWERDOWN_DELAY < 1
+ #error "SPINDLE_LASER_POWERDOWN_DELAY must be greater than 0."
+ #elif !defined(SPINDLE_LASER_PWM_INVERT)
+ #error "SPINDLE_LASER_PWM_INVERT missing."
+ #elif !defined(SPEED_POWER_SLOPE) || !defined(SPEED_POWER_INTERCEPT) || !defined(SPEED_POWER_MIN) || !defined(SPEED_POWER_MAX)
+ #error "SPINDLE_LASER_PWM equation constant(s) missing."
+ #elif SPINDLE_LASER_PWM_PIN == 4 || WITHIN(SPINDLE_LASER_PWM_PIN, 11, 13)
+ #error "Counter/Timer for SPINDLE_LASER_PWM_PIN is used by a system interrupt."
+ #elif PIN_EXISTS(X_MAX) && X_MAX_PIN == SPINDLE_LASER_PWM_PIN
+ #error "SPINDLE_LASER_PWM pin is in use by X_MAX endstop."
+ #elif PIN_EXISTS(X_MIN) && X_MIN_PIN == SPINDLE_LASER_PWM_PIN
+ #error "SPINDLE_LASER_PWM pin is in use by X_MIN endstop."
+ #elif PIN_EXISTS(Z_STEP) && Z_STEP_PIN == SPINDLE_LASER_PWM_PIN
+ #error "SPINDLE_LASER_PWM pin in use by Z_STEP."
+ #elif NUM_SERVOS > 0 && (WITHIN(SPINDLE_LASER_PWM_PIN, 2, 3) || SPINDLE_LASER_PWM_PIN == 5)
+ #error "Counter/Timer for SPINDLE_LASER_PWM_PIN is used by the servo system."
+ #elif PIN_EXISTS(CASE_LIGHT) && SPINDLE_LASER_PWM_PIN == CASE_LIGHT_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used by CASE_LIGHT_PIN."
+ #elif PIN_EXISTS(E0_AUTO_FAN) && SPINDLE_LASER_PWM_PIN == E0_AUTO_FAN_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used by E0_AUTO_FAN_PIN."
+ #elif PIN_EXISTS(E1_AUTO_FAN) && SPINDLE_LASER_PWM_PIN == E1_AUTO_FAN_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used by E1_AUTO_FAN_PIN."
+ #elif PIN_EXISTS(E2_AUTO_FAN) && SPINDLE_LASER_PWM_PIN == E2_AUTO_FAN_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used by E2_AUTO_FAN_PIN."
+ #elif PIN_EXISTS(E3_AUTO_FAN) && SPINDLE_LASER_PWM_PIN == E3_AUTO_FAN_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used by E3_AUTO_FAN_PIN."
+ #elif PIN_EXISTS(E4_AUTO_FAN) && SPINDLE_LASER_PWM_PIN == E4_AUTO_FAN_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used by E4_AUTO_FAN_PIN."
+ #elif PIN_EXISTS(FAN) && SPINDLE_LASER_PWM_PIN == FAN_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used FAN_PIN."
+ #elif PIN_EXISTS(FAN1) && SPINDLE_LASER_PWM_PIN == FAN1_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used FAN1_PIN."
+ #elif PIN_EXISTS(FAN2) && SPINDLE_LASER_PWM_PIN == FAN2_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used FAN2_PIN."
+ #elif PIN_EXISTS(CONTROLLERFAN) && SPINDLE_LASER_PWM_PIN == CONTROLLERFAN_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used by CONTROLLERFAN_PIN."
+ #elif PIN_EXISTS(MOTOR_CURRENT_PWM_XY) && SPINDLE_LASER_PWM_PIN == MOTOR_CURRENT_PWM_XY_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used by MOTOR_CURRENT_PWM_XY."
+ #elif PIN_EXISTS(MOTOR_CURRENT_PWM_Z) && SPINDLE_LASER_PWM_PIN == MOTOR_CURRENT_PWM_Z_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used by MOTOR_CURRENT_PWM_Z."
+ #elif PIN_EXISTS(MOTOR_CURRENT_PWM_E) && SPINDLE_LASER_PWM_PIN == MOTOR_CURRENT_PWM_E_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used by MOTOR_CURRENT_PWM_E."
+ #elif PIN_EXISTS(CASE_LIGHT) && SPINDLE_LASER_PWM_PIN == CASE_LIGHT_PIN
+ #error "SPINDLE_LASER_PWM_PIN is used by CASE_LIGHT."
+ #endif
+ #endif
+#endif // SPINDLE_LASER_ENABLE
+
+#if ENABLED(CNC_COORDINATE_SYSTEMS) && ENABLED(NO_WORKSPACE_OFFSETS)
+ #error "CNC_COORDINATE_SYSTEMS is incompatible with NO_WORKSPACE_OFFSETS."
+#endif
+
+#if !BLOCK_BUFFER_SIZE || !IS_POWER_OF_2(BLOCK_BUFFER_SIZE)
+ #error "BLOCK_BUFFER_SIZE must be a power of 2."
+#endif
+
+#if ENABLED(LED_CONTROL_MENU) && DISABLED(ULTIPANEL)
+ #error "LED_CONTROL_MENU requires an LCD controller."
+#endif
+
+#if ENABLED(CASE_LIGHT_USE_NEOPIXEL) && DISABLED(NEOPIXEL_LED)
+ #error "CASE_LIGHT_USE_NEOPIXEL requires NEOPIXEL_LED."
+#endif
+
+#if ENABLED(SKEW_CORRECTION)
+ #if !defined(XY_SKEW_FACTOR) && !(defined(XY_DIAG_AC) && defined(XY_DIAG_BD) && defined(XY_SIDE_AD))
+ #error "SKEW_CORRECTION requires XY_SKEW_FACTOR or XY_DIAG_AC, XY_DIAG_BD, XY_SIDE_AD."
+ #endif
+ #if ENABLED(SKEW_CORRECTION_FOR_Z)
+ #if !defined(XZ_SKEW_FACTOR) && !(defined(XZ_DIAG_AC) && defined(XZ_DIAG_BD) && defined(XZ_SIDE_AD))
+ #error "SKEW_CORRECTION requires XZ_SKEW_FACTOR or XZ_DIAG_AC, XZ_DIAG_BD, XZ_SIDE_AD."
+ #endif
+ #if !defined(YZ_SKEW_FACTOR) && !(defined(YZ_DIAG_AC) && defined(YZ_DIAG_BD) && defined(YZ_SIDE_AD))
+ #error "SKEW_CORRECTION requires YZ_SKEW_FACTOR or YZ_DIAG_AC, YZ_DIAG_BD, YZ_SIDE_AD."
+ #endif
+ #endif
+#endif
+
+#endif // _SANITYCHECK_H_
diff --git a/Version.h b/Version.h
new file mode 100644
index 0000000000..224401111f
--- /dev/null
+++ b/Version.h
@@ -0,0 +1,96 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/**
+ * This file is the standard Marlin version identifier file, all fields can be
+ * overriden by the ones defined on _Version.h by using the Configuration.h
+ * directive USE_AUTOMATIC_VERSIONING.
+ */
+
+#if ENABLED(USE_AUTOMATIC_VERSIONING)
+
+ #include "_Version.h"
+
+#else
+
+ /**
+ * Marlin release version identifier
+ */
+ #define SHORT_BUILD_VERSION "1.1.8_TR1"
+
+ /**
+ * Verbose version identifier which should contain a reference to the location
+ * from where the binary was downloaded or the source code was compiled.
+ */
+
+
+ #define DETAILED_BUILD_VERSION SHORT_BUILD_VERSION " TM3D "
+
+ /**
+ * The STRING_DISTRIBUTION_DATE represents when the binary file was built,
+ * here we define this default string as the date where the latest release
+ * version was tagged.
+ */
+ #define STRING_DISTRIBUTION_DATE "2018-03-31"
+
+ /**
+ * Required minimum Configuration.h and Configuration_adv.h file versions.
+ *
+ * You must increment this version number for every significant change such as,
+ * but not limited to: ADD, DELETE RENAME OR REPURPOSE any directive/option on
+ * the configuration files.
+ */
+ #define REQUIRED_CONFIGURATION_H_VERSION 010107
+ #define REQUIRED_CONFIGURATION_ADV_H_VERSION 010107
+
+ /**
+ * The protocol for communication to the host. Protocol indicates communication
+ * standards such as the use of ASCII, "echo:" and "error:" line prefixes, etc.
+ * (Other behaviors are given by the firmware version and capabilities report.)
+ */
+ #define PROTOCOL_VERSION "1.0"
+
+ /**
+ * Defines a generic printer name to be output to the LCD after booting Marlin.
+ */
+ #define MACHINE_NAME "TM3D TRex2+"
+
+ /**
+ * The SOURCE_CODE_URL is the location where users will find the Marlin Source
+ * Code which is installed on the device. In most cases —unless the manufacturer
+ * has a distinct Github fork— the Source Code URL should just be the main
+ * Marlin repository.
+ */
+ #define SOURCE_CODE_URL "https://github.com/MarlinFirmware/Marlin"
+
+ /**
+ * Default generic printer UUID.
+ */
+ #define DEFAULT_MACHINE_UUID "cede2a2f-41a2-4748-9b12-c55c62f367ff"
+
+ /**
+ * The WEBSITE_URL is the location where users can get more information such as
+ * documentation about a specific Marlin release.
+ */
+ #define WEBSITE_URL "tinymachines3d.com"
+
+#endif // USE_AUTOMATIC_VERSIONING
diff --git a/_Bootscreen.h b/_Bootscreen.h
new file mode 100644
index 0000000000..64b224471c
--- /dev/null
+++ b/_Bootscreen.h
@@ -0,0 +1,106 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/**
+ * Custom Bitmap for splashscreen
+ *
+ * You may use one of the following tools to generate the C++ bitmap array from
+ * a black and white image:
+ *
+ * - http://www.marlinfw.org/tools/u8glib/converter.html
+ * - http://www.digole.com/tools/PicturetoC_Hex_converter.php
+ */
+#include
+
+#define CUSTOM_BOOTSCREEN_TIMEOUT 2500
+#define CUSTOM_BOOTSCREEN_BMPWIDTH 128
+#define CUSTOM_BOOTSCREEN_BMPHEIGHT 64
+
+const unsigned char custom_start_bmp[] PROGMEM = {
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFD, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFD, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFE, 0x07, 0xC0, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFC, 0x0A, 0x20, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFE, 0x14, 0x10, 0x05, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFC, 0x28, 0x08, 0x06, 0x07, 0xC0, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F, 0xFF, 0xFF, 0xFF,
+0xFE, 0x54, 0x04, 0x04, 0x0A, 0x20, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF,
+0xFC, 0x60, 0x04, 0x06, 0x14, 0x10, 0x05, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFE, 0x50, 0x04, 0x04, 0x28, 0x08, 0x06, 0x07, 0xC0, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F,
+0xFC, 0x60, 0x04, 0x06, 0x54, 0x04, 0x04, 0x0A, 0x20, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F,
+0xFE, 0x54, 0x04, 0x1C, 0x60, 0x04, 0x06, 0x14, 0x10, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F,
+0xFC, 0x28, 0x08, 0x2E, 0x50, 0x04, 0x04, 0x28, 0x08, 0x06, 0x07, 0xC0, 0x00, 0x01, 0xF0, 0x3F,
+0xFE, 0x14, 0x10, 0x54, 0x60, 0x04, 0x06, 0x54, 0x04, 0x04, 0x0A, 0x20, 0x00, 0x02, 0x08, 0x1F,
+0xFC, 0x0A, 0x20, 0x66, 0x54, 0x04, 0x1C, 0x60, 0x04, 0x06, 0x14, 0x10, 0x00, 0x05, 0x04, 0x3F,
+0xFE, 0x07, 0xC0, 0x54, 0x28, 0x08, 0x2E, 0x50, 0x04, 0x04, 0x28, 0x08, 0x00, 0x0A, 0x02, 0x1F,
+0xFC, 0x00, 0x00, 0x2E, 0x14, 0x10, 0x54, 0x60, 0x04, 0x06, 0x54, 0x04, 0x00, 0x15, 0x01, 0x3F,
+0xFE, 0x00, 0x00, 0x1C, 0x0A, 0x20, 0x66, 0x54, 0x04, 0x1C, 0x60, 0x04, 0x00, 0x18, 0x01, 0x1F,
+0xFC, 0x00, 0x00, 0x06, 0x07, 0xC0, 0x54, 0x28, 0x08, 0x2E, 0x50, 0x04, 0x00, 0x14, 0x01, 0x3F,
+0xFE, 0x00, 0x00, 0x04, 0x00, 0x00, 0x2E, 0x14, 0x10, 0x54, 0x60, 0x04, 0x00, 0x18, 0x01, 0x1F,
+0xFC, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1C, 0x0A, 0x20, 0x66, 0x54, 0x04, 0x1C, 0x15, 0x01, 0x3F,
+0xFE, 0xD5, 0x55, 0x54, 0x00, 0x00, 0x06, 0x07, 0xC0, 0x54, 0x28, 0x08, 0x2A, 0x0A, 0x02, 0x1F,
+0xFC, 0x84, 0x10, 0x46, 0x00, 0x00, 0x04, 0x00, 0x00, 0x2E, 0x14, 0x10, 0x51, 0x05, 0x04, 0x3F,
+0xFE, 0xC4, 0x10, 0x44, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1C, 0x0A, 0x20, 0x61, 0x02, 0x88, 0x1F,
+0xFC, 0x84, 0x10, 0x46, 0xD5, 0x55, 0x54, 0x00, 0x00, 0x06, 0x07, 0xC0, 0x51, 0x01, 0xF0, 0x3F,
+0xFE, 0x84, 0x10, 0x44, 0x84, 0x10, 0x46, 0x00, 0x00, 0x04, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x1F,
+0xFC, 0xC4, 0x10, 0x46, 0xC4, 0x10, 0x44, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x3F,
+0xFE, 0x84, 0x10, 0x44, 0x84, 0x10, 0x46, 0xD5, 0x55, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F,
+0xFC, 0xD5, 0x55, 0x56, 0x84, 0x10, 0x44, 0x84, 0x10, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F,
+0xFC, 0xFF, 0xFF, 0xFC, 0xC4, 0x10, 0x46, 0xC4, 0x10, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F,
+0xFE, 0x00, 0x00, 0x06, 0x84, 0x10, 0x44, 0x84, 0x10, 0x46, 0xD5, 0x55, 0x55, 0x55, 0x55, 0xBF,
+0xFD, 0x55, 0x55, 0x54, 0xD5, 0x55, 0x56, 0x84, 0x10, 0x44, 0x84, 0x10, 0x41, 0x04, 0x10, 0x9F,
+0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFC, 0xC4, 0x10, 0x46, 0xC4, 0x10, 0x41, 0x04, 0x11, 0xBF,
+0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x06, 0x84, 0x10, 0x44, 0x84, 0x10, 0x41, 0x04, 0x10, 0x9F,
+0xFF, 0xFF, 0xFF, 0xFD, 0x55, 0x55, 0x54, 0xD5, 0x55, 0x56, 0x84, 0x10, 0x41, 0x04, 0x10, 0x9F,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFC, 0xC4, 0x10, 0x41, 0x04, 0x11, 0xBF,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x06, 0x84, 0x10, 0x41, 0x04, 0x10, 0x9F,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x55, 0x55, 0x54, 0xD5, 0x55, 0x55, 0x55, 0x55, 0xBF,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xE0, 0x2E, 0xFB, 0x7D, 0xFB, 0xFB, 0xCF, 0xC1, 0xDF, 0xBB, 0x3E, 0xC0, 0xE1, 0xFE, 0x3C, 0x1F,
+0xFD, 0xEE, 0x7B, 0x39, 0xF9, 0xF3, 0xCF, 0x9E, 0xDF, 0xBB, 0x3E, 0xDF, 0xDE, 0xFC, 0xDD, 0xE7,
+0xFD, 0xEE, 0x3B, 0xBB, 0xF9, 0xEB, 0xD7, 0xBF, 0x5F, 0xBB, 0x5E, 0xDF, 0xDE, 0xFD, 0xED, 0xF7,
+0xFD, 0xEE, 0xBB, 0xD3, 0xFA, 0xEB, 0xB7, 0x3F, 0xDF, 0xBB, 0x4E, 0xDF, 0xDF, 0xFF, 0xCD, 0xF3,
+0xFD, 0xEE, 0xDB, 0xC7, 0xFA, 0xEB, 0xBB, 0x7F, 0xC0, 0x3B, 0x6E, 0xC0, 0xE3, 0xFF, 0x1D, 0xF3,
+0xFD, 0xEE, 0xCB, 0xEF, 0xFA, 0xDB, 0xBB, 0x7F, 0xDF, 0xBB, 0x66, 0xDF, 0xF8, 0xFF, 0xCD, 0xF3,
+0xFD, 0xEE, 0xEB, 0xEF, 0xFB, 0x5B, 0x03, 0x3F, 0x5F, 0xBB, 0x76, 0xDF, 0xFE, 0x7F, 0xED, 0xF3,
+0xFD, 0xEE, 0xF3, 0xEF, 0xFB, 0x5B, 0x79, 0xBE, 0xDF, 0xBB, 0x7A, 0xDF, 0xDE, 0x7D, 0xED, 0xF7,
+0xFD, 0xEE, 0xF3, 0xEF, 0xFB, 0xBA, 0xFD, 0x9E, 0xDF, 0xBB, 0x7C, 0xDF, 0xDE, 0xFD, 0xCD, 0xE7,
+0xFD, 0xEE, 0xFB, 0xEF, 0xFB, 0xBA, 0xFD, 0xC1, 0xDF, 0xBB, 0x7E, 0xC0, 0xE0, 0xFE, 0x1C, 0x1F,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+};
+
+
+
diff --git a/_Statusscreen.h b/_Statusscreen.h
new file mode 100644
index 0000000000..c26149a6b2
--- /dev/null
+++ b/_Statusscreen.h
@@ -0,0 +1,460 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/**
+ * Custom Status Screen bitmap
+ *
+ * Place this file in the root with your configuration files
+ * and enable CUSTOM_STATUS_SCREEN_IMAGE in Configuration.h.
+ *
+ * Use the Marlin Bitmap Converter to make your own:
+ * http://marlinfw.org/tools/u8glib/converter.html
+ */
+#include "MarlinConfig.h"
+
+//============================================
+
+#define STATUS_SCREENWIDTH 128
+
+#define STATUS_SCREEN_HOTEND_TEXT_X(E) (41 + (E) * 20)
+
+#define STATUS_SCREEN_BED_TEXT_X (HOTENDS > 1 ? 81 : 73)
+
+#define FAN_ANIM_FRAMES 3
+#define STATUS_SCREEN_FAN_TEXT_X (FAN_ANIM_FRAMES == 3 ? 103 : 105)
+#define STATUS_SCREEN_FAN_TEXT_Y (FAN_ANIM_FRAMES > 2 ? 28 : 27)
+
+//============================================
+
+#if HOTENDS < 2
+
+ #if FAN_ANIM_FRAMES <= 2
+
+ const unsigned char status_screen0_bmp[] PROGMEM = {
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000,
+ B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00111111,B00111000,
+ B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111110,B00011000,
+ B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111100,B00011000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101000,B01111100,B00001000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101100,B00111000,B00001000,
+ B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B00111001,B11001000,
+ B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11111111,B11101000,
+ B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11000111,B11101000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B11111111,B11101000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100111,B00111001,B11101000,
+ B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B00111000,B01101000,
+ B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B01111100,B00101000,
+ B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110000,B01111100,B00011000,
+ B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110000,B11111100,B00011000,
+ B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11111000,B00111000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000000,B11111000,
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000
+};
+ const unsigned char status_screen1_bmp[] PROGMEM = {
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B10000000,B11111000,
+ B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B10000000,B00111000,
+ B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B10000001,B11011000,
+ B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B11000011,B11011000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100111,B11000111,B11101000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100011,B11000111,B11111000,
+ B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100001,B11111111,B10001000,
+ B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01101100,B00001000,
+ B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01101100,B00001000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100000,B01101100,B00001000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100011,B11111111,B00001000,
+ B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00111111,B11000111,B10001000,
+ B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00101111,B11000111,B11001000,
+ B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110111,B10000111,B11011000,
+ B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110111,B00000011,B11011000,
+ B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00000011,B00111000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000010,B11111000,
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000
+};
+
+ #elif FAN_ANIM_FRAMES == 3
+
+
+const unsigned char status_screen0_bmp[] PROGMEM = {
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000,
+ B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00111111,B00111000,
+ B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111110,B00011000,
+ B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111100,B00011000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101000,B01111100,B00001000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101100,B00111000,B00001000,
+ B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B00111001,B11001000,
+ B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11111111,B11101000,
+ B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11000111,B11101000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B11111111,B11101000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100111,B00111001,B11101000,
+ B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B00111000,B01101000,
+ B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B01111100,B00101000,
+ B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110000,B01111100,B00011000,
+ B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110000,B11111100,B00011000,
+ B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11111000,B00111000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000000,B11111000,
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000
+};
+
+
+ const unsigned char status_screen1_bmp[] PROGMEM = {
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000,
+ B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00001111,B00111000,
+ B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110100,B00011111,B11011000,
+ B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110110,B00011111,B10011000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B00011111,B00001000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B10011110,B00001000,
+ B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B11111100,B00001000,
+ B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11011100,B00001000,
+ B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100111,B11101111,B11001000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100000,B01110111,B11101000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100000,B01111111,B11101000,
+ B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B11110011,B11101000,
+ B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100001,B11110001,B11101000,
+ B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110011,B11110000,B11011000,
+ B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110111,B11110000,B01011000,
+ B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11100000,B00111000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000000,B11111000,
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000
+};
+ const unsigned char status_screen2_bmp[] PROGMEM = {
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B10000000,B11111000,
+ B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B10000000,B00111000,
+ B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B10000001,B11011000,
+ B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B11000011,B11011000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100111,B11000111,B11101000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100011,B11000111,B11111000,
+ B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100001,B11111111,B10001000,
+ B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01101100,B00001000,
+ B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01101100,B00001000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100000,B01101100,B00001000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100011,B11111111,B00001000,
+ B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00111111,B11000111,B10001000,
+ B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00101111,B11000111,B11001000,
+ B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110111,B10000111,B11011000,
+ B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110111,B00000011,B11011000,
+ B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00000011,B00111000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000010,B11111000,
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000
+};
+
+ #elif FAN_ANIM_FRAMES == 4
+
+const unsigned char status_screen0_bmp[] PROGMEM = {
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000,
+ B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00111111,B00111000,
+ B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111110,B00011000,
+ B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111100,B00011000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101000,B01111100,B00001000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101100,B00111000,B00001000,
+ B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B00111001,B11001000,
+ B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11111111,B11101000,
+ B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11000111,B11101000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B11111111,B11101000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100111,B00111001,B11101000,
+ B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B00111000,B01101000,
+ B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B01111100,B00101000,
+ B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110000,B01111100,B00011000,
+ B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110000,B11111100,B00011000,
+ B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11111000,B00111000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000000,B11111000,
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000
+};
+
+const unsigned char status_screen1_bmp[] PROGMEM = {
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000,
+ B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00001111,B00111000,
+ B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110100,B00011111,B11011000,
+ B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110110,B00011111,B10011000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B00011111,B00001000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B10011110,B00001000,
+ B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B11111100,B00001000,
+ B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11011100,B00001000,
+ B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100111,B11101111,B11001000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100000,B01110111,B11101000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100000,B01111111,B11101000,
+ B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B11110011,B11101000,
+ B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100001,B11110001,B11101000,
+ B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110011,B11110000,B11011000,
+ B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110111,B11110000,B01011000,
+ B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11100000,B00111000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000000,B11111000,
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000
+};
+
+const unsigned char status_screen2_bmp[] PROGMEM = {
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B10000000,B11111000,
+ B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B10000000,B00111000,
+ B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B10000001,B11011000,
+ B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B11000011,B11011000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100111,B11000111,B11101000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100011,B11000111,B11111000,
+ B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100001,B11111111,B10001000,
+ B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01101100,B00001000,
+ B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01101100,B00001000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100000,B01101100,B00001000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100011,B11111111,B00001000,
+ B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00111111,B11000111,B10001000,
+ B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00101111,B11000111,B11001000,
+ B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110111,B10000111,B11011000,
+ B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110111,B00000011,B11011000,
+ B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00000011,B00111000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000010,B11111000,
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000
+};
+
+
+const unsigned char status_screen3_bmp[] PROGMEM = {
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000,
+ B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11110000,B00111000,
+ B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110001,B11100000,B00011000,
+ B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110001,B11100000,B00011000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100001,B11100001,B11101000,
+ B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100000,B11110011,B11101000,
+ B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100000,B01111111,B11101000,
+ B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01110111,B11101000,
+ B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101000,B11101110,B00101000,
+ B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B11011100,B00001000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00101111,B11111100,B00001000,
+ B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00101111,B10011110,B00001000,
+ B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00101111,B00001111,B00001000,
+ B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110000,B00001111,B00011000,
+ B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110000,B00001111,B00011000,
+ B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00011111,B00111000,
+ B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000000,B11111000,
+ B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000
+};
+
+
+ #endif
+
+#else // HOTENDS >= 2
+
+ #if FAN_ANIM_FRAMES <= 2
+
+ const unsigned char status_screen0_bmp[] PROGMEM = {
+ B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11110000,
+ B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00000000,B01110000,
+ B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B11111100,B00110000,
+ B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100000,B11111100,B00010000,
+ B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100000,B01111000,B00010000,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100000,B00110000,B00010000,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101100,B00000000,B11010000,
+ B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00101110,B00110001,B11010000,
+ B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00101111,B01111011,B11010000,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00101111,B01111011,B11010000,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00101110,B00110001,B11010000,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00101100,B00000000,B11010000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00100000,B00110000,B00010000,
+ B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00100000,B01111000,B00010000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00100000,B11111100,B00010000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00110000,B11111100,B00110000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00111000,B00000000,B01110000,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00111111,B11111111,B11110000,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00000000,B00000000,B00000000
+ };
+ const unsigned char status_screen1_bmp[] PROGMEM = {
+ B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11110000,
+ B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00000000,B01110000,
+ B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110011,B10000111,B00110000,
+ B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100111,B10000111,B10010000,
+ B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B10000111,B11010000,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B10000111,B11010000,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B00000011,B11010000,
+ B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00100000,B00110000,B00010000,
+ B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00100000,B01111000,B00010000,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00100000,B01111000,B00010000,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00100000,B00110000,B00010000,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00101111,B00000011,B11010000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00101111,B10000111,B11010000,
+ B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00101111,B10000111,B11010000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00100111,B10000111,B10010000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00110011,B10000111,B00110000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00111000,B00000000,B01110000,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00111111,B11111111,B11110000,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00000000,B00000000,B00000000
+ };
+
+ #elif FAN_ANIM_FRAMES == 3
+
+ const unsigned char status_screen0_bmp[] PROGMEM = {
+ B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B11111111,B11111111,
+ B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B11000000,B00011111,
+ B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B00100000,B00100111,
+ B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000110,B11110000,B01111011,
+ B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000110,B11110000,B01111011,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000101,B11111000,B11111101,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000100,B11111000,B11111001,
+ B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00000100,B00111111,B11100001,
+ B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00000100,B00001111,B10000001,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00000100,B00001111,B10000001,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00000100,B00001111,B10000001,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00000100,B00111111,B11100001,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00000100,B11111000,B11111001,
+ B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00000101,B11111000,B11111101,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00000110,B11110000,B01111011,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00000110,B11110000,B01111011,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00000111,B00100000,B00100111,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00000111,B11000000,B00011111,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00000111,B11111111,B11111111
+ };
+ const unsigned char status_screen1_bmp[] PROGMEM = {
+ B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B11111111,B11111111,
+ B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B11000110,B00011111,
+ B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B00111110,B00000111,
+ B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000110,B00111110,B00000011,
+ B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000110,B00011110,B00000011,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000100,B00011110,B00001101,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000100,B00000110,B00111101,
+ B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00000100,B00000111,B00111101,
+ B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00000100,B00001111,B11111111,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00000111,B11111111,B11111111,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00000111,B11111111,B10000001,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00000101,B11100111,B00000001,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00000101,B11000011,B00000001,
+ B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00000101,B10000011,B11000001,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00000110,B00000011,B11000011,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00000110,B00000011,B11100011,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00000111,B00000011,B11100111,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00000111,B11000011,B00011111,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00000111,B11111111,B11111111
+ };
+ const unsigned char status_screen2_bmp[] PROGMEM = {
+ B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B11111111,B11111111,
+ B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B11000011,B00011111,
+ B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B00000011,B11100111,
+ B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000110,B00000011,B11110011,
+ B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000110,B10000011,B11100011,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000101,B11000011,B11000001,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000101,B11100011,B10000001,
+ B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00000101,B11110111,B00000001,
+ B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00000111,B11111111,B10000001,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00000111,B11111111,B11111111,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00000100,B00001111,B11111111,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00000100,B00000111,B01111101,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00000100,B00001110,B00111101,
+ B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00000100,B00011110,B00011101,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00000110,B00111110,B00001011,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00000110,B01111110,B00000011,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00000111,B00111110,B00000111,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00000111,B11000110,B00011111,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00000111,B11111111,B11111111
+ };
+
+ #elif FAN_ANIM_FRAMES == 4
+
+ const unsigned char status_screen0_bmp[] PROGMEM = {
+ B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000,
+ B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00111111,B00111000,
+ B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111110,B00011000,
+ B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111100,B00011000,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101000,B01111100,B00001000,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101100,B00111000,B00001000,
+ B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00101111,B00111001,B11001000,
+ B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00101111,B11111111,B11101000,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00101111,B11000111,B11101000,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00101111,B11111111,B11101000,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00100111,B00111001,B11101000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00100000,B00111000,B01101000,
+ B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00100000,B01111100,B00101000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00110000,B01111100,B00011000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00110000,B11111100,B00011000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00111001,B11111000,B00111000,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00111110,B00000000,B11111000,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00111111,B11111111,B11111000
+ };
+ const unsigned char status_screen1_bmp[] PROGMEM = {
+ B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000,
+ B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00001111,B00111000,
+ B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110100,B00011111,B11011000,
+ B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110110,B00011111,B10011000,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B00011111,B00001000,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B10011110,B00001000,
+ B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00101111,B11111100,B00001000,
+ B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00101111,B11011100,B00001000,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00100111,B11101111,B11001000,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00100000,B01110111,B11101000,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00100000,B01111111,B11101000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00100000,B11110011,B11101000,
+ B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00100001,B11110001,B11101000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00110011,B11110000,B11011000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00110111,B11110000,B01011000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00111001,B11100000,B00111000,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00111110,B00000000,B11111000,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00111111,B11111111,B11111000
+ };
+ const unsigned char status_screen2_bmp[] PROGMEM = {
+ B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B10000000,B11111000,
+ B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B10000000,B00111000,
+ B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B10000001,B11011000,
+ B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B11000011,B11011000,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100111,B11000111,B11101000,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100011,B11000111,B11111000,
+ B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00100001,B11111111,B10001000,
+ B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00100000,B01101100,B00001000,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00100000,B01101100,B00001000,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00100000,B01101100,B00001000,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00100011,B11111111,B00001000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00111111,B11000111,B10001000,
+ B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00101111,B11000111,B11001000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00110111,B10000111,B11011000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00110111,B00000011,B11011000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00111000,B00000011,B00111000,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00111110,B00000010,B11111000,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00111111,B11111111,B11111000
+ };
+ const unsigned char status_screen3_bmp[] PROGMEM = {
+ B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000,
+ B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000,
+ B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11110000,B00111000,
+ B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110001,B11100000,B00011000,
+ B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110001,B11100000,B00011000,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100001,B11100001,B11101000,
+ B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100000,B11110011,B11101000,
+ B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00100000,B01111111,B11101000,
+ B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00100000,B01110111,B11101000,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00101000,B11101110,B00101000,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00101111,B11011100,B00001000,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00101111,B11111100,B00001000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00101111,B10011110,B00001000,
+ B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00101111,B00001111,B00001000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00110000,B00001111,B00011000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00110000,B00001111,B00011000,
+ B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00111000,B00011111,B00111000,
+ B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00111110,B00000000,B11111000,
+ B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00111111,B11111111,B11111000
+ };
+
+ #endif
+
+#endif // HOTENDS >= 2
diff --git a/language_en.h b/language_en.h
new file mode 100644
index 0000000000..1d4cd887c2
--- /dev/null
+++ b/language_en.h
@@ -0,0 +1,1071 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/**
+ * English
+ *
+ * LCD Menu Messages
+ * See also http://marlinfw.org/docs/development/lcd_language.html
+ *
+ */
+#ifndef LANGUAGE_EN_H
+#define LANGUAGE_EN_H
+
+#define en 1234
+#if LCD_LANGUAGE == en
+ #define NOT_EXTENDED_ISO10646_1_5X7
+#endif
+#undef en
+
+#ifndef CHARSIZE
+ #define CHARSIZE 1
+#endif
+
+#ifndef WELCOME_MSG
+ #define WELCOME_MSG MACHINE_NAME _UxGT(" Ready.")
+#endif
+#ifndef MSG_BACK
+ #define MSG_BACK _UxGT("Back")
+#endif
+#ifndef MSG_SD_INSERTED
+ #define MSG_SD_INSERTED _UxGT("Card inserted")
+#endif
+#ifndef MSG_SD_REMOVED
+ #define MSG_SD_REMOVED _UxGT("Card removed")
+#endif
+#ifndef MSG_LCD_ENDSTOPS
+ #define MSG_LCD_ENDSTOPS _UxGT("Endstops") // Max length 8 characters
+#endif
+#ifndef MSG_MAIN
+ #define MSG_MAIN _UxGT("Main")
+#endif
+#ifndef MSG_AUTOSTART
+ #define MSG_AUTOSTART _UxGT("Autostart")
+#endif
+#ifndef MSG_DISABLE_STEPPERS
+ #define MSG_DISABLE_STEPPERS _UxGT("Disable steppers")
+#endif
+#ifndef MSG_DEBUG_MENU
+ #define MSG_DEBUG_MENU _UxGT("Debug Menu")
+#endif
+#ifndef MSG_PROGRESS_BAR_TEST
+ #define MSG_PROGRESS_BAR_TEST _UxGT("Progress Bar Test")
+#endif
+#ifndef MSG_AUTO_HOME
+ #define MSG_AUTO_HOME _UxGT("Auto home")
+#endif
+#ifndef MSG_AUTO_HOME_X
+ #define MSG_AUTO_HOME_X _UxGT("Home X")
+#endif
+#ifndef MSG_AUTO_HOME_Y
+ #define MSG_AUTO_HOME_Y _UxGT("Home Y")
+#endif
+#ifndef MSG_AUTO_HOME_Z
+ #define MSG_AUTO_HOME_Z _UxGT("Home Z")
+#endif
+#ifndef MSG_LEVEL_BED_HOMING
+ #define MSG_LEVEL_BED_HOMING _UxGT("Homing XYZ")
+#endif
+#ifndef MSG_LEVEL_BED_WAITING
+ #define MSG_LEVEL_BED_WAITING _UxGT("Click to Begin")
+#endif
+#ifndef MSG_LEVEL_BED_NEXT_POINT
+ #define MSG_LEVEL_BED_NEXT_POINT _UxGT("Next Point")
+#endif
+#ifndef MSG_LEVEL_BED_DONE
+ #define MSG_LEVEL_BED_DONE _UxGT("Leveling Done!")
+#endif
+#ifndef MSG_Z_FADE_HEIGHT
+ #define MSG_Z_FADE_HEIGHT _UxGT("Fade Height")
+#endif
+#ifndef MSG_SET_HOME_OFFSETS
+ #define MSG_SET_HOME_OFFSETS _UxGT("Set home offsets")
+#endif
+#ifndef MSG_HOME_OFFSETS_APPLIED
+ #define MSG_HOME_OFFSETS_APPLIED _UxGT("Offsets applied")
+#endif
+#ifndef MSG_SET_ORIGIN
+ #define MSG_SET_ORIGIN _UxGT("Set origin")
+#endif
+#ifndef MSG_PREHEAT_1
+ #define MSG_PREHEAT_1 _UxGT("Preheat PLA")
+#endif
+#ifndef MSG_PREHEAT_1_N
+ #define MSG_PREHEAT_1_N MSG_PREHEAT_1 _UxGT(" ")
+#endif
+#ifndef MSG_PREHEAT_1_ALL
+ #define MSG_PREHEAT_1_ALL MSG_PREHEAT_1 _UxGT(" All")
+#endif
+#ifndef MSG_PREHEAT_1_END
+ #define MSG_PREHEAT_1_END MSG_PREHEAT_1 _UxGT(" End")
+#endif
+#ifndef MSG_PREHEAT_1_BEDONLY
+ #define MSG_PREHEAT_1_BEDONLY MSG_PREHEAT_1 _UxGT(" Bed")
+#endif
+#ifndef MSG_PREHEAT_1_SETTINGS
+ #define MSG_PREHEAT_1_SETTINGS MSG_PREHEAT_1 _UxGT(" conf")
+#endif
+#ifndef MSG_PREHEAT_2
+ #define MSG_PREHEAT_2 _UxGT("Preheat ABS")
+#endif
+#ifndef MSG_PREHEAT_2_N
+ #define MSG_PREHEAT_2_N MSG_PREHEAT_2 _UxGT(" ")
+#endif
+#ifndef MSG_PREHEAT_2_ALL
+ #define MSG_PREHEAT_2_ALL MSG_PREHEAT_2 _UxGT(" All")
+#endif
+#ifndef MSG_PREHEAT_2_END
+ #define MSG_PREHEAT_2_END MSG_PREHEAT_2 _UxGT(" End")
+#endif
+#ifndef MSG_PREHEAT_2_BEDONLY
+ #define MSG_PREHEAT_2_BEDONLY MSG_PREHEAT_2 _UxGT(" Bed")
+#endif
+#ifndef MSG_PREHEAT_2_SETTINGS
+ #define MSG_PREHEAT_2_SETTINGS MSG_PREHEAT_2 _UxGT(" conf")
+#endif
+#ifndef MSG_COOLDOWN
+ #define MSG_COOLDOWN _UxGT("Cooldown")
+#endif
+#ifndef MSG_SWITCH_PS_ON
+ #define MSG_SWITCH_PS_ON _UxGT("Switch power on")
+#endif
+#ifndef MSG_SWITCH_PS_OFF
+ #define MSG_SWITCH_PS_OFF _UxGT("Switch power off")
+#endif
+#ifndef MSG_EXTRUDE
+ #define MSG_EXTRUDE _UxGT("Extrude")
+#endif
+#ifndef MSG_RETRACT
+ #define MSG_RETRACT _UxGT("Retract")
+#endif
+#ifndef MSG_MOVE_AXIS
+ #define MSG_MOVE_AXIS _UxGT("Move axis")
+#endif
+#ifndef MSG_BED_LEVELING
+ #define MSG_BED_LEVELING _UxGT("Bed Leveling")
+#endif
+#ifndef MSG_LEVEL_BED
+ #define MSG_LEVEL_BED _UxGT("Level bed")
+#endif
+#ifndef MSG_LEVEL_CORNERS
+ #define MSG_LEVEL_CORNERS _UxGT("Level corners")
+#endif
+#ifndef MSG_NEXT_CORNER
+ #define MSG_NEXT_CORNER _UxGT("Next corner")
+#endif
+#ifndef MSG_EDITING_STOPPED
+ #define MSG_EDITING_STOPPED _UxGT("Mesh Editing Stopped")
+#endif
+#ifndef MSG_USER_MENU
+ #define MSG_USER_MENU _UxGT("Custom Commands")
+#endif
+#ifndef MSG_UBL_DOING_G29
+ #define MSG_UBL_DOING_G29 _UxGT("Doing G29")
+#endif
+#ifndef MSG_UBL_UNHOMED
+ #define MSG_UBL_UNHOMED _UxGT("Home XYZ first")
+#endif
+#ifndef MSG_UBL_TOOLS
+ #define MSG_UBL_TOOLS _UxGT("UBL Tools")
+#endif
+#ifndef MSG_UBL_LEVEL_BED
+ #define MSG_UBL_LEVEL_BED _UxGT("Unified Bed Leveling")
+#endif
+#ifndef MSG_UBL_MANUAL_MESH
+ #define MSG_UBL_MANUAL_MESH _UxGT("Manually Build Mesh")
+#endif
+#ifndef MSG_UBL_BC_INSERT
+ #define MSG_UBL_BC_INSERT _UxGT("Place shim & measure")
+#endif
+#ifndef MSG_UBL_BC_INSERT2
+ #define MSG_UBL_BC_INSERT2 _UxGT("Measure")
+#endif
+#ifndef MSG_UBL_BC_REMOVE
+ #define MSG_UBL_BC_REMOVE _UxGT("Remove & measure bed")
+#endif
+#ifndef MSG_UBL_MOVING_TO_NEXT
+ #define MSG_UBL_MOVING_TO_NEXT _UxGT("Moving to next")
+#endif
+#ifndef MSG_UBL_ACTIVATE_MESH
+ #define MSG_UBL_ACTIVATE_MESH _UxGT("Activate UBL")
+#endif
+#ifndef MSG_UBL_DEACTIVATE_MESH
+ #define MSG_UBL_DEACTIVATE_MESH _UxGT("Deactivate UBL")
+#endif
+#ifndef MSG_UBL_SET_BED_TEMP
+ #define MSG_UBL_SET_BED_TEMP _UxGT("Bed Temp")
+#endif
+#ifndef MSG_UBL_CUSTOM_BED_TEMP
+ #define MSG_UBL_CUSTOM_BED_TEMP MSG_UBL_SET_BED_TEMP
+#endif
+#ifndef MSG_UBL_SET_HOTEND_TEMP
+ #define MSG_UBL_SET_HOTEND_TEMP _UxGT("Hotend Temp")
+#endif
+#ifndef MSG_UBL_CUSTOM_HOTEND_TEMP
+ #define MSG_UBL_CUSTOM_HOTEND_TEMP MSG_UBL_SET_HOTEND_TEMP
+#endif
+#ifndef MSG_UBL_MESH_EDIT
+ #define MSG_UBL_MESH_EDIT _UxGT("Mesh Edit")
+#endif
+#ifndef MSG_UBL_EDIT_CUSTOM_MESH
+ #define MSG_UBL_EDIT_CUSTOM_MESH _UxGT("Edit Custom Mesh")
+#endif
+#ifndef MSG_UBL_FINE_TUNE_MESH
+ #define MSG_UBL_FINE_TUNE_MESH _UxGT("Fine Tuning Mesh")
+#endif
+#ifndef MSG_UBL_DONE_EDITING_MESH
+ #define MSG_UBL_DONE_EDITING_MESH _UxGT("Done Editing Mesh")
+#endif
+#ifndef MSG_UBL_BUILD_CUSTOM_MESH
+ #define MSG_UBL_BUILD_CUSTOM_MESH _UxGT("Build Custom Mesh")
+#endif
+#ifndef MSG_UBL_BUILD_MESH_MENU
+ #define MSG_UBL_BUILD_MESH_MENU _UxGT("Build Mesh")
+#endif
+#ifndef MSG_UBL_BUILD_PLA_MESH
+ #define MSG_UBL_BUILD_PLA_MESH _UxGT("Build PLA Mesh")
+#endif
+#ifndef MSG_UBL_BUILD_ABS_MESH
+ #define MSG_UBL_BUILD_ABS_MESH _UxGT("Build ABS Mesh")
+#endif
+#ifndef MSG_UBL_BUILD_COLD_MESH
+ #define MSG_UBL_BUILD_COLD_MESH _UxGT("Build Cold Mesh")
+#endif
+#ifndef MSG_UBL_MESH_HEIGHT_ADJUST
+ #define MSG_UBL_MESH_HEIGHT_ADJUST _UxGT("Adjust Mesh Height")
+#endif
+#ifndef MSG_UBL_MESH_HEIGHT_AMOUNT
+ #define MSG_UBL_MESH_HEIGHT_AMOUNT _UxGT("Height Amount")
+#endif
+#ifndef MSG_UBL_VALIDATE_MESH_MENU
+ #define MSG_UBL_VALIDATE_MESH_MENU _UxGT("Validate Mesh")
+#endif
+#ifndef MSG_UBL_VALIDATE_PLA_MESH
+ #define MSG_UBL_VALIDATE_PLA_MESH _UxGT("Validate PLA Mesh")
+#endif
+#ifndef MSG_UBL_VALIDATE_ABS_MESH
+ #define MSG_UBL_VALIDATE_ABS_MESH _UxGT("Validate ABS Mesh")
+#endif
+#ifndef MSG_UBL_VALIDATE_CUSTOM_MESH
+ #define MSG_UBL_VALIDATE_CUSTOM_MESH _UxGT("Validate Custom Mesh")
+#endif
+#ifndef MSG_UBL_CONTINUE_MESH
+ #define MSG_UBL_CONTINUE_MESH _UxGT("Continue Bed Mesh")
+#endif
+#ifndef MSG_UBL_MESH_LEVELING
+ #define MSG_UBL_MESH_LEVELING _UxGT("Mesh Leveling")
+#endif
+#ifndef MSG_UBL_3POINT_MESH_LEVELING
+ #define MSG_UBL_3POINT_MESH_LEVELING _UxGT("3-Point Leveling")
+#endif
+#ifndef MSG_UBL_GRID_MESH_LEVELING
+ #define MSG_UBL_GRID_MESH_LEVELING _UxGT("Grid Mesh Leveling")
+#endif
+#ifndef MSG_UBL_MESH_LEVEL
+ #define MSG_UBL_MESH_LEVEL _UxGT("Level Mesh")
+#endif
+#ifndef MSG_UBL_SIDE_POINTS
+ #define MSG_UBL_SIDE_POINTS _UxGT("Side Points")
+#endif
+#ifndef MSG_UBL_MAP_TYPE
+ #define MSG_UBL_MAP_TYPE _UxGT("Map Type")
+#endif
+#ifndef MSG_UBL_OUTPUT_MAP
+ #define MSG_UBL_OUTPUT_MAP _UxGT("Output Mesh Map")
+#endif
+#ifndef MSG_UBL_OUTPUT_MAP_HOST
+ #define MSG_UBL_OUTPUT_MAP_HOST _UxGT("Output for Host")
+#endif
+#ifndef MSG_UBL_OUTPUT_MAP_CSV
+ #define MSG_UBL_OUTPUT_MAP_CSV _UxGT("Output for CSV")
+#endif
+#ifndef MSG_UBL_OUTPUT_MAP_BACKUP
+ #define MSG_UBL_OUTPUT_MAP_BACKUP _UxGT("Off Printer Backup")
+#endif
+#ifndef MSG_UBL_INFO_UBL
+ #define MSG_UBL_INFO_UBL _UxGT("Output UBL Info")
+#endif
+#ifndef MSG_UBL_EDIT_MESH_MENU
+ #define MSG_UBL_EDIT_MESH_MENU _UxGT("Edit Mesh")
+#endif
+#ifndef MSG_UBL_FILLIN_AMOUNT
+ #define MSG_UBL_FILLIN_AMOUNT _UxGT("Fill-in Amount")
+#endif
+#ifndef MSG_UBL_MANUAL_FILLIN
+ #define MSG_UBL_MANUAL_FILLIN _UxGT("Manual Fill-in")
+#endif
+#ifndef MSG_UBL_SMART_FILLIN
+ #define MSG_UBL_SMART_FILLIN _UxGT("Smart Fill-in")
+#endif
+#ifndef MSG_UBL_FILLIN_MESH
+ #define MSG_UBL_FILLIN_MESH _UxGT("Fill-in Mesh")
+#endif
+#ifndef MSG_UBL_INVALIDATE_ALL
+ #define MSG_UBL_INVALIDATE_ALL _UxGT("Invalidate All")
+#endif
+#ifndef MSG_UBL_INVALIDATE_CLOSEST
+ #define MSG_UBL_INVALIDATE_CLOSEST _UxGT("Invalidate Closest")
+#endif
+#ifndef MSG_UBL_FINE_TUNE_ALL
+ #define MSG_UBL_FINE_TUNE_ALL _UxGT("Fine Tune All")
+#endif
+#ifndef MSG_UBL_FINE_TUNE_CLOSEST
+ #define MSG_UBL_FINE_TUNE_CLOSEST _UxGT("Fine Tune Closest")
+#endif
+#ifndef MSG_UBL_STORAGE_MESH_MENU
+ #define MSG_UBL_STORAGE_MESH_MENU _UxGT("Mesh Storage")
+#endif
+#ifndef MSG_UBL_STORAGE_SLOT
+ #define MSG_UBL_STORAGE_SLOT _UxGT("Memory Slot")
+#endif
+#ifndef MSG_UBL_LOAD_MESH
+ #define MSG_UBL_LOAD_MESH _UxGT("Load Bed Mesh")
+#endif
+#ifndef MSG_UBL_SAVE_MESH
+ #define MSG_UBL_SAVE_MESH _UxGT("Save Bed Mesh")
+#endif
+#ifndef MSG_MESH_LOADED
+ #define MSG_MESH_LOADED _UxGT("Mesh %i loaded")
+#endif
+#ifndef MSG_MESH_SAVED
+ #define MSG_MESH_SAVED _UxGT("Mesh %i saved")
+#endif
+#ifndef MSG_NO_STORAGE
+ #define MSG_NO_STORAGE _UxGT("No storage")
+#endif
+#ifndef MSG_UBL_SAVE_ERROR
+ #define MSG_UBL_SAVE_ERROR _UxGT("Err: UBL Save")
+#endif
+#ifndef MSG_UBL_RESTORE_ERROR
+ #define MSG_UBL_RESTORE_ERROR _UxGT("Err: UBL Restore")
+#endif
+#ifndef MSG_UBL_Z_OFFSET_STOPPED
+ #define MSG_UBL_Z_OFFSET_STOPPED _UxGT("Z-Offset Stopped")
+#endif
+#ifndef MSG_UBL_STEP_BY_STEP_MENU
+ #define MSG_UBL_STEP_BY_STEP_MENU _UxGT("Step-By-Step UBL")
+#endif
+
+#ifndef MSG_LED_CONTROL
+ #define MSG_LED_CONTROL _UxGT("LED Control")
+#endif
+#ifndef MSG_LEDS_ON
+ #define MSG_LEDS_ON _UxGT("Lights On")
+#endif
+#ifndef MSG_LEDS_OFF
+ #define MSG_LEDS_OFF _UxGT("Lights Off")
+#endif
+#ifndef MSG_LED_PRESETS
+ #define MSG_LED_PRESETS _UxGT("Light Presets")
+#endif
+#ifndef MSG_SET_LEDS_RED
+ #define MSG_SET_LEDS_RED _UxGT("Red")
+#endif
+#ifndef MSG_SET_LEDS_ORANGE
+ #define MSG_SET_LEDS_ORANGE _UxGT("Orange")
+#endif
+#ifndef MSG_SET_LEDS_YELLOW
+ #define MSG_SET_LEDS_YELLOW _UxGT("Yellow")
+#endif
+#ifndef MSG_SET_LEDS_GREEN
+ #define MSG_SET_LEDS_GREEN _UxGT("Green")
+#endif
+#ifndef MSG_SET_LEDS_BLUE
+ #define MSG_SET_LEDS_BLUE _UxGT("Blue")
+#endif
+#ifndef MSG_SET_LEDS_INDIGO
+ #define MSG_SET_LEDS_INDIGO _UxGT("Indigo")
+#endif
+#ifndef MSG_SET_LEDS_VIOLET
+ #define MSG_SET_LEDS_VIOLET _UxGT("Violet")
+#endif
+#ifndef MSG_SET_LEDS_WHITE
+ #define MSG_SET_LEDS_WHITE _UxGT("White")
+#endif
+#ifndef MSG_SET_LEDS_DEFAULT
+ #define MSG_SET_LEDS_DEFAULT _UxGT("Default")
+#endif
+#ifndef MSG_CUSTOM_LEDS
+ #define MSG_CUSTOM_LEDS _UxGT("Custom Lights")
+#endif
+#ifndef MSG_INTENSITY_R
+ #define MSG_INTENSITY_R _UxGT("Red Intensity")
+#endif
+#ifndef MSG_INTENSITY_G
+ #define MSG_INTENSITY_G _UxGT("Green Intensity")
+#endif
+#ifndef MSG_INTENSITY_B
+ #define MSG_INTENSITY_B _UxGT("Blue Intensity")
+#endif
+#ifndef MSG_INTENSITY_W
+ #define MSG_INTENSITY_W _UxGT("White Intensity")
+#endif
+#ifndef MSG_LED_BRIGHTNESS
+ #define MSG_LED_BRIGHTNESS _UxGT("Brightness")
+#endif
+
+#ifndef MSG_MOVING
+ #define MSG_MOVING _UxGT("Moving...")
+#endif
+#ifndef MSG_FREE_XY
+ #define MSG_FREE_XY _UxGT("Free XY")
+#endif
+#ifndef MSG_MOVE_X
+ #define MSG_MOVE_X _UxGT("Move X")
+#endif
+#ifndef MSG_MOVE_Y
+ #define MSG_MOVE_Y _UxGT("Move Y")
+#endif
+#ifndef MSG_MOVE_Z
+ #define MSG_MOVE_Z _UxGT("Move Z")
+#endif
+#ifndef MSG_MOVE_E
+ #define MSG_MOVE_E _UxGT("Extruder")
+#endif
+#ifndef MSG_MOVE_01MM
+ #define MSG_MOVE_01MM _UxGT("Move 0.1mm")
+#endif
+#ifndef MSG_MOVE_1MM
+ #define MSG_MOVE_1MM _UxGT("Move 1mm")
+#endif
+#ifndef MSG_MOVE_10MM
+ #define MSG_MOVE_10MM _UxGT("Move 10mm")
+#endif
+#ifndef MSG_SPEED
+ #define MSG_SPEED _UxGT("Speed")
+#endif
+#ifndef MSG_BED_Z
+ #define MSG_BED_Z _UxGT("Bed Z")
+#endif
+#ifndef MSG_NOZZLE
+ #define MSG_NOZZLE _UxGT("Nozzle")
+#endif
+#ifndef MSG_BED
+ #define MSG_BED _UxGT("Bed")
+#endif
+#ifndef MSG_FAN_SPEED
+ #define MSG_FAN_SPEED _UxGT("Fan speed")
+#endif
+
+#ifndef MSG_LASER_ON
+ #define MSG_LASER_ON _UxGT("Laser On")
+#endif
+#ifndef MSG_LASER_OFF
+ #define MSG_LASER_OFF _UxGT("Laser Off")
+#endif
+
+#ifndef MSG_FAN_SPEED
+ #define MSG_FAN_SPEED _UxGT("Fan speed")
+#endif
+#ifndef MSG_EXTRA_FAN_SPEED
+ #define MSG_EXTRA_FAN_SPEED _UxGT("Extra fan speed")
+#endif
+#ifndef MSG_FLOW
+ #define MSG_FLOW _UxGT("Flow")
+#endif
+#ifndef MSG_CONTROL
+ #define MSG_CONTROL _UxGT("Control")
+#endif
+#ifndef MSG_MIN
+ #define MSG_MIN _UxGT(" ") LCD_STR_THERMOMETER _UxGT(" Min")
+#endif
+#ifndef MSG_MAX
+ #define MSG_MAX _UxGT(" ") LCD_STR_THERMOMETER _UxGT(" Max")
+#endif
+#ifndef MSG_FACTOR
+ #define MSG_FACTOR _UxGT(" ") LCD_STR_THERMOMETER _UxGT(" Fact")
+#endif
+#ifndef MSG_AUTOTEMP
+ #define MSG_AUTOTEMP _UxGT("Autotemp")
+#endif
+#ifndef MSG_ON
+ #define MSG_ON _UxGT("On ")
+#endif
+#ifndef MSG_OFF
+ #define MSG_OFF _UxGT("Off")
+#endif
+#ifndef MSG_PID_P
+ #define MSG_PID_P _UxGT("PID-P")
+#endif
+#ifndef MSG_PID_I
+ #define MSG_PID_I _UxGT("PID-I")
+#endif
+#ifndef MSG_PID_D
+ #define MSG_PID_D _UxGT("PID-D")
+#endif
+#ifndef MSG_PID_C
+ #define MSG_PID_C _UxGT("PID-C")
+#endif
+#ifndef MSG_SELECT
+ #define MSG_SELECT _UxGT("Select")
+#endif
+#ifndef MSG_ACC
+ #define MSG_ACC _UxGT("Accel")
+#endif
+#ifndef MSG_JERK
+ #define MSG_JERK _UxGT("Jerk")
+#endif
+#if IS_KINEMATIC
+ #ifndef MSG_VA_JERK
+ #define MSG_VA_JERK _UxGT("Va-jerk")
+ #endif
+ #ifndef MSG_VB_JERK
+ #define MSG_VB_JERK _UxGT("Vb-jerk")
+ #endif
+ #ifndef MSG_VC_JERK
+ #define MSG_VC_JERK _UxGT("Vc-jerk")
+ #endif
+#else
+ #ifndef MSG_VA_JERK
+ #define MSG_VA_JERK _UxGT("Vx-jerk")
+ #endif
+ #ifndef MSG_VB_JERK
+ #define MSG_VB_JERK _UxGT("Vy-jerk")
+ #endif
+ #ifndef MSG_VC_JERK
+ #define MSG_VC_JERK _UxGT("Vz-jerk")
+ #endif
+#endif
+#ifndef MSG_VE_JERK
+ #define MSG_VE_JERK _UxGT("Ve-jerk")
+#endif
+#ifndef MSG_VELOCITY
+ #define MSG_VELOCITY _UxGT("Velocity")
+#endif
+#ifndef MSG_VMAX
+ #define MSG_VMAX _UxGT("Vmax ")
+#endif
+#ifndef MSG_VMIN
+ #define MSG_VMIN _UxGT("Vmin")
+#endif
+#ifndef MSG_VTRAV_MIN
+ #define MSG_VTRAV_MIN _UxGT("VTrav min")
+#endif
+#ifndef MSG_ACCELERATION
+ #define MSG_ACCELERATION _UxGT("Acceleration")
+#endif
+#ifndef MSG_AMAX
+ #define MSG_AMAX _UxGT("Amax ")
+#endif
+#ifndef MSG_A_RETRACT
+ #define MSG_A_RETRACT _UxGT("A-retract")
+#endif
+#ifndef MSG_A_TRAVEL
+ #define MSG_A_TRAVEL _UxGT("A-travel")
+#endif
+#ifndef MSG_STEPS_PER_MM
+ #define MSG_STEPS_PER_MM _UxGT("Steps/mm")
+#endif
+#if IS_KINEMATIC
+ #ifndef MSG_ASTEPS
+ #define MSG_ASTEPS _UxGT("Asteps/mm")
+ #endif
+ #ifndef MSG_BSTEPS
+ #define MSG_BSTEPS _UxGT("Bsteps/mm")
+ #endif
+ #ifndef MSG_CSTEPS
+ #define MSG_CSTEPS _UxGT("Csteps/mm")
+ #endif
+#else
+ #ifndef MSG_ASTEPS
+ #define MSG_ASTEPS _UxGT("Xsteps/mm")
+ #endif
+ #ifndef MSG_BSTEPS
+ #define MSG_BSTEPS _UxGT("Ysteps/mm")
+ #endif
+ #ifndef MSG_CSTEPS
+ #define MSG_CSTEPS _UxGT("Zsteps/mm")
+ #endif
+#endif
+#ifndef MSG_ESTEPS
+ #define MSG_ESTEPS _UxGT("Esteps/mm")
+#endif
+#ifndef MSG_E1STEPS
+ #define MSG_E1STEPS _UxGT("E1steps/mm")
+#endif
+#ifndef MSG_E2STEPS
+ #define MSG_E2STEPS _UxGT("E2steps/mm")
+#endif
+#ifndef MSG_E3STEPS
+ #define MSG_E3STEPS _UxGT("E3steps/mm")
+#endif
+#ifndef MSG_E4STEPS
+ #define MSG_E4STEPS _UxGT("E4steps/mm")
+#endif
+#ifndef MSG_E5STEPS
+ #define MSG_E5STEPS _UxGT("E5steps/mm")
+#endif
+#ifndef MSG_TEMPERATURE
+ #define MSG_TEMPERATURE _UxGT("Temperature")
+#endif
+#ifndef MSG_MOTION
+ #define MSG_MOTION _UxGT("Motion")
+#endif
+#ifndef MSG_FILAMENT
+ #define MSG_FILAMENT _UxGT("Filament")
+#endif
+#ifndef MSG_VOLUMETRIC_ENABLED
+ #define MSG_VOLUMETRIC_ENABLED _UxGT("E in mm3")
+#endif
+#ifndef MSG_FILAMENT_DIAM
+ #define MSG_FILAMENT_DIAM _UxGT("Fil. Dia.")
+#endif
+#ifndef MSG_FILAMENT_UNLOAD
+ #define MSG_FILAMENT_UNLOAD _UxGT("Unload mm")
+#endif
+#ifndef MSG_FILAMENT_LOAD
+ #define MSG_FILAMENT_LOAD _UxGT("Load mm")
+#endif
+#ifndef MSG_ADVANCE_K
+ #define MSG_ADVANCE_K _UxGT("Advance K")
+#endif
+#ifndef MSG_CONTRAST
+ #define MSG_CONTRAST _UxGT("LCD contrast")
+#endif
+#ifndef MSG_STORE_EEPROM
+ #define MSG_STORE_EEPROM _UxGT("Store settings")
+#endif
+#ifndef MSG_LOAD_EEPROM
+ #define MSG_LOAD_EEPROM _UxGT("Load settings")
+#endif
+#ifndef MSG_RESTORE_FAILSAFE
+ #define MSG_RESTORE_FAILSAFE _UxGT("Restore failsafe")
+#endif
+#ifndef MSG_INIT_EEPROM
+ #define MSG_INIT_EEPROM _UxGT("Initialize EEPROM")
+#endif
+#ifndef MSG_REFRESH
+ #define MSG_REFRESH _UxGT("Refresh")
+#endif
+#ifndef MSG_WATCH
+ #define MSG_WATCH _UxGT("Info screen")
+#endif
+#ifndef MSG_PREPARE
+ #define MSG_PREPARE _UxGT("Prepare")
+#endif
+#ifndef MSG_TUNE
+ #define MSG_TUNE _UxGT("Tune")
+#endif
+#ifndef MSG_PAUSE_PRINT
+ #define MSG_PAUSE_PRINT _UxGT("Pause print")
+#endif
+#ifndef MSG_RESUME_PRINT
+ #define MSG_RESUME_PRINT _UxGT("Resume print")
+#endif
+#ifndef MSG_STOP_PRINT
+ #define MSG_STOP_PRINT _UxGT("Stop print")
+#endif
+#ifndef MSG_CARD_MENU
+ #define MSG_CARD_MENU _UxGT("Print from SD")
+#endif
+#ifndef MSG_NO_CARD
+ #define MSG_NO_CARD _UxGT("No SD card")
+#endif
+#ifndef MSG_DWELL
+ #define MSG_DWELL _UxGT("Sleep...")
+#endif
+#ifndef MSG_USERWAIT
+ #define MSG_USERWAIT _UxGT("Click to resume...")
+#endif
+#ifndef MSG_PRINT_PAUSED
+ #define MSG_PRINT_PAUSED _UxGT("Print paused")
+#endif
+#ifndef MSG_RESUMING
+ #define MSG_RESUMING _UxGT("Resuming print")
+#endif
+#ifndef MSG_PRINT_ABORTED
+ #define MSG_PRINT_ABORTED _UxGT("Print aborted")
+#endif
+#ifndef MSG_NO_MOVE
+ #define MSG_NO_MOVE _UxGT("No move.")
+#endif
+#ifndef MSG_KILLED
+ #define MSG_KILLED _UxGT("KILLED. ")
+#endif
+#ifndef MSG_STOPPED
+ #define MSG_STOPPED _UxGT("STOPPED. ")
+#endif
+#ifndef MSG_CONTROL_RETRACT
+ #define MSG_CONTROL_RETRACT _UxGT("Retract mm")
+#endif
+#ifndef MSG_CONTROL_RETRACT_SWAP
+ #define MSG_CONTROL_RETRACT_SWAP _UxGT("Swap Re.mm")
+#endif
+#ifndef MSG_CONTROL_RETRACTF
+ #define MSG_CONTROL_RETRACTF _UxGT("Retract V")
+#endif
+#ifndef MSG_CONTROL_RETRACT_ZLIFT
+ #define MSG_CONTROL_RETRACT_ZLIFT _UxGT("Hop mm")
+#endif
+#ifndef MSG_CONTROL_RETRACT_RECOVER
+ #define MSG_CONTROL_RETRACT_RECOVER _UxGT("UnRet mm")
+#endif
+#ifndef MSG_CONTROL_RETRACT_RECOVER_SWAP
+ #define MSG_CONTROL_RETRACT_RECOVER_SWAP _UxGT("S UnRet mm")
+#endif
+#ifndef MSG_CONTROL_RETRACT_RECOVERF
+ #define MSG_CONTROL_RETRACT_RECOVERF _UxGT("UnRet V")
+#endif
+#ifndef MSG_CONTROL_RETRACT_RECOVER_SWAPF
+ #define MSG_CONTROL_RETRACT_RECOVER_SWAPF _UxGT("S UnRet V")
+#endif
+#ifndef MSG_AUTORETRACT
+ #define MSG_AUTORETRACT _UxGT("AutoRetr.")
+#endif
+#ifndef MSG_FILAMENTCHANGE
+ #define MSG_FILAMENTCHANGE _UxGT("Change filament")
+#endif
+#ifndef MSG_FILAMENTLOAD
+ #define MSG_FILAMENTLOAD _UxGT("Load filament")
+#endif
+#ifndef MSG_FILAMENTUNLOAD
+ #define MSG_FILAMENTUNLOAD _UxGT("Unload filament")
+#endif
+#ifndef MSG_FILAMENTUNLOAD_ALL
+ #define MSG_FILAMENTUNLOAD_ALL _UxGT("Unload All")
+#endif
+#ifndef MSG_INIT_SDCARD
+ #define MSG_INIT_SDCARD _UxGT("Init. SD card")
+#endif
+#ifndef MSG_CNG_SDCARD
+ #define MSG_CNG_SDCARD _UxGT("Change SD card")
+#endif
+#ifndef MSG_ZPROBE_OUT
+ #define MSG_ZPROBE_OUT _UxGT("Z probe out. bed")
+#endif
+#ifndef MSG_SKEW_FACTOR
+ #define MSG_SKEW_FACTOR _UxGT("Skew Factor")
+#endif
+#ifndef MSG_BLTOUCH
+ #define MSG_BLTOUCH _UxGT("BLTouch")
+#endif
+#ifndef MSG_BLTOUCH_SELFTEST
+ #define MSG_BLTOUCH_SELFTEST _UxGT("BLTouch Self-Test")
+#endif
+#ifndef MSG_BLTOUCH_RESET
+ #define MSG_BLTOUCH_RESET _UxGT("Reset BLTouch")
+#endif
+#ifndef MSG_BLTOUCH_DEPLOY
+ #define MSG_BLTOUCH_DEPLOY _UxGT("Deploy BLTouch")
+#endif
+#ifndef MSG_BLTOUCH_STOW
+ #define MSG_BLTOUCH_STOW _UxGT("Stow BLTouch")
+#endif
+#ifndef MSG_HOME
+ #define MSG_HOME _UxGT("Home") // Used as MSG_HOME " " MSG_X MSG_Y MSG_Z " " MSG_FIRST
+#endif
+#ifndef MSG_FIRST
+ #define MSG_FIRST _UxGT("first")
+#endif
+#ifndef MSG_ZPROBE_ZOFFSET
+ #define MSG_ZPROBE_ZOFFSET _UxGT("Z Offset")
+#endif
+#ifndef MSG_BABYSTEP_X
+ #define MSG_BABYSTEP_X _UxGT("Babystep X")
+#endif
+#ifndef MSG_BABYSTEP_Y
+ #define MSG_BABYSTEP_Y _UxGT("Babystep Y")
+#endif
+#ifndef MSG_BABYSTEP_Z
+ #define MSG_BABYSTEP_Z _UxGT("Babystep Z")
+#endif
+#ifndef MSG_ENDSTOP_ABORT
+ #define MSG_ENDSTOP_ABORT _UxGT("Endstop abort")
+#endif
+#ifndef MSG_HEATING_FAILED_LCD
+ #define MSG_HEATING_FAILED_LCD _UxGT("Heating failed")
+#endif
+#ifndef MSG_ERR_REDUNDANT_TEMP
+ #define MSG_ERR_REDUNDANT_TEMP _UxGT("Err: REDUNDANT TEMP")
+#endif
+#ifndef MSG_THERMAL_RUNAWAY
+ #define MSG_THERMAL_RUNAWAY _UxGT("THERMAL RUNAWAY")
+#endif
+#ifndef MSG_THERMAL_RUNAWAY_BED
+ #define MSG_THERMAL_RUNAWAY_BED _UxGT("BED THERMAL RUNAWAY")
+#endif
+#ifndef MSG_ERR_MAXTEMP
+ #define MSG_ERR_MAXTEMP _UxGT("Err: MAXTEMP")
+#endif
+#ifndef MSG_ERR_MINTEMP
+ #define MSG_ERR_MINTEMP _UxGT("Err: MINTEMP")
+#endif
+#ifndef MSG_ERR_MAXTEMP_BED
+ #define MSG_ERR_MAXTEMP_BED _UxGT("Err: MAXTEMP BED")
+#endif
+#ifndef MSG_ERR_MINTEMP_BED
+ #define MSG_ERR_MINTEMP_BED _UxGT("Err: MINTEMP BED")
+#endif
+#ifndef MSG_ERR_Z_HOMING
+ #define MSG_ERR_Z_HOMING MSG_HOME _UxGT(" ") MSG_X MSG_Y _UxGT(" ") MSG_FIRST
+#endif
+#ifndef MSG_HALTED
+ #define MSG_HALTED _UxGT("PRINTER HALTED")
+#endif
+#ifndef MSG_PLEASE_RESET
+ #define MSG_PLEASE_RESET _UxGT("Please reset")
+#endif
+#ifndef MSG_SHORT_DAY
+ #define MSG_SHORT_DAY _UxGT("d") // One character only
+#endif
+#ifndef MSG_SHORT_HOUR
+ #define MSG_SHORT_HOUR _UxGT("h") // One character only
+#endif
+#ifndef MSG_SHORT_MINUTE
+ #define MSG_SHORT_MINUTE _UxGT("m") // One character only
+#endif
+#ifndef MSG_HEATING
+ #define MSG_HEATING _UxGT("Heating...")
+#endif
+#ifndef MSG_HEATING_COMPLETE
+ #define MSG_HEATING_COMPLETE _UxGT("Heating done.")
+#endif
+#ifndef MSG_BED_HEATING
+ #define MSG_BED_HEATING _UxGT("Bed Heating.")
+#endif
+#ifndef MSG_BED_DONE
+ #define MSG_BED_DONE _UxGT("Bed done.")
+#endif
+#ifndef MSG_DELTA_CALIBRATE
+ #define MSG_DELTA_CALIBRATE _UxGT("Delta Calibration")
+#endif
+#ifndef MSG_DELTA_CALIBRATE_X
+ #define MSG_DELTA_CALIBRATE_X _UxGT("Calibrate X")
+#endif
+#ifndef MSG_DELTA_CALIBRATE_Y
+ #define MSG_DELTA_CALIBRATE_Y _UxGT("Calibrate Y")
+#endif
+#ifndef MSG_DELTA_CALIBRATE_Z
+ #define MSG_DELTA_CALIBRATE_Z _UxGT("Calibrate Z")
+#endif
+#ifndef MSG_DELTA_CALIBRATE_CENTER
+ #define MSG_DELTA_CALIBRATE_CENTER _UxGT("Calibrate Center")
+#endif
+#ifndef MSG_DELTA_SETTINGS
+ #define MSG_DELTA_SETTINGS _UxGT("Delta Settings")
+#endif
+#ifndef MSG_DELTA_AUTO_CALIBRATE
+ #define MSG_DELTA_AUTO_CALIBRATE _UxGT("Auto Calibration")
+#endif
+#ifndef MSG_DELTA_HEIGHT_CALIBRATE
+ #define MSG_DELTA_HEIGHT_CALIBRATE _UxGT("Set Delta Height")
+#endif
+#ifndef MSG_DELTA_DIAG_ROD
+ #define MSG_DELTA_DIAG_ROD _UxGT("Diag Rod")
+#endif
+#ifndef MSG_DELTA_HEIGHT
+ #define MSG_DELTA_HEIGHT _UxGT("Height")
+#endif
+#ifndef MSG_DELTA_RADIUS
+ #define MSG_DELTA_RADIUS _UxGT("Radius")
+#endif
+#ifndef MSG_INFO_MENU
+ #define MSG_INFO_MENU _UxGT("About Printer")
+#endif
+#ifndef MSG_INFO_PRINTER_MENU
+ #define MSG_INFO_PRINTER_MENU _UxGT("Printer Info")
+#endif
+#ifndef MSG_3POINT_LEVELING
+ #define MSG_3POINT_LEVELING _UxGT("3-Point Leveling")
+#endif
+#ifndef MSG_LINEAR_LEVELING
+ #define MSG_LINEAR_LEVELING _UxGT("Linear Leveling")
+#endif
+#ifndef MSG_BILINEAR_LEVELING
+ #define MSG_BILINEAR_LEVELING _UxGT("Bilinear Leveling")
+#endif
+#ifndef MSG_UBL_LEVELING
+ #define MSG_UBL_LEVELING _UxGT("Unified Bed Leveling")
+#endif
+#ifndef MSG_MESH_LEVELING
+ #define MSG_MESH_LEVELING _UxGT("Mesh Leveling")
+#endif
+#ifndef MSG_INFO_STATS_MENU
+ #define MSG_INFO_STATS_MENU _UxGT("Printer Stats")
+#endif
+#ifndef MSG_INFO_BOARD_MENU
+ #define MSG_INFO_BOARD_MENU _UxGT("Board Info")
+#endif
+#ifndef MSG_INFO_THERMISTOR_MENU
+ #define MSG_INFO_THERMISTOR_MENU _UxGT("Thermistors")
+#endif
+#ifndef MSG_INFO_EXTRUDERS
+ #define MSG_INFO_EXTRUDERS _UxGT("Extruders")
+#endif
+#ifndef MSG_INFO_BAUDRATE
+ #define MSG_INFO_BAUDRATE _UxGT("Baud")
+#endif
+#ifndef MSG_INFO_PROTOCOL
+ #define MSG_INFO_PROTOCOL _UxGT("Protocol")
+#endif
+#ifndef MSG_CASE_LIGHT
+ #define MSG_CASE_LIGHT _UxGT("Case light")
+#endif
+#ifndef MSG_CASE_LIGHT_BRIGHTNESS
+ #define MSG_CASE_LIGHT_BRIGHTNESS _UxGT("Light BRIGHTNESS")
+#endif
+#if LCD_WIDTH >= 20
+ #ifndef MSG_INFO_PRINT_COUNT
+ #define MSG_INFO_PRINT_COUNT _UxGT("Print Count")
+ #endif
+ #ifndef MSG_INFO_COMPLETED_PRINTS
+ #define MSG_INFO_COMPLETED_PRINTS _UxGT("Completed")
+ #endif
+ #ifndef MSG_INFO_PRINT_TIME
+ #define MSG_INFO_PRINT_TIME _UxGT("Total print time")
+ #endif
+ #ifndef MSG_INFO_PRINT_LONGEST
+ #define MSG_INFO_PRINT_LONGEST _UxGT("Longest job time")
+ #endif
+ #ifndef MSG_INFO_PRINT_FILAMENT
+ #define MSG_INFO_PRINT_FILAMENT _UxGT("Extruded total")
+ #endif
+#else
+ #ifndef MSG_INFO_PRINT_COUNT
+ #define MSG_INFO_PRINT_COUNT _UxGT("Prints")
+ #endif
+ #ifndef MSG_INFO_COMPLETED_PRINTS
+ #define MSG_INFO_COMPLETED_PRINTS _UxGT("Completed")
+ #endif
+ #ifndef MSG_INFO_PRINT_TIME
+ #define MSG_INFO_PRINT_TIME _UxGT("Total")
+ #endif
+ #ifndef MSG_INFO_PRINT_LONGEST
+ #define MSG_INFO_PRINT_LONGEST _UxGT("Longest")
+ #endif
+ #ifndef MSG_INFO_PRINT_FILAMENT
+ #define MSG_INFO_PRINT_FILAMENT _UxGT("Extruded")
+ #endif
+#endif
+#ifndef MSG_INFO_MIN_TEMP
+ #define MSG_INFO_MIN_TEMP _UxGT("Min Temp")
+#endif
+#ifndef MSG_INFO_MAX_TEMP
+ #define MSG_INFO_MAX_TEMP _UxGT("Max Temp")
+#endif
+#ifndef MSG_INFO_PSU
+ #define MSG_INFO_PSU _UxGT("PSU")
+#endif
+#ifndef MSG_DRIVE_STRENGTH
+ #define MSG_DRIVE_STRENGTH _UxGT("Drive Strength")
+#endif
+#ifndef MSG_DAC_PERCENT
+ #define MSG_DAC_PERCENT _UxGT("Driver %")
+#endif
+#ifndef MSG_DAC_EEPROM_WRITE
+ #define MSG_DAC_EEPROM_WRITE _UxGT("DAC EEPROM Write")
+#endif
+#ifndef MSG_FILAMENT_CHANGE_HEADER_PAUSE
+ #define MSG_FILAMENT_CHANGE_HEADER_PAUSE _UxGT("PRINT PAUSED")
+#endif
+#ifndef MSG_FILAMENT_CHANGE_HEADER_LOAD
+ #define MSG_FILAMENT_CHANGE_HEADER_LOAD _UxGT("LOAD FILAMENT")
+#endif
+#ifndef MSG_FILAMENT_CHANGE_HEADER_UNLOAD
+ #define MSG_FILAMENT_CHANGE_HEADER_UNLOAD _UxGT("UNLOAD FILAMENT")
+#endif
+#ifndef MSG_FILAMENT_CHANGE_OPTION_HEADER
+ #define MSG_FILAMENT_CHANGE_OPTION_HEADER _UxGT("RESUME OPTIONS:")
+#endif
+#ifndef MSG_FILAMENT_CHANGE_OPTION_PURGE
+ #define MSG_FILAMENT_CHANGE_OPTION_PURGE _UxGT("Purge more")
+#endif
+#ifndef MSG_FILAMENT_CHANGE_OPTION_RESUME
+ #define MSG_FILAMENT_CHANGE_OPTION_RESUME _UxGT("Continue")
+#endif
+#ifndef MSG_FILAMENT_CHANGE_NOZZLE
+ #define MSG_FILAMENT_CHANGE_NOZZLE _UxGT(" Nozzle: ")
+#endif
+#ifndef MSG_ERR_HOMING_FAILED
+ #define MSG_ERR_HOMING_FAILED _UxGT("Homing failed")
+#endif
+#ifndef MSG_ERR_PROBING_FAILED
+ #define MSG_ERR_PROBING_FAILED _UxGT("Probing failed")
+#endif
+#ifndef MSG_M600_TOO_COLD
+ #define MSG_M600_TOO_COLD _UxGT("M600: Too cold")
+#endif
+
+//
+// Filament Change screens show up to 3 lines on a 4-line display
+// ...or up to 2 lines on a 3-line display
+//
+#if LCD_HEIGHT >= 4
+ #ifndef MSG_FILAMENT_CHANGE_INIT_1
+ #define MSG_FILAMENT_CHANGE_INIT_1 _UxGT("Wait for start")
+ #define MSG_FILAMENT_CHANGE_INIT_2 _UxGT("of the filament")
+ #define MSG_FILAMENT_CHANGE_INIT_3 _UxGT("change")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_UNLOAD_1
+ #define MSG_FILAMENT_CHANGE_UNLOAD_1 _UxGT("Wait for")
+ #define MSG_FILAMENT_CHANGE_UNLOAD_2 _UxGT("filament unload")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_INSERT_1
+ #define MSG_FILAMENT_CHANGE_INSERT_1 _UxGT("Insert filament")
+ #define MSG_FILAMENT_CHANGE_INSERT_2 _UxGT("and press button")
+ #define MSG_FILAMENT_CHANGE_INSERT_3 _UxGT("to continue...")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_HEAT_1
+ #define MSG_FILAMENT_CHANGE_HEAT_1 _UxGT("Press button to")
+ #define MSG_FILAMENT_CHANGE_HEAT_2 _UxGT("heat nozzle.")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_HEATING_1
+ #define MSG_FILAMENT_CHANGE_HEATING_1 _UxGT("Heating nozzle")
+ #define MSG_FILAMENT_CHANGE_HEATING_2 _UxGT("Please wait...")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_LOAD_1
+ #define MSG_FILAMENT_CHANGE_LOAD_1 _UxGT("Wait for")
+ #define MSG_FILAMENT_CHANGE_LOAD_2 _UxGT("filament load")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_PURGE_1
+ #define MSG_FILAMENT_CHANGE_PURGE_1 _UxGT("Wait for")
+ #define MSG_FILAMENT_CHANGE_PURGE_2 _UxGT("filament purge")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_RESUME_1
+ #define MSG_FILAMENT_CHANGE_RESUME_1 _UxGT("Wait for print")
+ #define MSG_FILAMENT_CHANGE_RESUME_2 _UxGT("to resume")
+ #endif
+#else // LCD_HEIGHT < 4
+ #ifndef MSG_FILAMENT_CHANGE_INIT_1
+ #define MSG_FILAMENT_CHANGE_INIT_1 _UxGT("Please wait...")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_UNLOAD_1
+ #define MSG_FILAMENT_CHANGE_UNLOAD_1 _UxGT("Ejecting...")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_INSERT_1
+ #define MSG_FILAMENT_CHANGE_INSERT_1 _UxGT("Insert and Click")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_HEATING_1
+ #define MSG_FILAMENT_CHANGE_HEATING_1 _UxGT("Heating...")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_LOAD_1
+ #define MSG_FILAMENT_CHANGE_LOAD_1 _UxGT("Loading...")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_PURGE_1
+ #define MSG_FILAMENT_CHANGE_PURGE_1 _UxGT("Purging...")
+ #endif
+ #ifndef MSG_FILAMENT_CHANGE_RESUME_1
+ #define MSG_FILAMENT_CHANGE_RESUME_1 _UxGT("Resuming...")
+ #endif
+#endif // LCD_HEIGHT < 4
+
+#endif // LANGUAGE_EN_H
diff --git a/pins_RUMBA.h b/pins_RUMBA.h
new file mode 100644
index 0000000000..b0e8108d9a
--- /dev/null
+++ b/pins_RUMBA.h
@@ -0,0 +1,324 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/**
+ * RUMBA pin assignments
+ */
+
+#ifndef __AVR_ATmega2560__
+ #error "Oops! Make sure you have 'Arduino Mega' selected from the 'Tools -> Boards' menu."
+#endif
+
+#if E_STEPPERS > 3 || HOTENDS > 3
+ #error "RUMBA supports up to 3 hotends / E-steppers. Comment this line to keep going."
+#endif
+
+#define DEFAULT_MACHINE_NAME "Rumba"
+#define BOARD_NAME "Rumba"
+//#endif
+
+#define LARGE_FLASH true
+
+//
+// Servos
+//
+#ifdef IS_RAMPS_13
+ #define SERVO0_PIN 7 // RAMPS_13 // Will conflict with BTN_EN2 on LCD_I2C_VIKI
+#else
+ #define SERVO0_PIN 11
+#endif
+#define SERVO1_PIN 6
+#define SERVO2_PIN 5
+#define SERVO3_PIN -1
+
+//
+// Limit Switches
+//
+#define X_MIN_PIN 3
+#ifndef X_MAX_PIN
+ #define X_MAX_PIN 2
+#endif
+#define Y_MIN_PIN 14
+#define Y_MAX_PIN 15
+#define Z_MIN_PIN 18
+#define Z_MAX_PIN 19
+
+//
+// Z Probe (when not Z_MIN_PIN)
+//
+#ifndef Z_MIN_PROBE_PIN
+ #define Z_MIN_PROBE_PIN 32
+#endif
+
+#define SLED_PIN -1
+
+//
+// Steppers
+//
+#define X_STEP_PIN 54
+#define X_DIR_PIN 55
+#define X_ENABLE_PIN 38
+#define X_CS_PIN 53
+
+#define Y_STEP_PIN 60
+#define Y_DIR_PIN 61
+#define Y_ENABLE_PIN 56
+#define Y_CS_PIN 49
+
+#define Z_STEP_PIN 46
+#define Z_DIR_PIN 48
+#define Z_ENABLE_PIN 62
+#define Z_CS_PIN 40
+
+#define E0_STEP_PIN 26
+#define E0_DIR_PIN 28
+#define E0_ENABLE_PIN 24
+#define E0_CS_PIN 42
+
+#define E1_STEP_PIN 36
+#define E1_DIR_PIN 34
+#define E1_ENABLE_PIN 30
+#define E1_CS_PIN 44
+
+#define E2_STEP_PIN 42
+#define E2_DIR_PIN 43
+#define E2_ENABLE_PIN 44
+
+//
+// Temperature Sensors
+//
+#define TEMP_0_PIN 13 // Analog Input
+#define TEMP_1_PIN 15 // Analog Input
+#define TEMP_BED_PIN 14 // Analog Input
+
+// SPI for Max6675 or Max31855 Thermocouple
+#if DISABLED(SDSUPPORT)
+ #define MAX6675_SS 66 // Do not use pin 53 if there is even the remote possibility of using Display/SD card
+#else
+ #define MAX6675_SS 66 // Do not use pin 49 as this is tied to the switch inside the SD card socket to detect if there is an SD card present
+#endif
+
+//
+// Augmentation for auto-assigning RAMPS plugs
+//
+#if DISABLED(IS_RAMPS_EEB) && DISABLED(IS_RAMPS_EEF) && DISABLED(IS_RAMPS_EFB) && DISABLED(IS_RAMPS_EFF) && DISABLED(IS_RAMPS_SF) && !PIN_EXISTS(MOSFET_D)
+ #if HOTENDS > 1
+ #if TEMP_SENSOR_BED
+ #define IS_RAMPS_EEB
+ #else
+ #define IS_RAMPS_EEF
+ #endif
+ #elif TEMP_SENSOR_BED
+ #define IS_RAMPS_EFB
+ #else
+ #define IS_RAMPS_EFF
+ #endif
+#endif
+
+//
+// Heaters / Fans
+//
+#define HEATER_0_PIN 10
+#define HEATER_1_PIN 7
+//#define HEATER_2_PIN 6
+//#define HEATER_3_PIN 8
+#define HEATER_BED_PIN -1
+
+#define LED4_PIN 8
+#define LASER_PIN -1
+
+#define FAN_PIN 9
+#define FAN1_PIN 4
+
+//
+// Misc. Functions
+//
+#define SDSS 53
+#define LED_PIN 13
+
+// Use the RAMPS 1.4 Analog input 5 on the AUX2 connector
+#define FILWIDTH_PIN 5 // Analog Input
+
+// define digital pin 4 for the filament runout sensor. Use the RAMPS 1.4 digital input 4 on the servos connector
+//#define FIL_RUNOUT_PIN 4
+#define FIL_RUNOUT_PIN 42
+
+#define PS_ON_PIN 12
+
+//
+// LCD / Controller
+//
+#if ENABLED(ULTRA_LCD)
+
+ #if ENABLED(REPRAPWORLD_GRAPHICAL_LCD)
+ #define LCD_PINS_RS 49 // CS chip select /SS chip slave select
+ #define LCD_PINS_ENABLE 51 // SID (MOSI)
+ #define LCD_PINS_D4 52 // SCK (CLK) clock
+ #elif ENABLED(NEWPANEL) && ENABLED(PANEL_ONE)
+ #define LCD_PINS_RS 40
+ #define LCD_PINS_ENABLE 42
+ #define LCD_PINS_D4 65
+ #define LCD_PINS_D5 66
+ #define LCD_PINS_D6 44
+ #define LCD_PINS_D7 64
+ #else
+ #define LCD_PINS_RS 16
+ #define LCD_PINS_ENABLE 17
+ #define LCD_PINS_D4 23
+ #define LCD_PINS_D5 25
+ #define LCD_PINS_D6 27
+ #define LCD_PINS_D7 29
+ #if DISABLED(NEWPANEL)
+ #define BEEPER_PIN 33
+ // Buttons are attached to a shift register
+ // Not wired yet
+ //#define SHIFT_CLK 38
+ //#define SHIFT_LD 42
+ //#define SHIFT_OUT 40
+ //#define SHIFT_EN 17
+ #endif
+ #endif
+
+ #if ENABLED(NEWPANEL)
+
+ #if ENABLED(REPRAP_DISCOUNT_SMART_CONTROLLER)
+ #define BEEPER_PIN 37
+
+ #define BTN_EN1 31
+ #define BTN_EN2 33
+ #define BTN_ENC 35
+
+ #define SD_DETECT_PIN 49
+ #define KILL_PIN 41
+
+ #if ENABLED(BQ_LCD_SMART_CONTROLLER)
+ #define LCD_BACKLIGHT_PIN 39
+ #endif
+
+ #elif ENABLED(REPRAPWORLD_GRAPHICAL_LCD)
+ #define BTN_EN1 64
+ #define BTN_EN2 59
+ #define BTN_ENC 63
+ #define SD_DETECT_PIN 42
+ #elif ENABLED(LCD_I2C_PANELOLU2)
+ #define BTN_EN1 47 // reverse if the encoder turns the wrong way.
+ #define BTN_EN2 43
+ #define BTN_ENC 32
+ #define LCD_SDSS 53
+ #define SD_DETECT_PIN -1
+ #define KILL_PIN 41
+ #elif ENABLED(LCD_I2C_VIKI)
+ #define BTN_EN1 22 // reverse if the encoder turns the wrong way.
+ #define BTN_EN2 7 // http://files.panucatt.com/datasheets/viki_wiring_diagram.pdf
+ // tells about 40/42.
+ // 22/7 are unused on RAMPS_14. 22 is unused and 7 the SERVO0_PIN on RAMPS_13.
+ #define BTN_ENC -1
+ #define LCD_SDSS 53
+ #define SD_DETECT_PIN 49
+ #elif ENABLED(VIKI2) || ENABLED(miniVIKI)
+ #define BEEPER_PIN 33
+
+ // Pins for DOGM SPI LCD Support
+ #define DOGLCD_A0 44
+ #define DOGLCD_CS 45
+ #define LCD_SCREEN_ROT_180
+
+ #define BTN_EN1 22
+ #define BTN_EN2 7
+ #define BTN_ENC 39
+
+ #define SDSS 53
+ #define SD_DETECT_PIN -1 // Pin 49 for display sd interface, 72 for easy adapter board
+
+ #define KILL_PIN 31
+
+ #define STAT_LED_RED_PIN 32
+ #define STAT_LED_BLUE_PIN 35
+
+ #elif ENABLED(ELB_FULL_GRAPHIC_CONTROLLER)
+ #define BTN_EN1 35 // reverse if the encoder turns the wrong way.
+ #define BTN_EN2 37
+ #define BTN_ENC 31
+ #define SD_DETECT_PIN 49
+ #define LCD_SDSS 53
+ #define KILL_PIN 41
+ #define BEEPER_PIN 23
+ #define DOGLCD_CS 29
+ #define DOGLCD_A0 27
+ #define LCD_BACKLIGHT_PIN 33
+ #elif ENABLED(MINIPANEL)
+ #define BEEPER_PIN 42
+ // Pins for DOGM SPI LCD Support
+ #define DOGLCD_A0 44
+ #define DOGLCD_CS 66
+ #define LCD_BACKLIGHT_PIN 65 // backlight LED on A11/D65
+ #define SDSS 53
+
+ #define KILL_PIN 64
+ // GLCD features
+ //#define LCD_CONTRAST 190
+ // Uncomment screen orientation
+ //#define LCD_SCREEN_ROT_90
+ //#define LCD_SCREEN_ROT_180
+ //#define LCD_SCREEN_ROT_270
+ // The encoder and click button
+ #define BTN_EN1 40
+ #define BTN_EN2 63
+ #define BTN_ENC 59
+ // not connected to a pin
+ #define SD_DETECT_PIN 49
+
+ #else
+
+ // Beeper on AUX-4
+ #define BEEPER_PIN 33
+
+ // buttons are directly attached using AUX-2
+ #if ENABLED(REPRAPWORLD_KEYPAD)
+ #define BTN_EN1 64 // encoder
+ #define BTN_EN2 59 // encoder
+ #define BTN_ENC 63 // enter button
+ #define SHIFT_OUT 40 // shift register
+ #define SHIFT_CLK 44 // shift register
+ #define SHIFT_LD 42 // shift register
+ #elif ENABLED(PANEL_ONE)
+ #define BTN_EN1 59 // AUX2 PIN 3
+ #define BTN_EN2 63 // AUX2 PIN 4
+ #define BTN_ENC 49 // AUX3 PIN 7
+ #else
+ #define BTN_EN1 37
+ #define BTN_EN2 35
+ #define BTN_ENC 31 // the click
+ #endif
+
+ #if ENABLED(G3D_PANEL)
+ #define SD_DETECT_PIN 49
+ #define KILL_PIN 41
+ #else
+ //#define SD_DETECT_PIN -1 // Ramps doesn't use this
+ #endif
+
+ #endif
+ #endif // NEWPANEL
+
+#endif // ULTRA_LCD
+
diff --git a/ultralcd.cpp b/ultralcd.cpp
new file mode 100644
index 0000000000..adbb7be772
--- /dev/null
+++ b/ultralcd.cpp
@@ -0,0 +1,5582 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "MarlinConfig.h"
+
+#if ENABLED(ULTRA_LCD)
+
+#include "ultralcd.h"
+#include "Marlin.h"
+#include "language.h"
+#include "cardreader.h"
+#include "temperature.h"
+#include "planner.h"
+#include "stepper.h"
+#include "configuration_store.h"
+#include "utility.h"
+#include "parser.h"
+
+#if HAS_BUZZER && DISABLED(LCD_USE_I2C_BUZZER)
+ #include "buzzer.h"
+#endif
+
+#include "printcounter.h"
+
+#if ENABLED(PRINTCOUNTER)
+ #include "duration_t.h"
+#endif
+
+#if ENABLED(BLTOUCH)
+ #include "endstops.h"
+#endif
+
+#if ENABLED(AUTO_BED_LEVELING_UBL)
+ #include "ubl.h"
+#elif HAS_ABL
+ #include "planner.h"
+#elif ENABLED(MESH_BED_LEVELING) && ENABLED(LCD_BED_LEVELING)
+ #include "mesh_bed_leveling.h"
+#endif
+
+#if ENABLED(FWRETRACT)
+ #include "fwretract.h"
+#endif
+
+#if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(G26_MESH_VALIDATION)
+ bool lcd_external_control; // = false
+#endif
+
+// Initialized by settings.load()
+int16_t lcd_preheat_hotend_temp[2], lcd_preheat_bed_temp[2], lcd_preheat_fan_speed[2];
+
+#if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
+ millis_t previous_lcd_status_ms = 0;
+#endif
+
+#if ENABLED(BABYSTEPPING)
+ long babysteps_done = 0;
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ static void lcd_babystep_zoffset();
+ #else
+ static void lcd_babystep_z();
+ #endif
+#endif
+
+uint8_t lcd_status_update_delay = 1, // First update one loop delayed
+ lcd_status_message_level; // Higher level blocks lower level
+
+#if ENABLED(STATUS_MESSAGE_SCROLLING)
+ #if LONG_FILENAME_LENGTH > CHARSIZE * 2 * (LCD_WIDTH)
+ #define MAX_MESSAGE_LENGTH LONG_FILENAME_LENGTH
+ #else
+ #define MAX_MESSAGE_LENGTH CHARSIZE * 2 * (LCD_WIDTH)
+ #endif
+ uint8_t status_scroll_pos = 0;
+#else
+ #define MAX_MESSAGE_LENGTH CHARSIZE * (LCD_WIDTH)
+#endif
+
+char lcd_status_message[MAX_MESSAGE_LENGTH + 1];
+
+#if ENABLED(SCROLL_LONG_FILENAMES)
+ uint8_t filename_scroll_pos, filename_scroll_max, filename_scroll_hash;
+#endif
+
+#if ENABLED(LCD_SET_PROGRESS_MANUALLY)
+ uint8_t progress_bar_percent;
+#endif
+
+#if ENABLED(DOGLCD)
+ #include "ultralcd_impl_DOGM.h"
+ #include
+#else
+ #include "ultralcd_impl_HD44780.h"
+#endif
+
+#if ENABLED(ULTIPANEL)
+ #define DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(_type, _name, _strFunc) \
+ inline void lcd_implementation_drawmenu_setting_edit_ ## _name (const bool sel, const uint8_t row, const char* pstr, const char* pstr2, _type * const data, ...) { \
+ UNUSED(pstr2); \
+ DRAWMENU_SETTING_EDIT_GENERIC(_strFunc(*(data))); \
+ } \
+ inline void lcd_implementation_drawmenu_setting_edit_callback_ ## _name (const bool sel, const uint8_t row, const char* pstr, const char* pstr2, _type * const data, ...) { \
+ UNUSED(pstr2); \
+ DRAWMENU_SETTING_EDIT_GENERIC(_strFunc(*(data))); \
+ } \
+ inline void lcd_implementation_drawmenu_setting_edit_accessor_ ## _name (const bool sel, const uint8_t row, const char* pstr, const char* pstr2, _type (*pget)(), void (*pset)(_type), ...) { \
+ UNUSED(pstr2); UNUSED(pset); \
+ DRAWMENU_SETTING_EDIT_GENERIC(_strFunc(pget())); \
+ } \
+ typedef void _name##_void
+ DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(int16_t, int3, itostr3);
+ DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(uint8_t, int8, i8tostr3);
+ DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float3, ftostr3);
+ DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float32, ftostr32);
+ DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float43, ftostr43sign);
+ DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float5, ftostr5rj);
+ DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float51, ftostr51sign);
+ DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float52, ftostr52sign);
+ DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float62, ftostr62rj);
+ DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(uint32_t, long5, ftostr5rj);
+ #define lcd_implementation_drawmenu_setting_edit_bool(sel, row, pstr, pstr2, data) DRAW_BOOL_SETTING(sel, row, pstr, data)
+ #define lcd_implementation_drawmenu_setting_edit_callback_bool(sel, row, pstr, pstr2, data, callback) DRAW_BOOL_SETTING(sel, row, pstr, data)
+ #define lcd_implementation_drawmenu_setting_edit_accessor_bool(sel, row, pstr, pstr2, pget, pset) DRAW_BOOL_SETTING(sel, row, pstr, data)
+#endif // ULTIPANEL
+
+// The main status screen
+void lcd_status_screen();
+
+millis_t next_lcd_update_ms;
+
+uint8_t lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW; // Set when the LCD needs to draw, decrements after every draw. Set to 2 in LCD routines so the LCD gets at least 1 full redraw (first redraw is partial)
+uint16_t max_display_update_time = 0;
+
+#if ENABLED(DOGLCD)
+ bool drawing_screen, // = false
+ first_page;
+#else
+ constexpr bool first_page = true;
+#endif
+
+#if ENABLED(DAC_STEPPER_CURRENT)
+ #include "stepper_dac.h" //was dac_mcp4728.h MarlinMain uses stepper dac for the m-codes
+ uint8_t driverPercent[XYZE];
+#endif
+
+#if ENABLED(ULTIPANEL)
+
+ #ifndef TALL_FONT_CORRECTION
+ #define TALL_FONT_CORRECTION 0
+ #endif
+
+ #if HAS_POWER_SWITCH
+ extern bool powersupply_on;
+ #endif
+
+ bool no_reentry = false;
+ constexpr int8_t menu_bottom = LCD_HEIGHT - (TALL_FONT_CORRECTION);
+
+ ////////////////////////////////////////////
+ ///////////////// Menu Tree ////////////////
+ ////////////////////////////////////////////
+
+ void lcd_main_menu();
+ void lcd_tune_menu();
+ void lcd_prepare_menu();
+ void lcd_move_menu();
+ void lcd_control_menu();
+ void lcd_control_temperature_menu();
+ void lcd_control_motion_menu();
+
+ #if DISABLED(SLIM_LCD_MENUS)
+ void lcd_control_temperature_preheat_material1_settings_menu();
+ void lcd_control_temperature_preheat_material2_settings_menu();
+ #endif
+
+ #if DISABLED(NO_VOLUMETRICS) || ENABLED(ADVANCED_PAUSE_FEATURE)
+ void lcd_control_filament_menu();
+ #endif
+
+ #if ENABLED(LCD_INFO_MENU)
+ #if ENABLED(PRINTCOUNTER)
+ void lcd_info_stats_menu();
+ #endif
+ void lcd_info_thermistors_menu();
+ void lcd_info_board_menu();
+ void lcd_info_menu();
+ #endif // LCD_INFO_MENU
+
+ #if ENABLED(LED_CONTROL_MENU)
+ #include "leds.h"
+ void lcd_led_menu();
+ #endif
+
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ #if E_STEPPERS > 1 || ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ void lcd_change_filament_menu();
+ #else
+ void lcd_temp_menu_e0_filament_change();
+ #endif
+ void lcd_advanced_pause_option_menu();
+ void lcd_advanced_pause_init_message();
+ void lcd_advanced_pause_unload_message();
+ void lcd_advanced_pause_insert_message();
+ void lcd_advanced_pause_load_message();
+ void lcd_advanced_pause_heat_nozzle();
+ void lcd_advanced_pause_purge_message();
+ void lcd_advanced_pause_resume_message();
+ #endif
+
+ #if ENABLED(DAC_STEPPER_CURRENT)
+ void dac_driver_commit();
+ void dac_driver_getValues();
+ void lcd_dac_menu();
+ void lcd_dac_write_eeprom();
+ #endif
+
+ #if ENABLED(FWRETRACT)
+ void lcd_control_retract_menu();
+ #endif
+
+ #if ENABLED(DELTA_CALIBRATION_MENU) || ENABLED(DELTA_AUTO_CALIBRATION)
+ void lcd_delta_calibrate_menu();
+ #endif
+
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ static float new_z_fade_height;
+ void _lcd_set_z_fade_height() { set_z_fade_height(new_z_fade_height); }
+ #endif
+
+ ////////////////////////////////////////////
+ //////////// Menu System Actions ///////////
+ ////////////////////////////////////////////
+
+ #define menu_action_back(dummy) _menu_action_back()
+ void _menu_action_back();
+ void menu_action_submenu(screenFunc_t data);
+ void menu_action_gcode(const char* pgcode);
+ void menu_action_function(menuAction_t data);
+
+ #define DECLARE_MENU_EDIT_TYPE(_type, _name) \
+ bool _menu_edit_ ## _name(); \
+ void menu_edit_ ## _name(); \
+ void menu_edit_callback_ ## _name(); \
+ void _menu_action_setting_edit_ ## _name(const char * const pstr, _type* const ptr, const _type minValue, const _type maxValue); \
+ void menu_action_setting_edit_ ## _name(const char * const pstr, _type * const ptr, const _type minValue, const _type maxValue); \
+ void menu_action_setting_edit_callback_ ## _name(const char * const pstr, _type * const ptr, const _type minValue, const _type maxValue, const screenFunc_t callback, const bool live=false); \
+ typedef void _name##_void
+
+ DECLARE_MENU_EDIT_TYPE(int16_t, int3);
+ DECLARE_MENU_EDIT_TYPE(uint8_t, int8);
+ DECLARE_MENU_EDIT_TYPE(float, float3);
+ DECLARE_MENU_EDIT_TYPE(float, float32);
+ DECLARE_MENU_EDIT_TYPE(float, float43);
+ DECLARE_MENU_EDIT_TYPE(float, float5);
+ DECLARE_MENU_EDIT_TYPE(float, float51);
+ DECLARE_MENU_EDIT_TYPE(float, float52);
+ DECLARE_MENU_EDIT_TYPE(float, float62);
+ DECLARE_MENU_EDIT_TYPE(uint32_t, long5);
+
+ void menu_action_setting_edit_bool(const char* pstr, bool* ptr);
+ void menu_action_setting_edit_callback_bool(const char* pstr, bool* ptr, screenFunc_t callbackFunc);
+
+ #if ENABLED(SDSUPPORT)
+ void lcd_sdcard_menu();
+ void menu_action_sdfile(const char* filename, char* longFilename);
+ void menu_action_sddirectory(const char* filename, char* longFilename);
+ #endif
+
+ ////////////////////////////////////////////
+ //////////// Menu System Macros ////////////
+ ////////////////////////////////////////////
+
+ /**
+ * MENU_ITEM generates draw & handler code for a menu item, potentially calling:
+ *
+ * lcd_implementation_drawmenu_[type](sel, row, label, arg3...)
+ * menu_action_[type](arg3...)
+ *
+ * Examples:
+ * MENU_ITEM(back, MSG_WATCH, 0 [dummy parameter] )
+ * or
+ * MENU_BACK(MSG_WATCH)
+ * lcd_implementation_drawmenu_back(sel, row, PSTR(MSG_WATCH))
+ * menu_action_back()
+ *
+ * MENU_ITEM(function, MSG_PAUSE_PRINT, lcd_sdcard_pause)
+ * lcd_implementation_drawmenu_function(sel, row, PSTR(MSG_PAUSE_PRINT), lcd_sdcard_pause)
+ * menu_action_function(lcd_sdcard_pause)
+ *
+ * MENU_ITEM_EDIT(int3, MSG_SPEED, &feedrate_percentage, 10, 999)
+ * MENU_ITEM(setting_edit_int3, MSG_SPEED, PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
+ * lcd_implementation_drawmenu_setting_edit_int3(sel, row, PSTR(MSG_SPEED), PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
+ * menu_action_setting_edit_int3(PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
+ *
+ */
+ #define _MENU_ITEM_PART_1(TYPE, ...) \
+ if (_menuLineNr == _thisItemNr) { \
+ if (encoderLine == _thisItemNr && lcd_clicked) { \
+ lcd_clicked = false
+
+ #define _MENU_ITEM_PART_2(TYPE, PLABEL, ...) \
+ menu_action_ ## TYPE(__VA_ARGS__); \
+ if (screen_changed) return; \
+ } \
+ if (lcdDrawUpdate) \
+ lcd_implementation_drawmenu_ ## TYPE(encoderLine == _thisItemNr, _lcdLineNr, PLABEL, ## __VA_ARGS__); \
+ } \
+ ++_thisItemNr
+
+ #define MENU_ITEM_P(TYPE, PLABEL, ...) do { \
+ _skipStatic = false; \
+ _MENU_ITEM_PART_1(TYPE, ## __VA_ARGS__); \
+ _MENU_ITEM_PART_2(TYPE, PLABEL, ## __VA_ARGS__); \
+ }while(0)
+
+ #define MENU_ITEM(TYPE, LABEL, ...) MENU_ITEM_P(TYPE, PSTR(LABEL), ## __VA_ARGS__)
+
+ #define MENU_BACK(LABEL) MENU_ITEM(back, LABEL, 0)
+
+ // Used to print static text with no visible cursor.
+ // Parameters: label [, bool center [, bool invert [, char *value] ] ]
+ #define STATIC_ITEM_P(LABEL, ...) \
+ if (_menuLineNr == _thisItemNr) { \
+ if (_skipStatic && encoderLine <= _thisItemNr) { \
+ encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; \
+ ++encoderLine; \
+ } \
+ if (lcdDrawUpdate) \
+ lcd_implementation_drawmenu_static(_lcdLineNr, LABEL, ## __VA_ARGS__); \
+ } \
+ ++_thisItemNr
+
+ #define STATIC_ITEM(LABEL, ...) STATIC_ITEM_P(PSTR(LABEL), ## __VA_ARGS__)
+
+ #if ENABLED(ENCODER_RATE_MULTIPLIER)
+
+ bool encoderRateMultiplierEnabled;
+ #define ENCODER_RATE_MULTIPLY(F) (encoderRateMultiplierEnabled = F)
+
+ //#define ENCODER_RATE_MULTIPLIER_DEBUG // If defined, output the encoder steps per second value
+
+ /**
+ * MENU_MULTIPLIER_ITEM generates drawing and handling code for a multiplier menu item
+ */
+ #define MENU_MULTIPLIER_ITEM(TYPE, LABEL, ...) do { \
+ _MENU_ITEM_PART_1(TYPE, ## __VA_ARGS__); \
+ encoderRateMultiplierEnabled = true; \
+ lastEncoderMovementMillis = 0; \
+ _MENU_ITEM_PART_2(TYPE, PSTR(LABEL), ## __VA_ARGS__); \
+ }while(0)
+
+ #else // !ENCODER_RATE_MULTIPLIER
+ #define ENCODER_RATE_MULTIPLY(F) NOOP
+ #endif // !ENCODER_RATE_MULTIPLIER
+
+ #define MENU_ITEM_DUMMY() do { _thisItemNr++; }while(0)
+ #define MENU_ITEM_EDIT(TYPE, LABEL, ...) MENU_ITEM(setting_edit_ ## TYPE, LABEL, PSTR(LABEL), ## __VA_ARGS__)
+ #define MENU_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...) MENU_ITEM(setting_edit_callback_ ## TYPE, LABEL, PSTR(LABEL), ## __VA_ARGS__)
+ #if ENABLED(ENCODER_RATE_MULTIPLIER)
+ #define MENU_MULTIPLIER_ITEM_EDIT(TYPE, LABEL, ...) MENU_MULTIPLIER_ITEM(setting_edit_ ## TYPE, LABEL, PSTR(LABEL), ## __VA_ARGS__)
+ #define MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...) MENU_MULTIPLIER_ITEM(setting_edit_callback_ ## TYPE, LABEL, PSTR(LABEL), ## __VA_ARGS__)
+ #else // !ENCODER_RATE_MULTIPLIER
+ #define MENU_MULTIPLIER_ITEM_EDIT(TYPE, LABEL, ...) MENU_ITEM(setting_edit_ ## TYPE, LABEL, PSTR(LABEL), ## __VA_ARGS__)
+ #define MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...) MENU_ITEM(setting_edit_callback_ ## TYPE, LABEL, PSTR(LABEL), ## __VA_ARGS__)
+ #endif // !ENCODER_RATE_MULTIPLIER
+
+ #define SCREEN_OR_MENU_LOOP() \
+ int8_t _menuLineNr = encoderTopLine, _thisItemNr; \
+ for (int8_t _lcdLineNr = 0; _lcdLineNr < menu_bottom; _lcdLineNr++, _menuLineNr++) { \
+ _thisItemNr = 0
+
+ /**
+ * START_SCREEN Opening code for a screen having only static items.
+ * Do simplified scrolling of the entire screen.
+ *
+ * START_MENU Opening code for a screen with menu items.
+ * Scroll as-needed to keep the selected line in view.
+ */
+ #define START_SCREEN() \
+ scroll_screen(menu_bottom, false); \
+ bool _skipStatic = false; \
+ SCREEN_OR_MENU_LOOP()
+
+ #define START_MENU() \
+ scroll_screen(1, true); \
+ bool _skipStatic = true; \
+ SCREEN_OR_MENU_LOOP()
+
+ #define END_SCREEN() \
+ } \
+ screen_items = _thisItemNr
+
+ #define END_MENU() \
+ } \
+ screen_items = _thisItemNr; \
+ UNUSED(_skipStatic)
+
+ ////////////////////////////////////////////
+ ///////////// Global Variables /////////////
+ ////////////////////////////////////////////
+
+ /**
+ * REVERSE_MENU_DIRECTION
+ *
+ * To reverse the menu direction we need a general way to reverse
+ * the direction of the encoder everywhere. So encoderDirection is
+ * added to allow the encoder to go the other way.
+ *
+ * This behavior is limited to scrolling Menus and SD card listings,
+ * and is disabled in other contexts.
+ */
+ #if ENABLED(REVERSE_MENU_DIRECTION)
+ int8_t encoderDirection = 1;
+ #define ENCODER_DIRECTION_NORMAL() (encoderDirection = 1)
+ #define ENCODER_DIRECTION_MENUS() (encoderDirection = -1)
+ #else
+ #define ENCODER_DIRECTION_NORMAL() ;
+ #define ENCODER_DIRECTION_MENUS() ;
+ #endif
+
+ // Encoder Movement
+ volatile int8_t encoderDiff; // Updated in lcd_buttons_update, added to encoderPosition every LCD update
+ uint32_t encoderPosition;
+ millis_t lastEncoderMovementMillis = 0;
+
+ // Button States
+ bool lcd_clicked, wait_for_unclick;
+ volatile uint8_t buttons;
+ millis_t next_button_update_ms;
+ #if ENABLED(REPRAPWORLD_KEYPAD)
+ volatile uint8_t buttons_reprapworld_keypad;
+ #endif
+ #if ENABLED(LCD_HAS_SLOW_BUTTONS)
+ volatile uint8_t slow_buttons;
+ #endif
+
+ // Menu System Navigation
+ screenFunc_t currentScreen = lcd_status_screen;
+ int8_t encoderTopLine;
+ typedef struct {
+ screenFunc_t menu_function;
+ uint32_t encoder_position;
+ } menuPosition;
+ menuPosition screen_history[6];
+ uint8_t screen_history_depth = 0;
+ bool screen_changed, defer_return_to_status;
+
+ // Value Editing
+ const char *editLabel;
+ void *editValue;
+ int32_t minEditValue, maxEditValue;
+ screenFunc_t callbackFunc;
+ bool liveEdit;
+
+ // Manual Moves
+ const float manual_feedrate_mm_m[] = MANUAL_FEEDRATE;
+ millis_t manual_move_start_time = 0;
+ int8_t manual_move_axis = (int8_t)NO_AXIS;
+ #if EXTRUDERS > 1
+ int8_t manual_move_e_index = 0;
+ #else
+ #define manual_move_e_index 0
+ #endif
+
+ #if IS_KINEMATIC
+ bool processing_manual_move = false;
+ float manual_move_offset = 0.0;
+ #else
+ constexpr bool processing_manual_move = false;
+ #endif
+
+ #if PIN_EXISTS(SD_DETECT)
+ uint8_t lcd_sd_status;
+ #endif
+
+ #if ENABLED(PIDTEMP)
+ float raw_Ki, raw_Kd; // place-holders for Ki and Kd edits
+ #endif
+
+ inline bool use_click() {
+ const bool click = lcd_clicked;
+ lcd_clicked = false;
+ return click;
+ }
+
+ /**
+ * General function to go directly to a screen
+ */
+ void lcd_goto_screen(screenFunc_t screen, const uint32_t encoder/*=0*/) {
+ if (currentScreen != screen) {
+
+ #if ENABLED(DOUBLECLICK_FOR_Z_BABYSTEPPING) && ENABLED(BABYSTEPPING)
+ static millis_t doubleclick_expire_ms = 0;
+ // Going to lcd_main_menu from status screen? Remember first click time.
+ // Going back to status screen within a very short time? Go to Z babystepping.
+ if (screen == lcd_main_menu) {
+ if (currentScreen == lcd_status_screen)
+ doubleclick_expire_ms = millis() + DOUBLECLICK_MAX_INTERVAL;
+ }
+ else if (screen == lcd_status_screen && currentScreen == lcd_main_menu && PENDING(millis(), doubleclick_expire_ms))
+ screen =
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ lcd_babystep_zoffset
+ #else
+ lcd_babystep_z
+ #endif
+ ;
+ #endif
+
+ currentScreen = screen;
+ encoderPosition = encoder;
+ if (screen == lcd_status_screen) {
+ defer_return_to_status = false;
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+ ubl.lcd_map_control = false;
+ #endif
+ screen_history_depth = 0;
+ }
+ lcd_implementation_clear();
+ // Re-initialize custom characters that may be re-used
+ #if DISABLED(DOGLCD) && ENABLED(AUTO_BED_LEVELING_UBL)
+ if (!ubl.lcd_map_control) {
+ lcd_set_custom_characters(
+ #if ENABLED(LCD_PROGRESS_BAR)
+ screen == lcd_status_screen ? CHARSET_INFO : CHARSET_MENU
+ #endif
+ );
+ }
+ #elif ENABLED(LCD_PROGRESS_BAR)
+ lcd_set_custom_characters(screen == lcd_status_screen ? CHARSET_INFO : CHARSET_MENU);
+ #endif
+ lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+ screen_changed = true;
+ #if ENABLED(DOGLCD)
+ drawing_screen = false;
+ #endif
+ }
+ }
+
+ /**
+ * Show "Moving..." till moves are done, then revert to previous display.
+ */
+ static const char moving[] PROGMEM = MSG_MOVING;
+ static const char *sync_message = moving;
+
+ //
+ // Display the synchronize screen until moves are
+ // finished, and don't return to the caller until
+ // done. ** This blocks the command queue! **
+ //
+ void _lcd_synchronize() {
+ if (lcdDrawUpdate) lcd_implementation_drawmenu_static(LCD_HEIGHT >= 4 ? 1 : 0, sync_message);
+ if (no_reentry) return;
+ // Make this the current handler till all moves are done
+ no_reentry = true;
+ const screenFunc_t old_screen = currentScreen;
+ lcd_goto_screen(_lcd_synchronize);
+ stepper.synchronize(); // idle() is called until moves complete
+ no_reentry = false;
+ lcd_goto_screen(old_screen);
+ }
+
+ // Display the synchronize screen with a custom message
+ // ** This blocks the command queue! **
+ void lcd_synchronize(const char * const msg=NULL) {
+ sync_message = msg ? msg : moving;
+ _lcd_synchronize();
+ }
+
+ void lcd_return_to_status() { lcd_goto_screen(lcd_status_screen); }
+
+ void lcd_save_previous_screen() {
+ if (screen_history_depth < COUNT(screen_history)) {
+ screen_history[screen_history_depth].menu_function = currentScreen;
+ screen_history[screen_history_depth].encoder_position = encoderPosition;
+ ++screen_history_depth;
+ }
+ }
+
+ void lcd_goto_previous_menu() {
+ if (screen_history_depth > 0) {
+ --screen_history_depth;
+ lcd_goto_screen(
+ screen_history[screen_history_depth].menu_function,
+ screen_history[screen_history_depth].encoder_position
+ );
+ }
+ else
+ lcd_return_to_status();
+ }
+
+ void lcd_goto_previous_menu_no_defer() {
+ defer_return_to_status = false;
+ lcd_goto_previous_menu();
+ }
+
+ /**
+ * Scrolling for menus and other line-based screens
+ *
+ * encoderLine is the position based on the encoder
+ * encoderTopLine is the top menu line to display
+ * _lcdLineNr is the index of the LCD line (e.g., 0-3)
+ * _menuLineNr is the menu item to draw and process
+ * _thisItemNr is the index of each MENU_ITEM or STATIC_ITEM
+ * screen_items is the total number of items in the menu (after one call)
+ */
+ int8_t encoderLine, screen_items;
+ void scroll_screen(const uint8_t limit, const bool is_menu) {
+ ENCODER_DIRECTION_MENUS();
+ ENCODER_RATE_MULTIPLY(false);
+ if (encoderPosition > 0x8000) encoderPosition = 0;
+ if (first_page) {
+ encoderLine = encoderPosition / (ENCODER_STEPS_PER_MENU_ITEM);
+ screen_changed = false;
+ }
+ if (screen_items > 0 && encoderLine >= screen_items - limit) {
+ encoderLine = max(0, screen_items - limit);
+ encoderPosition = encoderLine * (ENCODER_STEPS_PER_MENU_ITEM);
+ }
+ if (is_menu) {
+ NOMORE(encoderTopLine, encoderLine);
+ if (encoderLine >= encoderTopLine + menu_bottom)
+ encoderTopLine = encoderLine - menu_bottom + 1;
+ }
+ else
+ encoderTopLine = encoderLine;
+ }
+
+#endif // ULTIPANEL
+
+/**
+ *
+ * "Info Screen"
+ *
+ * This is very display-dependent, so the lcd implementation draws this.
+ */
+
+void lcd_status_screen() {
+
+ #if ENABLED(ULTIPANEL)
+ ENCODER_DIRECTION_NORMAL();
+ ENCODER_RATE_MULTIPLY(false);
+ #endif
+
+ #if ENABLED(LCD_SET_PROGRESS_MANUALLY) && ENABLED(SDSUPPORT) && (ENABLED(LCD_PROGRESS_BAR) || ENABLED(DOGLCD))
+ // Progress bar % comes from SD when actively printing
+ if (IS_SD_PRINTING)
+ progress_bar_percent = card.percentDone();
+ #endif
+
+ #if ENABLED(LCD_PROGRESS_BAR)
+
+ //
+ // HD44780 implements the following message blinking and
+ // message expiration because Status Line and Progress Bar
+ // share the same line on the display.
+ //
+
+ millis_t ms = millis();
+
+ // If the message will blink rather than expire...
+ #if DISABLED(PROGRESS_MSG_ONCE)
+ if (ELAPSED(ms, progress_bar_ms + PROGRESS_BAR_MSG_TIME + PROGRESS_BAR_BAR_TIME))
+ progress_bar_ms = ms;
+ #endif
+
+ #if PROGRESS_MSG_EXPIRE > 0
+
+ // Handle message expire
+ if (expire_status_ms > 0) {
+
+ #if DISABLED(LCD_SET_PROGRESS_MANUALLY)
+ const uint8_t progress_bar_percent = card.percentDone();
+ #endif
+
+ // Expire the message if a job is active and the bar has ticks
+ if (progress_bar_percent > 2 && !print_job_timer.isPaused()) {
+ if (ELAPSED(ms, expire_status_ms)) {
+ lcd_status_message[0] = '\0';
+ expire_status_ms = 0;
+ }
+ }
+ else {
+ // Defer message expiration before bar appears
+ // and during any pause (not just SD)
+ expire_status_ms += LCD_UPDATE_INTERVAL;
+ }
+ }
+
+ #endif // PROGRESS_MSG_EXPIRE
+
+ #endif // LCD_PROGRESS_BAR
+
+ #if ENABLED(ULTIPANEL)
+
+ if (use_click()) {
+ #if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
+ previous_lcd_status_ms = millis(); // get status message to show up for a while
+ #endif
+ lcd_implementation_init( // to maybe revive the LCD if static electricity killed it.
+ #if ENABLED(LCD_PROGRESS_BAR)
+ CHARSET_MENU
+ #endif
+ );
+ lcd_goto_screen(lcd_main_menu);
+ return;
+ }
+
+ #if ENABLED(ULTIPANEL_FEEDMULTIPLY)
+ const int16_t new_frm = feedrate_percentage + (int32_t)encoderPosition;
+ // Dead zone at 100% feedrate
+ if ((feedrate_percentage < 100 && new_frm > 100) || (feedrate_percentage > 100 && new_frm < 100)) {
+ feedrate_percentage = 100;
+ encoderPosition = 0;
+ }
+ else if (feedrate_percentage == 100) {
+ if ((int32_t)encoderPosition > ENCODER_FEEDRATE_DEADZONE) {
+ feedrate_percentage += (int32_t)encoderPosition - (ENCODER_FEEDRATE_DEADZONE);
+ encoderPosition = 0;
+ }
+ else if ((int32_t)encoderPosition < -(ENCODER_FEEDRATE_DEADZONE)) {
+ feedrate_percentage += (int32_t)encoderPosition + ENCODER_FEEDRATE_DEADZONE;
+ encoderPosition = 0;
+ }
+ }
+ else {
+ feedrate_percentage = new_frm;
+ encoderPosition = 0;
+ }
+ #endif // ULTIPANEL_FEEDMULTIPLY
+
+ feedrate_percentage = constrain(feedrate_percentage, 10, 999);
+
+ #endif // ULTIPANEL
+
+ lcd_implementation_status_screen();
+}
+
+void lcd_reset_status() { lcd_setstatusPGM(PSTR(""), -1); }
+
+/**
+ *
+ * draw the kill screen
+ *
+ */
+void kill_screen(const char* lcd_msg) {
+ lcd_init();
+ lcd_setalertstatusPGM(lcd_msg);
+ lcd_kill_screen();
+}
+
+#if ENABLED(ULTIPANEL)
+
+ /**
+ *
+ * Audio feedback for controller clicks
+ *
+ */
+ void lcd_buzz(const long duration, const uint16_t freq) {
+ #if ENABLED(LCD_USE_I2C_BUZZER)
+ lcd.buzz(duration, freq);
+ #elif PIN_EXISTS(BEEPER)
+ buzzer.tone(duration, freq);
+ #else
+ UNUSED(duration); UNUSED(freq);
+ #endif
+ }
+
+ void lcd_quick_feedback(const bool clear_buttons) {
+ lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW;
+ if (clear_buttons) buttons = 0;
+ next_button_update_ms = millis() + 500;
+
+ // Buzz and wait. The delay is needed for buttons to settle!
+ lcd_buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ);
+ #if ENABLED(LCD_USE_I2C_BUZZER)
+ delay(10);
+ #elif PIN_EXISTS(BEEPER)
+ for (int8_t i = 5; i--;) { buzzer.tick(); delay(2); }
+ #endif
+ }
+
+ void lcd_completion_feedback(const bool good/*=true*/) {
+ if (good) {
+ lcd_buzz(100, 659);
+ lcd_buzz(100, 698);
+ }
+ else lcd_buzz(20, 440);
+ }
+
+ inline void line_to_current_z() {
+ planner.buffer_line_kinematic(current_position, MMM_TO_MMS(manual_feedrate_mm_m[Z_AXIS]), active_extruder);
+ }
+
+ inline void line_to_z(const float &z) {
+ current_position[Z_AXIS] = z;
+ line_to_current_z();
+ }
+
+ #if ENABLED(SDSUPPORT)
+
+ void lcd_sdcard_pause() {
+ card.pauseSDPrint();
+ print_job_timer.pause();
+ #if ENABLED(PARK_HEAD_ON_PAUSE)
+ enqueue_and_echo_commands_P(PSTR("M125"));
+ #endif
+ lcd_setstatusPGM(PSTR(MSG_PRINT_PAUSED), -1);
+ }
+
+ void lcd_sdcard_resume() {
+ #if ENABLED(PARK_HEAD_ON_PAUSE)
+ enqueue_and_echo_commands_P(PSTR("M24"));
+ #else
+ card.startFileprint();
+ print_job_timer.start();
+ #endif
+ lcd_reset_status();
+ }
+
+ void lcd_sdcard_stop() {
+ card.stopSDPrint(
+ #if SD_RESORT
+ true
+ #endif
+ );
+ clear_command_queue();
+ quickstop_stepper();
+ print_job_timer.stop();
+ thermalManager.disable_all_heaters();
+ #if FAN_COUNT > 0
+ for (uint8_t i = 0; i < FAN_COUNT; i++) fanSpeeds[i] = 0;
+ #endif
+ wait_for_heatup = false;
+ lcd_setstatusPGM(PSTR(MSG_PRINT_ABORTED), -1);
+ lcd_return_to_status();
+ }
+
+ #endif // SDSUPPORT
+
+ #if ENABLED(MENU_ITEM_CASE_LIGHT)
+
+ extern uint8_t case_light_brightness;
+ extern bool case_light_on;
+ extern void update_case_light();
+
+ void case_light_menu() {
+ START_MENU();
+ //
+ // ^ Main
+ //
+ MENU_BACK(MSG_MAIN);
+ MENU_ITEM_EDIT_CALLBACK(int8, MSG_CASE_LIGHT_BRIGHTNESS, &case_light_brightness, 0, 255, update_case_light, true);
+ MENU_ITEM_EDIT_CALLBACK(bool, MSG_CASE_LIGHT, (bool*)&case_light_on, update_case_light);
+ END_MENU();
+ }
+ #endif // MENU_ITEM_CASE_LIGHT
+
+ #if ENABLED(BLTOUCH)
+
+ /**
+ *
+ * "BLTouch" submenu
+ *
+ */
+ static void bltouch_menu() {
+ START_MENU();
+ //
+ // ^ Main
+ //
+ MENU_BACK(MSG_MAIN);
+ MENU_ITEM(gcode, MSG_BLTOUCH_RESET, PSTR("M280 P" STRINGIFY(Z_ENDSTOP_SERVO_NR) " S" STRINGIFY(BLTOUCH_RESET)));
+ MENU_ITEM(gcode, MSG_BLTOUCH_SELFTEST, PSTR("M280 P" STRINGIFY(Z_ENDSTOP_SERVO_NR) " S" STRINGIFY(BLTOUCH_SELFTEST)));
+ MENU_ITEM(gcode, MSG_BLTOUCH_DEPLOY, PSTR("M280 P" STRINGIFY(Z_ENDSTOP_SERVO_NR) " S" STRINGIFY(BLTOUCH_DEPLOY)));
+ MENU_ITEM(gcode, MSG_BLTOUCH_STOW, PSTR("M280 P" STRINGIFY(Z_ENDSTOP_SERVO_NR) " S" STRINGIFY(BLTOUCH_STOW)));
+ END_MENU();
+ }
+
+ #endif // BLTOUCH
+
+ #if ENABLED(LCD_PROGRESS_BAR_TEST)
+
+ static void progress_bar_test() {
+ static int8_t bar_percent = 0;
+ if (use_click()) {
+ lcd_goto_previous_menu();
+ lcd_set_custom_characters(CHARSET_MENU);
+ return;
+ }
+ bar_percent += (int8_t)encoderPosition;
+ bar_percent = constrain(bar_percent, 0, 100);
+ encoderPosition = 0;
+ lcd_implementation_drawmenu_static(0, PSTR(MSG_PROGRESS_BAR_TEST), true, true);
+ lcd.setCursor((LCD_WIDTH) / 2 - 2, LCD_HEIGHT - 2);
+ lcd.print(itostr3(bar_percent)); lcd.write('%');
+ lcd.setCursor(0, LCD_HEIGHT - 1); lcd_draw_progress_bar(bar_percent);
+ }
+
+ void _progress_bar_test() {
+ lcd_goto_screen(progress_bar_test);
+ lcd_set_custom_characters();
+ }
+
+ #endif // LCD_PROGRESS_BAR_TEST
+
+ #if HAS_DEBUG_MENU
+
+ void lcd_debug_menu() {
+ START_MENU();
+
+ MENU_BACK(MSG_MAIN); // ^ Main
+
+ #if ENABLED(LCD_PROGRESS_BAR_TEST)
+ MENU_ITEM(submenu, MSG_PROGRESS_BAR_TEST, _progress_bar_test);
+ #endif
+
+ END_MENU();
+ }
+
+ #endif // HAS_DEBUG_MENU
+
+ #if ENABLED(CUSTOM_USER_MENUS)
+
+ #ifdef USER_SCRIPT_DONE
+ #define _DONE_SCRIPT "\n" USER_SCRIPT_DONE
+ #else
+ #define _DONE_SCRIPT ""
+ #endif
+
+ void _lcd_user_gcode(const char * const cmd) {
+ enqueue_and_echo_commands_P(cmd);
+ #if ENABLED(USER_SCRIPT_AUDIBLE_FEEDBACK)
+ lcd_completion_feedback();
+ #endif
+ #if ENABLED(USER_SCRIPT_RETURN)
+ lcd_return_to_status();
+ #endif
+ }
+
+ #if defined(USER_DESC_1) && defined(USER_GCODE_1)
+ void lcd_user_gcode_1() { _lcd_user_gcode(PSTR(USER_GCODE_1 _DONE_SCRIPT)); }
+ #endif
+ #if defined(USER_DESC_2) && defined(USER_GCODE_2)
+ void lcd_user_gcode_2() { _lcd_user_gcode(PSTR(USER_GCODE_2 _DONE_SCRIPT)); }
+ #endif
+ #if defined(USER_DESC_3) && defined(USER_GCODE_3)
+ void lcd_user_gcode_3() { _lcd_user_gcode(PSTR(USER_GCODE_3 _DONE_SCRIPT)); }
+ #endif
+ #if defined(USER_DESC_4) && defined(USER_GCODE_4)
+ void lcd_user_gcode_4() { _lcd_user_gcode(PSTR(USER_GCODE_4 _DONE_SCRIPT)); }
+ #endif
+ #if defined(USER_DESC_5) && defined(USER_GCODE_5)
+ void lcd_user_gcode_5() { _lcd_user_gcode(PSTR(USER_GCODE_5 _DONE_SCRIPT)); }
+ #endif
+
+ void _lcd_user_menu() {
+ START_MENU();
+ MENU_BACK(MSG_MAIN);
+ #if defined(USER_DESC_1) && defined(USER_GCODE_1)
+ MENU_ITEM(function, USER_DESC_1, lcd_user_gcode_1);
+ #endif
+ #if defined(USER_DESC_2) && defined(USER_GCODE_2)
+ MENU_ITEM(function, USER_DESC_2, lcd_user_gcode_2);
+ #endif
+ #if defined(USER_DESC_3) && defined(USER_GCODE_3)
+ MENU_ITEM(function, USER_DESC_3, lcd_user_gcode_3);
+ #endif
+ #if defined(USER_DESC_4) && defined(USER_GCODE_4)
+ MENU_ITEM(function, USER_DESC_4, lcd_user_gcode_4);
+ #endif
+ #if defined(USER_DESC_5) && defined(USER_GCODE_5)
+ MENU_ITEM(function, USER_DESC_5, lcd_user_gcode_5);
+ #endif
+ END_MENU();
+ }
+
+ #endif
+
+ /**
+ *
+ * "Main" menu
+ *
+ */
+
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ void _lcd_goto_tune_menu() {
+ lcd_goto_screen(lcd_tune_menu);
+ new_z_fade_height = planner.z_fade_height;
+ }
+ #endif
+
+ void lcd_main_menu() {
+ START_MENU();
+ MENU_BACK(MSG_WATCH);
+
+ #if ENABLED(CUSTOM_USER_MENUS)
+ MENU_ITEM(submenu, MSG_USER_MENU, _lcd_user_menu);
+ #endif
+
+ //
+ // Debug Menu when certain options are enabled
+ //
+ #if HAS_DEBUG_MENU
+ MENU_ITEM(submenu, MSG_DEBUG_MENU, lcd_debug_menu);
+ #endif
+
+ //
+ // Set Case light on/off/brightness
+ //
+ #if ENABLED(MENU_ITEM_CASE_LIGHT)
+ if (USEABLE_HARDWARE_PWM(CASE_LIGHT_PIN)) {
+ MENU_ITEM(submenu, MSG_CASE_LIGHT, case_light_menu);
+ }
+ else
+ MENU_ITEM_EDIT_CALLBACK(bool, MSG_CASE_LIGHT, (bool*)&case_light_on, update_case_light);
+ #endif
+
+ if (planner.movesplanned() || IS_SD_PRINTING) {
+ MENU_ITEM(submenu, MSG_TUNE,
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ _lcd_goto_tune_menu
+ #else
+ lcd_tune_menu
+ #endif
+ );
+ }
+ else {
+ MENU_ITEM(submenu, MSG_PREPARE, lcd_prepare_menu);
+ }
+ MENU_ITEM(submenu, MSG_CONTROL, lcd_control_menu);
+
+ #if ENABLED(SDSUPPORT)
+ if (card.cardOK) {
+ if (card.isFileOpen()) {
+ if (card.sdprinting)
+ MENU_ITEM(function, MSG_PAUSE_PRINT, lcd_sdcard_pause);
+ else
+ MENU_ITEM(function, MSG_RESUME_PRINT, lcd_sdcard_resume);
+ MENU_ITEM(function, MSG_STOP_PRINT, lcd_sdcard_stop);
+ }
+ else {
+ MENU_ITEM(submenu, MSG_CARD_MENU, lcd_sdcard_menu);
+ #if !PIN_EXISTS(SD_DETECT)
+ MENU_ITEM(gcode, MSG_CNG_SDCARD, PSTR("M21")); // SD-card changed by user
+ #endif
+ }
+ }
+ else {
+ MENU_ITEM(submenu, MSG_NO_CARD, lcd_sdcard_menu);
+ #if !PIN_EXISTS(SD_DETECT)
+ MENU_ITEM(gcode, MSG_INIT_SDCARD, PSTR("M21")); // Manually initialize the SD-card via user interface
+ #endif
+ }
+ #endif // SDSUPPORT
+
+ #if ENABLED(LCD_INFO_MENU)
+ MENU_ITEM(submenu, MSG_INFO_MENU, lcd_info_menu);
+ #endif
+
+ #if ENABLED(LED_CONTROL_MENU)
+ MENU_ITEM(submenu, MSG_LED_CONTROL, lcd_led_menu);
+ #endif
+
+ END_MENU();
+ }
+
+ /**
+ *
+ * "Tune" submenu items
+ *
+ */
+
+ #if HAS_M206_COMMAND
+ /**
+ * Set the home offset based on the current_position
+ */
+ void lcd_set_home_offsets() {
+ // M428 Command
+ enqueue_and_echo_commands_P(PSTR("M428"));
+ lcd_return_to_status();
+ }
+ #endif
+
+ #if ENABLED(BABYSTEP_ZPROBE_GFX_OVERLAY) || ENABLED(MESH_EDIT_GFX_OVERLAY)
+
+ void _lcd_zoffset_overlay_gfx(const float zvalue) {
+ // Determine whether the user is raising or lowering the nozzle.
+ static int8_t dir;
+ static float old_zvalue;
+ if (zvalue != old_zvalue) {
+ dir = zvalue ? zvalue < old_zvalue ? -1 : 1 : 0;
+ old_zvalue = zvalue;
+ }
+
+ #if ENABLED(OVERLAY_GFX_REVERSE)
+ const unsigned char *rot_up = ccw_bmp, *rot_down = cw_bmp;
+ #else
+ const unsigned char *rot_up = cw_bmp, *rot_down = ccw_bmp;
+ #endif
+
+ #if ENABLED(USE_BIG_EDIT_FONT)
+ const int left = 0, right = 45, nozzle = 95;
+ #else
+ const int left = 5, right = 90, nozzle = 60;
+ #endif
+
+ // Draw a representation of the nozzle
+ if (PAGE_CONTAINS(3, 16)) u8g.drawBitmapP(nozzle + 6, 4 - dir, 2, 12, nozzle_bmp);
+ if (PAGE_CONTAINS(20, 20)) u8g.drawBitmapP(nozzle + 0, 20, 3, 1, offset_bedline_bmp);
+
+ // Draw cw/ccw indicator and up/down arrows.
+ if (PAGE_CONTAINS(47, 62)) {
+ u8g.drawBitmapP(left + 0, 47, 3, 16, rot_down);
+ u8g.drawBitmapP(right + 0, 47, 3, 16, rot_up);
+ u8g.drawBitmapP(right + 20, 48 - dir, 2, 13, up_arrow_bmp);
+ u8g.drawBitmapP(left + 20, 49 - dir, 2, 13, down_arrow_bmp);
+ }
+ }
+
+ #endif // BABYSTEP_ZPROBE_GFX_OVERLAY || MESH_EDIT_GFX_OVERLAY
+
+ #if ENABLED(BABYSTEPPING)
+
+ void _lcd_babystep(const AxisEnum axis, const char* msg) {
+ if (use_click()) { return lcd_goto_previous_menu_no_defer(); }
+ ENCODER_DIRECTION_NORMAL();
+ if (encoderPosition) {
+ const int16_t babystep_increment = (int32_t)encoderPosition * (BABYSTEP_MULTIPLICATOR);
+ encoderPosition = 0;
+ lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+ thermalManager.babystep_axis(axis, babystep_increment);
+ babysteps_done += babystep_increment;
+ }
+ if (lcdDrawUpdate)
+ lcd_implementation_drawedit(msg, ftostr43sign(planner.steps_to_mm[axis] * babysteps_done));
+ }
+
+ #if ENABLED(BABYSTEP_XY)
+ void _lcd_babystep_x() { _lcd_babystep(X_AXIS, PSTR(MSG_BABYSTEP_X)); }
+ void _lcd_babystep_y() { _lcd_babystep(Y_AXIS, PSTR(MSG_BABYSTEP_Y)); }
+ void lcd_babystep_x() { lcd_goto_screen(_lcd_babystep_x); babysteps_done = 0; defer_return_to_status = true; }
+ void lcd_babystep_y() { lcd_goto_screen(_lcd_babystep_y); babysteps_done = 0; defer_return_to_status = true; }
+ #endif
+
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+
+ void lcd_babystep_zoffset() {
+ if (use_click()) { return lcd_goto_previous_menu_no_defer(); }
+ defer_return_to_status = true;
+ ENCODER_DIRECTION_NORMAL();
+ if (encoderPosition) {
+ const int16_t babystep_increment = (int32_t)encoderPosition * (BABYSTEP_MULTIPLICATOR);
+ encoderPosition = 0;
+
+ const float new_zoffset = zprobe_zoffset + planner.steps_to_mm[Z_AXIS] * babystep_increment;
+ if (WITHIN(new_zoffset, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX)) {
+ thermalManager.babystep_axis(Z_AXIS, babystep_increment);
+ zprobe_zoffset = new_zoffset;
+ lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+ }
+ }
+ if (lcdDrawUpdate) {
+ lcd_implementation_drawedit(PSTR(MSG_ZPROBE_ZOFFSET), ftostr43sign(zprobe_zoffset));
+ #if ENABLED(BABYSTEP_ZPROBE_GFX_OVERLAY)
+ _lcd_zoffset_overlay_gfx(zprobe_zoffset);
+ #endif
+ }
+ }
+
+ #else // !BABYSTEP_ZPROBE_OFFSET
+
+ void _lcd_babystep_z() { _lcd_babystep(Z_AXIS, PSTR(MSG_BABYSTEP_Z)); }
+ void lcd_babystep_z() { lcd_goto_screen(_lcd_babystep_z); babysteps_done = 0; defer_return_to_status = true; }
+
+ #endif // !BABYSTEP_ZPROBE_OFFSET
+
+ #endif // BABYSTEPPING
+
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+
+ float mesh_edit_value, mesh_edit_accumulator; // We round mesh_edit_value to 2.5 decimal places. So we keep a
+ // separate value that doesn't lose precision.
+ static int16_t ubl_encoderPosition = 0;
+
+ static void _lcd_mesh_fine_tune(const char* msg) {
+ defer_return_to_status = true;
+ if (ubl.encoder_diff) {
+ ubl_encoderPosition = (ubl.encoder_diff > 0) ? 1 : -1;
+ ubl.encoder_diff = 0;
+
+ mesh_edit_accumulator += float(ubl_encoderPosition) * 0.005 / 2.0;
+ mesh_edit_value = mesh_edit_accumulator;
+ encoderPosition = 0;
+ lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+
+ const int32_t rounded = (int32_t)(mesh_edit_value * 1000.0);
+ mesh_edit_value = float(rounded - (rounded % 5L)) / 1000.0;
+ }
+
+ if (lcdDrawUpdate) {
+ lcd_implementation_drawedit(msg, ftostr43sign(mesh_edit_value));
+ #if ENABLED(MESH_EDIT_GFX_OVERLAY)
+ _lcd_zoffset_overlay_gfx(mesh_edit_value);
+ #endif
+ }
+ }
+
+ void _lcd_mesh_edit_NOP() {
+ defer_return_to_status = true;
+ }
+
+ float lcd_mesh_edit() {
+ lcd_goto_screen(_lcd_mesh_edit_NOP);
+ lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+ _lcd_mesh_fine_tune(PSTR("Mesh Editor"));
+ return mesh_edit_value;
+ }
+
+ void lcd_mesh_edit_setup(const float &initial) {
+ mesh_edit_value = mesh_edit_accumulator = initial;
+ lcd_goto_screen(_lcd_mesh_edit_NOP);
+ }
+
+ void _lcd_z_offset_edit() {
+ _lcd_mesh_fine_tune(PSTR("Z-Offset: "));
+ }
+
+ float lcd_z_offset_edit() {
+ lcd_goto_screen(_lcd_z_offset_edit);
+ return mesh_edit_value;
+ }
+
+ void lcd_z_offset_edit_setup(const float &initial) {
+ mesh_edit_value = mesh_edit_accumulator = initial;
+ lcd_goto_screen(_lcd_z_offset_edit);
+ }
+
+ #endif // AUTO_BED_LEVELING_UBL
+
+
+ /**
+ * Watch temperature callbacks
+ */
+ #if HAS_TEMP_HOTEND
+ #if WATCH_HOTENDS
+ #define _WATCH_FUNC(N) thermalManager.start_watching_heater(N)
+ #else
+ #define _WATCH_FUNC(N) NOOP
+ #endif
+ void watch_temp_callback_E0() { _WATCH_FUNC(0); }
+ #if HOTENDS > 1
+ void watch_temp_callback_E1() { _WATCH_FUNC(1); }
+ #if HOTENDS > 2
+ void watch_temp_callback_E2() { _WATCH_FUNC(2); }
+ #if HOTENDS > 3
+ void watch_temp_callback_E3() { _WATCH_FUNC(3); }
+ #if HOTENDS > 4
+ void watch_temp_callback_E4() { _WATCH_FUNC(4); }
+ #endif // HOTENDS > 4
+ #endif // HOTENDS > 3
+ #endif // HOTENDS > 2
+ #endif // HOTENDS > 1
+ #endif // HAS_TEMP_HOTEND
+
+ void watch_temp_callback_bed() {
+ #if WATCH_THE_BED
+ thermalManager.start_watching_bed();
+ #endif
+ }
+
+ // First Fan Speed title in "Tune" and "Control>Temperature" menus
+ #if FAN_COUNT > 0 && HAS_FAN0
+ #if FAN_COUNT > 1
+ #define FAN_SPEED_1_SUFFIX " 1"
+ #else
+ #define FAN_SPEED_1_SUFFIX ""
+ #endif
+ #endif
+
+ // Refresh the E factor after changing flow
+ inline void _lcd_refresh_e_factor_0() { planner.refresh_e_factor(0); }
+ #if EXTRUDERS > 1
+ inline void _lcd_refresh_e_factor() { planner.refresh_e_factor(active_extruder); }
+ inline void _lcd_refresh_e_factor_1() { planner.refresh_e_factor(1); }
+ #if EXTRUDERS > 2
+ inline void _lcd_refresh_e_factor_2() { planner.refresh_e_factor(2); }
+ #if EXTRUDERS > 3
+ inline void _lcd_refresh_e_factor_3() { planner.refresh_e_factor(3); }
+ #if EXTRUDERS > 4
+ inline void _lcd_refresh_e_factor_4() { planner.refresh_e_factor(4); }
+ #endif // EXTRUDERS > 4
+ #endif // EXTRUDERS > 3
+ #endif // EXTRUDERS > 2
+ #endif // EXTRUDERS > 1
+
+ /**
+ *
+ * "Tune" submenu
+ *
+ */
+ void lcd_tune_menu() {
+ START_MENU();
+
+ //
+ // ^ Main
+ //
+ MENU_BACK(MSG_MAIN);
+
+ //
+ // Speed:
+ //
+ MENU_ITEM_EDIT(int3, MSG_SPEED, &feedrate_percentage, 10, 999);
+
+ // Manual bed leveling, Bed Z:
+ #if ENABLED(MESH_BED_LEVELING) && ENABLED(LCD_BED_LEVELING)
+ MENU_ITEM_EDIT(float43, MSG_BED_Z, &mbl.z_offset, -1, 1);
+ #endif
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float3, MSG_Z_FADE_HEIGHT, &new_z_fade_height, 0.0, 100.0, _lcd_set_z_fade_height);
+ #endif
+ //
+ // Nozzle:
+ // Nozzle [1-4]:
+ //
+ #if HOTENDS == 1
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE, &thermalManager.target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0);
+ #else // HOTENDS > 1
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N1, &thermalManager.target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0);
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N2, &thermalManager.target_temperature[1], 0, HEATER_1_MAXTEMP - 15, watch_temp_callback_E1);
+ #if HOTENDS > 2
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N3, &thermalManager.target_temperature[2], 0, HEATER_2_MAXTEMP - 15, watch_temp_callback_E2);
+ #if HOTENDS > 3
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N4, &thermalManager.target_temperature[3], 0, HEATER_3_MAXTEMP - 15, watch_temp_callback_E3);
+ #if HOTENDS > 4
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N5, &thermalManager.target_temperature[4], 0, HEATER_4_MAXTEMP - 15, watch_temp_callback_E4);
+ #endif // HOTENDS > 4
+ #endif // HOTENDS > 3
+ #endif // HOTENDS > 2
+ #endif // HOTENDS > 1
+
+ //
+ // Bed:
+ //
+ #if HAS_TEMP_BED
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_BED, &thermalManager.target_temperature_bed, 0, BED_MAXTEMP - 15, watch_temp_callback_bed);
+ #endif
+
+#if ENABLED(FAN_AS_LASER)
+ MENU_ITEM(gcode, MSG_LASER_ON, PSTR("M3"));
+ MENU_ITEM(gcode, MSG_LASER_OFF, PSTR("M5"));
+#endif
+ //
+ // Fan Speed:
+ //
+ #if FAN_COUNT > 0
+ #if (HAS_FAN0 && FAN_NUM_AS_LASER!=0)
+ MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_FAN_SPEED FAN_SPEED_1_SUFFIX, &fanSpeeds[0], 0, 255);
+ #if ENABLED(EXTRA_FAN_SPEED)
+ MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_EXTRA_FAN_SPEED FAN_SPEED_1_SUFFIX, &new_fanSpeeds[0], 3, 255);
+ #endif
+ #endif
+ #if (HAS_FAN1 && FAN_NUM_AS_LASER!=1)
+ MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_FAN_SPEED " 2", &fanSpeeds[1], 0, 255);
+ #if ENABLED(EXTRA_FAN_SPEED)
+ MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_EXTRA_FAN_SPEED " 2", &new_fanSpeeds[1], 3, 255);
+ #endif
+ #endif
+ #if (HAS_FAN2 && FAN_NUM_AS_LASER!=2)
+ MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_FAN_SPEED " 3", &fanSpeeds[2], 0, 255);
+ #if ENABLED(EXTRA_FAN_SPEED)
+ MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_EXTRA_FAN_SPEED " 3", &new_fanSpeeds[2], 3, 255);
+ #endif
+ #endif
+ #endif // FAN_COUNT > 0
+
+ //
+ // Flow:
+ // Flow [1-5]:
+ //
+ #if EXTRUDERS == 1
+ MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW, &planner.flow_percentage[0], 10, 999, _lcd_refresh_e_factor_0);
+ #else // EXTRUDERS > 1
+ MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW, &planner.flow_percentage[active_extruder], 10, 999, _lcd_refresh_e_factor);
+ MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW MSG_N1, &planner.flow_percentage[0], 10, 999, _lcd_refresh_e_factor_0);
+ MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW MSG_N2, &planner.flow_percentage[1], 10, 999, _lcd_refresh_e_factor_1);
+ #if EXTRUDERS > 2
+ MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW MSG_N3, &planner.flow_percentage[2], 10, 999, _lcd_refresh_e_factor_2);
+ #if EXTRUDERS > 3
+ MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW MSG_N4, &planner.flow_percentage[3], 10, 999, _lcd_refresh_e_factor_3);
+ #if EXTRUDERS > 4
+ MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW MSG_N5, &planner.flow_percentage[4], 10, 999, _lcd_refresh_e_factor_4);
+ #endif // EXTRUDERS > 4
+ #endif // EXTRUDERS > 3
+ #endif // EXTRUDERS > 2
+ #endif // EXTRUDERS > 1
+
+ //
+ // Babystep X:
+ // Babystep Y:
+ // Babystep Z:
+ //
+ #if ENABLED(BABYSTEPPING)
+ #if ENABLED(BABYSTEP_XY)
+ MENU_ITEM(submenu, MSG_BABYSTEP_X, lcd_babystep_x);
+ MENU_ITEM(submenu, MSG_BABYSTEP_Y, lcd_babystep_y);
+ #endif
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ MENU_ITEM(submenu, MSG_ZPROBE_ZOFFSET, lcd_babystep_zoffset);
+ #else
+ MENU_ITEM(submenu, MSG_BABYSTEP_Z, lcd_babystep_z);
+ #endif
+ #endif
+
+ //
+ // Change filament
+ //
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ #if E_STEPPERS == 1 && !ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ if (thermalManager.targetHotEnoughToExtrude(active_extruder))
+ MENU_ITEM(gcode, MSG_FILAMENTCHANGE, PSTR("M600 B0"));
+ else
+ MENU_ITEM(submenu, MSG_FILAMENTCHANGE, lcd_temp_menu_e0_filament_change);
+ #else
+ MENU_ITEM(submenu, MSG_FILAMENTCHANGE, lcd_change_filament_menu);
+ #endif
+ #endif
+
+ END_MENU();
+ }
+
+ /**
+ *
+ * "Driver current control" submenu items
+ *
+ */
+ #if ENABLED(DAC_STEPPER_CURRENT)
+
+ void dac_driver_getValues() { LOOP_XYZE(i) driverPercent[i] = dac_current_get_percent((AxisEnum)i); }
+
+ void dac_driver_commit() { dac_current_set_percents(driverPercent); }
+
+ void dac_driver_eeprom_write() { dac_commit_eeprom(); }
+
+ void lcd_dac_menu() {
+ dac_driver_getValues();
+ START_MENU();
+ MENU_BACK(MSG_CONTROL);
+ MENU_ITEM_EDIT_CALLBACK(int8, MSG_X " " MSG_DAC_PERCENT, &driverPercent[X_AXIS], 0, 100, dac_driver_commit);
+ MENU_ITEM_EDIT_CALLBACK(int8, MSG_Y " " MSG_DAC_PERCENT, &driverPercent[Y_AXIS], 0, 100, dac_driver_commit);
+ MENU_ITEM_EDIT_CALLBACK(int8, MSG_Z " " MSG_DAC_PERCENT, &driverPercent[Z_AXIS], 0, 100, dac_driver_commit);
+ MENU_ITEM_EDIT_CALLBACK(int8, MSG_E " " MSG_DAC_PERCENT, &driverPercent[E_AXIS], 0, 100, dac_driver_commit);
+ MENU_ITEM(function, MSG_DAC_EEPROM_WRITE, dac_driver_eeprom_write);
+ END_MENU();
+ }
+
+ #endif // DAC_STEPPER_CURRENT
+
+ #if HAS_MOTOR_CURRENT_PWM
+
+ void lcd_pwm_menu() {
+ START_MENU();
+ MENU_BACK(MSG_CONTROL);
+ #if PIN_EXISTS(MOTOR_CURRENT_PWM_XY)
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_X MSG_Y, &stepper.motor_current_setting[0], 100, 2000, Stepper::refresh_motor_power);
+ #endif
+ #if PIN_EXISTS(MOTOR_CURRENT_PWM_Z)
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_Z, &stepper.motor_current_setting[1], 100, 2000, Stepper::refresh_motor_power);
+ #endif
+ #if PIN_EXISTS(MOTOR_CURRENT_PWM_E)
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_E, &stepper.motor_current_setting[2], 100, 2000, Stepper::refresh_motor_power);
+ #endif
+ END_MENU();
+ }
+
+ #endif // HAS_MOTOR_CURRENT_PWM
+
+ constexpr int16_t heater_maxtemp[HOTENDS] = ARRAY_BY_HOTENDS(HEATER_0_MAXTEMP, HEATER_1_MAXTEMP, HEATER_2_MAXTEMP, HEATER_3_MAXTEMP, HEATER_4_MAXTEMP);
+
+ /**
+ *
+ * "Prepare" submenu items
+ *
+ */
+ void _lcd_preheat(const int16_t endnum, const int16_t temph, const int16_t tempb, const int16_t fan) {
+ if (temph > 0) thermalManager.setTargetHotend(min(heater_maxtemp[endnum], temph), endnum);
+ #if TEMP_SENSOR_BED != 0
+ if (tempb >= 0) thermalManager.setTargetBed(tempb);
+ #else
+ UNUSED(tempb);
+ #endif
+ #if FAN_COUNT > 0
+ #if FAN_COUNT > 1
+ fanSpeeds[active_extruder < FAN_COUNT ? active_extruder : 0] = fan;
+ #else
+ fanSpeeds[0] = fan;
+ #endif
+ #else
+ UNUSED(fan);
+ #endif
+ lcd_return_to_status();
+ }
+
+ #if TEMP_SENSOR_0 != 0
+ void lcd_preheat_m1_e0_only() { _lcd_preheat(0, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); }
+ void lcd_preheat_m2_e0_only() { _lcd_preheat(0, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); }
+ #if TEMP_SENSOR_BED != 0
+ void lcd_preheat_m1_e0() { _lcd_preheat(0, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
+ void lcd_preheat_m2_e0() { _lcd_preheat(0, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
+ #endif
+ #endif
+
+ #if HOTENDS > 1
+ void lcd_preheat_m1_e1_only() { _lcd_preheat(1, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); }
+ void lcd_preheat_m2_e1_only() { _lcd_preheat(1, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); }
+ #if TEMP_SENSOR_BED != 0
+ void lcd_preheat_m1_e1() { _lcd_preheat(1, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
+ void lcd_preheat_m2_e1() { _lcd_preheat(1, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
+ #endif
+ #if HOTENDS > 2
+ void lcd_preheat_m1_e2_only() { _lcd_preheat(2, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); }
+ void lcd_preheat_m2_e2_only() { _lcd_preheat(2, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); }
+ #if TEMP_SENSOR_BED != 0
+ void lcd_preheat_m1_e2() { _lcd_preheat(2, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
+ void lcd_preheat_m2_e2() { _lcd_preheat(2, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
+ #endif
+ #if HOTENDS > 3
+ void lcd_preheat_m1_e3_only() { _lcd_preheat(3, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); }
+ void lcd_preheat_m2_e3_only() { _lcd_preheat(3, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); }
+ #if TEMP_SENSOR_BED != 0
+ void lcd_preheat_m1_e3() { _lcd_preheat(3, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
+ void lcd_preheat_m2_e3() { _lcd_preheat(3, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
+ #endif
+ #if HOTENDS > 4
+ void lcd_preheat_m1_e4_only() { _lcd_preheat(4, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); }
+ void lcd_preheat_m2_e4_only() { _lcd_preheat(4, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); }
+ #if TEMP_SENSOR_BED != 0
+ void lcd_preheat_m1_e4() { _lcd_preheat(4, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
+ void lcd_preheat_m2_e4() { _lcd_preheat(4, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
+ #endif
+ #endif // HOTENDS > 4
+ #endif // HOTENDS > 3
+ #endif // HOTENDS > 2
+
+ void lcd_preheat_m1_all() {
+ #if HOTENDS > 1
+ thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 1);
+ #if HOTENDS > 2
+ thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 2);
+ #if HOTENDS > 3
+ thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 3);
+ #if HOTENDS > 4
+ thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 4);
+ #endif // HOTENDS > 4
+ #endif // HOTENDS > 3
+ #endif // HOTENDS > 2
+ #endif // HOTENDS > 1
+ #if TEMP_SENSOR_BED != 0
+ lcd_preheat_m1_e0();
+ #else
+ lcd_preheat_m1_e0_only();
+ #endif
+ }
+ void lcd_preheat_m2_all() {
+ #if HOTENDS > 1
+ thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 1);
+ #if HOTENDS > 2
+ thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 2);
+ #if HOTENDS > 3
+ thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 3);
+ #if HOTENDS > 4
+ thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 4);
+ #endif // HOTENDS > 4
+ #endif // HOTENDS > 3
+ #endif // HOTENDS > 2
+ #endif // HOTENDS > 1
+ #if TEMP_SENSOR_BED != 0
+ lcd_preheat_m2_e0();
+ #else
+ lcd_preheat_m2_e0_only();
+ #endif
+ }
+
+ #endif // HOTENDS > 1
+
+ #if TEMP_SENSOR_BED != 0
+ void lcd_preheat_m1_bedonly() { _lcd_preheat(0, 0, lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
+ void lcd_preheat_m2_bedonly() { _lcd_preheat(0, 0, lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
+ #endif
+
+ #if TEMP_SENSOR_0 != 0 && (TEMP_SENSOR_1 != 0 || TEMP_SENSOR_2 != 0 || TEMP_SENSOR_3 != 0 || TEMP_SENSOR_4 != 0 || TEMP_SENSOR_BED != 0)
+
+ void lcd_preheat_m1_menu() {
+ START_MENU();
+ MENU_BACK(MSG_PREPARE);
+ #if HOTENDS == 1
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM(function, MSG_PREHEAT_1, lcd_preheat_m1_e0);
+ MENU_ITEM(function, MSG_PREHEAT_1_END, lcd_preheat_m1_e0_only);
+ #else
+ MENU_ITEM(function, MSG_PREHEAT_1, lcd_preheat_m1_e0_only);
+ #endif
+ #else
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H1, lcd_preheat_m1_e0);
+ MENU_ITEM(function, MSG_PREHEAT_1_END " " MSG_E1, lcd_preheat_m1_e0_only);
+ MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H2, lcd_preheat_m1_e1);
+ MENU_ITEM(function, MSG_PREHEAT_1_END " " MSG_E2, lcd_preheat_m1_e1_only);
+ #else
+ MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H1, lcd_preheat_m1_e0_only);
+ MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H2, lcd_preheat_m1_e1_only);
+ #endif
+ #if HOTENDS > 2
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H3, lcd_preheat_m1_e2);
+ MENU_ITEM(function, MSG_PREHEAT_1_END " " MSG_E3, lcd_preheat_m1_e2_only);
+ #else
+ MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H3, lcd_preheat_m1_e2_only);
+ #endif
+ #if HOTENDS > 3
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H4, lcd_preheat_m1_e3);
+ MENU_ITEM(function, MSG_PREHEAT_1_END " " MSG_E4, lcd_preheat_m1_e3_only);
+ #else
+ MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H4, lcd_preheat_m1_e3_only);
+ #endif
+ #if HOTENDS > 4
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H5, lcd_preheat_m1_e4);
+ MENU_ITEM(function, MSG_PREHEAT_1_END " " MSG_E5, lcd_preheat_m1_e4_only);
+ #else
+ MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H5, lcd_preheat_m1_e4_only);
+ #endif
+ #endif // HOTENDS > 4
+ #endif // HOTENDS > 3
+ #endif // HOTENDS > 2
+ MENU_ITEM(function, MSG_PREHEAT_1_ALL, lcd_preheat_m1_all);
+ #endif // HOTENDS > 1
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM(function, MSG_PREHEAT_1_BEDONLY, lcd_preheat_m1_bedonly);
+ #endif
+ END_MENU();
+ }
+
+ void lcd_preheat_m2_menu() {
+ START_MENU();
+ MENU_BACK(MSG_PREPARE);
+ #if HOTENDS == 1
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM(function, MSG_PREHEAT_2, lcd_preheat_m2_e0);
+ MENU_ITEM(function, MSG_PREHEAT_2_END, lcd_preheat_m2_e0_only);
+ #else
+ MENU_ITEM(function, MSG_PREHEAT_2, lcd_preheat_m2_e0_only);
+ #endif
+ #else
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H1, lcd_preheat_m2_e0);
+ MENU_ITEM(function, MSG_PREHEAT_2_END " " MSG_E1, lcd_preheat_m2_e0_only);
+ MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H2, lcd_preheat_m2_e1);
+ MENU_ITEM(function, MSG_PREHEAT_2_END " " MSG_E2, lcd_preheat_m2_e1_only);
+ #else
+ MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H1, lcd_preheat_m2_e0_only);
+ MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H2, lcd_preheat_m2_e1_only);
+ #endif
+ #if HOTENDS > 2
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H3, lcd_preheat_m2_e2);
+ MENU_ITEM(function, MSG_PREHEAT_2_END " " MSG_E3, lcd_preheat_m2_e2_only);
+ #else
+ MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H3, lcd_preheat_m2_e2_only);
+ #endif
+ #if HOTENDS > 3
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H4, lcd_preheat_m2_e3);
+ MENU_ITEM(function, MSG_PREHEAT_2_END " " MSG_E4, lcd_preheat_m2_e3_only);
+ #else
+ MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H4, lcd_preheat_m2_e3_only);
+ #endif
+ #if HOTENDS > 4
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H5, lcd_preheat_m2_e4);
+ MENU_ITEM(function, MSG_PREHEAT_2_END " " MSG_E5, lcd_preheat_m2_e4_only);
+ #else
+ MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H5, lcd_preheat_m2_e4_only);
+ #endif
+ #endif // HOTENDS > 4
+ #endif // HOTENDS > 3
+ #endif // HOTENDS > 2
+ MENU_ITEM(function, MSG_PREHEAT_2_ALL, lcd_preheat_m2_all);
+ #endif // HOTENDS > 1
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM(function, MSG_PREHEAT_2_BEDONLY, lcd_preheat_m2_bedonly);
+ #endif
+ END_MENU();
+ }
+
+ #endif // TEMP_SENSOR_0 && (TEMP_SENSOR_1 || TEMP_SENSOR_2 || TEMP_SENSOR_3 || TEMP_SENSOR_4 || TEMP_SENSOR_BED)
+
+ void lcd_cooldown() {
+ #if FAN_COUNT > 0
+ for (uint8_t i = 0; i < FAN_COUNT; i++) fanSpeeds[i] = 0;
+ #endif
+ thermalManager.disable_all_heaters();
+ lcd_return_to_status();
+ }
+
+ #if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(PID_AUTOTUNE_MENU) || ENABLED(ADVANCED_PAUSE_FEATURE)
+
+ /**
+ * If the queue is full, the command will fail, so we have to loop
+ * with idle() to make sure the command has been enqueued.
+ */
+ void lcd_enqueue_command(char * const cmd) {
+ no_reentry = true;
+ enqueue_and_echo_command_now(cmd);
+ no_reentry = false;
+ }
+
+ void lcd_enqueue_commands_P(const char * const cmd) {
+ no_reentry = true;
+ enqueue_and_echo_commands_P_now(cmd);
+ no_reentry = false;
+ }
+
+ #endif
+
+ #if ENABLED(SDSUPPORT) && ENABLED(MENU_ADDAUTOSTART)
+
+ void lcd_autostart_sd() {
+ card.autostart_index = 0;
+ card.setroot();
+ card.checkautostart(true);
+ }
+
+ #endif
+
+ #if ENABLED(EEPROM_SETTINGS)
+ static void lcd_store_settings() { lcd_completion_feedback(settings.save()); }
+ static void lcd_load_settings() { lcd_completion_feedback(settings.load()); }
+ #endif
+
+ #if ENABLED(LEVEL_BED_CORNERS)
+
+ /**
+ * Level corners, starting in the front-left corner.
+ */
+ static int8_t bed_corner;
+ void _lcd_goto_next_corner() {
+ line_to_z(4.0);
+ switch (bed_corner) {
+ case 0:
+ current_position[X_AXIS] = X_MIN_BED + 10;
+ current_position[Y_AXIS] = Y_MIN_BED + 10;
+ break;
+ case 1:
+ current_position[X_AXIS] = X_MAX_BED - 10;
+ break;
+ case 2:
+ current_position[Y_AXIS] = Y_MAX_BED - 10;
+ break;
+ case 3:
+ current_position[X_AXIS] = X_MIN_BED + 10;
+ break;
+ }
+ planner.buffer_line_kinematic(current_position, MMM_TO_MMS(manual_feedrate_mm_m[X_AXIS]), active_extruder);
+ line_to_z(0.0);
+ if (++bed_corner > 3) bed_corner = 0;
+ }
+
+ void _lcd_corner_submenu() {
+ START_MENU();
+ MENU_ITEM(function, MSG_NEXT_CORNER, _lcd_goto_next_corner);
+ MENU_ITEM(function, MSG_BACK, lcd_goto_previous_menu_no_defer);
+ END_MENU();
+ }
+
+ void _lcd_level_bed_corners() {
+ defer_return_to_status = true;
+ lcd_goto_screen(_lcd_corner_submenu);
+ bed_corner = 0;
+ _lcd_goto_next_corner();
+ }
+
+ #endif // LEVEL_BED_CORNERS
+
+ #if ENABLED(LCD_BED_LEVELING)
+
+ /**
+ *
+ * "Prepare" > "Level Bed" handlers
+ *
+ */
+
+ static uint8_t manual_probe_index;
+
+ // LCD probed points are from defaults
+ constexpr uint8_t total_probe_points = (
+ #if ENABLED(AUTO_BED_LEVELING_3POINT)
+ 3
+ #elif ABL_GRID || ENABLED(MESH_BED_LEVELING)
+ GRID_MAX_POINTS
+ #endif
+ );
+
+ bool lcd_wait_for_move;
+
+ //
+ // Bed leveling is done. Wait for G29 to complete.
+ // A flag is used so that this can release control
+ // and allow the command queue to be processed.
+ //
+ // When G29 finishes the last move:
+ // - Raise Z to the "manual probe height"
+ // - Don't return until done.
+ //
+ // ** This blocks the command queue! **
+ //
+ void _lcd_level_bed_done() {
+ if (!lcd_wait_for_move) {
+ #if MANUAL_PROBE_HEIGHT > 0 && DISABLED(MESH_BED_LEVELING)
+ // Display "Done" screen and wait for moves to complete
+ line_to_z(Z_MIN_POS + MANUAL_PROBE_HEIGHT);
+ lcd_synchronize(PSTR(MSG_LEVEL_BED_DONE));
+ #endif
+ lcd_goto_previous_menu_no_defer();
+ lcd_completion_feedback();
+ }
+ if (lcdDrawUpdate) lcd_implementation_drawmenu_static(LCD_HEIGHT >= 4 ? 1 : 0, PSTR(MSG_LEVEL_BED_DONE));
+ lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+ }
+
+ void _lcd_level_goto_next_point();
+
+ /**
+ * Step 7: Get the Z coordinate, click goes to the next point or exits
+ */
+ void _lcd_level_bed_get_z() {
+ ENCODER_DIRECTION_NORMAL();
+
+ if (use_click()) {
+
+ //
+ // Save the current Z position and move
+ //
+
+ // If done...
+ if (++manual_probe_index >= total_probe_points) {
+ //
+ // The last G29 records the point and enables bed leveling
+ //
+ lcd_wait_for_move = true;
+ lcd_goto_screen(_lcd_level_bed_done);
+ #if ENABLED(PROBE_MANUALLY)
+ enqueue_and_echo_commands_P(PSTR("G29 V1"));
+ #elif ENABLED(MESH_BED_LEVELING)
+ enqueue_and_echo_commands_P(PSTR("G29 S2"));
+ #endif
+ }
+ else
+ _lcd_level_goto_next_point();
+
+ return;
+ }
+
+ //
+ // Encoder knob or keypad buttons adjust the Z position
+ //
+ if (encoderPosition) {
+ const float z = current_position[Z_AXIS] + float((int32_t)encoderPosition) * (MBL_Z_STEP);
+ line_to_z(constrain(z, -(LCD_PROBE_Z_RANGE) * 0.5, (LCD_PROBE_Z_RANGE) * 0.5));
+ lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+ encoderPosition = 0;
+ }
+
+ //
+ // Draw on first display, then only on Z change
+ //
+ if (lcdDrawUpdate) {
+ const float v = current_position[Z_AXIS];
+ lcd_implementation_drawedit(PSTR(MSG_MOVE_Z), ftostr43sign(v + (v < 0 ? -0.0001 : 0.0001), '+'));
+ }
+ }
+
+ /**
+ * Step 6: Display "Next point: 1 / 9" while waiting for move to finish
+ */
+ void _lcd_level_bed_moving() {
+ if (lcdDrawUpdate) {
+ char msg[10];
+ sprintf_P(msg, PSTR("%i / %u"), (int)(manual_probe_index + 1), total_probe_points);
+ lcd_implementation_drawedit(PSTR(MSG_LEVEL_BED_NEXT_POINT), msg);
+ }
+ lcdDrawUpdate = LCDVIEW_CALL_NO_REDRAW;
+ if (!lcd_wait_for_move) lcd_goto_screen(_lcd_level_bed_get_z);
+ }
+
+ /**
+ * Step 5: Initiate a move to the next point
+ */
+ void _lcd_level_goto_next_point() {
+ lcd_goto_screen(_lcd_level_bed_moving);
+
+ // G29 Records Z, moves, and signals when it pauses
+ lcd_wait_for_move = true;
+ #if ENABLED(PROBE_MANUALLY)
+ enqueue_and_echo_commands_P(PSTR("G29 V1"));
+ #elif ENABLED(MESH_BED_LEVELING)
+ enqueue_and_echo_commands_P(manual_probe_index ? PSTR("G29 S2") : PSTR("G29 S1"));
+ #endif
+ }
+
+ /**
+ * Step 4: Display "Click to Begin", wait for click
+ * Move to the first probe position
+ */
+ void _lcd_level_bed_homing_done() {
+ if (lcdDrawUpdate) lcd_implementation_drawedit(PSTR(MSG_LEVEL_BED_WAITING));
+ if (use_click()) {
+ manual_probe_index = 0;
+ _lcd_level_goto_next_point();
+ }
+ }
+
+ /**
+ * Step 3: Display "Homing XYZ" - Wait for homing to finish
+ */
+ void _lcd_level_bed_homing() {
+ if (lcdDrawUpdate) lcd_implementation_drawedit(PSTR(MSG_LEVEL_BED_HOMING), NULL);
+ lcdDrawUpdate = LCDVIEW_CALL_NO_REDRAW;
+ if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS])
+ lcd_goto_screen(_lcd_level_bed_homing_done);
+ }
+
+ #if ENABLED(PROBE_MANUALLY)
+ extern bool g29_in_progress;
+ #endif
+
+ /**
+ * Step 2: Continue Bed Leveling...
+ */
+ void _lcd_level_bed_continue() {
+ defer_return_to_status = true;
+ axis_homed[X_AXIS] = axis_homed[Y_AXIS] = axis_homed[Z_AXIS] = false;
+ lcd_goto_screen(_lcd_level_bed_homing);
+ enqueue_and_echo_commands_P(PSTR("G28"));
+ }
+
+ static bool new_level_state;
+ void _lcd_toggle_bed_leveling() { set_bed_leveling_enabled(new_level_state); }
+
+ /**
+ * Step 1: Bed Level entry-point
+ *
+ * << Prepare
+ * Auto Home (if homing needed)
+ * Leveling On/Off (if data exists, and homed)
+ * Fade Height: --- (Req: ENABLE_LEVELING_FADE_HEIGHT)
+ * Mesh Z Offset: --- (Req: MESH_BED_LEVELING)
+ * Z Probe Offset: --- (Req: HAS_BED_PROBE, Opt: BABYSTEP_ZPROBE_OFFSET)
+ * Level Bed >
+ * Level Corners > (if homed)
+ * Load Settings (Req: EEPROM_SETTINGS)
+ * Save Settings (Req: EEPROM_SETTINGS)
+ */
+ void lcd_bed_leveling() {
+ START_MENU();
+ MENU_BACK(MSG_PREPARE);
+
+ #if DISABLED(MESH_BED_LEVELING)
+ if (!(axis_known_position[X_AXIS] && axis_known_position[Y_AXIS] && axis_known_position[Z_AXIS]))
+ MENU_ITEM(gcode, MSG_AUTO_HOME, PSTR("G28"));
+ else
+ #endif
+ if (leveling_is_valid()) {
+ new_level_state = planner.leveling_active;
+ MENU_ITEM_EDIT_CALLBACK(bool, MSG_BED_LEVELING, &new_level_state, _lcd_toggle_bed_leveling);
+ }
+
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_Z_FADE_HEIGHT, &new_z_fade_height, 0.0, 100.0, _lcd_set_z_fade_height);
+ #endif
+
+ //
+ // MBL Z Offset
+ //
+ #if ENABLED(MESH_BED_LEVELING)
+ MENU_ITEM_EDIT(float43, MSG_BED_Z, &mbl.z_offset, -1, 1);
+ #endif
+
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ MENU_ITEM(submenu, MSG_ZPROBE_ZOFFSET, lcd_babystep_zoffset);
+ #elif HAS_BED_PROBE
+ MENU_ITEM_EDIT(float32, MSG_ZPROBE_ZOFFSET, &zprobe_zoffset, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX);
+ #endif
+
+ MENU_ITEM(submenu, MSG_LEVEL_BED, _lcd_level_bed_continue);
+
+ #if ENABLED(LEVEL_BED_CORNERS)
+ // Move to the next corner for leveling
+ if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS])
+ MENU_ITEM(submenu, MSG_LEVEL_CORNERS, _lcd_level_bed_corners);
+ #endif
+
+ #if ENABLED(EEPROM_SETTINGS)
+ MENU_ITEM(function, MSG_LOAD_EEPROM, lcd_load_settings);
+ MENU_ITEM(function, MSG_STORE_EEPROM, lcd_store_settings);
+ #endif
+ END_MENU();
+ }
+
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ void _lcd_goto_bed_leveling() {
+ lcd_goto_screen(lcd_bed_leveling);
+ new_z_fade_height = planner.z_fade_height;
+ }
+ #endif
+
+ #elif ENABLED(AUTO_BED_LEVELING_UBL)
+
+ void _lcd_ubl_level_bed();
+
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ void _lcd_goto_ubl_level_bed() {
+ lcd_goto_screen(_lcd_ubl_level_bed);
+ new_z_fade_height = planner.z_fade_height;
+ }
+ #endif
+
+ static int16_t ubl_storage_slot = 0,
+ custom_hotend_temp = 190,
+ side_points = 3,
+ ubl_fillin_amount = 5,
+ ubl_height_amount = 1,
+ n_edit_pts = 1,
+ x_plot = 0,
+ y_plot = 0;
+
+ #if HAS_TEMP_BED
+ static int16_t custom_bed_temp = 50;
+ #endif
+
+ /**
+ * UBL Build Custom Mesh Command
+ */
+ void _lcd_ubl_build_custom_mesh() {
+ char UBL_LCD_GCODE[20];
+ enqueue_and_echo_commands_P(PSTR("G28"));
+ #if HAS_TEMP_BED
+ sprintf_P(UBL_LCD_GCODE, PSTR("M190 S%i"), custom_bed_temp);
+ lcd_enqueue_command(UBL_LCD_GCODE);
+ #endif
+ sprintf_P(UBL_LCD_GCODE, PSTR("M109 S%i"), custom_hotend_temp);
+ lcd_enqueue_command(UBL_LCD_GCODE);
+ enqueue_and_echo_commands_P(PSTR("G29 P1"));
+ }
+
+ /**
+ * UBL Custom Mesh submenu
+ *
+ * << Build Mesh
+ * Hotend Temp: ---
+ * Bed Temp: ---
+ * Build Custom Mesh
+ */
+ void _lcd_ubl_custom_mesh() {
+ START_MENU();
+ MENU_BACK(MSG_UBL_BUILD_MESH_MENU);
+ MENU_ITEM_EDIT(int3, MSG_UBL_CUSTOM_HOTEND_TEMP, &custom_hotend_temp, EXTRUDE_MINTEMP, (HEATER_0_MAXTEMP - 10));
+ #if HAS_TEMP_BED
+ MENU_ITEM_EDIT(int3, MSG_UBL_CUSTOM_BED_TEMP, &custom_bed_temp, BED_MINTEMP, (BED_MAXTEMP - 15));
+ #endif
+ MENU_ITEM(function, MSG_UBL_BUILD_CUSTOM_MESH, _lcd_ubl_build_custom_mesh);
+ END_MENU();
+ }
+
+ /**
+ * UBL Adjust Mesh Height Command
+ */
+ void _lcd_ubl_adjust_height_cmd() {
+ char UBL_LCD_GCODE[16];
+ const int ind = ubl_height_amount > 0 ? 9 : 10;
+ strcpy_P(UBL_LCD_GCODE, PSTR("G29 P6 C -"));
+ sprintf_P(&UBL_LCD_GCODE[ind], PSTR(".%i"), abs(ubl_height_amount));
+ lcd_enqueue_command(UBL_LCD_GCODE);
+ }
+
+ /**
+ * UBL Adjust Mesh Height submenu
+ *
+ * << Edit Mesh
+ * Height Amount: ---
+ * Adjust Mesh Height
+ * << Info Screen
+ */
+ void _lcd_ubl_height_adjust_menu() {
+ START_MENU();
+ MENU_BACK(MSG_UBL_EDIT_MESH_MENU);
+ MENU_ITEM_EDIT_CALLBACK(int3, MSG_UBL_MESH_HEIGHT_AMOUNT, &ubl_height_amount, -9, 9, _lcd_ubl_adjust_height_cmd);
+ MENU_ITEM(function, MSG_WATCH, lcd_return_to_status);
+ END_MENU();
+ }
+
+ /**
+ * UBL Edit Mesh submenu
+ *
+ * << UBL Tools
+ * Fine Tune All
+ * Fine Tune Closest
+ * - Adjust Mesh Height >>
+ * << Info Screen
+ */
+ void _lcd_ubl_edit_mesh() {
+ START_MENU();
+ MENU_BACK(MSG_UBL_TOOLS);
+ MENU_ITEM(gcode, MSG_UBL_FINE_TUNE_ALL, PSTR("G29 P4 R999 T"));
+ MENU_ITEM(gcode, MSG_UBL_FINE_TUNE_CLOSEST, PSTR("G29 P4 T"));
+ MENU_ITEM(submenu, MSG_UBL_MESH_HEIGHT_ADJUST, _lcd_ubl_height_adjust_menu);
+ MENU_ITEM(function, MSG_WATCH, lcd_return_to_status);
+ END_MENU();
+ }
+
+ /**
+ * UBL Validate Custom Mesh Command
+ */
+ void _lcd_ubl_validate_custom_mesh() {
+ char UBL_LCD_GCODE[24];
+ const int temp =
+ #if HAS_TEMP_BED
+ custom_bed_temp
+ #else
+ 0
+ #endif
+ ;
+ sprintf_P(UBL_LCD_GCODE, PSTR("G26 C B%i H%i P"), temp, custom_hotend_temp);
+ lcd_enqueue_commands_P(PSTR("G28"));
+ lcd_enqueue_command(UBL_LCD_GCODE);
+ }
+
+ /**
+ * UBL Validate Mesh submenu
+ *
+ * << UBL Tools
+ * PLA Mesh Validation
+ * ABS Mesh Validation
+ * Validate Custom Mesh
+ * << Info Screen
+ */
+ void _lcd_ubl_validate_mesh() {
+ START_MENU();
+ MENU_BACK(MSG_UBL_TOOLS);
+ #if HAS_TEMP_BED
+ MENU_ITEM(gcode, MSG_UBL_VALIDATE_PLA_MESH, PSTR("G28\nG26 C B" STRINGIFY(PREHEAT_1_TEMP_BED) " H" STRINGIFY(PREHEAT_1_TEMP_HOTEND) " P"));
+ MENU_ITEM(gcode, MSG_UBL_VALIDATE_ABS_MESH, PSTR("G28\nG26 C B" STRINGIFY(PREHEAT_2_TEMP_BED) " H" STRINGIFY(PREHEAT_2_TEMP_HOTEND) " P"));
+ #else
+ MENU_ITEM(gcode, MSG_UBL_VALIDATE_PLA_MESH, PSTR("G28\nG26 C B0 H" STRINGIFY(PREHEAT_1_TEMP_HOTEND) " P"));
+ MENU_ITEM(gcode, MSG_UBL_VALIDATE_ABS_MESH, PSTR("G28\nG26 C B0 H" STRINGIFY(PREHEAT_2_TEMP_HOTEND) " P"));
+ #endif
+ MENU_ITEM(function, MSG_UBL_VALIDATE_CUSTOM_MESH, _lcd_ubl_validate_custom_mesh);
+ MENU_ITEM(function, MSG_WATCH, lcd_return_to_status);
+ END_MENU();
+ }
+
+ /**
+ * UBL Grid Leveling Command
+ */
+ void _lcd_ubl_grid_level_cmd() {
+ char UBL_LCD_GCODE[10];
+ sprintf_P(UBL_LCD_GCODE, PSTR("G29 J%i"), side_points);
+ lcd_enqueue_command(UBL_LCD_GCODE);
+ }
+
+ /**
+ * UBL Grid Leveling submenu
+ *
+ * << UBL Tools
+ * Side points: ---
+ * Level Mesh
+ */
+ void _lcd_ubl_grid_level() {
+ START_MENU();
+ MENU_BACK(MSG_UBL_TOOLS);
+ MENU_ITEM_EDIT(int3, MSG_UBL_SIDE_POINTS, &side_points, 2, 6);
+ MENU_ITEM(function, MSG_UBL_MESH_LEVEL, _lcd_ubl_grid_level_cmd);
+ END_MENU();
+ }
+
+ /**
+ * UBL Mesh Leveling submenu
+ *
+ * << UBL Tools
+ * 3-Point Mesh Leveling
+ * - Grid Mesh Leveling >>
+ * << Info Screen
+ */
+ void _lcd_ubl_mesh_leveling() {
+ START_MENU();
+ MENU_BACK(MSG_UBL_TOOLS);
+ MENU_ITEM(gcode, MSG_UBL_3POINT_MESH_LEVELING, PSTR("G29 J0"));
+ MENU_ITEM(submenu, MSG_UBL_GRID_MESH_LEVELING, _lcd_ubl_grid_level);
+ MENU_ITEM(function, MSG_WATCH, lcd_return_to_status);
+ END_MENU();
+ }
+
+ /**
+ * UBL Fill-in Amount Mesh Command
+ */
+ void _lcd_ubl_fillin_amount_cmd() {
+ char UBL_LCD_GCODE[16];
+ sprintf_P(UBL_LCD_GCODE, PSTR("G29 P3 R C.%i"), ubl_fillin_amount);
+ lcd_enqueue_command(UBL_LCD_GCODE);
+ }
+
+ /**
+ * UBL Fill-in Mesh submenu
+ *
+ * << Build Mesh
+ * Fill-in Amount: ---
+ * Fill-in Mesh
+ * Smart Fill-in
+ * Manual Fill-in
+ * << Info Screen
+ */
+ void _lcd_ubl_fillin_menu() {
+ START_MENU();
+ MENU_BACK(MSG_UBL_BUILD_MESH_MENU);
+ MENU_ITEM_EDIT_CALLBACK(int3, MSG_UBL_FILLIN_AMOUNT, &ubl_fillin_amount, 0, 9, _lcd_ubl_fillin_amount_cmd);
+ MENU_ITEM(gcode, MSG_UBL_SMART_FILLIN, PSTR("G29 P3 T0"));
+ MENU_ITEM(gcode, MSG_UBL_MANUAL_FILLIN, PSTR("G29 P2 B T0"));
+ MENU_ITEM(function, MSG_WATCH, lcd_return_to_status);
+ END_MENU();
+ }
+
+ void _lcd_ubl_invalidate() {
+ ubl.invalidate();
+ SERIAL_PROTOCOLLNPGM("Mesh invalidated.");
+ }
+
+ /**
+ * UBL Build Mesh submenu
+ *
+ * << UBL Tools
+ * Build PLA Mesh
+ * Build ABS Mesh
+ * - Build Custom Mesh >>
+ * Build Cold Mesh
+ * - Fill-in Mesh >>
+ * Continue Bed Mesh
+ * Invalidate All
+ * Invalidate Closest
+ * << Info Screen
+ */
+ void _lcd_ubl_build_mesh() {
+ START_MENU();
+ MENU_BACK(MSG_UBL_TOOLS);
+ #if HAS_TEMP_BED
+ MENU_ITEM(gcode, MSG_UBL_BUILD_PLA_MESH, PSTR(
+ "G28\n"
+ "M190 S" STRINGIFY(PREHEAT_1_TEMP_BED) "\n"
+ "M109 S" STRINGIFY(PREHEAT_1_TEMP_HOTEND) "\n"
+ "G29 P1\n"
+ "M104 S0\n"
+ "M140 S0"
+ ));
+ MENU_ITEM(gcode, MSG_UBL_BUILD_ABS_MESH, PSTR(
+ "G28\n"
+ "M190 S" STRINGIFY(PREHEAT_2_TEMP_BED) "\n"
+ "M109 S" STRINGIFY(PREHEAT_2_TEMP_HOTEND) "\n"
+ "G29 P1\n"
+ "M104 S0\n"
+ "M140 S0"
+ ));
+ #else
+ MENU_ITEM(gcode, MSG_UBL_BUILD_PLA_MESH, PSTR(
+ "G28\n"
+ "M109 S" STRINGIFY(PREHEAT_1_TEMP_HOTEND) "\n"
+ "G29 P1\n"
+ "M104 S0"
+ ));
+ MENU_ITEM(gcode, MSG_UBL_BUILD_ABS_MESH, PSTR(
+ "G28\n"
+ "M109 S" STRINGIFY(PREHEAT_2_TEMP_HOTEND) "\n"
+ "G29 P1\n"
+ "M104 S0"
+ ));
+ #endif
+ MENU_ITEM(submenu, MSG_UBL_BUILD_CUSTOM_MESH, _lcd_ubl_custom_mesh);
+ MENU_ITEM(gcode, MSG_UBL_BUILD_COLD_MESH, PSTR("G28\nG29 P1"));
+ MENU_ITEM(submenu, MSG_UBL_FILLIN_MESH, _lcd_ubl_fillin_menu);
+ MENU_ITEM(gcode, MSG_UBL_CONTINUE_MESH, PSTR("G29 P1 C"));
+ MENU_ITEM(function, MSG_UBL_INVALIDATE_ALL, _lcd_ubl_invalidate);
+ MENU_ITEM(gcode, MSG_UBL_INVALIDATE_CLOSEST, PSTR("G29 I"));
+ MENU_ITEM(function, MSG_WATCH, lcd_return_to_status);
+ END_MENU();
+ }
+
+ /**
+ * UBL Load Mesh Command
+ */
+ void _lcd_ubl_load_mesh_cmd() {
+ char UBL_LCD_GCODE[25];
+ sprintf_P(UBL_LCD_GCODE, PSTR("G29 L%i"), ubl_storage_slot);
+ lcd_enqueue_command(UBL_LCD_GCODE);
+ sprintf_P(UBL_LCD_GCODE, PSTR("M117 " MSG_MESH_LOADED), ubl_storage_slot);
+ lcd_enqueue_command(UBL_LCD_GCODE);
+ }
+
+ /**
+ * UBL Save Mesh Command
+ */
+ void _lcd_ubl_save_mesh_cmd() {
+ char UBL_LCD_GCODE[25];
+ sprintf_P(UBL_LCD_GCODE, PSTR("G29 S%i"), ubl_storage_slot);
+ lcd_enqueue_command(UBL_LCD_GCODE);
+ sprintf_P(UBL_LCD_GCODE, PSTR("M117 " MSG_MESH_SAVED), ubl_storage_slot);
+ lcd_enqueue_command(UBL_LCD_GCODE);
+ }
+
+ /**
+ * UBL Mesh Storage submenu
+ *
+ * << Unified Bed Leveling
+ * Memory Slot: ---
+ * Load Bed Mesh
+ * Save Bed Mesh
+ */
+ void _lcd_ubl_storage_mesh() {
+ int16_t a = settings.calc_num_meshes();
+ START_MENU();
+ MENU_BACK(MSG_UBL_LEVEL_BED);
+ if (!WITHIN(ubl_storage_slot, 0, a - 1)) {
+ STATIC_ITEM(MSG_NO_STORAGE);
+ }
+ else {
+ MENU_ITEM_EDIT(int3, MSG_UBL_STORAGE_SLOT, &ubl_storage_slot, 0, a - 1);
+ MENU_ITEM(function, MSG_UBL_LOAD_MESH, _lcd_ubl_load_mesh_cmd);
+ MENU_ITEM(function, MSG_UBL_SAVE_MESH, _lcd_ubl_save_mesh_cmd);
+ }
+ END_MENU();
+ }
+
+ /**
+ * UBL LCD "radar" map homing
+ */
+ void _lcd_ubl_output_map_lcd();
+
+ void _lcd_ubl_map_homing() {
+ defer_return_to_status = true;
+ if (lcdDrawUpdate) lcd_implementation_drawmenu_static(LCD_HEIGHT < 3 ? 0 : (LCD_HEIGHT > 4 ? 2 : 1), PSTR(MSG_LEVEL_BED_HOMING));
+ lcdDrawUpdate = LCDVIEW_CALL_NO_REDRAW;
+ if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS]) {
+ ubl.lcd_map_control = true; // Return to the map screen
+ lcd_goto_screen(_lcd_ubl_output_map_lcd);
+ }
+ }
+
+ /**
+ * UBL LCD "radar" map point editing
+ */
+ void _lcd_ubl_map_lcd_edit_cmd() {
+ char UBL_LCD_GCODE[50], str[10], str2[10];
+ dtostrf(pgm_read_float(&ubl._mesh_index_to_xpos[x_plot]), 0, 2, str);
+ dtostrf(pgm_read_float(&ubl._mesh_index_to_ypos[y_plot]), 0, 2, str2);
+ snprintf_P(UBL_LCD_GCODE, sizeof(UBL_LCD_GCODE), PSTR("G29 P4 X%s Y%s R%i"), str, str2, n_edit_pts);
+ lcd_enqueue_command(UBL_LCD_GCODE);
+ }
+
+ /**
+ * UBL LCD Map Movement
+ */
+ void ubl_map_move_to_xy() {
+ current_position[X_AXIS] = pgm_read_float(&ubl._mesh_index_to_xpos[x_plot]);
+ current_position[Y_AXIS] = pgm_read_float(&ubl._mesh_index_to_ypos[y_plot]);
+ planner.buffer_line_kinematic(current_position, MMM_TO_MMS(XY_PROBE_SPEED), active_extruder);
+ }
+
+ /**
+ * UBL LCD "radar" map
+ */
+ void set_current_from_steppers_for_axis(const AxisEnum axis);
+
+ void _lcd_do_nothing() {}
+ void _lcd_hard_stop() {
+ stepper.quick_stop();
+ const screenFunc_t old_screen = currentScreen;
+ currentScreen = _lcd_do_nothing;
+ while (planner.movesplanned()) idle();
+ currentScreen = old_screen;
+ stepper.cleaning_buffer_counter = 0;
+ set_current_from_steppers_for_axis(ALL_AXES);
+ sync_plan_position();
+ }
+
+ void _lcd_ubl_output_map_lcd() {
+ static int16_t step_scaler = 0;
+
+ if (!(axis_known_position[X_AXIS] && axis_known_position[Y_AXIS] && axis_known_position[Z_AXIS]))
+ return lcd_goto_screen(_lcd_ubl_map_homing);
+
+ if (use_click()) return _lcd_ubl_map_lcd_edit_cmd();
+ ENCODER_DIRECTION_NORMAL();
+
+ if (encoderPosition) {
+ step_scaler += (int32_t)encoderPosition;
+ x_plot += step_scaler / (ENCODER_STEPS_PER_MENU_ITEM);
+ if (abs(step_scaler) >= ENCODER_STEPS_PER_MENU_ITEM) step_scaler = 0;
+ encoderPosition = 0;
+ lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+ }
+
+ // Encoder to the right (++)
+ if (x_plot >= GRID_MAX_POINTS_X) { x_plot = 0; y_plot++; }
+ if (y_plot >= GRID_MAX_POINTS_Y) y_plot = 0;
+
+ // Encoder to the left (--)
+ if (x_plot <= GRID_MAX_POINTS_X - (GRID_MAX_POINTS_X + 1)) { x_plot = GRID_MAX_POINTS_X - 1; y_plot--; }
+ if (y_plot <= GRID_MAX_POINTS_Y - (GRID_MAX_POINTS_Y + 1)) y_plot = GRID_MAX_POINTS_Y - 1;
+
+ // Prevent underrun/overrun of plot numbers
+ x_plot = constrain(x_plot, GRID_MAX_POINTS_X - (GRID_MAX_POINTS_X + 1), GRID_MAX_POINTS_X + 1);
+ y_plot = constrain(y_plot, GRID_MAX_POINTS_Y - (GRID_MAX_POINTS_Y + 1), GRID_MAX_POINTS_Y + 1);
+
+ // Determine number of points to edit
+ #if IS_KINEMATIC
+ n_edit_pts = 9; //TODO: Delta accessible edit points
+ #else
+ const bool xc = WITHIN(x_plot, 1, GRID_MAX_POINTS_X - 2),
+ yc = WITHIN(y_plot, 1, GRID_MAX_POINTS_Y - 2);
+ n_edit_pts = yc ? (xc ? 9 : 6) : (xc ? 6 : 4); // Corners
+ #endif
+
+ if (lcdDrawUpdate) {
+ lcd_implementation_ubl_plot(x_plot, y_plot);
+
+ if (planner.movesplanned()) // If the nozzle is already moving, cancel the move.
+ _lcd_hard_stop();
+
+ ubl_map_move_to_xy(); // Move to new location
+ }
+ }
+
+ /**
+ * UBL Homing before LCD map
+ */
+ void _lcd_ubl_output_map_lcd_cmd() {
+ if (!(axis_known_position[X_AXIS] && axis_known_position[Y_AXIS] && axis_known_position[Z_AXIS])) {
+ axis_homed[X_AXIS] = axis_homed[Y_AXIS] = axis_homed[Z_AXIS] = false;
+ enqueue_and_echo_commands_P(PSTR("G28"));
+ }
+ lcd_goto_screen(_lcd_ubl_map_homing);
+ }
+
+ /**
+ * UBL Output map submenu
+ *
+ * << Unified Bed Leveling
+ * Output for Host
+ * Output for CSV
+ * Off Printer Backup
+ * Output Mesh Map
+ */
+ void _lcd_ubl_output_map() {
+ START_MENU();
+ MENU_BACK(MSG_UBL_LEVEL_BED);
+ MENU_ITEM(gcode, MSG_UBL_OUTPUT_MAP_HOST, PSTR("G29 T0"));
+ MENU_ITEM(gcode, MSG_UBL_OUTPUT_MAP_CSV, PSTR("G29 T1"));
+ MENU_ITEM(gcode, MSG_UBL_OUTPUT_MAP_BACKUP, PSTR("G29 S-1"));
+ MENU_ITEM(function, MSG_UBL_OUTPUT_MAP, _lcd_ubl_output_map_lcd_cmd);
+ END_MENU();
+ }
+
+ /**
+ * UBL Tools submenu
+ *
+ * << Unified Bed Leveling
+ * - Build Mesh >>
+ * - Validate Mesh >>
+ * - Edit Mesh >>
+ * - Mesh Leveling >>
+ */
+ void _lcd_ubl_tools_menu() {
+ START_MENU();
+ MENU_BACK(MSG_UBL_LEVEL_BED);
+ MENU_ITEM(submenu, MSG_UBL_BUILD_MESH_MENU, _lcd_ubl_build_mesh);
+ MENU_ITEM(gcode, MSG_UBL_MANUAL_MESH, PSTR("G29 I999\nG29 P2 B T0"));
+ MENU_ITEM(submenu, MSG_UBL_VALIDATE_MESH_MENU, _lcd_ubl_validate_mesh);
+ MENU_ITEM(submenu, MSG_UBL_EDIT_MESH_MENU, _lcd_ubl_edit_mesh);
+ MENU_ITEM(submenu, MSG_UBL_MESH_LEVELING, _lcd_ubl_mesh_leveling);
+ END_MENU();
+ }
+
+ /**
+ * UBL Step-By-Step submenu
+ *
+ * << Unified Bed Leveling
+ * 1 Build Cold Mesh
+ * 2 Smart Fill-in
+ * - 3 Validate Mesh >>
+ * 4 Fine Tune All
+ * - 5 Validate Mesh >>
+ * 6 Fine Tune All
+ * 7 Save Bed Mesh
+ */
+ void _lcd_ubl_step_by_step() {
+ START_MENU();
+ MENU_BACK(MSG_UBL_LEVEL_BED);
+ MENU_ITEM(gcode, "1 " MSG_UBL_BUILD_COLD_MESH, PSTR("G28\nG29 P1"));
+ MENU_ITEM(gcode, "2 " MSG_UBL_SMART_FILLIN, PSTR("G29 P3 T0"));
+ MENU_ITEM(submenu, "3 " MSG_UBL_VALIDATE_MESH_MENU, _lcd_ubl_validate_mesh);
+ MENU_ITEM(gcode, "4 " MSG_UBL_FINE_TUNE_ALL, PSTR("G29 P4 R999 T"));
+ MENU_ITEM(submenu, "5 " MSG_UBL_VALIDATE_MESH_MENU, _lcd_ubl_validate_mesh);
+ MENU_ITEM(gcode, "6 " MSG_UBL_FINE_TUNE_ALL, PSTR("G29 P4 R999 T"));
+ MENU_ITEM(function, "7 " MSG_UBL_SAVE_MESH, _lcd_ubl_save_mesh_cmd);
+ END_MENU();
+ }
+
+ /**
+ * UBL System submenu
+ *
+ * << Prepare
+ * - Manually Build Mesh >>
+ * - Activate UBL >>
+ * - Deactivate UBL >>
+ * - Step-By-Step UBL >>
+ * - Mesh Storage >>
+ * - Output Map >>
+ * - UBL Tools >>
+ * - Output UBL Info >>
+ */
+
+ void _lcd_ubl_level_bed() {
+ START_MENU();
+ MENU_BACK(MSG_PREPARE);
+ MENU_ITEM(gcode, MSG_UBL_ACTIVATE_MESH, PSTR("G29 A"));
+ MENU_ITEM(gcode, MSG_UBL_DEACTIVATE_MESH, PSTR("G29 D"));
+ MENU_ITEM(submenu, MSG_UBL_STEP_BY_STEP_MENU, _lcd_ubl_step_by_step);
+ MENU_ITEM(function, MSG_UBL_MESH_EDIT, _lcd_ubl_output_map_lcd_cmd);
+ MENU_ITEM(submenu, MSG_UBL_STORAGE_MESH_MENU, _lcd_ubl_storage_mesh);
+ MENU_ITEM(submenu, MSG_UBL_OUTPUT_MAP, _lcd_ubl_output_map);
+ MENU_ITEM(submenu, MSG_UBL_TOOLS, _lcd_ubl_tools_menu);
+ MENU_ITEM(gcode, MSG_UBL_INFO_UBL, PSTR("G29 W"));
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float3, MSG_Z_FADE_HEIGHT, &new_z_fade_height, 0.0, 100.0, _lcd_set_z_fade_height);
+ #endif
+ END_MENU();
+ }
+
+ #endif // AUTO_BED_LEVELING_UBL
+
+ /**
+ *
+ * "Prepare" submenu
+ *
+ */
+
+ void lcd_prepare_menu() {
+ START_MENU();
+
+ //
+ // ^ Main
+ //
+ MENU_BACK(MSG_MAIN);
+
+ //
+ // Move Axis
+ //
+ #if ENABLED(DELTA)
+ if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS])
+ #endif
+ MENU_ITEM(submenu, MSG_MOVE_AXIS, lcd_move_menu);
+
+ //
+ // Auto Home
+ //
+ MENU_ITEM(gcode, MSG_AUTO_HOME, PSTR("G28"));
+ #if ENABLED(INDIVIDUAL_AXIS_HOMING_MENU)
+ MENU_ITEM(gcode, MSG_AUTO_HOME_X, PSTR("G28 X"));
+ MENU_ITEM(gcode, MSG_AUTO_HOME_Y, PSTR("G28 Y"));
+ MENU_ITEM(gcode, MSG_AUTO_HOME_Z, PSTR("G28 Z"));
+ #endif
+
+ //
+ // Level Bed
+ //
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+ MENU_ITEM(submenu, MSG_UBL_LEVEL_BED,
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ _lcd_goto_ubl_level_bed
+ #else
+ _lcd_ubl_level_bed
+ #endif
+ );
+ #elif ENABLED(LCD_BED_LEVELING)
+ #if ENABLED(PROBE_MANUALLY)
+ if (!g29_in_progress)
+ #endif
+ MENU_ITEM(submenu, MSG_BED_LEVELING,
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ _lcd_goto_bed_leveling
+ #else
+ lcd_bed_leveling
+ #endif
+ );
+ #elif PLANNER_LEVELING && DISABLED(PROBE_MANUALLY) && DISABLED(SLIM_LCD_MENUS)
+ MENU_ITEM(gcode, MSG_BED_LEVELING, PSTR("G28\nG29"));
+ #endif
+
+ #if ENABLED(LEVEL_BED_CORNERS) && DISABLED(LCD_BED_LEVELING)
+ if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS])
+ MENU_ITEM(function, MSG_LEVEL_CORNERS, _lcd_level_bed_corners);
+ #endif
+
+ #if HAS_M206_COMMAND && DISABLED(SLIM_LCD_MENUS)
+ //
+ // Set Home Offsets
+ //
+ MENU_ITEM(function, MSG_SET_HOME_OFFSETS, lcd_set_home_offsets);
+ #endif
+
+ //
+ // Disable Steppers
+ //
+ MENU_ITEM(gcode, MSG_DISABLE_STEPPERS, PSTR("M84"));
+
+ //
+ // Change filament
+ //
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ if (!IS_SD_FILE_OPEN) {
+ #if E_STEPPERS == 1 && !ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ if (thermalManager.targetHotEnoughToExtrude(active_extruder))
+ MENU_ITEM(gcode, MSG_FILAMENTCHANGE, PSTR("M600 B0"));
+ else
+ MENU_ITEM(submenu, MSG_FILAMENTCHANGE, lcd_temp_menu_e0_filament_change);
+ #else
+ MENU_ITEM(submenu, MSG_FILAMENTCHANGE, lcd_change_filament_menu);
+ #endif
+ }
+ #endif // ADVANCED_PAUSE_FEATURE
+
+ #if TEMP_SENSOR_0 != 0
+
+ //
+ // Cooldown
+ //
+ bool has_heat = false;
+ HOTEND_LOOP() if (thermalManager.target_temperature[HOTEND_INDEX]) { has_heat = true; break; }
+ #if HAS_TEMP_BED
+ if (thermalManager.target_temperature_bed) has_heat = true;
+ #endif
+ if (has_heat) MENU_ITEM(function, MSG_COOLDOWN, lcd_cooldown);
+
+ //
+ // Preheat for Material 1 and 2
+ //
+ #if TEMP_SENSOR_1 != 0 || TEMP_SENSOR_2 != 0 || TEMP_SENSOR_3 != 0 || TEMP_SENSOR_4 != 0 || TEMP_SENSOR_BED != 0
+ MENU_ITEM(submenu, MSG_PREHEAT_1, lcd_preheat_m1_menu);
+ MENU_ITEM(submenu, MSG_PREHEAT_2, lcd_preheat_m2_menu);
+ #else
+ MENU_ITEM(function, MSG_PREHEAT_1, lcd_preheat_m1_e0_only);
+ MENU_ITEM(function, MSG_PREHEAT_2, lcd_preheat_m2_e0_only);
+ #endif
+
+ #endif // TEMP_SENSOR_0 != 0
+
+ //
+ // BLTouch Self-Test and Reset
+ //
+ #if ENABLED(BLTOUCH)
+ MENU_ITEM(gcode, MSG_BLTOUCH_SELFTEST, PSTR("M280 P" STRINGIFY(Z_ENDSTOP_SERVO_NR) " S" STRINGIFY(BLTOUCH_SELFTEST)));
+ if (!endstops.z_probe_enabled && TEST_BLTOUCH())
+ MENU_ITEM(gcode, MSG_BLTOUCH_RESET, PSTR("M280 P" STRINGIFY(Z_ENDSTOP_SERVO_NR) " S" STRINGIFY(BLTOUCH_RESET)));
+ #endif
+
+ //
+ // Switch power on/off
+ //
+ #if HAS_POWER_SWITCH
+ if (powersupply_on)
+ MENU_ITEM(gcode, MSG_SWITCH_PS_OFF, PSTR("M81"));
+ else
+ MENU_ITEM(gcode, MSG_SWITCH_PS_ON, PSTR("M80"));
+ #endif
+
+ //
+ // Autostart
+ //
+ #if ENABLED(SDSUPPORT) && ENABLED(MENU_ADDAUTOSTART)
+ MENU_ITEM(function, MSG_AUTOSTART, lcd_autostart_sd);
+ #endif
+
+ //
+ // Delta Calibration
+ //
+ #if ENABLED(DELTA_CALIBRATION_MENU) || ENABLED(DELTA_AUTO_CALIBRATION)
+ MENU_ITEM(submenu, MSG_DELTA_CALIBRATE, lcd_delta_calibrate_menu);
+ #endif
+
+ END_MENU();
+ }
+
+ float move_menu_scale;
+
+ #if ENABLED(DELTA_CALIBRATION_MENU) || (ENABLED(DELTA_AUTO_CALIBRATION) && !HAS_BED_PROBE)
+
+ void lcd_move_z();
+
+ void _man_probe_pt(const float &rx, const float &ry) {
+ #if HAS_LEVELING
+ reset_bed_level(); // After calibration bed-level data is no longer valid
+ #endif
+
+ line_to_z((Z_CLEARANCE_BETWEEN_PROBES) + (DELTA_PRINTABLE_RADIUS) / 5);
+ current_position[X_AXIS] = rx;
+ current_position[Y_AXIS] = ry;
+ line_to_current_z();
+ line_to_z(Z_CLEARANCE_BETWEEN_PROBES);
+
+ lcd_synchronize();
+ move_menu_scale = PROBE_MANUALLY_STEP;
+ lcd_goto_screen(lcd_move_z);
+ }
+
+ #endif // DELTA_CALIBRATION_MENU || (DELTA_AUTO_CALIBRATION && !HAS_BED_PROBE)
+
+ #if ENABLED(DELTA_AUTO_CALIBRATION) && !HAS_BED_PROBE
+
+ float lcd_probe_pt(const float &rx, const float &ry) {
+ _man_probe_pt(rx, ry);
+ KEEPALIVE_STATE(PAUSED_FOR_USER);
+ defer_return_to_status = true;
+ wait_for_user = true;
+ while (wait_for_user) idle();
+ KEEPALIVE_STATE(IN_HANDLER);
+ lcd_goto_previous_menu_no_defer();
+ return current_position[Z_AXIS];
+ }
+
+ #endif // DELTA_AUTO_CALIBRATION && !HAS_BED_PROBE
+
+ #if ENABLED(DELTA_CALIBRATION_MENU)
+
+ void _lcd_calibrate_homing() {
+ if (lcdDrawUpdate) lcd_implementation_drawmenu_static(LCD_HEIGHT >= 4 ? 1 : 0, PSTR(MSG_LEVEL_BED_HOMING));
+ lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+ if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS])
+ lcd_goto_previous_menu();
+ }
+
+ void _lcd_delta_calibrate_home() {
+ #if HAS_LEVELING
+ reset_bed_level(); // After calibration bed-level data is no longer valid
+ #endif
+
+ enqueue_and_echo_commands_P(PSTR("G28"));
+ lcd_goto_screen(_lcd_calibrate_homing);
+ }
+
+ void _goto_tower_x() { _man_probe_pt(cos(RADIANS(210)) * delta_calibration_radius, sin(RADIANS(210)) * delta_calibration_radius); }
+ void _goto_tower_y() { _man_probe_pt(cos(RADIANS(330)) * delta_calibration_radius, sin(RADIANS(330)) * delta_calibration_radius); }
+ void _goto_tower_z() { _man_probe_pt(cos(RADIANS( 90)) * delta_calibration_radius, sin(RADIANS( 90)) * delta_calibration_radius); }
+ void _goto_center() { _man_probe_pt(0,0); }
+
+ #endif // DELTA_CALIBRATION_MENU
+
+ #if ENABLED(DELTA_CALIBRATION_MENU) || ENABLED(DELTA_AUTO_CALIBRATION)
+
+ void lcd_delta_settings() {
+ START_MENU();
+ MENU_BACK(MSG_DELTA_CALIBRATE);
+ MENU_ITEM_EDIT_CALLBACK(float52, MSG_DELTA_DIAG_ROD, &delta_diagonal_rod, delta_diagonal_rod - 5.0, delta_diagonal_rod + 5.0, recalc_delta_settings);
+ MENU_ITEM_EDIT_CALLBACK(float52, MSG_DELTA_HEIGHT, &delta_height, delta_height - 10.0, delta_height + 10.0, recalc_delta_settings);
+ MENU_ITEM_EDIT_CALLBACK(float43, "Ex", &delta_endstop_adj[A_AXIS], -5.0, 5.0, recalc_delta_settings);
+ MENU_ITEM_EDIT_CALLBACK(float43, "Ey", &delta_endstop_adj[B_AXIS], -5.0, 5.0, recalc_delta_settings);
+ MENU_ITEM_EDIT_CALLBACK(float43, "Ez", &delta_endstop_adj[C_AXIS], -5.0, 5.0, recalc_delta_settings);
+ MENU_ITEM_EDIT_CALLBACK(float52, MSG_DELTA_RADIUS, &delta_radius, delta_radius - 5.0, delta_radius + 5.0, recalc_delta_settings);
+ MENU_ITEM_EDIT_CALLBACK(float43, "Tx", &delta_tower_angle_trim[A_AXIS], -5.0, 5.0, recalc_delta_settings);
+ MENU_ITEM_EDIT_CALLBACK(float43, "Ty", &delta_tower_angle_trim[B_AXIS], -5.0, 5.0, recalc_delta_settings);
+ MENU_ITEM_EDIT_CALLBACK(float43, "Tz", &delta_tower_angle_trim[C_AXIS], -5.0, 5.0, recalc_delta_settings);
+ END_MENU();
+ }
+
+ void lcd_delta_calibrate_menu() {
+ START_MENU();
+ MENU_BACK(MSG_MAIN);
+ #if ENABLED(DELTA_AUTO_CALIBRATION)
+ MENU_ITEM(gcode, MSG_DELTA_AUTO_CALIBRATE, PSTR("G33"));
+ MENU_ITEM(gcode, MSG_DELTA_HEIGHT_CALIBRATE, PSTR("G33 P1"));
+ #if ENABLED(EEPROM_SETTINGS)
+ MENU_ITEM(function, MSG_STORE_EEPROM, lcd_store_settings);
+ MENU_ITEM(function, MSG_LOAD_EEPROM, lcd_load_settings);
+ #endif
+ #endif
+ MENU_ITEM(submenu, MSG_DELTA_SETTINGS, lcd_delta_settings);
+ #if ENABLED(DELTA_CALIBRATION_MENU)
+ MENU_ITEM(submenu, MSG_AUTO_HOME, _lcd_delta_calibrate_home);
+ if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS]) {
+ MENU_ITEM(submenu, MSG_DELTA_CALIBRATE_X, _goto_tower_x);
+ MENU_ITEM(submenu, MSG_DELTA_CALIBRATE_Y, _goto_tower_y);
+ MENU_ITEM(submenu, MSG_DELTA_CALIBRATE_Z, _goto_tower_z);
+ MENU_ITEM(submenu, MSG_DELTA_CALIBRATE_CENTER, _goto_center);
+ }
+ #endif
+ END_MENU();
+ }
+
+ #endif // DELTA_CALIBRATION_MENU || DELTA_AUTO_CALIBRATION
+
+ /**
+ * If the most recent manual move hasn't been fed to the planner yet,
+ * and the planner can accept one, send immediately
+ */
+ inline void manage_manual_move() {
+
+ if (processing_manual_move) return;
+
+ if (manual_move_axis != (int8_t)NO_AXIS && ELAPSED(millis(), manual_move_start_time) && !planner.is_full()) {
+
+ #if IS_KINEMATIC
+
+ const float old_feedrate = feedrate_mm_s;
+ feedrate_mm_s = MMM_TO_MMS(manual_feedrate_mm_m[manual_move_axis]);
+
+ #if EXTRUDERS > 1
+ const int8_t old_extruder = active_extruder;
+ active_extruder = manual_move_e_index;
+ #endif
+
+ // Set movement on a single axis
+ set_destination_from_current();
+ destination[manual_move_axis] += manual_move_offset;
+
+ // Reset for the next move
+ manual_move_offset = 0.0;
+ manual_move_axis = (int8_t)NO_AXIS;
+
+ // DELTA and SCARA machines use segmented moves, which could fill the planner during the call to
+ // move_to_destination. This will cause idle() to be called, which can then call this function while the
+ // previous invocation is being blocked. Modifications to manual_move_offset shouldn't be made while
+ // processing_manual_move is true or the planner will get out of sync.
+ processing_manual_move = true;
+ prepare_move_to_destination(); // will call set_current_from_destination()
+ processing_manual_move = false;
+
+ feedrate_mm_s = old_feedrate;
+ #if EXTRUDERS > 1
+ active_extruder = old_extruder;
+ #endif
+
+ #else
+
+ planner.buffer_line_kinematic(current_position, MMM_TO_MMS(manual_feedrate_mm_m[manual_move_axis]), manual_move_e_index);
+ manual_move_axis = (int8_t)NO_AXIS;
+
+ #endif
+ }
+ }
+
+ /**
+ * Set a flag that lcd_update() should start a move
+ * to "current_position" after a short delay.
+ */
+ inline void manual_move_to_current(AxisEnum axis
+ #if E_MANUAL > 1
+ , const int8_t eindex=-1
+ #endif
+ ) {
+ #if ENABLED(DUAL_X_CARRIAGE) || E_MANUAL > 1
+ #if E_MANUAL > 1
+ if (axis == E_AXIS)
+ #endif
+ manual_move_e_index = eindex >= 0 ? eindex : active_extruder;
+ #endif
+ manual_move_start_time = millis() + (move_menu_scale < 0.99 ? 0UL : 250UL); // delay for bigger moves
+ manual_move_axis = (int8_t)axis;
+ }
+
+ /**
+ *
+ * "Prepare" > "Move Axis" submenu
+ *
+ */
+
+ void _lcd_move_xyz(const char* name, AxisEnum axis) {
+ if (use_click()) { return lcd_goto_previous_menu_no_defer(); }
+ ENCODER_DIRECTION_NORMAL();
+ if (encoderPosition && !processing_manual_move) {
+
+ // Start with no limits to movement
+ float min = current_position[axis] - 1000,
+ max = current_position[axis] + 1000;
+
+ // Limit to software endstops, if enabled
+ #if ENABLED(MIN_SOFTWARE_ENDSTOPS) || ENABLED(MAX_SOFTWARE_ENDSTOPS)
+ if (soft_endstops_enabled) switch (axis) {
+ case X_AXIS:
+ #if ENABLED(MIN_SOFTWARE_ENDSTOP_X)
+ min = soft_endstop_min[X_AXIS];
+ #endif
+ #if ENABLED(MAX_SOFTWARE_ENDSTOP_X)
+ max = soft_endstop_max[X_AXIS];
+ #endif
+ break;
+ case Y_AXIS:
+ #if ENABLED(MIN_SOFTWARE_ENDSTOP_Y)
+ min = soft_endstop_min[Y_AXIS];
+ #endif
+ #if ENABLED(MAX_SOFTWARE_ENDSTOP_Y)
+ max = soft_endstop_max[Y_AXIS];
+ #endif
+ break;
+ case Z_AXIS:
+ #if ENABLED(MIN_SOFTWARE_ENDSTOP_Z)
+ min = soft_endstop_min[Z_AXIS];
+ #endif
+ #if ENABLED(MAX_SOFTWARE_ENDSTOP_Z)
+ max = soft_endstop_max[Z_AXIS];
+ #endif
+ default: break;
+ }
+ #endif // MIN_SOFTWARE_ENDSTOPS || MAX_SOFTWARE_ENDSTOPS
+
+ // Delta limits XY based on the current offset from center
+ // This assumes the center is 0,0
+ #if ENABLED(DELTA)
+ if (axis != Z_AXIS) {
+ max = SQRT(sq((float)(DELTA_PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis
+ min = -max;
+ }
+ #endif
+
+ // Get the new position
+ const float diff = float((int32_t)encoderPosition) * move_menu_scale;
+ #if IS_KINEMATIC
+ manual_move_offset += diff;
+ // Limit only when trying to move towards the limit
+ if ((int32_t)encoderPosition < 0) NOLESS(manual_move_offset, min - current_position[axis]);
+ if ((int32_t)encoderPosition > 0) NOMORE(manual_move_offset, max - current_position[axis]);
+ #else
+ current_position[axis] += diff;
+ // Limit only when trying to move towards the limit
+ if ((int32_t)encoderPosition < 0) NOLESS(current_position[axis], min);
+ if ((int32_t)encoderPosition > 0) NOMORE(current_position[axis], max);
+ #endif
+
+ manual_move_to_current(axis);
+ lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+ }
+ encoderPosition = 0;
+ if (lcdDrawUpdate) {
+ const float pos = NATIVE_TO_LOGICAL(processing_manual_move ? destination[axis] : current_position[axis]
+ #if IS_KINEMATIC
+ + manual_move_offset
+ #endif
+ , axis);
+ lcd_implementation_drawedit(name, move_menu_scale >= 0.1 ? ftostr41sign(pos) : ftostr43sign(pos));
+ }
+ }
+ void lcd_move_x() { _lcd_move_xyz(PSTR(MSG_MOVE_X), X_AXIS); }
+ void lcd_move_y() { _lcd_move_xyz(PSTR(MSG_MOVE_Y), Y_AXIS); }
+ void lcd_move_z() { _lcd_move_xyz(PSTR(MSG_MOVE_Z), Z_AXIS); }
+ void _lcd_move_e(
+ #if E_MANUAL > 1
+ const int8_t eindex=-1
+ #endif
+ ) {
+ if (use_click()) { return lcd_goto_previous_menu_no_defer(); }
+ ENCODER_DIRECTION_NORMAL();
+ if (encoderPosition) {
+ if (!processing_manual_move) {
+ const float diff = float((int32_t)encoderPosition) * move_menu_scale;
+ #if IS_KINEMATIC
+ manual_move_offset += diff;
+ #else
+ current_position[E_AXIS] += diff;
+ #endif
+ manual_move_to_current(E_AXIS
+ #if E_MANUAL > 1
+ , eindex
+ #endif
+ );
+ lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+ }
+ encoderPosition = 0;
+ }
+ if (lcdDrawUpdate) {
+ PGM_P pos_label;
+ #if E_MANUAL == 1
+ pos_label = PSTR(MSG_MOVE_E);
+ #else
+ switch (eindex) {
+ default: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E1); break;
+ case 1: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E2); break;
+ #if E_MANUAL > 2
+ case 2: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E3); break;
+ #if E_MANUAL > 3
+ case 3: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E4); break;
+ #if E_MANUAL > 4
+ case 4: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E5); break;
+ #endif // E_MANUAL > 4
+ #endif // E_MANUAL > 3
+ #endif // E_MANUAL > 2
+ }
+ #endif // E_MANUAL > 1
+ lcd_implementation_drawedit(pos_label, ftostr41sign(current_position[E_AXIS]
+ #if IS_KINEMATIC
+ + manual_move_offset
+ #endif
+ ));
+ }
+ }
+
+ void lcd_move_e() { _lcd_move_e(); }
+ #if E_MANUAL > 1
+ void lcd_move_e0() { _lcd_move_e(0); }
+ void lcd_move_e1() { _lcd_move_e(1); }
+ #if E_MANUAL > 2
+ void lcd_move_e2() { _lcd_move_e(2); }
+ #if E_MANUAL > 3
+ void lcd_move_e3() { _lcd_move_e(3); }
+ #if E_MANUAL > 4
+ void lcd_move_e4() { _lcd_move_e(4); }
+ #endif // E_MANUAL > 4
+ #endif // E_MANUAL > 3
+ #endif // E_MANUAL > 2
+ #endif // E_MANUAL > 1
+
+ /**
+ *
+ * "Prepare" > "Move Xmm" > "Move XYZ" submenu
+ *
+ */
+
+ screenFunc_t _manual_move_func_ptr;
+
+ void _goto_manual_move(const float scale) {
+ defer_return_to_status = true;
+ move_menu_scale = scale;
+ lcd_goto_screen(_manual_move_func_ptr);
+ }
+ void lcd_move_menu_10mm() { _goto_manual_move(10.0); }
+ void lcd_move_menu_1mm() { _goto_manual_move( 1.0); }
+ void lcd_move_menu_01mm() { _goto_manual_move( 0.1); }
+
+ void _lcd_move_distance_menu(const AxisEnum axis, const screenFunc_t func) {
+ _manual_move_func_ptr = func;
+ START_MENU();
+ if (LCD_HEIGHT >= 4) {
+ switch (axis) {
+ case X_AXIS:
+ STATIC_ITEM(MSG_MOVE_X, true, true); break;
+ case Y_AXIS:
+ STATIC_ITEM(MSG_MOVE_Y, true, true); break;
+ case Z_AXIS:
+ STATIC_ITEM(MSG_MOVE_Z, true, true); break;
+ default:
+ STATIC_ITEM(MSG_MOVE_E, true, true); break;
+ }
+ }
+ MENU_BACK(MSG_MOVE_AXIS);
+ MENU_ITEM(submenu, MSG_MOVE_10MM, lcd_move_menu_10mm);
+ MENU_ITEM(submenu, MSG_MOVE_1MM, lcd_move_menu_1mm);
+ MENU_ITEM(submenu, MSG_MOVE_01MM, lcd_move_menu_01mm);
+ END_MENU();
+ }
+ void lcd_move_get_x_amount() { _lcd_move_distance_menu(X_AXIS, lcd_move_x); }
+ void lcd_move_get_y_amount() { _lcd_move_distance_menu(Y_AXIS, lcd_move_y); }
+ void lcd_move_get_z_amount() { _lcd_move_distance_menu(Z_AXIS, lcd_move_z); }
+ void lcd_move_get_e_amount() { _lcd_move_distance_menu(E_AXIS, lcd_move_e); }
+ #if E_MANUAL > 1
+ void lcd_move_get_e0_amount() { _lcd_move_distance_menu(E_AXIS, lcd_move_e0); }
+ void lcd_move_get_e1_amount() { _lcd_move_distance_menu(E_AXIS, lcd_move_e1); }
+ #if E_MANUAL > 2
+ void lcd_move_get_e2_amount() { _lcd_move_distance_menu(E_AXIS, lcd_move_e2); }
+ #if E_MANUAL > 3
+ void lcd_move_get_e3_amount() { _lcd_move_distance_menu(E_AXIS, lcd_move_e3); }
+ #if E_MANUAL > 4
+ void lcd_move_get_e4_amount() { _lcd_move_distance_menu(E_AXIS, lcd_move_e4); }
+ #endif // E_MANUAL > 4
+ #endif // E_MANUAL > 3
+ #endif // E_MANUAL > 2
+ #endif // E_MANUAL > 1
+
+ /**
+ *
+ * "Prepare" > "Move Axis" submenu
+ *
+ */
+
+ #if IS_KINEMATIC || ENABLED(NO_MOTION_BEFORE_HOMING)
+ #define _MOVE_XYZ_ALLOWED (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS])
+ #else
+ #define _MOVE_XYZ_ALLOWED true
+ #endif
+
+ #if ENABLED(DELTA)
+ #define _MOVE_XY_ALLOWED (current_position[Z_AXIS] <= delta_clip_start_height)
+ void lcd_lower_z_to_clip_height() {
+ line_to_z(delta_clip_start_height);
+ lcd_synchronize();
+ }
+ #else
+ #define _MOVE_XY_ALLOWED true
+ #endif
+
+ void lcd_move_menu() {
+ START_MENU();
+ MENU_BACK(MSG_PREPARE);
+
+ if (_MOVE_XYZ_ALLOWED) {
+ if (_MOVE_XY_ALLOWED) {
+ MENU_ITEM(submenu, MSG_MOVE_X, lcd_move_get_x_amount);
+ MENU_ITEM(submenu, MSG_MOVE_Y, lcd_move_get_y_amount);
+ }
+ #if ENABLED(DELTA)
+ else
+ MENU_ITEM(function, MSG_FREE_XY, lcd_lower_z_to_clip_height);
+ #endif
+
+ MENU_ITEM(submenu, MSG_MOVE_Z, lcd_move_get_z_amount);
+ }
+ else
+ MENU_ITEM(gcode, MSG_AUTO_HOME, PSTR("G28"));
+
+ #if ENABLED(SWITCHING_EXTRUDER)
+
+ #if EXTRUDERS == 4
+ switch (active_extruder) {
+ case 0: MENU_ITEM(gcode, MSG_SELECT " " MSG_E2, PSTR("T1")); break;
+ case 1: MENU_ITEM(gcode, MSG_SELECT " " MSG_E1, PSTR("T0")); break;
+ case 2: MENU_ITEM(gcode, MSG_SELECT " " MSG_E4, PSTR("T3")); break;
+ case 3: MENU_ITEM(gcode, MSG_SELECT " " MSG_E3, PSTR("T2")); break;
+ }
+ #elif EXTRUDERS == 3
+ if (active_extruder < 2) {
+ if (active_extruder)
+ MENU_ITEM(gcode, MSG_SELECT " " MSG_E1, PSTR("T0"));
+ else
+ MENU_ITEM(gcode, MSG_SELECT " " MSG_E2, PSTR("T1"));
+ }
+ #else
+ if (active_extruder)
+ MENU_ITEM(gcode, MSG_SELECT " " MSG_E1, PSTR("T0"));
+ else
+ MENU_ITEM(gcode, MSG_SELECT " " MSG_E2, PSTR("T1"));
+ #endif
+
+ #elif ENABLED(DUAL_X_CARRIAGE)
+
+ if (active_extruder)
+ MENU_ITEM(gcode, MSG_SELECT " " MSG_E1, PSTR("T0"));
+ else
+ MENU_ITEM(gcode, MSG_SELECT " " MSG_E2, PSTR("T1"));
+
+ #endif
+
+ MENU_ITEM(submenu, MSG_MOVE_E, lcd_move_get_e_amount);
+ #if E_MANUAL > 1
+ MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E1, lcd_move_get_e0_amount);
+ MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E2, lcd_move_get_e1_amount);
+ #if E_MANUAL > 2
+ MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E3, lcd_move_get_e2_amount);
+ #if E_MANUAL > 3
+ MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E4, lcd_move_get_e3_amount);
+ #if E_MANUAL > 4
+ MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E5, lcd_move_get_e4_amount);
+ #endif // E_MANUAL > 4
+ #endif // E_MANUAL > 3
+ #endif // E_MANUAL > 2
+ #endif // E_MANUAL > 1
+
+ END_MENU();
+ }
+
+ /**
+ *
+ * "Control" submenu
+ *
+ */
+
+ #if HAS_LCD_CONTRAST
+ void lcd_callback_set_contrast() { set_lcd_contrast(lcd_contrast); }
+ #endif
+
+ static void lcd_factory_settings() {
+ settings.reset();
+ lcd_completion_feedback();
+ }
+
+ #if ENABLED(EEPROM_SETTINGS)
+
+ static void lcd_init_eeprom() {
+ lcd_completion_feedback(settings.init_eeprom());
+ lcd_goto_previous_menu();
+ }
+
+ static void lcd_init_eeprom_confirm() {
+ START_MENU();
+ MENU_BACK(MSG_CONTROL);
+ MENU_ITEM(function, MSG_INIT_EEPROM, lcd_init_eeprom);
+ END_MENU();
+ }
+
+ #endif
+
+ void lcd_control_menu() {
+ START_MENU();
+ MENU_BACK(MSG_MAIN);
+ MENU_ITEM(submenu, MSG_TEMPERATURE, lcd_control_temperature_menu);
+ MENU_ITEM(submenu, MSG_MOTION, lcd_control_motion_menu);
+
+ #if DISABLED(NO_VOLUMETRICS) || ENABLED(ADVANCED_PAUSE_FEATURE)
+ MENU_ITEM(submenu, MSG_FILAMENT, lcd_control_filament_menu);
+ #elif ENABLED(LIN_ADVANCE)
+ MENU_ITEM_EDIT(float32, MSG_ADVANCE_K, &planner.extruder_advance_K, 0, 999);
+ #endif
+
+ #if HAS_LCD_CONTRAST
+ MENU_ITEM_EDIT_CALLBACK(int3, MSG_CONTRAST, &lcd_contrast, LCD_CONTRAST_MIN, LCD_CONTRAST_MAX, lcd_callback_set_contrast, true);
+ #endif
+ #if ENABLED(FWRETRACT)
+ MENU_ITEM(submenu, MSG_RETRACT, lcd_control_retract_menu);
+ #endif
+ #if ENABLED(DAC_STEPPER_CURRENT)
+ MENU_ITEM(submenu, MSG_DRIVE_STRENGTH, lcd_dac_menu);
+ #endif
+ #if HAS_MOTOR_CURRENT_PWM
+ MENU_ITEM(submenu, MSG_DRIVE_STRENGTH, lcd_pwm_menu);
+ #endif
+
+ #if ENABLED(BLTOUCH)
+ MENU_ITEM(submenu, MSG_BLTOUCH, bltouch_menu);
+ #endif
+
+ #if ENABLED(EEPROM_SETTINGS)
+ MENU_ITEM(function, MSG_STORE_EEPROM, lcd_store_settings);
+ MENU_ITEM(function, MSG_LOAD_EEPROM, lcd_load_settings);
+ #endif
+
+ MENU_ITEM(function, MSG_RESTORE_FAILSAFE, lcd_factory_settings);
+
+ #if ENABLED(EEPROM_SETTINGS) && DISABLED(SLIM_LCD_MENUS)
+ MENU_ITEM(submenu, MSG_INIT_EEPROM, lcd_init_eeprom_confirm);
+ #endif
+
+ END_MENU();
+ }
+
+ /**
+ *
+ * "Temperature" submenu
+ *
+ */
+
+ #if ENABLED(PID_AUTOTUNE_MENU)
+
+ #if ENABLED(PIDTEMP)
+ int16_t autotune_temp[HOTENDS] = ARRAY_BY_HOTENDS1(150);
+ #endif
+
+ #if ENABLED(PIDTEMPBED)
+ int16_t autotune_temp_bed = 70;
+ #endif
+
+ void _lcd_autotune(int16_t e) {
+ char cmd[30];
+ sprintf_P(cmd, PSTR("M303 U1 E%i S%i"), e,
+ #if HAS_PID_FOR_BOTH
+ e < 0 ? autotune_temp_bed : autotune_temp[e]
+ #elif ENABLED(PIDTEMPBED)
+ autotune_temp_bed
+ #else
+ autotune_temp[e]
+ #endif
+ );
+ lcd_enqueue_command(cmd);
+ }
+
+ #endif // PID_AUTOTUNE_MENU
+
+ #if ENABLED(PIDTEMP)
+
+ // Helpers for editing PID Ki & Kd values
+ // grab the PID value out of the temp variable; scale it; then update the PID driver
+ void copy_and_scalePID_i(int16_t e) {
+ #if DISABLED(PID_PARAMS_PER_HOTEND) || HOTENDS == 1
+ UNUSED(e);
+ #endif
+ PID_PARAM(Ki, e) = scalePID_i(raw_Ki);
+ thermalManager.updatePID();
+ }
+ void copy_and_scalePID_d(int16_t e) {
+ #if DISABLED(PID_PARAMS_PER_HOTEND) || HOTENDS == 1
+ UNUSED(e);
+ #endif
+ PID_PARAM(Kd, e) = scalePID_d(raw_Kd);
+ thermalManager.updatePID();
+ }
+ #define _DEFINE_PIDTEMP_BASE_FUNCS(N) \
+ void copy_and_scalePID_i_E ## N() { copy_and_scalePID_i(N); } \
+ void copy_and_scalePID_d_E ## N() { copy_and_scalePID_d(N); }
+
+ #if ENABLED(PID_AUTOTUNE_MENU)
+ #define DEFINE_PIDTEMP_FUNCS(N) \
+ _DEFINE_PIDTEMP_BASE_FUNCS(N); \
+ void lcd_autotune_callback_E ## N() { _lcd_autotune(N); } typedef void _pid_##N##_void
+ #else
+ #define DEFINE_PIDTEMP_FUNCS(N) _DEFINE_PIDTEMP_BASE_FUNCS(N) typedef void _pid_##N##_void
+ #endif
+
+ DEFINE_PIDTEMP_FUNCS(0);
+ #if ENABLED(PID_PARAMS_PER_HOTEND)
+ #if HOTENDS > 1
+ DEFINE_PIDTEMP_FUNCS(1);
+ #if HOTENDS > 2
+ DEFINE_PIDTEMP_FUNCS(2);
+ #if HOTENDS > 3
+ DEFINE_PIDTEMP_FUNCS(3);
+ #if HOTENDS > 4
+ DEFINE_PIDTEMP_FUNCS(4);
+ #endif // HOTENDS > 4
+ #endif // HOTENDS > 3
+ #endif // HOTENDS > 2
+ #endif // HOTENDS > 1
+ #endif // PID_PARAMS_PER_HOTEND
+
+ #endif // PIDTEMP
+
+ /**
+ *
+ * "Control" > "Temperature" submenu
+ *
+ */
+ void lcd_control_temperature_menu() {
+ START_MENU();
+
+ //
+ // ^ Control
+ //
+ MENU_BACK(MSG_CONTROL);
+
+ //
+ // Nozzle:
+ // Nozzle [1-5]:
+ //
+ #if HOTENDS == 1
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE, &thermalManager.target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0);
+ #else // HOTENDS > 1
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N1, &thermalManager.target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0);
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N2, &thermalManager.target_temperature[1], 0, HEATER_1_MAXTEMP - 15, watch_temp_callback_E1);
+ #if HOTENDS > 2
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N3, &thermalManager.target_temperature[2], 0, HEATER_2_MAXTEMP - 15, watch_temp_callback_E2);
+ #if HOTENDS > 3
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N4, &thermalManager.target_temperature[3], 0, HEATER_3_MAXTEMP - 15, watch_temp_callback_E3);
+ #if HOTENDS > 4
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N5, &thermalManager.target_temperature[4], 0, HEATER_4_MAXTEMP - 15, watch_temp_callback_E4);
+ #endif // HOTENDS > 4
+ #endif // HOTENDS > 3
+ #endif // HOTENDS > 2
+ #endif // HOTENDS > 1
+
+ //
+ // Bed:
+ //
+ #if HAS_TEMP_BED
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_BED, &thermalManager.target_temperature_bed, 0, BED_MAXTEMP - 15, watch_temp_callback_bed);
+ #endif
+#if ENABLED(FAN_AS_LASER)
+ MENU_ITEM(gcode, MSG_LASER_ON, PSTR("M3"));
+ MENU_ITEM(gcode, MSG_LASER_OFF, PSTR("M5"));
+#endif
+
+ //
+ // Fan Speed:
+ //
+ #if FAN_COUNT > 0
+ #if (HAS_FAN0 && FAN_NUM_AS_LASER!=0)
+ MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_FAN_SPEED FAN_SPEED_1_SUFFIX, &fanSpeeds[0], 0, 255);
+ #if ENABLED(EXTRA_FAN_SPEED)
+ MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_EXTRA_FAN_SPEED FAN_SPEED_1_SUFFIX, &new_fanSpeeds[0], 3, 255);
+ #endif
+ #endif
+ #if (HAS_FAN1 && FAN_NUM_AS_LASER!=1)
+ MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_FAN_SPEED " 2", &fanSpeeds[1], 0, 255);
+ #if ENABLED(EXTRA_FAN_SPEED)
+ MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_EXTRA_FAN_SPEED " 2", &new_fanSpeeds[1], 3, 255);
+ #endif
+ #endif
+ #if (HAS_FAN2 && FAN_NUM_AS_LASER!=2)
+ MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_FAN_SPEED " 3", &fanSpeeds[2], 0, 255);
+ #if ENABLED(EXTRA_FAN_SPEED)
+ MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_EXTRA_FAN_SPEED " 3", &new_fanSpeeds[2], 3, 255);
+ #endif
+ #endif
+ #endif // FAN_COUNT > 0
+
+ //
+ // Autotemp, Min, Max, Fact
+ //
+ #if ENABLED(AUTOTEMP) && (TEMP_SENSOR_0 != 0)
+ MENU_ITEM_EDIT(bool, MSG_AUTOTEMP, &planner.autotemp_enabled);
+ MENU_ITEM_EDIT(float3, MSG_MIN, &planner.autotemp_min, 0, HEATER_0_MAXTEMP - 15);
+ MENU_ITEM_EDIT(float3, MSG_MAX, &planner.autotemp_max, 0, HEATER_0_MAXTEMP - 15);
+ MENU_ITEM_EDIT(float32, MSG_FACTOR, &planner.autotemp_factor, 0.0, 1.0);
+ #endif
+
+ //
+ // PID-P, PID-I, PID-D, PID-C, PID Autotune
+ // PID-P E1, PID-I E1, PID-D E1, PID-C E1, PID Autotune E1
+ // PID-P E2, PID-I E2, PID-D E2, PID-C E2, PID Autotune E2
+ // PID-P E3, PID-I E3, PID-D E3, PID-C E3, PID Autotune E3
+ // PID-P E4, PID-I E4, PID-D E4, PID-C E4, PID Autotune E4
+ // PID-P E5, PID-I E5, PID-D E5, PID-C E5, PID Autotune E5
+ //
+ #if ENABLED(PIDTEMP)
+
+ #define _PID_BASE_MENU_ITEMS(ELABEL, eindex) \
+ raw_Ki = unscalePID_i(PID_PARAM(Ki, eindex)); \
+ raw_Kd = unscalePID_d(PID_PARAM(Kd, eindex)); \
+ MENU_ITEM_EDIT(float52, MSG_PID_P ELABEL, &PID_PARAM(Kp, eindex), 1, 9990); \
+ MENU_ITEM_EDIT_CALLBACK(float52, MSG_PID_I ELABEL, &raw_Ki, 0.01, 9990, copy_and_scalePID_i_E ## eindex); \
+ MENU_ITEM_EDIT_CALLBACK(float52, MSG_PID_D ELABEL, &raw_Kd, 1, 9990, copy_and_scalePID_d_E ## eindex)
+
+ #if ENABLED(PID_EXTRUSION_SCALING)
+ #define _PID_MENU_ITEMS(ELABEL, eindex) \
+ _PID_BASE_MENU_ITEMS(ELABEL, eindex); \
+ MENU_ITEM_EDIT(float3, MSG_PID_C ELABEL, &PID_PARAM(Kc, eindex), 1, 9990)
+ #else
+ #define _PID_MENU_ITEMS(ELABEL, eindex) _PID_BASE_MENU_ITEMS(ELABEL, eindex)
+ #endif
+
+ #if ENABLED(PID_AUTOTUNE_MENU)
+ #define PID_MENU_ITEMS(ELABEL, eindex) \
+ _PID_MENU_ITEMS(ELABEL, eindex); \
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_PID_AUTOTUNE ELABEL, &autotune_temp[eindex], 150, heater_maxtemp[eindex] - 15, lcd_autotune_callback_E ## eindex)
+ #else
+ #define PID_MENU_ITEMS(ELABEL, eindex) _PID_MENU_ITEMS(ELABEL, eindex)
+ #endif
+
+ #if ENABLED(PID_PARAMS_PER_HOTEND) && HOTENDS > 1
+ PID_MENU_ITEMS(" " MSG_E1, 0);
+ PID_MENU_ITEMS(" " MSG_E2, 1);
+ #if HOTENDS > 2
+ PID_MENU_ITEMS(" " MSG_E3, 2);
+ #if HOTENDS > 3
+ PID_MENU_ITEMS(" " MSG_E4, 3);
+ #if HOTENDS > 4
+ PID_MENU_ITEMS(" " MSG_E5, 4);
+ #endif // HOTENDS > 4
+ #endif // HOTENDS > 3
+ #endif // HOTENDS > 2
+ #else // !PID_PARAMS_PER_HOTEND || HOTENDS == 1
+ PID_MENU_ITEMS("", 0);
+ #endif // !PID_PARAMS_PER_HOTEND || HOTENDS == 1
+
+ #endif // PIDTEMP
+
+ #if DISABLED(SLIM_LCD_MENUS)
+ //
+ // Preheat Material 1 conf
+ //
+ MENU_ITEM(submenu, MSG_PREHEAT_1_SETTINGS, lcd_control_temperature_preheat_material1_settings_menu);
+
+ //
+ // Preheat Material 2 conf
+ //
+ MENU_ITEM(submenu, MSG_PREHEAT_2_SETTINGS, lcd_control_temperature_preheat_material2_settings_menu);
+ #endif
+
+ END_MENU();
+ }
+
+ #if DISABLED(SLIM_LCD_MENUS)
+
+ void _lcd_control_temperature_preheat_settings_menu(const uint8_t material) {
+ #if HOTENDS > 4
+ #define MINTEMP_ALL MIN5(HEATER_0_MINTEMP, HEATER_1_MINTEMP, HEATER_2_MINTEMP, HEATER_3_MINTEMP, HEATER_4_MINTEMP)
+ #define MAXTEMP_ALL MAX5(HEATER_0_MAXTEMP, HEATER_1_MAXTEMP, HEATER_2_MAXTEMP, HEATER_3_MAXTEMP, HEATER_4_MAXTEMP)
+ #elif HOTENDS > 3
+ #define MINTEMP_ALL MIN4(HEATER_0_MINTEMP, HEATER_1_MINTEMP, HEATER_2_MINTEMP, HEATER_3_MINTEMP)
+ #define MAXTEMP_ALL MAX4(HEATER_0_MAXTEMP, HEATER_1_MAXTEMP, HEATER_2_MAXTEMP, HEATER_3_MAXTEMP)
+ #elif HOTENDS > 2
+ #define MINTEMP_ALL MIN3(HEATER_0_MINTEMP, HEATER_1_MINTEMP, HEATER_2_MINTEMP)
+ #define MAXTEMP_ALL MAX3(HEATER_0_MAXTEMP, HEATER_1_MAXTEMP, HEATER_2_MAXTEMP)
+ #elif HOTENDS > 1
+ #define MINTEMP_ALL min(HEATER_0_MINTEMP, HEATER_1_MINTEMP)
+ #define MAXTEMP_ALL max(HEATER_0_MAXTEMP, HEATER_1_MAXTEMP)
+ #else
+ #define MINTEMP_ALL HEATER_0_MINTEMP
+ #define MAXTEMP_ALL HEATER_0_MAXTEMP
+ #endif
+ START_MENU();
+ MENU_BACK(MSG_TEMPERATURE);
+ MENU_ITEM_EDIT(int3, MSG_FAN_SPEED, &lcd_preheat_fan_speed[material], 0, 255);
+ #if TEMP_SENSOR_0 != 0
+ MENU_ITEM_EDIT(int3, MSG_NOZZLE, &lcd_preheat_hotend_temp[material], MINTEMP_ALL, MAXTEMP_ALL - 15);
+ #endif
+ #if TEMP_SENSOR_BED != 0
+ MENU_ITEM_EDIT(int3, MSG_BED, &lcd_preheat_bed_temp[material], BED_MINTEMP, BED_MAXTEMP - 15);
+ #endif
+ #if ENABLED(EEPROM_SETTINGS)
+ MENU_ITEM(function, MSG_STORE_EEPROM, lcd_store_settings);
+ #endif
+ END_MENU();
+ }
+
+ /**
+ *
+ * "Temperature" > "Preheat Material 1 conf" submenu
+ *
+ */
+ void lcd_control_temperature_preheat_material1_settings_menu() { _lcd_control_temperature_preheat_settings_menu(0); }
+
+ /**
+ *
+ * "Temperature" > "Preheat Material 2 conf" submenu
+ *
+ */
+ void lcd_control_temperature_preheat_material2_settings_menu() { _lcd_control_temperature_preheat_settings_menu(1); }
+
+ void _reset_acceleration_rates() { planner.reset_acceleration_rates(); }
+ #if ENABLED(DISTINCT_E_FACTORS)
+ void _reset_e_acceleration_rate(const uint8_t e) { if (e == active_extruder) _reset_acceleration_rates(); }
+ void _reset_e0_acceleration_rate() { _reset_e_acceleration_rate(0); }
+ void _reset_e1_acceleration_rate() { _reset_e_acceleration_rate(1); }
+ #if E_STEPPERS > 2
+ void _reset_e2_acceleration_rate() { _reset_e_acceleration_rate(2); }
+ #if E_STEPPERS > 3
+ void _reset_e3_acceleration_rate() { _reset_e_acceleration_rate(3); }
+ #if E_STEPPERS > 4
+ void _reset_e4_acceleration_rate() { _reset_e_acceleration_rate(4); }
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #endif
+
+ void _planner_refresh_positioning() { planner.refresh_positioning(); }
+ #if ENABLED(DISTINCT_E_FACTORS)
+ void _planner_refresh_e_positioning(const uint8_t e) {
+ if (e == active_extruder)
+ _planner_refresh_positioning();
+ else
+ planner.steps_to_mm[E_AXIS + e] = 1.0 / planner.axis_steps_per_mm[E_AXIS + e];
+ }
+ void _planner_refresh_e0_positioning() { _planner_refresh_e_positioning(0); }
+ void _planner_refresh_e1_positioning() { _planner_refresh_e_positioning(1); }
+ #if E_STEPPERS > 2
+ void _planner_refresh_e2_positioning() { _planner_refresh_e_positioning(2); }
+ #if E_STEPPERS > 3
+ void _planner_refresh_e3_positioning() { _planner_refresh_e_positioning(3); }
+ #if E_STEPPERS > 4
+ void _planner_refresh_e4_positioning() { _planner_refresh_e_positioning(4); }
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #endif
+
+ // M203 / M205 Velocity options
+ void lcd_control_motion_velocity_menu() {
+ START_MENU();
+ MENU_BACK(MSG_MOTION);
+
+ // M203 Max Feedrate
+ MENU_ITEM_EDIT(float3, MSG_VMAX MSG_A, &planner.max_feedrate_mm_s[A_AXIS], 1, 999);
+ MENU_ITEM_EDIT(float3, MSG_VMAX MSG_B, &planner.max_feedrate_mm_s[B_AXIS], 1, 999);
+ MENU_ITEM_EDIT(float3, MSG_VMAX MSG_C, &planner.max_feedrate_mm_s[C_AXIS], 1, 999);
+
+ #if ENABLED(DISTINCT_E_FACTORS)
+ MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E, &planner.max_feedrate_mm_s[E_AXIS + active_extruder], 1, 999);
+ MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E1, &planner.max_feedrate_mm_s[E_AXIS], 1, 999);
+ MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E2, &planner.max_feedrate_mm_s[E_AXIS + 1], 1, 999);
+ #if E_STEPPERS > 2
+ MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E3, &planner.max_feedrate_mm_s[E_AXIS + 2], 1, 999);
+ #if E_STEPPERS > 3
+ MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E4, &planner.max_feedrate_mm_s[E_AXIS + 3], 1, 999);
+ #if E_STEPPERS > 4
+ MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E5, &planner.max_feedrate_mm_s[E_AXIS + 4], 1, 999);
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #else
+ MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E, &planner.max_feedrate_mm_s[E_AXIS], 1, 999);
+ #endif
+
+ // M205 S Min Feedrate
+ MENU_ITEM_EDIT(float3, MSG_VMIN, &planner.min_feedrate_mm_s, 0, 999);
+
+ // M205 T Min Travel Feedrate
+ MENU_ITEM_EDIT(float3, MSG_VTRAV_MIN, &planner.min_travel_feedrate_mm_s, 0, 999);
+
+ END_MENU();
+ }
+
+ // M201 / M204 Accelerations
+ void lcd_control_motion_acceleration_menu() {
+ START_MENU();
+ MENU_BACK(MSG_MOTION);
+
+ // M204 P Acceleration
+ MENU_ITEM_EDIT(float5, MSG_ACC, &planner.acceleration, 10, 99000);
+
+ // M204 R Retract Acceleration
+ MENU_ITEM_EDIT(float5, MSG_A_RETRACT, &planner.retract_acceleration, 100, 99000);
+
+ // M204 T Travel Acceleration
+ MENU_ITEM_EDIT(float5, MSG_A_TRAVEL, &planner.travel_acceleration, 100, 99000);
+
+ // M201 settings
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_A, &planner.max_acceleration_mm_per_s2[A_AXIS], 100, 99000, _reset_acceleration_rates);
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_B, &planner.max_acceleration_mm_per_s2[B_AXIS], 100, 99000, _reset_acceleration_rates);
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_C, &planner.max_acceleration_mm_per_s2[C_AXIS], 10, 99000, _reset_acceleration_rates);
+
+ #if ENABLED(DISTINCT_E_FACTORS)
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E, &planner.max_acceleration_mm_per_s2[E_AXIS + active_extruder], 100, 99000, _reset_acceleration_rates);
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E1, &planner.max_acceleration_mm_per_s2[E_AXIS], 100, 99000, _reset_e0_acceleration_rate);
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E2, &planner.max_acceleration_mm_per_s2[E_AXIS + 1], 100, 99000, _reset_e1_acceleration_rate);
+ #if E_STEPPERS > 2
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E3, &planner.max_acceleration_mm_per_s2[E_AXIS + 2], 100, 99000, _reset_e2_acceleration_rate);
+ #if E_STEPPERS > 3
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E4, &planner.max_acceleration_mm_per_s2[E_AXIS + 3], 100, 99000, _reset_e3_acceleration_rate);
+ #if E_STEPPERS > 4
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E5, &planner.max_acceleration_mm_per_s2[E_AXIS + 4], 100, 99000, _reset_e4_acceleration_rate);
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #else
+ MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E, &planner.max_acceleration_mm_per_s2[E_AXIS], 100, 99000, _reset_acceleration_rates);
+ #endif
+
+ END_MENU();
+ }
+
+ // M205 Jerk
+ void lcd_control_motion_jerk_menu() {
+ START_MENU();
+ MENU_BACK(MSG_MOTION);
+
+ MENU_ITEM_EDIT(float3, MSG_VA_JERK, &planner.max_jerk[A_AXIS], 1, 990);
+ MENU_ITEM_EDIT(float3, MSG_VB_JERK, &planner.max_jerk[B_AXIS], 1, 990);
+ #if ENABLED(DELTA)
+ MENU_ITEM_EDIT(float3, MSG_VC_JERK, &planner.max_jerk[C_AXIS], 1, 990);
+ #else
+ MENU_ITEM_EDIT(float52, MSG_VC_JERK, &planner.max_jerk[C_AXIS], 0.1, 990);
+ #endif
+ MENU_ITEM_EDIT(float3, MSG_VE_JERK, &planner.max_jerk[E_AXIS], 1, 990);
+
+ END_MENU();
+ }
+
+ // M92 Steps-per-mm
+ void lcd_control_motion_steps_per_mm_menu() {
+ START_MENU();
+ MENU_BACK(MSG_MOTION);
+
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_ASTEPS, &planner.axis_steps_per_mm[A_AXIS], 5, 9999, _planner_refresh_positioning);
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_BSTEPS, &planner.axis_steps_per_mm[B_AXIS], 5, 9999, _planner_refresh_positioning);
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_CSTEPS, &planner.axis_steps_per_mm[C_AXIS], 5, 9999, _planner_refresh_positioning);
+
+ #if ENABLED(DISTINCT_E_FACTORS)
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_ESTEPS, &planner.axis_steps_per_mm[E_AXIS + active_extruder], 5, 9999, _planner_refresh_positioning);
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_E1STEPS, &planner.axis_steps_per_mm[E_AXIS], 5, 9999, _planner_refresh_e0_positioning);
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_E2STEPS, &planner.axis_steps_per_mm[E_AXIS + 1], 5, 9999, _planner_refresh_e1_positioning);
+ #if E_STEPPERS > 2
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_E3STEPS, &planner.axis_steps_per_mm[E_AXIS + 2], 5, 9999, _planner_refresh_e2_positioning);
+ #if E_STEPPERS > 3
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_E4STEPS, &planner.axis_steps_per_mm[E_AXIS + 3], 5, 9999, _planner_refresh_e3_positioning);
+ #if E_STEPPERS > 4
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_E5STEPS, &planner.axis_steps_per_mm[E_AXIS + 4], 5, 9999, _planner_refresh_e4_positioning);
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #else
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_ESTEPS, &planner.axis_steps_per_mm[E_AXIS], 5, 9999, _planner_refresh_positioning);
+ #endif
+
+ END_MENU();
+ }
+
+ #endif // !SLIM_LCD_MENUS
+
+ /**
+ *
+ * "Control" > "Motion" submenu
+ *
+ */
+
+ void lcd_control_motion_menu() {
+ START_MENU();
+ MENU_BACK(MSG_CONTROL);
+
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ MENU_ITEM(submenu, MSG_ZPROBE_ZOFFSET, lcd_babystep_zoffset);
+ #elif HAS_BED_PROBE
+ MENU_ITEM_EDIT(float32, MSG_ZPROBE_ZOFFSET, &zprobe_zoffset, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX);
+ #endif
+
+ #if DISABLED(SLIM_LCD_MENUS)
+
+ // M203 / M205 - Feedrate items
+ MENU_ITEM(submenu, MSG_VELOCITY, lcd_control_motion_velocity_menu);
+
+ // M201 - Acceleration items
+ MENU_ITEM(submenu, MSG_ACCELERATION, lcd_control_motion_acceleration_menu);
+
+ // M205 - Max Jerk
+ MENU_ITEM(submenu, MSG_JERK, lcd_control_motion_jerk_menu);
+
+ // M92 - Steps Per mm
+ MENU_ITEM(submenu, MSG_STEPS_PER_MM, lcd_control_motion_steps_per_mm_menu);
+
+ #endif // !SLIM_LCD_MENUS
+
+ // M540 S - Abort on endstop hit when SD printing
+ #if ENABLED(ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED)
+ MENU_ITEM_EDIT(bool, MSG_ENDSTOP_ABORT, &stepper.abort_on_endstop_hit);
+ #endif
+
+ END_MENU();
+ }
+
+ #if DISABLED(NO_VOLUMETRICS) || ENABLED(ADVANCED_PAUSE_FEATURE)
+ /**
+ *
+ * "Control" > "Filament" submenu
+ *
+ */
+ void lcd_control_filament_menu() {
+ START_MENU();
+ MENU_BACK(MSG_CONTROL);
+
+ #if ENABLED(LIN_ADVANCE)
+ MENU_ITEM_EDIT(float32, MSG_ADVANCE_K, &planner.extruder_advance_K, 0, 999);
+ #endif
+
+ #if DISABLED(NO_VOLUMETRICS)
+ MENU_ITEM_EDIT_CALLBACK(bool, MSG_VOLUMETRIC_ENABLED, &parser.volumetric_enabled, planner.calculate_volumetric_multipliers);
+
+ if (parser.volumetric_enabled) {
+ #if EXTRUDERS == 1
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM, &planner.filament_size[0], 1.5, 3.25, planner.calculate_volumetric_multipliers);
+ #else // EXTRUDERS > 1
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM, &planner.filament_size[active_extruder], 1.5, 3.25, planner.calculate_volumetric_multipliers);
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM MSG_DIAM_E1, &planner.filament_size[0], 1.5, 3.25, planner.calculate_volumetric_multipliers);
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM MSG_DIAM_E2, &planner.filament_size[1], 1.5, 3.25, planner.calculate_volumetric_multipliers);
+ #if EXTRUDERS > 2
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM MSG_DIAM_E3, &planner.filament_size[2], 1.5, 3.25, planner.calculate_volumetric_multipliers);
+ #if EXTRUDERS > 3
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM MSG_DIAM_E4, &planner.filament_size[3], 1.5, 3.25, planner.calculate_volumetric_multipliers);
+ #if EXTRUDERS > 4
+ MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM MSG_DIAM_E5, &planner.filament_size[4], 1.5, 3.25, planner.calculate_volumetric_multipliers);
+ #endif // EXTRUDERS > 4
+ #endif // EXTRUDERS > 3
+ #endif // EXTRUDERS > 2
+ #endif // EXTRUDERS > 1
+ }
+ #endif
+
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ const float extrude_maxlength =
+ #if ENABLED(PREVENT_LENGTHY_EXTRUDE)
+ EXTRUDE_MAXLENGTH
+ #else
+ 999.0f
+ #endif
+ ;
+
+ #if EXTRUDERS == 1
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD, &filament_change_unload_length[0], 0.0, extrude_maxlength);
+ #else // EXTRUDERS > 1
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD, &filament_change_unload_length[active_extruder], 0.0, extrude_maxlength);
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD MSG_DIAM_E1, &filament_change_unload_length[0], 0.0, extrude_maxlength);
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD MSG_DIAM_E2, &filament_change_unload_length[1], 0.0, extrude_maxlength);
+ #if EXTRUDERS > 2
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD MSG_DIAM_E3, &filament_change_unload_length[2], 0.0, extrude_maxlength);
+ #if EXTRUDERS > 3
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD MSG_DIAM_E4, &filament_change_unload_length[3], 0.0, extrude_maxlength);
+ #if EXTRUDERS > 4
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD MSG_DIAM_E5, &filament_change_unload_length[4], 0.0, extrude_maxlength);
+ #endif // EXTRUDERS > 4
+ #endif // EXTRUDERS > 3
+ #endif // EXTRUDERS > 2
+ #endif // EXTRUDERS > 1
+
+ #if EXTRUDERS == 1
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD, &filament_change_load_length[0], 0.0, extrude_maxlength);
+ #else // EXTRUDERS > 1
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD, &filament_change_load_length[active_extruder], 0.0, extrude_maxlength);
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD MSG_DIAM_E1, &filament_change_load_length[0], 0.0, extrude_maxlength);
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD MSG_DIAM_E2, &filament_change_load_length[1], 0.0, extrude_maxlength);
+ #if EXTRUDERS > 2
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD MSG_DIAM_E3, &filament_change_load_length[2], 0.0, extrude_maxlength);
+ #if EXTRUDERS > 3
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD MSG_DIAM_E4, &filament_change_load_length[3], 0.0, extrude_maxlength);
+ #if EXTRUDERS > 4
+ MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD MSG_DIAM_E5, &filament_change_load_length[4], 0.0, extrude_maxlength);
+ #endif // EXTRUDERS > 4
+ #endif // EXTRUDERS > 3
+ #endif // EXTRUDERS > 2
+ #endif // EXTRUDERS > 1
+ #endif
+
+ END_MENU();
+ }
+ #endif // !NO_VOLUMETRICS || ADVANCED_PAUSE_FEATURE
+
+ /**
+ *
+ * "Control" > "Retract" submenu
+ *
+ */
+ #if ENABLED(FWRETRACT)
+
+ void lcd_control_retract_menu() {
+ START_MENU();
+ MENU_BACK(MSG_CONTROL);
+ MENU_ITEM_EDIT_CALLBACK(bool, MSG_AUTORETRACT, &fwretract.autoretract_enabled, fwretract.refresh_autoretract);
+ MENU_ITEM_EDIT(float52, MSG_CONTROL_RETRACT, &fwretract.retract_length, 0, 100);
+ #if EXTRUDERS > 1
+ MENU_ITEM_EDIT(float52, MSG_CONTROL_RETRACT_SWAP, &fwretract.swap_retract_length, 0, 100);
+ #endif
+ MENU_ITEM_EDIT(float3, MSG_CONTROL_RETRACTF, &fwretract.retract_feedrate_mm_s, 1, 999);
+ MENU_ITEM_EDIT(float52, MSG_CONTROL_RETRACT_ZLIFT, &fwretract.retract_zlift, 0, 999);
+ MENU_ITEM_EDIT(float52, MSG_CONTROL_RETRACT_RECOVER, &fwretract.retract_recover_length, -100, 100);
+ #if EXTRUDERS > 1
+ MENU_ITEM_EDIT(float52, MSG_CONTROL_RETRACT_RECOVER_SWAP, &fwretract.swap_retract_recover_length, -100, 100);
+ #endif
+ MENU_ITEM_EDIT(float3, MSG_CONTROL_RETRACT_RECOVERF, &fwretract.retract_recover_feedrate_mm_s, 1, 999);
+ #if EXTRUDERS > 1
+ MENU_ITEM_EDIT(float3, MSG_CONTROL_RETRACT_RECOVER_SWAPF, &fwretract.swap_retract_recover_feedrate_mm_s, 1, 999);
+ #endif
+ END_MENU();
+ }
+
+ #endif // FWRETRACT
+
+ #if ENABLED(SDSUPPORT)
+
+ #if !PIN_EXISTS(SD_DETECT)
+ void lcd_sd_refresh() {
+ card.initsd();
+ encoderTopLine = 0;
+ }
+ #endif
+
+ void lcd_sd_updir() {
+ encoderPosition = card.updir() ? ENCODER_STEPS_PER_MENU_ITEM : 0;
+ encoderTopLine = 0;
+ screen_changed = true;
+ lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW;
+ }
+
+ /**
+ *
+ * "Print from SD" submenu
+ *
+ */
+
+ #if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
+ uint32_t last_sdfile_encoderPosition = 0xFFFF;
+
+ void lcd_reselect_last_file() {
+ if (last_sdfile_encoderPosition == 0xFFFF) return;
+ #if ENABLED(DOGLCD)
+ // Some of this is a hack to force the screen update to work.
+ // TODO: Fix the real issue that causes this!
+ lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+ _lcd_synchronize();
+ safe_delay(50);
+ _lcd_synchronize();
+ lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+ drawing_screen = screen_changed = true;
+ #endif
+
+ lcd_goto_screen(lcd_sdcard_menu, last_sdfile_encoderPosition);
+ defer_return_to_status = true;
+ last_sdfile_encoderPosition = 0xFFFF;
+
+ #if ENABLED(DOGLCD)
+ lcd_update();
+ #endif
+ }
+ #endif
+
+ void lcd_sdcard_menu() {
+ ENCODER_DIRECTION_MENUS();
+
+ const uint16_t fileCnt = card.get_num_Files();
+
+ START_MENU();
+ MENU_BACK(MSG_MAIN);
+ card.getWorkDirName();
+ if (card.filename[0] == '/') {
+ #if !PIN_EXISTS(SD_DETECT)
+ MENU_ITEM(function, LCD_STR_REFRESH MSG_REFRESH, lcd_sd_refresh);
+ #endif
+ }
+ else {
+ MENU_ITEM(function, LCD_STR_FOLDER "..", lcd_sd_updir);
+ }
+
+ for (uint16_t i = 0; i < fileCnt; i++) {
+ if (_menuLineNr == _thisItemNr) {
+ const uint16_t nr =
+ #if ENABLED(SDCARD_RATHERRECENTFIRST) && DISABLED(SDCARD_SORT_ALPHA)
+ fileCnt - 1 -
+ #endif
+ i;
+
+ #if ENABLED(SDCARD_SORT_ALPHA)
+ card.getfilename_sorted(nr);
+ #else
+ card.getfilename(nr);
+ #endif
+
+ if (card.filenameIsDir)
+ MENU_ITEM(sddirectory, MSG_CARD_MENU, card.filename, card.longFilename);
+ else
+ MENU_ITEM(sdfile, MSG_CARD_MENU, card.filename, card.longFilename);
+ }
+ else {
+ MENU_ITEM_DUMMY();
+ }
+ }
+ END_MENU();
+ }
+
+ #endif // SDSUPPORT
+
+ #if ENABLED(LCD_INFO_MENU)
+
+ #if ENABLED(PRINTCOUNTER)
+ /**
+ *
+ * About Printer > Statistics submenu
+ *
+ */
+ void lcd_info_stats_menu() {
+ if (use_click()) { return lcd_goto_previous_menu(); }
+
+ char buffer[21];
+ printStatistics stats = print_job_timer.getStats();
+
+ START_SCREEN(); // 12345678901234567890
+ STATIC_ITEM(MSG_INFO_PRINT_COUNT ": ", false, false, itostr3left(stats.totalPrints)); // Print Count: 999
+ STATIC_ITEM(MSG_INFO_COMPLETED_PRINTS": ", false, false, itostr3left(stats.finishedPrints)); // Completed : 666
+
+ duration_t elapsed = stats.printTime;
+ elapsed.toString(buffer);
+
+ STATIC_ITEM(MSG_INFO_PRINT_TIME ": ", false, false); // Total print Time:
+ STATIC_ITEM("", false, false, buffer); // 99y 364d 23h 59m 59s
+
+ elapsed = stats.longestPrint;
+ elapsed.toString(buffer);
+
+ STATIC_ITEM(MSG_INFO_PRINT_LONGEST ": ", false, false); // Longest job time:
+ STATIC_ITEM("", false, false, buffer); // 99y 364d 23h 59m 59s
+
+ sprintf_P(buffer, PSTR("%ld.%im"), long(stats.filamentUsed / 1000), int16_t(stats.filamentUsed / 100) % 10);
+ STATIC_ITEM(MSG_INFO_PRINT_FILAMENT ": ", false, false); // Extruded total:
+ STATIC_ITEM("", false, false, buffer); // 125m
+ END_SCREEN();
+ }
+ #endif // PRINTCOUNTER
+
+ /**
+ *
+ * About Printer > Thermistors
+ *
+ */
+ void lcd_info_thermistors_menu() {
+ if (use_click()) { return lcd_goto_previous_menu(); }
+ START_SCREEN();
+ #define THERMISTOR_ID TEMP_SENSOR_0
+ #include "thermistornames.h"
+ STATIC_ITEM("T0: " THERMISTOR_NAME, false, true);
+ STATIC_ITEM(MSG_INFO_MIN_TEMP ": " STRINGIFY(HEATER_0_MINTEMP), false);
+ STATIC_ITEM(MSG_INFO_MAX_TEMP ": " STRINGIFY(HEATER_0_MAXTEMP), false);
+
+ #if TEMP_SENSOR_1 != 0
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_1
+ #include "thermistornames.h"
+ STATIC_ITEM("T1: " THERMISTOR_NAME, false, true);
+ STATIC_ITEM(MSG_INFO_MIN_TEMP ": " STRINGIFY(HEATER_1_MINTEMP), false);
+ STATIC_ITEM(MSG_INFO_MAX_TEMP ": " STRINGIFY(HEATER_1_MAXTEMP), false);
+ #endif
+
+ #if TEMP_SENSOR_2 != 0
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_2
+ #include "thermistornames.h"
+ STATIC_ITEM("T2: " THERMISTOR_NAME, false, true);
+ STATIC_ITEM(MSG_INFO_MIN_TEMP ": " STRINGIFY(HEATER_2_MINTEMP), false);
+ STATIC_ITEM(MSG_INFO_MAX_TEMP ": " STRINGIFY(HEATER_2_MAXTEMP), false);
+ #endif
+
+ #if TEMP_SENSOR_3 != 0
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_3
+ #include "thermistornames.h"
+ STATIC_ITEM("T3: " THERMISTOR_NAME, false, true);
+ STATIC_ITEM(MSG_INFO_MIN_TEMP ": " STRINGIFY(HEATER_3_MINTEMP), false);
+ STATIC_ITEM(MSG_INFO_MAX_TEMP ": " STRINGIFY(HEATER_3_MAXTEMP), false);
+ #endif
+
+ #if TEMP_SENSOR_4 != 0
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_4
+ #include "thermistornames.h"
+ STATIC_ITEM("T4: " THERMISTOR_NAME, false, true);
+ STATIC_ITEM(MSG_INFO_MIN_TEMP ": " STRINGIFY(HEATER_4_MINTEMP), false);
+ STATIC_ITEM(MSG_INFO_MAX_TEMP ": " STRINGIFY(HEATER_4_MAXTEMP), false);
+ #endif
+
+ #if TEMP_SENSOR_BED != 0
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_BED
+ #include "thermistornames.h"
+ STATIC_ITEM("TBed:" THERMISTOR_NAME, false, true);
+ STATIC_ITEM(MSG_INFO_MIN_TEMP ": " STRINGIFY(BED_MINTEMP), false);
+ STATIC_ITEM(MSG_INFO_MAX_TEMP ": " STRINGIFY(BED_MAXTEMP), false);
+ #endif
+ END_SCREEN();
+ }
+
+ /**
+ *
+ * About Printer > Board Info
+ *
+ */
+ void lcd_info_board_menu() {
+ if (use_click()) { return lcd_goto_previous_menu(); }
+ START_SCREEN();
+ STATIC_ITEM(BOARD_NAME, true, true); // MyPrinterController
+ STATIC_ITEM(MSG_INFO_BAUDRATE ": " STRINGIFY(BAUDRATE), true); // Baud: 250000
+ STATIC_ITEM(MSG_INFO_PROTOCOL ": " PROTOCOL_VERSION, true); // Protocol: 1.0
+ #if POWER_SUPPLY == 0
+ STATIC_ITEM(MSG_INFO_PSU ": Generic", true);
+ #elif POWER_SUPPLY == 1
+ STATIC_ITEM(MSG_INFO_PSU ": ATX", true); // Power Supply: ATX
+ #elif POWER_SUPPLY == 2
+ STATIC_ITEM(MSG_INFO_PSU ": XBox", true); // Power Supply: XBox
+ #endif
+ END_SCREEN();
+ }
+
+ /**
+ *
+ * About Printer > Printer Info
+ *
+ */
+ void lcd_info_printer_menu() {
+ if (use_click()) { return lcd_goto_previous_menu(); }
+ START_SCREEN();
+ STATIC_ITEM(MSG_MARLIN, true, true); // Marlin
+ STATIC_ITEM(SHORT_BUILD_VERSION, true); // x.x.x-Branch
+ STATIC_ITEM(STRING_DISTRIBUTION_DATE, true); // YYYY-MM-DD HH:MM
+ STATIC_ITEM(MACHINE_NAME, true); // My3DPrinter
+ STATIC_ITEM(WEBSITE_URL, true); // www.my3dprinter.com
+ STATIC_ITEM(MSG_INFO_EXTRUDERS ": " STRINGIFY(EXTRUDERS), true); // Extruders: 2
+ #if ENABLED(AUTO_BED_LEVELING_3POINT)
+ STATIC_ITEM(MSG_3POINT_LEVELING, true); // 3-Point Leveling
+ #elif ENABLED(AUTO_BED_LEVELING_LINEAR)
+ STATIC_ITEM(MSG_LINEAR_LEVELING, true); // Linear Leveling
+ #elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
+ STATIC_ITEM(MSG_BILINEAR_LEVELING, true); // Bi-linear Leveling
+ #elif ENABLED(AUTO_BED_LEVELING_UBL)
+ STATIC_ITEM(MSG_UBL_LEVELING, true); // Unified Bed Leveling
+ #elif ENABLED(MESH_BED_LEVELING)
+ STATIC_ITEM(MSG_MESH_LEVELING, true); // Mesh Leveling
+ #endif
+ END_SCREEN();
+ }
+
+ /**
+ *
+ * "About Printer" submenu
+ *
+ */
+ void lcd_info_menu() {
+ START_MENU();
+ MENU_BACK(MSG_MAIN);
+ MENU_ITEM(submenu, MSG_INFO_PRINTER_MENU, lcd_info_printer_menu); // Printer Info >
+ MENU_ITEM(submenu, MSG_INFO_BOARD_MENU, lcd_info_board_menu); // Board Info >
+ MENU_ITEM(submenu, MSG_INFO_THERMISTOR_MENU, lcd_info_thermistors_menu); // Thermistors >
+ #if ENABLED(PRINTCOUNTER)
+ MENU_ITEM(submenu, MSG_INFO_STATS_MENU, lcd_info_stats_menu); // Printer Statistics >
+ #endif
+ END_MENU();
+ }
+ #endif // LCD_INFO_MENU
+
+ /**
+ *
+ * LED Menu
+ *
+ */
+
+ #if ENABLED(LED_CONTROL_MENU)
+
+ #if ENABLED(LED_COLOR_PRESETS)
+
+ void lcd_led_presets_menu() {
+ START_MENU();
+ #if LCD_HEIGHT > 2
+ STATIC_ITEM(MSG_LED_PRESETS, true, true);
+ #endif
+ MENU_BACK(MSG_LED_CONTROL);
+ MENU_ITEM(function, MSG_SET_LEDS_WHITE, leds.set_white);
+ MENU_ITEM(function, MSG_SET_LEDS_RED, leds.set_red);
+ MENU_ITEM(function, MSG_SET_LEDS_ORANGE, leds.set_orange);
+ MENU_ITEM(function, MSG_SET_LEDS_YELLOW,leds.set_yellow);
+ MENU_ITEM(function, MSG_SET_LEDS_GREEN, leds.set_green);
+ MENU_ITEM(function, MSG_SET_LEDS_BLUE, leds.set_blue);
+ MENU_ITEM(function, MSG_SET_LEDS_INDIGO, leds.set_indigo);
+ MENU_ITEM(function, MSG_SET_LEDS_VIOLET, leds.set_violet);
+ END_MENU();
+ }
+ #endif // LED_COLOR_PRESETS
+
+ void lcd_led_custom_menu() {
+ START_MENU();
+ MENU_BACK(MSG_LED_CONTROL);
+ MENU_ITEM_EDIT_CALLBACK(int8, MSG_INTENSITY_R, &leds.color.r, 0, 255, leds.update, true);
+ MENU_ITEM_EDIT_CALLBACK(int8, MSG_INTENSITY_G, &leds.color.g, 0, 255, leds.update, true);
+ MENU_ITEM_EDIT_CALLBACK(int8, MSG_INTENSITY_B, &leds.color.b, 0, 255, leds.update, true);
+ #if ENABLED(RGBW_LED) || ENABLED(NEOPIXEL_LED)
+ MENU_ITEM_EDIT_CALLBACK(int8, MSG_INTENSITY_W, &leds.color.w, 0, 255, leds.update, true);
+ #if ENABLED(NEOPIXEL_LED)
+ MENU_ITEM_EDIT_CALLBACK(int8, MSG_LED_BRIGHTNESS, &leds.color.i, 0, 255, leds.update, true);
+ #endif
+ #endif
+ END_MENU();
+ }
+
+ void lcd_led_menu() {
+ START_MENU();
+ MENU_BACK(MSG_MAIN);
+ if (leds.lights_on)
+ MENU_ITEM(function, MSG_LEDS_OFF, leds.toggle);
+ else
+ MENU_ITEM(function, MSG_LEDS_ON, leds.toggle);
+ MENU_ITEM(function, MSG_SET_LEDS_DEFAULT, leds.set_default);
+ #if ENABLED(LED_COLOR_PRESETS)
+ MENU_ITEM(submenu, MSG_LED_PRESETS, lcd_led_presets_menu);
+ #endif
+ MENU_ITEM(submenu, MSG_CUSTOM_LEDS, lcd_led_custom_menu);
+ END_MENU();
+ }
+
+ #endif // LED_CONTROL_MENU
+
+ /**
+ *
+ * Filament Change Feature Screens
+ *
+ */
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+
+ /**
+ *
+ * "Change Filament" > "Change/Unload/Load Filament" submenu
+ *
+ */
+ static AdvancedPauseMode _change_filament_temp_mode;
+ static int8_t _change_filament_temp_extruder;
+
+ static const char* _change_filament_temp_command() {
+ switch (_change_filament_temp_mode) {
+ case ADVANCED_PAUSE_MODE_LOAD_FILAMENT:
+ return PSTR("M701 T%d");
+ case ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT:
+ return _change_filament_temp_extruder >= 0 ? PSTR("M702 T%d") : PSTR("M702 ;%d");
+ case ADVANCED_PAUSE_MODE_PAUSE_PRINT:
+ default:
+ return PSTR("M600 B0 T%d");
+ }
+ return PSTR(MSG_FILAMENTCHANGE);
+ }
+
+ void _change_filament_temp(const uint8_t index) {
+ char cmd[11];
+ sprintf_P(cmd, _change_filament_temp_command(), _change_filament_temp_extruder);
+ thermalManager.setTargetHotend(index == 1 ? PREHEAT_1_TEMP_HOTEND : PREHEAT_2_TEMP_HOTEND, _change_filament_temp_extruder);
+ lcd_enqueue_command(cmd);
+ }
+ void _lcd_change_filament_temp_1_menu() { _change_filament_temp(1); }
+ void _lcd_change_filament_temp_2_menu() { _change_filament_temp(2); }
+
+ static const char* change_filament_header(const AdvancedPauseMode mode) {
+ switch (mode) {
+ case ADVANCED_PAUSE_MODE_LOAD_FILAMENT:
+ return PSTR(MSG_FILAMENTLOAD);
+ case ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT:
+ return PSTR(MSG_FILAMENTUNLOAD);
+ default: break;
+ }
+ return PSTR(MSG_FILAMENTCHANGE);
+ }
+
+ void _lcd_temp_menu_filament_op(const AdvancedPauseMode mode, const int8_t extruder) {
+ _change_filament_temp_mode = mode;
+ _change_filament_temp_extruder = extruder;
+ START_MENU();
+ if (LCD_HEIGHT >= 4) STATIC_ITEM_P(change_filament_header(mode), true, true);
+ MENU_BACK(MSG_FILAMENTCHANGE);
+ MENU_ITEM(submenu, MSG_PREHEAT_1, _lcd_change_filament_temp_1_menu);
+ MENU_ITEM(submenu, MSG_PREHEAT_2, _lcd_change_filament_temp_2_menu);
+ END_MENU();
+ }
+ void lcd_temp_menu_e0_filament_change() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_PAUSE_PRINT, 0); }
+ void lcd_temp_menu_e0_filament_load() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_LOAD_FILAMENT, 0); }
+ void lcd_temp_menu_e0_filament_unload() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, 0); }
+ #if E_STEPPERS > 1
+ void lcd_temp_menu_e1_filament_change() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_PAUSE_PRINT, 1); }
+ void lcd_temp_menu_e1_filament_load() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_LOAD_FILAMENT, 1); }
+ void lcd_temp_menu_e1_filament_unload() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, 1); }
+ #if ENABLED(FILAMENT_UNLOAD_ALL_EXTRUDERS)
+ void lcd_unload_filament_all_temp_menu() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, -1); }
+ #endif
+ #if E_STEPPERS > 2
+ void lcd_temp_menu_e2_filament_change() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_PAUSE_PRINT, 2); }
+ void lcd_temp_menu_e2_filament_load() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_LOAD_FILAMENT, 2); }
+ void lcd_temp_menu_e2_filament_unload() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, 2); }
+ #if E_STEPPERS > 3
+ void lcd_temp_menu_e3_filament_change() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_PAUSE_PRINT, 3); }
+ void lcd_temp_menu_e3_filament_load() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_LOAD_FILAMENT, 3); }
+ void lcd_temp_menu_e3_filament_unload() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, 3); }
+ #if E_STEPPERS > 4
+ void lcd_temp_menu_e4_filament_change() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_PAUSE_PRINT, 4); }
+ void lcd_temp_menu_e4_filament_load() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_LOAD_FILAMENT, 4); }
+ void lcd_temp_menu_e4_filament_unload() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, 4); }
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #endif // E_STEPPERS > 1
+
+ /**
+ *
+ * "Change Filament" submenu
+ *
+ */
+ #if E_STEPPERS > 1 || ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ void lcd_change_filament_menu() {
+ START_MENU();
+ MENU_BACK(MSG_PREPARE);
+
+ // Change filament
+ #if E_STEPPERS == 1
+ PGM_P msg0 = PSTR(MSG_FILAMENTCHANGE);
+ if (thermalManager.targetTooColdToExtrude(active_extruder))
+ MENU_ITEM_P(submenu, msg0, lcd_temp_menu_e0_filament_change);
+ else
+ MENU_ITEM_P(gcode, msg0, PSTR("M600 B0"));
+ #else
+ PGM_P msg0 = PSTR(MSG_FILAMENTCHANGE " " MSG_E1);
+ PGM_P msg1 = PSTR(MSG_FILAMENTCHANGE " " MSG_E2);
+ if (thermalManager.targetTooColdToExtrude(0))
+ MENU_ITEM_P(submenu, msg0, lcd_temp_menu_e0_filament_change);
+ else
+ MENU_ITEM_P(gcode, msg0, PSTR("M600 B0 T0"));
+ if (thermalManager.targetTooColdToExtrude(1))
+ MENU_ITEM_P(submenu, msg1, lcd_temp_menu_e1_filament_change);
+ else
+ MENU_ITEM_P(gcode, msg1, PSTR("M600 B0 T1"));
+ #if E_STEPPERS > 2
+ PGM_P msg2 = PSTR(MSG_FILAMENTCHANGE " " MSG_E3);
+ if (thermalManager.targetTooColdToExtrude(2))
+ MENU_ITEM_P(submenu, msg2, lcd_temp_menu_e2_filament_change);
+ else
+ MENU_ITEM_P(gcode, msg2, PSTR("M600 B0 T2"));
+ #if E_STEPPERS > 3
+ PGM_P msg3 = PSTR(MSG_FILAMENTCHANGE " " MSG_E4);
+ if (thermalManager.targetTooColdToExtrude(3))
+ MENU_ITEM_P(submenu, msg3, lcd_temp_menu_e3_filament_change);
+ else
+ MENU_ITEM_P(gcode, msg3, PSTR("M600 B0 T3"));
+ #if E_STEPPERS > 4
+ PGM_P msg4 = PSTR(MSG_FILAMENTCHANGE " " MSG_E5);
+ if (thermalManager.targetTooColdToExtrude(4))
+ MENU_ITEM_P(submenu, msg4, lcd_temp_menu_e4_filament_change);
+ else
+ MENU_ITEM_P(gcode, msg4, PSTR("M600 B0 T4"));
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #endif // E_STEPPERS == 1
+
+ #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ if (!planner.movesplanned() && !IS_SD_FILE_OPEN) {
+ // Load filament
+ #if E_STEPPERS == 1
+ PGM_P msg0 = PSTR(MSG_FILAMENTLOAD);
+ if (thermalManager.targetTooColdToExtrude(active_extruder))
+ MENU_ITEM_P(submenu, msg0, lcd_temp_menu_e0_filament_load);
+ else
+ MENU_ITEM_P(gcode, msg0, PSTR("M701"));
+ #else
+ PGM_P msg0 = PSTR(MSG_FILAMENTLOAD " " MSG_E1);
+ PGM_P msg1 = PSTR(MSG_FILAMENTLOAD " " MSG_E2);
+ if (thermalManager.targetTooColdToExtrude(0))
+ MENU_ITEM_P(submenu, msg0, lcd_temp_menu_e0_filament_load);
+ else
+ MENU_ITEM_P(gcode, msg0, PSTR("M701 T0"));
+ if (thermalManager.targetTooColdToExtrude(1))
+ MENU_ITEM_P(submenu, msg1, lcd_temp_menu_e1_filament_load);
+ else
+ MENU_ITEM_P(gcode, msg1, PSTR("M701 T1"));
+ #if E_STEPPERS > 2
+ PGM_P msg2 = PSTR(MSG_FILAMENTLOAD " " MSG_E3);
+ if (thermalManager.targetTooColdToExtrude(2))
+ MENU_ITEM_P(submenu, msg2, lcd_temp_menu_e2_filament_load);
+ else
+ MENU_ITEM_P(gcode, msg2, PSTR("M701 T2"));
+ #if E_STEPPERS > 3
+ PGM_P msg3 = PSTR(MSG_FILAMENTLOAD " " MSG_E4);
+ if (thermalManager.targetTooColdToExtrude(3))
+ MENU_ITEM_P(submenu, msg3, lcd_temp_menu_e3_filament_load);
+ else
+ MENU_ITEM_P(gcode, msg3, PSTR("M701 T3"));
+ #if E_STEPPERS > 4
+ PGM_P msg4 = PSTR(MSG_FILAMENTLOAD " " MSG_E5);
+ if (thermalManager.targetTooColdToExtrude(4))
+ MENU_ITEM_P(submenu, msg4, lcd_temp_menu_e4_filament_load);
+ else
+ MENU_ITEM_P(gcode, msg4, PSTR("M701 T4"));
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #endif // E_STEPPERS == 1
+
+ // Unload filament
+ #if E_STEPPERS == 1
+ if (thermalManager.targetHotEnoughToExtrude(active_extruder))
+ MENU_ITEM(gcode, MSG_FILAMENTUNLOAD, PSTR("M702"));
+ else
+ MENU_ITEM(submenu, MSG_FILAMENTUNLOAD, lcd_temp_menu_e0_filament_unload);
+ #else
+ #if ENABLED(FILAMENT_UNLOAD_ALL_EXTRUDERS)
+ if (thermalManager.targetHotEnoughToExtrude(0)
+ #if E_STEPPERS > 1
+ && thermalManager.targetHotEnoughToExtrude(1)
+ #if E_STEPPERS > 2
+ && thermalManager.targetHotEnoughToExtrude(2)
+ #if E_STEPPERS > 3
+ && thermalManager.targetHotEnoughToExtrude(3)
+ #if E_STEPPERS > 4
+ && thermalManager.targetHotEnoughToExtrude(4)
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #endif // E_STEPPERS > 1
+ )
+ MENU_ITEM(gcode, MSG_FILAMENTUNLOAD_ALL, PSTR("M702"));
+ else
+ MENU_ITEM(submenu, MSG_FILAMENTUNLOAD_ALL, lcd_unload_filament_all_temp_menu);
+ #endif
+ if (thermalManager.targetHotEnoughToExtrude(0))
+ MENU_ITEM(gcode, MSG_FILAMENTUNLOAD " " MSG_E1, PSTR("M702 T0"));
+ else
+ MENU_ITEM(submenu, MSG_FILAMENTUNLOAD " " MSG_E1, lcd_temp_menu_e0_filament_unload);
+ if (thermalManager.targetHotEnoughToExtrude(1))
+ MENU_ITEM(gcode, MSG_FILAMENTUNLOAD " " MSG_E2, PSTR("M702 T1"));
+ else
+ MENU_ITEM(submenu, MSG_FILAMENTUNLOAD " " MSG_E2, lcd_temp_menu_e1_filament_unload);
+ #if E_STEPPERS > 2
+ if (thermalManager.targetHotEnoughToExtrude(2))
+ MENU_ITEM(gcode, MSG_FILAMENTUNLOAD " " MSG_E3, PSTR("M702 T2"));
+ else
+ MENU_ITEM(submenu, MSG_FILAMENTUNLOAD " " MSG_E3, lcd_temp_menu_e2_filament_unload);
+ #if E_STEPPERS > 3
+ if (thermalManager.targetHotEnoughToExtrude(3))
+ MENU_ITEM(gcode, MSG_FILAMENTUNLOAD " " MSG_E4, PSTR("M702 T3"));
+ else
+ MENU_ITEM(submenu, MSG_FILAMENTUNLOAD " " MSG_E4, lcd_temp_menu_e3_filament_unload);
+ #if E_STEPPERS > 4
+ if (thermalManager.targetHotEnoughToExtrude(4))
+ MENU_ITEM(gcode, MSG_FILAMENTUNLOAD " " MSG_E5, PSTR("M702 T4"));
+ else
+ MENU_ITEM(submenu, MSG_FILAMENTUNLOAD " " MSG_E5, lcd_temp_menu_e4_filament_unload);
+ #endif // E_STEPPERS > 4
+ #endif // E_STEPPERS > 3
+ #endif // E_STEPPERS > 2
+ #endif // E_STEPPERS == 1
+ }
+ #endif
+
+ END_MENU();
+ }
+ #endif
+
+ static AdvancedPauseMode advanced_pause_mode = ADVANCED_PAUSE_MODE_PAUSE_PRINT;
+ static uint8_t hotend_status_extruder = 0;
+
+ static const char* advanced_pause_header() {
+ switch (advanced_pause_mode) {
+ case ADVANCED_PAUSE_MODE_LOAD_FILAMENT:
+ return PSTR(MSG_FILAMENT_CHANGE_HEADER_LOAD);
+ case ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT:
+ return PSTR(MSG_FILAMENT_CHANGE_HEADER_UNLOAD);
+ default: break;
+ }
+ return PSTR(MSG_FILAMENT_CHANGE_HEADER_PAUSE);
+ }
+
+ // Portions from STATIC_ITEM...
+ #define HOTEND_STATUS_ITEM() do { \
+ if (_menuLineNr == _thisItemNr) { \
+ if (lcdDrawUpdate) { \
+ lcd_implementation_drawmenu_static(_lcdLineNr, PSTR(MSG_FILAMENT_CHANGE_NOZZLE), false, true); \
+ lcd_implementation_hotend_status(_lcdLineNr, hotend_status_extruder); \
+ } \
+ if (_skipStatic && encoderLine <= _thisItemNr) { \
+ encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; \
+ ++encoderLine; \
+ } \
+ lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT; \
+ } \
+ ++_thisItemNr; \
+ }while(0)
+
+ void lcd_advanced_pause_resume_print() {
+ advanced_pause_menu_response = ADVANCED_PAUSE_RESPONSE_RESUME_PRINT;
+ }
+
+ void lcd_advanced_pause_extrude_more() {
+ advanced_pause_menu_response = ADVANCED_PAUSE_RESPONSE_EXTRUDE_MORE;
+ }
+
+ void lcd_advanced_pause_option_menu() {
+ START_MENU();
+ #if LCD_HEIGHT > 2
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_OPTION_HEADER, true, false);
+ #endif
+ MENU_ITEM(function, MSG_FILAMENT_CHANGE_OPTION_RESUME, lcd_advanced_pause_resume_print);
+ MENU_ITEM(function, MSG_FILAMENT_CHANGE_OPTION_PURGE, lcd_advanced_pause_extrude_more);
+ END_MENU();
+ }
+
+ void lcd_advanced_pause_init_message() {
+ START_SCREEN();
+ STATIC_ITEM_P(advanced_pause_header(), true, true);
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_INIT_1);
+ #ifdef MSG_FILAMENT_CHANGE_INIT_2
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_INIT_2);
+ #define __FC_LINES_A 3
+ #else
+ #define __FC_LINES_A 2
+ #endif
+ #ifdef MSG_FILAMENT_CHANGE_INIT_3
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_INIT_3);
+ #define _FC_LINES_A (__FC_LINES_A + 1)
+ #else
+ #define _FC_LINES_A __FC_LINES_A
+ #endif
+ #if LCD_HEIGHT > _FC_LINES_A + 1
+ STATIC_ITEM(" ");
+ #endif
+ HOTEND_STATUS_ITEM();
+ END_SCREEN();
+ }
+
+ void lcd_advanced_pause_unload_message() {
+ START_SCREEN();
+ STATIC_ITEM_P(advanced_pause_header(), true, true);
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_UNLOAD_1);
+ #ifdef MSG_FILAMENT_CHANGE_UNLOAD_2
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_UNLOAD_2);
+ #define __FC_LINES_B 3
+ #else
+ #define __FC_LINES_B 2
+ #endif
+ #ifdef MSG_FILAMENT_CHANGE_UNLOAD_3
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_UNLOAD_3);
+ #define _FC_LINES_B (__FC_LINES_B + 1)
+ #else
+ #define _FC_LINES_B __FC_LINES_B
+ #endif
+ #if LCD_HEIGHT > _FC_LINES_B + 1
+ STATIC_ITEM(" ");
+ #endif
+ HOTEND_STATUS_ITEM();
+ END_SCREEN();
+ }
+
+ void lcd_advanced_pause_wait_for_nozzles_to_heat() {
+ START_SCREEN();
+ STATIC_ITEM_P(advanced_pause_header(), true, true);
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_HEATING_1);
+ #ifdef MSG_FILAMENT_CHANGE_HEATING_2
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_HEATING_2);
+ #define _FC_LINES_C 3
+ #else
+ #define _FC_LINES_C 2
+ #endif
+ #if LCD_HEIGHT > _FC_LINES_C + 1
+ STATIC_ITEM(" ");
+ #endif
+ HOTEND_STATUS_ITEM();
+ END_SCREEN();
+ }
+
+ void lcd_advanced_pause_heat_nozzle() {
+ START_SCREEN();
+ STATIC_ITEM_P(advanced_pause_header(), true, true);
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_HEAT_1);
+ #ifdef MSG_FILAMENT_CHANGE_INSERT_2
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_HEAT_2);
+ #define _FC_LINES_D 3
+ #else
+ #define _FC_LINES_D 2
+ #endif
+ #if LCD_HEIGHT > _FC_LINES_D + 1
+ STATIC_ITEM(" ");
+ #endif
+ HOTEND_STATUS_ITEM();
+ END_SCREEN();
+ }
+
+ void lcd_advanced_pause_insert_message() {
+ START_SCREEN();
+ STATIC_ITEM_P(advanced_pause_header(), true, true);
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_INSERT_1);
+ #ifdef MSG_FILAMENT_CHANGE_INSERT_2
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_INSERT_2);
+ #define __FC_LINES_E 3
+ #else
+ #define __FC_LINES_E 2
+ #endif
+ #ifdef MSG_FILAMENT_CHANGE_INSERT_3
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_INSERT_3);
+ #define _FC_LINES_E (__FC_LINES_E + 1)
+ #else
+ #define _FC_LINES_E __FC_LINES_E
+ #endif
+ #if LCD_HEIGHT > _FC_LINES_E + 1
+ STATIC_ITEM(" ");
+ #endif
+ HOTEND_STATUS_ITEM();
+ END_SCREEN();
+ }
+
+ void lcd_advanced_pause_load_message() {
+ START_SCREEN();
+ STATIC_ITEM_P(advanced_pause_header(), true, true);
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_LOAD_1);
+ #ifdef MSG_FILAMENT_CHANGE_LOAD_2
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_LOAD_2);
+ #define __FC_LINES_F 3
+ #else
+ #define __FC_LINES_F 2
+ #endif
+ #ifdef MSG_FILAMENT_CHANGE_LOAD_3
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_LOAD_3);
+ #define _FC_LINES_F (__FC_LINES_F + 1)
+ #else
+ #define _FC_LINES_F __FC_LINES_F
+ #endif
+ #if LCD_HEIGHT > _FC_LINES_F + 1
+ STATIC_ITEM(" ");
+ #endif
+ HOTEND_STATUS_ITEM();
+ END_SCREEN();
+ }
+
+ void lcd_advanced_pause_purge_message() {
+ START_SCREEN();
+ STATIC_ITEM_P(advanced_pause_header(), true, true);
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_PURGE_1);
+ #ifdef MSG_FILAMENT_CHANGE_PURGE_2
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_PURGE_2);
+ #define __FC_LINES_G 3
+ #else
+ #define __FC_LINES_G 2
+ #endif
+ #ifdef MSG_FILAMENT_CHANGE_PURGE_3
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_PURGE_3);
+ #define _FC_LINES_G (__FC_LINES_G + 1)
+ #else
+ #define _FC_LINES_G __FC_LINES_G
+ #endif
+ #if LCD_HEIGHT > _FC_LINES_G + 1
+ STATIC_ITEM(" ");
+ #endif
+ HOTEND_STATUS_ITEM();
+ END_SCREEN();
+ }
+
+ void lcd_advanced_pause_resume_message() {
+ START_SCREEN();
+ STATIC_ITEM_P(advanced_pause_header(), true, true);
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_RESUME_1);
+ #ifdef MSG_FILAMENT_CHANGE_RESUME_2
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_RESUME_2);
+ #endif
+ #ifdef MSG_FILAMENT_CHANGE_RESUME_3
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_RESUME_3);
+ #endif
+ END_SCREEN();
+ }
+
+ FORCE_INLINE screenFunc_t ap_message_screen(const AdvancedPauseMessage message) {
+ switch (message) {
+ case ADVANCED_PAUSE_MESSAGE_INIT: return lcd_advanced_pause_init_message;
+ case ADVANCED_PAUSE_MESSAGE_UNLOAD: return lcd_advanced_pause_unload_message;
+ case ADVANCED_PAUSE_MESSAGE_INSERT: return lcd_advanced_pause_insert_message;
+ case ADVANCED_PAUSE_MESSAGE_LOAD: return lcd_advanced_pause_load_message;
+ case ADVANCED_PAUSE_MESSAGE_PURGE: return lcd_advanced_pause_purge_message;
+ case ADVANCED_PAUSE_MESSAGE_RESUME: return lcd_advanced_pause_resume_message;
+ case ADVANCED_PAUSE_MESSAGE_CLICK_TO_HEAT_NOZZLE: return lcd_advanced_pause_heat_nozzle;
+ case ADVANCED_PAUSE_MESSAGE_WAIT_FOR_NOZZLES_TO_HEAT: return lcd_advanced_pause_wait_for_nozzles_to_heat;
+ case ADVANCED_PAUSE_MESSAGE_OPTION: advanced_pause_menu_response = ADVANCED_PAUSE_RESPONSE_WAIT_FOR;
+ return lcd_advanced_pause_option_menu;
+ case ADVANCED_PAUSE_MESSAGE_STATUS:
+ default: break;
+ }
+ return NULL;
+ }
+
+ void lcd_advanced_pause_show_message(
+ const AdvancedPauseMessage message,
+ const AdvancedPauseMode mode/*=ADVANCED_PAUSE_MODE_PAUSE_PRINT*/,
+ const uint8_t extruder/*=active_extruder*/
+ ) {
+ advanced_pause_mode = mode;
+ hotend_status_extruder = extruder;
+ const screenFunc_t next_screen = ap_message_screen(message);
+ if (next_screen) {
+ defer_return_to_status = true;
+ lcd_goto_screen(next_screen);
+ }
+ else
+ lcd_return_to_status();
+ }
+
+ #endif // ADVANCED_PAUSE_FEATURE
+
+ /**
+ *
+ * Functions for editing single values
+ *
+ * The "DEFINE_MENU_EDIT_TYPE" macro generates the functions needed to edit a numerical value.
+ *
+ * For example, DEFINE_MENU_EDIT_TYPE(int16_t, int3, itostr3, 1) expands into these functions:
+ *
+ * bool _menu_edit_int3();
+ * void menu_edit_int3(); // edit int16_t (interactively)
+ * void menu_edit_callback_int3(); // edit int16_t (interactively) with callback on completion
+ * void _menu_action_setting_edit_int3(const char * const pstr, int16_t * const ptr, const int16_t minValue, const int16_t maxValue);
+ * void menu_action_setting_edit_int3(const char * const pstr, int16_t * const ptr, const int16_t minValue, const int16_t maxValue);
+ * void menu_action_setting_edit_callback_int3(const char * const pstr, int16_t * const ptr, const int16_t minValue, const int16_t maxValue, const screenFunc_t callback, const bool live); // edit int16_t with callback
+ *
+ * You can then use one of the menu macros to present the edit interface:
+ * MENU_ITEM_EDIT(int3, MSG_SPEED, &feedrate_percentage, 10, 999)
+ *
+ * This expands into a more primitive menu item:
+ * MENU_ITEM(setting_edit_int3, MSG_SPEED, PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
+ *
+ * ...which calls:
+ * menu_action_setting_edit_int3(PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
+ */
+ #define DEFINE_MENU_EDIT_TYPE(_type, _name, _strFunc, _scale) \
+ bool _menu_edit_ ## _name() { \
+ ENCODER_DIRECTION_NORMAL(); \
+ if ((int32_t)encoderPosition < 0) encoderPosition = 0; \
+ if ((int32_t)encoderPosition > maxEditValue) encoderPosition = maxEditValue; \
+ if (lcdDrawUpdate) \
+ lcd_implementation_drawedit(editLabel, _strFunc(((_type)((int32_t)encoderPosition + minEditValue)) * (1.0 / _scale))); \
+ if (lcd_clicked || (liveEdit && lcdDrawUpdate)) { \
+ _type value = ((_type)((int32_t)encoderPosition + minEditValue)) * (1.0 / _scale); \
+ if (editValue != NULL) *((_type*)editValue) = value; \
+ if (liveEdit) (*callbackFunc)(); \
+ if (lcd_clicked) lcd_goto_previous_menu(); \
+ } \
+ return use_click(); \
+ } \
+ void menu_edit_ ## _name() { _menu_edit_ ## _name(); } \
+ void menu_edit_callback_ ## _name() { if (_menu_edit_ ## _name()) (*callbackFunc)(); } \
+ void _menu_action_setting_edit_ ## _name(const char * const pstr, _type* const ptr, const _type minValue, const _type maxValue) { \
+ lcd_save_previous_screen(); \
+ \
+ lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW; \
+ \
+ editLabel = pstr; \
+ editValue = ptr; \
+ minEditValue = minValue * _scale; \
+ maxEditValue = maxValue * _scale - minEditValue; \
+ encoderPosition = (*ptr) * _scale - minEditValue; \
+ } \
+ void menu_action_setting_edit_ ## _name(const char * const pstr, _type * const ptr, const _type minValue, const _type maxValue) { \
+ _menu_action_setting_edit_ ## _name(pstr, ptr, minValue, maxValue); \
+ currentScreen = menu_edit_ ## _name; \
+ } \
+ void menu_action_setting_edit_callback_ ## _name(const char * const pstr, _type * const ptr, const _type minValue, const _type maxValue, const screenFunc_t callback, const bool live) { \
+ _menu_action_setting_edit_ ## _name(pstr, ptr, minValue, maxValue); \
+ currentScreen = menu_edit_callback_ ## _name; \
+ callbackFunc = callback; \
+ liveEdit = live; \
+ } \
+ typedef void _name
+
+ DEFINE_MENU_EDIT_TYPE(uint32_t, long5, ftostr5rj, 0.01);
+ DEFINE_MENU_EDIT_TYPE(int16_t, int3, itostr3, 1);
+ DEFINE_MENU_EDIT_TYPE(uint8_t, int8, i8tostr3, 1);
+ DEFINE_MENU_EDIT_TYPE(float, float3, ftostr3, 1.0);
+ DEFINE_MENU_EDIT_TYPE(float, float32, ftostr32, 100.0);
+ DEFINE_MENU_EDIT_TYPE(float, float43, ftostr43sign, 1000.0);
+ DEFINE_MENU_EDIT_TYPE(float, float5, ftostr5rj, 0.01);
+ DEFINE_MENU_EDIT_TYPE(float, float51, ftostr51sign, 10.0);
+ DEFINE_MENU_EDIT_TYPE(float, float52, ftostr52sign, 100.0);
+ DEFINE_MENU_EDIT_TYPE(float, float62, ftostr62rj, 100.0);
+
+ /**
+ *
+ * Handlers for Keypad input
+ *
+ */
+ #if ENABLED(ADC_KEYPAD)
+
+ inline bool handle_adc_keypad() {
+ #define ADC_MIN_KEY_DELAY 100
+ if (buttons_reprapworld_keypad) {
+ lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+ if (encoderDirection == -1) { // side effect which signals we are inside a menu
+ if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_DOWN) encoderPosition -= ENCODER_STEPS_PER_MENU_ITEM;
+ else if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_UP) encoderPosition += ENCODER_STEPS_PER_MENU_ITEM;
+ else if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_LEFT) { menu_action_back(); lcd_quick_feedback(true); }
+ else if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_RIGHT) { lcd_return_to_status(); lcd_quick_feedback(true); }
+ }
+ else {
+ if (buttons_reprapworld_keypad & (EN_REPRAPWORLD_KEYPAD_DOWN|EN_REPRAPWORLD_KEYPAD_UP|EN_REPRAPWORLD_KEYPAD_RIGHT)) {
+ if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_DOWN) encoderPosition += ENCODER_PULSES_PER_STEP;
+ else if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_UP) encoderPosition -= ENCODER_PULSES_PER_STEP;
+ else if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_RIGHT) encoderPosition = 0;
+ }
+ }
+ #if ENABLED(ADC_KEYPAD_DEBUG)
+ SERIAL_PROTOCOLLNPAIR("buttons_reprapworld_keypad = ", (uint32_t)buttons_reprapworld_keypad);
+ SERIAL_PROTOCOLLNPAIR("encoderPosition = ", (uint32_t)encoderPosition);
+ #endif
+ next_button_update_ms = millis() + ADC_MIN_KEY_DELAY;
+ return true;
+ }
+
+ return false;
+ }
+
+ #elif ENABLED(REPRAPWORLD_KEYPAD)
+
+ void _reprapworld_keypad_move(const AxisEnum axis, const int16_t dir) {
+ move_menu_scale = REPRAPWORLD_KEYPAD_MOVE_STEP;
+ encoderPosition = dir;
+ switch (axis) {
+ case X_AXIS: lcd_move_x(); break;
+ case Y_AXIS: lcd_move_y(); break;
+ case Z_AXIS: lcd_move_z();
+ default: break;
+ }
+ }
+ void reprapworld_keypad_move_z_up() { _reprapworld_keypad_move(Z_AXIS, 1); }
+ void reprapworld_keypad_move_z_down() { _reprapworld_keypad_move(Z_AXIS, -1); }
+ void reprapworld_keypad_move_x_left() { _reprapworld_keypad_move(X_AXIS, -1); }
+ void reprapworld_keypad_move_x_right() { _reprapworld_keypad_move(X_AXIS, 1); }
+ void reprapworld_keypad_move_y_up() { _reprapworld_keypad_move(Y_AXIS, -1); }
+ void reprapworld_keypad_move_y_down() { _reprapworld_keypad_move(Y_AXIS, 1); }
+ void reprapworld_keypad_move_home() { enqueue_and_echo_commands_P(PSTR("G28")); } // move all axes home and wait
+ void reprapworld_keypad_move_menu() { lcd_goto_screen(lcd_move_menu); }
+
+ inline void handle_reprapworld_keypad() {
+
+ static uint8_t keypad_debounce = 0;
+
+ if (!REPRAPWORLD_KEYPAD_PRESSED) {
+ if (keypad_debounce > 0) keypad_debounce--;
+ }
+ else if (!keypad_debounce) {
+ keypad_debounce = 2;
+
+ if (REPRAPWORLD_KEYPAD_MOVE_MENU) reprapworld_keypad_move_menu();
+
+ #if DISABLED(DELTA) && Z_HOME_DIR == -1
+ if (REPRAPWORLD_KEYPAD_MOVE_Z_UP) reprapworld_keypad_move_z_up();
+ #endif
+
+ if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS]) {
+ #if ENABLED(DELTA) || Z_HOME_DIR != -1
+ if (REPRAPWORLD_KEYPAD_MOVE_Z_UP) reprapworld_keypad_move_z_up();
+ #endif
+ if (REPRAPWORLD_KEYPAD_MOVE_Z_DOWN) reprapworld_keypad_move_z_down();
+ if (REPRAPWORLD_KEYPAD_MOVE_X_LEFT) reprapworld_keypad_move_x_left();
+ if (REPRAPWORLD_KEYPAD_MOVE_X_RIGHT) reprapworld_keypad_move_x_right();
+ if (REPRAPWORLD_KEYPAD_MOVE_Y_DOWN) reprapworld_keypad_move_y_down();
+ if (REPRAPWORLD_KEYPAD_MOVE_Y_UP) reprapworld_keypad_move_y_up();
+ }
+ else {
+ if (REPRAPWORLD_KEYPAD_MOVE_HOME) reprapworld_keypad_move_home();
+ }
+ }
+ }
+
+ #endif // REPRAPWORLD_KEYPAD
+
+ /**
+ *
+ * Menu actions
+ *
+ */
+ void _menu_action_back() { lcd_goto_previous_menu(); }
+ void menu_action_submenu(screenFunc_t func) { lcd_save_previous_screen(); lcd_goto_screen(func); }
+ void menu_action_gcode(const char* pgcode) { enqueue_and_echo_commands_P(pgcode); }
+ void menu_action_function(screenFunc_t func) { (*func)(); }
+
+ #if ENABLED(SDSUPPORT)
+
+ void menu_action_sdfile(const char* filename, char* longFilename) {
+ #if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
+ last_sdfile_encoderPosition = encoderPosition; // Save which file was selected for later use
+ #endif
+ UNUSED(longFilename);
+ card.openAndPrintFile(filename);
+ lcd_return_to_status();
+ }
+
+ void menu_action_sddirectory(const char* filename, char* longFilename) {
+ UNUSED(longFilename);
+ card.chdir(filename);
+ encoderTopLine = 0;
+ encoderPosition = 2 * ENCODER_STEPS_PER_MENU_ITEM;
+ screen_changed = true;
+ #if ENABLED(DOGLCD)
+ drawing_screen = false;
+ #endif
+ lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW;
+ }
+
+ #endif // SDSUPPORT
+
+ void menu_action_setting_edit_bool(const char* pstr, bool* ptr) { UNUSED(pstr); *ptr ^= true; lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW; }
+ void menu_action_setting_edit_callback_bool(const char* pstr, bool* ptr, screenFunc_t callback) {
+ menu_action_setting_edit_bool(pstr, ptr);
+ (*callback)();
+ }
+
+#endif // ULTIPANEL
+
+void lcd_init() {
+
+ lcd_implementation_init();
+
+ #if ENABLED(NEWPANEL)
+ #if BUTTON_EXISTS(EN1)
+ SET_INPUT_PULLUP(BTN_EN1);
+ #endif
+ #if BUTTON_EXISTS(EN2)
+ SET_INPUT_PULLUP(BTN_EN2);
+ #endif
+ #if BUTTON_EXISTS(ENC)
+ SET_INPUT_PULLUP(BTN_ENC);
+ #endif
+
+ #if ENABLED(REPRAPWORLD_KEYPAD) && DISABLED(ADC_KEYPAD)
+ SET_OUTPUT(SHIFT_CLK);
+ OUT_WRITE(SHIFT_LD, HIGH);
+ SET_INPUT_PULLUP(SHIFT_OUT);
+ #endif
+
+ #if BUTTON_EXISTS(UP)
+ SET_INPUT(BTN_UP);
+ #endif
+ #if BUTTON_EXISTS(DWN)
+ SET_INPUT(BTN_DWN);
+ #endif
+ #if BUTTON_EXISTS(LFT)
+ SET_INPUT(BTN_LFT);
+ #endif
+ #if BUTTON_EXISTS(RT)
+ SET_INPUT(BTN_RT);
+ #endif
+
+ #else // !NEWPANEL
+
+ #if ENABLED(SR_LCD_2W_NL) // Non latching 2 wire shift register
+ SET_OUTPUT(SR_DATA_PIN);
+ SET_OUTPUT(SR_CLK_PIN);
+ #elif defined(SHIFT_CLK)
+ SET_OUTPUT(SHIFT_CLK);
+ OUT_WRITE(SHIFT_LD, HIGH);
+ OUT_WRITE(SHIFT_EN, LOW);
+ SET_INPUT_PULLUP(SHIFT_OUT);
+ #endif // SR_LCD_2W_NL
+
+ #endif // !NEWPANEL
+
+ #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT)
+ SET_INPUT_PULLUP(SD_DETECT_PIN);
+ lcd_sd_status = 2; // UNKNOWN
+ #endif
+
+ #if ENABLED(LCD_HAS_SLOW_BUTTONS)
+ slow_buttons = 0;
+ #endif
+
+ lcd_buttons_update();
+
+ #if ENABLED(ULTIPANEL)
+ encoderDiff = 0;
+ #endif
+}
+
+int16_t lcd_strlen(const char* s) {
+ int16_t i = 0, j = 0;
+ while (s[i]) {
+ if (PRINTABLE(s[i])) j++;
+ i++;
+ }
+ return j;
+}
+
+int16_t lcd_strlen_P(const char* s) {
+ int16_t j = 0;
+ while (pgm_read_byte(s)) {
+ if (PRINTABLE(pgm_read_byte(s))) j++;
+ s++;
+ }
+ return j;
+}
+
+bool lcd_blink() {
+ static uint8_t blink = 0;
+ static millis_t next_blink_ms = 0;
+ millis_t ms = millis();
+ if (ELAPSED(ms, next_blink_ms)) {
+ blink ^= 0xFF;
+ next_blink_ms = ms + 1000 - (LCD_UPDATE_INTERVAL) / 2;
+ }
+ return blink != 0;
+}
+
+/**
+ * Update the LCD, read encoder buttons, etc.
+ * - Read button states
+ * - Check the SD Card slot state
+ * - Act on RepRap World keypad input
+ * - Update the encoder position
+ * - Apply acceleration to the encoder position
+ * - Set lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NOW on controller events
+ * - Reset the Info Screen timeout if there's any input
+ * - Update status indicators, if any
+ *
+ * Run the current LCD menu handler callback function:
+ * - Call the handler only if lcdDrawUpdate != LCDVIEW_NONE
+ * - Before calling the handler, LCDVIEW_CALL_NO_REDRAW => LCDVIEW_NONE
+ * - Call the menu handler. Menu handlers should do the following:
+ * - If a value changes, set lcdDrawUpdate to LCDVIEW_REDRAW_NOW and draw the value
+ * (Encoder events automatically set lcdDrawUpdate for you.)
+ * - if (lcdDrawUpdate) { redraw }
+ * - Before exiting the handler set lcdDrawUpdate to:
+ * - LCDVIEW_CLEAR_CALL_REDRAW to clear screen and set LCDVIEW_CALL_REDRAW_NEXT.
+ * - LCDVIEW_REDRAW_NOW to draw now (including remaining stripes).
+ * - LCDVIEW_CALL_REDRAW_NEXT to draw now and get LCDVIEW_REDRAW_NOW on the next loop.
+ * - LCDVIEW_CALL_NO_REDRAW to draw now and get LCDVIEW_NONE on the next loop.
+ * - NOTE: For graphical displays menu handlers may be called 2 or more times per loop,
+ * so don't change lcdDrawUpdate without considering this.
+ *
+ * After the menu handler callback runs (or not):
+ * - Clear the LCD if lcdDrawUpdate == LCDVIEW_CLEAR_CALL_REDRAW
+ * - Update lcdDrawUpdate for the next loop (i.e., move one state down, usually)
+ *
+ * No worries. This function is only called from the main thread.
+ */
+void lcd_update() {
+
+ #if ENABLED(ULTIPANEL)
+ static millis_t return_to_status_ms = 0;
+
+ // Handle any queued Move Axis motion
+ manage_manual_move();
+
+ // Update button states for LCD_CLICKED, etc.
+ // After state changes the next button update
+ // may be delayed 300-500ms.
+ lcd_buttons_update();
+
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+ // Don't run the debouncer if UBL owns the display
+ #define UBL_CONDITION !lcd_external_control
+ #else
+ #define UBL_CONDITION true
+ #endif
+
+ // If the action button is pressed...
+ if (UBL_CONDITION && LCD_CLICKED) {
+ if (!wait_for_unclick) { // If not waiting for a debounce release:
+ wait_for_unclick = true; // Set debounce flag to ignore continous clicks
+ lcd_clicked = !wait_for_user && !no_reentry; // Keep the click if not waiting for a user-click
+ wait_for_user = false; // Any click clears wait for user
+ lcd_quick_feedback(true); // Always make a click sound
+ }
+ }
+ else wait_for_unclick = false;
+
+ #if BUTTON_EXISTS(BACK)
+ if (LCD_BACK_CLICKED) {
+ lcd_quick_feedback(true);
+ lcd_goto_previous_menu();
+ }
+ #endif
+
+ #endif
+
+ #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT)
+
+ const bool sd_status = IS_SD_INSERTED;
+ if (sd_status != lcd_sd_status && lcd_detected()) {
+
+ bool old_sd_status = lcd_sd_status; // prevent re-entry to this block!
+ lcd_sd_status = sd_status;
+
+ if (sd_status) {
+ safe_delay(1000); // some boards need a delay or the LCD won't show the new status
+ card.initsd();
+ if (old_sd_status != 2) LCD_MESSAGEPGM(MSG_SD_INSERTED);
+ }
+ else {
+ card.release();
+ if (old_sd_status != 2) LCD_MESSAGEPGM(MSG_SD_REMOVED);
+ }
+
+ lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW;
+ lcd_implementation_init( // to maybe revive the LCD if static electricity killed it.
+ #if ENABLED(LCD_PROGRESS_BAR)
+ currentScreen == lcd_status_screen ? CHARSET_INFO : CHARSET_MENU
+ #endif
+ );
+ }
+
+ #endif // SDSUPPORT && SD_DETECT_PIN
+
+ const millis_t ms = millis();
+ if (ELAPSED(ms, next_lcd_update_ms)
+ #if ENABLED(DOGLCD)
+ || drawing_screen
+ #endif
+ ) {
+
+ next_lcd_update_ms = ms + LCD_UPDATE_INTERVAL;
+
+ #if ENABLED(LCD_HAS_STATUS_INDICATORS)
+ lcd_implementation_update_indicators();
+ #endif
+
+ #if ENABLED(ULTIPANEL)
+
+ #if ENABLED(LCD_HAS_SLOW_BUTTONS)
+ slow_buttons = lcd_implementation_read_slow_buttons(); // buttons which take too long to read in interrupt context
+ #endif
+
+ #if ENABLED(ADC_KEYPAD)
+
+ if (handle_adc_keypad())
+ return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS;
+
+ #elif ENABLED(REPRAPWORLD_KEYPAD)
+
+ handle_reprapworld_keypad();
+
+ #endif
+
+ const bool encoderPastThreshold = (abs(encoderDiff) >= ENCODER_PULSES_PER_STEP);
+ if (encoderPastThreshold || lcd_clicked) {
+ if (encoderPastThreshold) {
+ int32_t encoderMultiplier = 1;
+
+ #if ENABLED(ENCODER_RATE_MULTIPLIER)
+
+ if (encoderRateMultiplierEnabled) {
+ int32_t encoderMovementSteps = abs(encoderDiff) / ENCODER_PULSES_PER_STEP;
+
+ if (lastEncoderMovementMillis) {
+ // Note that the rate is always calculated between two passes through the
+ // loop and that the abs of the encoderDiff value is tracked.
+ float encoderStepRate = float(encoderMovementSteps) / float(ms - lastEncoderMovementMillis) * 1000.0;
+
+ if (encoderStepRate >= ENCODER_100X_STEPS_PER_SEC) encoderMultiplier = 100;
+ else if (encoderStepRate >= ENCODER_10X_STEPS_PER_SEC) encoderMultiplier = 10;
+
+ #if ENABLED(ENCODER_RATE_MULTIPLIER_DEBUG)
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPAIR("Enc Step Rate: ", encoderStepRate);
+ SERIAL_ECHOPAIR(" Multiplier: ", encoderMultiplier);
+ SERIAL_ECHOPAIR(" ENCODER_10X_STEPS_PER_SEC: ", ENCODER_10X_STEPS_PER_SEC);
+ SERIAL_ECHOPAIR(" ENCODER_100X_STEPS_PER_SEC: ", ENCODER_100X_STEPS_PER_SEC);
+ SERIAL_EOL();
+ #endif // ENCODER_RATE_MULTIPLIER_DEBUG
+ }
+
+ lastEncoderMovementMillis = ms;
+ } // encoderRateMultiplierEnabled
+ #endif // ENCODER_RATE_MULTIPLIER
+
+ encoderPosition += (encoderDiff * encoderMultiplier) / ENCODER_PULSES_PER_STEP;
+ encoderDiff = 0;
+ }
+ return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS;
+ lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+ }
+ #endif // ULTIPANEL
+
+ // We arrive here every ~100ms when idling often enough.
+ // Instead of tracking the changes simply redraw the Info Screen ~1 time a second.
+ if (
+ #if ENABLED(ULTIPANEL)
+ currentScreen == lcd_status_screen &&
+ #endif
+ !lcd_status_update_delay--
+ ) {
+ lcd_status_update_delay = 9
+ #if ENABLED(DOGLCD)
+ + 3
+ #endif
+ ;
+ max_display_update_time--;
+ lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+ }
+
+ #if ENABLED(SCROLL_LONG_FILENAMES)
+ // If scrolling of long file names is enabled and we are in the sd card menu,
+ // cause a refresh to occur until all the text has scrolled into view.
+ if (currentScreen == lcd_sdcard_menu && filename_scroll_pos < filename_scroll_max && !lcd_status_update_delay--) {
+ lcd_status_update_delay = 6;
+ lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+ filename_scroll_pos++;
+ return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS;
+ }
+ #endif
+
+ // then we want to use 1/2 of the time only.
+ uint16_t bbr2 = planner.block_buffer_runtime() >> 1;
+
+ #if ENABLED(DOGLCD)
+ #define IS_DRAWING drawing_screen
+ #else
+ #define IS_DRAWING false
+ #endif
+
+ if ((lcdDrawUpdate || IS_DRAWING) && (!bbr2 || bbr2 > max_display_update_time)) {
+
+ // Change state of drawing flag between screen updates
+ if (!IS_DRAWING) switch (lcdDrawUpdate) {
+ case LCDVIEW_CALL_NO_REDRAW:
+ lcdDrawUpdate = LCDVIEW_NONE;
+ break;
+ case LCDVIEW_CLEAR_CALL_REDRAW:
+ case LCDVIEW_CALL_REDRAW_NEXT:
+ lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+ case LCDVIEW_REDRAW_NOW: // set above, or by a handler through LCDVIEW_CALL_REDRAW_NEXT
+ case LCDVIEW_NONE:
+ break;
+ } // switch
+
+ #if ENABLED(ADC_KEYPAD)
+ buttons_reprapworld_keypad = 0;
+ #endif
+
+ #if ENABLED(ULTIPANEL)
+ #define CURRENTSCREEN() (*currentScreen)()
+ #else
+ #define CURRENTSCREEN() lcd_status_screen()
+ #endif
+
+ #if ENABLED(DOGLCD)
+ #if ENABLED(LIGHTWEIGHT_UI)
+ #if ENABLED(ULTIPANEL)
+ const bool in_status = currentScreen == lcd_status_screen;
+ #else
+ constexpr bool in_status = true;
+ #endif
+ const bool do_u8g_loop = !in_status;
+ lcd_in_status(in_status);
+ if (in_status) lcd_status_screen();
+ #else
+ constexpr bool do_u8g_loop = true;
+ #endif
+ if (do_u8g_loop) {
+ if (!drawing_screen) { // If not already drawing pages
+ u8g.firstPage(); // Start the first page
+ drawing_screen = first_page = true; // Flag as drawing pages
+ }
+ lcd_setFont(FONT_MENU); // Setup font for every page draw
+ u8g.setColorIndex(1); // And reset the color
+ CURRENTSCREEN(); // Draw and process the current screen
+ first_page = false;
+
+ // The screen handler can clear drawing_screen for an action that changes the screen.
+ // If still drawing and there's another page, update max-time and return now.
+ // The nextPage will already be set up on the next call.
+ if (drawing_screen && (drawing_screen = u8g.nextPage())) {
+ NOLESS(max_display_update_time, millis() - ms);
+ return;
+ }
+ }
+ #else
+ CURRENTSCREEN();
+ #endif
+
+ #if ENABLED(ULTIPANEL)
+ lcd_clicked = false;
+ #endif
+
+ // Keeping track of the longest time for an individual LCD update.
+ // Used to do screen throttling when the planner starts to fill up.
+ NOLESS(max_display_update_time, millis() - ms);
+ }
+
+ #if ENABLED(ULTIPANEL)
+
+ // Return to Status Screen after a timeout
+ if (currentScreen == lcd_status_screen || defer_return_to_status)
+ return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS;
+ else if (ELAPSED(ms, return_to_status_ms))
+ lcd_return_to_status();
+
+ #endif // ULTIPANEL
+
+ // Change state of drawing flag between screen updates
+ if (!IS_DRAWING) switch (lcdDrawUpdate) {
+ case LCDVIEW_CLEAR_CALL_REDRAW:
+ lcd_implementation_clear(); break;
+ case LCDVIEW_REDRAW_NOW:
+ lcdDrawUpdate = LCDVIEW_NONE;
+ case LCDVIEW_NONE:
+ case LCDVIEW_CALL_REDRAW_NEXT:
+ case LCDVIEW_CALL_NO_REDRAW:
+ default: break;
+ } // switch
+
+ } // ELAPSED(ms, next_lcd_update_ms)
+}
+
+inline void pad_message_string() {
+ uint8_t i = 0, j = 0;
+ char c;
+ lcd_status_message[MAX_MESSAGE_LENGTH] = '\0';
+ while ((c = lcd_status_message[i]) && j < LCD_WIDTH) {
+ if (PRINTABLE(c)) j++;
+ i++;
+ }
+ if (true
+ #if ENABLED(STATUS_MESSAGE_SCROLLING)
+ && j < LCD_WIDTH
+ #endif
+ ) {
+ // pad with spaces to fill up the line
+ while (j++ < LCD_WIDTH) lcd_status_message[i++] = ' ';
+ // chop off at the edge
+ lcd_status_message[i] = '\0';
+ }
+}
+
+void lcd_finishstatus(const bool persist=false) {
+
+ pad_message_string();
+
+ #if !(ENABLED(LCD_PROGRESS_BAR) && (PROGRESS_MSG_EXPIRE > 0))
+ UNUSED(persist);
+ #endif
+
+ #if ENABLED(LCD_PROGRESS_BAR)
+ progress_bar_ms = millis();
+ #if PROGRESS_MSG_EXPIRE > 0
+ expire_status_ms = persist ? 0 : progress_bar_ms + PROGRESS_MSG_EXPIRE;
+ #endif
+ #endif
+ lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW;
+
+ #if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
+ previous_lcd_status_ms = millis(); //get status message to show up for a while
+ #endif
+
+ #if ENABLED(STATUS_MESSAGE_SCROLLING)
+ status_scroll_pos = 0;
+ #endif
+}
+
+#if ENABLED(LCD_PROGRESS_BAR) && PROGRESS_MSG_EXPIRE > 0
+ void dontExpireStatus() { expire_status_ms = 0; }
+#endif
+
+bool lcd_hasstatus() { return (lcd_status_message[0] != '\0'); }
+
+void lcd_setstatus(const char * const message, const bool persist) {
+ if (lcd_status_message_level > 0) return;
+ strncpy(lcd_status_message, message, MAX_MESSAGE_LENGTH);
+ lcd_finishstatus(persist);
+}
+
+void lcd_setstatusPGM(const char * const message, int8_t level) {
+ if (level < 0) level = lcd_status_message_level = 0;
+ if (level < lcd_status_message_level) return;
+ lcd_status_message_level = level;
+ strncpy_P(lcd_status_message, message, MAX_MESSAGE_LENGTH);
+ lcd_finishstatus(level > 0);
+}
+
+void lcd_status_printf_P(const uint8_t level, const char * const fmt, ...) {
+ if (level < lcd_status_message_level) return;
+ lcd_status_message_level = level;
+ va_list args;
+ va_start(args, fmt);
+ vsnprintf_P(lcd_status_message, MAX_MESSAGE_LENGTH, fmt, args);
+ va_end(args);
+ lcd_finishstatus(level > 0);
+}
+
+void lcd_setalertstatusPGM(const char * const message) {
+ lcd_setstatusPGM(message, 1);
+ #if ENABLED(ULTIPANEL)
+ lcd_return_to_status();
+ #endif
+}
+
+void lcd_reset_alert_level() { lcd_status_message_level = 0; }
+
+#if HAS_LCD_CONTRAST
+
+ void set_lcd_contrast(const int16_t value) {
+ lcd_contrast = constrain(value, LCD_CONTRAST_MIN, LCD_CONTRAST_MAX);
+ u8g.setContrast(lcd_contrast);
+ }
+
+#endif
+
+#if ENABLED(ULTIPANEL)
+
+ /**
+ * Setup Rotary Encoder Bit Values (for two pin encoders to indicate movement)
+ * These values are independent of which pins are used for EN_A and EN_B indications
+ * The rotary encoder part is also independent to the chipset used for the LCD
+ */
+ #if defined(EN_A) && defined(EN_B)
+ #define encrot0 0
+ #define encrot1 2
+ #define encrot2 3
+ #define encrot3 1
+ #endif
+
+ #define GET_SHIFT_BUTTON_STATES(DST) \
+ uint8_t new_##DST = 0; \
+ WRITE(SHIFT_LD, LOW); \
+ WRITE(SHIFT_LD, HIGH); \
+ for (int8_t i = 0; i < 8; i++) { \
+ new_##DST >>= 1; \
+ if (READ(SHIFT_OUT)) SBI(new_##DST, 7); \
+ WRITE(SHIFT_CLK, HIGH); \
+ WRITE(SHIFT_CLK, LOW); \
+ } \
+ DST = ~new_##DST; //invert it, because a pressed switch produces a logical 0
+
+
+ /**
+ * Read encoder buttons from the hardware registers
+ * Warning: This function is called from interrupt context!
+ */
+ void lcd_buttons_update() {
+ static uint8_t lastEncoderBits;
+ const millis_t now = millis();
+ if (ELAPSED(now, next_button_update_ms)) {
+
+ #if ENABLED(NEWPANEL)
+ uint8_t newbutton = 0;
+
+ #if BUTTON_EXISTS(EN1)
+ if (BUTTON_PRESSED(EN1)) newbutton |= EN_A;
+ #endif
+
+ #if BUTTON_EXISTS(EN2)
+ if (BUTTON_PRESSED(EN2)) newbutton |= EN_B;
+ #endif
+
+ #if BUTTON_EXISTS(ENC)
+ if (BUTTON_PRESSED(ENC)) newbutton |= EN_C;
+ #endif
+ #if BUTTON_EXISTS(BACK)
+ if (BUTTON_PRESSED(BACK)) newbutton |= EN_D;
+ #endif
+
+ //
+ // Directional buttons
+ //
+ #if LCD_HAS_DIRECTIONAL_BUTTONS
+
+ #if ENABLED(REVERSE_MENU_DIRECTION)
+ #define _ENCODER_UD_STEPS (ENCODER_STEPS_PER_MENU_ITEM * encoderDirection)
+ #else
+ #define _ENCODER_UD_STEPS ENCODER_STEPS_PER_MENU_ITEM
+ #endif
+ #if ENABLED(REVERSE_ENCODER_DIRECTION)
+ #define ENCODER_UD_STEPS _ENCODER_UD_STEPS
+ #define ENCODER_LR_PULSES ENCODER_PULSES_PER_STEP
+ #else
+ #define ENCODER_UD_STEPS -(_ENCODER_UD_STEPS)
+ #define ENCODER_LR_PULSES -(ENCODER_PULSES_PER_STEP)
+ #endif
+
+ if (false) {
+ // for the else-ifs below
+ }
+ #if BUTTON_EXISTS(UP)
+ else if (BUTTON_PRESSED(UP)) {
+ encoderDiff = -(ENCODER_UD_STEPS);
+ next_button_update_ms = now + 300;
+ }
+ #endif
+ #if BUTTON_EXISTS(DWN)
+ else if (BUTTON_PRESSED(DWN)) {
+ encoderDiff = ENCODER_UD_STEPS;
+ next_button_update_ms = now + 300;
+ }
+ #endif
+ #if BUTTON_EXISTS(LFT)
+ else if (BUTTON_PRESSED(LFT)) {
+ encoderDiff = -(ENCODER_LR_PULSES);
+ next_button_update_ms = now + 300;
+ }
+ #endif
+ #if BUTTON_EXISTS(RT)
+ else if (BUTTON_PRESSED(RT)) {
+ encoderDiff = ENCODER_LR_PULSES;
+ next_button_update_ms = now + 300;
+ }
+ #endif
+
+ #endif // LCD_HAS_DIRECTIONAL_BUTTONS
+
+ buttons = newbutton;
+ #if ENABLED(LCD_HAS_SLOW_BUTTONS)
+ buttons |= slow_buttons;
+ #endif
+
+ #if ENABLED(ADC_KEYPAD)
+
+ uint8_t newbutton_reprapworld_keypad = 0;
+ buttons = 0;
+ if (buttons_reprapworld_keypad == 0) {
+ newbutton_reprapworld_keypad = get_ADC_keyValue();
+ if (WITHIN(newbutton_reprapworld_keypad, 1, 8))
+ buttons_reprapworld_keypad = _BV(newbutton_reprapworld_keypad - 1);
+ }
+
+ #elif ENABLED(REPRAPWORLD_KEYPAD)
+
+ GET_SHIFT_BUTTON_STATES(buttons_reprapworld_keypad);
+
+ #endif
+
+ #else // !NEWPANEL
+
+ GET_SHIFT_BUTTON_STATES(buttons);
+
+ #endif
+
+ } // next_button_update_ms
+
+ // Manage encoder rotation
+ #if ENABLED(REVERSE_MENU_DIRECTION) && ENABLED(REVERSE_ENCODER_DIRECTION)
+ #define ENCODER_DIFF_CW (encoderDiff -= encoderDirection)
+ #define ENCODER_DIFF_CCW (encoderDiff += encoderDirection)
+ #elif ENABLED(REVERSE_MENU_DIRECTION)
+ #define ENCODER_DIFF_CW (encoderDiff += encoderDirection)
+ #define ENCODER_DIFF_CCW (encoderDiff -= encoderDirection)
+ #elif ENABLED(REVERSE_ENCODER_DIRECTION)
+ #define ENCODER_DIFF_CW (encoderDiff--)
+ #define ENCODER_DIFF_CCW (encoderDiff++)
+ #else
+ #define ENCODER_DIFF_CW (encoderDiff++)
+ #define ENCODER_DIFF_CCW (encoderDiff--)
+ #endif
+ #define ENCODER_SPIN(_E1, _E2) switch (lastEncoderBits) { case _E1: ENCODER_DIFF_CW; break; case _E2: ENCODER_DIFF_CCW; }
+
+ uint8_t enc = 0;
+ if (buttons & EN_A) enc |= B01;
+ if (buttons & EN_B) enc |= B10;
+ if (enc != lastEncoderBits) {
+ switch (enc) {
+ case encrot0: ENCODER_SPIN(encrot3, encrot1); break;
+ case encrot1: ENCODER_SPIN(encrot0, encrot2); break;
+ case encrot2: ENCODER_SPIN(encrot1, encrot3); break;
+ case encrot3: ENCODER_SPIN(encrot2, encrot0); break;
+ }
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+ if (lcd_external_control) {
+ ubl.encoder_diff = encoderDiff; // Make encoder rotation available to UBL G29 mesh editing.
+ encoderDiff = 0; // Hide the encoder event from the current screen handler.
+ }
+ #endif
+ lastEncoderBits = enc;
+ }
+ }
+
+ #if (ENABLED(LCD_I2C_TYPE_MCP23017) || ENABLED(LCD_I2C_TYPE_MCP23008)) && ENABLED(DETECT_DEVICE)
+ bool lcd_detected() { return lcd.LcdDetected() == 1; }
+ #else
+ bool lcd_detected() { return true; }
+ #endif
+
+ #if ENABLED(G26_MESH_VALIDATION)
+ void lcd_chirp() {
+ #if ENABLED(LCD_USE_I2C_BUZZER)
+ lcd.buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ);
+ #elif PIN_EXISTS(BEEPER)
+ buzzer.tone(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ);
+ #endif
+ }
+ #endif
+
+ #if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(G26_MESH_VALIDATION)
+ bool is_lcd_clicked() { return LCD_CLICKED; }
+ void wait_for_release() {
+ while (is_lcd_clicked()) safe_delay(50);
+ safe_delay(50);
+ }
+ #endif
+
+#endif // ULTIPANEL
+
+#if ENABLED(ADC_KEYPAD)
+
+ typedef struct {
+ uint16_t ADCKeyValueMin, ADCKeyValueMax;
+ uint8_t ADCKeyNo;
+ } _stADCKeypadTable_;
+
+ static const _stADCKeypadTable_ stADCKeyTable[] PROGMEM = {
+ // VALUE_MIN, VALUE_MAX, KEY
+ { 4000, 4096, BLEN_REPRAPWORLD_KEYPAD_F1 + 1 }, // F1
+ { 4000, 4096, BLEN_REPRAPWORLD_KEYPAD_F2 + 1 }, // F2
+ { 4000, 4096, BLEN_REPRAPWORLD_KEYPAD_F3 + 1 }, // F3
+ { 300, 500, BLEN_REPRAPWORLD_KEYPAD_LEFT + 1 }, // LEFT
+ { 1900, 2200, BLEN_REPRAPWORLD_KEYPAD_RIGHT + 1 }, // RIGHT
+ { 570, 870, BLEN_REPRAPWORLD_KEYPAD_UP + 1 }, // UP
+ { 2670, 2870, BLEN_REPRAPWORLD_KEYPAD_DOWN + 1 }, // DOWN
+ { 1150, 1450, BLEN_REPRAPWORLD_KEYPAD_MIDDLE + 1 }, // ENTER
+ };
+
+ uint8_t get_ADC_keyValue(void) {
+ if (thermalManager.ADCKey_count >= 16) {
+ const uint16_t currentkpADCValue = thermalManager.current_ADCKey_raw >> 2;
+ #if ENABLED(ADC_KEYPAD_DEBUG)
+ SERIAL_PROTOCOLLN(currentkpADCValue);
+ #endif
+ thermalManager.current_ADCKey_raw = 0;
+ thermalManager.ADCKey_count = 0;
+ if (currentkpADCValue < 4000)
+ for (uint8_t i = 0; i < ADC_KEY_NUM; i++) {
+ const uint16_t lo = pgm_read_word(&stADCKeyTable[i].ADCKeyValueMin),
+ hi = pgm_read_word(&stADCKeyTable[i].ADCKeyValueMax);
+ if (WITHIN(currentkpADCValue, lo, hi)) return pgm_read_byte(&stADCKeyTable[i].ADCKeyNo);
+ }
+ }
+ return 0;
+ }
+#endif
+
+#endif // ULTRA_LCD