- 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 (!ignore_protection && pin_is_protected(pin)) 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 (!ignore_protection && pin_is_protected(pin)) 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();
-
- float 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++) {
- float 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
- */
- float 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
-
- #ifdef 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_reset_status();
- }
- #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_SENSOR
- 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, 255U);
- }
- }
-
- /**
- * 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_reset_status();
- }
- else
- print_job_timer.start();
- #endif
-
- #if ENABLED(ULTRA_LCD)
- const bool heating = thermalManager.isHeatingHotend(target_extruder);
- if (heating || !no_wait_for_cooling)
- #if HOTENDS > 1
- lcd_status_printf_P(0, heating ? PSTR("E%i " MSG_HEATING) : PSTR("E%i " MSG_COOLING), target_extruder + 1);
- #else
- lcd_setstatusPGM(heating ? PSTR("E " MSG_HEATING) : PSTR("E " MSG_COOLING));
- #endif
- #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, old_temp = 9999;
- 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 = ABS(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 < float(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_reset_status();
- #if ENABLED(PRINTER_EVENT_LEDS)
- leds.set_white();
- #endif
- }
-
- #if DISABLED(BUSY_WHILE_HEATING)
- KEEPALIVE_STATE(IN_HANDLER);
- #endif
-}
-
-#if HAS_HEATED_BED
-
- /**
- * M140: Set bed temperature
- */
- inline void gcode_M140() {
- if (DEBUGGING(DRYRUN)) return;
- if (parser.seenval('S')) thermalManager.setTargetBed(parser.value_celsius());
- }
-
- #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;
-
- 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;
-
- lcd_setstatusPGM(thermalManager.isHeatingBed() ? PSTR(MSG_BED_HEATING) : PSTR(MSG_BED_COOLING));
-
- #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 = ABS(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 < float(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_reset_status();
- #if DISABLED(BUSY_WHILE_HEATING)
- KEEPALIVE_STATE(IN_HANDLER);
- #endif
- }
-
-#endif // HAS_HEATED_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');
-
- static const 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
- ;
-
- static const 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);
- #if !defined(__AVR__) || !defined(USBCON)
- #if ENABLED(SERIAL_STATS_RX_BUFFER_OVERRUNS)
- SERIAL_ECHOPAIR("\nBuffer Overruns: ", customizedSerial.buffer_overruns());
- #endif
-
- #if ENABLED(SERIAL_STATS_RX_FRAMING_ERRORS)
- SERIAL_ECHOPAIR("\nFraming Errors: ", customizedSerial.framing_errors());
- #endif
-
- #if ENABLED(SERIAL_STATS_DROPPED_RX)
- SERIAL_ECHOPAIR("\nDropped bytes: ", customizedSerial.dropped());
- #endif
-
- #if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
- SERIAL_ECHOPAIR("\nMax RX Queue Size: ", customizedSerial.rxMaxEnqueued());
- #endif
- #endif // !__AVR__ || !USBCON
- }
- 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
-
-#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_reset_status();
- #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();
- planner.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
- 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) {
- planner.finish_and_disable();
- }
- else {
- planner.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) {
- float factor = planner.axis_steps_per_mm[E_AXIS + TARGET_EXTRUDER] / value; // increase e constants if M92 E14 is given for netfab.
- #if DISABLED(JUNCTION_DEVIATION)
- planner.max_jerk[E_AXIS] *= factor;
- #endif
- 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:", planner.get_axis_position_degrees(A_AXIS));
- SERIAL_PROTOCOLLNPAIR(" Psi+Theta:", planner.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() {
-
- 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
-
- planner.synchronize();
-
- 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] = {
- planner.get_axis_position_degrees(A_AXIS),
- planner.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], planner.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
-
- planner.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
- );
-
- // THERMAL_PROTECTION
- cap_line(PSTR("THERMAL_PROTECTION")
- #if ENABLED(THERMAL_PROTECTION_HOTENDS) && ENABLED(THERMAL_PROTECTION_BED)
- , true
- #endif
- );
-
- #endif // EXTENDED_CAPABILITIES_REPORT
-}
-
-/**
- * M117: Set LCD Status Message
- */
-inline void gcode_M117() {
- if (parser.string_arg[0])
- lcd_setstatus(parser.string_arg);
- else
- lcd_reset_status();
-}
-
-/**
- * M118: Display a message in the host console.
- *
- * A1 Prepend '// ' for an action command, as in OctoPrint
- * E1 Have the host 'echo:' the text
- */
-inline void gcode_M118() {
- bool hasE = false, hasA = false;
- char *p = parser.string_arg;
- for (uint8_t i = 2; i--;)
- if ((p[0] == 'A' || p[0] == 'E') && p[1] == '1') {
- if (p[0] == 'A') hasA = true;
- if (p[0] == 'E') hasE = true;
- p += 2;
- while (*p == ' ') ++p;
- }
- if (hasE) SERIAL_ECHO_START();
- if (hasA) SERIAL_ECHOPGM("// ");
- SERIAL_ECHOLN(p);
-}
-
-/**
- * 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 = -ABS(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)) )
- 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
- *
- * B = Min Segment Time (µs)
- * S = Min Feed Rate (units/s)
- * T = Min Travel Feed Rate (units/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)
- * J = Junction Deviation (mm) (Requires JUNCTION_DEVIATION)
- */
-inline void gcode_M205() {
- if (parser.seen('B')) planner.min_segment_time_us = parser.value_ulong();
- 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 ENABLED(JUNCTION_DEVIATION)
- if (parser.seen('J')) {
- const float junc_dev = parser.value_linear_units();
- if (WITHIN(junc_dev, 0.01f, 0.3f)) {
- planner.junction_deviation_mm = junc_dev;
- planner.recalculate_max_e_jerk();
- }
- else {
- SERIAL_ERROR_START();
- SERIAL_ERRORLNPGM("?J out of range (0.01 to 0.3)");
- }
- }
- #else
- 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.1f)
- 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();
- #endif
-}
-
-#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 = Gamma (Tower 3) angle trim
- */
- 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, SWITCHING_NOZZLE, and PARKING_EXTRUDER
- */
- 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 HAS_HOTEND_OFFSET_Z
- 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 HAS_HOTEND_OFFSET_Z
- 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);
- }
- else {
- SERIAL_ECHO_START();
- SERIAL_CHAR('E');
- SERIAL_CHAR('0' + target_extruder);
- SERIAL_ECHOPAIR(" Flow: ", planner.flow_percentage[target_extruder]);
- SERIAL_CHAR('%');
- SERIAL_EOL();
- }
-}
-
-/**
- * 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);
- if (WITHIN(pin_state, -1, 1) && pin > -1) {
- if (pin_is_protected(pin))
- protected_pin_err();
- else {
- int target = LOW;
- planner.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[int] 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')) thermalManager.lpq_len = parser.value_float();
- NOMORE(thermalManager.lpq_len, LPQ_MAX_LEN);
- NOLESS(thermalManager.lpq_len, 0);
- #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() { planner.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();
- #ifdef 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() {
- planner.quick_stop();
- planner.synchronize();
- set_current_from_steppers_for_axis(ALL_AXES);
- SYNC_PLAN_POSITION_KINEMATIC();
-}
-
-
-
-#if HAS_LEVELING
-
- //#define M420_C_USE_MEAN
-
- /**
- * 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)
- * T[map] 0:Human-readable 1:CSV 2:"LCD" 4:Compact
- *
- * With mesh-based leveling only:
- *
- * C Center mesh on the mean of the lowest and highest
- */
- inline void gcode_M420() {
- const bool seen_S = parser.seen('S');
- bool to_enable = seen_S ? parser.value_bool() : planner.leveling_active;
-
- // If disabling leveling do it right away
- // (Don't disable for just M420 or M420 V)
- if (seen_S && !to_enable) set_bed_leveling_enabled(false);
-
- 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')) {
-
- set_bed_leveling_enabled(false);
-
- #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 or V display the map info
- if (parser.seen('L') || parser.seen('V')) {
- ubl.display_map(parser.byteval('T'));
- SERIAL_ECHOPGM("Mesh is ");
- if (!ubl.mesh_is_valid()) SERIAL_ECHOPGM("in");
- SERIAL_ECHOLNPAIR("valid\nStorage slot: ", ubl.storage_slot);
- }
-
- #endif // AUTO_BED_LEVELING_UBL
-
- #if HAS_MESH
-
- #if ENABLED(MESH_BED_LEVELING)
- #define Z_VALUES(X,Y) mbl.z_values[X][Y]
- #else
- #define Z_VALUES(X,Y) z_values[X][Y]
- #endif
-
- // Subtract the given value or the mean from all mesh values
- if (leveling_is_valid() && parser.seen('C')) {
- const float cval = parser.value_float();
- #if ENABLED(AUTO_BED_LEVELING_UBL)
-
- set_bed_leveling_enabled(false);
- ubl.adjust_mesh_to_mean(true, cval);
-
- #else
-
- #if ENABLED(M420_C_USE_MEAN)
-
- // Get the sum and average of all mesh values
- float mesh_sum = 0;
- for (uint8_t x = GRID_MAX_POINTS_X; x--;)
- for (uint8_t y = GRID_MAX_POINTS_Y; y--;)
- mesh_sum += Z_VALUES(x, y);
- const float zmean = mesh_sum / float(GRID_MAX_POINTS);
-
- #else
-
- // Find the low and high mesh values
- float lo_val = 100, hi_val = -100;
- for (uint8_t x = GRID_MAX_POINTS_X; x--;)
- for (uint8_t y = GRID_MAX_POINTS_Y; y--;) {
- const float z = Z_VALUES(x, y);
- NOMORE(lo_val, z);
- NOLESS(hi_val, z);
- }
- // Take the mean of the lowest and highest
- const float zmean = (lo_val + hi_val) / 2.0 + cval;
-
- #endif
-
- // If not very close to 0, adjust the mesh
- if (!NEAR_ZERO(zmean)) {
- set_bed_leveling_enabled(false);
- // Subtract the mean from all values
- for (uint8_t x = GRID_MAX_POINTS_X; x--;)
- for (uint8_t y = GRID_MAX_POINTS_Y; y--;)
- Z_VALUES(x, y) -= zmean;
- #if ENABLED(ABL_BILINEAR_SUBDIVISION)
- bed_level_virt_interpolate();
- #endif
- }
-
- #endif
- }
-
- #endif // HAS_MESH
-
- // 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
-
- // Enable leveling if specified, or if previously active
- set_bed_leveling_enabled(to_enable);
-
- // Error if leveling failed to enable or reenable
- if (to_enable && !planner.leveling_active) {
- SERIAL_ERROR_START();
- SERIAL_ERRORLNPGM(MSG_ERR_M420_FAILED);
- }
-
- SERIAL_ECHO_START();
- SERIAL_ECHOLNPAIR("Bed Leveling ", planner.leveling_active ? 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 // HAS_LEVELING
-
-#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