update code base to Marlin 2.0.9.2

This commit is contained in:
Stefan Kalscheuer
2021-10-03 18:57:12 +02:00
parent b9d7ba838e
commit 7077da3591
2617 changed files with 332093 additions and 103438 deletions

View File

@@ -0,0 +1,54 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2021 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 <https://www.gnu.org/licenses/>.
*
*/
#include "../inc/MarlinConfig.h"
#if ENABLED(I2C_AMMETER)
#include "ammeter.h"
#ifndef I2C_AMMETER_IMAX
#define I2C_AMMETER_IMAX 0.500 // Calibration range 500 Milliamps
#endif
INA226 ina;
Ammeter ammeter;
float Ammeter::scale;
float Ammeter::current;
void Ammeter::init() {
ina.begin();
ina.configure(INA226_AVERAGES_16, INA226_BUS_CONV_TIME_1100US, INA226_SHUNT_CONV_TIME_1100US, INA226_MODE_SHUNT_BUS_CONT);
ina.calibrate(I2C_AMMETER_SHUNT_RESISTOR, I2C_AMMETER_IMAX);
}
float Ammeter::read() {
scale = 1;
current = ina.readShuntCurrent();
if (current <= 0.0001f) current = 0; // Clean up least-significant-bit amplification errors
if (current < 0.1f) scale = 1000;
return current * scale;
}
#endif // I2C_AMMETER

View File

@@ -0,0 +1,39 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2021 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "../inc/MarlinConfigPre.h"
#include <Wire.h>
#include <INA226.h>
class Ammeter {
private:
static float scale;
public:
static float current;
static void init();
static float read();
};
extern Ammeter ammeter;

View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -26,7 +26,8 @@
#include "babystep.h"
#include "../MarlinCore.h"
#include "../module/planner.h"
#include "../module/motion.h" // for axes_should_home()
#include "../module/planner.h" // for axis_steps_per_mm[]
#include "../module/stepper.h"
#if ENABLED(BABYSTEP_ALWAYS_AVAILABLE)
@@ -49,71 +50,18 @@ void Babystep::step_axis(const AxisEnum axis) {
}
}
void Babystep::add_mm(const AxisEnum axis, const float &mm) {
void Babystep::add_mm(const AxisEnum axis, const_float_t mm) {
add_steps(axis, mm * planner.settings.axis_steps_per_mm[axis]);
}
void Babystep::add_steps(const AxisEnum axis, const int16_t distance) {
if (DISABLED(BABYSTEP_WITHOUT_HOMING) && !TEST(axis_known_position, axis)) return;
if (DISABLED(BABYSTEP_WITHOUT_HOMING) && axes_should_home(_BV(axis))) return;
accum += distance; // Count up babysteps for the UI
#if ENABLED(BABYSTEP_DISPLAY_TOTAL)
axis_total[BS_TOTAL_IND(axis)] += distance;
#endif
#if ENABLED(BABYSTEP_ALWAYS_AVAILABLE)
#define BSA_ENABLE(AXIS) do{ switch (AXIS) { case X_AXIS: ENABLE_AXIS_X(); break; case Y_AXIS: ENABLE_AXIS_Y(); break; case Z_AXIS: ENABLE_AXIS_Z(); break; default: break; } }while(0)
#else
#define BSA_ENABLE(AXIS) NOOP
#endif
#if IS_CORE
#if ENABLED(BABYSTEP_XY)
switch (axis) {
case CORE_AXIS_1: // X on CoreXY and CoreXZ, Y on CoreYZ
BSA_ENABLE(CORE_AXIS_1);
BSA_ENABLE(CORE_AXIS_2);
steps[CORE_AXIS_1] += distance * 2;
steps[CORE_AXIS_2] += distance * 2;
break;
case CORE_AXIS_2: // Y on CoreXY, Z on CoreXZ and CoreYZ
BSA_ENABLE(CORE_AXIS_1);
BSA_ENABLE(CORE_AXIS_2);
steps[CORE_AXIS_1] += CORESIGN(distance * 2);
steps[CORE_AXIS_2] -= CORESIGN(distance * 2);
break;
case NORMAL_AXIS: // Z on CoreXY, Y on CoreXZ, X on CoreYZ
default:
BSA_ENABLE(NORMAL_AXIS);
steps[NORMAL_AXIS] += distance;
break;
}
#elif CORE_IS_XZ || CORE_IS_YZ
// Only Z stepping needs to be handled here
BSA_ENABLE(CORE_AXIS_1);
BSA_ENABLE(CORE_AXIS_2);
steps[CORE_AXIS_1] += CORESIGN(distance * 2);
steps[CORE_AXIS_2] -= CORESIGN(distance * 2);
#else
BSA_ENABLE(Z_AXIS);
steps[Z_AXIS] += distance;
#endif
#else
#if ENABLED(BABYSTEP_XY)
BSA_ENABLE(axis);
#else
BSA_ENABLE(Z_AXIS);
#endif
steps[BS_AXIS_IND(axis)] += distance;
#endif
#if ENABLED(BABYSTEP_ALWAYS_AVAILABLE)
gcode.reset_stepper_timeout();
#endif
#if ENABLED(INTEGRATED_BABYSTEPPING)
if (has_steps()) stepper.initiateBabystepping();
#endif
steps[BS_AXIS_IND(axis)] += distance;
TERN_(BABYSTEP_DISPLAY_TOTAL, axis_total[BS_TOTAL_IND(axis)] += distance);
TERN_(BABYSTEP_ALWAYS_AVAILABLE, gcode.reset_stepper_timeout());
TERN_(INTEGRATED_BABYSTEPPING, if (has_steps()) stepper.initiateBabystepping());
}
#endif // BABYSTEPPING

View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -55,16 +55,13 @@ public:
#if ENABLED(BABYSTEP_DISPLAY_TOTAL)
static int16_t axis_total[BS_TOTAL_IND(Z_AXIS) + 1]; // Total babysteps since G28
static inline void reset_total(const AxisEnum axis) {
if (true
#if ENABLED(BABYSTEP_XY)
&& axis == Z_AXIS
#endif
) axis_total[BS_TOTAL_IND(axis)] = 0;
if (TERN1(BABYSTEP_XY, axis == Z_AXIS))
axis_total[BS_TOTAL_IND(axis)] = 0;
}
#endif
static void add_steps(const AxisEnum axis, const int16_t distance);
static void add_mm(const AxisEnum axis, const float &mm);
static void add_mm(const AxisEnum axis, const_float_t mm);
static inline bool has_steps() {
return steps[BS_AXIS_IND(X_AXIS)] || steps[BS_AXIS_IND(Y_AXIS)] || steps[BS_AXIS_IND(Z_AXIS)];

67
Marlin/src/feature/backlash.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -60,13 +60,27 @@ Backlash backlash;
* spread over multiple segments, smoothing out artifacts even more.
*/
void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const int32_t &dc, const uint8_t dm, block_t * const block) {
static uint8_t last_direction_bits;
uint8_t changed_dir = last_direction_bits ^ dm;
// Ignore direction change if no steps are taken in that direction
if (da == 0) CBI(changed_dir, X_AXIS);
if (db == 0) CBI(changed_dir, Y_AXIS);
if (dc == 0) CBI(changed_dir, Z_AXIS);
void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const int32_t &dc, const axis_bits_t dm, block_t * const block) {
static axis_bits_t last_direction_bits;
axis_bits_t changed_dir = last_direction_bits ^ dm;
// Ignore direction change unless steps are taken in that direction
#if DISABLED(CORE_BACKLASH) || ENABLED(MARKFORGED_XY)
if (!da) CBI(changed_dir, X_AXIS);
if (!db) CBI(changed_dir, Y_AXIS);
if (!dc) CBI(changed_dir, Z_AXIS);
#elif CORE_IS_XY
if (!(da + db)) CBI(changed_dir, X_AXIS);
if (!(da - db)) CBI(changed_dir, Y_AXIS);
if (!dc) CBI(changed_dir, Z_AXIS);
#elif CORE_IS_XZ
if (!(da + dc)) CBI(changed_dir, X_AXIS);
if (!(da - dc)) CBI(changed_dir, Z_AXIS);
if (!db) CBI(changed_dir, Y_AXIS);
#elif CORE_IS_YZ
if (!(db + dc)) CBI(changed_dir, Y_AXIS);
if (!(db - dc)) CBI(changed_dir, Z_AXIS);
if (!da) CBI(changed_dir, X_AXIS);
#endif
last_direction_bits ^= changed_dir;
if (correction == 0) return;
@@ -90,7 +104,7 @@ void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const
const float f_corr = float(correction) / 255.0f;
LOOP_XYZ(axis) {
LOOP_LINEAR_AXES(axis) {
if (distance_mm[axis]) {
const bool reversing = TEST(dm,axis);
@@ -105,42 +119,57 @@ void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const
// Take up a portion of the residual_error in this segment, but only when
// the current segment travels in the same direction as the correction
if (reversing == (error_correction < 0)) {
if (segment_proportion == 0)
segment_proportion = _MIN(1.0f, block->millimeters / smoothing_mm);
if (segment_proportion == 0) segment_proportion = _MIN(1.0f, block->millimeters / smoothing_mm);
error_correction = CEIL(segment_proportion * error_correction);
}
else
error_correction = 0; // Don't take up any backlash in this segment, as it would subtract steps
}
#endif
// Making a correction reduces the residual error and adds block steps
// This correction reduces the residual error and adds block steps
if (error_correction) {
block->steps[axis] += ABS(error_correction);
residual_error[axis] -= error_correction;
#if ENABLED(CORE_BACKLASH)
switch (axis) {
case CORE_AXIS_1:
//block->steps[CORE_AXIS_2] += influence_distance_mm[axis] * planner.settings.axis_steps_per_mm[CORE_AXIS_2];
//SERIAL_ECHOLNPGM("CORE_AXIS_1 dir change. distance=", distance_mm[axis], " r.err=", residual_error[axis],
// " da=", da, " db=", db, " block->steps[axis]=", block->steps[axis], " err_corr=", error_correction);
break;
case CORE_AXIS_2:
//block->steps[CORE_AXIS_1] += influence_distance_mm[axis] * planner.settings.axis_steps_per_mm[CORE_AXIS_1];;
//SERIAL_ECHOLNPGM("CORE_AXIS_2 dir change. distance=", distance_mm[axis], " r.err=", residual_error[axis],
// " da=", da, " db=", db, " block->steps[axis]=", block->steps[axis], " err_corr=", error_correction);
break;
case NORMAL_AXIS: break;
}
residual_error[axis] = 0; // No residual_error needed for next CORE block, I think...
#else
residual_error[axis] -= error_correction;
#endif
}
}
}
}
#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
#if HAS_CUSTOM_PROBE_PIN
#define TEST_PROBE_PIN (READ(Z_MIN_PROBE_PIN) != Z_MIN_PROBE_ENDSTOP_INVERTING)
#else
#define TEST_PROBE_PIN (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING)
#endif
#include "../module/probe.h"
// Measure Z backlash by raising nozzle in increments until probe deactivates
void Backlash::measure_with_probe() {
if (measured_count.z == 255) return;
const float start_height = current_position.z;
while (current_position.z < (start_height + BACKLASH_MEASUREMENT_LIMIT) && TEST_PROBE_PIN)
while (current_position.z < (start_height + BACKLASH_MEASUREMENT_LIMIT) && PROBE_TRIGGERED())
do_blocking_move_to_z(current_position.z + BACKLASH_MEASUREMENT_RESOLUTION, MMM_TO_MMS(BACKLASH_MEASUREMENT_FEEDRATE));
// The backlash from all probe points is averaged, so count the number of measurements
measured_mm.z += current_position.z - start_height;
measured_count.z++;
}
#endif
#endif // BACKLASH_COMPENSATION

28
Marlin/src/feature/backlash.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -35,7 +35,7 @@ public:
static float smoothing_mm;
#endif
static inline void set_correction(const float &v) { correction = _MAX(0, _MIN(1.0, v)) * all_on; }
static inline void set_correction(const_float_t v) { correction = _MAX(0, _MIN(1.0, v)) * all_on; }
static inline float get_correction() { return float(ui8_to_percent(correction)) / 100.0f; }
#else
static constexpr uint8_t correction = (BACKLASH_CORRECTION) * 0xFF;
@@ -54,34 +54,24 @@ public:
#endif
static inline float get_measurement(const AxisEnum a) {
UNUSED(a);
// Return the measurement averaged over all readings
return (
#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
measured_count[a] > 0 ? measured_mm[a] / measured_count[a] :
#endif
0
return TERN(MEASURE_BACKLASH_WHEN_PROBING
, measured_count[a] > 0 ? measured_mm[a] / measured_count[a] : 0
, 0
);
#if DISABLED(MEASURE_BACKLASH_WHEN_PROBING)
UNUSED(a);
#endif
}
static inline bool has_measurement(const AxisEnum a) {
return (false
#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
|| (measured_count[a] > 0)
#endif
);
#if DISABLED(MEASURE_BACKLASH_WHEN_PROBING)
UNUSED(a);
#endif
UNUSED(a);
return TERN0(MEASURE_BACKLASH_WHEN_PROBING, measured_count[a] > 0);
}
static inline bool has_any_measurement() {
return has_measurement(X_AXIS) || has_measurement(Y_AXIS) || has_measurement(Z_AXIS);
}
void add_correction_steps(const int32_t &da, const int32_t &db, const int32_t &dc, const uint8_t dm, block_t * const block);
void add_correction_steps(const int32_t &da, const int32_t &db, const int32_t &dc, const axis_bits_t dm, block_t * const block);
};
extern Backlash backlash;

2
Marlin/src/feature/baricuda.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/

2
Marlin/src/feature/baricuda.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once

72
Marlin/src/feature/bedlevel/abl/abl.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -47,11 +47,11 @@ static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t
if (DEBUGGING(LEVELING)) {
DEBUG_ECHOPGM("Extrapolate [");
if (x < 10) DEBUG_CHAR(' ');
DEBUG_ECHO((int)x);
DEBUG_ECHO(x);
DEBUG_CHAR(xdir ? (xdir > 0 ? '+' : '-') : ' ');
DEBUG_CHAR(' ');
if (y < 10) DEBUG_CHAR(' ');
DEBUG_ECHO((int)y);
DEBUG_ECHO(y);
DEBUG_CHAR(ydir ? (ydir > 0 ? '+' : '-') : ' ');
DEBUG_ECHOLNPGM("]");
}
@@ -74,9 +74,7 @@ static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t
// Take the average instead of the median
z_values[x][y] = (a + b + c) / 3.0;
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(x, y, z_values[x][y]);
#endif
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y]));
// Median is robust (ignores outliers).
// z_values[x][y] = (a < b) ? ((b < c) ? b : (c < a) ? a : c)
@@ -87,9 +85,9 @@ static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t
//#define EXTRAPOLATE_FROM_EDGE
#if ENABLED(EXTRAPOLATE_FROM_EDGE)
#if GRID_MAX_POINTS_X < GRID_MAX_POINTS_Y
#if (GRID_MAX_POINTS_X) < (GRID_MAX_POINTS_Y)
#define HALF_IN_X
#elif GRID_MAX_POINTS_Y < GRID_MAX_POINTS_X
#elif (GRID_MAX_POINTS_Y) < (GRID_MAX_POINTS_X)
#define HALF_IN_Y
#endif
#endif
@@ -100,23 +98,23 @@ static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t
*/
void extrapolate_unprobed_bed_level() {
#ifdef HALF_IN_X
constexpr uint8_t ctrx2 = 0, xlen = GRID_MAX_POINTS_X - 1;
constexpr uint8_t ctrx2 = 0, xend = GRID_MAX_POINTS_X - 1;
#else
constexpr uint8_t ctrx1 = (GRID_MAX_POINTS_X - 1) / 2, // left-of-center
ctrx2 = (GRID_MAX_POINTS_X) / 2, // right-of-center
xlen = ctrx1;
constexpr uint8_t ctrx1 = (GRID_MAX_CELLS_X) / 2, // left-of-center
ctrx2 = (GRID_MAX_POINTS_X) / 2, // right-of-center
xend = ctrx1;
#endif
#ifdef HALF_IN_Y
constexpr uint8_t ctry2 = 0, ylen = GRID_MAX_POINTS_Y - 1;
constexpr uint8_t ctry2 = 0, yend = GRID_MAX_POINTS_Y - 1;
#else
constexpr uint8_t ctry1 = (GRID_MAX_POINTS_Y - 1) / 2, // top-of-center
ctry2 = (GRID_MAX_POINTS_Y) / 2, // bottom-of-center
ylen = ctry1;
constexpr uint8_t ctry1 = (GRID_MAX_CELLS_Y) / 2, // top-of-center
ctry2 = (GRID_MAX_POINTS_Y) / 2, // bottom-of-center
yend = ctry1;
#endif
LOOP_LE_N(xo, xlen)
LOOP_LE_N(yo, ylen) {
LOOP_LE_N(xo, xend)
LOOP_LE_N(yo, yend) {
uint8_t x2 = ctrx2 + xo, y2 = ctry2 + yo;
#ifndef HALF_IN_X
const uint8_t x1 = ctrx1 - xo;
@@ -145,8 +143,8 @@ void print_bilinear_leveling_grid() {
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
#define ABL_GRID_POINTS_VIRT_X (GRID_MAX_POINTS_X - 1) * (BILINEAR_SUBDIVISIONS) + 1
#define ABL_GRID_POINTS_VIRT_Y (GRID_MAX_POINTS_Y - 1) * (BILINEAR_SUBDIVISIONS) + 1
#define ABL_GRID_POINTS_VIRT_X GRID_MAX_CELLS_X * (BILINEAR_SUBDIVISIONS) + 1
#define ABL_GRID_POINTS_VIRT_Y GRID_MAX_CELLS_Y * (BILINEAR_SUBDIVISIONS) + 1
#define ABL_TEMP_POINTS_X (GRID_MAX_POINTS_X + 2)
#define ABL_TEMP_POINTS_Y (GRID_MAX_POINTS_Y + 2)
float z_values_virt[ABL_GRID_POINTS_VIRT_X][ABL_GRID_POINTS_VIRT_Y];
@@ -163,10 +161,18 @@ void print_bilinear_leveling_grid() {
#define LINEAR_EXTRAPOLATION(E, I) ((E) * 2 - (I))
float bed_level_virt_coord(const uint8_t x, const uint8_t y) {
uint8_t ep = 0, ip = 1;
if (x > (GRID_MAX_POINTS_X) + 1 || y > (GRID_MAX_POINTS_Y) + 1) {
// The requested point requires extrapolating two points beyond the mesh.
// These values are only requested for the edges of the mesh, which are always an actual mesh point,
// and do not require interpolation. When interpolation is not needed, this "Mesh + 2" point is
// cancelled out in bed_level_virt_cmr and does not impact the result. Return 0.0 rather than
// making this function more complex by extrapolating two points.
return 0.0;
}
if (!x || x == ABL_TEMP_POINTS_X - 1) {
if (x) {
ep = GRID_MAX_POINTS_X - 1;
ip = GRID_MAX_POINTS_X - 2;
ep = (GRID_MAX_POINTS_X) - 1;
ip = GRID_MAX_CELLS_X - 1;
}
if (WITHIN(y, 1, ABL_TEMP_POINTS_Y - 2))
return LINEAR_EXTRAPOLATION(
@@ -181,8 +187,8 @@ void print_bilinear_leveling_grid() {
}
if (!y || y == ABL_TEMP_POINTS_Y - 1) {
if (y) {
ep = GRID_MAX_POINTS_Y - 1;
ip = GRID_MAX_POINTS_Y - 2;
ep = (GRID_MAX_POINTS_Y) - 1;
ip = GRID_MAX_CELLS_Y - 1;
}
if (WITHIN(x, 1, ABL_TEMP_POINTS_X - 2))
return LINEAR_EXTRAPOLATION(
@@ -207,7 +213,7 @@ void print_bilinear_leveling_grid() {
) * 0.5f;
}
static float bed_level_virt_2cmr(const uint8_t x, const uint8_t y, const float &tx, const float &ty) {
static float bed_level_virt_2cmr(const uint8_t x, const uint8_t y, const_float_t tx, const_float_t ty) {
float row[4], column[4];
LOOP_L_N(i, 4) {
LOOP_L_N(j, 4) {
@@ -241,9 +247,7 @@ void print_bilinear_leveling_grid() {
// Refresh after other values have been updated
void refresh_bed_level() {
bilinear_grid_factor = bilinear_grid_spacing.reciprocal();
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
bed_level_virt_interpolate();
#endif
TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate());
}
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
@@ -332,11 +336,11 @@ float bilinear_z_offset(const xy_pos_t &raw) {
/*
static float last_offset = 0;
if (ABS(last_offset - offset) > 0.2) {
SERIAL_ECHOLNPAIR("Sudden Shift at x=", rel.x, " / ", bilinear_grid_spacing.x, " -> thisg.x=", thisg.x);
SERIAL_ECHOLNPAIR(" y=", rel.y, " / ", bilinear_grid_spacing.y, " -> thisg.y=", thisg.y);
SERIAL_ECHOLNPAIR(" ratio.x=", ratio.x, " ratio.y=", ratio.y);
SERIAL_ECHOLNPAIR(" z1=", z1, " z2=", z2, " z3=", z3, " z4=", z4);
SERIAL_ECHOLNPAIR(" L=", L, " R=", R, " offset=", offset);
SERIAL_ECHOLNPGM("Sudden Shift at x=", rel.x, " / ", bilinear_grid_spacing.x, " -> thisg.x=", thisg.x);
SERIAL_ECHOLNPGM(" y=", rel.y, " / ", bilinear_grid_spacing.y, " -> thisg.y=", thisg.y);
SERIAL_ECHOLNPGM(" ratio.x=", ratio.x, " ratio.y=", ratio.y);
SERIAL_ECHOLNPGM(" z1=", z1, " z2=", z2, " z3=", z3, " z4=", z4);
SERIAL_ECHOLNPGM(" L=", L, " R=", R, " offset=", offset);
}
last_offset = offset;
//*/
@@ -352,7 +356,7 @@ float bilinear_z_offset(const xy_pos_t &raw) {
* Prepare a bilinear-leveled linear move on Cartesian,
* splitting the move where it crosses grid borders.
*/
void bilinear_line_to_destination(const feedRate_t scaled_fr_mm_s, uint16_t x_splits, uint16_t y_splits) {
void bilinear_line_to_destination(const_feedRate_t scaled_fr_mm_s, uint16_t x_splits, uint16_t y_splits) {
// Get current and destination cells for this line
xy_int_t c1 { CELL_INDEX(x, current_position.x), CELL_INDEX(y, current_position.y) },
c2 { CELL_INDEX(x, destination.x), CELL_INDEX(y, destination.y) };

4
Marlin/src/feature/bedlevel/abl/abl.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -37,7 +37,7 @@ void refresh_bed_level();
#endif
#if IS_CARTESIAN && DISABLED(SEGMENT_LEVELED_MOVES)
void bilinear_line_to_destination(const feedRate_t &scaled_fr_mm_s, uint16_t x_splits=0xFFFF, uint16_t y_splits=0xFFFF);
void bilinear_line_to_destination(const_feedRate_t scaled_fr_mm_s, uint16_t x_splits=0xFFFF, uint16_t y_splits=0xFFFF);
#endif
#define _GET_MESH_X(I) float(bilinear_start.x + (I) * bilinear_grid_spacing.x)

70
Marlin/src/feature/bedlevel/bedlevel.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -36,7 +36,7 @@
#endif
#if ENABLED(LCD_BED_LEVELING)
#include "../../lcd/ultralcd.h"
#include "../../lcd/marlinui.h"
#endif
#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE)
@@ -47,17 +47,9 @@
#endif
bool leveling_is_valid() {
return
#if ENABLED(MESH_BED_LEVELING)
mbl.has_mesh()
#elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
!!bilinear_grid_spacing.x
#elif ENABLED(AUTO_BED_LEVELING_UBL)
ubl.mesh_is_valid()
#else // 3POINT, LINEAR
true
#endif
;
return TERN1(MESH_BED_LEVELING, mbl.has_mesh())
&& TERN1(AUTO_BED_LEVELING_BILINEAR, !!bilinear_grid_spacing.x)
&& TERN1(AUTO_BED_LEVELING_UBL, ubl.mesh_is_valid());
}
/**
@@ -69,11 +61,7 @@ bool leveling_is_valid() {
*/
void set_bed_leveling_enabled(const bool enable/*=true*/) {
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
const bool can_change = (!enable || leveling_is_valid());
#else
constexpr bool can_change = true;
#endif
const bool can_change = TERN1(AUTO_BED_LEVELING_BILINEAR, !enable || leveling_is_valid());
if (can_change && enable != planner.leveling_active) {
@@ -110,7 +98,7 @@ TemporaryBedLevelingState::TemporaryBedLevelingState(const bool enable) : saved(
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
void set_z_fade_height(const float zfh, const bool do_report/*=true*/) {
void set_z_fade_height(const_float_t zfh, const bool do_report/*=true*/) {
if (planner.z_fade_height == zfh) return;
@@ -145,9 +133,7 @@ void reset_bed_level() {
bilinear_grid_spacing.reset();
GRID_LOOP(x, y) {
z_values[x][y] = NAN;
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(x, y, 0);
#endif
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, 0));
}
#elif ABL_PLANAR
planner.bed_level_matrix.set_to_identity();
@@ -174,7 +160,7 @@ void reset_bed_level() {
#ifndef SCAD_MESH_OUTPUT
LOOP_L_N(x, sx) {
serial_spaces(precision + (x < 10 ? 3 : 2));
SERIAL_ECHO(int(x));
SERIAL_ECHO(x);
}
SERIAL_EOL();
#endif
@@ -186,7 +172,7 @@ void reset_bed_level() {
SERIAL_ECHOPGM(" ["); // open sub-array
#else
if (y < 10) SERIAL_CHAR(' ');
SERIAL_ECHO(int(y));
SERIAL_ECHO(y);
#endif
LOOP_L_N(x, sx) {
SERIAL_CHAR(' ');
@@ -210,7 +196,7 @@ void reset_bed_level() {
#endif
}
#ifdef SCAD_MESH_OUTPUT
SERIAL_CHAR(' ', ']'); // close sub-array
SERIAL_ECHOPGM(" ]"); // close sub-array
if (y < sy - 1) SERIAL_CHAR(',');
#endif
SERIAL_EOL();
@@ -227,29 +213,27 @@ void reset_bed_level() {
void _manual_goto_xy(const xy_pos_t &pos) {
// Get the resting Z position for after the XY move
#ifdef MANUAL_PROBE_START_Z
constexpr float startz = _MAX(0, MANUAL_PROBE_START_Z);
#if MANUAL_PROBE_HEIGHT > 0
do_blocking_move_to_xy_z(pos, MANUAL_PROBE_HEIGHT);
do_blocking_move_to_z(startz);
#else
do_blocking_move_to_xy_z(pos, startz);
#endif
#elif MANUAL_PROBE_HEIGHT > 0
const float prev_z = current_position.z;
do_blocking_move_to_xy_z(pos, MANUAL_PROBE_HEIGHT);
do_blocking_move_to_z(prev_z);
constexpr float finalz = _MAX(0, MANUAL_PROBE_START_Z); // If a MANUAL_PROBE_START_Z value is set, always respect it
#else
do_blocking_move_to_xy(pos);
#warning "It's recommended to set some MANUAL_PROBE_START_Z value for manual leveling."
#endif
#if Z_CLEARANCE_BETWEEN_MANUAL_PROBES > 0 // A probe/obstacle clearance exists so there is a raise:
#ifndef MANUAL_PROBE_START_Z
const float finalz = current_position.z; // - Use the current Z for starting-Z if no MANUAL_PROBE_START_Z was provided
#endif
do_blocking_move_to_xy_z(pos, Z_CLEARANCE_BETWEEN_MANUAL_PROBES); // - Raise Z, then move to the new XY
do_blocking_move_to_z(finalz); // - Lower down to the starting Z height, ready for adjustment!
#elif defined(MANUAL_PROBE_START_Z) // A starting-Z was provided, but there's no raise:
do_blocking_move_to_xy_z(pos, finalz); // - Move in XY then down to the starting Z height, ready for adjustment!
#else // Zero raise and no starting Z height either:
do_blocking_move_to_xy(pos); // - Move over with no raise, ready for adjustment!
#endif
current_position = pos;
#if ENABLED(LCD_BED_LEVELING)
ui.wait_for_move = false;
#endif
TERN_(LCD_BED_LEVELING, ui.wait_for_move = false);
}
#endif
#endif // MESH_BED_LEVELING || PROBE_MANUALLY
#endif // HAS_LEVELING

8
Marlin/src/feature/bedlevel/bedlevel.h Executable file → Normal file
View File

@@ -16,13 +16,17 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "../../inc/MarlinConfigPre.h"
#if EITHER(RESTORE_LEVELING_AFTER_G28, ENABLE_LEVELING_AFTER_G28)
#define CAN_SET_LEVELING_AFTER_G28 1
#endif
#if ENABLED(PROBE_MANUALLY)
extern bool g29_in_progress;
#else
@@ -34,7 +38,7 @@ void set_bed_leveling_enabled(const bool enable=true);
void reset_bed_level();
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
void set_z_fade_height(const float zfh, const bool do_report=true);
void set_z_fade_height(const_float_t zfh, const bool do_report=true);
#endif
#if EITHER(MESH_BED_LEVELING, PROBE_MANUALLY)

View File

@@ -0,0 +1,110 @@
/*********************
* hilbert_curve.cpp *
*********************/
/****************************************************************************
* Written By Marcio Teixeira 2021 - SynDaver Labs, Inc. *
* *
* 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. *
* *
* To view a copy of the GNU General Public License, go to the following *
* location: <https://www.gnu.org/licenses/>. *
****************************************************************************/
#include "../../inc/MarlinConfig.h"
#if ENABLED(UBL_HILBERT_CURVE)
#include "bedlevel.h"
#include "hilbert_curve.h"
constexpr int8_t to_fix(int8_t v) { return v * 2; }
constexpr int8_t to_int(int8_t v) { return v / 2; }
constexpr uint8_t log2(uint8_t n) { return (n > 1) ? 1 + log2(n >> 1) : 0; }
constexpr uint8_t order(uint8_t n) { return uint8_t(log2(n - 1)) + 1; }
constexpr uint8_t ord = order(_MAX(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y));
constexpr uint8_t dim = _BV(ord);
static inline bool eval_candidate(int8_t x, int8_t y, hilbert_curve::callback_ptr func, void *data) {
// The print bed likely has fewer points than the full Hilbert
// curve, so cull unnecessary points
return x < (GRID_MAX_POINTS_X) && y < (GRID_MAX_POINTS_Y) ? func(x, y, data) : false;
}
bool hilbert_curve::hilbert(int8_t x, int8_t y, int8_t xi, int8_t xj, int8_t yi, int8_t yj, uint8_t n, hilbert_curve::callback_ptr func, void *data) {
/**
* Hilbert space-filling curve implementation
*
* x and y : coordinates of the bottom left corner
* xi and xj : i and j components of the unit x vector of the frame
* yi and yj : i and j components of the unit y vector of the frame
*
* From: http://www.fundza.com/algorithmic/space_filling/hilbert/basics/index.html
*/
if (n)
return hilbert(x, y, yi/2, yj/2, xi/2, xj/2, n-1, func, data) ||
hilbert(x+xi/2, y+xj/2, xi/2, xj/2, yi/2, yj/2, n-1, func, data) ||
hilbert(x+xi/2+yi/2, y+xj/2+yj/2, xi/2, xj/2, yi/2, yj/2, n-1, func, data) ||
hilbert(x+xi/2+yi, y+xj/2+yj, -yi/2, -yj/2, -xi/2, -xj/2, n-1, func, data);
else
return eval_candidate(to_int(x+(xi+yi)/2), to_int(y+(xj+yj)/2), func, data);
}
/**
* Calls func(x, y, data) for all points in the Hilbert curve.
* If that function returns true, the search is terminated.
*/
bool hilbert_curve::search(hilbert_curve::callback_ptr func, void *data) {
return hilbert(to_fix(0), to_fix(0),to_fix(dim), to_fix(0), to_fix(0), to_fix(dim), ord, func, data);
}
/* Helper function for starting the search at a particular point */
typedef struct {
uint8_t x, y;
bool found_1st;
hilbert_curve::callback_ptr func;
void *data;
} search_from_t;
static bool search_from_helper(uint8_t x, uint8_t y, void *data) {
search_from_t *d = (search_from_t *) data;
if (d->x == x && d->y == y)
d->found_1st = true;
return d->found_1st ? d->func(x, y, d->data) : false;
}
/**
* Same as search, except start at a specific grid intersection point.
*/
bool hilbert_curve::search_from(uint8_t x, uint8_t y, hilbert_curve::callback_ptr func, void *data) {
search_from_t d;
d.x = x;
d.y = y;
d.found_1st = false;
d.func = func;
d.data = data;
// Call twice to allow search to wrap back to the beginning and picked up points prior to the start.
return search(search_from_helper, &d) || search(search_from_helper, &d);
}
/**
* Like search_from, but takes a bed position and starts from the nearest
* point on the Hilbert curve.
*/
bool hilbert_curve::search_from_closest(const xy_pos_t &pos, hilbert_curve::callback_ptr func, void *data) {
// Find closest grid intersection
const uint8_t grid_x = LROUND(constrain(float(pos.x - (MESH_MIN_X)) / (MESH_X_DIST), 0, (GRID_MAX_POINTS_X) - 1));
const uint8_t grid_y = LROUND(constrain(float(pos.y - (MESH_MIN_Y)) / (MESH_Y_DIST), 0, (GRID_MAX_POINTS_Y) - 1));
return search_from(grid_x, grid_y, func, data);
}
#endif // UBL_HILBERT_CURVE

View File

@@ -0,0 +1,32 @@
/*******************
* hilbert_curve.h *
*******************/
/****************************************************************************
* Written By Marcio Teixeira 2021 - SynDaver Labs, Inc. *
* *
* 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. *
* *
* To view a copy of the GNU General Public License, go to the following *
* location: <https://www.gnu.org/licenses/>. *
****************************************************************************/
#pragma once
class hilbert_curve {
public:
typedef bool (*callback_ptr)(uint8_t x, uint8_t y, void *data);
static bool search(callback_ptr func, void *data);
static bool search_from(uint8_t x, uint8_t y, callback_ptr func, void *data);
static bool search_from_closest(const xy_pos_t &pos, callback_ptr func, void *data);
private:
static bool hilbert(int8_t x, int8_t y, int8_t xi, int8_t xj, int8_t yi, int8_t yj, uint8_t n, callback_ptr func, void *data);
};

16
Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -61,18 +61,18 @@
* Prepare a mesh-leveled linear move in a Cartesian setup,
* splitting the move where it crosses mesh borders.
*/
void mesh_bed_leveling::line_to_destination(const feedRate_t &scaled_fr_mm_s, uint8_t x_splits, uint8_t y_splits) {
void mesh_bed_leveling::line_to_destination(const_feedRate_t scaled_fr_mm_s, uint8_t x_splits, uint8_t y_splits) {
// Get current and destination cells for this line
xy_int8_t scel = cell_indexes(current_position), ecel = cell_indexes(destination);
NOMORE(scel.x, GRID_MAX_POINTS_X - 2);
NOMORE(scel.y, GRID_MAX_POINTS_Y - 2);
NOMORE(ecel.x, GRID_MAX_POINTS_X - 2);
NOMORE(ecel.y, GRID_MAX_POINTS_Y - 2);
NOMORE(scel.x, GRID_MAX_CELLS_X - 1);
NOMORE(scel.y, GRID_MAX_CELLS_Y - 1);
NOMORE(ecel.x, GRID_MAX_CELLS_X - 1);
NOMORE(ecel.y, GRID_MAX_CELLS_Y - 1);
// Start and end in the same cell? No split needed.
if (scel == ecel) {
line_to_destination(scaled_fr_mm_s);
current_position = destination;
line_to_current_position(scaled_fr_mm_s);
return;
}
@@ -104,8 +104,8 @@
else {
// Must already have been split on these border(s)
// This should be a rare case.
line_to_destination(scaled_fr_mm_s);
current_position = destination;
line_to_current_position(scaled_fr_mm_s);
return;
}

45
Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -32,8 +32,8 @@ enum MeshLevelingState : char {
MeshReset // G29 S5
};
#define MESH_X_DIST (float(MESH_MAX_X - (MESH_MIN_X)) / float(GRID_MAX_POINTS_X - 1))
#define MESH_Y_DIST (float(MESH_MAX_Y - (MESH_MIN_Y)) / float(GRID_MAX_POINTS_Y - 1))
#define MESH_X_DIST (float(MESH_MAX_X - (MESH_MIN_X)) / (GRID_MAX_CELLS_X))
#define MESH_Y_DIST (float(MESH_MAX_Y - (MESH_MIN_Y)) / (GRID_MAX_CELLS_Y))
#define _GET_MESH_X(I) mbl.index_to_xpos[I]
#define _GET_MESH_Y(J) mbl.index_to_ypos[J]
#define Z_VALUES_ARR mbl.z_values
@@ -56,56 +56,54 @@ public:
return false;
}
static void set_z(const int8_t px, const int8_t py, const float &z) { z_values[px][py] = z; }
static void set_z(const int8_t px, const int8_t py, const_float_t z) { z_values[px][py] = z; }
static inline void zigzag(const int8_t index, int8_t &px, int8_t &py) {
px = index % (GRID_MAX_POINTS_X);
py = index / (GRID_MAX_POINTS_X);
if (py & 1) px = (GRID_MAX_POINTS_X - 1) - px; // Zig zag
if (py & 1) px = (GRID_MAX_POINTS_X) - 1 - px; // Zig zag
}
static void set_zigzag_z(const int8_t index, const float &z) {
static void set_zigzag_z(const int8_t index, const_float_t z) {
int8_t px, py;
zigzag(index, px, py);
set_z(px, py, z);
}
static int8_t cell_index_x(const float &x) {
static int8_t cell_index_x(const_float_t x) {
int8_t cx = (x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST);
return constrain(cx, 0, (GRID_MAX_POINTS_X) - 2);
return constrain(cx, 0, GRID_MAX_CELLS_X - 1);
}
static int8_t cell_index_y(const float &y) {
static int8_t cell_index_y(const_float_t y) {
int8_t cy = (y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST);
return constrain(cy, 0, (GRID_MAX_POINTS_Y) - 2);
return constrain(cy, 0, GRID_MAX_CELLS_Y - 1);
}
static inline xy_int8_t cell_indexes(const float &x, const float &y) {
static inline xy_int8_t cell_indexes(const_float_t x, const_float_t y) {
return { cell_index_x(x), cell_index_y(y) };
}
static inline xy_int8_t cell_indexes(const xy_pos_t &xy) { return cell_indexes(xy.x, xy.y); }
static int8_t probe_index_x(const float &x) {
static int8_t probe_index_x(const_float_t x) {
int8_t px = (x - (MESH_MIN_X) + 0.5f * (MESH_X_DIST)) * RECIPROCAL(MESH_X_DIST);
return WITHIN(px, 0, GRID_MAX_POINTS_X - 1) ? px : -1;
return WITHIN(px, 0, (GRID_MAX_POINTS_X) - 1) ? px : -1;
}
static int8_t probe_index_y(const float &y) {
static int8_t probe_index_y(const_float_t y) {
int8_t py = (y - (MESH_MIN_Y) + 0.5f * (MESH_Y_DIST)) * RECIPROCAL(MESH_Y_DIST);
return WITHIN(py, 0, GRID_MAX_POINTS_Y - 1) ? py : -1;
return WITHIN(py, 0, (GRID_MAX_POINTS_Y) - 1) ? py : -1;
}
static inline xy_int8_t probe_indexes(const float &x, const float &y) {
static inline xy_int8_t probe_indexes(const_float_t x, const_float_t y) {
return { probe_index_x(x), probe_index_y(y) };
}
static inline xy_int8_t probe_indexes(const xy_pos_t &xy) { return probe_indexes(xy.x, xy.y); }
static float calc_z0(const float &a0, const float &a1, const float &z1, const float &a2, const float &z2) {
static float calc_z0(const_float_t a0, const_float_t a1, const_float_t z1, const_float_t a2, const_float_t z2) {
const float delta_z = (z2 - z1) / (a2 - a1),
delta_a = a0 - a1;
return z1 + delta_a * delta_z;
}
static float get_z(const xy_pos_t &pos
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
, const float &factor=1.0f
#endif
OPTARG(ENABLE_LEVELING_FADE_HEIGHT, const_float_t factor=1.0f)
) {
#if DISABLED(ENABLE_LEVELING_FADE_HEIGHT)
constexpr float factor = 1.0f;
@@ -114,13 +112,14 @@ public:
const float x1 = index_to_xpos[ind.x], x2 = index_to_xpos[ind.x+1],
y1 = index_to_xpos[ind.y], y2 = index_to_xpos[ind.y+1],
z1 = calc_z0(pos.x, x1, z_values[ind.x][ind.y ], x2, z_values[ind.x+1][ind.y ]),
z2 = calc_z0(pos.x, x1, z_values[ind.x][ind.y+1], x2, z_values[ind.x+1][ind.y+1]);
z2 = calc_z0(pos.x, x1, z_values[ind.x][ind.y+1], x2, z_values[ind.x+1][ind.y+1]),
zf = calc_z0(pos.y, y1, z1, y2, z2);
return z_offset + calc_z0(pos.y, y1, z1, y2, z2) * factor;
return z_offset + zf * factor;
}
#if IS_CARTESIAN && DISABLED(SEGMENT_LEVELED_MOVES)
static void line_to_destination(const feedRate_t &scaled_fr_mm_s, uint8_t x_splits=0xFF, uint8_t y_splits=0xFF);
static void line_to_destination(const_feedRate_t scaled_fr_mm_s, uint8_t x_splits=0xFF, uint8_t y_splits=0xFF);
#endif
};

427
Marlin/src/feature/bedlevel/ubl/ubl.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -24,220 +24,279 @@
#if ENABLED(AUTO_BED_LEVELING_UBL)
#include "../bedlevel.h"
#include "../bedlevel.h"
unified_bed_leveling ubl;
unified_bed_leveling ubl;
#include "../../../MarlinCore.h"
#include "../../../gcode/gcode.h"
#include "../../../MarlinCore.h"
#include "../../../gcode/gcode.h"
#include "../../../module/configuration_store.h"
#include "../../../module/planner.h"
#include "../../../module/motion.h"
#include "../../../module/probe.h"
#include "../../../module/settings.h"
#include "../../../module/planner.h"
#include "../../../module/motion.h"
#include "../../../module/probe.h"
#include "../../../module/temperature.h"
#if ENABLED(EXTENSIBLE_UI)
#include "../../../lcd/extui/ui_api.h"
#endif
#include "math.h"
void unified_bed_leveling::echo_name() { SERIAL_ECHOPGM("Unified Bed Leveling"); }
void unified_bed_leveling::report_current_mesh() {
if (!leveling_is_valid()) return;
SERIAL_ECHO_MSG(" G29 I999");
GRID_LOOP(x, y)
if (!isnan(z_values[x][y])) {
SERIAL_ECHO_START();
SERIAL_ECHOPGM(" M421 I", x, " J", y);
SERIAL_ECHOLNPAIR_F_P(SP_Z_STR, z_values[x][y], 4);
serial_delay(75); // Prevent Printrun from exploding
}
}
void unified_bed_leveling::report_state() {
echo_name();
SERIAL_ECHO_TERNARY(planner.leveling_active, " System v" UBL_VERSION " ", "", "in", "active\n");
serial_delay(50);
}
int8_t unified_bed_leveling::storage_slot;
float unified_bed_leveling::z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
#define _GRIDPOS(A,N) (MESH_MIN_##A + N * (MESH_##A##_DIST))
const float
unified_bed_leveling::_mesh_index_to_xpos[GRID_MAX_POINTS_X] PROGMEM = ARRAY_N(GRID_MAX_POINTS_X,
_GRIDPOS(X, 0), _GRIDPOS(X, 1), _GRIDPOS(X, 2), _GRIDPOS(X, 3),
_GRIDPOS(X, 4), _GRIDPOS(X, 5), _GRIDPOS(X, 6), _GRIDPOS(X, 7),
_GRIDPOS(X, 8), _GRIDPOS(X, 9), _GRIDPOS(X, 10), _GRIDPOS(X, 11),
_GRIDPOS(X, 12), _GRIDPOS(X, 13), _GRIDPOS(X, 14), _GRIDPOS(X, 15)
),
unified_bed_leveling::_mesh_index_to_ypos[GRID_MAX_POINTS_Y] PROGMEM = ARRAY_N(GRID_MAX_POINTS_Y,
_GRIDPOS(Y, 0), _GRIDPOS(Y, 1), _GRIDPOS(Y, 2), _GRIDPOS(Y, 3),
_GRIDPOS(Y, 4), _GRIDPOS(Y, 5), _GRIDPOS(Y, 6), _GRIDPOS(Y, 7),
_GRIDPOS(Y, 8), _GRIDPOS(Y, 9), _GRIDPOS(Y, 10), _GRIDPOS(Y, 11),
_GRIDPOS(Y, 12), _GRIDPOS(Y, 13), _GRIDPOS(Y, 14), _GRIDPOS(Y, 15)
);
volatile int16_t unified_bed_leveling::encoder_diff;
unified_bed_leveling::unified_bed_leveling() { reset(); }
void unified_bed_leveling::reset() {
const bool was_enabled = planner.leveling_active;
set_bed_leveling_enabled(false);
storage_slot = -1;
ZERO(z_values);
#if ENABLED(EXTENSIBLE_UI)
#include "../../../lcd/extui/ui_api.h"
GRID_LOOP(x, y) ExtUI::onMeshUpdate(x, y, 0);
#endif
if (was_enabled) report_current_position();
}
#include "math.h"
void unified_bed_leveling::invalidate() {
set_bed_leveling_enabled(false);
set_all_mesh_points_to_value(NAN);
}
void unified_bed_leveling::echo_name() {
SERIAL_ECHOPGM("Unified Bed Leveling");
void unified_bed_leveling::set_all_mesh_points_to_value(const_float_t value) {
GRID_LOOP(x, y) {
z_values[x][y] = value;
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, value));
}
}
#if ENABLED(OPTIMIZED_MESH_STORAGE)
constexpr float mesh_store_scaling = 1000;
constexpr int16_t Z_STEPS_NAN = INT16_MAX;
void unified_bed_leveling::set_store_from_mesh(const bed_mesh_t &in_values, mesh_store_t &stored_values) {
auto z_to_store = [](const_float_t z) {
if (isnan(z)) return Z_STEPS_NAN;
const int32_t z_scaled = TRUNC(z * mesh_store_scaling);
if (z_scaled == Z_STEPS_NAN || !WITHIN(z_scaled, INT16_MIN, INT16_MAX))
return Z_STEPS_NAN; // If Z is out of range, return our custom 'NaN'
return int16_t(z_scaled);
};
GRID_LOOP(x, y) stored_values[x][y] = z_to_store(in_values[x][y]);
}
void unified_bed_leveling::report_current_mesh() {
if (!leveling_is_valid()) return;
SERIAL_ECHO_MSG(" G29 I99");
LOOP_L_N(x, GRID_MAX_POINTS_X)
for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++)
if (!isnan(z_values[x][y])) {
SERIAL_ECHO_START();
SERIAL_ECHOPAIR(" M421 I", int(x), " J", int(y));
SERIAL_ECHOLNPAIR_F_P(SP_Z_STR, z_values[x][y], 4);
serial_delay(75); // Prevent Printrun from exploding
}
void unified_bed_leveling::set_mesh_from_store(const mesh_store_t &stored_values, bed_mesh_t &out_values) {
auto store_to_z = [](const int16_t z_scaled) {
return z_scaled == Z_STEPS_NAN ? NAN : z_scaled / mesh_store_scaling;
};
GRID_LOOP(x, y) out_values[x][y] = store_to_z(stored_values[x][y]);
}
void unified_bed_leveling::report_state() {
echo_name();
SERIAL_ECHO_TERNARY(planner.leveling_active, " System v" UBL_VERSION " ", "", "in", "active\n");
serial_delay(50);
}
#endif // OPTIMIZED_MESH_STORAGE
int8_t unified_bed_leveling::storage_slot;
static void serial_echo_xy(const uint8_t sp, const int16_t x, const int16_t y) {
SERIAL_ECHO_SP(sp);
SERIAL_CHAR('(');
if (x < 100) { SERIAL_CHAR(' '); if (x < 10) SERIAL_CHAR(' '); }
SERIAL_ECHO(x);
SERIAL_CHAR(',');
if (y < 100) { SERIAL_CHAR(' '); if (y < 10) SERIAL_CHAR(' '); }
SERIAL_ECHO(y);
SERIAL_CHAR(')');
serial_delay(5);
}
float unified_bed_leveling::z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
#define _GRIDPOS(A,N) (MESH_MIN_##A + N * (MESH_##A##_DIST))
const float
unified_bed_leveling::_mesh_index_to_xpos[GRID_MAX_POINTS_X] PROGMEM = ARRAY_N(GRID_MAX_POINTS_X,
_GRIDPOS(X, 0), _GRIDPOS(X, 1), _GRIDPOS(X, 2), _GRIDPOS(X, 3),
_GRIDPOS(X, 4), _GRIDPOS(X, 5), _GRIDPOS(X, 6), _GRIDPOS(X, 7),
_GRIDPOS(X, 8), _GRIDPOS(X, 9), _GRIDPOS(X, 10), _GRIDPOS(X, 11),
_GRIDPOS(X, 12), _GRIDPOS(X, 13), _GRIDPOS(X, 14), _GRIDPOS(X, 15)
),
unified_bed_leveling::_mesh_index_to_ypos[GRID_MAX_POINTS_Y] PROGMEM = ARRAY_N(GRID_MAX_POINTS_Y,
_GRIDPOS(Y, 0), _GRIDPOS(Y, 1), _GRIDPOS(Y, 2), _GRIDPOS(Y, 3),
_GRIDPOS(Y, 4), _GRIDPOS(Y, 5), _GRIDPOS(Y, 6), _GRIDPOS(Y, 7),
_GRIDPOS(Y, 8), _GRIDPOS(Y, 9), _GRIDPOS(Y, 10), _GRIDPOS(Y, 11),
_GRIDPOS(Y, 12), _GRIDPOS(Y, 13), _GRIDPOS(Y, 14), _GRIDPOS(Y, 15)
);
#if HAS_LCD_MENU
bool unified_bed_leveling::lcd_map_control = false;
#endif
volatile int unified_bed_leveling::encoder_diff;
unified_bed_leveling::unified_bed_leveling() {
reset();
}
void unified_bed_leveling::reset() {
const bool was_enabled = planner.leveling_active;
set_bed_leveling_enabled(false);
storage_slot = -1;
ZERO(z_values);
#if ENABLED(EXTENSIBLE_UI)
GRID_LOOP(x, y) ExtUI::onMeshUpdate(x, y, 0);
#endif
if (was_enabled) report_current_position();
}
void unified_bed_leveling::invalidate() {
set_bed_leveling_enabled(false);
set_all_mesh_points_to_value(NAN);
}
void unified_bed_leveling::set_all_mesh_points_to_value(const float value) {
GRID_LOOP(x, y) {
z_values[x][y] = value;
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(x, y, value);
#endif
}
}
static void serial_echo_xy(const uint8_t sp, const int16_t x, const int16_t y) {
static void serial_echo_column_labels(const uint8_t sp) {
SERIAL_ECHO_SP(7);
LOOP_L_N(i, GRID_MAX_POINTS_X) {
if (i < 10) SERIAL_CHAR(' ');
SERIAL_ECHO(i);
SERIAL_ECHO_SP(sp);
SERIAL_CHAR('(');
if (x < 100) { SERIAL_CHAR(' '); if (x < 10) SERIAL_CHAR(' '); }
SERIAL_ECHO(x);
SERIAL_CHAR(',');
if (y < 100) { SERIAL_CHAR(' '); if (y < 10) SERIAL_CHAR(' '); }
SERIAL_ECHO(y);
SERIAL_CHAR(')');
serial_delay(5);
}
serial_delay(10);
}
/**
* Produce one of these mesh maps:
* 0: Human-readable
* 1: CSV format for spreadsheet import
* 2: TODO: Display on Graphical LCD
* 4: Compact Human-Readable
*/
void unified_bed_leveling::display_map(const uint8_t map_type) {
const bool was = gcode.set_autoreport_paused(true);
constexpr uint8_t eachsp = 1 + 6 + 1, // [-3.567]
twixt = eachsp * (GRID_MAX_POINTS_X) - 9 * 2; // Leading 4sp, Coordinates 9sp each
const bool human = !(map_type & 0x3), csv = map_type == 1, lcd = map_type == 2, comp = map_type & 0x4;
SERIAL_ECHOPGM("\nBed Topography Report");
if (human) {
SERIAL_ECHOLNPGM(":\n");
serial_echo_xy(4, MESH_MIN_X, MESH_MAX_Y);
serial_echo_xy(twixt, MESH_MAX_X, MESH_MAX_Y);
SERIAL_EOL();
serial_echo_column_labels(eachsp - 2);
}
else {
SERIAL_ECHOPGM(" for ");
SERIAL_ECHOPGM_P(csv ? PSTR("CSV:\n") : PSTR("LCD:\n"));
}
static void serial_echo_column_labels(const uint8_t sp) {
SERIAL_ECHO_SP(7);
for (int8_t i = 0; i < GRID_MAX_POINTS_X; i++) {
if (i < 10) SERIAL_CHAR(' ');
SERIAL_ECHO(i);
SERIAL_ECHO_SP(sp);
// Add XY probe offset from extruder because probe.probe_at_point() subtracts them when
// moving to the XY position to be measured. This ensures better agreement between
// the current Z position after G28 and the mesh values.
const xy_int8_t curr = closest_indexes(xy_pos_t(current_position) + probe.offset_xy);
if (!lcd) SERIAL_EOL();
for (int8_t j = (GRID_MAX_POINTS_Y) - 1; j >= 0; j--) {
// Row Label (J index)
if (human) {
if (j < 10) SERIAL_CHAR(' ');
SERIAL_ECHO(j);
SERIAL_ECHOPGM(" |");
}
serial_delay(10);
// Row Values (I indexes)
LOOP_L_N(i, GRID_MAX_POINTS_X) {
// Opening Brace or Space
const bool is_current = i == curr.x && j == curr.y;
if (human) SERIAL_CHAR(is_current ? '[' : ' ');
// Z Value at current I, J
const float f = z_values[i][j];
if (lcd) {
// TODO: Display on Graphical LCD
}
else if (isnan(f))
SERIAL_ECHOPGM_P(human ? PSTR(" . ") : PSTR("NAN"));
else if (human || csv) {
if (human && f >= 0.0) SERIAL_CHAR(f > 0 ? '+' : ' '); // Display sign also for positive numbers (' ' for 0)
SERIAL_ECHO_F(f, 3); // Positive: 5 digits, Negative: 6 digits
}
if (csv && i < (GRID_MAX_POINTS_X) - 1) SERIAL_CHAR('\t');
// Closing Brace or Space
if (human) SERIAL_CHAR(is_current ? ']' : ' ');
SERIAL_FLUSHTX();
idle_no_sleep();
}
if (!lcd) SERIAL_EOL();
// A blank line between rows (unless compact)
if (j && human && !comp) SERIAL_ECHOLNPGM(" |");
}
if (human) {
serial_echo_column_labels(eachsp - 2);
SERIAL_EOL();
serial_echo_xy(4, MESH_MIN_X, MESH_MIN_Y);
serial_echo_xy(twixt, MESH_MAX_X, MESH_MIN_Y);
SERIAL_EOL();
SERIAL_EOL();
}
gcode.set_autoreport_paused(was);
}
bool unified_bed_leveling::sanity_check() {
uint8_t error_flag = 0;
if (settings.calc_num_meshes() < 1) {
SERIAL_ECHOLNPGM("?Mesh too big for EEPROM.");
error_flag++;
}
return !!error_flag;
}
#if ENABLED(UBL_MESH_WIZARD)
/**
* Produce one of these mesh maps:
* 0: Human-readable
* 1: CSV format for spreadsheet import
* 2: TODO: Display on Graphical LCD
* 4: Compact Human-Readable
* M1004: UBL Mesh Wizard - One-click mesh creation with or without a probe
*/
void unified_bed_leveling::display_map(const int map_type) {
const bool was = gcode.set_autoreport_paused(true);
void GcodeSuite::M1004() {
constexpr uint8_t eachsp = 1 + 6 + 1, // [-3.567]
twixt = eachsp * (GRID_MAX_POINTS_X) - 9 * 2; // Leading 4sp, Coordinates 9sp each
#define ALIGN_GCODE TERN(Z_STEPPER_AUTO_ALIGN, "G34", "")
#define PROBE_GCODE TERN(HAS_BED_PROBE, "G29P1\nG29P3", "G29P4R")
const bool human = !(map_type & 0x3), csv = map_type == 1, lcd = map_type == 2, comp = map_type & 0x4;
SERIAL_ECHOPGM("\nBed Topography Report");
if (human) {
SERIAL_ECHOLNPGM(":\n");
serial_echo_xy(4, MESH_MIN_X, MESH_MAX_Y);
serial_echo_xy(twixt, MESH_MAX_X, MESH_MAX_Y);
SERIAL_EOL();
serial_echo_column_labels(eachsp - 2);
}
else {
SERIAL_ECHOPGM(" for ");
serialprintPGM(csv ? PSTR("CSV:\n") : PSTR("LCD:\n"));
}
// Add XY probe offset from extruder because probe.probe_at_point() subtracts them when
// moving to the XY position to be measured. This ensures better agreement between
// the current Z position after G28 and the mesh values.
const xy_int8_t curr = closest_indexes(xy_pos_t(current_position) + probe.offset_xy);
if (!lcd) SERIAL_EOL();
for (int8_t j = GRID_MAX_POINTS_Y - 1; j >= 0; j--) {
// Row Label (J index)
if (human) {
if (j < 10) SERIAL_CHAR(' ');
SERIAL_ECHO(j);
SERIAL_ECHOPGM(" |");
#if HAS_HOTEND
if (parser.seenval('H')) { // Handle H# parameter to set Hotend temp
const celsius_t hotend_temp = parser.value_int(); // Marlin never sends itself F or K, always C
thermalManager.setTargetHotend(hotend_temp, 0);
thermalManager.wait_for_hotend(false);
}
#endif
// Row Values (I indexes)
LOOP_L_N(i, GRID_MAX_POINTS_X) {
// Opening Brace or Space
const bool is_current = i == curr.x && j == curr.y;
if (human) SERIAL_CHAR(is_current ? '[' : ' ');
// Z Value at current I, J
const float f = z_values[i][j];
if (lcd) {
// TODO: Display on Graphical LCD
}
else if (isnan(f))
serialprintPGM(human ? PSTR(" . ") : PSTR("NAN"));
else if (human || csv) {
if (human && f >= 0.0) SERIAL_CHAR(f > 0 ? '+' : ' '); // Space for positive ('-' for negative)
SERIAL_ECHO_F(f, 3); // Positive: 5 digits, Negative: 6 digits
}
if (csv && i < GRID_MAX_POINTS_X - 1) SERIAL_CHAR('\t');
// Closing Brace or Space
if (human) SERIAL_CHAR(is_current ? ']' : ' ');
SERIAL_FLUSHTX();
idle();
#if HAS_HEATED_BED
if (parser.seenval('B')) { // Handle B# parameter to set Bed temp
const celsius_t bed_temp = parser.value_int(); // Marlin never sends itself F or K, always C
thermalManager.setTargetBed(bed_temp);
thermalManager.wait_for_bed(false);
}
if (!lcd) SERIAL_EOL();
#endif
// A blank line between rows (unless compact)
if (j && human && !comp) SERIAL_ECHOLNPGM(" |");
process_subcommands_now_P(G28_STR); // Home
process_subcommands_now_P(PSTR(ALIGN_GCODE "\n" // Align multi z axis if available
PROBE_GCODE "\n" // Build mesh with available hardware
"G29P3\nG29P3")); // Ensure mesh is complete by running smart fill twice
if (parser.seenval('S')) {
char umw_gcode[32];
sprintf_P(umw_gcode, PSTR("G29S%i"), parser.value_int());
queue.inject(umw_gcode);
}
if (human) {
serial_echo_column_labels(eachsp - 2);
SERIAL_EOL();
serial_echo_xy(4, MESH_MIN_X, MESH_MIN_Y);
serial_echo_xy(twixt, MESH_MAX_X, MESH_MIN_Y);
SERIAL_EOL();
SERIAL_EOL();
}
gcode.set_autoreport_paused(was);
process_subcommands_now_P(PSTR("G29A\nG29F10\n" // Set UBL Active & Fade 10
"M140S0\nM104S0\n" // Turn off heaters
"M500")); // Store settings
}
bool unified_bed_leveling::sanity_check() {
uint8_t error_flag = 0;
if (settings.calc_num_meshes() < 1) {
SERIAL_ECHOLNPGM("?Mesh too big for EEPROM.");
error_flag++;
}
return !!error_flag;
}
#endif // UBL_MESH_WIZARD
#endif // AUTO_BED_LEVELING_UBL

493
Marlin/src/feature/bedlevel/ubl/ubl.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -32,276 +32,279 @@
#define UBL_OK false
#define UBL_ERR true
enum MeshPointType : char { INVALID, REAL, SET_IN_BITMAP };
enum MeshPointType : char { INVALID, REAL, SET_IN_BITMAP, CLOSEST };
// External references
struct mesh_index_pair;
#define MESH_X_DIST (float(MESH_MAX_X - (MESH_MIN_X)) / float(GRID_MAX_POINTS_X - 1))
#define MESH_Y_DIST (float(MESH_MAX_Y - (MESH_MIN_Y)) / float(GRID_MAX_POINTS_Y - 1))
#define MESH_X_DIST (float(MESH_MAX_X - (MESH_MIN_X)) / (GRID_MAX_CELLS_X))
#define MESH_Y_DIST (float(MESH_MAX_Y - (MESH_MIN_Y)) / (GRID_MAX_CELLS_Y))
#if ENABLED(OPTIMIZED_MESH_STORAGE)
typedef int16_t mesh_store_t[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
#endif
typedef struct {
bool C_seen;
int8_t KLS_storage_slot;
uint8_t R_repetition,
V_verbosity,
P_phase,
T_map_type;
float B_shim_thickness,
C_constant;
xy_pos_t XY_pos;
xy_bool_t XY_seen;
#if HAS_BED_PROBE
uint8_t J_grid_size;
#endif
} G29_parameters_t;
class unified_bed_leveling {
private:
private:
static int g29_verbose_level,
g29_phase_value,
g29_repetition_cnt,
g29_storage_slot,
g29_map_type;
static bool g29_c_flag;
static float g29_card_thickness,
g29_constant;
static xy_pos_t g29_pos;
static xy_bool_t xy_seen;
static G29_parameters_t param;
#if HAS_BED_PROBE
static int g29_grid_size;
#endif
#if IS_NEWPANEL
static void move_z_with_encoder(const_float_t multiplier);
static float measure_point_with_encoder();
static float measure_business_card_thickness();
static void manually_probe_remaining_mesh(const xy_pos_t&, const_float_t , const_float_t , const bool) _O0;
static void fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) _O0;
#endif
#if ENABLED(NEWPANEL)
static void move_z_with_encoder(const float &multiplier);
static float measure_point_with_encoder();
static float measure_business_card_thickness(float in_height);
static void manually_probe_remaining_mesh(const xy_pos_t&, const float&, const float&, const bool) _O0;
static void fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) _O0;
#endif
static bool G29_parse_parameters() _O0;
static void shift_mesh_height();
static void probe_entire_mesh(const xy_pos_t &near, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) _O0;
static void tilt_mesh_based_on_3pts(const_float_t z1, const_float_t z2, const_float_t z3);
static void tilt_mesh_based_on_probed_grid(const bool do_ubl_mesh_map);
static bool smart_fill_one(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir);
static inline bool smart_fill_one(const xy_uint8_t &pos, const xy_uint8_t &dir) {
return smart_fill_one(pos.x, pos.y, dir.x, dir.y);
}
static void smart_fill_mesh();
static bool g29_parameter_parsing() _O0;
static void shift_mesh_height();
static void probe_entire_mesh(const xy_pos_t &near, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) _O0;
static void tilt_mesh_based_on_3pts(const float &z1, const float &z2, const float &z3);
static void tilt_mesh_based_on_probed_grid(const bool do_ubl_mesh_map);
static bool smart_fill_one(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir);
static inline bool smart_fill_one(const xy_uint8_t &pos, const xy_uint8_t &dir) {
return smart_fill_one(pos.x, pos.y, dir.x, dir.y);
#if ENABLED(UBL_DEVEL_DEBUGGING)
static void g29_what_command();
static void g29_eeprom_dump();
static void g29_compare_current_mesh_to_stored_mesh();
#endif
public:
static void echo_name();
static void report_current_mesh();
static void report_state();
static void save_ubl_active_state_and_disable();
static void restore_ubl_active_state_and_leave();
static void display_map(const uint8_t) _O0;
static mesh_index_pair find_closest_mesh_point_of_type(const MeshPointType, const xy_pos_t&, const bool=false, MeshFlags *done_flags=nullptr) _O0;
static mesh_index_pair find_furthest_invalid_mesh_point() _O0;
static void reset();
static void invalidate();
static void set_all_mesh_points_to_value(const_float_t value);
static void adjust_mesh_to_mean(const bool cflag, const_float_t value);
static bool sanity_check();
static void G29() _O0; // O0 for no optimization
static void smart_fill_wlsf(const_float_t ) _O2; // O2 gives smaller code than Os on A2560
static int8_t storage_slot;
static bed_mesh_t z_values;
#if ENABLED(OPTIMIZED_MESH_STORAGE)
static void set_store_from_mesh(const bed_mesh_t &in_values, mesh_store_t &stored_values);
static void set_mesh_from_store(const mesh_store_t &stored_values, bed_mesh_t &out_values);
#endif
static const float _mesh_index_to_xpos[GRID_MAX_POINTS_X],
_mesh_index_to_ypos[GRID_MAX_POINTS_Y];
#if HAS_LCD_MENU
static bool lcd_map_control;
static void steppers_were_disabled();
#else
static inline void steppers_were_disabled() {}
#endif
static volatile int16_t encoder_diff; // Volatile because buttons may change it at interrupt time
unified_bed_leveling();
FORCE_INLINE static void set_z(const int8_t px, const int8_t py, const_float_t z) { z_values[px][py] = z; }
static int8_t cell_index_x_raw(const_float_t x) {
return FLOOR((x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST));
}
static int8_t cell_index_y_raw(const_float_t y) {
return FLOOR((y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST));
}
static int8_t cell_index_x_valid(const_float_t x) {
return WITHIN(cell_index_x_raw(x), 0, GRID_MAX_CELLS_X - 1);
}
static int8_t cell_index_y_valid(const_float_t y) {
return WITHIN(cell_index_y_raw(y), 0, GRID_MAX_CELLS_Y - 1);
}
static int8_t cell_index_x(const_float_t x) {
return constrain(cell_index_x_raw(x), 0, GRID_MAX_CELLS_X - 1);
}
static int8_t cell_index_y(const_float_t y) {
return constrain(cell_index_y_raw(y), 0, GRID_MAX_CELLS_Y - 1);
}
static inline xy_int8_t cell_indexes(const_float_t x, const_float_t y) {
return { cell_index_x(x), cell_index_y(y) };
}
static inline xy_int8_t cell_indexes(const xy_pos_t &xy) { return cell_indexes(xy.x, xy.y); }
static int8_t closest_x_index(const_float_t x) {
const int8_t px = (x - (MESH_MIN_X) + (MESH_X_DIST) * 0.5) * RECIPROCAL(MESH_X_DIST);
return WITHIN(px, 0, (GRID_MAX_POINTS_X) - 1) ? px : -1;
}
static int8_t closest_y_index(const_float_t y) {
const int8_t py = (y - (MESH_MIN_Y) + (MESH_Y_DIST) * 0.5) * RECIPROCAL(MESH_Y_DIST);
return WITHIN(py, 0, (GRID_MAX_POINTS_Y) - 1) ? py : -1;
}
static inline xy_int8_t closest_indexes(const xy_pos_t &xy) {
return { closest_x_index(xy.x), closest_y_index(xy.y) };
}
/**
* z2 --|
* z0 | |
* | | + (z2-z1)
* z1 | | |
* ---+-------------+--------+-- --|
* a1 a0 a2
* |<---delta_a---------->|
*
* calc_z0 is the basis for all the Mesh Based correction. It is used to
* find the expected Z Height at a position between two known Z-Height locations.
*
* It is fairly expensive with its 4 floating point additions and 2 floating point
* multiplications.
*/
FORCE_INLINE static float calc_z0(const_float_t a0, const_float_t a1, const_float_t z1, const_float_t a2, const_float_t z2) {
return z1 + (z2 - z1) * (a0 - a1) / (a2 - a1);
}
#ifdef UBL_Z_RAISE_WHEN_OFF_MESH
#define _UBL_OUTER_Z_RAISE UBL_Z_RAISE_WHEN_OFF_MESH
#else
#define _UBL_OUTER_Z_RAISE NAN
#endif
/**
* z_correction_for_x_on_horizontal_mesh_line is an optimization for
* the case where the printer is making a vertical line that only crosses horizontal mesh lines.
*/
static inline float z_correction_for_x_on_horizontal_mesh_line(const_float_t rx0, const int x1_i, const int yi) {
if (!WITHIN(x1_i, 0, (GRID_MAX_POINTS_X) - 1) || !WITHIN(yi, 0, (GRID_MAX_POINTS_Y) - 1)) {
if (DEBUGGING(LEVELING)) {
if (WITHIN(x1_i, 0, (GRID_MAX_POINTS_X) - 1)) DEBUG_ECHOPGM("yi"); else DEBUG_ECHOPGM("x1_i");
DEBUG_ECHOLNPGM(" out of bounds in z_correction_for_x_on_horizontal_mesh_line(rx0=", rx0, ",x1_i=", x1_i, ",yi=", yi, ")");
}
// The requested location is off the mesh. Return UBL_Z_RAISE_WHEN_OFF_MESH or NAN.
return _UBL_OUTER_Z_RAISE;
}
static void smart_fill_mesh();
#if ENABLED(UBL_DEVEL_DEBUGGING)
static void g29_what_command();
static void g29_eeprom_dump();
static void g29_compare_current_mesh_to_stored_mesh();
#endif
const float xratio = (rx0 - mesh_index_to_xpos(x1_i)) * RECIPROCAL(MESH_X_DIST),
z1 = z_values[x1_i][yi];
public:
return z1 + xratio * (z_values[_MIN(x1_i, (GRID_MAX_POINTS_X) - 2) + 1][yi] - z1); // Don't allow x1_i+1 to be past the end of the array
// If it is, it is clamped to the last element of the
// z_values[][] array and no correction is applied.
}
static void echo_name();
static void report_current_mesh();
static void report_state();
static void save_ubl_active_state_and_disable();
static void restore_ubl_active_state_and_leave();
static void display_map(const int) _O0;
static mesh_index_pair find_closest_mesh_point_of_type(const MeshPointType, const xy_pos_t&, const bool=false, MeshFlags *done_flags=nullptr) _O0;
static mesh_index_pair find_furthest_invalid_mesh_point() _O0;
static void reset();
static void invalidate();
static void set_all_mesh_points_to_value(const float value);
static void adjust_mesh_to_mean(const bool cflag, const float value);
static bool sanity_check();
//
// See comments above for z_correction_for_x_on_horizontal_mesh_line
//
static inline float z_correction_for_y_on_vertical_mesh_line(const_float_t ry0, const int xi, const int y1_i) {
if (!WITHIN(xi, 0, (GRID_MAX_POINTS_X) - 1) || !WITHIN(y1_i, 0, (GRID_MAX_POINTS_Y) - 1)) {
static void G29() _O0; // O0 for no optimization
static void smart_fill_wlsf(const float &) _O2; // O2 gives smaller code than Os on A2560
if (DEBUGGING(LEVELING)) {
if (WITHIN(xi, 0, (GRID_MAX_POINTS_X) - 1)) DEBUG_ECHOPGM("y1_i"); else DEBUG_ECHOPGM("xi");
DEBUG_ECHOLNPGM(" out of bounds in z_correction_for_y_on_vertical_mesh_line(ry0=", ry0, ", xi=", xi, ", y1_i=", y1_i, ")");
}
static int8_t storage_slot;
static bed_mesh_t z_values;
static const float _mesh_index_to_xpos[GRID_MAX_POINTS_X],
_mesh_index_to_ypos[GRID_MAX_POINTS_Y];
#if HAS_LCD_MENU
static bool lcd_map_control;
#endif
static volatile int encoder_diff; // Volatile because it's changed at interrupt time.
unified_bed_leveling();
FORCE_INLINE static void set_z(const int8_t px, const int8_t py, const float &z) { z_values[px][py] = z; }
static int8_t cell_index_x(const float &x) {
const int8_t cx = (x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST);
return constrain(cx, 0, (GRID_MAX_POINTS_X) - 1); // -1 is appropriate if we want all movement to the X_MAX
} // position. But with this defined this way, it is possible
// to extrapolate off of this point even further out. Probably
// that is OK because something else should be keeping that from
// happening and should not be worried about at this level.
static int8_t cell_index_y(const float &y) {
const int8_t cy = (y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST);
return constrain(cy, 0, (GRID_MAX_POINTS_Y) - 1); // -1 is appropriate if we want all movement to the Y_MAX
} // position. But with this defined this way, it is possible
// to extrapolate off of this point even further out. Probably
// that is OK because something else should be keeping that from
// happening and should not be worried about at this level.
static inline xy_int8_t cell_indexes(const float &x, const float &y) {
return { cell_index_x(x), cell_index_y(y) };
// The requested location is off the mesh. Return UBL_Z_RAISE_WHEN_OFF_MESH or NAN.
return _UBL_OUTER_Z_RAISE;
}
static inline xy_int8_t cell_indexes(const xy_pos_t &xy) { return cell_indexes(xy.x, xy.y); }
static int8_t closest_x_index(const float &x) {
const int8_t px = (x - (MESH_MIN_X) + (MESH_X_DIST) * 0.5) * RECIPROCAL(MESH_X_DIST);
return WITHIN(px, 0, GRID_MAX_POINTS_X - 1) ? px : -1;
}
static int8_t closest_y_index(const float &y) {
const int8_t py = (y - (MESH_MIN_Y) + (MESH_Y_DIST) * 0.5) * RECIPROCAL(MESH_Y_DIST);
return WITHIN(py, 0, GRID_MAX_POINTS_Y - 1) ? py : -1;
}
static inline xy_int8_t closest_indexes(const xy_pos_t &xy) {
return { closest_x_index(xy.x), closest_y_index(xy.y) };
}
const float yratio = (ry0 - mesh_index_to_ypos(y1_i)) * RECIPROCAL(MESH_Y_DIST),
z1 = z_values[xi][y1_i];
return z1 + yratio * (z_values[xi][_MIN(y1_i, (GRID_MAX_POINTS_Y) - 2) + 1] - z1); // Don't allow y1_i+1 to be past the end of the array
// If it is, it is clamped to the last element of the
// z_values[][] array and no correction is applied.
}
/**
* This is the generic Z-Correction. It works anywhere within a Mesh Cell. It first
* does a linear interpolation along both of the bounding X-Mesh-Lines to find the
* Z-Height at both ends. Then it does a linear interpolation of these heights based
* on the Y position within the cell.
*/
static float get_z_correction(const_float_t rx0, const_float_t ry0) {
const int8_t cx = cell_index_x(rx0), cy = cell_index_y(ry0); // return values are clamped
/**
* z2 --|
* z0 | |
* | | + (z2-z1)
* z1 | | |
* ---+-------------+--------+-- --|
* a1 a0 a2
* |<---delta_a---------->|
*
* calc_z0 is the basis for all the Mesh Based correction. It is used to
* find the expected Z Height at a position between two known Z-Height locations.
*
* It is fairly expensive with its 4 floating point additions and 2 floating point
* multiplications.
* Check if the requested location is off the mesh. If so, and
* UBL_Z_RAISE_WHEN_OFF_MESH is specified, that value is returned.
*/
FORCE_INLINE static float calc_z0(const float &a0, const float &a1, const float &z1, const float &a2, const float &z2) {
return z1 + (z2 - z1) * (a0 - a1) / (a2 - a1);
}
/**
* z_correction_for_x_on_horizontal_mesh_line is an optimization for
* the case where the printer is making a vertical line that only crosses horizontal mesh lines.
*/
static inline float z_correction_for_x_on_horizontal_mesh_line(const float &rx0, const int x1_i, const int yi) {
if (!WITHIN(x1_i, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(yi, 0, GRID_MAX_POINTS_Y - 1)) {
if (DEBUGGING(LEVELING)) {
if (WITHIN(x1_i, 0, GRID_MAX_POINTS_X - 1)) DEBUG_ECHOPGM("yi"); else DEBUG_ECHOPGM("x1_i");
DEBUG_ECHOLNPAIR(" out of bounds in z_correction_for_x_on_horizontal_mesh_line(rx0=", rx0, ",x1_i=", x1_i, ",yi=", yi, ")");
}
// The requested location is off the mesh. Return UBL_Z_RAISE_WHEN_OFF_MESH or NAN.
return (
#ifdef UBL_Z_RAISE_WHEN_OFF_MESH
UBL_Z_RAISE_WHEN_OFF_MESH
#else
NAN
#endif
);
}
const float xratio = (rx0 - mesh_index_to_xpos(x1_i)) * RECIPROCAL(MESH_X_DIST),
z1 = z_values[x1_i][yi];
return z1 + xratio * (z_values[_MIN(x1_i, GRID_MAX_POINTS_X - 2) + 1][yi] - z1); // Don't allow x1_i+1 to be past the end of the array
// If it is, it is clamped to the last element of the
// z_values[][] array and no correction is applied.
}
//
// See comments above for z_correction_for_x_on_horizontal_mesh_line
//
static inline float z_correction_for_y_on_vertical_mesh_line(const float &ry0, const int xi, const int y1_i) {
if (!WITHIN(xi, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(y1_i, 0, GRID_MAX_POINTS_Y - 1)) {
if (DEBUGGING(LEVELING)) {
if (WITHIN(xi, 0, GRID_MAX_POINTS_X - 1)) DEBUG_ECHOPGM("y1_i"); else DEBUG_ECHOPGM("xi");
DEBUG_ECHOLNPAIR(" out of bounds in z_correction_for_y_on_vertical_mesh_line(ry0=", ry0, ", xi=", xi, ", y1_i=", y1_i, ")");
}
// The requested location is off the mesh. Return UBL_Z_RAISE_WHEN_OFF_MESH or NAN.
return (
#ifdef UBL_Z_RAISE_WHEN_OFF_MESH
UBL_Z_RAISE_WHEN_OFF_MESH
#else
NAN
#endif
);
}
const float yratio = (ry0 - mesh_index_to_ypos(y1_i)) * RECIPROCAL(MESH_Y_DIST),
z1 = z_values[xi][y1_i];
return z1 + yratio * (z_values[xi][_MIN(y1_i, GRID_MAX_POINTS_Y - 2) + 1] - z1); // Don't allow y1_i+1 to be past the end of the array
// If it is, it is clamped to the last element of the
// z_values[][] array and no correction is applied.
}
/**
* This is the generic Z-Correction. It works anywhere within a Mesh Cell. It first
* does a linear interpolation along both of the bounding X-Mesh-Lines to find the
* Z-Height at both ends. Then it does a linear interpolation of these heights based
* on the Y position within the cell.
*/
static float get_z_correction(const float &rx0, const float &ry0) {
const int8_t cx = cell_index_x(rx0), cy = cell_index_y(ry0); // return values are clamped
/**
* Check if the requested location is off the mesh. If so, and
* UBL_Z_RAISE_WHEN_OFF_MESH is specified, that value is returned.
*/
#ifdef UBL_Z_RAISE_WHEN_OFF_MESH
if (!WITHIN(rx0, MESH_MIN_X, MESH_MAX_X) || !WITHIN(ry0, MESH_MIN_Y, MESH_MAX_Y))
return UBL_Z_RAISE_WHEN_OFF_MESH;
#endif
const float z1 = calc_z0(rx0,
mesh_index_to_xpos(cx), z_values[cx][cy],
mesh_index_to_xpos(cx + 1), z_values[_MIN(cx, GRID_MAX_POINTS_X - 2) + 1][cy]);
const float z2 = calc_z0(rx0,
mesh_index_to_xpos(cx), z_values[cx][_MIN(cy, GRID_MAX_POINTS_Y - 2) + 1],
mesh_index_to_xpos(cx + 1), z_values[_MIN(cx, GRID_MAX_POINTS_X - 2) + 1][_MIN(cy, GRID_MAX_POINTS_Y - 2) + 1]);
float z0 = calc_z0(ry0,
mesh_index_to_ypos(cy), z1,
mesh_index_to_ypos(cy + 1), z2);
if (DEBUGGING(MESH_ADJUST)) {
DEBUG_ECHOPAIR(" raw get_z_correction(", rx0);
DEBUG_CHAR(','); DEBUG_ECHO(ry0);
DEBUG_ECHOPAIR_F(") = ", z0, 6);
DEBUG_ECHOLNPAIR_F(" >>>---> ", z0, 6);
}
if (isnan(z0)) { // if part of the Mesh is undefined, it will show up as NAN
z0 = 0.0; // in ubl.z_values[][] and propagate through the
// calculations. If our correction is NAN, we throw it out
// because part of the Mesh is undefined and we don't have the
// information we need to complete the height correction.
if (DEBUGGING(MESH_ADJUST)) {
DEBUG_ECHOPAIR("??? Yikes! NAN in get_z_correction(", rx0);
DEBUG_CHAR(',');
DEBUG_ECHO(ry0);
DEBUG_CHAR(')');
DEBUG_EOL();
}
}
return z0;
}
static inline float get_z_correction(const xy_pos_t &pos) { return get_z_correction(pos.x, pos.y); }
static inline float mesh_index_to_xpos(const uint8_t i) {
return i < GRID_MAX_POINTS_X ? pgm_read_float(&_mesh_index_to_xpos[i]) : MESH_MIN_X + i * (MESH_X_DIST);
}
static inline float mesh_index_to_ypos(const uint8_t i) {
return i < GRID_MAX_POINTS_Y ? pgm_read_float(&_mesh_index_to_ypos[i]) : MESH_MIN_Y + i * (MESH_Y_DIST);
}
#if UBL_SEGMENTED
static bool line_to_destination_segmented(const feedRate_t &scaled_fr_mm_s);
#else
static void line_to_destination_cartesian(const feedRate_t &scaled_fr_mm_s, const uint8_t e);
#ifdef UBL_Z_RAISE_WHEN_OFF_MESH
if (!WITHIN(rx0, MESH_MIN_X, MESH_MAX_X) || !WITHIN(ry0, MESH_MIN_Y, MESH_MAX_Y))
return UBL_Z_RAISE_WHEN_OFF_MESH;
#endif
static inline bool mesh_is_valid() {
GRID_LOOP(x, y) if (isnan(z_values[x][y])) return false;
return true;
const uint8_t mx = _MIN(cx, (GRID_MAX_POINTS_X) - 2) + 1, my = _MIN(cy, (GRID_MAX_POINTS_Y) - 2) + 1;
const float z1 = calc_z0(rx0, mesh_index_to_xpos(cx), z_values[cx][cy], mesh_index_to_xpos(cx + 1), z_values[mx][cy]);
const float z2 = calc_z0(rx0, mesh_index_to_xpos(cx), z_values[cx][my], mesh_index_to_xpos(cx + 1), z_values[mx][my]);
float z0 = calc_z0(ry0, mesh_index_to_ypos(cy), z1, mesh_index_to_ypos(cy + 1), z2);
if (isnan(z0)) { // if part of the Mesh is undefined, it will show up as NAN
z0 = 0.0; // in ubl.z_values[][] and propagate through the
// calculations. If our correction is NAN, we throw it out
// because part of the Mesh is undefined and we don't have the
// information we need to complete the height correction.
if (DEBUGGING(MESH_ADJUST)) DEBUG_ECHOLNPGM("??? Yikes! NAN in ");
}
if (DEBUGGING(MESH_ADJUST)) {
DEBUG_ECHOPGM("get_z_correction(", rx0, ", ", ry0);
DEBUG_ECHOLNPAIR_F(") => ", z0, 6);
}
return z0;
}
static inline float get_z_correction(const xy_pos_t &pos) { return get_z_correction(pos.x, pos.y); }
static inline float mesh_index_to_xpos(const uint8_t i) {
return i < (GRID_MAX_POINTS_X) ? pgm_read_float(&_mesh_index_to_xpos[i]) : MESH_MIN_X + i * (MESH_X_DIST);
}
static inline float mesh_index_to_ypos(const uint8_t i) {
return i < (GRID_MAX_POINTS_Y) ? pgm_read_float(&_mesh_index_to_ypos[i]) : MESH_MIN_Y + i * (MESH_Y_DIST);
}
#if UBL_SEGMENTED
static bool line_to_destination_segmented(const_feedRate_t scaled_fr_mm_s);
#else
static void line_to_destination_cartesian(const_feedRate_t scaled_fr_mm_s, const uint8_t e);
#endif
static inline bool mesh_is_valid() {
GRID_LOOP(x, y) if (isnan(z_values[x][y])) return false;
return true;
}
}; // class unified_bed_leveling
extern unified_bed_leveling ubl;

3290
Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp Executable file → Normal file

File diff suppressed because it is too large Load Diff

198
Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp Executable file → Normal file
View File

@@ -16,9 +16,10 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "../../../inc/MarlinConfig.h"
#if ENABLED(AUTO_BED_LEVELING_UBL)
@@ -37,7 +38,7 @@
#if !UBL_SEGMENTED
void unified_bed_leveling::line_to_destination_cartesian(const feedRate_t &scaled_fr_mm_s, const uint8_t extruder) {
void unified_bed_leveling::line_to_destination_cartesian(const_feedRate_t scaled_fr_mm_s, const uint8_t extruder) {
/**
* Much of the nozzle movement will be within the same cell. So we will do as little computation
* as possible to determine if this is the case. If this move is within the same cell, we will
@@ -56,39 +57,32 @@
// A move within the same cell needs no splitting
if (istart == iend) {
// For a move off the bed, use a constant Z raise
if (!WITHIN(iend.x, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(iend.y, 0, GRID_MAX_POINTS_Y - 1)) {
// Note: There is no Z Correction in this case. We are off the grid and don't know what
// a reasonable correction would be. If the user has specified a UBL_Z_RAISE_WHEN_OFF_MESH
// value, that will be used instead of a calculated (Bi-Linear interpolation) correction.
#ifdef UBL_Z_RAISE_WHEN_OFF_MESH
end.z += UBL_Z_RAISE_WHEN_OFF_MESH;
#endif
planner.buffer_segment(end, scaled_fr_mm_s, extruder);
current_position = destination;
return;
}
FINAL_MOVE:
// The distance is always MESH_X_DIST so multiply by the constant reciprocal.
const float xratio = (end.x - mesh_index_to_xpos(iend.x)) * RECIPROCAL(MESH_X_DIST);
// When UBL_Z_RAISE_WHEN_OFF_MESH is disabled Z correction is extrapolated from the edge of the mesh
#ifdef UBL_Z_RAISE_WHEN_OFF_MESH
// For a move off the UBL mesh, use a constant Z raise
if (!cell_index_x_valid(end.x) || !cell_index_y_valid(end.y)) {
float z1, z2;
if (iend.x >= GRID_MAX_POINTS_X - 1)
z1 = z2 = 0.0;
else {
z1 = z_values[iend.x ][iend.y ] + xratio *
(z_values[iend.x + 1][iend.y ] - z_values[iend.x][iend.y ]),
z2 = z_values[iend.x ][iend.y + 1] + xratio *
(z_values[iend.x + 1][iend.y + 1] - z_values[iend.x][iend.y + 1]);
}
// Note: There is no Z Correction in this case. We are off the mesh and don't know what
// a reasonable correction would be, UBL_Z_RAISE_WHEN_OFF_MESH will be used instead of
// a calculated (Bi-Linear interpolation) correction.
end.z += UBL_Z_RAISE_WHEN_OFF_MESH;
planner.buffer_segment(end, scaled_fr_mm_s, extruder);
current_position = destination;
return;
}
#endif
// The distance is always MESH_X_DIST so multiply by the constant reciprocal.
const float xratio = (end.x - mesh_index_to_xpos(iend.x)) * RECIPROCAL(MESH_X_DIST),
yratio = (end.y - mesh_index_to_ypos(iend.y)) * RECIPROCAL(MESH_Y_DIST),
z1 = z_values[iend.x][iend.y ] + xratio * (z_values[iend.x + 1][iend.y ] - z_values[iend.x][iend.y ]),
z2 = z_values[iend.x][iend.y + 1] + xratio * (z_values[iend.x + 1][iend.y + 1] - z_values[iend.x][iend.y + 1]);
// X cell-fraction done. Interpolate the two Z offsets with the Y fraction for the final Z offset.
const float yratio = (end.y - mesh_index_to_ypos(iend.y)) * RECIPROCAL(MESH_Y_DIST),
z0 = iend.y < GRID_MAX_POINTS_Y - 1 ? (z1 + (z2 - z1) * yratio) * planner.fade_scaling_factor_for_z(end.z) : 0.0;
const float z0 = (z1 + (z2 - z1) * yratio) * planner.fade_scaling_factor_for_z(end.z);
// Undefined parts of the Mesh in z_values[][] are NAN.
// Replace NAN corrections with 0.0 to prevent NAN propagation.
@@ -120,20 +114,22 @@
const xy_float_t ad = sign * dist;
const bool use_x_dist = ad.x > ad.y;
float on_axis_distance = use_x_dist ? dist.x : dist.y,
e_position = end.e - start.e,
z_position = end.z - start.z;
float on_axis_distance = use_x_dist ? dist.x : dist.y;
const float e_normalized_dist = e_position / on_axis_distance, // Allow divide by zero
z_normalized_dist = z_position / on_axis_distance;
const float z_normalized_dist = (end.z - start.z) / on_axis_distance; // Allow divide by zero
#if HAS_EXTRUDERS
const float e_normalized_dist = (end.e - start.e) / on_axis_distance;
const bool inf_normalized_flag = isinf(e_normalized_dist);
#endif
xy_int8_t icell = istart;
const float ratio = dist.y / dist.x, // Allow divide by zero
c = start.y - ratio * start.x;
const bool inf_normalized_flag = isinf(e_normalized_dist),
inf_ratio_flag = isinf(ratio);
const bool inf_ratio_flag = isinf(ratio);
xyze_pos_t dest; // Stores XYZE for segmented moves
/**
* Handle vertical lines that stay within one column.
@@ -150,34 +146,36 @@
* For others the next X is the same so this can continue.
* Calculate X at the next Y mesh line.
*/
const float rx = inf_ratio_flag ? start.x : (next_mesh_line_y - c) / ratio;
dest.x = inf_ratio_flag ? start.x : (next_mesh_line_y - c) / ratio;
float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, icell.x, icell.y)
float z0 = z_correction_for_x_on_horizontal_mesh_line(dest.x, icell.x, icell.y)
* planner.fade_scaling_factor_for_z(end.z);
// Undefined parts of the Mesh in z_values[][] are NAN.
// Replace NAN corrections with 0.0 to prevent NAN propagation.
if (isnan(z0)) z0 = 0.0;
const float ry = mesh_index_to_ypos(icell.y);
dest.y = mesh_index_to_ypos(icell.y);
/**
* Without this check, it's possible to generate a zero length move, as in the case where
* the line is heading down, starting exactly on a mesh line boundary. Since this is rare
* it might be fine to remove this check and let planner.buffer_segment() filter it out.
*/
if (ry != start.y) {
if (dest.y != start.y) {
if (!inf_normalized_flag) { // fall-through faster than branch
on_axis_distance = use_x_dist ? rx - start.x : ry - start.y;
e_position = start.e + on_axis_distance * e_normalized_dist;
z_position = start.z + on_axis_distance * z_normalized_dist;
on_axis_distance = use_x_dist ? dest.x - start.x : dest.y - start.y;
TERN_(HAS_EXTRUDERS, dest.e = start.e + on_axis_distance * e_normalized_dist);
dest.z = start.z + on_axis_distance * z_normalized_dist;
}
else {
e_position = end.e;
z_position = end.z;
TERN_(HAS_EXTRUDERS, dest.e = end.e);
dest.z = end.z;
}
planner.buffer_segment(rx, ry, z_position + z0, e_position, scaled_fr_mm_s, extruder);
dest.z += z0;
planner.buffer_segment(dest, scaled_fr_mm_s, extruder);
} //else printf("FIRST MOVE PRUNED ");
}
@@ -195,12 +193,13 @@
*/
if (iadd.y == 0) { // Horizontal line?
icell.x += ineg.x; // Heading left? Just go to the left edge of the cell for the first move.
while (icell.x != iend.x + ineg.x) {
icell.x += iadd.x;
const float rx = mesh_index_to_xpos(icell.x);
const float ry = ratio * rx + c; // Calculate Y at the next X mesh line
dest.x = mesh_index_to_xpos(icell.x);
dest.y = ratio * dest.x + c; // Calculate Y at the next X mesh line
float z0 = z_correction_for_y_on_vertical_mesh_line(ry, icell.x, icell.y)
float z0 = z_correction_for_y_on_vertical_mesh_line(dest.y, icell.x, icell.y)
* planner.fade_scaling_factor_for_z(end.z);
// Undefined parts of the Mesh in z_values[][] are NAN.
@@ -212,19 +211,20 @@
* the line is heading left, starting exactly on a mesh line boundary. Since this is rare
* it might be fine to remove this check and let planner.buffer_segment() filter it out.
*/
if (rx != start.x) {
if (dest.x != start.x) {
if (!inf_normalized_flag) {
on_axis_distance = use_x_dist ? rx - start.x : ry - start.y;
e_position = start.e + on_axis_distance * e_normalized_dist; // is based on X or Y because this is a horizontal move
z_position = start.z + on_axis_distance * z_normalized_dist;
on_axis_distance = use_x_dist ? dest.x - start.x : dest.y - start.y;
TERN_(HAS_EXTRUDERS, dest.e = start.e + on_axis_distance * e_normalized_dist); // Based on X or Y because the move is horizontal
dest.z = start.z + on_axis_distance * z_normalized_dist;
}
else {
e_position = end.e;
z_position = end.z;
TERN_(HAS_EXTRUDERS, dest.e = end.e);
dest.z = end.z;
}
if (!planner.buffer_segment(rx, ry, z_position + z0, e_position, scaled_fr_mm_s, extruder))
break;
dest.z += z0;
if (!planner.buffer_segment(dest, scaled_fr_mm_s, extruder)) break;
} //else printf("FIRST MOVE PRUNED ");
}
@@ -236,9 +236,7 @@
}
/**
*
* Generic case of a line crossing both X and Y Mesh lines.
*
*/
xy_int8_t cnt = (istart - iend).ABS();
@@ -248,57 +246,65 @@
while (cnt) {
const float next_mesh_line_x = mesh_index_to_xpos(icell.x + iadd.x),
next_mesh_line_y = mesh_index_to_ypos(icell.y + iadd.y),
ry = ratio * next_mesh_line_x + c, // Calculate Y at the next X mesh line
rx = (next_mesh_line_y - c) / ratio; // Calculate X at the next Y mesh line
// (No need to worry about ratio == 0.
// In that case, it was already detected
// as a vertical line move above.)
next_mesh_line_y = mesh_index_to_ypos(icell.y + iadd.y);
if (neg.x == (rx > next_mesh_line_x)) { // Check if we hit the Y line first
dest.y = ratio * next_mesh_line_x + c; // Calculate Y at the next X mesh line
dest.x = (next_mesh_line_y - c) / ratio; // Calculate X at the next Y mesh line
// (No need to worry about ratio == 0.
// In that case, it was already detected
// as a vertical line move above.)
if (neg.x == (dest.x > next_mesh_line_x)) { // Check if we hit the Y line first
// Yes! Crossing a Y Mesh Line next
float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, icell.x - ineg.x, icell.y + iadd.y)
float z0 = z_correction_for_x_on_horizontal_mesh_line(dest.x, icell.x - ineg.x, icell.y + iadd.y)
* planner.fade_scaling_factor_for_z(end.z);
// Undefined parts of the Mesh in z_values[][] are NAN.
// Replace NAN corrections with 0.0 to prevent NAN propagation.
if (isnan(z0)) z0 = 0.0;
dest.y = next_mesh_line_y;
if (!inf_normalized_flag) {
on_axis_distance = use_x_dist ? rx - start.x : next_mesh_line_y - start.y;
e_position = start.e + on_axis_distance * e_normalized_dist;
z_position = start.z + on_axis_distance * z_normalized_dist;
on_axis_distance = use_x_dist ? dest.x - start.x : dest.y - start.y;
TERN_(HAS_EXTRUDERS, dest.e = start.e + on_axis_distance * e_normalized_dist);
dest.z = start.z + on_axis_distance * z_normalized_dist;
}
else {
e_position = end.e;
z_position = end.z;
TERN_(HAS_EXTRUDERS, dest.e = end.e);
dest.z = end.z;
}
if (!planner.buffer_segment(rx, next_mesh_line_y, z_position + z0, e_position, scaled_fr_mm_s, extruder))
break;
dest.z += z0;
if (!planner.buffer_segment(dest, scaled_fr_mm_s, extruder)) break;
icell.y += iadd.y;
cnt.y--;
}
else {
// Yes! Crossing a X Mesh Line next
float z0 = z_correction_for_y_on_vertical_mesh_line(ry, icell.x + iadd.x, icell.y - ineg.y)
float z0 = z_correction_for_y_on_vertical_mesh_line(dest.y, icell.x + iadd.x, icell.y - ineg.y)
* planner.fade_scaling_factor_for_z(end.z);
// Undefined parts of the Mesh in z_values[][] are NAN.
// Replace NAN corrections with 0.0 to prevent NAN propagation.
if (isnan(z0)) z0 = 0.0;
dest.x = next_mesh_line_x;
if (!inf_normalized_flag) {
on_axis_distance = use_x_dist ? next_mesh_line_x - start.x : ry - start.y;
e_position = start.e + on_axis_distance * e_normalized_dist;
z_position = start.z + on_axis_distance * z_normalized_dist;
on_axis_distance = use_x_dist ? dest.x - start.x : dest.y - start.y;
TERN_(HAS_EXTRUDERS, dest.e = start.e + on_axis_distance * e_normalized_dist);
dest.z = start.z + on_axis_distance * z_normalized_dist;
}
else {
e_position = end.e;
z_position = end.z;
TERN_(HAS_EXTRUDERS, dest.e = end.e);
dest.z = end.z;
}
if (!planner.buffer_segment(next_mesh_line_x, ry, z_position + z0, e_position, scaled_fr_mm_s, extruder))
break;
dest.z += z0;
if (!planner.buffer_segment(dest, scaled_fr_mm_s, extruder)) break;
icell.x += iadd.x;
cnt.x--;
}
@@ -318,6 +324,8 @@
#define DELTA_SEGMENT_MIN_LENGTH 0.25 // SCARA minimum segment size is 0.25mm
#elif ENABLED(DELTA)
#define DELTA_SEGMENT_MIN_LENGTH 0.10 // mm (still subject to DELTA_SEGMENTS_PER_SECOND)
#elif ENABLED(POLARGRAPH)
#define DELTA_SEGMENT_MIN_LENGTH 0.10 // mm (still subject to DELTA_SEGMENTS_PER_SECOND)
#else // CARTESIAN
#ifdef LEVELED_SEGMENT_LENGTH
#define DELTA_SEGMENT_MIN_LENGTH LEVELED_SEGMENT_LENGTH
@@ -332,7 +340,7 @@
* Returns true if did NOT move, false if moved (requires current_position update).
*/
bool _O2 unified_bed_leveling::line_to_destination_segmented(const feedRate_t &scaled_fr_mm_s) {
bool _O2 unified_bed_leveling::line_to_destination_segmented(const_feedRate_t scaled_fr_mm_s) {
if (!position_is_reachable(destination)) // fail if moving outside reachable boundary
return true; // did not move, so current_position still accurate
@@ -344,7 +352,7 @@
#if IS_KINEMATIC
const float seconds = cart_xy_mm / scaled_fr_mm_s; // Duration of XY move at requested rate
uint16_t segments = LROUND(delta_segments_per_second * seconds), // Preferred number of segments for distance @ feedrate
uint16_t segments = LROUND(segments_per_second * seconds), // Preferred number of segments for distance @ feedrate
seglimit = LROUND(cart_xy_mm * RECIPROCAL(DELTA_SEGMENT_MIN_LENGTH)); // Number of segments at minimum segment length
NOMORE(segments, seglimit); // Limit to minimum segment length (fewer segments)
#else
@@ -371,15 +379,11 @@
while (--segments) {
raw += diff;
planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, segment_xyz_mm
#if ENABLED(SCARA_FEEDRATE_SCALING)
, inv_duration
#endif
OPTARG(SCARA_FEEDRATE_SCALING, inv_duration)
);
}
planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, segment_xyz_mm
#if ENABLED(SCARA_FEEDRATE_SCALING)
, inv_duration
#endif
OPTARG(SCARA_FEEDRATE_SCALING, inv_duration)
);
return false; // Did not set current from destination
}
@@ -406,8 +410,8 @@
int8_t((raw.x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST)),
int8_t((raw.y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST))
};
LIMIT(icell.x, 0, (GRID_MAX_POINTS_X) - 1);
LIMIT(icell.y, 0, (GRID_MAX_POINTS_Y) - 1);
LIMIT(icell.x, 0, GRID_MAX_CELLS_X);
LIMIT(icell.y, 0, GRID_MAX_CELLS_Y);
float z_x0y0 = z_values[icell.x ][icell.y ], // z at lower left corner
z_x1y0 = z_values[icell.x+1][icell.y ], // z at upper left corner
@@ -451,11 +455,9 @@
#endif
;
planner.buffer_line(raw.x, raw.y, raw.z + z_cxcy, raw.e, scaled_fr_mm_s, active_extruder, segment_xyz_mm
#if ENABLED(SCARA_FEEDRATE_SCALING)
, inv_duration
#endif
);
const float oldz = raw.z; raw.z += z_cxcy;
planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, segment_xyz_mm OPTARG(SCARA_FEEDRATE_SCALING, inv_duration) );
raw.z = oldz;
if (segments == 0) // done with last segment
return false; // didn't set current from destination

View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -25,7 +25,7 @@
#if ENABLED(BINARY_FILE_TRANSFER)
#include "../sd/cardreader.h"
#include "binary_protocol.h"
#include "binary_stream.h"
char* SDFileTransferProtocol::Packet::Open::data = nullptr;
size_t SDFileTransferProtocol::data_waiting, SDFileTransferProtocol::transfer_timeout, SDFileTransferProtocol::idle_timeout;
@@ -33,4 +33,4 @@ bool SDFileTransferProtocol::transfer_active, SDFileTransferProtocol::dummy_tran
BinaryStream binaryStream[NUM_SERIAL];
#endif // BINARY_FILE_TRANSFER
#endif

View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -24,44 +24,29 @@
#include "../inc/MarlinConfig.h"
#define BINARY_STREAM_COMPRESSION
#if ENABLED(BINARY_STREAM_COMPRESSION)
#include "../libs/heatshrink/heatshrink_decoder.h"
#endif
inline bool bs_serial_data_available(const uint8_t index) {
switch (index) {
case 0: return MYSERIAL0.available();
#if NUM_SERIAL > 1
case 1: return MYSERIAL1.available();
#endif
}
return false;
}
inline int bs_read_serial(const uint8_t index) {
switch (index) {
case 0: return MYSERIAL0.read();
#if NUM_SERIAL > 1
case 1: return MYSERIAL1.read();
#endif
}
return -1;
}
#if ENABLED(BINARY_STREAM_COMPRESSION)
// STM32 (and others?) require a word-aligned buffer for SD card transfers via DMA
static __attribute__((aligned(sizeof(size_t)))) uint8_t decode_buffer[512] = {};
static heatshrink_decoder hsd;
static uint8_t decode_buffer[512] = {};
#endif
inline bool bs_serial_data_available(const serial_index_t index) {
return SERIAL_IMPL.available(index);
}
inline int bs_read_serial(const serial_index_t index) {
return SERIAL_IMPL.read(index);
}
class SDFileTransferProtocol {
private:
struct Packet {
struct [[gnu::packed]] Open {
static bool validate(char* buffer, size_t length) {
static bool validate(char *buffer, size_t length) {
return (length > sizeof(Open) && buffer[length - 1] == '\0');
}
static Open& decode(char* buffer) {
static Open& decode(char *buffer) {
data = &buffer[2];
return *reinterpret_cast<Open*>(buffer);
}
@@ -74,7 +59,7 @@ private:
};
};
static bool file_open(char* filename) {
static bool file_open(char *filename) {
if (!dummy_transfer) {
card.mount();
card.openFileWrite(filename);
@@ -82,13 +67,11 @@ private:
}
transfer_active = true;
data_waiting = 0;
#if ENABLED(BINARY_STREAM_COMPRESSION)
heatshrink_decoder_reset(&hsd);
#endif
TERN_(BINARY_STREAM_COMPRESSION, heatshrink_decoder_reset(&hsd));
return true;
}
static bool file_write(char* buffer, const size_t length) {
static bool file_write(char *buffer, const size_t length) {
#if ENABLED(BINARY_STREAM_COMPRESSION)
if (compression) {
size_t total_processed = 0, processed_count = 0;
@@ -127,9 +110,7 @@ private:
card.closefile();
card.release();
}
#if ENABLED(BINARY_STREAM_COMPRESSION)
heatshrink_decoder_finish(&hsd);
#endif
TERN_(BINARY_STREAM_COMPRESSION, heatshrink_decoder_finish(&hsd));
transfer_active = false;
return true;
}
@@ -139,9 +120,7 @@ private:
card.closefile();
card.removeFile(card.filename);
card.release();
#if ENABLED(BINARY_STREAM_COMPRESSION)
heatshrink_decoder_finish(&hsd);
#endif
TERN_(BINARY_STREAM_COMPRESSION, heatshrink_decoder_finish(&hsd));
}
transfer_active = false;
return;
@@ -163,15 +142,15 @@ public:
}
}
static void process(uint8_t packet_type, char* buffer, const uint16_t length) {
static void process(uint8_t packet_type, char *buffer, const uint16_t length) {
transfer_timeout = millis() + TIMEOUT;
switch (static_cast<FileTransfer>(packet_type)) {
case FileTransfer::QUERY:
SERIAL_ECHOPAIR("PFT:version:", VERSION_MAJOR, ".", VERSION_MINOR, ".", VERSION_PATCH);
SERIAL_ECHOPGM("PFT:version:", VERSION_MAJOR, ".", VERSION_MINOR, ".", VERSION_PATCH);
#if ENABLED(BINARY_STREAM_COMPRESSION)
SERIAL_ECHOLNPAIR(":compresion:heatshrink,", HEATSHRINK_STATIC_WINDOW_BITS, ",", HEATSHRINK_STATIC_LOOKAHEAD_BITS);
SERIAL_ECHOLNPGM(":compression:heatshrink,", HEATSHRINK_STATIC_WINDOW_BITS, ",", HEATSHRINK_STATIC_LOOKAHEAD_BITS);
#else
SERIAL_ECHOLNPGM(":compresion:none");
SERIAL_ECHOLNPGM(":compression:none");
#endif
break;
case FileTransfer::OPEN:
@@ -303,7 +282,7 @@ public:
millis_t transfer_window = millis() + RX_TIMESLICE;
#if ENABLED(SDSUPPORT)
PORT_REDIRECT(card.transfer_port_index);
PORT_REDIRECT(SERIAL_PORTMASK(card.transfer_port_index));
#endif
#pragma GCC diagnostic push
@@ -343,7 +322,7 @@ public:
if (packet.header.checksum == packet.header_checksum) {
// The SYNC control packet is a special case in that it doesn't require the stream sync to be correct
if (static_cast<Protocol>(packet.header.protocol()) == Protocol::CONTROL && static_cast<ProtocolControl>(packet.header.type()) == ProtocolControl::SYNC) {
SERIAL_ECHOLNPAIR("ss", sync, ",", buffer_size, ",", VERSION_MAJOR, ".", VERSION_MINOR, ".", VERSION_PATCH);
SERIAL_ECHOLNPGM("ss", sync, ",", buffer_size, ",", VERSION_MAJOR, ".", VERSION_MINOR, ".", VERSION_PATCH);
stream_state = StreamState::PACKET_RESET;
break;
}
@@ -358,7 +337,7 @@ public:
stream_state = StreamState::PACKET_PROCESS;
}
else if (packet.header.sync == sync - 1) { // ok response must have been lost
SERIAL_ECHOLNPAIR("ok", packet.header.sync); // transmit valid packet received and drop the payload
SERIAL_ECHOLNPGM("ok", packet.header.sync); // transmit valid packet received and drop the payload
stream_state = StreamState::PACKET_RESET;
}
else if (packet_retries) {
@@ -370,8 +349,7 @@ public:
}
}
else {
SERIAL_ECHO_START();
SERIAL_ECHOLNPAIR("Packet header(", packet.header.sync, "?) corrupt");
SERIAL_ECHO_MSG("Packet header(", packet.header.sync, "?) corrupt");
stream_state = StreamState::PACKET_RESEND;
}
}
@@ -405,8 +383,7 @@ public:
stream_state = StreamState::PACKET_PROCESS;
}
else {
SERIAL_ECHO_START();
SERIAL_ECHOLNPAIR("Packet(", packet.header.sync, ") payload corrupt");
SERIAL_ECHO_MSG("Packet(", packet.header.sync, ") payload corrupt");
stream_state = StreamState::PACKET_RESEND;
}
}
@@ -416,7 +393,7 @@ public:
packet_retries = 0;
bytes_received += packet.header.size;
SERIAL_ECHOLNPAIR("ok", packet.header.sync); // transmit valid packet received
SERIAL_ECHOLNPGM("ok", packet.header.sync); // transmit valid packet received
dispatch();
stream_state = StreamState::PACKET_RESET;
break;
@@ -424,9 +401,8 @@ public:
if (packet_retries < MAX_RETRIES || MAX_RETRIES == 0) {
packet_retries++;
stream_state = StreamState::PACKET_RESET;
SERIAL_ECHO_START();
SERIAL_ECHOLNPAIR("Resend request ", int(packet_retries));
SERIAL_ECHOLNPAIR("rs", sync);
SERIAL_ECHO_MSG("Resend request ", packet_retries);
SERIAL_ECHOLNPGM("rs", sync);
}
else
stream_state = StreamState::PACKET_ERROR;
@@ -436,7 +412,7 @@ public:
stream_state = StreamState::PACKET_RESEND;
break;
case StreamState::PACKET_ERROR:
SERIAL_ECHOLNPAIR("fe", packet.header.sync);
SERIAL_ECHOLNPGM("fe", packet.header.sync);
reset(); // reset everything, resync required
stream_state = StreamState::PACKET_RESET;
break;
@@ -447,9 +423,9 @@ public:
}
void dispatch() {
switch(static_cast<Protocol>(packet.header.protocol())) {
switch (static_cast<Protocol>(packet.header.protocol())) {
case Protocol::CONTROL:
switch(static_cast<ProtocolControl>(packet.header.type())) {
switch (static_cast<ProtocolControl>(packet.header.type())) {
case ProtocolControl::CLOSE: // revert back to ASCII mode
card.flag.binary_mode = false;
break;

25
Marlin/src/feature/bltouch.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -31,6 +31,7 @@ BLTouch bltouch;
bool BLTouch::last_written_mode; // Initialized by settings.load, 0 = Open Drain; 1 = 5V Drain
#include "../module/servo.h"
#include "../module/probe.h"
void stop();
@@ -38,7 +39,7 @@ void stop();
#include "../core/debug_out.h"
bool BLTouch::command(const BLTCommand cmd, const millis_t &ms) {
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPAIR("BLTouch Command :", cmd);
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("BLTouch Command :", cmd);
MOVE_SERVO(Z_PROBE_SERVO_NR, cmd);
safe_delay(_MAX(ms, (uint32_t)BLTOUCH_DELAY)); // BLTOUCH_DELAY is also the *minimum* delay
return triggered();
@@ -63,7 +64,7 @@ void BLTouch::init(const bool set_voltage/*=false*/) {
#else
if (DEBUGGING(LEVELING)) {
DEBUG_ECHOLNPAIR("last_written_mode - ", (int)last_written_mode);
DEBUG_ECHOLNPGM("last_written_mode - ", last_written_mode);
DEBUG_ECHOLNPGM("config mode - "
#if ENABLED(BLTOUCH_SET_5V_MODE)
"BLTOUCH_SET_5V_MODE"
@@ -90,15 +91,7 @@ void BLTouch::clear() {
_stow(); // STOW to be ready for meaningful work. Could fail, don't care
}
bool BLTouch::triggered() {
return (
#if ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN)
READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING
#else
READ(Z_MIN_PROBE_PIN) != Z_MIN_PROBE_ENDSTOP_INVERTING
#endif
);
}
bool BLTouch::triggered() { return PROBE_TRIGGERED(); }
bool BLTouch::deploy_proc() {
// Do a DEPLOY
@@ -124,9 +117,7 @@ bool BLTouch::deploy_proc() {
}
// One of the recommended ANTClabs ways to probe, using SW MODE
#if ENABLED(BLTOUCH_FORCE_SW_MODE)
_set_SW_mode();
#endif
TERN_(BLTOUCH_FORCE_SW_MODE, _set_SW_mode());
// Now the probe is ready to issue a 10ms pulse when the pin goes up.
// The trigger STOW (see motion.cpp for example) will pull up the probes pin as soon as the pulse
@@ -184,7 +175,7 @@ bool BLTouch::status_proc() {
_set_SW_mode(); // Incidentally, _set_SW_mode() will also RESET any active alarm
const bool tr = triggered(); // If triggered in SW mode, the pin is up, it is STOWED
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("BLTouch is ", (int)tr);
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("BLTouch is ", tr);
if (tr) _stow(); else _deploy(); // Turn off SW mode, reset any trigger, honor pin state
return !tr;
@@ -196,7 +187,7 @@ void BLTouch::mode_conv_proc(const bool M5V) {
* BLTOUCH V3.0: This will set the mode (twice) and sadly, a STOW is needed at the end, because of the deploy
* BLTOUCH V3.1: This will set the mode and store it in the eeprom. The STOW is not needed but does not hurt
*/
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("BLTouch Set Mode - ", (int)M5V);
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("BLTouch Set Mode - ", M5V);
_deploy();
if (M5V) _set_5V_mode(); else _set_OD_mode();
_mode_store();

41
Marlin/src/feature/bltouch.h Executable file → Normal file
View File

@@ -16,19 +16,24 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "../inc/MarlinConfigPre.h"
#if DISABLED(BLTOUCH_HS_MODE)
#define BLTOUCH_SLOW_MODE 1
#endif
// BLTouch commands are sent as servo angles
typedef unsigned char BLTCommand;
#define STOW_ALARM true
#define BLTOUCH_DEPLOY 10
#define BLTOUCH_SW_MODE 60
#define BLTOUCH_STOW 90
#define BLTOUCH_SW_MODE 60
#define BLTOUCH_SELFTEST 120
#define BLTOUCH_MODE_STORE 130
#define BLTOUCH_5V_MODE 140
@@ -69,33 +74,33 @@ public:
static bool last_written_mode; // Initialized by settings.load, 0 = Open Drain; 1 = 5V Drain
// DEPLOY and STOW are wrapped for error handling - these are used by homing and by probing
FORCE_INLINE static bool deploy() { return deploy_proc(); }
FORCE_INLINE static bool stow() { return stow_proc(); }
FORCE_INLINE static bool status() { return status_proc(); }
static bool deploy() { return deploy_proc(); }
static bool stow() { return stow_proc(); }
static bool status() { return status_proc(); }
// Native BLTouch commands ("Underscore"...), used in lcd menus and internally
FORCE_INLINE static void _reset() { command(BLTOUCH_RESET, BLTOUCH_RESET_DELAY); }
static void _reset() { command(BLTOUCH_RESET, BLTOUCH_RESET_DELAY); }
FORCE_INLINE static void _selftest() { command(BLTOUCH_SELFTEST, BLTOUCH_DELAY); }
static void _selftest() { command(BLTOUCH_SELFTEST, BLTOUCH_DELAY); }
FORCE_INLINE static void _set_SW_mode() { command(BLTOUCH_SW_MODE, BLTOUCH_DELAY); }
FORCE_INLINE static void _reset_SW_mode() { if (triggered()) _stow(); else _deploy(); }
static void _set_SW_mode() { command(BLTOUCH_SW_MODE, BLTOUCH_DELAY); }
static void _reset_SW_mode() { if (triggered()) _stow(); else _deploy(); }
FORCE_INLINE static void _set_5V_mode() { command(BLTOUCH_5V_MODE, BLTOUCH_SET5V_DELAY); }
FORCE_INLINE static void _set_OD_mode() { command(BLTOUCH_OD_MODE, BLTOUCH_SETOD_DELAY); }
FORCE_INLINE static void _mode_store() { command(BLTOUCH_MODE_STORE, BLTOUCH_MODE_STORE_DELAY); }
static void _set_5V_mode() { command(BLTOUCH_5V_MODE, BLTOUCH_SET5V_DELAY); }
static void _set_OD_mode() { command(BLTOUCH_OD_MODE, BLTOUCH_SETOD_DELAY); }
static void _mode_store() { command(BLTOUCH_MODE_STORE, BLTOUCH_MODE_STORE_DELAY); }
FORCE_INLINE static void _deploy() { command(BLTOUCH_DEPLOY, BLTOUCH_DEPLOY_DELAY); }
FORCE_INLINE static void _stow() { command(BLTOUCH_STOW, BLTOUCH_STOW_DELAY); }
static void _deploy() { command(BLTOUCH_DEPLOY, BLTOUCH_DEPLOY_DELAY); }
static void _stow() { command(BLTOUCH_STOW, BLTOUCH_STOW_DELAY); }
FORCE_INLINE static void mode_conv_5V() { mode_conv_proc(true); }
FORCE_INLINE static void mode_conv_OD() { mode_conv_proc(false); }
static void mode_conv_5V() { mode_conv_proc(true); }
static void mode_conv_OD() { mode_conv_proc(false); }
static bool triggered();
private:
FORCE_INLINE static bool _deploy_query_alarm() { return command(BLTOUCH_DEPLOY, BLTOUCH_DEPLOY_DELAY); }
FORCE_INLINE static bool _stow_query_alarm() { return command(BLTOUCH_STOW, BLTOUCH_STOW_DELAY); }
static bool _deploy_query_alarm() { return command(BLTOUCH_DEPLOY, BLTOUCH_DEPLOY_DELAY); }
static bool _stow_query_alarm() { return command(BLTOUCH_STOW, BLTOUCH_STOW_DELAY) == STOW_ALARM; }
static void clear();
static bool command(const BLTCommand cmd, const millis_t &ms);

15
Marlin/src/feature/cancel_object.cpp Executable file → Normal file
View File

@@ -16,16 +16,17 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "../inc/MarlinConfig.h"
#if ENABLED(CANCEL_OBJECTS)
#include "cancel_object.h"
#include "../gcode/gcode.h"
#include "../lcd/ultralcd.h"
#include "../lcd/marlinui.h"
CancelObject cancelable;
@@ -43,9 +44,9 @@ void CancelObject::set_active_object(const int8_t obj) {
else
skipping = false;
#if HAS_DISPLAY
#if BOTH(HAS_STATUS_MESSAGE, CANCEL_OBJECTS_REPORTING)
if (active_object >= 0)
ui.status_printf_P(0, PSTR(S_FMT " %i"), GET_TEXT(MSG_PRINTING_OBJECT), int(active_object + 1));
ui.status_printf_P(0, PSTR(S_FMT " %i"), GET_TEXT(MSG_PRINTING_OBJECT), int(active_object));
else
ui.reset_status();
#endif
@@ -66,10 +67,8 @@ void CancelObject::uncancel_object(const int8_t obj) {
}
void CancelObject::report() {
if (active_object >= 0) {
SERIAL_ECHO_START();
SERIAL_ECHOLNPAIR("Active Object: ", int(active_object));
}
if (active_object >= 0)
SERIAL_ECHO_MSG("Active Object: ", active_object);
if (canceled) {
SERIAL_ECHO_START();

2
Marlin/src/feature/cancel_object.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once

91
Marlin/src/feature/caselight.cpp Executable file → Normal file
View File

@@ -16,63 +16,60 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "../inc/MarlinConfig.h"
#if HAS_CASE_LIGHT
#if ENABLED(CASE_LIGHT_ENABLE)
uint8_t case_light_brightness = CASE_LIGHT_DEFAULT_BRIGHTNESS;
bool case_light_on = CASE_LIGHT_DEFAULT_ON;
#include "caselight.h"
#if ENABLED(CASE_LIGHT_USE_NEOPIXEL)
CaseLight caselight;
#if CASELIGHT_USES_BRIGHTNESS && !defined(CASE_LIGHT_DEFAULT_BRIGHTNESS)
#define CASE_LIGHT_DEFAULT_BRIGHTNESS 0 // For use on PWM pin as non-PWM just sets a default
#endif
#if CASELIGHT_USES_BRIGHTNESS
uint8_t CaseLight::brightness = CASE_LIGHT_DEFAULT_BRIGHTNESS;
#endif
bool CaseLight::on = CASE_LIGHT_DEFAULT_ON;
#if CASE_LIGHT_IS_COLOR_LED
#include "leds/leds.h"
LEDColor case_light_color =
#ifdef CASE_LIGHT_NEOPIXEL_COLOR
CASE_LIGHT_NEOPIXEL_COLOR
#else
{ 255, 255, 255, 255 }
#endif
;
constexpr uint8_t init_case_light[] = CASE_LIGHT_DEFAULT_COLOR;
LEDColor CaseLight::color = { init_case_light[0], init_case_light[1], init_case_light[2] OPTARG(HAS_WHITE_LED, init_case_light[3]) };
#endif
/**
* The following are needed because ARM chips ignore a "WRITE(CASE_LIGHT_PIN,x)" command to the pins that
* are directly controlled by the PWM module. In order to turn them off the brightness level needs to be
* set to off. Since we can't use the pwm register to save the last brightness level we need a variable
* to save it.
*/
uint8_t case_light_brightness_sav; // saves brighness info so can restore when "M355 S1" received
bool case_light_arg_flag; // flag to notify if S or P argument type
void CaseLight::update(const bool sflag) {
#if CASELIGHT_USES_BRIGHTNESS
/**
* The brightness_sav (and sflag) is needed because ARM chips ignore
* a "WRITE(CASE_LIGHT_PIN,x)" command to the pins that are directly
* controlled by the PWM module. In order to turn them off the brightness
* level needs to be set to OFF. Since we can't use the PWM register to
* save the last brightness level we need a variable to save it.
*/
static uint8_t brightness_sav; // Save brightness info for restore on "M355 S1"
#ifndef INVERT_CASE_LIGHT
#define INVERT_CASE_LIGHT false
#endif
if (on || !sflag)
brightness_sav = brightness; // Save brightness except for M355 S0
if (sflag && on)
brightness = brightness_sav; // Restore last brightness for M355 S1
void update_case_light() {
if (!(case_light_arg_flag && !case_light_on))
case_light_brightness_sav = case_light_brightness; // save brightness except if this is an S0 argument
if (case_light_arg_flag && case_light_on)
case_light_brightness = case_light_brightness_sav; // restore last brightens if this is an S1 argument
#if ENABLED(CASE_LIGHT_USE_NEOPIXEL) || DISABLED(CASE_LIGHT_NO_BRIGHTNESS)
const uint8_t i = case_light_on ? case_light_brightness : 0, n10ct = INVERT_CASE_LIGHT ? 255 - i : i;
const uint8_t i = on ? brightness : 0, n10ct = ENABLED(INVERT_CASE_LIGHT) ? 255 - i : i;
UNUSED(n10ct);
#endif
#if ENABLED(CASE_LIGHT_USE_NEOPIXEL)
#if CASE_LIGHT_IS_COLOR_LED
leds.set_color(LEDColor(color.r, color.g, color.b OPTARG(HAS_WHITE_LED, color.w), n10ct));
#else // !CASE_LIGHT_IS_COLOR_LED
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
#if DISABLED(CASE_LIGHT_NO_BRIGHTNESS)
if (PWM_PIN(CASE_LIGHT_PIN))
#if CASELIGHT_USES_BRIGHTNESS
if (pin_is_pwm())
analogWrite(pin_t(CASE_LIGHT_PIN), (
#if CASE_LIGHT_MAX_PWM == 255
n10ct
@@ -83,11 +80,15 @@ void update_case_light() {
else
#endif
{
const bool s = case_light_on ? !INVERT_CASE_LIGHT : INVERT_CASE_LIGHT;
const bool s = on ? TERN(INVERT_CASE_LIGHT, LOW, HIGH) : TERN(INVERT_CASE_LIGHT, HIGH, LOW);
WRITE(CASE_LIGHT_PIN, s ? HIGH : LOW);
}
#endif // !CASE_LIGHT_USE_NEOPIXEL
#endif // !CASE_LIGHT_IS_COLOR_LED
#if ENABLED(CASE_LIGHT_USE_RGB_LED)
if (leds.lights_on) leds.update(); else leds.set_off();
#endif
}
#endif // HAS_CASE_LIGHT
#endif // CASE_LIGHT_ENABLE

44
Marlin/src/feature/caselight.h Executable file → Normal file
View File

@@ -16,14 +16,46 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
extern uint8_t case_light_brightness;
extern bool case_light_on;
extern uint8_t case_light_brightness_sav; // saves brighness info when case_light_on is false
extern bool case_light_arg_flag; // flag to notify if S or P argument type
#include "../inc/MarlinConfig.h"
void update_case_light();
#if CASE_LIGHT_IS_COLOR_LED
#include "leds/leds.h" // for LEDColor
#endif
#if NONE(CASE_LIGHT_NO_BRIGHTNESS, CASE_LIGHT_IS_COLOR_LED) || ENABLED(CASE_LIGHT_USE_NEOPIXEL)
#define CASELIGHT_USES_BRIGHTNESS 1
#endif
class CaseLight {
public:
static bool on;
#if ENABLED(CASELIGHT_USES_BRIGHTNESS)
static uint8_t brightness;
#endif
static bool pin_is_pwm() { return TERN0(NEED_CASE_LIGHT_PIN, PWM_PIN(CASE_LIGHT_PIN)); }
static bool has_brightness() { return TERN0(CASELIGHT_USES_BRIGHTNESS, TERN(CASE_LIGHT_USE_NEOPIXEL, true, pin_is_pwm())); }
static void init() {
#if NEED_CASE_LIGHT_PIN
if (pin_is_pwm()) SET_PWM(CASE_LIGHT_PIN); else SET_OUTPUT(CASE_LIGHT_PIN);
#endif
update_brightness();
}
static void update(const bool sflag);
static inline void update_brightness() { update(false); }
static inline void update_enabled() { update(true); }
#if ENABLED(CASE_LIGHT_IS_COLOR_LED)
private:
static LEDColor color;
#endif
};
extern CaseLight caselight;

9
Marlin/src/feature/closedloop.cpp Executable file → Normal file
View File

@@ -16,9 +16,10 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "../inc/MarlinConfig.h"
#if ENABLED(EXTERNAL_CLOSED_LOOP_CONTROLLER)
@@ -29,12 +30,14 @@
#include "closedloop.h"
void init_closedloop() {
ClosedLoop closedloop;
void ClosedLoop::init() {
OUT_WRITE(CLOSED_LOOP_ENABLE_PIN, LOW);
SET_INPUT_PULLUP(CLOSED_LOOP_MOVE_COMPLETE_PIN);
}
void set_closedloop(const byte val) {
void ClosedLoop::set(const byte val) {
OUT_WRITE(CLOSED_LOOP_ENABLE_PIN, val);
}

13
Marlin/src/feature/closedloop.h Executable file → Normal file
View File

@@ -16,10 +16,17 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
void init_closedloop();
void set_closedloop(const byte val);
class ClosedLoop {
public:
static void init();
static void set(const byte val);
};
extern ClosedLoop closedloop;
#define CLOSED_LOOP_WAITING() (READ(CLOSED_LOOP_ENABLE_PIN) && !READ(CLOSED_LOOP_MOVE_COMPLETE_PIN))

49
Marlin/src/feature/controllerfan.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -25,7 +25,7 @@
#if ENABLED(USE_CONTROLLER_FAN)
#include "controllerfan.h"
#include "../module/stepper/indirection.h"
#include "../module/stepper.h"
#include "../module/temperature.h"
ControllerFan controllerFan;
@@ -34,6 +34,8 @@ uint8_t ControllerFan::speed;
#if ENABLED(CONTROLLER_FAN_EDITABLE)
controllerFan_settings_t ControllerFan::settings; // {0}
#else
const controllerFan_settings_t &ControllerFan::settings = controllerFan_defaults;
#endif
void ControllerFan::setup() {
@@ -52,46 +54,21 @@ void ControllerFan::update() {
if (ELAPSED(ms, nextMotorCheck)) {
nextMotorCheck = ms + 2500UL; // Not a time critical function, so only check every 2.5s
#define MOTOR_IS_ON(A,B) (A##_ENABLE_READ() == bool(B##_ENABLE_ON))
#define _OR_ENABLED_E(N) || MOTOR_IS_ON(E##N,E)
const bool motor_on = MOTOR_IS_ON(Z,Z)
#if HAS_Z2_ENABLE
|| MOTOR_IS_ON(Z2,Z)
#endif
#if HAS_Z3_ENABLE
|| MOTOR_IS_ON(Z3,Z)
#endif
#if HAS_Z4_ENABLE
|| MOTOR_IS_ON(Z4,Z)
#endif
|| (DISABLED(CONTROLLER_FAN_USE_Z_ONLY) && (
MOTOR_IS_ON(X,X) || MOTOR_IS_ON(Y,Y)
#if HAS_X2_ENABLE
|| MOTOR_IS_ON(X2,X)
#endif
#if HAS_Y2_ENABLE
|| MOTOR_IS_ON(Y2,Y)
#endif
#if E_STEPPERS
REPEAT(E_STEPPERS, _OR_ENABLED_E)
#endif
)
)
;
// If any of the drivers or the heated bed are enabled...
if (motor_on
#if HAS_HEATED_BED
|| thermalManager.temp_bed.soft_pwm_amount > 0
#endif
// If any triggers for the controller fan are true...
// - At least one stepper driver is enabled
// - The heated bed is enabled
// - TEMP_SENSOR_BOARD is reporting >= CONTROLLER_FAN_MIN_BOARD_TEMP
const ena_mask_t axis_mask = TERN(CONTROLLER_FAN_USE_Z_ONLY, _BV(Z_AXIS), ~TERN0(CONTROLLER_FAN_IGNORE_Z, _BV(Z_AXIS)));
if ( (stepper.axis_enabled.bits & axis_mask)
|| TERN0(HAS_HEATED_BED, thermalManager.temp_bed.soft_pwm_amount > 0)
|| TERN0(HAS_CONTROLLER_FAN_MIN_BOARD_TEMP, thermalManager.wholeDegBoard() >= CONTROLLER_FAN_MIN_BOARD_TEMP)
) lastMotorOn = ms; //... set time to NOW so the fan will turn on
// Fan Settings. Set fan > 0:
// - If AutoMode is on and steppers have been enabled for CONTROLLERFAN_IDLE_TIME seconds.
// - If System is on idle and idle fan speed settings is activated.
set_fan_speed(
settings.auto_mode && lastMotorOn && PENDING(ms, lastMotorOn + settings.duration * 1000UL)
settings.auto_mode && lastMotorOn && PENDING(ms, lastMotorOn + SEC_TO_MS(settings.duration))
? settings.active_speed : settings.idle_speed
);

10
Marlin/src/feature/controllerfan.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -58,15 +58,11 @@ class ControllerFan {
#if ENABLED(CONTROLLER_FAN_EDITABLE)
static controllerFan_settings_t settings;
#else
static const controllerFan_settings_t constexpr &settings = controllerFan_defaults;
static const controllerFan_settings_t &settings;
#endif
static inline bool state() { return speed > 0; }
static inline void init() { reset(); }
static inline void reset() {
#if ENABLED(CONTROLLER_FAN_EDITABLE)
settings = controllerFan_defaults;
#endif
}
static inline void reset() { TERN_(CONTROLLER_FAN_EDITABLE, settings = controllerFan_defaults); }
static void setup();
static void update();
};

View File

@@ -0,0 +1,48 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2021 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 <https://www.gnu.org/licenses/>.
*
*/
#include "../inc/MarlinConfig.h"
#if EITHER(HAS_COOLER, LASER_COOLANT_FLOW_METER)
#include "cooler.h"
Cooler cooler;
#if HAS_COOLER
uint8_t Cooler::mode = 0;
uint16_t Cooler::capacity;
uint16_t Cooler::load;
bool Cooler::enabled = false;
#endif
#if ENABLED(LASER_COOLANT_FLOW_METER)
bool Cooler::flowmeter = false;
millis_t Cooler::flowmeter_next_ms; // = 0
volatile uint16_t Cooler::flowpulses;
float Cooler::flowrate;
#if ENABLED(FLOWMETER_SAFETY)
bool Cooler::flowsafety_enabled = true;
bool Cooler::flowfault = false;
#endif
#endif
#endif // HAS_COOLER || LASER_COOLANT_FLOW_METER

109
Marlin/src/feature/cooler.h Normal file
View File

@@ -0,0 +1,109 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2021 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "../inc/MarlinConfigPre.h"
#ifndef FLOWMETER_PPL
#define FLOWMETER_PPL 5880 // Pulses per liter
#endif
#ifndef FLOWMETER_INTERVAL
#define FLOWMETER_INTERVAL 1000 // milliseconds
#endif
// Cooling device
class Cooler {
public:
static uint16_t capacity; // Cooling capacity in watts
static uint16_t load; // Cooling load in watts
static bool enabled;
static void enable() { enabled = true; }
static void disable() { enabled = false; }
static void toggle() { enabled = !enabled; }
static uint8_t mode; // 0 = CO2 Liquid cooling, 1 = Laser Diode TEC Heatsink Cooling
static void set_mode(const uint8_t m) { mode = m; }
#if ENABLED(LASER_COOLANT_FLOW_METER)
static float flowrate; // Flow meter reading in liters-per-minute.
static bool flowmeter; // Flag to monitor the flow
static volatile uint16_t flowpulses; // Flowmeter IRQ pulse count
static millis_t flowmeter_next_ms; // Next time at which to calculate flow
static void set_flowmeter(const bool sflag) {
if (flowmeter != sflag) {
flowmeter = sflag;
if (sflag) {
flowpulses = 0;
flowmeter_next_ms = millis() + FLOWMETER_INTERVAL;
}
}
}
// To calculate flow we only need to count pulses
static void flowmeter_ISR() { flowpulses++; }
// Enable / Disable the flow meter interrupt
static void flowmeter_interrupt_enable() {
attachInterrupt(digitalPinToInterrupt(FLOWMETER_PIN), flowmeter_ISR, RISING);
}
static void flowmeter_interrupt_disable() {
detachInterrupt(digitalPinToInterrupt(FLOWMETER_PIN));
}
// Enable / Disable the flow meter interrupt
static void flowmeter_enable() { set_flowmeter(true); flowpulses = 0; flowmeter_interrupt_enable(); }
static void flowmeter_disable() { set_flowmeter(false); flowmeter_interrupt_disable(); flowpulses = 0; }
// Get the total flow (in liters per minute) since the last reading
static void calc_flowrate() {
// flowrate = (litres) * (seconds) = litres per minute
flowrate = (flowpulses / (float)FLOWMETER_PPL) * ((1000.0f / (float)FLOWMETER_INTERVAL) * 60.0f);
flowpulses = 0;
}
// Userland task to update the flow meter
static void flowmeter_task(const millis_t ms=millis()) {
if (!flowmeter) // !! The flow meter must always be on !!
flowmeter_enable(); // Init and prime
if (ELAPSED(ms, flowmeter_next_ms)) {
calc_flowrate();
flowmeter_next_ms = ms + FLOWMETER_INTERVAL;
}
}
#if ENABLED(FLOWMETER_SAFETY)
static bool flowfault; // Flag that the cooler is in a fault state
static bool flowsafety_enabled; // Flag to disable the cutter if flow rate is too low
static void flowsafety_toggle() { flowsafety_enabled = !flowsafety_enabled; }
static bool check_flow_too_low() {
const bool too_low = flowsafety_enabled && flowrate < (FLOWMETER_MIN_LITERS_PER_MINUTE);
flowfault = too_low;
return too_low;
}
#endif
#endif
};
extern Cooler cooler;

50
Marlin/src/feature/dac/dac_dac084s085.cpp Executable file → Normal file
View File

@@ -20,35 +20,35 @@ void dac084s085::begin() {
uint8_t externalDac_buf[] = { 0x20, 0x00 }; // all off
// All SPI chip-select HIGH
SET_OUTPUT(DAC0_SYNC);
#if EXTRUDERS > 1
SET_OUTPUT(DAC1_SYNC);
SET_OUTPUT(DAC0_SYNC_PIN);
#if HAS_MULTI_EXTRUDER
SET_OUTPUT(DAC1_SYNC_PIN);
#endif
cshigh();
spiBegin();
//init onboard DAC
DELAY_US(2);
WRITE(DAC0_SYNC, LOW);
WRITE(DAC0_SYNC_PIN, LOW);
DELAY_US(2);
WRITE(DAC0_SYNC, HIGH);
WRITE(DAC0_SYNC_PIN, HIGH);
DELAY_US(2);
WRITE(DAC0_SYNC, LOW);
WRITE(DAC0_SYNC_PIN, LOW);
spiSend(SPI_CHAN_DAC, externalDac_buf, COUNT(externalDac_buf));
WRITE(DAC0_SYNC, HIGH);
WRITE(DAC0_SYNC_PIN, HIGH);
#if EXTRUDERS > 1
#if HAS_MULTI_EXTRUDER
//init Piggy DAC
DELAY_US(2);
WRITE(DAC1_SYNC, LOW);
WRITE(DAC1_SYNC_PIN, LOW);
DELAY_US(2);
WRITE(DAC1_SYNC, HIGH);
WRITE(DAC1_SYNC_PIN, HIGH);
DELAY_US(2);
WRITE(DAC1_SYNC, LOW);
WRITE(DAC1_SYNC_PIN, LOW);
spiSend(SPI_CHAN_DAC, externalDac_buf, COUNT(externalDac_buf));
WRITE(DAC1_SYNC, HIGH);
WRITE(DAC1_SYNC_PIN, HIGH);
#endif
return;
@@ -66,18 +66,18 @@ void dac084s085::setValue(const uint8_t channel, const uint8_t value) {
cshigh();
if (channel > 3) { // DAC Piggy E1,E2,E3
WRITE(DAC1_SYNC, LOW);
WRITE(DAC1_SYNC_PIN, LOW);
DELAY_US(2);
WRITE(DAC1_SYNC, HIGH);
WRITE(DAC1_SYNC_PIN, HIGH);
DELAY_US(2);
WRITE(DAC1_SYNC, LOW);
WRITE(DAC1_SYNC_PIN, LOW);
}
else { // DAC onboard X,Y,Z,E0
WRITE(DAC0_SYNC, LOW);
WRITE(DAC0_SYNC_PIN, LOW);
DELAY_US(2);
WRITE(DAC0_SYNC, HIGH);
WRITE(DAC0_SYNC_PIN, HIGH);
DELAY_US(2);
WRITE(DAC0_SYNC, LOW);
WRITE(DAC0_SYNC_PIN, LOW);
}
DELAY_US(2);
@@ -85,14 +85,14 @@ void dac084s085::setValue(const uint8_t channel, const uint8_t value) {
}
void dac084s085::cshigh() {
WRITE(DAC0_SYNC, HIGH);
#if EXTRUDERS > 1
WRITE(DAC1_SYNC, HIGH);
WRITE(DAC0_SYNC_PIN, HIGH);
#if HAS_MULTI_EXTRUDER
WRITE(DAC1_SYNC_PIN, HIGH);
#endif
WRITE(SPI_EEPROM1_CS, HIGH);
WRITE(SPI_EEPROM2_CS, HIGH);
WRITE(SPI_FLASH_CS, HIGH);
WRITE(SS_PIN, HIGH);
WRITE(SPI_EEPROM1_CS_PIN, HIGH);
WRITE(SPI_EEPROM2_CS_PIN, HIGH);
WRITE(SPI_FLASH_CS_PIN, HIGH);
WRITE(SD_SS_PIN, HIGH);
}
#endif // MB(ALLIGATOR)

2
Marlin/src/feature/dac/dac_dac084s085.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once

70
Marlin/src/feature/dac/dac_mcp4728.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -24,33 +24,35 @@
* mcp4728.cpp - Arduino library for MicroChip MCP4728 I2C D/A converter
*
* For implementation details, please take a look at the datasheet:
* http://ww1.microchip.com/downloads/en/DeviceDoc/22187a.pdf
* https://ww1.microchip.com/downloads/en/DeviceDoc/22187a.pdf
*
* For discussion and feedback, please go to:
* http://arduino.cc/forum/index.php/topic,51842.0.html
* https://forum.arduino.cc/index.php/topic,51842.0.html
*/
#include "../../inc/MarlinConfig.h"
#if ENABLED(DAC_STEPPER_CURRENT)
#if HAS_MOTOR_CURRENT_DAC
#include "dac_mcp4728.h"
xyze_uint_t mcp4728_values;
MCP4728 mcp4728;
xyze_uint_t dac_values;
/**
* Begin I2C, get current values (input register and eeprom) of mcp4728
*/
void mcp4728_init() {
void MCP4728::init() {
Wire.begin();
Wire.requestFrom(I2C_ADDRESS(DAC_DEV_ADDRESS), 24);
Wire.requestFrom(I2C_ADDRESS(DAC_DEV_ADDRESS), uint8_t(24));
while (Wire.available()) {
char deviceID = Wire.read(),
hiByte = Wire.read(),
loByte = Wire.read();
if (!(deviceID & 0x08))
mcp4728_values[(deviceID & 0x30) >> 4] = word((hiByte & 0x0F), loByte);
dac_values[(deviceID & 0x30) >> 4] = word((hiByte & 0x0F), loByte);
}
}
@@ -58,38 +60,38 @@ void mcp4728_init() {
* Write input resister value to specified channel using fastwrite method.
* Channel : 0-3, Values : 0-4095
*/
uint8_t mcp4728_analogWrite(const uint8_t channel, const uint16_t value) {
mcp4728_values[channel] = value;
return mcp4728_fastWrite();
uint8_t MCP4728::analogWrite(const uint8_t channel, const uint16_t value) {
dac_values[channel] = value;
return fastWrite();
}
/**
* Write all input resistor values to EEPROM using SequencialWrite method.
* Write all input resistor values to EEPROM using SequentialWrite method.
* This will update both input register and EEPROM value
* This will also write current Vref, PowerDown, Gain settings to EEPROM
*/
uint8_t mcp4728_eepromWrite() {
uint8_t MCP4728::eepromWrite() {
Wire.beginTransmission(I2C_ADDRESS(DAC_DEV_ADDRESS));
Wire.write(SEQWRITE);
LOOP_XYZE(i) {
Wire.write(DAC_STEPPER_VREF << 7 | DAC_STEPPER_GAIN << 4 | highByte(mcp4728_values[i]));
Wire.write(lowByte(mcp4728_values[i]));
LOOP_LOGICAL_AXES(i) {
Wire.write(DAC_STEPPER_VREF << 7 | DAC_STEPPER_GAIN << 4 | highByte(dac_values[i]));
Wire.write(lowByte(dac_values[i]));
}
return Wire.endTransmission();
}
/**
* Write Voltage reference setting to all input regiters
* Write Voltage reference setting to all input registers
*/
uint8_t mcp4728_setVref_all(const uint8_t value) {
uint8_t MCP4728::setVref_all(const uint8_t value) {
Wire.beginTransmission(I2C_ADDRESS(DAC_DEV_ADDRESS));
Wire.write(VREFWRITE | (value ? 0x0F : 0x00));
return Wire.endTransmission();
}
/**
* Write Gain setting to all input regiters
* Write Gain setting to all input registers
*/
uint8_t mcp4728_setGain_all(const uint8_t value) {
uint8_t MCP4728::setGain_all(const uint8_t value) {
Wire.beginTransmission(I2C_ADDRESS(DAC_DEV_ADDRESS));
Wire.write(GAINWRITE | (value ? 0x0F : 0x00));
return Wire.endTransmission();
@@ -98,16 +100,16 @@ uint8_t mcp4728_setGain_all(const uint8_t value) {
/**
* Return Input Register value
*/
uint16_t mcp4728_getValue(const uint8_t channel) { return mcp4728_values[channel]; }
uint16_t MCP4728::getValue(const uint8_t channel) { return dac_values[channel]; }
#if 0
/**
* Steph: Might be useful in the future
* Return Vout
*/
uint16_t mcp4728_getVout(const uint8_t channel) {
uint16_t MCP4728::getVout(const uint8_t channel) {
const uint32_t vref = 2048,
vOut = (vref * mcp4728_values[channel] * (_DAC_STEPPER_GAIN + 1)) / 4096;
vOut = (vref * dac_values[channel] * (_DAC_STEPPER_GAIN + 1)) / 4096;
return _MIN(vOut, defaultVDD);
}
#endif
@@ -115,27 +117,27 @@ uint16_t mcp4728_getVout(const uint8_t channel) {
/**
* Returns DAC values as a 0-100 percentage of drive strength
*/
uint8_t mcp4728_getDrvPct(const uint8_t channel) { return uint8_t(100.0 * mcp4728_values[channel] / (DAC_STEPPER_MAX) + 0.5); }
uint8_t MCP4728::getDrvPct(const uint8_t channel) { return uint8_t(100.0 * dac_values[channel] / (DAC_STEPPER_MAX) + 0.5); }
/**
* Receives all Drive strengths as 0-100 percent values, updates
* DAC Values array and calls fastwrite to update the DAC.
*/
void mcp4728_setDrvPct(xyze_uint8_t &pct) {
mcp4728_values *= 0.01 * pct * (DAC_STEPPER_MAX);
mcp4728_fastWrite();
void MCP4728::setDrvPct(xyze_uint_t &pct) {
dac_values = pct * (DAC_STEPPER_MAX) * 0.01f;
fastWrite();
}
/**
* FastWrite input register values - All DAC ouput update. refer to DATASHEET 5.6.1
* FastWrite input register values - All DAC output update. refer to DATASHEET 5.6.1
* DAC Input and PowerDown bits update.
* No EEPROM update
*/
uint8_t mcp4728_fastWrite() {
uint8_t MCP4728::fastWrite() {
Wire.beginTransmission(I2C_ADDRESS(DAC_DEV_ADDRESS));
LOOP_XYZE(i) {
Wire.write(highByte(mcp4728_values[i]));
Wire.write(lowByte(mcp4728_values[i]));
LOOP_LOGICAL_AXES(i) {
Wire.write(highByte(dac_values[i]));
Wire.write(lowByte(dac_values[i]));
}
return Wire.endTransmission();
}
@@ -143,10 +145,10 @@ uint8_t mcp4728_fastWrite() {
/**
* Common function for simple general commands
*/
uint8_t mcp4728_simpleCommand(const byte simpleCommand) {
uint8_t MCP4728::simpleCommand(const byte simpleCommand) {
Wire.beginTransmission(I2C_ADDRESS(GENERALCALL));
Wire.write(simpleCommand);
return Wire.endTransmission();
}
#endif // DAC_STEPPER_CURRENT
#endif // HAS_MOTOR_CURRENT_DAC

29
Marlin/src/feature/dac/dac_mcp4728.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -40,7 +40,7 @@
#endif
#ifndef lowByte
#define lowByte(w) ((uint8_t) ((w) & 0xff))
#define lowByte(w) ((uint8_t) ((w) & 0xFF))
#endif
#ifndef highByte
@@ -65,13 +65,18 @@
// DAC_OR_ADDRESS defined in pins_BOARD.h file
#define DAC_DEV_ADDRESS (BASE_ADDR | DAC_OR_ADDRESS)
void mcp4728_init();
uint8_t mcp4728_analogWrite(const uint8_t channel, const uint16_t value);
uint8_t mcp4728_eepromWrite();
uint8_t mcp4728_setVref_all(const uint8_t value);
uint8_t mcp4728_setGain_all(const uint8_t value);
uint16_t mcp4728_getValue(const uint8_t channel);
uint8_t mcp4728_fastWrite();
uint8_t mcp4728_simpleCommand(const byte simpleCommand);
uint8_t mcp4728_getDrvPct(const uint8_t channel);
void mcp4728_setDrvPct(xyze_uint8_t &pct);
class MCP4728 {
public:
static void init();
static uint8_t analogWrite(const uint8_t channel, const uint16_t value);
static uint8_t eepromWrite();
static uint8_t setVref_all(const uint8_t value);
static uint8_t setGain_all(const uint8_t value);
static uint16_t getValue(const uint8_t channel);
static uint8_t fastWrite();
static uint8_t simpleCommand(const byte simpleCommand);
static uint8_t getDrvPct(const uint8_t channel);
static void setDrvPct(xyze_uint_t &pct);
};
extern MCP4728 mcp4728;

83
Marlin/src/feature/dac/stepper_dac.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -26,79 +26,80 @@
#include "../../inc/MarlinConfig.h"
#if ENABLED(DAC_STEPPER_CURRENT)
#if HAS_MOTOR_CURRENT_DAC
#include "stepper_dac.h"
#include "../../MarlinCore.h" // for SP_X_LBL...
bool dac_present = false;
constexpr xyze_uint8_t dac_order = DAC_STEPPER_ORDER;
xyze_uint8_t dac_channel_pct = DAC_MOTOR_CURRENT_DEFAULT;
xyze_uint_t dac_channel_pct = DAC_MOTOR_CURRENT_DEFAULT;
int dac_init() {
StepperDAC stepper_dac;
int StepperDAC::init() {
#if PIN_EXISTS(DAC_DISABLE)
OUT_WRITE(DAC_DISABLE_PIN, LOW); // set pin low to enable DAC
#endif
mcp4728_init();
mcp4728.init();
if (mcp4728_simpleCommand(RESET)) return -1;
if (mcp4728.simpleCommand(RESET)) return -1;
dac_present = true;
mcp4728_setVref_all(DAC_STEPPER_VREF);
mcp4728_setGain_all(DAC_STEPPER_GAIN);
mcp4728.setVref_all(DAC_STEPPER_VREF);
mcp4728.setGain_all(DAC_STEPPER_GAIN);
if (mcp4728_getDrvPct(0) < 1 || mcp4728_getDrvPct(1) < 1 || mcp4728_getDrvPct(2) < 1 || mcp4728_getDrvPct(3) < 1 ) {
mcp4728_setDrvPct(dac_channel_pct);
mcp4728_eepromWrite();
if (mcp4728.getDrvPct(0) < 1 || mcp4728.getDrvPct(1) < 1 || mcp4728.getDrvPct(2) < 1 || mcp4728.getDrvPct(3) < 1) {
mcp4728.setDrvPct(dac_channel_pct);
mcp4728.eepromWrite();
}
return 0;
}
void dac_current_percent(uint8_t channel, float val) {
if (!dac_present) return;
NOMORE(val, 100);
mcp4728_analogWrite(dac_order[channel], val * 0.01 * (DAC_STEPPER_MAX));
mcp4728_simpleCommand(UPDATE);
}
void dac_current_raw(uint8_t channel, uint16_t val) {
void StepperDAC::set_current_value(const uint8_t channel, uint16_t val) {
if (!dac_present) return;
NOMORE(val, uint16_t(DAC_STEPPER_MAX));
mcp4728_analogWrite(dac_order[channel], val);
mcp4728_simpleCommand(UPDATE);
mcp4728.analogWrite(dac_order[channel], val);
mcp4728.simpleCommand(UPDATE);
}
static float dac_perc(int8_t n) { return 100.0 * mcp4728_getValue(dac_order[n]) * RECIPROCAL(DAC_STEPPER_MAX); }
static float dac_amps(int8_t n) { return mcp4728_getDrvPct(dac_order[n]) * (DAC_STEPPER_MAX) * 0.125 * RECIPROCAL(DAC_STEPPER_SENSE); }
uint8_t dac_current_get_percent(const AxisEnum axis) { return mcp4728_getDrvPct(dac_order[axis]); }
void dac_current_set_percents(xyze_uint8_t &pct) {
LOOP_XYZE(i) dac_channel_pct[i] = pct[dac_order[i]];
mcp4728_setDrvPct(dac_channel_pct);
void StepperDAC::set_current_percent(const uint8_t channel, float val) {
set_current_value(channel, _MIN(val, 100.0f) * (DAC_STEPPER_MAX) / 100.0f);
}
void dac_print_values() {
static float dac_perc(int8_t n) { return mcp4728.getDrvPct(dac_order[n]); }
static float dac_amps(int8_t n) { return mcp4728.getValue(dac_order[n]) * 0.125 * RECIPROCAL(DAC_STEPPER_SENSE * 1000); }
uint8_t StepperDAC::get_current_percent(const AxisEnum axis) { return mcp4728.getDrvPct(dac_order[axis]); }
void StepperDAC::set_current_percents(xyze_uint8_t &pct) {
LOOP_LOGICAL_AXES(i) dac_channel_pct[i] = pct[dac_order[i]];
mcp4728.setDrvPct(dac_channel_pct);
}
void StepperDAC::print_values() {
if (!dac_present) return;
SERIAL_ECHO_MSG("Stepper current values in % (Amps):");
SERIAL_ECHO_START();
SERIAL_ECHOLNPAIR_P(
SP_X_LBL, dac_perc(X_AXIS), PSTR(" ("), dac_amps(X_AXIS), PSTR(")")
SP_Y_LBL, dac_perc(Y_AXIS), PSTR(" ("), dac_amps(Y_AXIS), PSTR(")")
SP_Z_LBL, dac_perc(Z_AXIS), PSTR(" ("), dac_amps(Z_AXIS), PSTR(")")
SP_E_LBL, dac_perc(E_AXIS), PSTR(" ("), dac_amps(E_AXIS), PSTR(")")
);
SERIAL_ECHOPGM_P(SP_X_LBL, dac_perc(X_AXIS), PSTR(" ("), dac_amps(X_AXIS), PSTR(")"));
#if HAS_Y_AXIS
SERIAL_ECHOPGM_P(SP_Y_LBL, dac_perc(Y_AXIS), PSTR(" ("), dac_amps(Y_AXIS), PSTR(")"));
#endif
#if HAS_Z_AXIS
SERIAL_ECHOPGM_P(SP_Z_LBL, dac_perc(Z_AXIS), PSTR(" ("), dac_amps(Z_AXIS), PSTR(")"));
#endif
#if HAS_EXTRUDERS
SERIAL_ECHOLNPGM_P(SP_E_LBL, dac_perc(E_AXIS), PSTR(" ("), dac_amps(E_AXIS), PSTR(")"));
#endif
}
void dac_commit_eeprom() {
void StepperDAC::commit_eeprom() {
if (!dac_present) return;
mcp4728_eepromWrite();
mcp4728.eepromWrite();
}
#endif // DAC_STEPPER_CURRENT
#endif // HAS_MOTOR_CURRENT_DAC

21
Marlin/src/feature/dac/stepper_dac.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -27,10 +27,15 @@
#include "dac_mcp4728.h"
int dac_init();
void dac_current_percent(uint8_t channel, float val);
void dac_current_raw(uint8_t channel, uint16_t val);
void dac_print_values();
void dac_commit_eeprom();
uint8_t dac_current_get_percent(AxisEnum axis);
void dac_current_set_percents(xyze_uint8_t &pct);
class StepperDAC {
public:
static int init();
static void set_current_percent(const uint8_t channel, float val);
static void set_current_value(const uint8_t channel, uint16_t val);
static void print_values();
static void commit_eeprom();
static uint8_t get_current_percent(const AxisEnum axis);
static void set_current_percents(xyze_uint8_t &pct);
};
extern StepperDAC stepper_dac;

14
Marlin/src/feature/digipot/digipot.h Executable file → Normal file
View File

@@ -16,10 +16,18 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
void digipot_i2c_set_current(const uint8_t channel, const float current);
void digipot_i2c_init();
//
// Header for MCP4018 and MCP4451 current control i2c devices
//
class DigipotI2C {
public:
static void init();
static void set_current(const uint8_t channel, const float current);
};
extern DigipotI2C digipot_i2c;

85
Marlin/src/feature/digipot/digipot_mcp4018.cpp Executable file → Normal file
View File

@@ -16,67 +16,61 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "../../inc/MarlinConfig.h"
#if BOTH(DIGIPOT_I2C, DIGIPOT_MCP4018)
#if ENABLED(DIGIPOT_MCP4018)
#include "Stream.h"
#include "utility/twi.h"
#include <SlowSoftI2CMaster.h> //https://github.com/stawel/SlowSoftI2CMaster
#include "digipot.h"
#include <Stream.h>
#include <SlowSoftI2CMaster.h> // https://github.com/felias-fogg/SlowSoftI2CMaster
// Settings for the I2C based DIGIPOT (MCP4018) based on WT150
#define DIGIPOT_A4988_Rsx 0.250
#define DIGIPOT_A4988_Vrefmax 1.666
#define DIGIPOT_A4988_MAX_VALUE 127
#define DIGIPOT_MCP4018_MAX_VALUE 127
#define DIGIPOT_A4988_Itripmax(Vref) ((Vref)/(8.0*DIGIPOT_A4988_Rsx))
#define DIGIPOT_A4988_Itripmax(Vref) ((Vref) / (8.0 * DIGIPOT_A4988_Rsx))
#define DIGIPOT_A4988_FACTOR ((DIGIPOT_A4988_MAX_VALUE)/DIGIPOT_A4988_Itripmax(DIGIPOT_A4988_Vrefmax))
#define DIGIPOT_A4988_FACTOR ((DIGIPOT_MCP4018_MAX_VALUE) / DIGIPOT_A4988_Itripmax(DIGIPOT_A4988_Vrefmax))
#define DIGIPOT_A4988_MAX_CURRENT 2.0
static byte current_to_wiper(const float current) {
const int16_t value = ceil(float(DIGIPOT_A4988_FACTOR) * current);
return byte(constrain(value, 0, DIGIPOT_A4988_MAX_VALUE));
const int16_t value = TERN(DIGIPOT_USE_RAW_VALUES, current, CEIL(current * DIGIPOT_A4988_FACTOR));
return byte(constrain(value, 0, DIGIPOT_MCP4018_MAX_VALUE));
}
const uint8_t sda_pins[DIGIPOT_I2C_NUM_CHANNELS] = {
DIGIPOTS_I2C_SDA_X
#if DIGIPOT_I2C_NUM_CHANNELS > 1
, DIGIPOTS_I2C_SDA_Y
#if DIGIPOT_I2C_NUM_CHANNELS > 2
, DIGIPOTS_I2C_SDA_Z
#if DIGIPOT_I2C_NUM_CHANNELS > 3
, DIGIPOTS_I2C_SDA_E0
#if DIGIPOT_I2C_NUM_CHANNELS > 4
, DIGIPOTS_I2C_SDA_E1
#endif
#endif
#endif
#endif
};
static SlowSoftI2CMaster pots[DIGIPOT_I2C_NUM_CHANNELS] = {
SlowSoftI2CMaster { sda_pins[X_AXIS], DIGIPOTS_I2C_SCL }
SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_X, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS))
#if DIGIPOT_I2C_NUM_CHANNELS > 1
, SlowSoftI2CMaster { sda_pins[Y_AXIS], DIGIPOTS_I2C_SCL }
, SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_Y, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS))
#if DIGIPOT_I2C_NUM_CHANNELS > 2
, SlowSoftI2CMaster { sda_pins[Z_AXIS], DIGIPOTS_I2C_SCL }
, SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_Z, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS))
#if DIGIPOT_I2C_NUM_CHANNELS > 3
, SlowSoftI2CMaster { sda_pins[E_AXIS], DIGIPOTS_I2C_SCL }
, SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E0, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS))
#if DIGIPOT_I2C_NUM_CHANNELS > 4
, SlowSoftI2CMaster { sda_pins[E_AXIS + 1], DIGIPOTS_I2C_SCL }
, SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E1, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS))
#if DIGIPOT_I2C_NUM_CHANNELS > 5
, SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E2, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS))
#if DIGIPOT_I2C_NUM_CHANNELS > 6
, SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E3, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS))
#if DIGIPOT_I2C_NUM_CHANNELS > 7
, SlowSoftI2CMaster(DIGIPOTS_I2C_SDA_E4, DIGIPOTS_I2C_SCL, ENABLED(DIGIPOT_ENABLE_I2C_PULLUPS))
#endif
#endif
#endif
#endif
#endif
#endif
#endif
};
static void i2c_send(const uint8_t channel, const byte v) {
static void digipot_i2c_send(const uint8_t channel, const byte v) {
if (WITHIN(channel, 0, DIGIPOT_I2C_NUM_CHANNELS - 1)) {
pots[channel].i2c_start(((DIGIPOT_I2C_ADDRESS_A) << 1) | I2C_WRITE);
pots[channel].i2c_write(v);
@@ -85,19 +79,26 @@ static void i2c_send(const uint8_t channel, const byte v) {
}
// This is for the MCP4018 I2C based digipot
void digipot_i2c_set_current(const uint8_t channel, const float current) {
i2c_send(channel, current_to_wiper(_MIN(_MAX(current, 0), float(DIGIPOT_A4988_MAX_CURRENT))));
void DigipotI2C::set_current(const uint8_t channel, const float current) {
const float ival = _MIN(_MAX(current, 0), float(DIGIPOT_MCP4018_MAX_VALUE));
digipot_i2c_send(channel, current_to_wiper(ival));
}
void digipot_i2c_init() {
static const float digipot_motor_current[] PROGMEM = DIGIPOT_I2C_MOTOR_CURRENTS;
void DigipotI2C::init() {
LOOP_L_N(i, DIGIPOT_I2C_NUM_CHANNELS) pots[i].i2c_init();
LOOP_L_N(i, DIGIPOT_I2C_NUM_CHANNELS)
pots[i].i2c_init();
// setup initial currents as defined in Configuration_adv.h
// Init currents according to Configuration_adv.h
static const float digipot_motor_current[] PROGMEM =
#if ENABLED(DIGIPOT_USE_RAW_VALUES)
DIGIPOT_MOTOR_CURRENT
#else
DIGIPOT_I2C_MOTOR_CURRENTS
#endif
;
LOOP_L_N(i, COUNT(digipot_motor_current))
digipot_i2c_set_current(i, pgm_read_float(&digipot_motor_current[i]));
set_current(i, pgm_read_float(&digipot_motor_current[i]));
}
#endif // DIGIPOT_I2C && DIGIPOT_MCP4018
DigipotI2C digipot_i2c;
#endif // DIGIPOT_MCP4018

51
Marlin/src/feature/digipot/digipot_mcp4451.cpp Executable file → Normal file
View File

@@ -16,15 +16,17 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "../../inc/MarlinConfig.h"
#if ENABLED(DIGIPOT_I2C) && DISABLED(DIGIPOT_MCP4018)
#if ENABLED(DIGIPOT_MCP4451)
#include "Stream.h"
#include "digipot.h"
#include <Stream.h>
#include <Wire.h>
#if MB(MKS_SBASE)
@@ -33,18 +35,21 @@
// Settings for the I2C based DIGIPOT (MCP4451) on Azteeg X3 Pro
#if MB(5DPRINT)
#define DIGIPOT_I2C_FACTOR 117.96
#define DIGIPOT_I2C_MAX_CURRENT 1.736
#define DIGIPOT_I2C_FACTOR 117.96f
#define DIGIPOT_I2C_MAX_CURRENT 1.736f
#elif MB(AZTEEG_X5_MINI, AZTEEG_X5_MINI_WIFI)
#define DIGIPOT_I2C_FACTOR 113.5
#define DIGIPOT_I2C_MAX_CURRENT 2.0
#define DIGIPOT_I2C_FACTOR 113.5f
#define DIGIPOT_I2C_MAX_CURRENT 2.0f
#elif MB(AZTEEG_X5_GT)
#define DIGIPOT_I2C_FACTOR 51.0f
#define DIGIPOT_I2C_MAX_CURRENT 3.0f
#else
#define DIGIPOT_I2C_FACTOR 106.7
#define DIGIPOT_I2C_MAX_CURRENT 2.5
#define DIGIPOT_I2C_FACTOR 106.7f
#define DIGIPOT_I2C_MAX_CURRENT 2.5f
#endif
static byte current_to_wiper(const float current) {
return byte(CEIL(float((DIGIPOT_I2C_FACTOR * current))));
return byte(TERN(DIGIPOT_USE_RAW_VALUES, current, CEIL(DIGIPOT_I2C_FACTOR * current)));
}
static void digipot_i2c_send(const byte addr, const byte a, const byte b) {
@@ -61,9 +66,9 @@ static void digipot_i2c_send(const byte addr, const byte a, const byte b) {
}
// This is for the MCP4451 I2C based digipot
void digipot_i2c_set_current(const uint8_t channel, const float current) {
// these addresses are specific to Azteeg X3 Pro, can be set to others,
// In this case first digipot is at address A0=0, A1= 0, second one is at A0=0, A1= 1
void DigipotI2C::set_current(const uint8_t channel, const float current) {
// These addresses are specific to Azteeg X3 Pro, can be set to others.
// In this case first digipot is at address A0=0, A1=0, second one is at A0=0, A1=1
const byte addr = channel < 4 ? DIGIPOT_I2C_ADDRESS_A : DIGIPOT_I2C_ADDRESS_B; // channel 0-3 vs 4-7
// Initial setup
@@ -75,16 +80,24 @@ void digipot_i2c_set_current(const uint8_t channel, const float current) {
digipot_i2c_send(addr, addresses[channel & 0x3], current_to_wiper(_MIN(float(_MAX(current, 0)), DIGIPOT_I2C_MAX_CURRENT)));
}
void digipot_i2c_init() {
void DigipotI2C::init() {
#if MB(MKS_SBASE)
configure_i2c(16); // Setting clock_option to 16 ensure the I2C bus is initialized at 400kHz
configure_i2c(16); // Set clock_option to 16 ensure I2C is initialized at 400kHz
#else
Wire.begin();
#endif
// setup initial currents as defined in Configuration_adv.h
static const float digipot_motor_current[] PROGMEM = DIGIPOT_I2C_MOTOR_CURRENTS;
// Set up initial currents as defined in Configuration_adv.h
static const float digipot_motor_current[] PROGMEM =
#if ENABLED(DIGIPOT_USE_RAW_VALUES)
DIGIPOT_MOTOR_CURRENT
#else
DIGIPOT_I2C_MOTOR_CURRENTS
#endif
;
LOOP_L_N(i, COUNT(digipot_motor_current))
digipot_i2c_set_current(i, pgm_read_float(&digipot_motor_current[i]));
set_current(i, pgm_read_float(&digipot_motor_current[i]));
}
#endif // DIGIPOT_I2C
DigipotI2C digipot_i2c;
#endif // DIGIPOT_MCP4451

View File

@@ -0,0 +1,263 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#include "../inc/MarlinConfigPre.h"
#if ENABLED(DIRECT_STEPPING)
#include "direct_stepping.h"
#include "../MarlinCore.h"
#define CHECK_PAGE(I, R) do{ \
if (I >= sizeof(page_states) / sizeof(page_states[0])) { \
fatal_error = true; \
return R; \
} \
}while(0)
#define CHECK_PAGE_STATE(I, R, S) do { \
CHECK_PAGE(I, R); \
if (page_states[I] != S) { \
fatal_error = true; \
return R; \
} \
}while(0)
namespace DirectStepping {
template<typename Cfg>
State SerialPageManager<Cfg>::state;
template<typename Cfg>
volatile bool SerialPageManager<Cfg>::fatal_error;
template<typename Cfg>
volatile PageState SerialPageManager<Cfg>::page_states[Cfg::NUM_PAGES];
template<typename Cfg>
volatile bool SerialPageManager<Cfg>::page_states_dirty;
template<typename Cfg>
uint8_t SerialPageManager<Cfg>::pages[Cfg::NUM_PAGES][Cfg::PAGE_SIZE];
template<typename Cfg>
uint8_t SerialPageManager<Cfg>::checksum;
template<typename Cfg>
typename Cfg::write_byte_idx_t SerialPageManager<Cfg>::write_byte_idx;
template<typename Cfg>
typename Cfg::page_idx_t SerialPageManager<Cfg>::write_page_idx;
template<typename Cfg>
typename Cfg::write_byte_idx_t SerialPageManager<Cfg>::write_page_size;
template <typename Cfg>
void SerialPageManager<Cfg>::init() {
for (int i = 0 ; i < Cfg::NUM_PAGES ; i++)
page_states[i] = PageState::FREE;
fatal_error = false;
state = State::NEWLINE;
page_states_dirty = false;
SERIAL_ECHOLNPGM("pages_ready");
}
template<typename Cfg>
FORCE_INLINE bool SerialPageManager<Cfg>::maybe_store_rxd_char(uint8_t c) {
switch (state) {
default:
case State::MONITOR:
switch (c) {
case '\n':
case '\r':
state = State::NEWLINE;
default:
return false;
}
case State::NEWLINE:
switch (c) {
case Cfg::CONTROL_CHAR:
state = State::ADDRESS;
return true;
case '\n':
case '\r':
state = State::NEWLINE;
return false;
default:
state = State::MONITOR;
return false;
}
case State::ADDRESS:
//TODO: 16 bit address, State::ADDRESS2
write_page_idx = c;
write_byte_idx = 0;
checksum = 0;
CHECK_PAGE(write_page_idx, true);
if (page_states[write_page_idx] == PageState::FAIL) {
// Special case for fail
state = State::UNFAIL;
return true;
}
set_page_state(write_page_idx, PageState::WRITING);
state = Cfg::DIRECTIONAL ? State::COLLECT : State::SIZE;
return true;
case State::SIZE:
// Zero means full page size
write_page_size = c;
state = State::COLLECT;
return true;
case State::COLLECT:
pages[write_page_idx][write_byte_idx++] = c;
checksum ^= c;
// check if still collecting
if (Cfg::PAGE_SIZE == 256) {
// special case for 8-bit, check if rolled back to 0
if (Cfg::DIRECTIONAL || !write_page_size) { // full 256 bytes
if (write_byte_idx) return true;
} else {
if (write_byte_idx < write_page_size) return true;
}
} else if (Cfg::DIRECTIONAL) {
if (write_byte_idx != Cfg::PAGE_SIZE) return true;
} else {
if (write_byte_idx < write_page_size) return true;
}
state = State::CHECKSUM;
return true;
case State::CHECKSUM: {
const PageState page_state = (checksum == c) ? PageState::OK : PageState::FAIL;
set_page_state(write_page_idx, page_state);
state = State::MONITOR;
return true;
}
case State::UNFAIL:
if (c == 0) {
set_page_state(write_page_idx, PageState::FREE);
} else {
fatal_error = true;
}
state = State::MONITOR;
return true;
}
}
template <typename Cfg>
void SerialPageManager<Cfg>::write_responses() {
if (fatal_error) {
kill(GET_TEXT(MSG_BAD_PAGE));
return;
}
if (!page_states_dirty) return;
page_states_dirty = false;
SERIAL_CHAR(Cfg::CONTROL_CHAR);
constexpr int state_bits = 2;
constexpr int n_bytes = Cfg::NUM_PAGES >> state_bits;
volatile uint8_t bits_b[n_bytes] = { 0 };
for (page_idx_t i = 0 ; i < Cfg::NUM_PAGES ; i++) {
bits_b[i >> state_bits] |= page_states[i] << ((i * state_bits) & 0x7);
}
uint8_t crc = 0;
for (uint8_t i = 0 ; i < n_bytes ; i++) {
crc ^= bits_b[i];
SERIAL_CHAR(bits_b[i]);
}
SERIAL_CHAR(crc);
SERIAL_EOL();
}
template <typename Cfg>
FORCE_INLINE void SerialPageManager<Cfg>::set_page_state(const page_idx_t page_idx, const PageState page_state) {
CHECK_PAGE(page_idx,);
page_states[page_idx] = page_state;
page_states_dirty = true;
}
template <>
FORCE_INLINE uint8_t *PageManager::get_page(const page_idx_t page_idx) {
CHECK_PAGE(page_idx, nullptr);
return pages[page_idx];
}
template <>
FORCE_INLINE void PageManager::free_page(const page_idx_t page_idx) {
set_page_state(page_idx, PageState::FREE);
}
};
DirectStepping::PageManager page_manager;
const uint8_t segment_table[DirectStepping::Config::NUM_SEGMENTS][DirectStepping::Config::SEGMENT_STEPS] PROGMEM = {
#if STEPPER_PAGE_FORMAT == SP_4x4D_128
{ 1, 1, 1, 1, 1, 1, 1 }, // 0 = -7
{ 1, 1, 1, 0, 1, 1, 1 }, // 1 = -6
{ 1, 1, 1, 0, 1, 0, 1 }, // 2 = -5
{ 1, 1, 0, 1, 0, 1, 0 }, // 3 = -4
{ 1, 1, 0, 0, 1, 0, 0 }, // 4 = -3
{ 0, 0, 1, 0, 0, 0, 1 }, // 5 = -2
{ 0, 0, 0, 1, 0, 0, 0 }, // 6 = -1
{ 0, 0, 0, 0, 0, 0, 0 }, // 7 = 0
{ 0, 0, 0, 1, 0, 0, 0 }, // 8 = 1
{ 0, 0, 1, 0, 0, 0, 1 }, // 9 = 2
{ 1, 1, 0, 0, 1, 0, 0 }, // 10 = 3
{ 1, 1, 0, 1, 0, 1, 0 }, // 11 = 4
{ 1, 1, 1, 0, 1, 0, 1 }, // 12 = 5
{ 1, 1, 1, 0, 1, 1, 1 }, // 13 = 6
{ 1, 1, 1, 1, 1, 1, 1 }, // 14 = 7
{ 0 }
#elif STEPPER_PAGE_FORMAT == SP_4x2_256
{ 0, 0, 0 }, // 0
{ 0, 1, 0 }, // 1
{ 1, 0, 1 }, // 2
{ 1, 1, 1 }, // 3
#elif STEPPER_PAGE_FORMAT == SP_4x1_512
{0} // Uncompressed format, table not used
#endif
};
#endif // DIRECT_STEPPING

View File

@@ -0,0 +1,133 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "../inc/MarlinConfig.h"
namespace DirectStepping {
enum State : char {
MONITOR, NEWLINE, ADDRESS, SIZE, COLLECT, CHECKSUM, UNFAIL
};
enum PageState : uint8_t {
FREE, WRITING, OK, FAIL
};
// Static state used for stepping through direct stepping pages
struct page_step_state_t {
// Current page
uint8_t *page;
// Current segment
uint16_t segment_idx;
// Current steps within segment
uint8_t segment_steps;
// Segment delta
xyze_uint8_t sd;
// Block delta
xyze_int_t bd;
};
template<typename Cfg>
class SerialPageManager {
public:
typedef typename Cfg::page_idx_t page_idx_t;
static bool maybe_store_rxd_char(uint8_t c);
static void write_responses();
// common methods for page managers
static void init();
static uint8_t *get_page(const page_idx_t page_idx);
static void free_page(const page_idx_t page_idx);
protected:
typedef typename Cfg::write_byte_idx_t write_byte_idx_t;
static State state;
static volatile bool fatal_error;
static volatile PageState page_states[Cfg::NUM_PAGES];
static volatile bool page_states_dirty;
static uint8_t pages[Cfg::NUM_PAGES][Cfg::PAGE_SIZE];
static uint8_t checksum;
static write_byte_idx_t write_byte_idx;
static page_idx_t write_page_idx;
static write_byte_idx_t write_page_size;
static void set_page_state(const page_idx_t page_idx, const PageState page_state);
};
template<bool b, typename T, typename F> struct TypeSelector { typedef T type;} ;
template<typename T, typename F> struct TypeSelector<false, T, F> { typedef F type; };
template <int num_pages, int num_axes, int bits_segment, bool dir, int segments>
struct config_t {
static constexpr char CONTROL_CHAR = '!';
static constexpr int NUM_PAGES = num_pages;
static constexpr int NUM_AXES = num_axes;
static constexpr int BITS_SEGMENT = bits_segment;
static constexpr int DIRECTIONAL = dir ? 1 : 0;
static constexpr int SEGMENTS = segments;
static constexpr int NUM_SEGMENTS = _BV(BITS_SEGMENT);
static constexpr int SEGMENT_STEPS = _BV(BITS_SEGMENT - DIRECTIONAL) - 1;
static constexpr int TOTAL_STEPS = SEGMENT_STEPS * SEGMENTS;
static constexpr int PAGE_SIZE = (NUM_AXES * BITS_SEGMENT * SEGMENTS) / 8;
typedef typename TypeSelector<(PAGE_SIZE>256), uint16_t, uint8_t>::type write_byte_idx_t;
typedef typename TypeSelector<(NUM_PAGES>256), uint16_t, uint8_t>::type page_idx_t;
};
template <uint8_t num_pages>
using SP_4x4D_128 = config_t<num_pages, 4, 4, true, 128>;
template <uint8_t num_pages>
using SP_4x2_256 = config_t<num_pages, 4, 2, false, 256>;
template <uint8_t num_pages>
using SP_4x1_512 = config_t<num_pages, 4, 1, false, 512>;
// configured types
typedef STEPPER_PAGE_FORMAT<STEPPER_PAGES> Config;
template class PAGE_MANAGER<Config>;
typedef PAGE_MANAGER<Config> PageManager;
};
#define SP_4x4D_128 1
//#define SP_4x4_128 2
//#define SP_4x2D_256 3
#define SP_4x2_256 4
#define SP_4x1_512 5
typedef typename DirectStepping::Config::page_idx_t page_idx_t;
// TODO: use config
typedef DirectStepping::page_step_state_t page_step_state_t;
extern const uint8_t segment_table[DirectStepping::Config::NUM_SEGMENTS][DirectStepping::Config::SEGMENT_STEPS];
extern DirectStepping::PageManager page_manager;

5
Marlin/src/feature/e_parser.cpp Executable file → Normal file
View File

@@ -16,12 +16,12 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
/**
* emergency_parser.cpp - Intercept special commands directly in the serial stream
* e_parser.cpp - Intercept special commands directly in the serial stream
*/
#include "../inc/MarlinConfigPre.h"
@@ -32,6 +32,7 @@
// Static data members
bool EmergencyParser::killed_by_M112, // = false
EmergencyParser::quickstop_by_M410,
EmergencyParser::enabled;
#if ENABLED(HOST_PROMPT_SUPPORT)

170
Marlin/src/feature/e_parser.h Executable file → Normal file
View File

@@ -16,13 +16,13 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
/**
* emergency_parser.h - Intercept special commands directly in the serial stream
* e_parser.h - Intercept special commands directly in the serial stream
*/
#include "../inc/MarlinConfigPre.h"
@@ -33,36 +33,46 @@
// External references
extern bool wait_for_user, wait_for_heatup;
void quickstop_stepper();
#if ENABLED(REALTIME_REPORTING_COMMANDS)
// From motion.h, which cannot be included here
void report_current_position_moving();
void quickpause_stepper();
void quickresume_stepper();
#endif
void HAL_reboot();
class EmergencyParser {
public:
// Currently looking for: M108, M112, M410, M876
enum State : char {
// Currently looking for: M108, M112, M410, M876 S[0-9], S000, P000, R000
enum State : uint8_t {
EP_RESET,
EP_N,
EP_M,
EP_M1,
EP_M10,
EP_M108,
EP_M11,
EP_M112,
EP_M4,
EP_M41,
EP_M410,
EP_M10, EP_M108,
EP_M11, EP_M112,
EP_M4, EP_M41, EP_M410,
#if ENABLED(HOST_PROMPT_SUPPORT)
EP_M8,
EP_M87,
EP_M876,
EP_M876S,
EP_M876SN,
EP_M8, EP_M87, EP_M876, EP_M876S, EP_M876SN,
#endif
#if ENABLED(REALTIME_REPORTING_COMMANDS)
EP_S, EP_S0, EP_S00, EP_GRBL_STATUS,
EP_R, EP_R0, EP_R00, EP_GRBL_RESUME,
EP_P, EP_P0, EP_P00, EP_GRBL_PAUSE,
#endif
#if ENABLED(SOFT_RESET_VIA_SERIAL)
EP_ctrl,
EP_K, EP_KI, EP_KIL, EP_KILL,
#endif
EP_IGNORE // to '\n'
};
static bool killed_by_M112;
static bool quickstop_by_M410;
#if ENABLED(HOST_PROMPT_SUPPORT)
static uint8_t M876_reason;
@@ -71,32 +81,63 @@ public:
EmergencyParser() { enable(); }
FORCE_INLINE static void enable() { enabled = true; }
FORCE_INLINE static void disable() { enabled = false; }
FORCE_INLINE static void update(State &state, const uint8_t c) {
#define ISEOL(C) ((C) == '\n' || (C) == '\r')
switch (state) {
case EP_RESET:
switch (c) {
case ' ': case '\n': case '\r': break;
case 'N': state = EP_N; break;
case 'M': state = EP_M; break;
default: state = EP_IGNORE;
case 'N': state = EP_N; break;
case 'M': state = EP_M; break;
#if ENABLED(REALTIME_REPORTING_COMMANDS)
case 'S': state = EP_S; break;
case 'P': state = EP_P; break;
case 'R': state = EP_R; break;
#endif
#if ENABLED(SOFT_RESET_VIA_SERIAL)
case '^': state = EP_ctrl; break;
case 'K': state = EP_K; break;
#endif
default: state = EP_IGNORE;
}
break;
case EP_N:
switch (c) {
case '0': case '1': case '2':
case '3': case '4': case '5':
case '6': case '7': case '8':
case '9': case '-': case ' ': break;
case 'M': state = EP_M; break;
default: state = EP_IGNORE;
case '0' ... '9':
case '-': case ' ': break;
case 'M': state = EP_M; break;
#if ENABLED(REALTIME_REPORTING_COMMANDS)
case 'S': state = EP_S; break;
case 'P': state = EP_P; break;
case 'R': state = EP_R; break;
#endif
default: state = EP_IGNORE;
}
break;
#if ENABLED(REALTIME_REPORTING_COMMANDS)
case EP_S: state = (c == '0') ? EP_S0 : EP_IGNORE; break;
case EP_S0: state = (c == '0') ? EP_S00 : EP_IGNORE; break;
case EP_S00: state = (c == '0') ? EP_GRBL_STATUS : EP_IGNORE; break;
case EP_R: state = (c == '0') ? EP_R0 : EP_IGNORE; break;
case EP_R0: state = (c == '0') ? EP_R00 : EP_IGNORE; break;
case EP_R00: state = (c == '0') ? EP_GRBL_RESUME : EP_IGNORE; break;
case EP_P: state = (c == '0') ? EP_P0 : EP_IGNORE; break;
case EP_P0: state = (c == '0') ? EP_P00 : EP_IGNORE; break;
case EP_P00: state = (c == '0') ? EP_GRBL_PAUSE : EP_IGNORE; break;
#endif
#if ENABLED(SOFT_RESET_VIA_SERIAL)
case EP_ctrl: state = (c == 'X') ? EP_KILL : EP_IGNORE; break;
case EP_K: state = (c == 'I') ? EP_KI : EP_IGNORE; break;
case EP_KI: state = (c == 'L') ? EP_KIL : EP_IGNORE; break;
case EP_KIL: state = (c == 'L') ? EP_KILL : EP_IGNORE; break;
#endif
case EP_M:
switch (c) {
case ' ': break;
@@ -117,51 +158,34 @@ public:
}
break;
case EP_M10:
state = (c == '8') ? EP_M108 : EP_IGNORE;
break;
case EP_M11:
state = (c == '2') ? EP_M112 : EP_IGNORE;
break;
case EP_M4:
state = (c == '1') ? EP_M41 : EP_IGNORE;
break;
case EP_M41:
state = (c == '0') ? EP_M410 : EP_IGNORE;
break;
case EP_M10: state = (c == '8') ? EP_M108 : EP_IGNORE; break;
case EP_M11: state = (c == '2') ? EP_M112 : EP_IGNORE; break;
case EP_M4: state = (c == '1') ? EP_M41 : EP_IGNORE; break;
case EP_M41: state = (c == '0') ? EP_M410 : EP_IGNORE; break;
#if ENABLED(HOST_PROMPT_SUPPORT)
case EP_M8:
state = (c == '7') ? EP_M87 : EP_IGNORE;
break;
case EP_M87:
state = (c == '6') ? EP_M876 : EP_IGNORE;
break;
case EP_M8: state = (c == '7') ? EP_M87 : EP_IGNORE; break;
case EP_M87: state = (c == '6') ? EP_M876 : EP_IGNORE; break;
case EP_M876:
switch (c) {
case ' ': break;
case 'S': state = EP_M876S; break;
default: state = EP_IGNORE; break;
}
break;
case EP_M876:
switch (c) {
case ' ': break;
case 'S': state = EP_M876S; break;
default: state = EP_IGNORE; break;
}
break;
case EP_M876S:
switch (c) {
case ' ': break;
case '0' ... '9':
state = EP_M876SN;
M876_reason = uint8_t(c - '0');
break;
}
break;
case EP_M876S:
switch (c) {
case ' ': break;
case '0': case '1': case '2':
case '3': case '4': case '5':
case '6': case '7': case '8':
case '9':
state = EP_M876SN;
M876_reason = (uint8_t)(c - '0');
break;
}
break;
#endif
case EP_IGNORE:
@@ -173,10 +197,18 @@ public:
if (enabled) switch (state) {
case EP_M108: wait_for_user = wait_for_heatup = false; break;
case EP_M112: killed_by_M112 = true; break;
case EP_M410: quickstop_stepper(); break;
case EP_M410: quickstop_by_M410 = true; break;
#if ENABLED(HOST_PROMPT_SUPPORT)
case EP_M876SN: host_response_handler(M876_reason); break;
#endif
#if ENABLED(REALTIME_REPORTING_COMMANDS)
case EP_GRBL_STATUS: report_current_position_moving(); break;
case EP_GRBL_PAUSE: quickpause_stepper(); break;
case EP_GRBL_RESUME: quickresume_stepper(); break;
#endif
#if ENABLED(SOFT_RESET_VIA_SERIAL)
case EP_KILL: HAL_reboot(); break;
#endif
default: break;
}
state = EP_RESET;

190
Marlin/src/feature/encoder_i2c.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -34,7 +34,6 @@
#include "encoder_i2c.h"
#include "../module/temperature.h"
#include "../module/stepper.h"
#include "../gcode/parser.h"
@@ -42,13 +41,15 @@
#include <Wire.h>
I2CPositionEncodersMgr I2CPEM;
void I2CPositionEncoder::init(const uint8_t address, const AxisEnum axis) {
encoderAxis = axis;
i2cAddress = address;
initialized++;
initialized = true;
SERIAL_ECHOLNPAIR("Setting up encoder on ", axis_codes[encoderAxis], " axis, addr = ", address);
SERIAL_ECHOLNPGM("Setting up encoder on ", AS_CHAR(axis_codes[encoderAxis]), " axis, addr = ", address);
position = get_position();
}
@@ -66,7 +67,7 @@ void I2CPositionEncoder::update() {
/*
if (trusted) { //commented out as part of the note below
trusted = false;
SERIAL_ECHOLMPAIR("Fault detected on ", axis_codes[encoderAxis], " axis encoder. Disengaging error correction until module is trusted again.");
SERIAL_ECHOLNPGM("Fault detected on ", AS_CHAR(axis_codes[encoderAxis]), " axis encoder. Disengaging error correction until module is trusted again.");
}
*/
return;
@@ -85,15 +86,15 @@ void I2CPositionEncoder::update() {
* the encoder would be re-enabled.
*/
/*
#if 0
// If the magnetic strength has been good for a certain time, start trusting the module again
if (millis() - lastErrorTime > I2CPE_TIME_TRUSTED) {
trusted = true;
SERIAL_ECHOLNPAIR("Untrusted encoder module on ", axis_codes[encoderAxis], " axis has been fault-free for set duration, reinstating error correction.");
SERIAL_ECHOLNPGM("Untrusted encoder module on ", AS_CHAR(axis_codes[encoderAxis]), " axis has been fault-free for set duration, reinstating error correction.");
//the encoder likely lost its place when the error occured, so we'll reset and use the printer's
//the encoder likely lost its place when the error occurred, so we'll reset and use the printer's
//idea of where it the axis is to re-initialize
const float pos = planner.get_axis_position_mm(encoderAxis);
int32_t positionInTicks = pos * get_ticks_unit();
@@ -102,16 +103,16 @@ void I2CPositionEncoder::update() {
zeroOffset -= (positionInTicks - get_position());
#ifdef I2CPE_DEBUG
SERIAL_ECHOLNPAIR("Current position is ", pos);
SERIAL_ECHOLNPAIR("Position in encoder ticks is ", positionInTicks);
SERIAL_ECHOLNPAIR("New zero-offset of ", zeroOffset);
SERIAL_ECHOPAIR("New position reads as ", get_position());
SERIAL_ECHOLNPGM("Current position is ", pos);
SERIAL_ECHOLNPGM("Position in encoder ticks is ", positionInTicks);
SERIAL_ECHOLNPGM("New zero-offset of ", zeroOffset);
SERIAL_ECHOPGM("New position reads as ", get_position());
SERIAL_CHAR('(');
SERIAL_ECHO(mm_from_count(get_position()));
SERIAL_DECIMAL(mm_from_count(get_position()));
SERIAL_ECHOLNPGM(")");
#endif
}
*/
#endif
return;
}
@@ -148,12 +149,12 @@ void I2CPositionEncoder::update() {
const int32_t error = get_axis_error_steps(false);
#endif
//SERIAL_ECHOLNPAIR("Axis error steps: ", error);
//SERIAL_ECHOLNPGM("Axis error steps: ", error);
#ifdef I2CPE_ERR_THRESH_ABORT
if (ABS(error) > I2CPE_ERR_THRESH_ABORT * planner.settings.axis_steps_per_mm[encoderAxis]) {
//kill(PSTR("Significant Error"));
SERIAL_ECHOLNPAIR("Axis error over threshold, aborting!", error);
SERIAL_ECHOLNPGM("Axis error over threshold, aborting!", error);
safe_delay(5000);
}
#endif
@@ -171,8 +172,8 @@ void I2CPositionEncoder::update() {
float sumP = 0;
LOOP_L_N(i, I2CPE_ERR_PRST_ARRAY_SIZE) sumP += errPrst[i];
const int32_t errorP = int32_t(sumP * RECIPROCAL(I2CPE_ERR_PRST_ARRAY_SIZE));
SERIAL_ECHO(axis_codes[encoderAxis]);
SERIAL_ECHOLNPAIR(" : CORRECT ERR ", errorP * planner.steps_to_mm[encoderAxis], "mm");
SERIAL_CHAR(axis_codes[encoderAxis]);
SERIAL_ECHOLNPGM(" : CORRECT ERR ", errorP * planner.mm_per_step[encoderAxis], "mm");
babystep.add_steps(encoderAxis, -LROUND(errorP));
errPrstIdx = 0;
}
@@ -191,8 +192,8 @@ void I2CPositionEncoder::update() {
if (ABS(error) > I2CPE_ERR_CNT_THRESH * planner.settings.axis_steps_per_mm[encoderAxis]) {
const millis_t ms = millis();
if (ELAPSED(ms, nextErrorCountTime)) {
SERIAL_ECHO(axis_codes[encoderAxis]);
SERIAL_ECHOLNPAIR(" : LARGE ERR ", int(error), "; diffSum=", diffSum);
SERIAL_CHAR(axis_codes[encoderAxis]);
SERIAL_ECHOLNPGM(" : LARGE ERR ", error, "; diffSum=", diffSum);
errorCount++;
nextErrorCountTime = ms + I2CPE_ERR_CNT_DEBOUNCE_MS;
}
@@ -208,12 +209,11 @@ void I2CPositionEncoder::set_homed() {
delay(10);
zeroOffset = get_raw_count();
homed++;
trusted++;
homed = trusted = true;
#ifdef I2CPE_DEBUG
SERIAL_ECHO(axis_codes[encoderAxis]);
SERIAL_ECHOLNPAIR(" axis encoder homed, offset of ", zeroOffset, " ticks.");
SERIAL_CHAR(axis_codes[encoderAxis]);
SERIAL_ECHOLNPGM(" axis encoder homed, offset of ", zeroOffset, " ticks.");
#endif
}
}
@@ -223,7 +223,7 @@ void I2CPositionEncoder::set_unhomed() {
homed = trusted = false;
#ifdef I2CPE_DEBUG
SERIAL_ECHO(axis_codes[encoderAxis]);
SERIAL_CHAR(axis_codes[encoderAxis]);
SERIAL_ECHOLNPGM(" axis encoder unhomed.");
#endif
}
@@ -231,7 +231,7 @@ void I2CPositionEncoder::set_unhomed() {
bool I2CPositionEncoder::passes_test(const bool report) {
if (report) {
if (H != I2CPE_MAG_SIG_GOOD) SERIAL_ECHOPGM("Warning. ");
SERIAL_ECHO(axis_codes[encoderAxis]);
SERIAL_CHAR(axis_codes[encoderAxis]);
serial_ternary(H == I2CPE_MAG_SIG_BAD, PSTR(" axis "), PSTR("magnetic strip "), PSTR("encoder "));
switch (H) {
case I2CPE_MAG_SIG_GOOD:
@@ -252,8 +252,8 @@ float I2CPositionEncoder::get_axis_error_mm(const bool report) {
error = ABS(diff) > 10000 ? 0 : diff; // Huge error is a bad reading
if (report) {
SERIAL_ECHO(axis_codes[encoderAxis]);
SERIAL_ECHOLNPAIR(" axis target=", target, "mm; actual=", actual, "mm; err=", error, "mm");
SERIAL_CHAR(axis_codes[encoderAxis]);
SERIAL_ECHOLNPGM(" axis target=", target, "mm; actual=", actual, "mm; err=", error, "mm");
}
return error;
@@ -262,7 +262,7 @@ float I2CPositionEncoder::get_axis_error_mm(const bool report) {
int32_t I2CPositionEncoder::get_axis_error_steps(const bool report) {
if (!active) {
if (report) {
SERIAL_ECHO(axis_codes[encoderAxis]);
SERIAL_CHAR(axis_codes[encoderAxis]);
SERIAL_ECHOLNPGM(" axis encoder not active!");
}
return 0;
@@ -287,8 +287,8 @@ int32_t I2CPositionEncoder::get_axis_error_steps(const bool report) {
errorPrev = error;
if (report) {
SERIAL_ECHO(axis_codes[encoderAxis]);
SERIAL_ECHOLNPAIR(" axis target=", target, "; actual=", encoderCountInStepperTicksScaled, "; err=", error);
SERIAL_CHAR(axis_codes[encoderAxis]);
SERIAL_ECHOLNPGM(" axis target=", target, "; actual=", encoderCountInStepperTicksScaled, "; err=", error);
}
if (suppressOutput) {
@@ -305,7 +305,7 @@ int32_t I2CPositionEncoder::get_raw_count() {
encoderCount.val = 0x00;
if (Wire.requestFrom((int)i2cAddress, 3) != 3) {
if (Wire.requestFrom(I2C_ADDRESS(i2cAddress), uint8_t(3)) != 3) {
//houston, we have a problem...
H = I2CPE_MAG_SIG_NF;
return 0;
@@ -327,17 +327,17 @@ int32_t I2CPositionEncoder::get_raw_count() {
}
bool I2CPositionEncoder::test_axis() {
//only works on XYZ cartesian machines for the time being
// Only works on XYZ Cartesian machines for the time being
if (!(encoderAxis == X_AXIS || encoderAxis == Y_AXIS || encoderAxis == Z_AXIS)) return false;
const float startPosition = soft_endstop.min[encoderAxis] + 10,
endPosition = soft_endstop.max[encoderAxis] - 10;
const feedRate_t fr_mm_s = FLOOR(MMM_TO_MMS((encoderAxis == Z_AXIS) ? HOMING_FEEDRATE_Z : HOMING_FEEDRATE_XY));
const feedRate_t fr_mm_s = FLOOR(homing_feedrate(encoderAxis));
ec = false;
xyze_pos_t startCoord, endCoord;
LOOP_XYZ(a) {
LOOP_LINEAR_AXES(a) {
startCoord[a] = planner.get_axis_position_mm((AxisEnum)a);
endCoord[a] = planner.get_axis_position_mm((AxisEnum)a);
}
@@ -345,9 +345,12 @@ bool I2CPositionEncoder::test_axis() {
endCoord[encoderAxis] = endPosition;
planner.synchronize();
startCoord.e = planner.get_axis_position_mm(E_AXIS);
planner.buffer_line(startCoord, fr_mm_s, 0);
planner.synchronize();
#if HAS_EXTRUDERS
startCoord.e = planner.get_axis_position_mm(E_AXIS);
planner.buffer_line(startCoord, fr_mm_s, 0);
planner.synchronize();
#endif
// if the module isn't currently trusted, wait until it is (or until it should be if things are working)
if (!trusted) {
@@ -357,7 +360,7 @@ bool I2CPositionEncoder::test_axis() {
}
if (trusted) { // if trusted, commence test
endCoord.e = planner.get_axis_position_mm(E_AXIS);
TERN_(HAS_EXTRUDERS, endCoord.e = planner.get_axis_position_mm(E_AXIS));
planner.buffer_line(endCoord, fr_mm_s, 0);
planner.synchronize();
}
@@ -382,7 +385,7 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) {
int32_t startCount, stopCount;
const feedRate_t fr_mm_s = MMM_TO_MMS((encoderAxis == Z_AXIS) ? HOMING_FEEDRATE_Z : HOMING_FEEDRATE_XY);
const feedRate_t fr_mm_s = homing_feedrate(encoderAxis);
bool oldec = ec;
ec = false;
@@ -392,7 +395,7 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) {
travelDistance = endDistance - startDistance;
xyze_pos_t startCoord, endCoord;
LOOP_XYZ(a) {
LOOP_LINEAR_AXES(a) {
startCoord[a] = planner.get_axis_position_mm((AxisEnum)a);
endCoord[a] = planner.get_axis_position_mm((AxisEnum)a);
}
@@ -402,7 +405,7 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) {
planner.synchronize();
LOOP_L_N(i, iter) {
startCoord.e = planner.get_axis_position_mm(E_AXIS);
TERN_(HAS_EXTRUDERS, startCoord.e = planner.get_axis_position_mm(E_AXIS));
planner.buffer_line(startCoord, fr_mm_s, 0);
planner.synchronize();
@@ -411,7 +414,7 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) {
//do_blocking_move_to(endCoord);
endCoord.e = planner.get_axis_position_mm(E_AXIS);
TERN_(HAS_EXTRUDERS, endCoord.e = planner.get_axis_position_mm(E_AXIS));
planner.buffer_line(endCoord, fr_mm_s, 0);
planner.synchronize();
@@ -421,15 +424,15 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) {
travelledDistance = mm_from_count(ABS(stopCount - startCount));
SERIAL_ECHOLNPAIR("Attempted travel: ", travelDistance, "mm");
SERIAL_ECHOLNPAIR(" Actual travel: ", travelledDistance, "mm");
SERIAL_ECHOLNPGM("Attempted travel: ", travelDistance, "mm");
SERIAL_ECHOLNPGM(" Actual travel: ", travelledDistance, "mm");
//Calculate new axis steps per unit
old_steps_mm = planner.settings.axis_steps_per_mm[encoderAxis];
new_steps_mm = (old_steps_mm * travelDistance) / travelledDistance;
SERIAL_ECHOLNPAIR("Old steps/mm: ", old_steps_mm);
SERIAL_ECHOLNPAIR("New steps/mm: ", new_steps_mm);
SERIAL_ECHOLNPGM("Old steps/mm: ", old_steps_mm);
SERIAL_ECHOLNPGM("New steps/mm: ", new_steps_mm);
//Save new value
planner.settings.axis_steps_per_mm[encoderAxis] = new_steps_mm;
@@ -446,7 +449,7 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) {
if (iter > 1) {
total /= (float)iter;
SERIAL_ECHOLNPAIR("Average steps/mm: ", total);
SERIAL_ECHOLNPGM("Average steps/mm: ", total);
}
ec = oldec;
@@ -459,9 +462,7 @@ void I2CPositionEncoder::reset() {
Wire.write(I2CPE_RESET_COUNT);
Wire.endTransmission();
#if ENABLED(I2CPE_ERR_ROLLING_AVERAGE)
ZERO(err);
#endif
TERN_(I2CPE_ERR_ROLLING_AVERAGE, ZERO(err));
}
@@ -499,9 +500,7 @@ void I2CPositionEncodersMgr::init() {
encoders[i].set_active(encoders[i].passes_test(true));
#if I2CPE_ENC_1_AXIS == E_AXIS
encoders[i].set_homed();
#endif
TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_1_AXIS == E_AXIS) encoders[i].set_homed());
#endif
#if I2CPE_ENCODER_CNT > 1
@@ -530,9 +529,7 @@ void I2CPositionEncodersMgr::init() {
encoders[i].set_active(encoders[i].passes_test(true));
#if I2CPE_ENC_2_AXIS == E_AXIS
encoders[i].set_homed();
#endif
TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_2_AXIS == E_AXIS) encoders[i].set_homed());
#endif
#if I2CPE_ENCODER_CNT > 2
@@ -559,11 +556,9 @@ void I2CPositionEncodersMgr::init() {
encoders[i].set_ec_threshold(I2CPE_ENC_3_EC_THRESH);
#endif
encoders[i].set_active(encoders[i].passes_test(true));
encoders[i].set_active(encoders[i].passes_test(true));
#if I2CPE_ENC_3_AXIS == E_AXIS
encoders[i].set_homed();
#endif
TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_3_AXIS == E_AXIS) encoders[i].set_homed());
#endif
#if I2CPE_ENCODER_CNT > 3
@@ -592,9 +587,7 @@ void I2CPositionEncodersMgr::init() {
encoders[i].set_active(encoders[i].passes_test(true));
#if I2CPE_ENC_4_AXIS == E_AXIS
encoders[i].set_homed();
#endif
TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_4_AXIS == E_AXIS) encoders[i].set_homed());
#endif
#if I2CPE_ENCODER_CNT > 4
@@ -623,9 +616,7 @@ void I2CPositionEncodersMgr::init() {
encoders[i].set_active(encoders[i].passes_test(true));
#if I2CPE_ENC_5_AXIS == E_AXIS
encoders[i].set_homed();
#endif
TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_5_AXIS == E_AXIS) encoders[i].set_homed());
#endif
#if I2CPE_ENCODER_CNT > 5
@@ -654,9 +645,7 @@ void I2CPositionEncodersMgr::init() {
encoders[i].set_active(encoders[i].passes_test(true));
#if I2CPE_ENC_6_AXIS == E_AXIS
encoders[i].set_homed();
#endif
TERN_(HAS_EXTRUDERS, if (I2CPE_ENC_6_AXIS == E_AXIS) encoders[i].set_homed());
#endif
}
@@ -668,8 +657,7 @@ void I2CPositionEncodersMgr::report_position(const int8_t idx, const bool units,
else {
if (noOffset) {
const int32_t raw_count = encoders[idx].get_raw_count();
SERIAL_ECHO(axis_codes[encoders[idx].get_axis()]);
SERIAL_CHAR(' ');
SERIAL_CHAR(axis_codes[encoders[idx].get_axis()], ' ');
for (uint8_t j = 31; j > 0; j--)
SERIAL_ECHO((bool)(0x00000001 & (raw_count >> j)));
@@ -687,18 +675,18 @@ void I2CPositionEncodersMgr::change_module_address(const uint8_t oldaddr, const
// First check 'new' address is not in use
Wire.beginTransmission(I2C_ADDRESS(newaddr));
if (!Wire.endTransmission()) {
SERIAL_ECHOLNPAIR("?There is already a device with that address on the I2C bus! (", newaddr, ")");
SERIAL_ECHOLNPGM("?There is already a device with that address on the I2C bus! (", newaddr, ")");
return;
}
// Now check that we can find the module on the oldaddr address
Wire.beginTransmission(I2C_ADDRESS(oldaddr));
if (Wire.endTransmission()) {
SERIAL_ECHOLNPAIR("?No module detected at this address! (", oldaddr, ")");
SERIAL_ECHOLNPGM("?No module detected at this address! (", oldaddr, ")");
return;
}
SERIAL_ECHOLNPAIR("Module found at ", oldaddr, ", changing address to ", newaddr);
SERIAL_ECHOLNPGM("Module found at ", oldaddr, ", changing address to ", newaddr);
// Change the modules address
Wire.beginTransmission(I2C_ADDRESS(oldaddr));
@@ -724,7 +712,7 @@ void I2CPositionEncodersMgr::change_module_address(const uint8_t oldaddr, const
// and enable it (it will likely have failed initialization on power-up, before the address change).
const int8_t idx = idx_from_addr(newaddr);
if (idx >= 0 && !encoders[idx].get_active()) {
SERIAL_ECHO(axis_codes[encoders[idx].get_axis()]);
SERIAL_CHAR(axis_codes[encoders[idx].get_axis()]);
SERIAL_ECHOLNPGM(" axis encoder was not detected on printer startup. Trying again.");
encoders[idx].set_active(encoders[idx].passes_test(true));
}
@@ -734,11 +722,11 @@ void I2CPositionEncodersMgr::report_module_firmware(const uint8_t address) {
// First check there is a module
Wire.beginTransmission(I2C_ADDRESS(address));
if (Wire.endTransmission()) {
SERIAL_ECHOLNPAIR("?No module detected at this address! (", address, ")");
SERIAL_ECHOLNPGM("?No module detected at this address! (", address, ")");
return;
}
SERIAL_ECHOLNPAIR("Requesting version info from module at address ", address, ":");
SERIAL_ECHOLNPGM("Requesting version info from module at address ", address, ":");
Wire.beginTransmission(I2C_ADDRESS(address));
Wire.write(I2CPE_SET_REPORT_MODE);
@@ -746,10 +734,10 @@ void I2CPositionEncodersMgr::report_module_firmware(const uint8_t address) {
Wire.endTransmission();
// Read value
if (Wire.requestFrom((int)address, 32)) {
if (Wire.requestFrom(I2C_ADDRESS(address), uint8_t(32))) {
char c;
while (Wire.available() > 0 && (c = (char)Wire.read()) > 0)
SERIAL_ECHO(c);
SERIAL_CHAR(c);
SERIAL_EOL();
}
@@ -785,13 +773,13 @@ int8_t I2CPositionEncodersMgr::parse() {
else if (parser.seenval('I')) {
if (!parser.has_value()) {
SERIAL_ECHOLNPAIR("?I seen, but no index specified! [0-", I2CPE_ENCODER_CNT - 1, "]");
SERIAL_ECHOLNPGM("?I seen, but no index specified! [0-", I2CPE_ENCODER_CNT - 1, "]");
return I2CPE_PARSE_ERR;
};
I2CPE_idx = parser.value_byte();
if (I2CPE_idx >= I2CPE_ENCODER_CNT) {
SERIAL_ECHOLNPAIR("?Index out of range. [0-", I2CPE_ENCODER_CNT - 1, "]");
SERIAL_ECHOLNPGM("?Index out of range. [0-", I2CPE_ENCODER_CNT - 1, "]");
return I2CPE_PARSE_ERR;
}
@@ -818,16 +806,15 @@ int8_t I2CPositionEncodersMgr::parse() {
* Y Report on Y axis encoder, if present.
* Z Report on Z axis encoder, if present.
* E Report on E axis encoder, if present.
*
*/
void I2CPositionEncodersMgr::M860() {
if (parse()) return;
const bool hasU = parser.seen('U'), hasO = parser.seen('O');
const bool hasU = parser.seen_test('U'), hasO = parser.seen_test('O');
if (I2CPE_idx == 0xFF) {
LOOP_XYZE(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen_test(axis_codes[i])) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) report_position(idx, hasU, hasO);
}
@@ -848,13 +835,12 @@ void I2CPositionEncodersMgr::M860() {
* Y Report on Y axis encoder, if present.
* Z Report on Z axis encoder, if present.
* E Report on E axis encoder, if present.
*
*/
void I2CPositionEncodersMgr::M861() {
if (parse()) return;
if (I2CPE_idx == 0xFF) {
LOOP_XYZE(i) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) report_status(idx);
@@ -877,13 +863,12 @@ void I2CPositionEncodersMgr::M861() {
* Y Report on Y axis encoder, if present.
* Z Report on Z axis encoder, if present.
* E Report on E axis encoder, if present.
*
*/
void I2CPositionEncodersMgr::M862() {
if (parse()) return;
if (I2CPE_idx == 0xFF) {
LOOP_XYZE(i) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) test_axis(idx);
@@ -907,7 +892,6 @@ void I2CPositionEncodersMgr::M862() {
* Y Report on Y axis encoder, if present.
* Z Report on Z axis encoder, if present.
* E Report on E axis encoder, if present.
*
*/
void I2CPositionEncodersMgr::M863() {
if (parse()) return;
@@ -915,7 +899,7 @@ void I2CPositionEncodersMgr::M863() {
const uint8_t iterations = constrain(parser.byteval('P', 1), 1, 10);
if (I2CPE_idx == 0xFF) {
LOOP_XYZE(i) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) calibrate_steps_mm(idx, iterations);
@@ -963,14 +947,14 @@ void I2CPositionEncodersMgr::M864() {
return;
}
else {
if (parser.seen('X')) newAddress = I2CPE_PRESET_ADDR_X;
else if (parser.seen('Y')) newAddress = I2CPE_PRESET_ADDR_Y;
else if (parser.seen('Z')) newAddress = I2CPE_PRESET_ADDR_Z;
else if (parser.seen('E')) newAddress = I2CPE_PRESET_ADDR_E;
if (parser.seen_test('X')) newAddress = I2CPE_PRESET_ADDR_X;
else if (parser.seen_test('Y')) newAddress = I2CPE_PRESET_ADDR_Y;
else if (parser.seen_test('Z')) newAddress = I2CPE_PRESET_ADDR_Z;
else if (parser.seen_test('E')) newAddress = I2CPE_PRESET_ADDR_E;
else return;
}
SERIAL_ECHOLNPAIR("Changing module at address ", I2CPE_addr, " to address ", newAddress);
SERIAL_ECHOLNPGM("Changing module at address ", I2CPE_addr, " to address ", newAddress);
change_module_address(I2CPE_addr, newAddress);
}
@@ -991,7 +975,7 @@ void I2CPositionEncodersMgr::M865() {
if (parse()) return;
if (!I2CPE_addr) {
LOOP_XYZE(i) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) report_module_firmware(encoders[idx].get_address());
@@ -1019,10 +1003,10 @@ void I2CPositionEncodersMgr::M865() {
void I2CPositionEncodersMgr::M866() {
if (parse()) return;
const bool hasR = parser.seen('R');
const bool hasR = parser.seen_test('R');
if (I2CPE_idx == 0xFF) {
LOOP_XYZE(i) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) {
@@ -1060,7 +1044,7 @@ void I2CPositionEncodersMgr::M867() {
const int8_t onoff = parser.seenval('S') ? parser.value_int() : -1;
if (I2CPE_idx == 0xFF) {
LOOP_XYZE(i) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) {
@@ -1096,7 +1080,7 @@ void I2CPositionEncodersMgr::M868() {
const float newThreshold = parser.seenval('T') ? parser.value_float() : -9999;
if (I2CPE_idx == 0xFF) {
LOOP_XYZE(i) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) {
@@ -1130,7 +1114,7 @@ void I2CPositionEncodersMgr::M869() {
if (parse()) return;
if (I2CPE_idx == 0xFF) {
LOOP_XYZE(i) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) report_error(idx);

16
Marlin/src/feature/encoder_i2c.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -188,7 +188,7 @@ class I2CPositionEncoder {
FORCE_INLINE void set_ec_method(const byte method) { ecMethod = method; }
FORCE_INLINE float get_ec_threshold() { return ecThreshold; }
FORCE_INLINE void set_ec_threshold(const float newThreshold) { ecThreshold = newThreshold; }
FORCE_INLINE void set_ec_threshold(const_float_t newThreshold) { ecThreshold = newThreshold; }
FORCE_INLINE int get_encoder_ticks_mm() {
switch (type) {
@@ -236,7 +236,7 @@ class I2CPositionEncodersMgr {
static void report_status(const int8_t idx) {
CHECK_IDX();
SERIAL_ECHOLNPAIR("Encoder ", idx, ": ");
SERIAL_ECHOLNPGM("Encoder ", idx, ": ");
encoders[idx].get_raw_count();
encoders[idx].passes_test(true);
}
@@ -261,32 +261,32 @@ class I2CPositionEncodersMgr {
static void report_error_count(const int8_t idx, const AxisEnum axis) {
CHECK_IDX();
SERIAL_ECHOLNPAIR("Error count on ", axis_codes[axis], " axis is ", encoders[idx].get_error_count());
SERIAL_ECHOLNPGM("Error count on ", AS_CHAR(axis_codes[axis]), " axis is ", encoders[idx].get_error_count());
}
static void reset_error_count(const int8_t idx, const AxisEnum axis) {
CHECK_IDX();
encoders[idx].set_error_count(0);
SERIAL_ECHOLNPAIR("Error count on ", axis_codes[axis], " axis has been reset.");
SERIAL_ECHOLNPGM("Error count on ", AS_CHAR(axis_codes[axis]), " axis has been reset.");
}
static void enable_ec(const int8_t idx, const bool enabled, const AxisEnum axis) {
CHECK_IDX();
encoders[idx].set_ec_enabled(enabled);
SERIAL_ECHOPAIR("Error correction on ", axis_codes[axis]);
SERIAL_ECHOPGM("Error correction on ", AS_CHAR(axis_codes[axis]));
SERIAL_ECHO_TERNARY(encoders[idx].get_ec_enabled(), " axis is ", "en", "dis", "abled.\n");
}
static void set_ec_threshold(const int8_t idx, const float newThreshold, const AxisEnum axis) {
CHECK_IDX();
encoders[idx].set_ec_threshold(newThreshold);
SERIAL_ECHOLNPAIR("Error correct threshold for ", axis_codes[axis], " axis set to ", FIXFLOAT(newThreshold), "mm.");
SERIAL_ECHOLNPGM("Error correct threshold for ", AS_CHAR(axis_codes[axis]), " axis set to ", newThreshold, "mm.");
}
static void get_ec_threshold(const int8_t idx, const AxisEnum axis) {
CHECK_IDX();
const float threshold = encoders[idx].get_ec_threshold();
SERIAL_ECHOLNPAIR("Error correct threshold for ", axis_codes[axis], " axis is ", FIXFLOAT(threshold), "mm.");
SERIAL_ECHOLNPGM("Error correct threshold for ", AS_CHAR(axis_codes[axis]), " axis is ", threshold, "mm.");
}
static int8_t idx_from_axis(const AxisEnum axis) {

View File

@@ -0,0 +1,175 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#include "../inc/MarlinConfigPre.h"
#if HAS_ETHERNET
#include "ethernet.h"
#include "../core/serial.h"
#define DEBUG_OUT ENABLED(DEBUG_ETHERNET)
#include "../core/debug_out.h"
bool MarlinEthernet::hardware_enabled, // = false
MarlinEthernet::have_telnet_client; // = false
IPAddress MarlinEthernet::ip,
MarlinEthernet::myDns,
MarlinEthernet::gateway,
MarlinEthernet::subnet;
EthernetClient MarlinEthernet::telnetClient; // connected client
MarlinEthernet ethernet;
EthernetServer server(23); // telnet server
enum linkStates { UNLINKED, LINKING, LINKED, CONNECTING, CONNECTED, NO_HARDWARE } linkState;
#ifdef __IMXRT1062__
static void teensyMAC(uint8_t * const mac) {
const uint32_t m1 = HW_OCOTP_MAC1, m2 = HW_OCOTP_MAC0;
mac[0] = m1 >> 8;
mac[1] = m1 >> 0;
mac[2] = m2 >> 24;
mac[3] = m2 >> 16;
mac[4] = m2 >> 8;
mac[5] = m2 >> 0;
}
#else
byte mac[] = MAC_ADDRESS;
#endif
void ethernet_cable_error() { SERIAL_ERROR_MSG("Ethernet cable is not connected."); }
void MarlinEthernet::init() {
if (!hardware_enabled) return;
SERIAL_ECHO_MSG("Starting network...");
// Init the Ethernet device
#ifdef __IMXRT1062__
uint8_t mac[6];
teensyMAC(mac);
#endif
if (!ip) {
Ethernet.begin(mac); // use DHCP
}
else {
if (!gateway) {
gateway = ip;
gateway[3] = 1;
myDns = gateway;
subnet = IPAddress(255,255,255,0);
}
if (!myDns) myDns = gateway;
if (!subnet) subnet = IPAddress(255,255,255,0);
Ethernet.begin(mac, ip, myDns, gateway, subnet);
}
// Check for Ethernet hardware present
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
SERIAL_ERROR_MSG("No Ethernet hardware found.");
linkState = NO_HARDWARE;
return;
}
linkState = UNLINKED;
if (Ethernet.linkStatus() == LinkOFF)
ethernet_cable_error();
}
void MarlinEthernet::check() {
if (!hardware_enabled) return;
switch (linkState) {
case NO_HARDWARE:
break;
case UNLINKED:
if (Ethernet.linkStatus() == LinkOFF) break;
SERIAL_ECHOLNPGM("Ethernet cable connected");
server.begin();
linkState = LINKING;
break;
case LINKING:
if (!Ethernet.localIP()) break;
SERIAL_ECHOPGM("Successfully started telnet server with IP ");
MYSERIAL1.println(Ethernet.localIP());
linkState = LINKED;
break;
case LINKED:
if (Ethernet.linkStatus() == LinkOFF) {
ethernet_cable_error();
linkState = UNLINKED;
break;
}
telnetClient = server.accept();
if (telnetClient) linkState = CONNECTING;
break;
case CONNECTING:
telnetClient.println("Marlin " SHORT_BUILD_VERSION);
#if defined(STRING_DISTRIBUTION_DATE) && defined(STRING_CONFIG_H_AUTHOR)
telnetClient.println(
" Last Updated: " STRING_DISTRIBUTION_DATE
" | Author: " STRING_CONFIG_H_AUTHOR
);
#endif
telnetClient.println(" Compiled: " __DATE__);
SERIAL_ECHOLNPGM("Client connected");
have_telnet_client = true;
linkState = CONNECTED;
break;
case CONNECTED:
if (telnetClient && !telnetClient.connected()) {
SERIAL_ECHOLNPGM("Client disconnected");
telnetClient.stop();
have_telnet_client = false;
linkState = LINKED;
}
if (Ethernet.linkStatus() == LinkOFF) {
ethernet_cable_error();
if (telnetClient) telnetClient.stop();
linkState = UNLINKED;
}
break;
default: break;
}
}
#endif // HAS_ETHERNET

View File

@@ -0,0 +1,39 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#ifdef __IMXRT1062__
#include <NativeEthernet.h>
#endif
// Teensy 4.1 uses internal MAC Address
class MarlinEthernet {
public:
static bool hardware_enabled, have_telnet_client;
static IPAddress ip, myDns, gateway, subnet;
static EthernetClient telnetClient;
static void init();
static void check();
};
extern MarlinEthernet ethernet;

2
Marlin/src/feature/fanmux.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/

6
Marlin/src/feature/fanmux.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -25,5 +25,5 @@
* feature/fanmux.h - Cooling Fan Multiplexer support functions
*/
extern void fanmux_switch(const uint8_t e);
extern void fanmux_init();
void fanmux_switch(const uint8_t e);
void fanmux_init();

2
Marlin/src/feature/filwidth.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/

4
Marlin/src/feature/filwidth.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -78,7 +78,7 @@ public:
static inline void update_measured_mm() { measured_mm = raw_to_mm(); }
// Update ring buffer used to delay filament measurements
static inline void advance_e(const float &e_move) {
static inline void advance_e(const_float_t e_move) {
// Increment counters with the E distance
e_count += e_move;

156
Marlin/src/feature/fwretract.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -36,13 +36,15 @@ FWRetract fwretract; // Single instance - this calls the constructor
#include "../module/planner.h"
#include "../module/stepper.h"
#include "../gcode/gcode.h"
#if ENABLED(RETRACT_SYNC_MIXING)
#include "mixing.h"
#endif
// private:
#if EXTRUDERS > 1
#if HAS_MULTI_EXTRUDER
bool FWRetract::retracted_swap[EXTRUDERS]; // Which extruders are swap-retracted
#endif
@@ -60,9 +62,7 @@ float FWRetract::current_retract[EXTRUDERS], // Retract value used by p
FWRetract::current_hop;
void FWRetract::reset() {
#if ENABLED(FWRETRACT_AUTORETRACT)
autoretract_enabled = false;
#endif
TERN_(FWRETRACT_AUTORETRACT, autoretract_enabled = false);
settings.retract_length = RETRACT_LENGTH;
settings.retract_feedrate_mm_s = RETRACT_FEEDRATE;
settings.retract_zraise = RETRACT_ZRAISE;
@@ -75,9 +75,7 @@ void FWRetract::reset() {
LOOP_L_N(i, EXTRUDERS) {
retracted[i] = false;
#if EXTRUDERS > 1
retracted_swap[i] = false;
#endif
E_TERN_(retracted_swap[i] = false);
current_retract[i] = 0.0;
}
}
@@ -93,16 +91,12 @@ void FWRetract::reset() {
* Note: Auto-retract will apply the set Z hop in addition to any Z hop
* included in the G-code. Use M207 Z0 to to prevent double hop.
*/
void FWRetract::retract(const bool retracting
#if EXTRUDERS > 1
, bool swapping /* =false */
#endif
) {
void FWRetract::retract(const bool retracting E_OPTARG(bool swapping/*=false*/)) {
// Prevent two retracts or recovers in a row
if (retracted[active_extruder] == retracting) return;
// Prevent two swap-retract or recovers in a row
#if EXTRUDERS > 1
#if HAS_MULTI_EXTRUDER
// Allow G10 S1 only after G11
if (swapping && retracted_swap[active_extruder] == retracting) return;
// G11 priority to recover the long retract if activated
@@ -112,28 +106,24 @@ void FWRetract::retract(const bool retracting
#endif
/* // debugging
SERIAL_ECHOLNPAIR(
"retracting ", retracting,
SERIAL_ECHOLNPGM(
"retracting ", AS_DIGIT(retracting),
" swapping ", swapping,
" active extruder ", active_extruder
);
LOOP_L_N(i, EXTRUDERS) {
SERIAL_ECHOLNPAIR("retracted[", i, "] ", retracted[i]);
#if EXTRUDERS > 1
SERIAL_ECHOLNPAIR("retracted_swap[", i, "] ", retracted_swap[i]);
SERIAL_ECHOLNPGM("retracted[", i, "] ", AS_DIGIT(retracted[i]));
#if HAS_MULTI_EXTRUDER
SERIAL_ECHOLNPGM("retracted_swap[", i, "] ", AS_DIGIT(retracted_swap[i]));
#endif
}
SERIAL_ECHOLNPAIR("current_position.z ", current_position.z);
SERIAL_ECHOLNPAIR("current_position.e ", current_position.e);
SERIAL_ECHOLNPAIR("current_hop ", current_hop);
SERIAL_ECHOLNPGM("current_position.z ", current_position.z);
SERIAL_ECHOLNPGM("current_position.e ", current_position.e);
SERIAL_ECHOLNPGM("current_hop ", current_hop);
//*/
const float base_retract = (
(swapping ? settings.swap_retract_length : settings.retract_length)
#if ENABLED(RETRACT_SYNC_MIXING)
* (MIXING_STEPPERS)
#endif
);
const float base_retract = TERN1(RETRACT_SYNC_MIXING, (MIXING_STEPPERS))
* (swapping ? settings.swap_retract_length : settings.retract_length);
// The current position will be the destination for E and Z moves
destination = current_position;
@@ -147,11 +137,8 @@ void FWRetract::retract(const bool retracting
if (retracting) {
// Retract by moving from a faux E position back to the current E position
current_retract[active_extruder] = base_retract;
prepare_internal_move_to_destination( // set current to destination
settings.retract_feedrate_mm_s
#if ENABLED(RETRACT_SYNC_MIXING)
* (MIXING_STEPPERS)
#endif
prepare_internal_move_to_destination( // set current from destination
settings.retract_feedrate_mm_s * TERN1(RETRACT_SYNC_MIXING, (MIXING_STEPPERS))
);
// Is a Z hop set, and has the hop not yet been done?
@@ -177,40 +164,107 @@ void FWRetract::retract(const bool retracting
current_retract[active_extruder] = 0;
const feedRate_t fr_mm_s = (
// Recover E, set_current_to_destination
prepare_internal_move_to_destination(
(swapping ? settings.swap_retract_recover_feedrate_mm_s : settings.retract_recover_feedrate_mm_s)
#if ENABLED(RETRACT_SYNC_MIXING)
* (MIXING_STEPPERS)
#endif
* TERN1(RETRACT_SYNC_MIXING, (MIXING_STEPPERS))
);
prepare_internal_move_to_destination(fr_mm_s); // Recover E, set_current_to_destination
}
#if ENABLED(RETRACT_SYNC_MIXING)
mixer.T(old_mixing_tool); // Restore original mixing tool
#endif
TERN_(RETRACT_SYNC_MIXING, mixer.T(old_mixing_tool)); // Restore original mixing tool
retracted[active_extruder] = retracting; // Active extruder now retracted / recovered
// If swap retract/recover update the retracted_swap flag too
#if EXTRUDERS > 1
#if HAS_MULTI_EXTRUDER
if (swapping) retracted_swap[active_extruder] = retracting;
#endif
/* // debugging
SERIAL_ECHOLNPAIR("retracting ", retracting);
SERIAL_ECHOLNPAIR("swapping ", swapping);
SERIAL_ECHOLNPAIR("active_extruder ", active_extruder);
SERIAL_ECHOLNPGM("retracting ", AS_DIGIT(retracting));
SERIAL_ECHOLNPGM("swapping ", AS_DIGIT(swapping));
SERIAL_ECHOLNPGM("active_extruder ", active_extruder);
LOOP_L_N(i, EXTRUDERS) {
SERIAL_ECHOLNPAIR("retracted[", i, "] ", retracted[i]);
#if EXTRUDERS > 1
SERIAL_ECHOLNPAIR("retracted_swap[", i, "] ", retracted_swap[i]);
SERIAL_ECHOLNPGM("retracted[", i, "] ", AS_DIGIT(retracted[i]));
#if HAS_MULTI_EXTRUDER
SERIAL_ECHOLNPGM("retracted_swap[", i, "] ", AS_DIGIT(retracted_swap[i]));
#endif
}
SERIAL_ECHOLNPAIR("current_position.z ", current_position.z);
SERIAL_ECHOLNPAIR("current_position.e ", current_position.e);
SERIAL_ECHOLNPAIR("current_hop ", current_hop);
SERIAL_ECHOLNPGM("current_position.z ", current_position.z);
SERIAL_ECHOLNPGM("current_position.e ", current_position.e);
SERIAL_ECHOLNPGM("current_hop ", current_hop);
//*/
}
//extern const char SP_Z_STR[];
/**
* 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_zraise
*/
void FWRetract::M207() {
if (!parser.seen("FSWZ")) return M207_report();
if (parser.seenval('S')) settings.retract_length = parser.value_axis_units(E_AXIS);
if (parser.seenval('F')) settings.retract_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS));
if (parser.seenval('Z')) settings.retract_zraise = parser.value_linear_units();
if (parser.seenval('W')) settings.swap_retract_length = parser.value_axis_units(E_AXIS);
}
void FWRetract::M207_report() {
SERIAL_ECHOLNPGM_P(
PSTR(" M207 S"), LINEAR_UNIT(settings.retract_length)
, PSTR(" W"), LINEAR_UNIT(settings.swap_retract_length)
, PSTR(" F"), LINEAR_UNIT(MMS_TO_MMM(settings.retract_feedrate_mm_s))
, SP_Z_STR, LINEAR_UNIT(settings.retract_zraise)
);
}
/**
* M208: Set firmware un-retraction values
*
* S[+units] retract_recover_extra (in addition to M207 S*)
* W[+units] swap_retract_recover_extra (multi-extruder)
* F[units/min] retract_recover_feedrate_mm_s
* R[units/min] swap_retract_recover_feedrate_mm_s
*/
void FWRetract::M208() {
if (!parser.seen("FSRW")) return M208_report();
if (parser.seen('S')) settings.retract_recover_extra = parser.value_axis_units(E_AXIS);
if (parser.seen('F')) settings.retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS));
if (parser.seen('R')) settings.swap_retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS));
if (parser.seen('W')) settings.swap_retract_recover_extra = parser.value_axis_units(E_AXIS);
}
void FWRetract::M208_report() {
SERIAL_ECHOLNPGM(
" M208 S", LINEAR_UNIT(settings.retract_recover_extra)
, " W", LINEAR_UNIT(settings.swap_retract_recover_extra)
, " F", LINEAR_UNIT(MMS_TO_MMM(settings.retract_recover_feedrate_mm_s))
);
}
#if ENABLED(FWRETRACT_AUTORETRACT)
/**
* M209: Enable automatic retract (M209 S1)
* For slicers that don't support G10/11, reversed extrude-only
* moves will be classified as retraction.
*/
void FWRetract::M209() {
if (!parser.seen('S')) return M209_report();
if (MIN_AUTORETRACT <= MAX_AUTORETRACT)
enable_autoretract(parser.value_bool());
}
void FWRetract::M209_report() {
SERIAL_ECHOLNPGM(" M209 S", AS_DIGIT(autoretract_enabled));
}
#endif // FWRETRACT_AUTORETRACT
#endif // FWRETRACT

19
Marlin/src/feature/fwretract.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -42,7 +42,7 @@ typedef struct {
class FWRetract {
private:
#if EXTRUDERS > 1
#if HAS_MULTI_EXTRUDER
static bool retracted_swap[EXTRUDERS]; // Which extruders are swap-retracted
#endif
@@ -74,11 +74,16 @@ public:
#endif
}
static void retract(const bool retracting
#if EXTRUDERS > 1
, bool swapping = false
#endif
);
static void retract(const bool retracting E_OPTARG(bool swapping=false));
static void M207_report();
static void M207();
static void M208_report();
static void M208();
#if ENABLED(FWRETRACT_AUTORETRACT)
static void M209_report();
static void M209();
#endif
};
extern FWRetract fwretract;

80
Marlin/src/feature/host_actions.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -37,9 +37,10 @@
#include "runout.h"
#endif
void host_action(const char * const pstr, const bool eol) {
void host_action(PGM_P const pstr, const bool eol) {
PORT_REDIRECT(SerialMask::All);
SERIAL_ECHOPGM("//action:");
serialprintPGM(pstr);
SERIAL_ECHOPGM_P(pstr);
if (eol) SERIAL_EOL();
}
@@ -61,11 +62,14 @@ void host_action(const char * const pstr, const bool eol) {
#ifdef ACTION_ON_CANCEL
void host_action_cancel() { host_action(PSTR(ACTION_ON_CANCEL)); }
#endif
#ifdef ACTION_ON_START
void host_action_start() { host_action(PSTR(ACTION_ON_START)); }
#endif
#if ENABLED(HOST_PROMPT_SUPPORT)
const char CONTINUE_STR[] PROGMEM = "Continue",
DISMISS_STR[] PROGMEM = "Dismiss";
PGMSTR(CONTINUE_STR, "Continue");
PGMSTR(DISMISS_STR, "Dismiss");
#if HAS_RESUME_CONTINUE
extern bool wait_for_user;
@@ -74,45 +78,57 @@ void host_action(const char * const pstr, const bool eol) {
PromptReason host_prompt_reason = PROMPT_NOT_DEFINED;
void host_action_notify(const char * const message) {
PORT_REDIRECT(SerialMask::All);
host_action(PSTR("notification "), false);
serialprintPGM(message);
SERIAL_EOL();
SERIAL_ECHOLN(message);
}
void host_action_prompt(const char * const ptype, const bool eol=true) {
void host_action_notify_P(PGM_P const message) {
PORT_REDIRECT(SerialMask::All);
host_action(PSTR("notification "), false);
SERIAL_ECHOLNPGM_P(message);
}
void host_action_prompt(PGM_P const ptype, const bool eol=true) {
PORT_REDIRECT(SerialMask::All);
host_action(PSTR("prompt_"), false);
serialprintPGM(ptype);
SERIAL_ECHOPGM_P(ptype);
if (eol) SERIAL_EOL();
}
void host_action_prompt_plus(const char * const ptype, const char * const pstr, const char extra_char='\0') {
void host_action_prompt_plus(PGM_P const ptype, PGM_P const pstr, const char extra_char='\0') {
host_action_prompt(ptype, false);
PORT_REDIRECT(SerialMask::All);
SERIAL_CHAR(' ');
serialprintPGM(pstr);
SERIAL_ECHOPGM_P(pstr);
if (extra_char != '\0') SERIAL_CHAR(extra_char);
SERIAL_EOL();
}
void host_action_prompt_begin(const PromptReason reason, const char * const pstr, const char extra_char/*='\0'*/) {
void host_action_prompt_begin(const PromptReason reason, PGM_P const pstr, const char extra_char/*='\0'*/) {
host_action_prompt_end();
host_prompt_reason = reason;
host_action_prompt_plus(PSTR("begin"), pstr, extra_char);
}
void host_action_prompt_button(const char * const pstr) { host_action_prompt_plus(PSTR("button"), pstr); }
void host_action_prompt_button(PGM_P const pstr) { host_action_prompt_plus(PSTR("button"), pstr); }
void host_action_prompt_end() { host_action_prompt(PSTR("end")); }
void host_action_prompt_show() { host_action_prompt(PSTR("show")); }
void host_prompt_do(const PromptReason reason, const char * const pstr, const char * const btn1/*=nullptr*/, const char * const btn2/*=nullptr*/) {
host_action_prompt_begin(reason, pstr);
void _host_prompt_show(PGM_P const btn1/*=nullptr*/, PGM_P const btn2/*=nullptr*/) {
if (btn1) host_action_prompt_button(btn1);
if (btn2) host_action_prompt_button(btn2);
host_action_prompt_show();
}
void host_prompt_do(const PromptReason reason, PGM_P const pstr, PGM_P const btn1/*=nullptr*/, PGM_P const btn2/*=nullptr*/) {
host_action_prompt_begin(reason, pstr);
_host_prompt_show(btn1, btn2);
}
void host_prompt_do(const PromptReason reason, PGM_P const pstr, const char extra_char, PGM_P const btn1/*=nullptr*/, PGM_P const btn2/*=nullptr*/) {
host_action_prompt_begin(reason, pstr, extra_char);
_host_prompt_show(btn1, btn2);
}
void filament_load_host_prompt() {
const bool disable_to_continue = (false
#if HAS_FILAMENT_SENSOR
|| runout.filament_ran_out
#endif
);
const bool disable_to_continue = TERN0(HAS_FILAMENT_SENSOR, runout.filament_ran_out);
host_prompt_do(PROMPT_FILAMENT_RUNOUT, PSTR("Paused"), PSTR("PurgeMore"),
disable_to_continue ? PSTR("DisableRunout") : CONTINUE_STR
);
@@ -126,28 +142,20 @@ void host_action(const char * const pstr, const bool eol) {
// - Dismissal of info
//
void host_response_handler(const uint8_t response) {
#ifdef DEBUG_HOST_ACTIONS
static const char m876_prefix[] PROGMEM = "M876 Handle Re";
serialprintPGM(m876_prefix); SERIAL_ECHOLNPAIR("ason: ", host_prompt_reason);
serialprintPGM(m876_prefix); SERIAL_ECHOLNPAIR("sponse: ", response);
#endif
const char *msg = PSTR("UNKNOWN STATE");
const PromptReason hpr = host_prompt_reason;
host_prompt_reason = PROMPT_NOT_DEFINED; // Reset now ahead of logic
switch (hpr) {
case PROMPT_FILAMENT_RUNOUT:
msg = PSTR("FILAMENT_RUNOUT");
switch (response) {
case 0: // "Purge More" button
#if HAS_LCD_MENU && ENABLED(ADVANCED_PAUSE_FEATURE)
#if BOTH(M600_PURGE_MORE_RESUMABLE, ADVANCED_PAUSE_FEATURE)
pause_menu_response = PAUSE_RESPONSE_EXTRUDE_MORE; // Simulate menu selection (menu exits, doesn't extrude more)
#endif
filament_load_host_prompt(); // Initiate another host prompt. (NOTE: The loop in load_filament may also do this!)
break;
case 1: // "Continue" / "Disable Runout" button
#if HAS_LCD_MENU && ENABLED(ADVANCED_PAUSE_FEATURE)
#if BOTH(M600_PURGE_MORE_RESUMABLE, ADVANCED_PAUSE_FEATURE)
pause_menu_response = PAUSE_RESPONSE_RESUME_PRINT; // Simulate menu selection
#endif
#if HAS_FILAMENT_SENSOR
@@ -160,26 +168,18 @@ void host_action(const char * const pstr, const bool eol) {
}
break;
case PROMPT_USER_CONTINUE:
#if HAS_RESUME_CONTINUE
wait_for_user = false;
#endif
msg = PSTR("FILAMENT_RUNOUT_CONTINUE");
TERN_(HAS_RESUME_CONTINUE, wait_for_user = false);
break;
case PROMPT_PAUSE_RESUME:
msg = PSTR("LCD_PAUSE_RESUME");
#if ENABLED(ADVANCED_PAUSE_FEATURE)
#if BOTH(ADVANCED_PAUSE_FEATURE, SDSUPPORT)
extern const char M24_STR[];
queue.inject_P(M24_STR);
#endif
break;
case PROMPT_INFO:
msg = PSTR("GCODE_INFO");
break;
default: break;
}
SERIAL_ECHOPGM("M876 Responding PROMPT_");
serialprintPGM(msg);
SERIAL_EOL();
}
#endif // HOST_PROMPT_SUPPORT

18
Marlin/src/feature/host_actions.h Executable file → Normal file
View File

@@ -16,14 +16,15 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "../inc/MarlinConfigPre.h"
#include "../HAL/shared/Marduino.h"
void host_action(const char * const pstr, const bool eol=true);
void host_action(PGM_P const pstr, const bool eol=true);
#ifdef ACTION_ON_KILL
void host_action_kill();
@@ -43,6 +44,9 @@ void host_action(const char * const pstr, const bool eol=true);
#ifdef ACTION_ON_CANCEL
void host_action_cancel();
#endif
#ifdef ACTION_ON_START
void host_action_start();
#endif
#if ENABLED(HOST_PROMPT_SUPPORT)
@@ -61,12 +65,14 @@ void host_action(const char * const pstr, const bool eol=true);
void host_response_handler(const uint8_t response);
void host_action_notify(const char * const message);
void host_action_prompt_begin(const PromptReason reason, const char * const pstr, const char extra_char='\0');
void host_action_prompt_button(const char * const pstr);
void host_action_notify_P(PGM_P const message);
void host_action_prompt_begin(const PromptReason reason, PGM_P const pstr, const char extra_char='\0');
void host_action_prompt_button(PGM_P const pstr);
void host_action_prompt_end();
void host_action_prompt_show();
void host_prompt_do(const PromptReason reason, const char * const pstr, const char * const btn1=nullptr, const char * const btn2=nullptr);
inline void host_prompt_open(const PromptReason reason, const char * const pstr, const char * const btn1=nullptr, const char * const btn2=nullptr) {
void host_prompt_do(const PromptReason reason, PGM_P const pstr, PGM_P const btn1=nullptr, PGM_P const btn2=nullptr);
void host_prompt_do(const PromptReason reason, PGM_P const pstr, const char extra_char, PGM_P const btn1=nullptr, PGM_P const btn2=nullptr);
inline void host_prompt_open(const PromptReason reason, PGM_P const pstr, PGM_P const btn1=nullptr, PGM_P const btn2=nullptr) {
if (host_prompt_reason == PROMPT_NOT_DEFINED) host_prompt_do(reason, pstr, btn1, btn2);
}

27
Marlin/src/feature/joystick.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -32,7 +32,6 @@
#include "../inc/MarlinConfig.h" // for pins
#include "../module/planner.h"
#include "../module/temperature.h"
Joystick joystick;
@@ -69,13 +68,13 @@ Joystick joystick;
void Joystick::report() {
SERIAL_ECHOPGM("Joystick");
#if HAS_JOY_ADC_X
SERIAL_ECHOPAIR_P(SP_X_STR, JOY_X(x.raw));
SERIAL_ECHOPGM_P(SP_X_STR, JOY_X(x.raw));
#endif
#if HAS_JOY_ADC_Y
SERIAL_ECHOPAIR_P(SP_Y_STR, JOY_Y(y.raw));
SERIAL_ECHOPGM_P(SP_Y_STR, JOY_Y(y.raw));
#endif
#if HAS_JOY_ADC_Z
SERIAL_ECHOPAIR_P(SP_Z_STR, JOY_Z(z.raw));
SERIAL_ECHOPGM_P(SP_Z_STR, JOY_Z(z.raw));
#endif
#if HAS_JOY_ADC_EN
SERIAL_ECHO_TERNARY(READ(JOY_EN_PIN), " EN=", "HIGH (dis", "LOW (en", "abled)");
@@ -127,6 +126,11 @@ Joystick joystick;
static bool injecting_now; // = false;
if (injecting_now) return;
#if ENABLED(NO_MOTION_BEFORE_HOMING)
if (TERN0(HAS_JOY_ADC_X, axis_should_home(X_AXIS)) || TERN0(HAS_JOY_ADC_Y, axis_should_home(Y_AXIS)) || TERN0(HAS_JOY_ADC_Z, axis_should_home(Z_AXIS)))
return;
#endif
static constexpr int QUEUE_DEPTH = 5; // Insert up to this many movements
static constexpr float target_lag = 0.25f, // Aim for 1/4 second lag
seg_time = target_lag / QUEUE_DEPTH; // 0.05 seconds, short segments inserted every 1/20th of a second
@@ -154,20 +158,13 @@ Joystick joystick;
// Other non-joystick poll-based jogging could be implemented here
// with "jogging" encapsulated as a more general class.
#if ENABLED(EXTENSIBLE_UI)
ExtUI::_joystick_update(norm_jog);
#endif
TERN_(EXTENSIBLE_UI, ExtUI::_joystick_update(norm_jog));
// norm_jog values of [-1 .. 1] maps linearly to [-feedrate .. feedrate]
xyz_float_t move_dist{0};
float hypot2 = 0;
LOOP_XYZ(i) if (norm_jog[i]) {
move_dist[i] = seg_time * norm_jog[i] *
#if ENABLED(EXTENSIBLE_UI)
manual_feedrate_mm_s[i];
#else
planner.settings.max_feedrate_mm_s[i];
#endif
LOOP_LINEAR_AXES(i) if (norm_jog[i]) {
move_dist[i] = seg_time * norm_jog[i] * TERN(EXTENSIBLE_UI, manual_feedrate_mm_s, planner.settings.max_feedrate_mm_s)[i];
hypot2 += sq(move_dist[i]);
}

5
Marlin/src/feature/joystick.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -27,11 +27,8 @@
#include "../inc/MarlinConfigPre.h"
#include "../core/types.h"
#include "../core/macros.h"
#include "../module/temperature.h"
//#define JOYSTICK_DEBUG
class Joystick {
friend class Temperature;
private:

2
Marlin/src/feature/leds/blinkm.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/

2
Marlin/src/feature/leds/blinkm.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once

147
Marlin/src/feature/leds/leds.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -39,20 +39,22 @@
#endif
#if ENABLED(PCA9533)
#include <SailfishRGB_LED.h>
#include "pca9533.h"
#endif
#if ENABLED(CASE_LIGHT_USE_RGB_LED)
#include "../../feature/caselight.h"
#endif
#if ENABLED(LED_COLOR_PRESETS)
const LEDColor LEDLights::defaultLEDColor = MakeLEDColor(
LED_USER_PRESET_RED,
LED_USER_PRESET_GREEN,
LED_USER_PRESET_BLUE,
LED_USER_PRESET_WHITE,
LED_USER_PRESET_BRIGHTNESS
const LEDColor LEDLights::defaultLEDColor = LEDColor(
LED_USER_PRESET_RED, LED_USER_PRESET_GREEN, LED_USER_PRESET_BLUE
OPTARG(HAS_WHITE_LED, LED_USER_PRESET_WHITE)
OPTARG(NEOPIXEL_LED, LED_USER_PRESET_BRIGHTNESS)
);
#endif
#if EITHER(LED_CONTROL_MENU, PRINTER_EVENT_LEDS)
#if ANY(LED_CONTROL_MENU, PRINTER_EVENT_LEDS, CASE_LIGHT_IS_COLOR_LED)
LEDColor LEDLights::color;
bool LEDLights::lights_on;
#endif
@@ -68,45 +70,41 @@ void LEDLights::setup() {
if (PWM_PIN(RGB_LED_W_PIN)) SET_PWM(RGB_LED_W_PIN); else SET_OUTPUT(RGB_LED_W_PIN);
#endif
#endif
#if ENABLED(NEOPIXEL_LED)
neo.init();
#endif
#if ENABLED(PCA9533)
RGBinit();
#endif
#if ENABLED(LED_USER_PRESET_STARTUP)
set_default();
#endif
TERN_(NEOPIXEL_LED, neo.init());
TERN_(PCA9533, PCA9533_init());
TERN_(LED_USER_PRESET_STARTUP, set_default());
}
void LEDLights::set_color(const LEDColor &incol
#if ENABLED(NEOPIXEL_LED)
, bool isSequence/*=false*/
#endif
OPTARG(NEOPIXEL_IS_SEQUENTIAL, bool isSequence/*=false*/)
) {
#if ENABLED(NEOPIXEL_LED)
const uint32_t neocolor = LEDColorWhite() == incol
? neo.Color(NEO_WHITE)
: neo.Color(incol.r, incol.g, incol.b, incol.w);
static uint16_t nextLed = 0;
: neo.Color(incol.r, incol.g, incol.b OPTARG(HAS_WHITE_LED, incol.w));
#ifdef NEOPIXEL_BKGD_LED_INDEX
if (NEOPIXEL_BKGD_LED_INDEX == nextLed) {
if (++nextLed >= neo.pixels()) nextLed = 0;
return;
}
#if ENABLED(NEOPIXEL_IS_SEQUENTIAL)
static uint16_t nextLed = 0;
#ifdef NEOPIXEL_BKGD_INDEX_FIRST
while (WITHIN(nextLed, NEOPIXEL_BKGD_INDEX_FIRST, NEOPIXEL_BKGD_INDEX_LAST)) {
neo.reset_background_color();
if (++nextLed >= neo.pixels()) { nextLed = 0; return; }
}
#endif
#endif
neo.set_brightness(incol.i);
if (isSequence) {
neo.set_pixel_color(nextLed, neocolor);
neo.show();
if (++nextLed >= neo.pixels()) nextLed = 0;
return;
}
#if ENABLED(NEOPIXEL_IS_SEQUENTIAL)
if (isSequence) {
neo.set_pixel_color(nextLed, neocolor);
neo.show();
if (++nextLed >= neo.pixels()) nextLed = 0;
return;
}
#endif
neo.set_color(neocolor);
@@ -123,26 +121,23 @@ void LEDLights::set_color(const LEDColor &incol
// This variant uses 3-4 separate pins for the RGB(W) components.
// If the pins can do PWM then their intensity will be set.
#define UPDATE_RGBW(C,c) do { if (PWM_PIN(RGB_LED_##C##_PIN)) \
analogWrite(pin_t(RGB_LED_##C##_PIN), incol.c); \
else WRITE(RGB_LED_##C##_PIN, incol.c ? HIGH : LOW); }while(0)
UPDATE_RGBW(R,r);
UPDATE_RGBW(G,g);
UPDATE_RGBW(B,b);
#define _UPDATE_RGBW(C,c) do { \
if (PWM_PIN(RGB_LED_##C##_PIN)) \
analogWrite(pin_t(RGB_LED_##C##_PIN), c); \
else \
WRITE(RGB_LED_##C##_PIN, c ? HIGH : LOW); \
}while(0)
#define UPDATE_RGBW(C,c) _UPDATE_RGBW(C, TERN1(CASE_LIGHT_USE_RGB_LED, caselight.on) ? incol.c : 0)
UPDATE_RGBW(R,r); UPDATE_RGBW(G,g); UPDATE_RGBW(B,b);
#if ENABLED(RGBW_LED)
UPDATE_RGBW(W,w);
#endif
#endif
#if ENABLED(PCA9632)
// Update I2C LED driver
pca9632_set_led_color(incol);
#endif
#if ENABLED(PCA9533)
RGBsetColor(incol.r, incol.g, incol.b, true);
#endif
// Update I2C LED driver
TERN_(PCA9632, PCA9632_set_led_color(incol));
TERN_(PCA9533, PCA9533_set_rgb(incol.r, incol.g, incol.b));
#if EITHER(LED_CONTROL_MENU, PRINTER_EVENT_LEDS)
// Don't update the color when OFF
@@ -160,13 +155,57 @@ void LEDLights::set_color(const LEDColor &incol
millis_t LEDLights::led_off_time; // = 0
void LEDLights::update_timeout(const bool power_on) {
const millis_t ms = millis();
if (power_on)
reset_timeout(ms);
else if (ELAPSED(ms, led_off_time))
set_off();
if (lights_on) {
const millis_t ms = millis();
if (power_on)
reset_timeout(ms);
else if (ELAPSED(ms, led_off_time))
set_off();
}
}
#endif
#endif // HAS_COLOR_LEDS
#if ENABLED(NEOPIXEL2_SEPARATE)
#if ENABLED(NEO2_COLOR_PRESETS)
const LEDColor LEDLights2::defaultLEDColor = LEDColor(
LED_USER_PRESET_RED, LED_USER_PRESET_GREEN, LED_USER_PRESET_BLUE
OPTARG(HAS_WHITE_LED2, LED_USER_PRESET_WHITE)
OPTARG(NEOPIXEL_LED, LED_USER_PRESET_BRIGHTNESS)
);
#endif
#if ENABLED(LED_CONTROL_MENU)
LEDColor LEDLights2::color;
bool LEDLights2::lights_on;
#endif
LEDLights2 leds2;
void LEDLights2::setup() {
neo2.init();
TERN_(NEO2_USER_PRESET_STARTUP, set_default());
}
void LEDLights2::set_color(const LEDColor &incol) {
const uint32_t neocolor = LEDColorWhite() == incol
? neo2.Color(NEO2_WHITE)
: neo2.Color(incol.r, incol.g, incol.b OPTARG(HAS_WHITE_LED2, incol.w));
neo2.set_brightness(incol.i);
neo2.set_color(neocolor);
#if ENABLED(LED_CONTROL_MENU)
// Don't update the color when OFF
lights_on = !incol.is_off();
if (lights_on) color = incol;
#endif
}
#if ENABLED(LED_CONTROL_MENU)
void LEDLights2::toggle() { if (lights_on) set_off(); else update(); }
#endif
#endif // NEOPIXEL2_SEPARATE
#endif // HAS_COLOR_LEDS

153
Marlin/src/feature/leds/leds.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -29,65 +29,42 @@
#include <string.h>
#if ENABLED(NEOPIXEL_LED)
#include "neopixel.h"
// A white component can be passed
#if EITHER(RGBW_LED, PCA9632_RGBW)
#define HAS_WHITE_LED 1
#endif
// A white component can be passed
#define HAS_WHITE_LED EITHER(RGBW_LED, NEOPIXEL_LED)
#if ENABLED(NEOPIXEL_LED)
#define _NEOPIXEL_INCLUDE_
#include "neopixel.h"
#undef _NEOPIXEL_INCLUDE_
#endif
/**
* LEDcolor type for use with leds.set_color
*/
typedef struct LEDColor {
uint8_t r, g, b
#if HAS_WHITE_LED
, w
#if ENABLED(NEOPIXEL_LED)
, i
#endif
#endif
OPTARG(HAS_WHITE_LED, w)
OPTARG(NEOPIXEL_LED, i)
;
LEDColor() : r(255), g(255), b(255)
#if HAS_WHITE_LED
, w(255)
#if ENABLED(NEOPIXEL_LED)
, i(NEOPIXEL_BRIGHTNESS)
#endif
#endif
OPTARG(HAS_WHITE_LED, w(255))
OPTARG(NEOPIXEL_LED, i(NEOPIXEL_BRIGHTNESS))
{}
LEDColor(uint8_t r, uint8_t g, uint8_t b
#if HAS_WHITE_LED
, uint8_t w=0
#if ENABLED(NEOPIXEL_LED)
, uint8_t i=NEOPIXEL_BRIGHTNESS
#endif
#endif
) : r(r), g(g), b(b)
#if HAS_WHITE_LED
, w(w)
#if ENABLED(NEOPIXEL_LED)
, i(i)
#endif
#endif
{}
LEDColor(uint8_t r, uint8_t g, uint8_t b OPTARG(HAS_WHITE_LED, uint8_t w=0) OPTARG(NEOPIXEL_LED, uint8_t i=NEOPIXEL_BRIGHTNESS))
: r(r), g(g), b(b) OPTARG(HAS_WHITE_LED, w(w)) OPTARG(NEOPIXEL_LED, i(i)) {}
LEDColor(const uint8_t (&rgbw)[4]) : r(rgbw[0]), g(rgbw[1]), b(rgbw[2])
#if HAS_WHITE_LED
, w(rgbw[3])
#if ENABLED(NEOPIXEL_LED)
, i(NEOPIXEL_BRIGHTNESS)
#endif
#endif
OPTARG(HAS_WHITE_LED, w(rgbw[3]))
OPTARG(NEOPIXEL_LED, i(NEOPIXEL_BRIGHTNESS))
{}
LEDColor& operator=(const uint8_t (&rgbw)[4]) {
r = rgbw[0]; g = rgbw[1]; b = rgbw[2];
#if HAS_WHITE_LED
w = rgbw[3];
#endif
TERN_(HAS_WHITE_LED, w = rgbw[3]);
return *this;
}
@@ -104,26 +81,13 @@ typedef struct LEDColor {
bool operator!=(const LEDColor &right) { return !operator==(right); }
bool is_off() const {
return 3 > r + g + b
#if HAS_WHITE_LED
+ w
#endif
;
return 3 > r + g + b + TERN0(HAS_WHITE_LED, w);
}
} LEDColor;
/**
* Color helpers and presets
* Color presets
*/
#if HAS_WHITE_LED
#if ENABLED(NEOPIXEL_LED)
#define MakeLEDColor(R,G,B,W,I) LEDColor(R, G, B, W, I)
#else
#define MakeLEDColor(R,G,B,W,I) LEDColor(R, G, B, W)
#endif
#else
#define MakeLEDColor(R,G,B,W,I) LEDColor(R, G, B)
#endif
#define LEDColorOff() LEDColor( 0, 0, 0)
#define LEDColorRed() LEDColor(255, 0, 0)
@@ -151,27 +115,15 @@ public:
static void setup(); // init()
static void set_color(const LEDColor &color
#if ENABLED(NEOPIXEL_LED)
, bool isSequence=false
#endif
OPTARG(NEOPIXEL_IS_SEQUENTIAL, bool isSequence=false)
);
inline void set_color(uint8_t r, uint8_t g, uint8_t b
#if HAS_WHITE_LED
, uint8_t w=0
#if ENABLED(NEOPIXEL_LED)
, uint8_t i=NEOPIXEL_BRIGHTNESS
#endif
#endif
#if ENABLED(NEOPIXEL_LED)
, bool isSequence=false
#endif
static inline void set_color(uint8_t r, uint8_t g, uint8_t b
OPTARG(HAS_WHITE_LED, uint8_t w=0)
OPTARG(NEOPIXEL_LED, uint8_t i=NEOPIXEL_BRIGHTNESS)
OPTARG(NEOPIXEL_IS_SEQUENTIAL, bool isSequence=false)
) {
set_color(MakeLEDColor(r, g, b, w, i)
#if ENABLED(NEOPIXEL_LED)
, isSequence
#endif
);
set_color(LEDColor(r, g, b OPTARG(HAS_WHITE_LED, w) OPTARG(NEOPIXEL_LED, i)) OPTARG(NEOPIXEL_IS_SEQUENTIAL, isSequence));
}
static inline void set_off() { set_color(LEDColorOff()); }
@@ -193,13 +145,15 @@ public:
static inline LEDColor get_color() { return lights_on ? color : LEDColorOff(); }
#endif
#if EITHER(LED_CONTROL_MENU, PRINTER_EVENT_LEDS)
#if ANY(LED_CONTROL_MENU, PRINTER_EVENT_LEDS, CASE_LIGHT_IS_COLOR_LED)
static LEDColor color; // last non-off color
static bool lights_on; // the last set color was "on"
#endif
#if ENABLED(LED_CONTROL_MENU)
static void toggle(); // swap "off" with color
#endif
#if EITHER(LED_CONTROL_MENU, CASE_LIGHT_USE_RGB_LED)
static inline void update() { set_color(color); }
#endif
@@ -209,10 +163,57 @@ public:
public:
static inline void reset_timeout(const millis_t &ms) {
led_off_time = ms + LED_BACKLIGHT_TIMEOUT;
if (!lights_on) set_default();
if (!lights_on) update();
}
static void update_timeout(const bool power_on);
#endif
};
extern LEDLights leds;
#if ENABLED(NEOPIXEL2_SEPARATE)
class LEDLights2 {
public:
LEDLights2() {}
static void setup(); // init()
static void set_color(const LEDColor &color);
static inline void set_color(uint8_t r, uint8_t g, uint8_t b
OPTARG(HAS_WHITE_LED, uint8_t w=0)
OPTARG(NEOPIXEL_LED, uint8_t i=NEOPIXEL_BRIGHTNESS)
) {
set_color(LEDColor(r, g, b
OPTARG(HAS_WHITE_LED, w)
OPTARG(NEOPIXEL_LED, i)
));
}
static inline void set_off() { set_color(LEDColorOff()); }
static inline void set_green() { set_color(LEDColorGreen()); }
static inline void set_white() { set_color(LEDColorWhite()); }
#if ENABLED(NEO2_COLOR_PRESETS)
static const LEDColor defaultLEDColor;
static inline void set_default() { set_color(defaultLEDColor); }
static inline void set_red() { set_color(LEDColorRed()); }
static inline void set_orange() { set_color(LEDColorOrange()); }
static inline void set_yellow() { set_color(LEDColorYellow()); }
static inline void set_blue() { set_color(LEDColorBlue()); }
static inline void set_indigo() { set_color(LEDColorIndigo()); }
static inline void set_violet() { set_color(LEDColorViolet()); }
#endif
#if ENABLED(NEOPIXEL2_SEPARATE)
static LEDColor color; // last non-off color
static bool lights_on; // the last set color was "on"
static void toggle(); // swap "off" with color
static inline void update() { set_color(color); }
#endif
};
extern LEDLights2 leds2;
#endif // NEOPIXEL2_SEPARATE

147
Marlin/src/feature/leds/neopixel.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -28,38 +28,50 @@
#if ENABLED(NEOPIXEL_LED)
#include "neopixel.h"
#include "leds.h"
#if ENABLED(NEOPIXEL_STARTUP_TEST)
#if EITHER(NEOPIXEL_STARTUP_TEST, NEOPIXEL2_STARTUP_TEST)
#include "../../core/utility.h"
#endif
Marlin_NeoPixel neo;
int8_t Marlin_NeoPixel::neoindex;
Adafruit_NeoPixel Marlin_NeoPixel::adaneo1(NEOPIXEL_PIXELS, NEOPIXEL_PIN, NEOPIXEL_TYPE + NEO_KHZ800)
#if MULTIPLE_NEOPIXEL_TYPES
, Marlin_NeoPixel::adaneo2(NEOPIXEL_PIXELS, NEOPIXEL2_PIN, NEOPIXEL2_TYPE + NEO_KHZ800)
#endif
;
Adafruit_NeoPixel Marlin_NeoPixel::adaneo1(NEOPIXEL_PIXELS, NEOPIXEL_PIN, NEOPIXEL_TYPE + NEO_KHZ800);
#if CONJOINED_NEOPIXEL
Adafruit_NeoPixel Marlin_NeoPixel::adaneo2(NEOPIXEL_PIXELS, NEOPIXEL2_PIN, NEOPIXEL2_TYPE + NEO_KHZ800);
#endif
#ifdef NEOPIXEL_BKGD_LED_INDEX
#ifdef NEOPIXEL_BKGD_INDEX_FIRST
void Marlin_NeoPixel::set_color_background() {
uint8_t background_color[4] = NEOPIXEL_BKGD_COLOR;
set_pixel_color(NEOPIXEL_BKGD_LED_INDEX, adaneo1.Color(background_color[0], background_color[1], background_color[2], background_color[3]));
void Marlin_NeoPixel::set_background_color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
for (int background_led = NEOPIXEL_BKGD_INDEX_FIRST; background_led <= NEOPIXEL_BKGD_INDEX_LAST; background_led++)
set_pixel_color(background_led, adaneo1.Color(r, g, b, w));
}
void Marlin_NeoPixel::reset_background_color() {
constexpr uint8_t background_color[4] = NEOPIXEL_BKGD_COLOR;
set_background_color(background_color[0], background_color[1], background_color[2], background_color[3]);
}
#endif
void Marlin_NeoPixel::set_color(const uint32_t color) {
for (uint16_t i = 0; i < pixels(); ++i) {
#ifdef NEOPIXEL_BKGD_LED_INDEX
if (i == NEOPIXEL_BKGD_LED_INDEX && color != 0x000000) {
set_color_background();
continue;
}
#endif
set_pixel_color(i, color);
if (neoindex >= 0) {
set_pixel_color(neoindex, color);
neoindex = -1;
}
else {
for (uint16_t i = 0; i < pixels(); ++i) {
#ifdef NEOPIXEL_BKGD_INDEX_FIRST
if (i == NEOPIXEL_BKGD_INDEX_FIRST && TERN(NEOPIXEL_BKGD_ALWAYS_ON, true, color != 0x000000)) {
reset_background_color();
i += NEOPIXEL_BKGD_INDEX_LAST - (NEOPIXEL_BKGD_INDEX_FIRST);
continue;
}
#endif
set_pixel_color(i, color);
}
}
show();
}
@@ -71,47 +83,86 @@ void Marlin_NeoPixel::set_color_startup(const uint32_t color) {
}
void Marlin_NeoPixel::init() {
SET_OUTPUT(NEOPIXEL_PIN);
set_brightness(NEOPIXEL_BRIGHTNESS); // 0 - 255 range
neoindex = -1; // -1 .. NEOPIXEL_PIXELS-1 range
set_brightness(NEOPIXEL_BRIGHTNESS); // 0 .. 255 range
begin();
show(); // initialize to all off
#if ENABLED(NEOPIXEL_STARTUP_TEST)
safe_delay(1000);
set_color_startup(adaneo1.Color(255, 0, 0, 0)); // red
safe_delay(1000);
safe_delay(500);
set_color_startup(adaneo1.Color(0, 255, 0, 0)); // green
safe_delay(1000);
safe_delay(500);
set_color_startup(adaneo1.Color(0, 0, 255, 0)); // blue
safe_delay(1000);
safe_delay(500);
#if HAS_WHITE_LED
set_color_startup(adaneo1.Color(0, 0, 0, 255)); // white
safe_delay(500);
#endif
#endif
#ifdef NEOPIXEL_BKGD_LED_INDEX
set_color_background();
#ifdef NEOPIXEL_BKGD_INDEX_FIRST
reset_background_color();
#endif
#if ENABLED(LED_USER_PRESET_STARTUP)
set_color(adaneo1.Color(LED_USER_PRESET_RED, LED_USER_PRESET_GREEN, LED_USER_PRESET_BLUE, LED_USER_PRESET_WHITE));
#else
set_color(adaneo1.Color(0, 0, 0, 0));
#endif
set_color(adaneo1.Color
TERN(LED_USER_PRESET_STARTUP,
(LED_USER_PRESET_RED, LED_USER_PRESET_GREEN, LED_USER_PRESET_BLUE, LED_USER_PRESET_WHITE),
(0, 0, 0, 0))
);
}
#if 0
bool Marlin_NeoPixel::set_led_color(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t w, const uint8_t p) {
const uint32_t color = adaneo1.Color(r, g, b, w);
set_brightness(p);
#if DISABLED(NEOPIXEL_IS_SEQUENTIAL)
set_color(color);
return false;
#else
static uint16_t nextLed = 0;
set_pixel_color(nextLed, color);
#if ENABLED(NEOPIXEL2_SEPARATE)
Marlin_NeoPixel2 neo2;
int8_t Marlin_NeoPixel2::neoindex;
Adafruit_NeoPixel Marlin_NeoPixel2::adaneo(NEOPIXEL2_PIXELS, NEOPIXEL2_PIN, NEOPIXEL2_TYPE);
void Marlin_NeoPixel2::set_color(const uint32_t color) {
if (neoindex >= 0) {
set_pixel_color(neoindex, color);
neoindex = -1;
}
else {
for (uint16_t i = 0; i < pixels(); ++i)
set_pixel_color(i, color);
}
show();
if (++nextLed >= pixels()) nextLed = 0;
return true;
#endif
}
#endif
}
void Marlin_NeoPixel2::set_color_startup(const uint32_t color) {
for (uint16_t i = 0; i < pixels(); ++i)
set_pixel_color(i, color);
show();
}
void Marlin_NeoPixel2::init() {
neoindex = -1; // -1 .. NEOPIXEL2_PIXELS-1 range
set_brightness(NEOPIXEL2_BRIGHTNESS); // 0 .. 255 range
begin();
show(); // initialize to all off
#if ENABLED(NEOPIXEL2_STARTUP_TEST)
set_color_startup(adaneo.Color(255, 0, 0, 0)); // red
safe_delay(500);
set_color_startup(adaneo.Color(0, 255, 0, 0)); // green
safe_delay(500);
set_color_startup(adaneo.Color(0, 0, 255, 0)); // blue
safe_delay(500);
#if HAS_WHITE_LED2
set_color_startup(adaneo.Color(0, 0, 0, 255)); // white
safe_delay(500);
#endif
#endif
set_color(adaneo.Color
TERN(NEO2_USER_PRESET_STARTUP,
(NEO2_USER_PRESET_RED, NEO2_USER_PRESET_GREEN, NEO2_USER_PRESET_BLUE, NEO2_USER_PRESET_WHITE),
(0, 0, 0, 0))
);
}
#endif // NEOPIXEL2_SEPARATE
#endif // NEOPIXEL_LED

126
Marlin/src/feature/leds/neopixel.h Executable file → Normal file
View File

@@ -16,15 +16,19 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
/**
* Neopixel support
* NeoPixel support
*/
#ifndef _NEOPIXEL_INCLUDE_
#error "Always include 'leds.h' and not 'neopixel.h' directly."
#endif
// ------------------------
// Includes
// ------------------------
@@ -38,15 +42,24 @@
// Defines
// ------------------------
#define MULTIPLE_NEOPIXEL_TYPES (defined(NEOPIXEL2_TYPE) && (NEOPIXEL2_TYPE != NEOPIXEL_TYPE))
#define _NEO_IS_RGB(N) (N == NEO_RGB || N == NEO_RBG || N == NEO_GRB || N == NEO_GBR || N == NEO_BRG || N == NEO_BGR)
#define NEOPIXEL_IS_RGB (NEOPIXEL_TYPE == NEO_RGB || NEOPIXEL_TYPE == NEO_RBG || NEOPIXEL_TYPE == NEO_GRB || NEOPIXEL_TYPE == NEO_GBR || NEOPIXEL_TYPE == NEO_BRG || NEOPIXEL_TYPE == NEO_BGR)
#define NEOPIXEL_IS_RGBW !NEOPIXEL_IS_RGB
#if !_NEO_IS_RGB(NEOPIXEL_TYPE)
#define HAS_WHITE_LED 1
#endif
#if NEOPIXEL_IS_RGB
#define NEO_WHITE 255, 255, 255, 0
#else
#if HAS_WHITE_LED
#define NEO_WHITE 0, 0, 0, 255
#else
#define NEO_WHITE 255, 255, 255
#endif
#if defined(NEOPIXEL2_TYPE) && NEOPIXEL2_TYPE != NEOPIXEL_TYPE && DISABLED(NEOPIXEL2_SEPARATE)
#define MULTIPLE_NEOPIXEL_TYPES 1
#endif
#if EITHER(MULTIPLE_NEOPIXEL_TYPES, NEOPIXEL2_INSERIES)
#define CONJOINED_NEOPIXEL 1
#endif
// ------------------------
@@ -55,66 +68,113 @@
class Marlin_NeoPixel {
private:
static Adafruit_NeoPixel adaneo1
#if MULTIPLE_NEOPIXEL_TYPES
, adaneo2
#endif
;
static Adafruit_NeoPixel adaneo1;
#if CONJOINED_NEOPIXEL
static Adafruit_NeoPixel adaneo2;
#endif
public:
static int8_t neoindex;
static void init();
static void set_color_startup(const uint32_t c);
static void set_color(const uint32_t c);
#ifdef NEOPIXEL_BKGD_LED_INDEX
static void set_color_background();
#ifdef NEOPIXEL_BKGD_INDEX_FIRST
static void set_background_color(uint8_t r, uint8_t g, uint8_t b, uint8_t w);
static void reset_background_color();
#endif
static inline void begin() {
adaneo1.begin();
#if MULTIPLE_NEOPIXEL_TYPES
adaneo2.begin();
#endif
TERN_(CONJOINED_NEOPIXEL, adaneo2.begin());
}
static inline void set_pixel_color(const uint16_t n, const uint32_t c) {
adaneo1.setPixelColor(n, c);
#if MULTIPLE_NEOPIXEL_TYPES
adaneo2.setPixelColor(n, c);
#if ENABLED(NEOPIXEL2_INSERIES)
if (n >= NEOPIXEL_PIXELS) adaneo2.setPixelColor(n - (NEOPIXEL_PIXELS), c);
else adaneo1.setPixelColor(n, c);
#else
adaneo1.setPixelColor(n, c);
TERN_(MULTIPLE_NEOPIXEL_TYPES, adaneo2.setPixelColor(n, c));
#endif
}
static inline void set_brightness(const uint8_t b) {
adaneo1.setBrightness(b);
#if MULTIPLE_NEOPIXEL_TYPES
adaneo2.setBrightness(b);
#endif
TERN_(CONJOINED_NEOPIXEL, adaneo2.setBrightness(b));
}
static inline void show() {
// Some platforms cannot maintain PWM output when NeoPixel disables interrupts for long durations.
TERN_(HAS_PAUSE_SERVO_OUTPUT, PAUSE_SERVO_OUTPUT());
adaneo1.show();
#if PIN_EXISTS(NEOPIXEL2)
#if MULTIPLE_NEOPIXEL_TYPES
#if CONJOINED_NEOPIXEL
adaneo2.show();
#else
adaneo1.setPin(NEOPIXEL2_PIN);
adaneo1.show();
adaneo1.setPin(NEOPIXEL_PIN);
#endif
#endif
TERN_(HAS_PAUSE_SERVO_OUTPUT, RESUME_SERVO_OUTPUT());
}
#if 0
bool set_led_color(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t w, const uint8_t p);
#endif
// Accessors
static inline uint16_t pixels() { return adaneo1.numPixels(); }
static inline uint16_t pixels() { return adaneo1.numPixels() * TERN1(NEOPIXEL2_INSERIES, 2); }
static inline uint8_t brightness() { return adaneo1.getBrightness(); }
static inline uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
return adaneo1.Color(r, g, b, w);
static inline uint32_t Color(uint8_t r, uint8_t g, uint8_t b OPTARG(HAS_WHITE_LED, uint8_t w)) {
return adaneo1.Color(r, g, b OPTARG(HAS_WHITE_LED, w));
}
};
extern Marlin_NeoPixel neo;
// Neo pixel channel 2
#if ENABLED(NEOPIXEL2_SEPARATE)
#if _NEO_IS_RGB(NEOPIXEL2_TYPE)
#define NEOPIXEL2_IS_RGB 1
#define NEO2_WHITE 255, 255, 255
#else
#define NEOPIXEL2_IS_RGBW 1
#define HAS_WHITE_LED2 1 // A white component can be passed for NEOPIXEL2
#define NEO2_WHITE 0, 0, 0, 255
#endif
class Marlin_NeoPixel2 {
private:
static Adafruit_NeoPixel adaneo;
public:
static int8_t neoindex;
static void init();
static void set_color_startup(const uint32_t c);
static void set_color(const uint32_t c);
static inline void begin() { adaneo.begin(); }
static inline void set_pixel_color(const uint16_t n, const uint32_t c) { adaneo.setPixelColor(n, c); }
static inline void set_brightness(const uint8_t b) { adaneo.setBrightness(b); }
static inline void show() {
adaneo.show();
adaneo.setPin(NEOPIXEL2_PIN);
}
// Accessors
static inline uint16_t pixels() { return adaneo.numPixels();}
static inline uint8_t brightness() { return adaneo.getBrightness(); }
static inline uint32_t Color(uint8_t r, uint8_t g, uint8_t b OPTARG(HAS_WHITE_LED2, uint8_t w)) {
return adaneo.Color(r, g, b OPTARG(HAS_WHITE_LED2, w));
}
};
extern Marlin_NeoPixel2 neo2;
#endif // NEOPIXEL2_SEPARATE
#undef _NEO_IS_RGB

View File

@@ -0,0 +1,127 @@
/*
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
/**
* PCA9533 LED controller driver (MightyBoard, FlashForge Creator Pro, etc.)
* by @grauerfuchs - 1 Apr 2020
*/
#include "../../inc/MarlinConfig.h"
#if ENABLED(PCA9533)
#include "pca9533.h"
#include <Wire.h>
void PCA9533_init() {
Wire.begin();
PCA9533_reset();
}
static void PCA9533_writeAllRegisters(uint8_t psc0, uint8_t pwm0, uint8_t psc1, uint8_t pwm1, uint8_t ls0) {
uint8_t data[6] = { PCA9533_REG_PSC0 | PCA9533_REGM_AI, psc0, pwm0, psc1, pwm1, ls0 };
Wire.beginTransmission(PCA9533_Addr >> 1);
Wire.write(data, 6);
Wire.endTransmission();
delayMicroseconds(1);
}
static void PCA9533_writeRegister(uint8_t reg, uint8_t val) {
uint8_t data[2] = { reg, val };
Wire.beginTransmission(PCA9533_Addr >> 1);
Wire.write(data, 2);
Wire.endTransmission();
delayMicroseconds(1);
}
// Reset (clear) all registers
void PCA9533_reset() {
PCA9533_writeAllRegisters(0, 0, 0, 0, 0);
}
// Turn all LEDs off
void PCA9533_setOff() {
PCA9533_writeRegister(PCA9533_REG_SEL, 0);
}
void PCA9533_set_rgb(uint8_t red, uint8_t green, uint8_t blue) {
uint8_t r_pwm0 = 0; // Register data - PWM value
uint8_t r_pwm1 = 0; // Register data - PWM value
uint8_t op_g = 0, op_r = 0, op_b = 0; // Opcodes - Green, Red, Blue
// Light theory! GREEN takes priority because
// it's the most visible to the human eye.
if (green == 0) op_g = PCA9533_LED_OP_OFF;
else if (green == 255) op_g = PCA9533_LED_OP_ON;
else { r_pwm0 = green; op_g = PCA9533_LED_OP_PWM0; }
// RED
if (red == 0) op_r = PCA9533_LED_OP_OFF;
else if (red == 255) op_r = PCA9533_LED_OP_ON;
else if (r_pwm0 == 0 || r_pwm0 == red) {
r_pwm0 = red; op_r = PCA9533_LED_OP_PWM0;
}
else {
r_pwm1 = red; op_r = PCA9533_LED_OP_PWM1;
}
// BLUE
if (blue == 0) op_b = PCA9533_LED_OP_OFF;
else if (blue == 255) op_b = PCA9533_LED_OP_ON;
else if (r_pwm0 == 0 || r_pwm0 == blue) {
r_pwm0 = blue; op_b = PCA9533_LED_OP_PWM0;
}
else if (r_pwm1 == 0 || r_pwm1 == blue) {
r_pwm1 = blue; op_b = PCA9533_LED_OP_PWM1;
}
else {
/**
* Conflict. 3 values are requested but only 2 channels exist.
* G is on channel 0 and R is on channel 1, so work from there.
* Find the closest match, average the values, then use the free channel.
*/
uint8_t dgb = ABS(green - blue),
dgr = ABS(green - red),
dbr = ABS(blue - red);
if (dgb < dgr && dgb < dbr) { // Mix with G on channel 0.
op_b = PCA9533_LED_OP_PWM0;
r_pwm0 = uint8_t(((uint16_t)green + (uint16_t)blue) / 2);
}
else if (dbr <= dgr && dbr <= dgb) { // Mix with R on channel 1.
op_b = PCA9533_LED_OP_PWM1;
r_pwm1 = uint8_t(((uint16_t)red + (uint16_t)blue) / 2);
}
else { // Mix R+G on 0 and put B on 1.
op_r = PCA9533_LED_OP_PWM0;
r_pwm0 = uint8_t(((uint16_t)green + (uint16_t)red) / 2);
op_b = PCA9533_LED_OP_PWM1;
r_pwm1 = blue;
}
}
// Write the changes to the hardware
PCA9533_writeAllRegisters(0, r_pwm0, 0, r_pwm1,
(op_g << PCA9533_LED_OFS_GRN) | (op_r << PCA9533_LED_OFS_RED) | (op_b << PCA9533_LED_OFS_BLU)
);
}
#endif // PCA9533

View File

@@ -0,0 +1,59 @@
/*
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
/*
* Driver for the PCA9533 LED controller found on the MightyBoard
* used by FlashForge Creator Pro, MakerBot, etc.
* Written 2020 APR 01 by grauerfuchs
*/
#include <Arduino.h>
#define ENABLE_I2C_PULLUPS
// Chip address (for Wire)
#define PCA9533_Addr 0xC4
// Control registers
#define PCA9533_REG_READ 0x00
#define PCA9533_REG_PSC0 0x01
#define PCA9533_REG_PWM0 0x02
#define PCA9533_REG_PSC1 0x03
#define PCA9533_REG_PWM1 0x04
#define PCA9533_REG_SEL 0x05
#define PCA9533_REGM_AI 0x10
// LED selector operation
#define PCA9533_LED_OP_OFF 0B00
#define PCA9533_LED_OP_ON 0B01
#define PCA9533_LED_OP_PWM0 0B10
#define PCA9533_LED_OP_PWM1 0B11
// Select register bit offsets for LED colors
#define PCA9533_LED_OFS_RED 0
#define PCA9533_LED_OFS_GRN 2
#define PCA9533_LED_OFS_BLU 4
void PCA9533_init();
void PCA9533_reset();
void PCA9533_set_rgb(uint8_t red, uint8_t green, uint8_t blue);
void PCA9533_setOff();

48
Marlin/src/feature/leds/pca9632.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -58,7 +58,7 @@
#define PCA9632_AUTOGLO 0xC0
#define PCA9632_AUTOGI 0xE0
// Red=LED0 Green=LED1 Blue=LED2
// Red=LED0 Green=LED1 Blue=LED2 White=LED3
#ifndef PCA9632_RED
#define PCA9632_RED 0x00
#endif
@@ -68,9 +68,12 @@
#ifndef PCA9632_BLU
#define PCA9632_BLU 0x04
#endif
#if HAS_WHITE_LED && !defined(PCA9632_WHT)
#define PCA9632_WHT 0x06
#endif
// If any of the color indexes are greater than 0x04 they can't use auto increment
#if !defined(PCA9632_NO_AUTO_INC) && (PCA9632_RED > 0x04 || PCA9632_GRN > 0x04 || PCA9632_BLU > 0x04)
#if !defined(PCA9632_NO_AUTO_INC) && (PCA9632_RED > 0x04 || PCA9632_GRN > 0x04 || PCA9632_BLU > 0x04 || PCA9632_WHT > 0x04)
#define PCA9632_NO_AUTO_INC
#endif
@@ -89,25 +92,26 @@ static void PCA9632_WriteRegister(const byte addr, const byte regadd, const byte
Wire.endTransmission();
}
static void PCA9632_WriteAllRegisters(const byte addr, const byte regadd, const byte vr, const byte vg, const byte vb) {
static void PCA9632_WriteAllRegisters(const byte addr, const byte regadd, const byte vr, const byte vg, const byte vb
OPTARG(PCA9632_RGBW, const byte vw)
) {
#if DISABLED(PCA9632_NO_AUTO_INC)
uint8_t data[4], len = 4;
uint8_t data[4];
data[0] = PCA9632_AUTO_IND | regadd;
data[1 + (PCA9632_RED >> 1)] = vr;
data[1 + (PCA9632_GRN >> 1)] = vg;
data[1 + (PCA9632_BLU >> 1)] = vb;
Wire.beginTransmission(I2C_ADDRESS(addr));
Wire.write(data, sizeof(data));
Wire.endTransmission();
#else
uint8_t data[6], len = 6;
data[0] = regadd + (PCA9632_RED >> 1);
data[1] = vr;
data[2] = regadd + (PCA9632_GRN >> 1);
data[3] = vg;
data[4] = regadd + (PCA9632_BLU >> 1);
data[5] = vb;
PCA9632_WriteRegister(addr, regadd + (PCA9632_RED >> 1), vr);
PCA9632_WriteRegister(addr, regadd + (PCA9632_GRN >> 1), vg);
PCA9632_WriteRegister(addr, regadd + (PCA9632_BLU >> 1), vb);
#if ENABLED(PCA9632_RGBW)
PCA9632_WriteRegister(addr, regadd + (PCA9632_WHT >> 1), vw);
#endif
#endif
Wire.beginTransmission(I2C_ADDRESS(addr));
Wire.write(data, len);
Wire.endTransmission();
}
#if 0
@@ -120,7 +124,7 @@ static void PCA9632_WriteAllRegisters(const byte addr, const byte regadd, const
}
#endif
void pca9632_set_led_color(const LEDColor &color) {
void PCA9632_set_led_color(const LEDColor &color) {
Wire.begin();
if (!PCA_init) {
PCA_init = 1;
@@ -130,15 +134,21 @@ void pca9632_set_led_color(const LEDColor &color) {
const byte LEDOUT = (color.r ? LED_PWM << PCA9632_RED : 0)
| (color.g ? LED_PWM << PCA9632_GRN : 0)
| (color.b ? LED_PWM << PCA9632_BLU : 0);
| (color.b ? LED_PWM << PCA9632_BLU : 0)
#if ENABLED(PCA9632_RGBW)
| (color.w ? LED_PWM << PCA9632_WHT : 0)
#endif
;
PCA9632_WriteAllRegisters(PCA9632_ADDRESS,PCA9632_PWM0, color.r, color.g, color.b);
PCA9632_WriteAllRegisters(PCA9632_ADDRESS,PCA9632_PWM0, color.r, color.g, color.b
OPTARG(PCA9632_RGBW, color.w)
);
PCA9632_WriteRegister(PCA9632_ADDRESS,PCA9632_LEDOUT, LEDOUT);
}
#if ENABLED(PCA9632_BUZZER)
void pca9632_buzz(const long, const uint16_t) {
void PCA9632_buzz(const long, const uint16_t) {
uint8_t data[] = PCA9632_BUZZER_DATA;
Wire.beginTransmission(I2C_ADDRESS(PCA9632_ADDRESS));
Wire.write(data, sizeof(data));

6
Marlin/src/feature/leds/pca9632.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -29,9 +29,9 @@
struct LEDColor;
typedef LEDColor LEDColor;
void pca9632_set_led_color(const LEDColor &color);
void PCA9632_set_led_color(const LEDColor &color);
#if ENABLED(PCA9632_BUZZER)
#include <stdint.h>
void pca9632_buzz(const long, const uint16_t);
void PCA9632_buzz(const long, const uint16_t);
#endif

36
Marlin/src/feature/leds/printer_event_leds.cpp Executable file → Normal file
View File

@@ -16,12 +16,12 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
/**
* printer_event_leds.cpp - LED color changing based on printer status
* feature/leds/printer_event_leds.cpp - LED color changing based on printer status
*/
#include "../../inc/MarlinConfigPre.h"
@@ -40,24 +40,23 @@ PrinterEventLEDs printerEventLEDs;
uint8_t PrinterEventLEDs::old_intensity = 0;
inline uint8_t pel_intensity(const float &start, const float &current, const float &target) {
return (uint8_t)map(constrain(current, start, target), start, target, 0.f, 255.f);
inline uint8_t pel_intensity(const celsius_t start, const celsius_t current, const celsius_t target) {
if (start == target) return 255;
return (uint8_t)map(constrain(current, start, target), start, target, 0, 255);
}
inline void pel_set_rgb(const uint8_t r, const uint8_t g, const uint8_t b) {
inline void pel_set_rgb(const uint8_t r, const uint8_t g, const uint8_t b OPTARG(HAS_WHITE_LED, const uint8_t w=0)) {
leds.set_color(
MakeLEDColor(r, g, b, 0, neo.brightness())
#if ENABLED(NEOPIXEL_IS_SEQUENTIAL)
, true
#endif
);
LEDColor(r, g, b OPTARG(HAS_WHITE_LED, w) OPTARG(NEOPIXEL_LED, neo.brightness()))
OPTARG(NEOPIXEL_IS_SEQUENTIAL, true)
);
}
#endif
#if HAS_TEMP_HOTEND
void PrinterEventLEDs::onHotendHeating(const float &start, const float &current, const float &target) {
void PrinterEventLEDs::onHotendHeating(const celsius_t start, const celsius_t current, const celsius_t target) {
const uint8_t blue = pel_intensity(start, current, target);
if (blue != old_intensity) {
old_intensity = blue;
@@ -69,13 +68,26 @@ PrinterEventLEDs printerEventLEDs;
#if HAS_HEATED_BED
void PrinterEventLEDs::onBedHeating(const float &start, const float &current, const float &target) {
void PrinterEventLEDs::onBedHeating(const celsius_t start, const celsius_t current, const celsius_t target) {
const uint8_t red = pel_intensity(start, current, target);
if (red != old_intensity) {
old_intensity = red;
pel_set_rgb(red, 0, 255);
}
}
#endif
#if HAS_HEATED_CHAMBER
void PrinterEventLEDs::onChamberHeating(const celsius_t start, const celsius_t current, const celsius_t target) {
const uint8_t green = pel_intensity(start, current, target);
if (green != old_intensity) {
old_intensity = green;
pel_set_rgb(255, green, 255);
}
}
#endif
#endif // PRINTER_EVENT_LEDS

25
Marlin/src/feature/leds/printer_event_leds.h Executable file → Normal file
View File

@@ -16,13 +16,13 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
/**
* printer_event_leds.h - LED color changing based on printer status
* feature/leds/printer_event_leds.h - LED color changing based on printer status
*/
#include "leds.h"
@@ -36,27 +36,26 @@ private:
static bool leds_off_after_print;
#endif
static inline void set_done() {
#if ENABLED(LED_COLOR_PRESETS)
leds.set_default();
#else
leds.set_off();
#endif
}
static inline void set_done() { TERN(LED_COLOR_PRESETS, leds.set_default(), leds.set_off()); }
public:
#if HAS_TEMP_HOTEND
static inline LEDColor onHotendHeatingStart() { old_intensity = 0; return leds.get_color(); }
static void onHotendHeating(const float &start, const float &current, const float &target);
static void onHotendHeating(const celsius_t start, const celsius_t current, const celsius_t target);
#endif
#if HAS_HEATED_BED
static inline LEDColor onBedHeatingStart() { old_intensity = 127; return leds.get_color(); }
static void onBedHeating(const float &start, const float &current, const float &target);
static void onBedHeating(const celsius_t start, const celsius_t current, const celsius_t target);
#endif
#if HAS_TEMP_HOTEND || HAS_HEATED_BED
static inline void onHeatingDone() { leds.set_white(); }
#if HAS_HEATED_CHAMBER
static inline LEDColor onChamberHeatingStart() { old_intensity = 127; return leds.get_color(); }
static void onChamberHeating(const celsius_t start, const celsius_t current, const celsius_t target);
#endif
#if HAS_TEMP_HOTEND || HAS_HEATED_BED || HAS_HEATED_CHAMBER
static inline void onHeatingDone() { leds.set_white(); }
static inline void onPidTuningDone(LEDColor c) { leds.set_color(c); }
#endif

11
Marlin/src/feature/leds/tempstat.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -36,13 +36,10 @@ void handle_status_leds() {
static millis_t next_status_led_update_ms = 0;
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_HEATED_BED
max_temp = _MAX(thermalManager.degTargetBed(), thermalManager.degBed());
#endif
celsius_t max_temp = TERN0(HAS_HEATED_BED, _MAX(thermalManager.degTargetBed(), thermalManager.wholeDegBed()));
HOTEND_LOOP()
max_temp = _MAX(max_temp, thermalManager.degHotend(e), thermalManager.degTargetHotend(e));
const int8_t new_red = (max_temp > 55.0) ? HIGH : (max_temp < 54.0 || old_red < 0) ? LOW : old_red;
max_temp = _MAX(max_temp, thermalManager.wholeDegHotend(e), thermalManager.degTargetHotend(e));
const int8_t new_red = (max_temp > 55) ? HIGH : (max_temp < 54 || old_red < 0) ? LOW : old_red;
if (new_red != old_red) {
old_red = new_red;
#if PIN_EXISTS(STAT_LED_RED)

2
Marlin/src/feature/leds/tempstat.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once

8
Marlin/src/feature/max7219.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -127,10 +127,10 @@ uint8_t Max7219::suspended; // = 0;
void Max7219::error(const char * const func, const int32_t v1, const int32_t v2/*=-1*/) {
#if ENABLED(MAX7219_ERRORS)
SERIAL_ECHOPGM("??? Max7219::");
serialprintPGM(func);
SERIAL_ECHOPGM_P(func);
SERIAL_CHAR('(');
SERIAL_ECHO(v1);
if (v2 > 0) SERIAL_ECHOPAIR(", ", v2);
if (v2 > 0) SERIAL_ECHOPGM(", ", v2);
SERIAL_CHAR(')');
SERIAL_EOL();
#else
@@ -256,7 +256,7 @@ void Max7219::set(const uint8_t line, const uint8_t bits) {
}
// Draw a float with a decimal point and optional digits
void Max7219::print(const uint8_t start, const float value, const uint8_t pre_size, const uint8_t post_size, const bool leadzero=false) {
void Max7219::print(const uint8_t start, const_float_t value, const uint8_t pre_size, const uint8_t post_size, const bool leadzero=false) {
if (pre_size) print(start, value, pre_size, leadzero, !!post_size);
if (post_size) {
const int16_t after = ABS(value) * (10 ^ post_size);

9
Marlin/src/feature/max7219.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -100,6 +100,13 @@ public:
// Update a single native line on just one unit
static void refresh_unit_line(const uint8_t line);
#if ENABLED(MAX7219_NUMERIC)
// Draw an integer with optional leading zeros and optional decimal point
void print(const uint8_t start, int16_t value, uint8_t size, const bool leadzero=false, bool dec=false);
// Draw a float with a decimal point and optional digits
void print(const uint8_t start, const_float_t value, const uint8_t pre_size, const uint8_t post_size, const bool leadzero=false);
#endif
// Set a single LED by XY coordinate
static void led_set(const uint8_t x, const uint8_t y, const bool on);
static void led_on(const uint8_t x, const uint8_t y);

View File

@@ -39,10 +39,9 @@
#include "../inc/MarlinConfig.h"
#if ENABLED(MEATPACK)
#if HAS_MEATPACK
#include "meatpack.h"
MeatPack meatpack;
#define MeatPack_ProtocolVersion "PV01"
//#define MP_DEBUG
@@ -50,30 +49,24 @@ MeatPack meatpack;
#define DEBUG_OUT ENABLED(MP_DEBUG)
#include "../core/debug_out.h"
bool MeatPack::cmd_is_next = false; // A command is pending
uint8_t MeatPack::state = 0; // Configuration state OFF
uint8_t MeatPack::second_char = 0; // The unpacked 2nd character from an out-of-sequence packed pair
uint8_t MeatPack::cmd_count = 0, // Counts how many command bytes are received (need 2)
MeatPack::full_char_count = 0, // Counts how many full-width characters are to be received
MeatPack::char_out_count = 0; // Stores number of characters to be read out.
uint8_t MeatPack::char_out_buf[2]; // Output buffer for caching up to 2 characters
// The 15 most-common characters used in G-code, ~90-95% of all G-code uses these characters
// Stored in SRAM for performance.
uint8_t meatPackLookupTable[16] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'.', ' ', '\n', 'G', 'X',
'\0' // Unused. 0b1111 indicates a literal character
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'.', ' ', '\n', 'G', 'X',
'\0' // Unused. 0b1111 indicates a literal character
};
TERN_(MP_DEBUG, uint8_t chars_decoded = 0); // Log the first 64 bytes after each reset
#if ENABLED(MP_DEBUG)
uint8_t chars_decoded = 0; // Log the first 64 bytes after each reset
#endif
void MeatPack::reset_state() {
state = 0;
cmd_is_next = false;
second_char = 0;
cmd_count = full_char_count = char_out_count = 0;
TERN_(MP_DEBUG, chars_decoded = 0);
state = 0;
cmd_is_next = false;
second_char = 0;
cmd_count = full_char_count = char_out_count = 0;
TERN_(MP_DEBUG, chars_decoded = 0);
}
/**
@@ -81,25 +74,25 @@ void MeatPack::reset_state() {
* Return flags indicating whether any literal bytes follow.
*/
uint8_t MeatPack::unpack_chars(const uint8_t pk, uint8_t* __restrict const chars_out) {
uint8_t out = 0;
uint8_t out = 0;
// If lower nybble is 1111, the higher nybble is unused, and next char is full.
if ((pk & kFirstNotPacked) == kFirstNotPacked)
out = kFirstCharIsLiteral;
else {
const uint8_t chr = pk & 0x0F;
chars_out[0] = meatPackLookupTable[chr]; // Set the first char
}
// If lower nybble is 1111, the higher nybble is unused, and next char is full.
if ((pk & kFirstNotPacked) == kFirstNotPacked)
out = kFirstCharIsLiteral;
else {
const uint8_t chr = pk & 0x0F;
chars_out[0] = meatPackLookupTable[chr]; // Set the first char
}
// Check if upper nybble is 1111... if so, we don't need the second char.
if ((pk & kSecondNotPacked) == kSecondNotPacked)
out |= kSecondCharIsLiteral;
else {
const uint8_t chr = (pk >> 4) & 0x0F;
chars_out[1] = meatPackLookupTable[chr]; // Set the second char
}
// Check if upper nybble is 1111... if so, we don't need the second char.
if ((pk & kSecondNotPacked) == kSecondNotPacked)
out |= kSecondCharIsLiteral;
else {
const uint8_t chr = (pk >> 4) & 0x0F;
chars_out[1] = meatPackLookupTable[chr]; // Set the second char
}
return out;
return out;
}
/**
@@ -107,33 +100,33 @@ uint8_t MeatPack::unpack_chars(const uint8_t pk, uint8_t* __restrict const chars
* according to the current MeatPack state.
*/
void MeatPack::handle_rx_char_inner(const uint8_t c) {
if (TEST(state, MPConfig_Bit_Active)) { // Is MeatPack active?
if (!full_char_count) { // No literal characters to fetch?
uint8_t buf[2] = { 0, 0 };
register const uint8_t res = unpack_chars(c, buf); // Decode the byte into one or two characters.
if (res & kFirstCharIsLiteral) { // The 1st character couldn't be packed.
++full_char_count; // So the next stream byte is a full character.
if (res & kSecondCharIsLiteral) ++full_char_count; // The 2nd character couldn't be packed. Another stream byte is a full character.
else second_char = buf[1]; // Retain the unpacked second character.
}
else {
handle_output_char(buf[0]); // Send the unpacked first character out.
if (buf[0] != '\n') { // After a newline the next char won't be set
if (res & kSecondCharIsLiteral) ++full_char_count; // The 2nd character couldn't be packed. The next stream byte is a full character.
else handle_output_char(buf[1]); // Send the unpacked second character out.
}
}
}
else {
handle_output_char(c); // Pass through the character that couldn't be packed...
if (second_char) {
handle_output_char(second_char); // ...and send an unpacked 2nd character, if set.
second_char = 0;
}
--full_char_count; // One literal character was consumed
if (TEST(state, MPConfig_Bit_Active)) { // Is MeatPack active?
if (!full_char_count) { // No literal characters to fetch?
uint8_t buf[2] = { 0, 0 };
const uint8_t res = unpack_chars(c, buf); // Decode the byte into one or two characters.
if (res & kFirstCharIsLiteral) { // The 1st character couldn't be packed.
++full_char_count; // So the next stream byte is a full character.
if (res & kSecondCharIsLiteral) ++full_char_count; // The 2nd character couldn't be packed. Another stream byte is a full character.
else second_char = buf[1]; // Retain the unpacked second character.
}
else {
handle_output_char(buf[0]); // Send the unpacked first character out.
if (buf[0] != '\n') { // After a newline the next char won't be set
if (res & kSecondCharIsLiteral) ++full_char_count; // The 2nd character couldn't be packed. The next stream byte is a full character.
else handle_output_char(buf[1]); // Send the unpacked second character out.
}
}
}
else // Packing not enabled, just copy character to output
else {
handle_output_char(c); // Pass through the character that couldn't be packed...
if (second_char) {
handle_output_char(second_char); // ...and send an unpacked 2nd character, if set.
second_char = 0;
}
--full_char_count; // One literal character was consumed
}
}
else // Packing not enabled, just copy character to output
handle_output_char(c);
}
@@ -142,14 +135,12 @@ void MeatPack::handle_rx_char_inner(const uint8_t c) {
* GCodeQueue::get_serial_commands via calls to get_result_char
*/
void MeatPack::handle_output_char(const uint8_t c) {
char_out_buf[char_out_count++] = c;
char_out_buf[char_out_count++] = c;
#if ENABLED(MP_DEBUG)
if (chars_decoded < 1024) {
++chars_decoded;
DEBUG_ECHOPGM("RB: ");
MYSERIAL.print((char)c);
DEBUG_EOL();
++chars_decoded;
DEBUG_ECHOLNPGM("RB: ", AS_CHAR(c));
}
#endif
}
@@ -159,29 +150,29 @@ void MeatPack::handle_output_char(const uint8_t c) {
* Report the new state to serial.
*/
void MeatPack::handle_command(const MeatPack_Command c) {
switch (c) {
case MPCommand_QueryConfig: break;
case MPCommand_EnablePacking: SBI(state, MPConfig_Bit_Active); DEBUG_ECHOLNPGM("[MPDBG] ENA REC"); break;
case MPCommand_DisablePacking: CBI(state, MPConfig_Bit_Active); DEBUG_ECHOLNPGM("[MPDBG] DIS REC"); break;
case MPCommand_ResetAll: reset_state(); DEBUG_ECHOLNPGM("[MPDBG] RESET REC"); break;
case MPCommand_EnableNoSpaces:
SBI(state, MPConfig_Bit_NoSpaces);
meatPackLookupTable[kSpaceCharIdx] = kSpaceCharReplace; DEBUG_ECHOLNPGM("[MPDBG] ENA NSP"); break;
case MPCommand_DisableNoSpaces:
CBI(state, MPConfig_Bit_NoSpaces);
meatPackLookupTable[kSpaceCharIdx] = ' '; DEBUG_ECHOLNPGM("[MPDBG] DIS NSP"); break;
default: DEBUG_ECHOLNPGM("[MPDBG] UNK CMD REC");
}
report_state();
switch (c) {
case MPCommand_QueryConfig: break;
case MPCommand_EnablePacking: SBI(state, MPConfig_Bit_Active); DEBUG_ECHOLNPGM("[MPDBG] ENA REC"); break;
case MPCommand_DisablePacking: CBI(state, MPConfig_Bit_Active); DEBUG_ECHOLNPGM("[MPDBG] DIS REC"); break;
case MPCommand_ResetAll: reset_state(); DEBUG_ECHOLNPGM("[MPDBG] RESET REC"); break;
case MPCommand_EnableNoSpaces:
SBI(state, MPConfig_Bit_NoSpaces);
meatPackLookupTable[kSpaceCharIdx] = kSpaceCharReplace; DEBUG_ECHOLNPGM("[MPDBG] ENA NSP"); break;
case MPCommand_DisableNoSpaces:
CBI(state, MPConfig_Bit_NoSpaces);
meatPackLookupTable[kSpaceCharIdx] = ' '; DEBUG_ECHOLNPGM("[MPDBG] DIS NSP"); break;
default: DEBUG_ECHOLNPGM("[MPDBG] UNK CMD REC");
}
report_state();
}
void MeatPack::report_state() {
// NOTE: if any configuration vars are added below, the outgoing sync text for host plugin
// should not contain the "PV' substring, as this is used to indicate protocol version
SERIAL_ECHOPGM("[MP] ");
SERIAL_ECHOPGM(MeatPack_ProtocolVersion " ");
serialprint_onoff(TEST(state, MPConfig_Bit_Active));
serialprintPGM(TEST(state, MPConfig_Bit_NoSpaces) ? PSTR(" NSP\n") : PSTR(" ESP\n"));
// NOTE: if any configuration vars are added below, the outgoing sync text for host plugin
// should not contain the "PV' substring, as this is used to indicate protocol version
SERIAL_ECHOPGM("[MP] ");
SERIAL_ECHOPGM(MeatPack_ProtocolVersion " ");
serialprint_onoff(TEST(state, MPConfig_Bit_Active));
SERIAL_ECHOPGM_P(TEST(state, MPConfig_Bit_NoSpaces) ? PSTR(" NSP\n") : PSTR(" ESP\n"));
}
/**
@@ -189,40 +180,40 @@ void MeatPack::report_state() {
* according to the current meatpack state.
*/
void MeatPack::handle_rx_char(const uint8_t c, const serial_index_t serial_ind) {
if (c == kCommandByte) { // A command (0xFF) byte?
if (cmd_count) { // In fact, two in a row?
cmd_is_next = true; // Then a MeatPack command follows
cmd_count = 0;
}
else
++cmd_count; // cmd_count = 1 // One command byte received so far...
return;
if (c == kCommandByte) { // A command (0xFF) byte?
if (cmd_count) { // In fact, two in a row?
cmd_is_next = true; // Then a MeatPack command follows
cmd_count = 0;
}
else
++cmd_count; // cmd_count = 1 // One command byte received so far...
return;
}
if (cmd_is_next) { // Were two command bytes received?
PORT_REDIRECT(serial_ind);
handle_command((MeatPack_Command)c); // Then the byte is a MeatPack command
cmd_is_next = false;
return;
}
if (cmd_is_next) { // Were two command bytes received?
PORT_REDIRECT(SERIAL_PORTMASK(serial_ind));
handle_command((MeatPack_Command)c); // Then the byte is a MeatPack command
cmd_is_next = false;
return;
}
if (cmd_count) { // Only a single 0xFF was received
handle_rx_char_inner(kCommandByte); // A single 0xFF is passed on literally so it can be interpreted as kFirstNotPacked|kSecondNotPacked
cmd_count = 0;
}
if (cmd_count) { // Only a single 0xFF was received
handle_rx_char_inner(kCommandByte); // A single 0xFF is passed on literally so it can be interpreted as kFirstNotPacked|kSecondNotPacked
cmd_count = 0;
}
handle_rx_char_inner(c); // Other characters are passed on for MeatPack decoding
handle_rx_char_inner(c); // Other characters are passed on for MeatPack decoding
}
uint8_t MeatPack::get_result_char(char* const __restrict out) {
uint8_t res = 0;
if (char_out_count) {
res = char_out_count;
char_out_count = 0;
for (register uint8_t i = 0; i < res; ++i)
out[i] = (char)char_out_buf[i];
}
return res;
uint8_t MeatPack::get_result_char(char * const __restrict out) {
uint8_t res = 0;
if (char_out_count) {
res = char_out_count;
char_out_count = 0;
for (uint8_t i = 0; i < res; ++i)
out[i] = (char)char_out_buf[i];
}
return res;
}
#endif // MEATPACK
#endif // HAS_MEATPACK

View File

@@ -49,6 +49,7 @@
#pragma once
#include <stdint.h>
#include "../core/serial_hook.h"
/**
* Commands sent to MeatPack to control its behavior.
@@ -63,61 +64,112 @@
* some non-0xFF character.
*/
enum MeatPack_Command : uint8_t {
MPCommand_None = 0,
MPCommand_EnablePacking = 0xFB,
MPCommand_DisablePacking = 0xFA,
MPCommand_ResetAll = 0xF9,
MPCommand_QueryConfig = 0xF8,
MPCommand_EnableNoSpaces = 0xF7,
MPCommand_DisableNoSpaces = 0xF6
MPCommand_None = 0,
MPCommand_EnablePacking = 0xFB,
MPCommand_DisablePacking = 0xFA,
MPCommand_ResetAll = 0xF9,
MPCommand_QueryConfig = 0xF8,
MPCommand_EnableNoSpaces = 0xF7,
MPCommand_DisableNoSpaces = 0xF6
};
enum MeatPack_ConfigStateBits : uint8_t {
MPConfig_Bit_Active = 0,
MPConfig_Bit_NoSpaces = 1
MPConfig_Bit_Active = 0,
MPConfig_Bit_NoSpaces = 1
};
class MeatPack {
private:
friend class GCodeQueue;
// Utility definitions
static const uint8_t kCommandByte = 0b11111111,
kFirstNotPacked = 0b00001111,
kSecondNotPacked = 0b11110000,
kFirstCharIsLiteral = 0b00000001,
kSecondCharIsLiteral = 0b00000010;
// Utility definitions
static const uint8_t kCommandByte = 0b11111111,
kFirstNotPacked = 0b00001111,
kSecondNotPacked = 0b11110000,
kFirstCharIsLiteral = 0b00000001,
kSecondCharIsLiteral = 0b00000010;
static const uint8_t kSpaceCharIdx = 11;
static const char kSpaceCharReplace = 'E';
static const uint8_t kSpaceCharIdx = 11;
static const char kSpaceCharReplace = 'E';
static bool cmd_is_next; // A command is pending
static uint8_t state; // Configuration state
static uint8_t second_char; // Buffers a character if dealing with out-of-sequence pairs
static uint8_t cmd_count, // Counter of command bytes received (need 2)
full_char_count, // Counter for full-width characters to be received
char_out_count; // Stores number of characters to be read out.
static uint8_t char_out_buf[2]; // Output buffer for caching up to 2 characters
bool cmd_is_next; // A command is pending
uint8_t state; // Configuration state
uint8_t second_char; // Buffers a character if dealing with out-of-sequence pairs
uint8_t cmd_count, // Counter of command bytes received (need 2)
full_char_count, // Counter for full-width characters to be received
char_out_count; // Stores number of characters to be read out.
uint8_t char_out_buf[2]; // Output buffer for caching up to 2 characters
// Pass in a character rx'd by SD card or serial. Automatically parses command/ctrl sequences,
// and will control state internally.
static void handle_rx_char(const uint8_t c, const serial_index_t serial_ind);
public:
// Pass in a character rx'd by SD card or serial. Automatically parses command/ctrl sequences,
// and will control state internally.
void handle_rx_char(const uint8_t c, const serial_index_t serial_ind);
/**
* After passing in rx'd char using above method, call this to get characters out.
* Can return from 0 to 2 characters at once.
* @param out [in] Output pointer for unpacked/processed data.
* @return Number of characters returned. Range from 0 to 2.
*/
static uint8_t get_result_char(char* const __restrict out);
/**
* After passing in rx'd char using above method, call this to get characters out.
* Can return from 0 to 2 characters at once.
* @param out [in] Output pointer for unpacked/processed data.
* @return Number of characters returned. Range from 0 to 2.
*/
uint8_t get_result_char(char * const __restrict out);
static void reset_state();
static void report_state();
static uint8_t unpacked_char(register const uint8_t in);
static uint8_t unpack_chars(const uint8_t pk, uint8_t* __restrict const chars_out);
static void handle_command(const MeatPack_Command c);
static void handle_output_char(const uint8_t c);
static void handle_rx_char_inner(const uint8_t c);
void reset_state();
void report_state();
uint8_t unpack_chars(const uint8_t pk, uint8_t* __restrict const chars_out);
void handle_command(const MeatPack_Command c);
void handle_output_char(const uint8_t c);
void handle_rx_char_inner(const uint8_t c);
MeatPack() : cmd_is_next(false), state(0), second_char(0), cmd_count(0), full_char_count(0), char_out_count(0) {}
};
extern MeatPack meatpack;
// Implement the MeatPack serial class so it's transparent to rest of the code
template <typename SerialT>
struct MeatpackSerial : public SerialBase <MeatpackSerial < SerialT >> {
typedef SerialBase< MeatpackSerial<SerialT> > BaseClassT;
SerialT & out;
MeatPack meatpack;
char serialBuffer[2];
uint8_t charCount;
uint8_t readIndex;
NO_INLINE void write(uint8_t c) { out.write(c); }
void flush() { out.flush(); }
void begin(long br) { out.begin(br); readIndex = 0; }
void end() { out.end(); }
void msgDone() { out.msgDone(); }
// Existing instances implement Arduino's operator bool, so use that if it's available
bool connected() { return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; }
void flushTX() { CALL_IF_EXISTS(void, &out, flushTX); }
SerialFeature features(serial_index_t index) const { return SerialFeature::MeatPack | CALL_IF_EXISTS(SerialFeature, &out, features, index); }
int available(serial_index_t index) {
if (charCount) return charCount; // The buffer still has data
if (out.available(index) <= 0) return 0; // No data to read
// Don't read in read method, instead do it here, so we can make progress in the read method
const int r = out.read(index);
if (r == -1) return 0; // This is an error from the underlying serial code
meatpack.handle_rx_char((uint8_t)r, index);
charCount = meatpack.get_result_char(serialBuffer);
readIndex = 0;
return charCount;
}
int readImpl(const serial_index_t index) {
// Not enough char to make progress?
if (charCount == 0 && available(index) == 0) return -1;
charCount--;
return serialBuffer[readIndex++];
}
int read(serial_index_t index) { return readImpl(index); }
int available() { return available(0); }
int read() { return readImpl(0); }
MeatpackSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {}
};

68
Marlin/src/feature/mixing.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -44,7 +44,7 @@ int_fast8_t Mixer::runner = 0;
mixer_comp_t Mixer::s_color[MIXING_STEPPERS];
mixer_accu_t Mixer::accu[MIXING_STEPPERS] = { 0 };
#if DUAL_MIXING_EXTRUDER || ENABLED(GRADIENT_MIX)
#if EITHER(HAS_DUAL_MIXING, GRADIENT_MIX)
mixer_perc_t Mixer::mix[MIXING_STEPPERS];
#endif
@@ -90,15 +90,13 @@ void Mixer::normalize(const uint8_t tool_index) {
SERIAL_ECHOLNPGM("]");
#endif
#if ENABLED(GRADIENT_MIX)
refresh_gradient();
#endif
TERN_(GRADIENT_MIX, refresh_gradient());
}
void Mixer::reset_vtools() {
// Virtual Tools 0, 1, 2, 3 = Filament 1, 2, 3, 4, etc.
// Every virtual tool gets a pure filament
LOOP_L_N(t, MIXING_VIRTUAL_TOOLS && t < MIXING_STEPPERS)
LOOP_L_N(t, _MIN(MIXING_VIRTUAL_TOOLS, MIXING_STEPPERS))
MIXER_STEPPER_LOOP(i)
color[t][i] = (t == i) ? COLOR_A_MASK : 0;
@@ -108,28 +106,45 @@ void Mixer::reset_vtools() {
MIXER_STEPPER_LOOP(i)
color[t][i] = (i == 0) ? COLOR_A_MASK : 0;
#endif
// MIXING_PRESETS: Set a variety of obvious mixes as presets
#if ENABLED(MIXING_PRESETS) && WITHIN(MIXING_STEPPERS, 2, 3)
#if MIXING_STEPPERS == 2
if (MIXING_VIRTUAL_TOOLS > 2) { collector[0] = 1; collector[1] = 1; mixer.normalize(2); } // 1:1
if (MIXING_VIRTUAL_TOOLS > 3) { collector[0] = 3; mixer.normalize(3); } // 3:1
if (MIXING_VIRTUAL_TOOLS > 4) { collector[0] = 1; collector[1] = 3; mixer.normalize(4); } // 1:3
if (MIXING_VIRTUAL_TOOLS > 5) { collector[1] = 2; mixer.normalize(5); } // 1:2
if (MIXING_VIRTUAL_TOOLS > 6) { collector[0] = 2; collector[1] = 1; mixer.normalize(6); } // 2:1
if (MIXING_VIRTUAL_TOOLS > 7) { collector[0] = 3; collector[1] = 2; mixer.normalize(7); } // 3:2
#else
if (MIXING_VIRTUAL_TOOLS > 3) { collector[0] = 1; collector[1] = 1; collector[2] = 1; mixer.normalize(3); } // 1:1:1
if (MIXING_VIRTUAL_TOOLS > 4) { collector[1] = 3; collector[2] = 0; mixer.normalize(4); } // 1:3:0
if (MIXING_VIRTUAL_TOOLS > 5) { collector[0] = 0; collector[2] = 1; mixer.normalize(5); } // 0:3:1
if (MIXING_VIRTUAL_TOOLS > 6) { collector[1] = 1; mixer.normalize(6); } // 0:1:1
if (MIXING_VIRTUAL_TOOLS > 7) { collector[0] = 1; collector[2] = 0; mixer.normalize(7); } // 1:1:0
#endif
ZERO(collector);
#endif
}
// called at boot
void Mixer::init() {
ZERO(collector);
reset_vtools();
#if ENABLED(RETRACT_SYNC_MIXING)
#if HAS_MIXER_SYNC_CHANNEL
// AUTORETRACT_TOOL gets the same amount of all filaments
MIXER_STEPPER_LOOP(i)
color[MIXER_AUTORETRACT_TOOL][i] = COLOR_A_MASK;
#endif
ZERO(collector);
#if DUAL_MIXING_EXTRUDER || ENABLED(GRADIENT_MIX)
#if EITHER(HAS_DUAL_MIXING, GRADIENT_MIX)
update_mix_from_vtool();
#endif
#if ENABLED(GRADIENT_MIX)
update_gradient_for_planner_z();
#endif
TERN_(GRADIENT_MIX, update_gradient_for_planner_z());
}
void Mixer::refresh_collector(const float proportion/*=1.0*/, const uint8_t t/*=selected_vtool*/, float (&c)[MIXING_STEPPERS]/*=collector*/) {
@@ -139,11 +154,11 @@ void Mixer::refresh_collector(const float proportion/*=1.0*/, const uint8_t t/*=
cmax = _MAX(cmax, v);
csum += v;
}
//SERIAL_ECHOPAIR("Mixer::refresh_collector(", proportion, ", ", int(t), ") cmax=", cmax, " csum=", csum, " color");
//SERIAL_ECHOPGM("Mixer::refresh_collector(", proportion, ", ", t, ") cmax=", cmax, " csum=", csum, " color");
const float inv_prop = proportion / csum;
MIXER_STEPPER_LOOP(i) {
c[i] = color[t][i] * inv_prop;
//SERIAL_ECHOPAIR(" [", int(t), "][", int(i), "] = ", int(color[t][i]), " (", c[i], ") ");
//SERIAL_ECHOPGM(" [", t, "][", i, "] = ", color[t][i], " (", c[i], ") ");
}
//SERIAL_EOL();
}
@@ -154,19 +169,17 @@ void Mixer::refresh_collector(const float proportion/*=1.0*/, const uint8_t t/*=
#include "../module/planner.h"
gradient_t Mixer::gradient = {
false, // enabled
{0}, // color (array)
0, 0, // start_z, end_z
0, 1, // start_vtool, end_vtool
{0}, {0} // start_mix[], end_mix[]
#if ENABLED(GRADIENT_VTOOL)
, -1 // vtool_index
#endif
false, // enabled
{0}, // color (array)
0, 0, // start_z, end_z
0, 1, // start_vtool, end_vtool
{0}, {0} // start_mix[], end_mix[]
OPTARG(GRADIENT_VTOOL, -1) // vtool_index
};
float Mixer::prev_z; // = 0
void Mixer::update_gradient_for_z(const float z) {
void Mixer::update_gradient_for_z(const_float_t z) {
if (z == prev_z) return;
prev_z = z;
@@ -184,7 +197,12 @@ void Mixer::refresh_collector(const float proportion/*=1.0*/, const uint8_t t/*=
}
void Mixer::update_gradient_for_planner_z() {
update_gradient_for_z(planner.get_axis_position_mm(Z_AXIS));
#if ENABLED(DELTA)
get_cartesian_from_steppers();
update_gradient_for_z(cartes.z);
#else
update_gradient_for_z(planner.get_axis_position_mm(Z_AXIS));
#endif
}
#endif // GRADIENT_MIX

71
Marlin/src/feature/mixing.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -25,7 +25,7 @@
//#define MIXER_NORMALIZER_DEBUG
#ifndef __AVR__ // || DUAL_MIXING_EXTRUDER
#ifndef __AVR__ // || HAS_DUAL_MIXING
// Use 16-bit (or fastest) data for the integer mix factors
typedef uint_fast16_t mixer_comp_t;
typedef uint_fast16_t mixer_accu_t;
@@ -48,29 +48,20 @@ typedef int8_t mixer_perc_t;
#endif
enum MixTool {
FIRST_USER_VIRTUAL_TOOL = 0,
LAST_USER_VIRTUAL_TOOL = MIXING_VIRTUAL_TOOLS - 1,
NR_USER_VIRTUAL_TOOLS,
MIXER_DIRECT_SET_TOOL = NR_USER_VIRTUAL_TOOLS,
#if ENABLED(RETRACT_SYNC_MIXING)
MIXER_AUTORETRACT_TOOL,
FIRST_USER_VIRTUAL_TOOL = 0
, LAST_USER_VIRTUAL_TOOL = MIXING_VIRTUAL_TOOLS - 1
, NR_USER_VIRTUAL_TOOLS
, MIXER_DIRECT_SET_TOOL = NR_USER_VIRTUAL_TOOLS
#if HAS_MIXER_SYNC_CHANNEL
, MIXER_AUTORETRACT_TOOL
#endif
NR_MIXING_VIRTUAL_TOOLS
, NR_MIXING_VIRTUAL_TOOLS
};
#if ENABLED(RETRACT_SYNC_MIXING)
#define MAX_VTOOLS 254
#else
#define MAX_VTOOLS 255
#endif
#define MAX_VTOOLS TERN(HAS_MIXER_SYNC_CHANNEL, 254, 255)
static_assert(NR_MIXING_VIRTUAL_TOOLS <= MAX_VTOOLS, "MIXING_VIRTUAL_TOOLS must be <= " STRINGIFY(MAX_VTOOLS) "!");
#define MIXER_STEPPER_LOOP(VAR) \
for (uint_fast8_t VAR = 0; VAR < MIXING_STEPPERS; VAR++)
#define MIXER_BLOCK_FIELD mixer_comp_t b_color[MIXING_STEPPERS]
#define MIXER_POPULATE_BLOCK() mixer.populate_block(block->b_color)
#define MIXER_STEPPER_SETUP() mixer.stepper_setup(current_block->b_color)
#define MIXER_STEPPER_LOOP(VAR) for (uint_fast8_t VAR = 0; VAR < MIXING_STEPPERS; VAR++)
#if ENABLED(GRADIENT_MIX)
@@ -79,7 +70,7 @@ static_assert(NR_MIXING_VIRTUAL_TOOLS <= MAX_VTOOLS, "MIXING_VIRTUAL_TOOLS must
mixer_comp_t color[MIXING_STEPPERS]; // The current gradient color
float start_z, end_z; // Region for gradient
int8_t start_vtool, end_vtool; // Start and end virtual tools
mixer_perc_t start_mix[MIXING_STEPPERS], // Start and end mixes from those tools
mixer_perc_t start_mix[MIXING_STEPPERS], // Start and end mixes from those tools
end_mix[MIXING_STEPPERS];
#if ENABLED(GRADIENT_VTOOL)
int8_t vtool_index; // Use this virtual tool number as index
@@ -112,12 +103,8 @@ class Mixer {
FORCE_INLINE static void T(const uint_fast8_t c) {
selected_vtool = c;
#if ENABLED(GRADIENT_VTOOL)
refresh_gradient();
#endif
#if DUAL_MIXING_EXTRUDER
update_mix_from_vtool();
#endif
TERN_(GRADIENT_VTOOL, refresh_gradient());
TERN_(HAS_DUAL_MIXING, update_mix_from_vtool());
}
// Used when dealing with blocks
@@ -135,7 +122,7 @@ class Mixer {
MIXER_STEPPER_LOOP(i) s_color[i] = b_color[i];
}
#if DUAL_MIXING_EXTRUDER || ENABLED(GRADIENT_MIX)
#if EITHER(HAS_DUAL_MIXING, GRADIENT_MIX)
static mixer_perc_t mix[MIXING_STEPPERS]; // Scratch array for the Mix in proportion to 100
@@ -151,9 +138,9 @@ class Mixer {
#ifdef MIXER_NORMALIZER_DEBUG
SERIAL_ECHOPGM("Mix [ ");
SERIAL_ECHOLIST_N(MIXING_STEPPERS, int(mix[0]), int(mix[1]), int(mix[2]), int(mix[3]), int(mix[4]), int(mix[5]));
SERIAL_ECHOLIST_N(MIXING_STEPPERS, mix[0], mix[1], mix[2], mix[3], mix[4], mix[5]);
SERIAL_ECHOPGM(" ] to Color [ ");
SERIAL_ECHOLIST_N(MIXING_STEPPERS, int(tcolor[0]), int(tcolor[1]), int(tcolor[2]), int(tcolor[3]), int(tcolor[4]), int(tcolor[5]));
SERIAL_ECHOLIST_N(MIXING_STEPPERS, tcolor[0], tcolor[1], tcolor[2], tcolor[3], tcolor[4], tcolor[5]);
SERIAL_ECHOLNPGM(" ]");
#endif
}
@@ -165,29 +152,27 @@ class Mixer {
MIXER_STEPPER_LOOP(i) mix[i] = mixer_perc_t(100.0f * color[j][i] / ctot);
#ifdef MIXER_NORMALIZER_DEBUG
SERIAL_ECHOPAIR("V-tool ", int(j), " [ ");
SERIAL_ECHOLIST_N(MIXING_STEPPERS, int(color[j][0]), int(color[j][1]), int(color[j][2]), int(color[j][3]), int(color[j][4]), int(color[j][5]));
SERIAL_ECHOPGM("V-tool ", j, " [ ");
SERIAL_ECHOLIST_N(MIXING_STEPPERS, color[j][0], color[j][1], color[j][2], color[j][3], color[j][4], color[j][5]);
SERIAL_ECHOPGM(" ] to Mix [ ");
SERIAL_ECHOLIST_N(MIXING_STEPPERS, int(mix[0]), int(mix[1]), int(mix[2]), int(mix[3]), int(mix[4]), int(mix[5]));
SERIAL_ECHOLIST_N(MIXING_STEPPERS, mix[0], mix[1], mix[2], mix[3], mix[4], mix[5]);
SERIAL_ECHOLNPGM(" ]");
#endif
}
#endif // DUAL_MIXING_EXTRUDER || GRADIENT_MIX
#endif // HAS_DUAL_MIXING || GRADIENT_MIX
#if DUAL_MIXING_EXTRUDER
#if HAS_DUAL_MIXING
// Update the virtual tool from an edited mix
static inline void update_vtool_from_mix() {
copy_mix_to_color(color[selected_vtool]);
#if ENABLED(GRADIENT_MIX)
refresh_gradient();
#endif
TERN_(GRADIENT_MIX, refresh_gradient());
// MIXER_STEPPER_LOOP(i) collector[i] = mix[i];
// normalize();
}
#endif // DUAL_MIXING_EXTRUDER
#endif // HAS_DUAL_MIXING
#if ENABLED(GRADIENT_MIX)
@@ -195,9 +180,9 @@ class Mixer {
static float prev_z;
// Update the current mix from the gradient for a given Z
static void update_gradient_for_z(const float z);
static void update_gradient_for_z(const_float_t z);
static void update_gradient_for_planner_z();
static inline void gradient_control(const float z) {
static inline void gradient_control(const_float_t z) {
if (gradient.enabled) {
if (z >= gradient.end_z)
T(gradient.end_vtool);
@@ -213,9 +198,9 @@ class Mixer {
#ifdef MIXER_NORMALIZER_DEBUG
SERIAL_ECHOPGM("Gradient [ ");
SERIAL_ECHOLIST_N(MIXING_STEPPERS, int(gradient.color[0]), int(gradient.color[1]), int(gradient.color[2]), int(gradient.color[3]), int(gradient.color[4]), int(gradient.color[5]));
SERIAL_ECHOLIST_N(MIXING_STEPPERS, gradient.color[0], gradient.color[1], gradient.color[2], gradient.color[3], gradient.color[4], gradient.color[5]);
SERIAL_ECHOPGM(" ] to Mix [ ");
SERIAL_ECHOLIST_N(MIXING_STEPPERS, int(mix[0]), int(mix[1]), int(mix[2]), int(mix[3]), int(mix[4]), int(mix[5]));
SERIAL_ECHOLIST_N(MIXING_STEPPERS, mix[0], mix[1], mix[2], mix[3], mix[4], mix[5]);
SERIAL_ECHOLNPGM(" ]");
#endif
}

View File

@@ -16,23 +16,31 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "../inc/MarlinConfig.h"
#include "../../inc/MarlinConfig.h"
#if ENABLED(MK2_MULTIPLEXER)
#if HAS_PRUSA_MMU1
#include "../MarlinCore.h"
#include "../module/planner.h"
#include "../module/stepper.h"
void mmu_init() {
SET_OUTPUT(E_MUX0_PIN);
SET_OUTPUT(E_MUX1_PIN);
SET_OUTPUT(E_MUX2_PIN);
}
void select_multiplexed_stepper(const uint8_t e) {
planner.synchronize();
disable_e_steppers();
stepper.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
#endif // HAS_PRUSA_MMU1

View File

@@ -16,9 +16,10 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
void mmu_init();
void select_multiplexed_stepper(const uint8_t e);

View File

@@ -26,7 +26,7 @@ Now we are sure MMU is available and ready. If there was a timeout or other comm
- *Firmware version* is an integer value, but we don't care about it
- *Build number* is an integer value and has to be >=126, or =>132 if 12V mode is enabled
- *FINDA status* is 1 if the is filament loaded to the extruder, 0 otherwise
- *FINDA status* is 1 if the filament is loaded to the extruder, 0 otherwise
*Build number* is checked against the required value, if it does not match, printer is halted.

915
Marlin/src/feature/mmu2/mmu2.cpp Executable file → Normal file

File diff suppressed because it is too large Load Diff

54
Marlin/src/feature/mmu2/mmu2.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -43,25 +43,24 @@ public:
static void init();
static void reset();
static inline bool enabled() { return _enabled; }
static void mmu_loop();
static void tool_change(uint8_t index);
static void tool_change(const char* special);
static void tool_change(const uint8_t index);
static void tool_change(const char *special);
static uint8_t get_current_tool();
static void set_filament_type(uint8_t index, uint8_t type);
static void set_filament_type(const uint8_t index, const uint8_t type);
#if HAS_LCD_MENU && ENABLED(MMU2_MENUS)
static bool unload();
static void load_filament(uint8_t);
static void load_all();
static bool load_filament_to_nozzle(uint8_t index);
static bool eject_filament(uint8_t index, bool recover);
#endif
static bool unload();
static void load_filament(uint8_t);
static void load_all();
static bool load_filament_to_nozzle(const uint8_t index);
static bool eject_filament(const uint8_t index, const bool recover);
private:
static bool rx_str_P(const char* str);
static void tx_str_P(const char* str);
static void tx_printf_P(const char* format, int argument);
static void tx_printf_P(const char* format, int argument1, int argument2);
static bool rx_str_P(const char *str);
static void tx_str_P(const char *str);
static void tx_printf_P(const char *format, const int argument);
static void tx_printf_P(const char *format, const int argument1, const int argument2);
static void clear_rx_buffer();
static bool rx_ok();
@@ -72,21 +71,32 @@ private:
static bool get_response();
static void manage_response(const bool move_axes, const bool turn_off_nozzle);
#if HAS_LCD_MENU && ENABLED(MMU2_MENUS)
static void load_to_nozzle();
static void filament_ramming();
static void execute_extruder_sequence(const E_Step * sequence, int steps);
#endif
static void load_to_nozzle();
static void execute_extruder_sequence(const E_Step * sequence, int steps);
static void filament_runout();
static bool enabled, ready, mmu_print_saved;
#if HAS_PRUSA_MMU2S
static bool mmu2s_triggered;
static void check_filament();
static bool can_load();
static bool load_to_gears();
#else
FORCE_INLINE static bool load_to_gears() { return true; }
#endif
#if ENABLED(MMU_EXTRUDER_SENSOR)
static void mmu_continue_loading();
#endif
static bool _enabled, ready, mmu_print_saved;
static uint8_t cmd, cmd_arg, last_cmd, extruder;
static int8_t state;
static volatile int8_t finda;
static volatile bool finda_runout_valid;
static int16_t version, buildnr;
static millis_t last_request, next_P0_request;
static millis_t prev_request, prev_P0_request;
static char rx_buffer[MMU_RX_SIZE], tx_buffer[MMU_TX_SIZE];
static inline void set_runout_valid(const bool valid) {

View File

@@ -0,0 +1,61 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#include "../../inc/MarlinConfigPre.h"
#if ENABLED(PASSWORD_FEATURE)
#include "password.h"
#include "../../gcode/gcode.h"
#include "../../core/serial.h"
Password password;
// public:
bool Password::is_set, Password::is_locked, Password::did_first_run; // = false
uint32_t Password::value, Password::value_entry;
//
// Authenticate user with password.
// Called from Setup, after SD Prinitng Stops/Aborts, and M510
//
void Password::lock_machine() {
is_locked = true;
TERN_(HAS_LCD_MENU, authenticate_user(ui.status_screen, screen_password_entry));
}
//
// Authentication check
//
void Password::authentication_check() {
if (value_entry == value) {
is_locked = false;
did_first_run = true;
}
else {
is_locked = true;
SERIAL_ECHOLNPGM(STR_WRONG_PASSWORD);
}
TERN_(HAS_LCD_MENU, authentication_done());
}
#endif // PASSWORD_FEATURE

View File

@@ -0,0 +1,57 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "../../lcd/marlinui.h"
class Password {
public:
static bool is_set, is_locked, did_first_run;
static uint32_t value, value_entry;
Password() {}
static void lock_machine();
static void authentication_check();
#if HAS_LCD_MENU
static void access_menu_password();
static void authentication_done();
static void media_gatekeeper();
private:
static void authenticate_user(const screenFunc_t, const screenFunc_t);
static void menu_password();
static void menu_password_entry();
static void screen_password_entry();
static void screen_set_password();
static void start_over();
static void digit_entered();
static void set_password_done(const bool with_set=true);
static void menu_password_report();
static void remove_password();
#endif
};
extern Password password;

497
Marlin/src/feature/pause.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -29,6 +29,8 @@
#if ENABLED(ADVANCED_PAUSE_FEATURE)
//#define DEBUG_PAUSE_RESUME
#include "../MarlinCore.h"
#include "../gcode/gcode.h"
#include "../module/motion.h"
@@ -51,23 +53,31 @@
#if ENABLED(EXTENSIBLE_UI)
#include "../lcd/extui/ui_api.h"
#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
#include "../lcd/e3v2/enhanced/dwin.h"
#endif
#include "../core/language.h"
#include "../lcd/ultralcd.h"
#include "../lcd/marlinui.h"
#if HAS_BUZZER
#include "../libs/buzzer.h"
#endif
#if ENABLED(POWER_LOSS_RECOVERY)
#include "powerloss.h"
#endif
#include "../libs/nozzle.h"
#include "pause.h"
#define DEBUG_OUT ENABLED(DEBUG_PAUSE_RESUME)
#include "../core/debug_out.h"
// private:
static xyze_pos_t resume_position;
#if HAS_LCD_MENU
#if M600_PURGE_MORE_RESUMABLE
PauseMenuResponse pause_menu_response;
PauseMode pause_mode = PAUSE_MODE_PAUSE_PRINT;
#endif
@@ -78,10 +88,6 @@ fil_change_settings_t fc_settings[EXTRUDERS];
#include "../sd/cardreader.h"
#endif
#ifdef ANYCUBIC_TOUCHSCREEN
#include "../lcd/anycubic_touchscreen.h"
#endif
#if ENABLED(EMERGENCY_PARSER)
#define _PMSG(L) L##_M108
#else
@@ -89,26 +95,30 @@ fil_change_settings_t fc_settings[EXTRUDERS];
#endif
#if HAS_BUZZER
static void filament_change_beep(const int8_t max_beep_count, const bool init=false) {
static void impatient_beep(const int8_t max_beep_count, const bool restart=false) {
#if HAS_LCD_MENU
if (pause_mode == PAUSE_MODE_PAUSE_PRINT) return;
#endif
if (TERN0(HAS_LCD_MENU, pause_mode == PAUSE_MODE_PAUSE_PRINT)) return;
static millis_t next_buzz = 0;
static int8_t runout_beep = 0;
if (init) next_buzz = runout_beep = 0;
if (restart) next_buzz = runout_beep = 0;
const bool always = max_beep_count < 0;
const millis_t ms = millis();
if (ELAPSED(ms, next_buzz)) {
if (max_beep_count < 0 || runout_beep < max_beep_count + 5) { // Only beep as long as we're supposed to
next_buzz = ms + ((max_beep_count < 0 || runout_beep < max_beep_count) ? 1000 : 500);
if (always || runout_beep < max_beep_count + 5) { // Only beep as long as we're supposed to
next_buzz = ms + ((always || runout_beep < max_beep_count) ? 1000 : 500);
BUZZ(50, 880 - (runout_beep & 1) * 220);
runout_beep++;
}
}
}
inline void first_impatient_beep(const int8_t max_beep_count) { impatient_beep(max_beep_count, true); }
#else
inline void impatient_beep(const int8_t, const bool=false) {}
inline void first_impatient_beep(const int8_t) {}
#endif
/**
@@ -120,22 +130,34 @@ fil_change_settings_t fc_settings[EXTRUDERS];
*
* Returns 'true' if heating was completed, 'false' for abort
*/
static bool ensure_safe_temperature(const PauseMode mode=PAUSE_MODE_SAME) {
static bool ensure_safe_temperature(const bool wait=true, const PauseMode mode=PAUSE_MODE_SAME) {
DEBUG_SECTION(est, "ensure_safe_temperature", true);
DEBUG_ECHOLNPGM("... wait:", wait, " mode:", mode);
#if ENABLED(PREVENT_COLD_EXTRUSION)
if (!DEBUGGING(DRYRUN) && thermalManager.targetTooColdToExtrude(active_extruder))
thermalManager.setTargetHotend(thermalManager.extrude_min_temp, active_extruder);
#endif
ui.pause_show_message(PAUSE_MESSAGE_HEATING, mode); UNUSED(mode);
if (wait) return thermalManager.wait_for_hotend(active_extruder);
// Allow interruption by Emergency Parser M108
wait_for_heatup = TERN1(PREVENT_COLD_EXTRUSION, !thermalManager.allow_cold_extrude);
while (wait_for_heatup && ABS(thermalManager.wholeDegHotend(active_extruder) - thermalManager.degTargetHotend(active_extruder)) > (TEMP_WINDOW))
idle();
wait_for_heatup = false;
#if ENABLED(PREVENT_COLD_EXTRUSION)
// A user can cancel wait-for-heating with M108
if (!DEBUGGING(DRYRUN) && thermalManager.targetTooColdToExtrude(active_extruder)) {
SERIAL_ECHO_MSG(STR_ERR_HOTEND_TOO_COLD);
return false;
}
#endif
#if HAS_LCD_MENU
lcd_pause_show_message(PAUSE_MESSAGE_HEATING, mode);
#else
UNUSED(mode);
#endif
return thermalManager.wait_for_hotend(active_extruder);
return true;
}
/**
@@ -150,67 +172,61 @@ static bool ensure_safe_temperature(const PauseMode mode=PAUSE_MODE_SAME) {
*
* Returns 'true' if load was completed, 'false' for abort
*/
bool load_filament(const float &slow_load_length/*=0*/, const float &fast_load_length/*=0*/, const float &purge_length/*=0*/, const int8_t max_beep_count/*=0*/,
bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load_length/*=0*/, const_float_t purge_length/*=0*/, const int8_t max_beep_count/*=0*/,
const bool show_lcd/*=false*/, const bool pause_for_user/*=false*/,
const PauseMode mode/*=PAUSE_MODE_PAUSE_PRINT*/
DXC_ARGS
) {
#if !HAS_LCD_MENU
UNUSED(show_lcd);
#endif
DEBUG_SECTION(lf, "load_filament", true);
DEBUG_ECHOLNPGM("... slowlen:", slow_load_length, " fastlen:", fast_load_length, " purgelen:", purge_length, " maxbeep:", max_beep_count, " showlcd:", show_lcd, " pauseforuser:", pause_for_user, " pausemode:", mode DXC_SAY);
if (!ensure_safe_temperature(mode)) {
#if HAS_LCD_MENU
if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_STATUS, mode);
#endif
if (!ensure_safe_temperature(false, mode)) {
if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_STATUS, mode);
return false;
}
if (pause_for_user) {
#if HAS_LCD_MENU
if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_INSERT, mode);
#endif
if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_INSERT, mode);
SERIAL_ECHO_MSG(_PMSG(STR_FILAMENT_CHANGE_INSERT));
#if HAS_BUZZER
filament_change_beep(max_beep_count, true);
#else
UNUSED(max_beep_count);
#endif
first_impatient_beep(max_beep_count);
KEEPALIVE_STATE(PAUSED_FOR_USER);
wait_for_user = true; // LCD click or M108 will clear this
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(PSTR("Load Filament")));
#if ENABLED(HOST_PROMPT_SUPPORT)
const char tool = '0'
#if NUM_RUNOUT_SENSORS > 1
+ active_extruder
#endif
;
host_action_prompt_begin(PROMPT_USER_CONTINUE, PSTR("Load Filament T"), tool);
host_action_prompt_button(CONTINUE_STR);
host_action_prompt_show();
#endif
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onUserConfirmRequired_P(PSTR("Load Filament"));
const char tool = '0' + TERN0(MULTI_FILAMENT_SENSOR, active_extruder);
host_prompt_do(PROMPT_USER_CONTINUE, PSTR("Load Filament T"), tool, CONTINUE_STR);
#endif
while (wait_for_user) {
#if HAS_BUZZER
filament_change_beep(max_beep_count);
impatient_beep(max_beep_count);
#if BOTH(FILAMENT_CHANGE_RESUME_ON_INSERT, FILAMENT_RUNOUT_SENSOR)
#if ENABLED(MULTI_FILAMENT_SENSOR)
#define _CASE_INSERTED(N) case N-1: if (READ(FIL_RUNOUT##N##_PIN) != FIL_RUNOUT##N##_STATE) wait_for_user = false; break;
switch (active_extruder) {
REPEAT_1(NUM_RUNOUT_SENSORS, _CASE_INSERTED)
}
#else
if (READ(FIL_RUNOUT_PIN) != FIL_RUNOUT_STATE) wait_for_user = false;
#endif
#endif
idle_no_sleep();
}
}
#if HAS_LCD_MENU
if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_LOAD, mode);
#endif
if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_LOAD, mode);
#if ENABLED(DUAL_X_CARRIAGE)
const int8_t saved_ext = active_extruder;
const bool saved_ext_dup_mode = extruder_duplication_enabled;
active_extruder = DXC_ext;
extruder_duplication_enabled = false;
set_duplication_enabled(false, DXC_ext);
#endif
TERN_(BELTPRINTER, do_blocking_move_to_xy(0.00, 50.00));
// Slow Load filament
if (slow_load_length) unscaled_e_move(slow_load_length, FILAMENT_CHANGE_SLOW_LOAD_FEEDRATE);
@@ -229,23 +245,16 @@ bool load_filament(const float &slow_load_length/*=0*/, const float &fast_load_l
}
#if ENABLED(DUAL_X_CARRIAGE) // Tie the two extruders movement back together.
active_extruder = saved_ext;
extruder_duplication_enabled = saved_ext_dup_mode;
stepper.set_directions();
set_duplication_enabled(saved_ext_dup_mode, saved_ext);
#endif
#if ENABLED(ADVANCED_PAUSE_CONTINUOUS_PURGE)
#if HAS_LCD_MENU
if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_PURGE);
#endif
if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_PURGE);
#if ENABLED(HOST_PROMPT_SUPPORT)
host_prompt_do(PROMPT_USER_CONTINUE, PSTR("Filament Purging..."), CONTINUE_STR);
#endif
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onUserConfirmRequired_P(PSTR("Filament Purging..."));
#endif
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(GET_TEXT(MSG_FILAMENT_CHANGE_PURGE)));
TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, GET_TEXT(MSG_FILAMENT_CHANGE_PURGE), CONTINUE_STR));
TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_Popup_Confirm(ICON_BLTouch, GET_TEXT(MSG_FILAMENT_CHANGE_PURGE), CONTINUE_STR));
wait_for_user = true; // A click or M108 breaks the purge_length loop
for (float purge_count = purge_length; purge_count > 0 && wait_for_user; --purge_count)
unscaled_e_move(1, ADVANCED_PAUSE_PURGE_FEEDRATE);
@@ -256,40 +265,49 @@ bool load_filament(const float &slow_load_length/*=0*/, const float &fast_load_l
do {
if (purge_length > 0) {
// "Wait for filament purge"
#if HAS_LCD_MENU
if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_PURGE);
#endif
if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_PURGE);
// Extrude filament to get into hotend
unscaled_e_move(purge_length, ADVANCED_PAUSE_PURGE_FEEDRATE);
}
#if ENABLED(HOST_PROMPT_SUPPORT)
filament_load_host_prompt(); // Initiate another host prompt. (NOTE: host_response_handler may also do this!)
#endif
TERN_(HOST_PROMPT_SUPPORT, filament_load_host_prompt()); // Initiate another host prompt.
#if HAS_LCD_MENU
#if M600_PURGE_MORE_RESUMABLE
if (show_lcd) {
// Show "Purge More" / "Resume" menu and wait for reply
KEEPALIVE_STATE(PAUSED_FOR_USER);
wait_for_user = false;
lcd_pause_show_message(PAUSE_MESSAGE_OPTION);
#if EITHER(HAS_LCD_MENU, DWIN_CREALITY_LCD_ENHANCED)
ui.pause_show_message(PAUSE_MESSAGE_OPTION); // Also sets PAUSE_RESPONSE_WAIT_FOR
#else
pause_menu_response = PAUSE_RESPONSE_WAIT_FOR;
#endif
while (pause_menu_response == PAUSE_RESPONSE_WAIT_FOR) idle_no_sleep();
}
#endif
// Keep looping if "Purge More" was selected
} while (false
#if HAS_LCD_MENU
|| (show_lcd && pause_menu_response == PAUSE_RESPONSE_EXTRUDE_MORE)
#endif
);
} while (TERN0(M600_PURGE_MORE_RESUMABLE, pause_menu_response == PAUSE_RESPONSE_EXTRUDE_MORE));
#endif
TERN_(HOST_PROMPT_SUPPORT, host_action_prompt_end());
return true;
}
/**
* Disabling E steppers for manual filament change should be fine
* as long as users don't spin the E motor ridiculously fast and
* send current back to their board, potentially frying it.
*/
inline void disable_active_extruder() {
#if HAS_EXTRUDERS
stepper.DISABLE_EXTRUDER(active_extruder);
safe_delay(100);
#endif
}
/**
* Unload filament from the hotend
*
@@ -300,30 +318,29 @@ bool load_filament(const float &slow_load_length/*=0*/, const float &fast_load_l
*
* Returns 'true' if unload was completed, 'false' for abort
*/
bool unload_filament(const float &unload_length, const bool show_lcd/*=false*/,
bool unload_filament(const_float_t unload_length, const bool show_lcd/*=false*/,
const PauseMode mode/*=PAUSE_MODE_PAUSE_PRINT*/
#if BOTH(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER)
, const float &mix_multiplier/*=1.0*/
, const_float_t mix_multiplier/*=1.0*/
#endif
) {
#if !HAS_LCD_MENU
UNUSED(show_lcd);
#endif
DEBUG_SECTION(uf, "unload_filament", true);
DEBUG_ECHOLNPGM("... unloadlen:", unload_length, " showlcd:", show_lcd, " mode:", mode
#if BOTH(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER)
, " mixmult:", mix_multiplier
#endif
);
#if !BOTH(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER)
constexpr float mix_multiplier = 1.0;
constexpr float mix_multiplier = 1.0f;
#endif
if (!ensure_safe_temperature(mode)) {
#if HAS_LCD_MENU
if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_STATUS);
#endif
if (!ensure_safe_temperature(false, mode)) {
if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_STATUS);
return false;
}
#if HAS_LCD_MENU
if (show_lcd) lcd_pause_show_message(PAUSE_MESSAGE_UNLOAD, mode);
#endif
if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_UNLOAD, mode);
// Retract filament
unscaled_e_move(-(FILAMENT_UNLOAD_PURGE_RETRACT) * mix_multiplier, (PAUSE_PARK_RETRACT_FEEDRATE) * mix_multiplier);
@@ -347,11 +364,8 @@ bool unload_filament(const float &unload_length, const bool show_lcd/*=false*/,
planner.settings.retract_acceleration = saved_acceleration;
#endif
// Disable E steppers for manual change
#if HAS_E_STEPPER_ENABLE
disable_e_stepper(active_extruder);
safe_delay(100);
#endif
// Disable the Extruder for manual change
disable_active_extruder();
return true;
}
@@ -373,11 +387,11 @@ bool unload_filament(const float &unload_length, const bool show_lcd/*=false*/,
*/
uint8_t did_pause_print = 0;
bool pause_print(const float &retract, const xyz_pos_t &park_point, const float &unload_length/*=0*/, const bool show_lcd/*=false*/ DXC_ARGS) {
bool pause_print(const_float_t retract, const xyz_pos_t &park_point, const bool show_lcd/*=false*/, const_float_t unload_length/*=0*/ DXC_ARGS) {
DEBUG_SECTION(pp, "pause_print", true);
DEBUG_ECHOLNPGM("... park.x:", park_point.x, " y:", park_point.y, " z:", park_point.z, " unloadlen:", unload_length, " showlcd:", show_lcd DXC_SAY);
#if !HAS_LCD_MENU
UNUSED(show_lcd);
#endif
UNUSED(show_lcd);
if (did_pause_print) return false; // already paused
@@ -389,29 +403,15 @@ bool pause_print(const float &retract, const xyz_pos_t &park_point, const float
#endif
#endif
#if ENABLED(HOST_PROMPT_SUPPORT)
host_prompt_open(PROMPT_INFO, PSTR("Pause"), DISMISS_STR);
#endif
if (!DEBUGGING(DRYRUN) && unload_length && thermalManager.targetTooColdToExtrude(active_extruder)) {
SERIAL_ECHO_MSG(STR_ERR_HOTEND_TOO_COLD);
#if HAS_LCD_MENU
if (show_lcd) { // Show status screen
lcd_pause_show_message(PAUSE_MESSAGE_STATUS);
LCD_MESSAGEPGM(MSG_M600_TOO_COLD);
}
#endif
return false; // unable to reach safe temperature
}
TERN_(HOST_PROMPT_SUPPORT, host_prompt_open(PROMPT_INFO, PSTR("Pause"), DISMISS_STR));
// Indicate that the printer is paused
++did_pause_print;
// Pause the print job and timer
#if ENABLED(SDSUPPORT)
if (IS_SD_PRINTING()) {
const bool was_sd_printing = IS_SD_PRINTING();
if (was_sd_printing) {
card.pauseSDPrint();
++did_pause_print; // Indicate SD pause also
}
@@ -422,37 +422,48 @@ bool pause_print(const float &retract, const xyz_pos_t &park_point, const float
// Save current position
resume_position = current_position;
// Will the nozzle be parking?
const bool do_park = !axes_should_home();
#if ENABLED(POWER_LOSS_RECOVERY)
// Save PLR info in case the power goes out while parked
const float park_raise = do_park ? nozzle.park_mode_0_height(park_point.z) - current_position.z : POWER_LOSS_ZRAISE;
if (was_sd_printing && recovery.enabled) recovery.save(true, park_raise, do_park);
#endif
// Wait for buffered blocks to complete
planner.synchronize();
#if ENABLED(ADVANCED_PAUSE_FANS_PAUSE) && FAN_COUNT > 0
#if ENABLED(ADVANCED_PAUSE_FANS_PAUSE) && HAS_FAN
thermalManager.set_fans_paused(true);
#endif
// Initial retract before move to filament change position
if (retract && thermalManager.hotEnoughToExtrude(active_extruder))
if (retract && thermalManager.hotEnoughToExtrude(active_extruder)) {
DEBUG_ECHOLNPGM("... retract:", retract);
unscaled_e_move(retract, PAUSE_PARK_RETRACT_FEEDRATE);
}
// Park the nozzle by moving up by z_lift and then moving to (x_pos, y_pos)
if (!axes_need_homing())
nozzle.park(2, park_point);
// If axes don't need to home then the nozzle can park
if (do_park) nozzle.park(0, park_point); // Park the nozzle by doing a Minimum Z Raise followed by an XY Move
#if ENABLED(DUAL_X_CARRIAGE)
const int8_t saved_ext = active_extruder;
const bool saved_ext_dup_mode = extruder_duplication_enabled;
active_extruder = DXC_ext;
extruder_duplication_enabled = false;
set_duplication_enabled(false, DXC_ext);
#endif
if (unload_length) // Unload the filament
// Unload the filament, if specified
if (unload_length)
unload_filament(unload_length, show_lcd, PAUSE_MODE_CHANGE_FILAMENT);
#if ENABLED(DUAL_X_CARRIAGE)
active_extruder = saved_ext;
extruder_duplication_enabled = saved_ext_dup_mode;
stepper.set_directions();
set_duplication_enabled(saved_ext_dup_mode, saved_ext);
#endif
// Disable the Extruder for manual change
disable_active_extruder();
return true;
}
@@ -470,131 +481,92 @@ bool pause_print(const float &retract, const xyz_pos_t &park_point, const float
*/
void show_continue_prompt(const bool is_reload) {
#if HAS_LCD_MENU
lcd_pause_show_message(is_reload ? PAUSE_MESSAGE_INSERT : PAUSE_MESSAGE_WAITING);
#endif
DEBUG_SECTION(scp, "pause_print", true);
DEBUG_ECHOLNPGM("... is_reload:", is_reload);
ui.pause_show_message(is_reload ? PAUSE_MESSAGE_INSERT : PAUSE_MESSAGE_WAITING);
SERIAL_ECHO_START();
serialprintPGM(is_reload ? PSTR(_PMSG(STR_FILAMENT_CHANGE_INSERT) "\n") : PSTR(_PMSG(STR_FILAMENT_CHANGE_WAIT) "\n"));
SERIAL_ECHOPGM_P(is_reload ? PSTR(_PMSG(STR_FILAMENT_CHANGE_INSERT) "\n") : PSTR(_PMSG(STR_FILAMENT_CHANGE_WAIT) "\n"));
}
void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep_count/*=0*/ DXC_ARGS) {
DEBUG_SECTION(wfc, "wait_for_confirmation", true);
DEBUG_ECHOLNPGM("... is_reload:", is_reload, " maxbeep:", max_beep_count DXC_SAY);
bool nozzle_timed_out = false;
show_continue_prompt(is_reload);
#if HAS_BUZZER
filament_change_beep(max_beep_count, true);
#else
UNUSED(max_beep_count);
#endif
first_impatient_beep(max_beep_count);
// Start the heater idle timers
const millis_t nozzle_timeout = (millis_t)(PAUSE_PARK_NOZZLE_TIMEOUT) * 1000UL;
const millis_t nozzle_timeout = SEC_TO_MS(PAUSE_PARK_NOZZLE_TIMEOUT);
HOTEND_LOOP() thermalManager.hotend_idle[e].start(nozzle_timeout);
HOTEND_LOOP() thermalManager.heater_idle[e].start(nozzle_timeout);
#if ENABLED(DUAL_X_CARRIAGE)
const int8_t saved_ext = active_extruder;
const bool saved_ext_dup_mode = extruder_duplication_enabled;
active_extruder = DXC_ext;
extruder_duplication_enabled = false;
set_duplication_enabled(false, DXC_ext);
#endif
// Wait for filament insert by user and press button
KEEPALIVE_STATE(PAUSED_FOR_USER);
#if ENABLED(HOST_PROMPT_SUPPORT)
host_prompt_do(PROMPT_USER_CONTINUE, PSTR("Nozzle Parked"), CONTINUE_STR);
#endif
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onUserConfirmRequired_P(PSTR("Nozzle Parked"));
#endif
TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, GET_TEXT(MSG_NOZZLE_PARKED), CONTINUE_STR));
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(GET_TEXT(MSG_NOZZLE_PARKED)));
wait_for_user = true; // LCD click or M108 will clear this
while (wait_for_user) {
#if HAS_BUZZER
filament_change_beep(max_beep_count);
#endif
impatient_beep(max_beep_count);
// If the nozzle has timed out...
if (!nozzle_timed_out)
HOTEND_LOOP() nozzle_timed_out |= thermalManager.hotend_idle[e].timed_out;
HOTEND_LOOP() nozzle_timed_out |= thermalManager.heater_idle[e].timed_out;
// Wait for the user to press the button to re-heat the nozzle, then
// re-heat the nozzle, re-show the continue prompt, restart idle timers, start over
if (nozzle_timed_out) {
#ifdef ANYCUBIC_TOUCHSCREEN
if (AnycubicTouchscreen.ai3m_pause_state < 3) {
AnycubicTouchscreen.ai3m_pause_state += 2;
#ifdef ANYCUBIC_TFT_DEBUG
SERIAL_ECHOPAIR(" DEBUG: NTO - AI3M Pause State set to: ", AnycubicTouchscreen.ai3m_pause_state);
SERIAL_EOL();
#endif
}
#endif
#if HAS_LCD_MENU
lcd_pause_show_message(PAUSE_MESSAGE_HEAT);
#endif
ui.pause_show_message(PAUSE_MESSAGE_HEAT);
SERIAL_ECHO_MSG(_PMSG(STR_FILAMENT_CHANGE_HEAT));
#if ENABLED(HOST_PROMPT_SUPPORT)
host_prompt_do(PROMPT_USER_CONTINUE, PSTR("HeaterTimeout"), PSTR("Reheat"));
#endif
TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, GET_TEXT(MSG_HEATER_TIMEOUT), GET_TEXT(MSG_REHEAT)));
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onUserConfirmRequired_P(PSTR("HeaterTimeout"));
#endif
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(GET_TEXT(MSG_HEATER_TIMEOUT)));
wait_for_user_response(0, true); // Wait for LCD click or M108
TERN_(HAS_RESUME_CONTINUE, wait_for_user_response(0, true)); // Wait for LCD click or M108
#if ENABLED(HOST_PROMPT_SUPPORT)
host_prompt_do(PROMPT_INFO, PSTR("Reheating"));
#endif
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onStatusChanged(PSTR("Reheating..."));
#endif
TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_INFO, GET_TEXT(MSG_REHEATING)));
TERN_(EXTENSIBLE_UI, ExtUI::onStatusChanged_P(GET_TEXT(MSG_REHEATING)));
TERN_(DWIN_CREALITY_LCD_ENHANCED, ui.set_status_P(GET_TEXT(MSG_REHEATING)));
// Re-enable the heaters if they timed out
HOTEND_LOOP() thermalManager.reset_hotend_idle_timer(e);
// Wait for the heaters to reach the target temperatures
ensure_safe_temperature();
ensure_safe_temperature(false);
// Show the prompt to continue
show_continue_prompt(is_reload);
// Start the heater idle timers
const millis_t nozzle_timeout = (millis_t)(PAUSE_PARK_NOZZLE_TIMEOUT) * 1000UL;
const millis_t nozzle_timeout = SEC_TO_MS(PAUSE_PARK_NOZZLE_TIMEOUT);
HOTEND_LOOP() thermalManager.heater_idle[e].start(nozzle_timeout);
TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, GET_TEXT(MSG_REHEATDONE), CONTINUE_STR));
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(GET_TEXT(MSG_REHEATDONE)));
TERN_(DWIN_CREALITY_LCD_ENHANCED, ui.set_status_P(GET_TEXT(MSG_REHEATDONE)));
IF_DISABLED(PAUSE_REHEAT_FAST_RESUME, wait_for_user = true);
HOTEND_LOOP() thermalManager.hotend_idle[e].start(nozzle_timeout);
#if ENABLED(HOST_PROMPT_SUPPORT)
host_prompt_do(PROMPT_USER_CONTINUE, PSTR("Reheat Done"), CONTINUE_STR);
#endif
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onUserConfirmRequired_P(PSTR("Reheat finished."));
#endif
wait_for_user = true;
nozzle_timed_out = false;
#ifdef ANYCUBIC_TOUCHSCREEN
if (AnycubicTouchscreen.ai3m_pause_state > 3) {
AnycubicTouchscreen.ai3m_pause_state -= 2;
#ifdef ANYCUBIC_TFT_DEBUG
SERIAL_ECHOPAIR(" DEBUG: NTO - AI3M Pause State set to: ", AnycubicTouchscreen.ai3m_pause_state);
SERIAL_EOL();
#endif
}
#endif
#if HAS_BUZZER
filament_change_beep(max_beep_count, true);
#endif
first_impatient_beep(max_beep_count);
}
idle_no_sleep();
}
#if ENABLED(DUAL_X_CARRIAGE)
active_extruder = saved_ext;
extruder_duplication_enabled = saved_ext_dup_mode;
stepper.set_directions();
set_duplication_enabled(saved_ext_dup_mode, saved_ext);
#endif
}
@@ -607,18 +579,23 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep
* - a nozzle timed out, or
* - the nozzle is already heated.
* - Display "wait for print to resume"
* - Retract to prevent oozing
* - Move the nozzle back to resume_position
* - Unretract
* - Re-prime the nozzle...
* - FWRETRACT: Recover/prime from the prior G10.
* - !FWRETRACT: Retract by resume_position.e, if negative.
* Not sure how this logic comes into use.
* - Move the nozzle back to resume_position
* - Sync the planner E to resume_position.e
* - Send host action for resume, if configured
* - Resume the current SD print job, if any
*/
void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_length/*=0*/, const float &purge_length/*=ADVANCED_PAUSE_PURGE_LENGTH*/, const int8_t max_beep_count/*=0*/ DXC_ARGS) {
void resume_print(const_float_t slow_load_length/*=0*/, const_float_t fast_load_length/*=0*/, const_float_t purge_length/*=ADVANCED_PAUSE_PURGE_LENGTH*/, const int8_t max_beep_count/*=0*/, const celsius_t targetTemp/*=0*/ DXC_ARGS) {
DEBUG_SECTION(rp, "resume_print", true);
DEBUG_ECHOLNPGM("... slowlen:", slow_load_length, " fastlen:", fast_load_length, " purgelen:", purge_length, " maxbeep:", max_beep_count, " targetTemp:", targetTemp DXC_SAY);
/*
SERIAL_ECHOLNPAIR(
SERIAL_ECHOLNPGM(
"start of resume_print()\ndual_x_carriage_mode:", dual_x_carriage_mode,
"\nextruder_duplication_enabled:", extruder_duplication_enabled,
"\nactive_extruder:", active_extruder,
@@ -630,26 +607,42 @@ void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_le
// Re-enable the heaters if they timed out
bool nozzle_timed_out = false;
#ifdef ANYCUBIC_TOUCHSCREEN
if (AnycubicTouchscreen.ai3m_pause_state > 3) {
AnycubicTouchscreen.ai3m_pause_state -= 2;
#ifdef ANYCUBIC_TFT_DEBUG
SERIAL_ECHOPAIR(" DEBUG: NTO - AI3M Pause State set to: ", AnycubicTouchscreen.ai3m_pause_state);
SERIAL_EOL();
#endif
}
#endif
HOTEND_LOOP() {
nozzle_timed_out |= thermalManager.hotend_idle[e].timed_out;
nozzle_timed_out |= thermalManager.heater_idle[e].timed_out;
thermalManager.reset_hotend_idle_timer(e);
}
if (nozzle_timed_out || thermalManager.hotEnoughToExtrude(active_extruder)) // Load the new filament
load_filament(slow_load_length, fast_load_length, purge_length, max_beep_count, true, nozzle_timed_out, PAUSE_MODE_SAME DXC_PASS);
if (targetTemp > thermalManager.degTargetHotend(active_extruder))
thermalManager.setTargetHotend(targetTemp, active_extruder);
#if HAS_LCD_MENU
lcd_pause_show_message(PAUSE_MESSAGE_RESUME);
#endif
// Load the new filament
load_filament(slow_load_length, fast_load_length, purge_length, max_beep_count, true, nozzle_timed_out, PAUSE_MODE_SAME DXC_PASS);
if (targetTemp > 0) {
thermalManager.setTargetHotend(targetTemp, active_extruder);
thermalManager.wait_for_hotend(active_extruder, false);
}
ui.pause_show_message(PAUSE_MESSAGE_RESUME);
// Check Temperature before moving hotend
ensure_safe_temperature(DISABLED(BELTPRINTER));
// Retract to prevent oozing
unscaled_e_move(-(PAUSE_PARK_RETRACT_LENGTH), feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE));
if (!axes_should_home()) {
// Move XY back to saved position
destination.set(resume_position.x, resume_position.y, current_position.z, current_position.e);
prepare_internal_move_to_destination(NOZZLE_PARK_XY_FEEDRATE);
// Move Z back to saved position
destination.z = resume_position.z;
prepare_internal_move_to_destination(NOZZLE_PARK_Z_FEEDRATE);
}
// Unretract
unscaled_e_move(PAUSE_PARK_RETRACT_LENGTH, feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE));
// Intelligent resuming
#if ENABLED(FWRETRACT)
@@ -660,13 +653,6 @@ void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_le
// If resume_position is negative
if (resume_position.e < 0) unscaled_e_move(resume_position.e, feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE));
// Move XY to starting position, then Z
do_blocking_move_to_xy(resume_position, feedRate_t(NOZZLE_PARK_XY_FEEDRATE));
// Move Z_AXIS to saved position
do_blocking_move_to_z(resume_position.z, feedRate_t(NOZZLE_PARK_Z_FEEDRATE));
#if ADVANCED_PAUSE_RESUME_PRIME != 0
unscaled_e_move(ADVANCED_PAUSE_RESUME_PRIME, feedRate_t(ADVANCED_PAUSE_PURGE_FEEDRATE));
#endif
@@ -675,9 +661,7 @@ void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_le
// Set extruder to saved position
planner.set_e_position_mm((destination.e = current_position.e = resume_position.e));
#if HAS_LCD_MENU
lcd_pause_show_message(PAUSE_MESSAGE_STATUS);
#endif
ui.pause_show_message(PAUSE_MESSAGE_STATUS);
#ifdef ACTION_ON_RESUMED
host_action_resumed();
@@ -687,34 +671,29 @@ void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_le
--did_pause_print;
#if ENABLED(HOST_PROMPT_SUPPORT)
host_prompt_open(PROMPT_INFO, PSTR("Resuming"), DISMISS_STR);
#endif
#if ENABLED(SDSUPPORT)
if (did_pause_print) {
card.startFileprint();
--did_pause_print;
}
#endif
#if ENABLED(ADVANCED_PAUSE_FANS_PAUSE) && FAN_COUNT > 0
thermalManager.set_fans_paused(false);
#endif
#if HAS_FILAMENT_SENSOR
runout.reset();
#endif
TERN_(HOST_PROMPT_SUPPORT, host_prompt_open(PROMPT_INFO, PSTR("Resuming"), DISMISS_STR));
// Resume the print job timer if it was running
if (print_job_timer.isPaused()) print_job_timer.start();
#if HAS_DISPLAY
ui.reset_status();
#if HAS_LCD_MENU
ui.return_to_status();
#endif
#if ENABLED(SDSUPPORT)
if (did_pause_print) {
--did_pause_print;
card.startOrResumeFilePrinting();
// Write PLR now to update the z axis value
TERN_(POWER_LOSS_RECOVERY, if (recovery.enabled) recovery.save(true));
}
#endif
#if ENABLED(ADVANCED_PAUSE_FANS_PAUSE) && HAS_FAN
thermalManager.set_fans_paused(false);
#endif
TERN_(HAS_FILAMENT_SENSOR, runout.reset());
TERN_(HAS_STATUS_MESSAGE, ui.reset_status());
TERN_(HAS_LCD_MENU, ui.return_to_status());
TERN_(DWIN_CREALITY_LCD_ENHANCED, HMI_ReturnScreen());
}
#endif // ADVANCED_PAUSE_FEATURE

57
Marlin/src/feature/pause.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -45,7 +45,7 @@ enum PauseMode : char {
};
enum PauseMessage : char {
PAUSE_MESSAGE_PAUSING,
PAUSE_MESSAGE_PARKING,
PAUSE_MESSAGE_CHANGING,
PAUSE_MESSAGE_WAITING,
PAUSE_MESSAGE_UNLOAD,
@@ -59,7 +59,7 @@ enum PauseMessage : char {
PAUSE_MESSAGE_HEATING
};
#if HAS_LCD_MENU
#if M600_PURGE_MORE_RESUMABLE
enum PauseMenuResponse : char {
PAUSE_RESPONSE_WAIT_FOR,
PAUSE_RESPONSE_EXTRUDE_MORE,
@@ -77,25 +77,60 @@ extern uint8_t did_pause_print;
#define DXC_PARAMS , const int8_t DXC_ext=-1
#define DXC_ARGS , const int8_t DXC_ext
#define DXC_PASS , DXC_ext
#define DXC_SAY , " dxc:", int(DXC_ext)
#else
#define DXC_PARAMS
#define DXC_ARGS
#define DXC_PASS
#define DXC_SAY
#endif
bool pause_print(const float &retract, const xyz_pos_t &park_point, const float &unload_length=0, const bool show_lcd=false DXC_PARAMS);
// Pause the print. If unload_length is set, do a Filament Unload
bool pause_print(
const_float_t retract, // (mm) Retraction length
const xyz_pos_t &park_point, // Parking XY Position and Z Raise
const bool show_lcd=false, // Set LCD status messages?
const_float_t unload_length=0 // (mm) Filament Change Unload Length - 0 to skip
DXC_PARAMS // Dual-X-Carriage extruder index
);
void wait_for_confirmation(const bool is_reload=false, const int8_t max_beep_count=0 DXC_PARAMS);
void wait_for_confirmation(
const bool is_reload=false, // Reload Filament? (otherwise Resume Print)
const int8_t max_beep_count=0 // Beep alert for attention
DXC_PARAMS // Dual-X-Carriage extruder index
);
void resume_print(const float &slow_load_length=0, const float &fast_load_length=0, const float &extrude_length=ADVANCED_PAUSE_PURGE_LENGTH, const int8_t max_beep_count=0 DXC_PARAMS);
void resume_print(
const_float_t slow_load_length=0, // (mm) Slow Load Length for finishing move
const_float_t fast_load_length=0, // (mm) Fast Load Length for initial move
const_float_t extrude_length=ADVANCED_PAUSE_PURGE_LENGTH, // (mm) Purge length
const int8_t max_beep_count=0, // Beep alert for attention
const celsius_t targetTemp=0 // (°C) A target temperature for the hotend
DXC_PARAMS // Dual-X-Carriage extruder index
);
bool load_filament(const float &slow_load_length=0, const float &fast_load_length=0, const float &extrude_length=0, const int8_t max_beep_count=0, const bool show_lcd=false,
const bool pause_for_user=false, const PauseMode mode=PAUSE_MODE_PAUSE_PRINT DXC_PARAMS);
bool load_filament(
const_float_t slow_load_length=0, // (mm) Slow Load Length for finishing move
const_float_t fast_load_length=0, // (mm) Fast Load Length for initial move
const_float_t extrude_length=0, // (mm) Purge length
const int8_t max_beep_count=0, // Beep alert for attention
const bool show_lcd=false, // Set LCD status messages?
const bool pause_for_user=false, // Pause for user before returning?
const PauseMode mode=PAUSE_MODE_PAUSE_PRINT // Pause Mode to apply
DXC_PARAMS // Dual-X-Carriage extruder index
);
bool unload_filament(const float &unload_length, const bool show_lcd=false, const PauseMode mode=PAUSE_MODE_PAUSE_PRINT
bool unload_filament(
const_float_t unload_length, // (mm) Filament Unload Length - 0 to skip
const bool show_lcd=false, // Set LCD status messages?
const PauseMode mode=PAUSE_MODE_PAUSE_PRINT // Pause Mode to apply
#if BOTH(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER)
, const float &mix_multiplier=1.0
, const_float_t mix_multiplier=1.0f // Extrusion multiplier (for a Mixing Extruder)
#endif
);
#endif // ADVANCED_PAUSE_FEATURE
#else // !ADVANCED_PAUSE_FEATURE
constexpr uint8_t did_pause_print = 0;
#endif // !ADVANCED_PAUSE_FEATURE

234
Marlin/src/feature/power.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -26,97 +26,181 @@
#include "../inc/MarlinConfig.h"
#if ENABLED(AUTO_POWER_CONTROL)
#include "power.h"
#include "../module/temperature.h"
#include "../module/stepper/indirection.h"
#include "../module/stepper.h"
#include "../MarlinCore.h"
#if ENABLED(PS_OFF_SOUND)
#include "../libs/buzzer.h"
#endif
#if defined(PSU_POWERUP_GCODE) || defined(PSU_POWEROFF_GCODE)
#include "../gcode/gcode.h"
#endif
#if EITHER(PSU_CONTROL, AUTO_POWER_CONTROL)
Power powerManager;
bool Power::psu_on;
millis_t Power::lastPowerOn;
bool Power::is_power_needed() {
#if ENABLED(AUTO_POWER_FANS)
FANS_LOOP(i) if (thermalManager.fan_speed[i]) return true;
#endif
#if ENABLED(AUTO_POWER_E_FANS)
HOTEND_LOOP() if (thermalManager.autofan_speed[e]) return true;
#endif
#if ENABLED(AUTO_POWER_CONTROL)
#include "../module/temperature.h"
#if BOTH(USE_CONTROLLER_FAN, AUTO_POWER_CONTROLLERFAN)
if (controllerFan.state()) return true;
#include "controllerfan.h"
#endif
#if ENABLED(AUTO_POWER_CHAMBER_FAN)
if (thermalManager.chamberfan_speed) return true;
#endif
millis_t Power::lastPowerOn;
#endif
// If any of the drivers or the bed are enabled...
if (X_ENABLE_READ() == X_ENABLE_ON || Y_ENABLE_READ() == Y_ENABLE_ON || Z_ENABLE_READ() == Z_ENABLE_ON
#if HAS_HEATED_BED
|| thermalManager.temp_bed.soft_pwm_amount > 0
#endif
#if HAS_X2_ENABLE
|| X2_ENABLE_READ() == X_ENABLE_ON
#endif
#if HAS_Y2_ENABLE
|| Y2_ENABLE_READ() == Y_ENABLE_ON
#endif
#if HAS_Z2_ENABLE
|| Z2_ENABLE_READ() == Z_ENABLE_ON
#endif
#if E_STEPPERS
#define _OR_ENABLED_E(N) || E##N##_ENABLE_READ() == E_ENABLE_ON
REPEAT(E_STEPPERS, _OR_ENABLED_E)
#endif
) return true;
HOTEND_LOOP() if (thermalManager.degTargetHotend(e) > 0) return true;
#if HAS_HEATED_BED
if (thermalManager.degTargetBed() > 0) return true;
#endif
#if HOTENDS && AUTO_POWER_E_TEMP
HOTEND_LOOP() if (thermalManager.degHotend(e) >= AUTO_POWER_E_TEMP) return true;
#endif
#if HAS_HEATED_CHAMBER && AUTO_POWER_CHAMBER_TEMP
if (thermalManager.degChamber() >= AUTO_POWER_CHAMBER_TEMP) return true;
#endif
return false;
}
void Power::check() {
static millis_t nextPowerCheck = 0;
millis_t ms = millis();
if (ELAPSED(ms, nextPowerCheck)) {
nextPowerCheck = ms + 2500UL;
if (is_power_needed())
power_on();
else if (!lastPowerOn || ELAPSED(ms, lastPowerOn + (POWER_TIMEOUT) * 1000UL))
power_off();
}
/**
* Initialize pins & state for the power manager.
*
*/
void Power::init() {
psu_on = ENABLED(PSU_DEFAULT_OFF); // Set opposite state to get full power_off/on
TERN(PSU_DEFAULT_OFF, power_off(), power_on());
}
/**
* Power on if the power is currently off.
* Restores stepper drivers and processes any PSU_POWERUP_GCODE.
*
*/
void Power::power_on() {
lastPowerOn = millis();
if (!powersupply_on) {
PSU_PIN_ON();
#if ENABLED(AUTO_POWER_CONTROL)
const millis_t now = millis();
lastPowerOn = now + !now;
#endif
#if HAS_TRINAMIC_CONFIG
delay(PSU_POWERUP_DELAY); // Wait for power to settle
restore_stepper_drivers();
#endif
}
if (psu_on) return;
OUT_WRITE(PS_ON_PIN, PSU_ACTIVE_STATE);
psu_on = true;
safe_delay(PSU_POWERUP_DELAY);
restore_stepper_drivers();
TERN_(HAS_TRINAMIC_CONFIG, safe_delay(PSU_POWERUP_DELAY));
#ifdef PSU_POWERUP_GCODE
GcodeSuite::process_subcommands_now_P(PSTR(PSU_POWERUP_GCODE));
#endif
}
/**
* Power off if the power is currently on.
* Processes any PSU_POWEROFF_GCODE and makes a PS_OFF_SOUND if enabled.
*
*/
void Power::power_off() {
if (powersupply_on) PSU_PIN_OFF();
if (!psu_on) return;
#ifdef PSU_POWEROFF_GCODE
GcodeSuite::process_subcommands_now_P(PSTR(PSU_POWEROFF_GCODE));
#endif
#if ENABLED(PS_OFF_SOUND)
BUZZ(1000, 659);
#endif
OUT_WRITE(PS_ON_PIN, !PSU_ACTIVE_STATE);
psu_on = false;
}
#if ENABLED(AUTO_POWER_CONTROL)
#ifndef POWER_TIMEOUT
#define POWER_TIMEOUT 0
#endif
/**
* Check all conditions that would signal power needing to be on.
*
* @returns bool if power is needed
*/
bool Power::is_power_needed() {
// If any of the stepper drivers are enabled...
if (stepper.axis_enabled.bits) return true;
if (printJobOngoing() || printingIsPaused()) return true;
#if ENABLED(AUTO_POWER_FANS)
FANS_LOOP(i) if (thermalManager.fan_speed[i]) return true;
#endif
#if ENABLED(AUTO_POWER_E_FANS)
HOTEND_LOOP() if (thermalManager.autofan_speed[e]) return true;
#endif
#if BOTH(USE_CONTROLLER_FAN, AUTO_POWER_CONTROLLERFAN)
if (controllerFan.state()) return true;
#endif
if (TERN0(AUTO_POWER_CHAMBER_FAN, thermalManager.chamberfan_speed))
return true;
if (TERN0(AUTO_POWER_COOLER_FAN, thermalManager.coolerfan_speed))
return true;
#if HAS_HOTEND
HOTEND_LOOP() if (thermalManager.degTargetHotend(e) > 0 || thermalManager.temp_hotend[e].soft_pwm_amount > 0) return true;
#endif
if (TERN0(HAS_HEATED_BED, thermalManager.degTargetBed() > 0 || thermalManager.temp_bed.soft_pwm_amount > 0)) return true;
#if HAS_HOTEND && AUTO_POWER_E_TEMP
HOTEND_LOOP() if (thermalManager.degHotend(e) >= (AUTO_POWER_E_TEMP)) return true;
#endif
#if HAS_HEATED_CHAMBER && AUTO_POWER_CHAMBER_TEMP
if (thermalManager.degChamber() >= (AUTO_POWER_CHAMBER_TEMP)) return true;
#endif
#if HAS_COOLER && AUTO_POWER_COOLER_TEMP
if (thermalManager.degCooler() >= (AUTO_POWER_COOLER_TEMP)) return true;
#endif
return false;
}
/**
* Check if we should power off automatically (POWER_TIMEOUT elapsed, !is_power_needed).
*
* @param pause pause the 'timer'
*/
void Power::check(const bool pause) {
static millis_t nextPowerCheck = 0;
const millis_t now = millis();
#if POWER_TIMEOUT > 0
static bool _pause = false;
if (pause != _pause) {
lastPowerOn = now + !now;
_pause = pause;
}
if (pause) return;
#endif
if (ELAPSED(now, nextPowerCheck)) {
nextPowerCheck = now + 2500UL;
if (is_power_needed())
power_on();
else if (!lastPowerOn || (POWER_TIMEOUT > 0 && ELAPSED(now, lastPowerOn + SEC_TO_MS(POWER_TIMEOUT))))
power_off();
}
}
#if POWER_OFF_DELAY > 0
/**
* Power off with a delay. Power off is triggered by check() after the delay.
*
*/
void Power::power_off_soon() {
lastPowerOn = millis() - SEC_TO_MS(POWER_TIMEOUT) + SEC_TO_MS(POWER_OFF_DELAY);
}
#endif
#endif // AUTO_POWER_CONTROL
#endif // PSU_CONTROL || AUTO_POWER_CONTROL

28
Marlin/src/feature/power.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -25,16 +25,32 @@
* power.h - power control
*/
#include "../core/millis_t.h"
#if ENABLED(AUTO_POWER_CONTROL)
#include "../core/millis_t.h"
#endif
class Power {
public:
static void check();
static bool psu_on;
static void init();
static void power_on();
static void power_off();
private:
static millis_t lastPowerOn;
static bool is_power_needed();
#if ENABLED(AUTO_POWER_CONTROL) && POWER_OFF_DELAY > 0
static void power_off_soon();
#else
static inline void power_off_soon() { power_off(); }
#endif
#if ENABLED(AUTO_POWER_CONTROL)
static void check(const bool pause);
private:
static millis_t lastPowerOn;
static bool is_power_needed();
#endif
};
extern Power powerManager;

View File

@@ -0,0 +1,78 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#include "../inc/MarlinConfigPre.h"
#if HAS_POWER_MONITOR
#include "power_monitor.h"
#if HAS_LCD_MENU
#include "../lcd/marlinui.h"
#include "../lcd/lcdprint.h"
#endif
#include "../libs/numtostr.h"
uint8_t PowerMonitor::flags; // = 0
#if ENABLED(POWER_MONITOR_CURRENT)
pm_lpf_t<PowerMonitor::amps_adc_scale, PM_K_VALUE, PM_K_SCALE> PowerMonitor::amps;
#endif
#if ENABLED(POWER_MONITOR_VOLTAGE)
pm_lpf_t<PowerMonitor::volts_adc_scale, PM_K_VALUE, PM_K_SCALE> PowerMonitor::volts;
#endif
millis_t PowerMonitor::display_item_ms;
uint8_t PowerMonitor::display_item;
PowerMonitor power_monitor; // Single instance - this calls the constructor
#if HAS_MARLINUI_U8GLIB
#if ENABLED(POWER_MONITOR_CURRENT)
void PowerMonitor::draw_current() {
const float amps = getAmps();
lcd_put_u8str(amps < 100 ? ftostr31ns(amps) : ui16tostr4rj((uint16_t)amps));
lcd_put_wchar('A');
}
#endif
#if ENABLED(POWER_MONITOR_VOLTAGE)
void PowerMonitor::draw_voltage() {
const float volts = getVolts();
lcd_put_u8str(volts < 100 ? ftostr31ns(volts) : ui16tostr4rj((uint16_t)volts));
lcd_put_wchar('V');
}
#endif
#if HAS_POWER_MONITOR_WATTS
void PowerMonitor::draw_power() {
const float power = getPower();
lcd_put_u8str(power < 100 ? ftostr31ns(power) : ui16tostr4rj((uint16_t)power));
lcd_put_wchar('W');
}
#endif
#endif // HAS_MARLINUI_U8GLIB
#endif // HAS_POWER_MONITOR

View File

@@ -0,0 +1,138 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "../inc/MarlinConfig.h"
#define PM_SAMPLE_RANGE HAL_ADC_RANGE
#define PM_K_VALUE 6
#define PM_K_SCALE 6
template <const float & SCALE, int K_VALUE, int K_SCALE>
struct pm_lpf_t {
uint32_t filter_buf;
float value;
void add_sample(const uint16_t sample) {
filter_buf = filter_buf - (filter_buf >> K_VALUE) + (uint32_t(sample) << K_SCALE);
}
void capture() {
value = filter_buf * (SCALE * (1.0f / (1UL << (PM_K_VALUE + PM_K_SCALE))));
}
void reset(uint16_t reset_value = 0) {
filter_buf = uint32_t(reset_value) << (K_VALUE + K_SCALE);
capture();
}
};
class PowerMonitor {
private:
#if ENABLED(POWER_MONITOR_CURRENT)
static constexpr float amps_adc_scale = float(ADC_VREF) / (POWER_MONITOR_VOLTS_PER_AMP * PM_SAMPLE_RANGE);
static pm_lpf_t<amps_adc_scale, PM_K_VALUE, PM_K_SCALE> amps;
#endif
#if ENABLED(POWER_MONITOR_VOLTAGE)
static constexpr float volts_adc_scale = float(ADC_VREF) / (POWER_MONITOR_VOLTS_PER_VOLT * PM_SAMPLE_RANGE);
static pm_lpf_t<volts_adc_scale, PM_K_VALUE, PM_K_SCALE> volts;
#endif
public:
static uint8_t flags; // M430 flags to display current
static millis_t display_item_ms;
static uint8_t display_item;
PowerMonitor() { reset(); }
enum PM_Display_Bit : uint8_t {
PM_DISP_BIT_I, // Current display enable bit
PM_DISP_BIT_V, // Voltage display enable bit
PM_DISP_BIT_P // Power display enable bit
};
#if ENABLED(POWER_MONITOR_CURRENT)
FORCE_INLINE static float getAmps() { return amps.value + (POWER_MONITOR_CURRENT_OFFSET); }
void add_current_sample(const uint16_t value) { amps.add_sample(value); }
#endif
#if ENABLED(POWER_MONITOR_VOLTAGE)
FORCE_INLINE static float getVolts() { return volts.value + (POWER_MONITOR_VOLTAGE_OFFSET); }
void add_voltage_sample(const uint16_t value) { volts.add_sample(value); }
#else
FORCE_INLINE static float getVolts() { return POWER_MONITOR_FIXED_VOLTAGE; }
#endif
#if HAS_POWER_MONITOR_WATTS
FORCE_INLINE static float getPower() { return getAmps() * getVolts(); }
#endif
#if HAS_WIRED_LCD
#if HAS_MARLINUI_U8GLIB && DISABLED(LIGHTWEIGHT_UI)
FORCE_INLINE static bool display_enabled() { return flags != 0x00; }
#endif
#if ENABLED(POWER_MONITOR_CURRENT)
static void draw_current();
FORCE_INLINE static bool current_display_enabled() { return TEST(flags, PM_DISP_BIT_I); }
FORCE_INLINE static void set_current_display(const bool b) { SET_BIT_TO(flags, PM_DISP_BIT_I, b); }
FORCE_INLINE static void toggle_current_display() { TBI(flags, PM_DISP_BIT_I); }
#endif
#if ENABLED(POWER_MONITOR_VOLTAGE)
static void draw_voltage();
FORCE_INLINE static bool voltage_display_enabled() { return TEST(flags, PM_DISP_BIT_V); }
FORCE_INLINE static void set_voltage_display(const bool b) { SET_BIT_TO(flags, PM_DISP_BIT_V, b); }
FORCE_INLINE static void toggle_voltage_display() { TBI(flags, PM_DISP_BIT_V); }
#endif
#if HAS_POWER_MONITOR_WATTS
static void draw_power();
FORCE_INLINE static bool power_display_enabled() { return TEST(flags, PM_DISP_BIT_P); }
FORCE_INLINE static void set_power_display(const bool b) { SET_BIT_TO(flags, PM_DISP_BIT_P, b); }
FORCE_INLINE static void toggle_power_display() { TBI(flags, PM_DISP_BIT_P); }
#endif
#endif
static void reset() {
flags = 0x00;
#if ENABLED(POWER_MONITOR_CURRENT)
amps.reset();
#endif
#if ENABLED(POWER_MONITOR_VOLTAGE)
volts.reset();
#endif
#if ENABLED(SDSUPPORT)
display_item_ms = 0;
display_item = 0;
#endif
}
static void capture_values() {
#if ENABLED(POWER_MONITOR_CURRENT)
amps.capture();
#endif
#if ENABLED(POWER_MONITOR_VOLTAGE)
volts.capture();
#endif
}
};
extern PowerMonitor power_monitor;

502
Marlin/src/feature/powerloss.cpp Executable file → Normal file
View File

@@ -16,12 +16,12 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
/**
* power_loss_recovery.cpp - Resume an SD print after power-loss
* feature/powerloss.cpp - Resume an SD print after power-loss
*/
#include "../inc/MarlinConfigPre.h"
@@ -40,8 +40,12 @@ uint8_t PrintJobRecovery::queue_index_r;
uint32_t PrintJobRecovery::cmd_sdpos, // = 0
PrintJobRecovery::sdpos[BUFSIZE];
#if HAS_DWIN_E3V2_BASIC
bool PrintJobRecovery::dwin_flag; // = false
#endif
#include "../sd/cardreader.h"
#include "../lcd/ultralcd.h"
#include "../lcd/marlinui.h"
#include "../gcode/queue.h"
#include "../gcode/gcode.h"
#include "../module/motion.h"
@@ -62,12 +66,13 @@ PrintJobRecovery recovery;
#ifndef POWER_LOSS_PURGE_LEN
#define POWER_LOSS_PURGE_LEN 0
#endif
#if DISABLED(BACKUP_POWER_SUPPLY)
#undef POWER_LOSS_RETRACT_LEN // No retract at outage without backup power
#endif
#ifndef POWER_LOSS_RETRACT_LEN
#define POWER_LOSS_RETRACT_LEN 0
#endif
#ifndef POWER_LOSS_ZRAISE
#define POWER_LOSS_ZRAISE 2
#endif
/**
* Clear the recovery info
@@ -103,8 +108,8 @@ void PrintJobRecovery::check() {
//if (!card.isMounted()) card.mount();
if (card.isMounted()) {
load();
if (!valid()) return purge();
queue.inject_P(PSTR("M1000 S"));
if (!valid()) return cancel();
queue.inject_P(PSTR("M1000S"));
}
}
@@ -132,14 +137,16 @@ void PrintJobRecovery::load() {
* Set info fields that won't change
*/
void PrintJobRecovery::prepare() {
card.getAbsFilename(info.sd_filename); // SD filename
card.getAbsFilenameInCWD(info.sd_filename); // SD filename
cmd_sdpos = 0;
}
/**
* Save the current machine state to the power-loss recovery file
*/
void PrintJobRecovery::save(const bool force/*=false*/) {
void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POWER_LOSS_ZRAISE*/, const bool raised/*=false*/) {
// We don't check IS_SD_PRINTING here so a save may occur during a pause
#if SAVE_INFO_INTERVAL_MS > 0
static millis_t next_save_ms; // = 0
@@ -172,64 +179,55 @@ void PrintJobRecovery::save(const bool force/*=false*/) {
// Machine state
info.current_position = current_position;
#if HAS_HOME_OFFSET
info.home_offset = home_offset;
#endif
#if HAS_POSITION_SHIFT
info.position_shift = position_shift;
#endif
info.feedrate = uint16_t(feedrate_mm_s * 60.0f);
info.feedrate = uint16_t(MMS_TO_MMM(feedrate_mm_s));
info.zraise = zraise;
info.flag.raised = raised; // Was Z raised before power-off?
#if EXTRUDERS > 1
info.active_extruder = active_extruder;
#endif
TERN_(GCODE_REPEAT_MARKERS, info.stored_repeat = repeat);
TERN_(HAS_HOME_OFFSET, info.home_offset = home_offset);
TERN_(HAS_POSITION_SHIFT, info.position_shift = position_shift);
E_TERN_(info.active_extruder = active_extruder);
#if DISABLED(NO_VOLUMETRICS)
info.volumetric_enabled = parser.volumetric_enabled;
#if EXTRUDERS > 1
info.flag.volumetric_enabled = parser.volumetric_enabled;
#if HAS_MULTI_EXTRUDER
for (int8_t e = 0; e < EXTRUDERS; e++) info.filament_size[e] = planner.filament_size[e];
#else
if (parser.volumetric_enabled) info.filament_size = planner.filament_size[active_extruder];
if (parser.volumetric_enabled) info.filament_size[0] = planner.filament_size[active_extruder];
#endif
#endif
#if EXTRUDERS
HOTEND_LOOP() info.target_temperature[e] = thermalManager.temp_hotend[e].target;
#if HAS_EXTRUDERS
HOTEND_LOOP() info.target_temperature[e] = thermalManager.degTargetHotend(e);
#endif
#if HAS_HEATED_BED
info.target_temperature_bed = thermalManager.temp_bed.target;
#endif
TERN_(HAS_HEATED_BED, info.target_temperature_bed = thermalManager.degTargetBed());
#if FAN_COUNT
#if HAS_FAN
COPY(info.fan_speed, thermalManager.fan_speed);
#endif
#if HAS_LEVELING
info.leveling = planner.leveling_active;
info.fade = (
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
planner.z_fade_height
#else
0
#endif
);
info.flag.leveling = planner.leveling_active;
info.fade = TERN0(ENABLE_LEVELING_FADE_HEIGHT, planner.z_fade_height);
#endif
#if ENABLED(GRADIENT_MIX)
memcpy(&info.gradient, &mixer.gradient, sizeof(info.gradient));
#endif
TERN_(GRADIENT_MIX, memcpy(&info.gradient, &mixer.gradient, sizeof(info.gradient)));
#if ENABLED(FWRETRACT)
COPY(info.retract, fwretract.current_retract);
info.retract_hop = fwretract.current_hop;
#endif
// Elapsed print job time
info.print_job_elapsed = print_job_timer.duration();
// Relative axis modes
info.axis_relative = gcode.axis_relative;
// Elapsed print job time
info.print_job_elapsed = print_job_timer.duration();
// Misc. Marlin flags
info.flag.dryrun = !!(marlin_debug_flags & MARLIN_DEBUG_DRYRUN);
info.flag.allow_cold_extrusion = TERN0(PREVENT_COLD_EXTRUSION, thermalManager.allow_cold_extrude);
write();
}
@@ -237,33 +235,75 @@ void PrintJobRecovery::save(const bool force/*=false*/) {
#if PIN_EXISTS(POWER_LOSS)
#if ENABLED(BACKUP_POWER_SUPPLY)
void PrintJobRecovery::retract_and_lift(const_float_t zraise) {
#if POWER_LOSS_RETRACT_LEN || POWER_LOSS_ZRAISE
gcode.set_relative_mode(true); // Use relative coordinates
#if POWER_LOSS_RETRACT_LEN
// Retract filament now
gcode.process_subcommands_now_P(PSTR("G1 F3000 E-" STRINGIFY(POWER_LOSS_RETRACT_LEN)));
#endif
#if POWER_LOSS_ZRAISE
// Raise the Z axis now
if (zraise) {
char cmd[20], str_1[16];
sprintf_P(cmd, PSTR("G0Z%s"), dtostrf(zraise, 1, 3, str_1));
gcode.process_subcommands_now(cmd);
}
#else
UNUSED(zraise);
#endif
//gcode.axis_relative = info.axis_relative;
planner.synchronize();
#endif
}
#endif
/**
* An outage was detected by a sensor pin.
* - If not SD printing, let the machine turn off on its own with no "KILL" screen
* - Disable all heaters first to save energy
* - Save the recovery data for the current instant
* - If backup power is available Retract E and Raise Z
* - Go to the KILL screen
*/
void PrintJobRecovery::_outage() {
#if ENABLED(BACKUP_POWER_SUPPLY)
static bool lock = false;
if (lock) return; // No re-entrance from idle() during raise_z()
if (lock) return; // No re-entrance from idle() during retract_and_lift()
lock = true;
#endif
if (IS_SD_PRINTING()) save(true);
#if POWER_LOSS_ZRAISE
// Get the limited Z-raise to do now or on resume
const float zraise = _MAX(0, _MIN(current_position.z + POWER_LOSS_ZRAISE, Z_MAX_POS - 1) - current_position.z);
#else
constexpr float zraise = 0;
#endif
// Save the current position, distance that Z was (or should be) raised,
// and a flag whether the raise was already done here.
if (IS_SD_PRINTING()) save(true, zraise, ENABLED(BACKUP_POWER_SUPPLY));
// Disable all heaters to reduce power loss
thermalManager.disable_all_heaters();
#if ENABLED(BACKUP_POWER_SUPPLY)
raise_z();
// Do a hard-stop of the steppers (with possibly a loud thud)
quickstop_stepper();
// With backup power a retract and raise can be done now
retract_and_lift(zraise);
#endif
kill(GET_TEXT(MSG_OUTAGE_RECOVERY));
}
#if ENABLED(BACKUP_POWER_SUPPLY)
void PrintJobRecovery::raise_z() {
// Disable all heaters to reduce power loss
thermalManager.disable_all_heaters();
quickstop_stepper();
// Raise Z axis
gcode.process_subcommands_now_P(PSTR("G91\nG0 Z" STRINGIFY(POWER_LOSS_ZRAISE)));
planner.synchronize();
}
#endif
#endif
/**
@@ -285,111 +325,178 @@ void PrintJobRecovery::write() {
*/
void PrintJobRecovery::resume() {
char cmd[MAX_CMD_SIZE+16], str_1[16], str_2[16];
const uint32_t resume_sdpos = info.sdpos; // Get here before the stepper ISR overwrites it
// Apply the dry-run flag if enabled
if (info.flag.dryrun) marlin_debug_flags |= MARLIN_DEBUG_DRYRUN;
// Restore cold extrusion permission
TERN_(PREVENT_COLD_EXTRUSION, thermalManager.allow_cold_extrude = info.flag.allow_cold_extrusion);
#if HAS_LEVELING
// Make sure leveling is off before any G92 and G28
gcode.process_subcommands_now_P(PSTR("M420 S0 Z0"));
#endif
// Reset E, raise Z, home XY...
gcode.process_subcommands_now_P(PSTR("G92.9 E0"
#if Z_HOME_DIR > 0
#if HAS_HEATED_BED
const celsius_t bt = info.target_temperature_bed;
if (bt) {
// Restore the bed temperature
sprintf_P(cmd, PSTR("M190S%i"), bt);
gcode.process_subcommands_now(cmd);
}
#endif
// If Z homing goes to max, just reset E and home all
"\n"
"G28R0"
#if ENABLED(MARLIN_DEV_MODE)
"S"
#endif
// Heat hotend enough to soften material
#if HAS_HOTEND
HOTEND_LOOP() {
const celsius_t et = _MAX(info.target_temperature[e], 180);
if (et) {
#if HAS_MULTI_HOTEND
sprintf_P(cmd, PSTR("T%iS"), e);
gcode.process_subcommands_now(cmd);
#endif
sprintf_P(cmd, PSTR("M109S%i"), et);
gcode.process_subcommands_now(cmd);
}
}
#endif
#else // "G92.9 E0 ..."
// Interpret the saved Z according to flags
const float z_print = info.current_position.z,
z_raised = z_print + info.zraise;
// Set Z to 0, raise Z by RECOVERY_ZRAISE, and Home (XY only for Cartesian)
// with no raise. (Only do simulated homing in Marlin Dev Mode.)
#if ENABLED(BACKUP_POWER_SUPPLY)
"Z" STRINGIFY(POWER_LOSS_ZRAISE) // Z-axis was already raised at outage
#else
"Z0\n" // Set Z=0
"G1Z" STRINGIFY(POWER_LOSS_ZRAISE) // Raise Z
#endif
"\n"
//
// Home the axes that can safely be homed, and
// establish the current position as best we can.
//
"G28R0"
#if ENABLED(MARLIN_DEV_MODE)
"S"
#elif !IS_KINEMATIC
"XY"
#endif
gcode.process_subcommands_now_P(PSTR("G92.9E0")); // Reset E to 0
#if Z_HOME_TO_MAX
float z_now = z_raised;
// If Z homing goes to max then just move back to the "raised" position
sprintf_P(cmd, PSTR(
"G28R0\n" // Home all axes (no raise)
"G1Z%sF1200" // Move Z down to (raised) height
), dtostrf(z_now, 1, 3, str_1));
gcode.process_subcommands_now(cmd);
#elif DISABLED(BELTPRINTER)
#if ENABLED(POWER_LOSS_RECOVER_ZHOME) && defined(POWER_LOSS_ZHOME_POS)
#define HOMING_Z_DOWN 1
#else
#define HOME_XY_ONLY 1
#endif
));
// Pretend that all axes are homed
axis_homed = axis_known_position = xyz_bits;
float z_now = info.flag.raised ? z_raised : z_print;
char cmd[MAX_CMD_SIZE+16], str_1[16], str_2[16];
// Reset E to 0 and set Z to the real position
#if HOME_XY_ONLY
sprintf_P(cmd, PSTR("G92.9Z%s"), dtostrf(z_now, 1, 3, str_1));
gcode.process_subcommands_now(cmd);
#endif
// Select the previously active tool (with no_move)
#if EXTRUDERS > 1
sprintf_P(cmd, PSTR("T%i S"), info.active_extruder);
// Does Z need to be raised now? It should be raised before homing XY.
if (z_raised > z_now) {
z_now = z_raised;
sprintf_P(cmd, PSTR("G1Z%sF600"), dtostrf(z_now, 1, 3, str_1));
gcode.process_subcommands_now(cmd);
}
// Home XY with no Z raise, and also home Z here if Z isn't homing down below.
gcode.process_subcommands_now_P(PSTR("G28R0" TERN_(HOME_XY_ONLY, "XY"))); // No raise during G28
#endif
#if HOMING_Z_DOWN
// Move to a safe XY position and home Z while avoiding the print.
constexpr xy_pos_t p = POWER_LOSS_ZHOME_POS;
sprintf_P(cmd, PSTR("G1X%sY%sF1000\nG28Z"), dtostrf(p.x, 1, 3, str_1), dtostrf(p.y, 1, 3, str_2));
gcode.process_subcommands_now(cmd);
#endif
// Mark all axes as having been homed (no effect on current_position)
set_all_homed();
#if HAS_LEVELING
// Restore Z fade and possibly re-enable bed leveling compensation.
// Leveling may already be enabled due to the ENABLE_LEVELING_AFTER_G28 option.
// TODO: Add a G28 parameter to leave leveling disabled.
sprintf_P(cmd, PSTR("M420S%cZ%s"), '0' + (char)info.flag.leveling, dtostrf(info.fade, 1, 1, str_1));
gcode.process_subcommands_now(cmd);
#if HOME_XY_ONLY
// The physical Z was adjusted at power-off so undo the M420S1 correction to Z with G92.9.
sprintf_P(cmd, PSTR("G92.9Z%s"), dtostrf(z_now, 1, 1, str_1));
gcode.process_subcommands_now(cmd);
#endif
#endif
#if ENABLED(POWER_LOSS_RECOVER_ZHOME)
// Z was homed down to the bed, so move up to the raised height.
z_now = z_raised;
sprintf_P(cmd, PSTR("G1Z%sF600"), dtostrf(z_now, 1, 3, str_1));
gcode.process_subcommands_now(cmd);
#endif
// Recover volumetric extrusion state
#if DISABLED(NO_VOLUMETRICS)
#if EXTRUDERS > 1
#if HAS_MULTI_EXTRUDER
for (int8_t e = 0; e < EXTRUDERS; e++) {
dtostrf(info.filament_size[e], 1, 3, str_1);
sprintf_P(cmd, PSTR("M200 T%i D%s"), e, str_1);
sprintf_P(cmd, PSTR("M200T%iD%s"), e, dtostrf(info.filament_size[e], 1, 3, str_1));
gcode.process_subcommands_now(cmd);
}
if (!info.volumetric_enabled) {
sprintf_P(cmd, PSTR("M200 T%i D0"), info.active_extruder);
if (!info.flag.volumetric_enabled) {
sprintf_P(cmd, PSTR("M200T%iD0"), info.active_extruder);
gcode.process_subcommands_now(cmd);
}
#else
if (info.volumetric_enabled) {
dtostrf(info.filament_size, 1, 3, str_1);
sprintf_P(cmd, PSTR("M200 D%s"), str_1);
if (info.flag.volumetric_enabled) {
sprintf_P(cmd, PSTR("M200D%s"), dtostrf(info.filament_size[0], 1, 3, str_1));
gcode.process_subcommands_now(cmd);
}
#endif
#endif
#if HAS_HEATED_BED
const int16_t bt = info.target_temperature_bed;
if (bt) {
// Restore the bed temperature
sprintf_P(cmd, PSTR("M190 S%i"), bt);
gcode.process_subcommands_now(cmd);
}
#endif
// Restore all hotend temperatures
#if HOTENDS
#if HAS_HOTEND
HOTEND_LOOP() {
const int16_t et = info.target_temperature[e];
const celsius_t et = info.target_temperature[e];
if (et) {
#if HOTENDS > 1
sprintf_P(cmd, PSTR("T%i"), e);
#if HAS_MULTI_HOTEND
sprintf_P(cmd, PSTR("T%iS"), e);
gcode.process_subcommands_now(cmd);
#endif
sprintf_P(cmd, PSTR("M109 S%i"), et);
sprintf_P(cmd, PSTR("M109S%i"), et);
gcode.process_subcommands_now(cmd);
}
}
#endif
// Restore print cooling fan speeds
FANS_LOOP(i) {
uint8_t f = info.fan_speed[i];
if (f) {
sprintf_P(cmd, PSTR("M106 P%i S%i"), i, f);
gcode.process_subcommands_now(cmd);
}
}
// Restore the previously active tool (with no_move)
#if HAS_MULTI_EXTRUDER || HAS_MULTI_HOTEND
sprintf_P(cmd, PSTR("T%i S"), info.active_extruder);
gcode.process_subcommands_now(cmd);
#endif
// Restore retract and hop state
// Restore print cooling fan speeds
#if HAS_FAN
FANS_LOOP(i) {
const int f = info.fan_speed[i];
if (f) {
sprintf_P(cmd, PSTR("M106P%iS%i"), i, f);
gcode.process_subcommands_now(cmd);
}
}
#endif
// Restore retract and hop state from an active `G10` command
#if ENABLED(FWRETRACT)
LOOP_L_N(e, EXTRUDERS) {
if (info.retract[e] != 0.0) {
@@ -400,124 +507,125 @@ void PrintJobRecovery::resume() {
fwretract.current_hop = info.retract_hop;
#endif
#if HAS_LEVELING
// Restore leveling state before 'G92 Z' to ensure
// the Z stepper count corresponds to the native Z.
if (info.fade || info.leveling) {
sprintf_P(cmd, PSTR("M420 S%i Z%s"), int(info.leveling), dtostrf(info.fade, 1, 1, str_1));
gcode.process_subcommands_now(cmd);
}
#endif
#if ENABLED(GRADIENT_MIX)
memcpy(&mixer.gradient, &info.gradient, sizeof(info.gradient));
#endif
// Extrude and retract to clean the nozzle
#if POWER_LOSS_PURGE_LEN
//sprintf_P(cmd, PSTR("G1 E%d F200"), POWER_LOSS_PURGE_LEN);
//gcode.process_subcommands_now(cmd);
gcode.process_subcommands_now_P(PSTR("G1 E" STRINGIFY(POWER_LOSS_PURGE_LEN) " F200"));
// Un-retract if there was a retract at outage
#if ENABLED(BACKUP_POWER_SUPPLY) && POWER_LOSS_RETRACT_LEN > 0
gcode.process_subcommands_now_P(PSTR("G1E" STRINGIFY(POWER_LOSS_RETRACT_LEN) "F3000"));
#endif
#if POWER_LOSS_RETRACT_LEN
sprintf_P(cmd, PSTR("G1 E%d F3000"), POWER_LOSS_PURGE_LEN - (POWER_LOSS_RETRACT_LEN));
// Additional purge on resume if configured
#if POWER_LOSS_PURGE_LEN
sprintf_P(cmd, PSTR("G1 E%d F3000"), (POWER_LOSS_PURGE_LEN) + (POWER_LOSS_RETRACT_LEN));
gcode.process_subcommands_now(cmd);
#endif
// Move back to the saved XY
sprintf_P(cmd, PSTR("G1 X%s Y%s F3000"),
#if ENABLED(NOZZLE_CLEAN_FEATURE)
gcode.process_subcommands_now_P(PSTR("G12"));
#endif
// Move back over to the saved XY
sprintf_P(cmd, PSTR("G1X%sY%sF3000"),
dtostrf(info.current_position.x, 1, 3, str_1),
dtostrf(info.current_position.y, 1, 3, str_2)
);
gcode.process_subcommands_now(cmd);
// Move back to the saved Z
dtostrf(info.current_position.z, 1, 3, str_1);
#if Z_HOME_DIR > 0
sprintf_P(cmd, PSTR("G1 Z%s F200"), str_1);
#else
gcode.process_subcommands_now_P(PSTR("G1 Z0 F200"));
sprintf_P(cmd, PSTR("G92.9 Z%s"), str_1);
#endif
// Move back down to the saved Z for printing
sprintf_P(cmd, PSTR("G1Z%sF600"), dtostrf(z_print, 1, 3, str_1));
gcode.process_subcommands_now(cmd);
// Un-retract
#if POWER_LOSS_PURGE_LEN
//sprintf_P(cmd, PSTR("G1 E%d F3000"), POWER_LOSS_PURGE_LEN);
//gcode.process_subcommands_now(cmd);
gcode.process_subcommands_now_P(PSTR("G1 E" STRINGIFY(POWER_LOSS_PURGE_LEN) " F3000"));
#endif
// Restore the feedrate
sprintf_P(cmd, PSTR("G1 F%d"), info.feedrate);
sprintf_P(cmd, PSTR("G1F%d"), info.feedrate);
gcode.process_subcommands_now(cmd);
// Restore E position with G92.9
sprintf_P(cmd, PSTR("G92.9 E%s"), dtostrf(info.current_position.e, 1, 3, str_1));
sprintf_P(cmd, PSTR("G92.9E%s"), dtostrf(info.current_position.e, 1, 3, str_1));
gcode.process_subcommands_now(cmd);
TERN_(GCODE_REPEAT_MARKERS, repeat = info.stored_repeat);
TERN_(HAS_HOME_OFFSET, home_offset = info.home_offset);
TERN_(HAS_POSITION_SHIFT, position_shift = info.position_shift);
#if HAS_HOME_OFFSET || HAS_POSITION_SHIFT
LOOP_LINEAR_AXES(i) update_workspace_offset((AxisEnum)i);
#endif
// Relative axis modes
gcode.axis_relative = info.axis_relative;
#if HAS_HOME_OFFSET
home_offset = info.home_offset;
#endif
#if HAS_POSITION_SHIFT
position_shift = info.position_shift;
#endif
#if HAS_HOME_OFFSET || HAS_POSITION_SHIFT
LOOP_XYZ(i) update_workspace_offset((AxisEnum)i);
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
const uint8_t old_flags = marlin_debug_flags;
marlin_debug_flags |= MARLIN_DEBUG_ECHO;
#endif
// Continue to apply PLR when a file is resumed!
enable(true);
// Resume the SD file from the last position
char *fn = info.sd_filename;
extern const char M23_STR[];
sprintf_P(cmd, M23_STR, fn);
gcode.process_subcommands_now(cmd);
sprintf_P(cmd, PSTR("M24 S%ld T%ld"), resume_sdpos, info.print_job_elapsed);
sprintf_P(cmd, PSTR("M24S%ldT%ld"), resume_sdpos, info.print_job_elapsed);
gcode.process_subcommands_now(cmd);
TERN_(DEBUG_POWER_LOSS_RECOVERY, marlin_debug_flags = old_flags);
}
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
void PrintJobRecovery::debug(PGM_P const prefix) {
DEBUG_PRINT_P(prefix);
DEBUG_ECHOLNPAIR(" Job Recovery Info...\nvalid_head:", int(info.valid_head), " valid_foot:", int(info.valid_foot));
DEBUG_ECHOPGM_P(prefix);
DEBUG_ECHOLNPGM(" Job Recovery Info...\nvalid_head:", info.valid_head, " valid_foot:", info.valid_foot);
if (info.valid_head) {
if (info.valid_head == info.valid_foot) {
DEBUG_ECHOPGM("current_position: ");
LOOP_XYZE(i) {
LOOP_LOGICAL_AXES(i) {
if (i) DEBUG_CHAR(',');
DEBUG_ECHO(info.current_position[i]);
DEBUG_DECIMAL(info.current_position[i]);
}
DEBUG_EOL();
DEBUG_ECHOLNPGM("feedrate: ", info.feedrate);
DEBUG_ECHOLNPGM("zraise: ", info.zraise, " ", info.flag.raised ? "(before)" : "");
#if ENABLED(GCODE_REPEAT_MARKERS)
DEBUG_ECHOLNPGM("repeat index: ", info.stored_repeat.index);
LOOP_L_N(i, info.stored_repeat.index)
DEBUG_ECHOLNPGM("..... sdpos: ", info.stored_repeat.marker.sdpos, " count: ", info.stored_repeat.marker.counter);
#endif
#if HAS_HOME_OFFSET
DEBUG_ECHOPGM("home_offset: ");
LOOP_XYZ(i) {
LOOP_LINEAR_AXES(i) {
if (i) DEBUG_CHAR(',');
DEBUG_ECHO(info.home_offset[i]);
DEBUG_DECIMAL(info.home_offset[i]);
}
DEBUG_EOL();
#endif
#if HAS_POSITION_SHIFT
DEBUG_ECHOPGM("position_shift: ");
LOOP_XYZ(i) {
LOOP_LINEAR_AXES(i) {
if (i) DEBUG_CHAR(',');
DEBUG_ECHO(info.position_shift[i]);
DEBUG_DECIMAL(info.position_shift[i]);
}
DEBUG_EOL();
#endif
DEBUG_ECHOLNPAIR("feedrate: ", info.feedrate);
#if EXTRUDERS > 1
DEBUG_ECHOLNPAIR("active_extruder: ", int(info.active_extruder));
#if HAS_MULTI_EXTRUDER
DEBUG_ECHOLNPGM("active_extruder: ", info.active_extruder);
#endif
#if HOTENDS
#if DISABLED(NO_VOLUMETRICS)
DEBUG_ECHOPGM("filament_size:");
LOOP_L_N(i, EXTRUDERS) DEBUG_ECHOLNPGM(" ", info.filament_size[i]);
DEBUG_EOL();
#endif
#if HAS_HOTEND
DEBUG_ECHOPGM("target_temperature: ");
HOTEND_LOOP() {
DEBUG_ECHO(info.target_temperature[e]);
@@ -527,21 +635,22 @@ void PrintJobRecovery::resume() {
#endif
#if HAS_HEATED_BED
DEBUG_ECHOLNPAIR("target_temperature_bed: ", info.target_temperature_bed);
DEBUG_ECHOLNPGM("target_temperature_bed: ", info.target_temperature_bed);
#endif
#if FAN_COUNT
#if HAS_FAN
DEBUG_ECHOPGM("fan_speed: ");
FANS_LOOP(i) {
DEBUG_ECHO(int(info.fan_speed[i]));
DEBUG_ECHO(info.fan_speed[i]);
if (i < FAN_COUNT - 1) DEBUG_CHAR(',');
}
DEBUG_EOL();
#endif
#if HAS_LEVELING
DEBUG_ECHOLNPAIR("leveling: ", int(info.leveling), "\n fade: ", int(info.fade));
DEBUG_ECHOLNPGM("leveling: ", info.flag.leveling ? "ON" : "OFF", " fade: ", info.fade);
#endif
#if ENABLED(FWRETRACT)
DEBUG_ECHOPGM("retract: ");
for (int8_t e = 0; e < EXTRUDERS; e++) {
@@ -549,11 +658,30 @@ void PrintJobRecovery::resume() {
if (e < EXTRUDERS - 1) DEBUG_CHAR(',');
}
DEBUG_EOL();
DEBUG_ECHOLNPAIR("retract_hop: ", info.retract_hop);
DEBUG_ECHOLNPGM("retract_hop: ", info.retract_hop);
#endif
DEBUG_ECHOLNPAIR("sd_filename: ", info.sd_filename);
DEBUG_ECHOLNPAIR("sdpos: ", info.sdpos);
DEBUG_ECHOLNPAIR("print_job_elapsed: ", info.print_job_elapsed);
// Mixing extruder and gradient
#if BOTH(MIXING_EXTRUDER, GRADIENT_MIX)
DEBUG_ECHOLNPGM("gradient: ", info.gradient.enabled ? "ON" : "OFF");
#endif
DEBUG_ECHOLNPGM("sd_filename: ", info.sd_filename);
DEBUG_ECHOLNPGM("sdpos: ", info.sdpos);
DEBUG_ECHOLNPGM("print_job_elapsed: ", info.print_job_elapsed);
DEBUG_ECHOPGM("axis_relative:");
if (TEST(info.axis_relative, REL_X)) DEBUG_ECHOPGM(" REL_X");
if (TEST(info.axis_relative, REL_Y)) DEBUG_ECHOPGM(" REL_Y");
if (TEST(info.axis_relative, REL_Z)) DEBUG_ECHOPGM(" REL_Z");
if (TEST(info.axis_relative, REL_E)) DEBUG_ECHOPGM(" REL_E");
if (TEST(info.axis_relative, E_MODE_ABS)) DEBUG_ECHOPGM(" E_MODE_ABS");
if (TEST(info.axis_relative, E_MODE_REL)) DEBUG_ECHOPGM(" E_MODE_REL");
DEBUG_EOL();
DEBUG_ECHOLNPGM("flag.dryrun: ", AS_DIGIT(info.flag.dryrun));
DEBUG_ECHOLNPGM("flag.allow_cold_extrusion: ", AS_DIGIT(info.flag.allow_cold_extrusion));
DEBUG_ECHOLNPGM("flag.volumetric_enabled: ", AS_DIGIT(info.flag.volumetric_enabled));
}
else
DEBUG_ECHOLNPGM("INVALID DATA");

130
Marlin/src/feature/powerloss.h Executable file → Normal file
View File

@@ -16,18 +16,24 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
/**
* power_loss_recovery.h - Resume an SD print after power-loss
* feature/powerloss.h - Resume an SD print after power-loss
*/
#include "../sd/cardreader.h"
#include "../gcode/gcode.h"
#include "../inc/MarlinConfig.h"
#if ENABLED(GCODE_REPEAT_MARKERS)
#include "../feature/repeat.h"
#endif
#if ENABLED(MIXING_EXTRUDER)
#include "../feature/mixing.h"
#endif
@@ -36,6 +42,10 @@
#define POWER_LOSS_STATE HIGH
#endif
#ifndef POWER_LOSS_ZRAISE
#define POWER_LOSS_ZRAISE 2
#endif
//#define DEBUG_POWER_LOSS_RECOVERY
//#define SAVE_EACH_CMD_MODE
//#define SAVE_INFO_INTERVAL_MS 0
@@ -45,6 +55,14 @@ typedef struct {
// Machine state
xyze_pos_t current_position;
uint16_t feedrate;
float zraise;
// Repeat information
#if ENABLED(GCODE_REPEAT_MARKERS)
Repeat stored_repeat;
#endif
#if HAS_HOME_OFFSET
xyz_pos_t home_offset;
@@ -52,36 +70,25 @@ typedef struct {
#if HAS_POSITION_SHIFT
xyz_pos_t position_shift;
#endif
uint16_t feedrate;
#if EXTRUDERS > 1
#if HAS_MULTI_EXTRUDER
uint8_t active_extruder;
#endif
#if DISABLED(NO_VOLUMETRICS)
bool volumetric_enabled;
#if EXTRUDERS > 1
float filament_size[EXTRUDERS];
#else
float filament_size;
#endif
float filament_size[EXTRUDERS];
#endif
#if HOTENDS
int16_t target_temperature[HOTENDS];
#if HAS_HOTEND
celsius_t target_temperature[HOTENDS];
#endif
#if HAS_HEATED_BED
int16_t target_temperature_bed;
celsius_t target_temperature_bed;
#endif
#if FAN_COUNT
#if HAS_FAN
uint8_t fan_speed[FAN_COUNT];
#endif
#if HAS_LEVELING
bool leveling;
float fade;
#endif
@@ -98,9 +105,6 @@ typedef struct {
#endif
#endif
// Relative axis modes
uint8_t axis_relative;
// SD Filename and position
char sd_filename[MAXPATHNAMELENGTH];
volatile uint32_t sdpos;
@@ -108,8 +112,26 @@ typedef struct {
// Job elapsed time
millis_t print_job_elapsed;
// Relative axis modes
uint8_t axis_relative;
// Misc. Marlin flags
struct {
bool raised:1; // Raised before saved
bool dryrun:1; // M111 S8
bool allow_cold_extrusion:1; // M302 P1
#if HAS_LEVELING
bool leveling:1; // M420 S
#endif
#if DISABLED(NO_VOLUMETRICS)
bool volumetric_enabled:1; // M200 S D
#endif
} flag;
uint8_t valid_foot;
bool valid() { return valid_head && valid_head == valid_foot; }
} job_recovery_info_t;
class PrintJobRecovery {
@@ -123,17 +145,19 @@ class PrintJobRecovery {
static uint32_t cmd_sdpos, //!< SD position of the next command
sdpos[BUFSIZE]; //!< SD positions of queued commands
#if HAS_DWIN_E3V2_BASIC
static bool dwin_flag;
#endif
static void init();
static void prepare();
static inline void setup() {
#if PIN_EXISTS(POWER_LOSS)
#if ENABLED(POWER_LOSS_PULL)
#if POWER_LOSS_STATE == LOW
SET_INPUT_PULLUP(POWER_LOSS_PIN);
#else
SET_INPUT_PULLDOWN(POWER_LOSS_PIN);
#endif
#if ENABLED(POWER_LOSS_PULLUP)
SET_INPUT_PULLUP(POWER_LOSS_PIN);
#elif ENABLED(POWER_LOSS_PULLDOWN)
SET_INPUT_PULLDOWN(POWER_LOSS_PIN);
#else
SET_INPUT(POWER_LOSS_PIN);
#endif
@@ -156,36 +180,46 @@ class PrintJobRecovery {
static void resume();
static void purge();
static inline void cancel() { purge(); card.autostart_index = 0; }
static inline void cancel() { purge(); IF_DISABLED(NO_SD_AUTOSTART, card.autofile_begin()); }
static void load();
static void save(const bool force=ENABLED(SAVE_EACH_CMD_MODE));
static void save(const bool force=ENABLED(SAVE_EACH_CMD_MODE), const float zraise=POWER_LOSS_ZRAISE, const bool raised=false);
#if PIN_EXISTS(POWER_LOSS)
static inline void outage() {
if (enabled && READ(POWER_LOSS_PIN) == POWER_LOSS_STATE)
_outage();
}
#endif
#if PIN_EXISTS(POWER_LOSS)
static inline void outage() {
static constexpr uint8_t OUTAGE_THRESHOLD = 3;
static uint8_t outage_counter = 0;
if (enabled && READ(POWER_LOSS_PIN) == POWER_LOSS_STATE) {
outage_counter++;
if (outage_counter >= OUTAGE_THRESHOLD) _outage();
}
else
outage_counter = 0;
}
#endif
static inline bool valid() { return info.valid_head && info.valid_head == info.valid_foot; }
// The referenced file exists
static inline bool interrupted_file_exists() { return card.fileExists(info.sd_filename); }
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
static void debug(PGM_P const prefix);
#else
static inline void debug(PGM_P const) {}
#endif
static inline bool valid() { return info.valid() && interrupted_file_exists(); }
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
static void debug(PGM_P const prefix);
#else
static inline void debug(PGM_P const) {}
#endif
private:
static void write();
#if ENABLED(BACKUP_POWER_SUPPLY)
static void raise_z();
#endif
#if ENABLED(BACKUP_POWER_SUPPLY)
static void retract_and_lift(const_float_t zraise);
#endif
#if PIN_EXISTS(POWER_LOSS)
static void _outage();
#endif
#if PIN_EXISTS(POWER_LOSS)
friend class GcodeSuite;
static void _outage();
#endif
};
extern PrintJobRecovery recovery;

104
Marlin/src/feature/probe_temp_comp.cpp Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@@ -29,27 +29,27 @@
ProbeTempComp temp_comp;
int16_t ProbeTempComp::z_offsets_probe[ProbeTempComp::cali_info_init[TSI_PROBE].measurements], // = {0}
ProbeTempComp::z_offsets_bed[ProbeTempComp::cali_info_init[TSI_BED].measurements]; // = {0}
int16_t ProbeTempComp::z_offsets_probe[cali_info_init[TSI_PROBE].measurements], // = {0}
ProbeTempComp::z_offsets_bed[cali_info_init[TSI_BED].measurements]; // = {0}
#if ENABLED(USE_TEMP_EXT_COMPENSATION)
int16_t ProbeTempComp::z_offsets_ext[ProbeTempComp::cali_info_init[TSI_EXT].measurements]; // = {0}
int16_t ProbeTempComp::z_offsets_ext[cali_info_init[TSI_EXT].measurements]; // = {0}
#endif
int16_t *ProbeTempComp::sensor_z_offsets[TSI_COUNT] = {
ProbeTempComp::z_offsets_probe, ProbeTempComp::z_offsets_bed
#if ENABLED(USE_TEMP_EXT_COMPENSATION)
, ProbeTempComp::z_offsets_ext
#endif
OPTARG(USE_TEMP_EXT_COMPENSATION, ProbeTempComp::z_offsets_ext)
};
const temp_calib_t ProbeTempComp::cali_info[TSI_COUNT] = {
ProbeTempComp::cali_info_init[TSI_PROBE], ProbeTempComp::cali_info_init[TSI_BED]
#if ENABLED(USE_TEMP_EXT_COMPENSATION)
, ProbeTempComp::cali_info_init[TSI_EXT]
#endif
cali_info_init[TSI_PROBE], cali_info_init[TSI_BED]
OPTARG(USE_TEMP_EXT_COMPENSATION, cali_info_init[TSI_EXT])
};
constexpr xyz_pos_t ProbeTempComp::park_point;
constexpr xy_pos_t ProbeTempComp::measure_point;
constexpr celsius_t ProbeTempComp::probe_calib_bed_temp;
uint8_t ProbeTempComp::calib_idx; // = 0
float ProbeTempComp::init_measurement; // = 0.0
@@ -67,15 +67,15 @@ bool ProbeTempComp::set_offset(const TempSensorID tsi, const uint8_t idx, const
void ProbeTempComp::print_offsets() {
LOOP_L_N(s, TSI_COUNT) {
float temp = cali_info[s].start_temp;
celsius_t temp = cali_info[s].start_temp;
for (int16_t i = -1; i < cali_info[s].measurements; ++i) {
serialprintPGM(s == TSI_BED ? PSTR("Bed") :
SERIAL_ECHOPGM_P(s == TSI_BED ? PSTR("Bed") :
#if ENABLED(USE_TEMP_EXT_COMPENSATION)
s == TSI_EXT ? PSTR("Extruder") :
#endif
PSTR("Probe")
);
SERIAL_ECHOLNPAIR(
SERIAL_ECHOLNPGM(
" temp: ", temp,
"C; Offset: ", i < 0 ? 0.0f : sensor_z_offsets[s][i], " um"
);
@@ -84,12 +84,12 @@ void ProbeTempComp::print_offsets() {
}
}
void ProbeTempComp::prepare_new_calibration(const float &init_meas_z) {
void ProbeTempComp::prepare_new_calibration(const_float_t init_meas_z) {
calib_idx = 0;
init_measurement = init_meas_z;
}
void ProbeTempComp::push_back_new_measurement(const TempSensorID tsi, const float &meas_z) {
void ProbeTempComp::push_back_new_measurement(const TempSensorID tsi, const_float_t meas_z) {
switch (tsi) {
case TSI_PROBE:
case TSI_BED:
@@ -110,19 +110,19 @@ bool ProbeTempComp::finish_calibration(const TempSensorID tsi) {
}
const uint8_t measurements = cali_info[tsi].measurements;
const float start_temp = cali_info[tsi].start_temp,
res_temp = cali_info[tsi].temp_res;
const celsius_t start_temp = cali_info[tsi].start_temp,
res_temp = cali_info[tsi].temp_res;
int16_t * const data = sensor_z_offsets[tsi];
// Extrapolate
float k, d;
if (calib_idx < measurements) {
SERIAL_ECHOLNPAIR("Got ", calib_idx, " measurements. ");
SERIAL_ECHOLNPGM("Got ", calib_idx, " measurements. ");
if (linear_regression(tsi, k, d)) {
SERIAL_ECHOPGM("Applying linear extrapolation");
calib_idx--;
for (; calib_idx < measurements; ++calib_idx) {
const float temp = start_temp + float(calib_idx) * res_temp;
const celsius_float_t temp = start_temp + float(calib_idx) * res_temp;
data[calib_idx] = static_cast<int16_t>(k * temp + d);
}
}
@@ -139,13 +139,13 @@ bool ProbeTempComp::finish_calibration(const TempSensorID tsi) {
// Sanity check
for (calib_idx = 0; calib_idx < measurements; ++calib_idx) {
// Restrict the max. offset
if (abs(data[calib_idx]) > 2000) {
if (ABS(data[calib_idx]) > 2000) {
SERIAL_ECHOLNPGM("!Invalid Z-offset detected (0-2).");
clear_offsets(tsi);
return false;
}
// Restrict the max. offset difference between two probings
if (calib_idx > 0 && abs(data[calib_idx - 1] - data[calib_idx]) > 800) {
if (calib_idx > 0 && ABS(data[calib_idx - 1] - data[calib_idx]) > 800) {
SERIAL_ECHOLNPGM("!Invalid Z-offset between two probings detected (0-0.8).");
clear_offsets(TSI_PROBE);
return false;
@@ -155,34 +155,47 @@ bool ProbeTempComp::finish_calibration(const TempSensorID tsi) {
return true;
}
void ProbeTempComp::compensate_measurement(const TempSensorID tsi, const float &temp, float &meas_z) {
void ProbeTempComp::compensate_measurement(const TempSensorID tsi, const celsius_t temp, float &meas_z) {
if (WITHIN(temp, cali_info[tsi].start_temp, cali_info[tsi].end_temp))
meas_z -= get_offset_for_temperature(tsi, temp);
}
float ProbeTempComp::get_offset_for_temperature(const TempSensorID tsi, const float &temp) {
float ProbeTempComp::get_offset_for_temperature(const TempSensorID tsi, const celsius_t temp) {
const uint8_t measurements = cali_info[tsi].measurements;
const float start_temp = cali_info[tsi].start_temp,
end_temp = cali_info[tsi].end_temp,
res_temp = cali_info[tsi].temp_res;
const celsius_t start_temp = cali_info[tsi].start_temp,
res_temp = cali_info[tsi].temp_res;
const int16_t * const data = sensor_z_offsets[tsi];
if (temp <= start_temp) return 0.0f;
if (temp >= end_temp) return static_cast<float>(data[measurements - 1]) / 1000.0f;
auto point = [&](uint8_t i) -> xy_float_t {
return xy_float_t({ static_cast<float>(start_temp) + i * res_temp, static_cast<float>(data[i]) });
};
auto linear_interp = [](const_float_t x, xy_float_t p1, xy_float_t p2) {
return (p2.y - p1.y) / (p2.x - p2.y) * (x - p1.x) + p1.y;
};
// Linear interpolation
int16_t val1 = 0, val2 = data[0];
uint8_t idx = 0;
float meas_temp = start_temp + res_temp;
while (meas_temp < temp) {
if (++idx >= measurements) return static_cast<float>(val2) / 1000.0f;
meas_temp += res_temp;
val1 = val2;
val2 = data[idx];
}
const float factor = (meas_temp - temp) / static_cast<float>(res_temp);
return (static_cast<float>(val2) - static_cast<float>(val2 - val1) * factor) / 1000.0f;
uint8_t idx = static_cast<uint8_t>((temp - start_temp) / res_temp);
// offset in µm
float offset = 0.0f;
#if !defined(PTC_LINEAR_EXTRAPOLATION) || PTC_LINEAR_EXTRAPOLATION <= 0
if (idx < 0)
offset = 0.0f;
else if (idx > measurements - 2)
offset = static_cast<float>(data[measurements - 1]);
#else
if (idx < 0)
offset = linear_interp(temp, point(0), point(PTC_LINEAR_EXTRAPOLATION));
else if (idx > measurements - 2)
offset = linear_interp(temp, point(measurements - PTC_LINEAR_EXTRAPOLATION - 1), point(measurements - 1));
#endif
else
offset = linear_interp(temp, point(idx), point(idx + 1));
// return offset in mm
return offset / 1000.0f;
}
bool ProbeTempComp::linear_regression(const TempSensorID tsi, float &k, float &d) {
@@ -190,17 +203,18 @@ bool ProbeTempComp::linear_regression(const TempSensorID tsi, float &k, float &d
if (!WITHIN(calib_idx, 2, cali_info[tsi].measurements)) return false;
const float start_temp = cali_info[tsi].start_temp,
res_temp = cali_info[tsi].temp_res;
const celsius_t start_temp = cali_info[tsi].start_temp,
res_temp = cali_info[tsi].temp_res;
const int16_t * const data = sensor_z_offsets[tsi];
float sum_x = start_temp,
sum_x2 = sq(start_temp),
sum_xy = 0, sum_y = 0;
float xi = static_cast<float>(start_temp);
LOOP_L_N(i, calib_idx) {
const float xi = start_temp + (i + 1) * res_temp,
yi = static_cast<float>(data[i]);
const float yi = static_cast<float>(data[i]);
xi += res_temp;
sum_x += xi;
sum_x2 += sq(xi);
sum_xy += xi * yi;

89
Marlin/src/feature/probe_temp_comp.h Executable file → Normal file
View File

@@ -16,7 +16,7 @@
* 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 <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
@@ -34,9 +34,9 @@ enum TempSensorID : uint8_t {
typedef struct {
uint8_t measurements; // Max. number of measurements to be stored (35 - 80°C)
float temp_res, // Resolution in °C between measurements
start_temp, // Base measurement; z-offset == 0
end_temp;
celsius_t temp_res, // Resolution in °C between measurements
start_temp, // Base measurement; z-offset == 0
end_temp;
} temp_calib_t;
/**
@@ -44,31 +44,64 @@ typedef struct {
* Z-probes like the P.I.N.D.A V2 allow for compensation of
* measurement errors/shifts due to changed temperature.
*/
// Probe temperature calibration constants
#ifndef PTC_SAMPLE_COUNT
#define PTC_SAMPLE_COUNT 10
#endif
#ifndef PTC_SAMPLE_RES
#define PTC_SAMPLE_RES 5
#endif
#ifndef PTC_SAMPLE_START
#define PTC_SAMPLE_START 30
#endif
#define PTC_SAMPLE_END (PTC_SAMPLE_START + (PTC_SAMPLE_COUNT) * PTC_SAMPLE_RES)
// Bed temperature calibration constants
#ifndef BTC_PROBE_TEMP
#define BTC_PROBE_TEMP 30
#endif
#ifndef BTC_SAMPLE_COUNT
#define BTC_SAMPLE_COUNT 10
#endif
#ifndef BTC_SAMPLE_RES
#define BTC_SAMPLE_RES 5
#endif
#ifndef BTC_SAMPLE_START
#define BTC_SAMPLE_START 60
#endif
#define BTC_SAMPLE_END (BTC_SAMPLE_START + (BTC_SAMPLE_COUNT) * BTC_SAMPLE_RES)
#ifndef PTC_PROBE_HEATING_OFFSET
#define PTC_PROBE_HEATING_OFFSET 0.5f
#endif
#ifndef PTC_PROBE_RAISE
#define PTC_PROBE_RAISE 10
#endif
static constexpr temp_calib_t cali_info_init[TSI_COUNT] = {
{ PTC_SAMPLE_COUNT, PTC_SAMPLE_RES, PTC_SAMPLE_START, PTC_SAMPLE_END }, // Probe
{ BTC_SAMPLE_COUNT, BTC_SAMPLE_RES, BTC_SAMPLE_START, BTC_SAMPLE_END }, // Bed
#if ENABLED(USE_TEMP_EXT_COMPENSATION)
{ 20, 5, 180, 180 + 5 * 20 } // Extruder
#endif
};
class ProbeTempComp {
public:
static constexpr temp_calib_t cali_info_init[TSI_COUNT] = {
{ 10, 5, 30, 30 + 10 * 5 }, // Probe
{ 10, 5, 60, 60 + 10 * 5 }, // Bed
#if ENABLED(USE_TEMP_EXT_COMPENSATION)
{ 20, 5, 180, 180 + 5 * 20 } // Extruder
#endif
};
static const temp_calib_t cali_info[TSI_COUNT];
// Where to park nozzle to wait for probe cooldown
static constexpr float park_point_x = PTC_PARK_POS_X,
park_point_y = PTC_PARK_POS_Y,
park_point_z = PTC_PARK_POS_Z,
// XY coordinates of nozzle for probing the bed
measure_point_x = PTC_PROBE_POS_X, // Coordinates to probe
measure_point_y = PTC_PROBE_POS_Y;
//measure_point_x = 12.0f, // Coordinates to probe on MK52 magnetic heatbed
//measure_point_y = 7.3f;
static constexpr xyz_pos_t park_point = PTC_PARK_POS;
static constexpr int max_bed_temp = PTC_MAX_BED_TEMP, // Max temperature to avoid heating errors
probe_calib_bed_temp = max_bed_temp, // Bed temperature while calibrating probe
bed_calib_probe_temp = 30; // Probe temperature while calibrating bed
// XY coordinates of nozzle for probing the bed
static constexpr xy_pos_t measure_point = PTC_PROBE_POS; // Coordinates to probe
//measure_point = { 12.0f, 7.3f }; // Coordinates for the MK52 magnetic heatbed
static constexpr celsius_t probe_calib_bed_temp = BED_MAX_TARGET, // Bed temperature while calibrating probe
bed_calib_probe_temp = BTC_PROBE_TEMP; // Probe temperature while calibrating bed
static int16_t *sensor_z_offsets[TSI_COUNT],
z_offsets_probe[cali_info_init[TSI_PROBE].measurements], // (µm)
@@ -84,16 +117,14 @@ class ProbeTempComp {
static inline void clear_all_offsets() {
clear_offsets(TSI_BED);
clear_offsets(TSI_PROBE);
#if ENABLED(USE_TEMP_EXT_COMPENSATION)
clear_offsets(TSI_EXT);
#endif
TERN_(USE_TEMP_EXT_COMPENSATION, clear_offsets(TSI_EXT));
}
static bool set_offset(const TempSensorID tsi, const uint8_t idx, const int16_t offset);
static void print_offsets();
static void prepare_new_calibration(const float &init_meas_z);
static void push_back_new_measurement(const TempSensorID tsi, const float &meas_z);
static void prepare_new_calibration(const_float_t init_meas_z);
static void push_back_new_measurement(const TempSensorID tsi, const_float_t meas_z);
static bool finish_calibration(const TempSensorID tsi);
static void compensate_measurement(const TempSensorID tsi, const float &temp, float &meas_z);
static void compensate_measurement(const TempSensorID tsi, const celsius_t temp, float &meas_z);
private:
static uint8_t calib_idx;
@@ -104,7 +135,7 @@ class ProbeTempComp {
*/
static float init_measurement;
static float get_offset_for_temperature(const TempSensorID tsi, const float &temp);
static float get_offset_for_temperature(const TempSensorID tsi, const celsius_t temp);
/**
* Fit a linear function in measured temperature offsets

View File

@@ -0,0 +1,82 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#include "../inc/MarlinConfig.h"
#if ENABLED(GCODE_REPEAT_MARKERS)
//#define DEBUG_GCODE_REPEAT_MARKERS
#include "repeat.h"
#include "../gcode/gcode.h"
#include "../sd/cardreader.h"
#define DEBUG_OUT ENABLED(DEBUG_GCODE_REPEAT_MARKERS)
#include "../core/debug_out.h"
repeat_marker_t Repeat::marker[MAX_REPEAT_NESTING];
uint8_t Repeat::index;
void Repeat::add_marker(const uint32_t sdpos, const uint16_t count) {
if (index >= MAX_REPEAT_NESTING)
SERIAL_ECHO_MSG("!Too many markers.");
else {
marker[index].sdpos = sdpos;
marker[index].counter = count ?: -1;
index++;
DEBUG_ECHOLNPGM("Add Marker ", index, " at ", sdpos, " (", count, ")");
}
}
void Repeat::loop() {
if (!index) // No marker?
SERIAL_ECHO_MSG("!No marker set."); // Inform the user.
else {
const uint8_t ind = index - 1; // Active marker's index
if (!marker[ind].counter) { // Did its counter run out?
DEBUG_ECHOLNPGM("Pass Marker ", index);
index--; // Carry on. Previous marker on the next 'M808'.
}
else {
card.setIndex(marker[ind].sdpos); // Loop back to the marker.
if (marker[ind].counter > 0) // Ignore a negative (or zero) counter.
--marker[ind].counter; // Decrement the counter. If zero this 'M808' will be skipped next time.
DEBUG_ECHOLNPGM("Goto Marker ", index, " at ", marker[ind].sdpos, " (", marker[ind].counter, ")");
}
}
}
void Repeat::cancel() { LOOP_L_N(i, index) marker[i].counter = 0; }
void Repeat::early_parse_M808(char * const cmd) {
if (is_command_M808(cmd)) {
DEBUG_ECHOLNPGM("Parsing \"", cmd, "\"");
parser.parse(cmd);
if (parser.seen('L'))
add_marker(card.getIndex(), parser.value_ushort());
else
Repeat::loop();
}
}
#endif // GCODE_REPEAT_MARKERS

View File

@@ -0,0 +1,53 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "../inc/MarlinConfigPre.h"
#include "../gcode/parser.h"
#include <stdint.h>
#define MAX_REPEAT_NESTING 10
typedef struct {
uint32_t sdpos; // The repeat file position
int16_t counter; // The counter for looping
} repeat_marker_t;
class Repeat {
private:
static repeat_marker_t marker[MAX_REPEAT_NESTING];
static uint8_t index;
public:
static inline void reset() { index = 0; }
static inline bool is_active() {
LOOP_L_N(i, index) if (marker[i].counter) return true;
return false;
}
static bool is_command_M808(char * const cmd) { return cmd[0] == 'M' && cmd[1] == '8' && cmd[2] == '0' && cmd[3] == '8' && !NUMERIC(cmd[4]); }
static void early_parse_M808(char * const cmd);
static void add_marker(const uint32_t sdpos, const uint16_t count);
static void loop();
static void cancel();
};
extern Repeat repeat;

Some files were not shown because too many files have changed in this diff Show More