Merge upstream changes from Marlin 2.1.1

This commit is contained in:
Stefan Kalscheuer
2022-09-03 09:23:32 +02:00
parent 626283aadb
commit 986e416c7f
1610 changed files with 73839 additions and 40857 deletions

View File

@@ -0,0 +1,104 @@
/**
* 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/>.
*
*/
/**
* adc_mcp3426.cpp - library for MicroChip MCP3426 I2C A/D converter
*
* For implementation details, please take a look at the datasheet:
* https://www.microchip.com/en-us/product/MCP3426
*/
#include "../../inc/MarlinConfig.h"
#if ENABLED(HAS_MCP3426_ADC)
#include "adc_mcp3426.h"
// Read the ADC value from MCP342X on a specific channel
int16_t MCP3426::ReadValue(uint8_t channel, uint8_t gain, uint8_t address) {
Error = false;
#if PINS_EXIST(I2C_SCL, I2C_SDA) && DISABLED(SOFT_I2C_EEPROM)
Wire.setSDA(pin_t(I2C_SDA_PIN));
Wire.setSCL(pin_t(I2C_SCL_PIN));
#endif
Wire.begin(); // No address joins the BUS as the master
Wire.beginTransmission(I2C_ADDRESS(address));
// Continuous Conversion Mode, 16 bit, Channel 1, Gain x4
// 26 = 0b00011000
// RXXCSSGG
// R = Ready Bit
// XX = Channel (00=1, 01=2, 10=3 (MCP3428), 11=4 (MCP3428))
// C = Conversion Mode Bit (1= Continuous Conversion Mode (Default))
// SS = Sample rate, 10=15 samples per second @ 16 bits
// GG = Gain 00 =x1
uint8_t controlRegister = 0b00011000;
if (channel == 2) controlRegister |= 0b00100000; // Select channel 2
if (gain == 2)
controlRegister |= 0b00000001;
else if (gain == 4)
controlRegister |= 0b00000010;
else if (gain == 8)
controlRegister |= 0b00000011;
Wire.write(controlRegister);
if (Wire.endTransmission() != 0) {
Error = true;
return 0;
}
const uint8_t len = 3;
uint8_t buffer[len] = {};
do {
Wire.requestFrom(I2C_ADDRESS(address), len);
if (Wire.available() != len) {
Error = true;
return 0;
}
for (uint8_t i = 0; i < len; ++i)
buffer[i] = Wire.read();
// Is conversion ready, if not loop around again
} while ((buffer[2] & 0x80) != 0);
union TwoBytesToInt16 {
uint8_t bytes[2];
int16_t integervalue;
};
TwoBytesToInt16 ConversionUnion;
ConversionUnion.bytes[1] = buffer[0];
ConversionUnion.bytes[0] = buffer[1];
return ConversionUnion.integervalue;
}
MCP3426 mcp3426;
#endif // HAS_MCP3426_ADC

View File

@@ -0,0 +1,38 @@
/**
* 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
/**
* Arduino library for MicroChip MCP3426 I2C A/D converter.
* https://www.microchip.com/en-us/product/MCP3426
*/
#include <stdint.h>
#include <Wire.h>
class MCP3426 {
public:
int16_t ReadValue(uint8_t channel, uint8_t gain, uint8_t address);
bool Error;
};
extern MCP3426 mcp3426;

View File

@@ -54,6 +54,18 @@ void Babystep::add_mm(const AxisEnum axis, const_float_t mm) {
add_steps(axis, mm * planner.settings.axis_steps_per_mm[axis]);
}
#if ENABLED(BD_SENSOR)
void Babystep::set_mm(const AxisEnum axis, const_float_t mm) {
//if (DISABLED(BABYSTEP_WITHOUT_HOMING) && axes_should_home(_BV(axis))) return;
const int16_t distance = mm * planner.settings.axis_steps_per_mm[axis];
accum = distance; // Count up babysteps for the UI
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
void Babystep::add_steps(const AxisEnum axis, const int16_t distance) {
if (DISABLED(BABYSTEP_WITHOUT_HOMING) && axes_should_home(_BV(axis))) return;

View File

@@ -54,7 +54,7 @@ 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) {
static void reset_total(const AxisEnum axis) {
if (TERN1(BABYSTEP_XY, axis == Z_AXIS))
axis_total[BS_TOTAL_IND(axis)] = 0;
}
@@ -63,7 +63,11 @@ public:
static void add_steps(const AxisEnum axis, const int16_t distance);
static void add_mm(const AxisEnum axis, const_float_t mm);
static inline bool has_steps() {
#if ENABLED(BD_SENSOR)
static void set_mm(const AxisEnum axis, const_float_t mm);
#endif
static bool has_steps() {
return steps[BS_AXIS_IND(X_AXIS)] || steps[BS_AXIS_IND(Y_AXIS)] || steps[BS_AXIS_IND(Z_AXIS)];
}
@@ -71,7 +75,7 @@ public:
// Called by the Temperature or Stepper ISR to
// apply accumulated babysteps to the axes.
//
static inline void task() {
static void task() {
LOOP_LE_N(i, BS_AXIS_IND(Z_AXIS)) step_axis(BS_AXIS(i));
}

View File

@@ -29,6 +29,9 @@
#include "../module/motion.h"
#include "../module/planner.h"
axis_bits_t Backlash::last_direction_bits;
xyz_long_t Backlash::residual_error{0};
#ifdef BACKLASH_DISTANCE_MM
#if ENABLED(BACKLASH_GCODE)
xyz_float_t Backlash::distance_mm = BACKLASH_DISTANCE_MM;
@@ -38,7 +41,7 @@
#endif
#if ENABLED(BACKLASH_GCODE)
uint8_t Backlash::correction = (BACKLASH_CORRECTION) * 0xFF;
uint8_t Backlash::correction = (BACKLASH_CORRECTION) * all_on;
#ifdef BACKLASH_SMOOTHING_MM
float Backlash::smoothing_mm = BACKLASH_SMOOTHING_MM;
#endif
@@ -61,10 +64,9 @@ Backlash backlash;
*/
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 DISABLED(CORE_BACKLASH) || EITHER(MARKFORGED_XY, MARKFORGED_YX)
if (!da) CBI(changed_dir, X_AXIS);
if (!db) CBI(changed_dir, Y_AXIS);
if (!dc) CBI(changed_dir, Z_AXIS);
@@ -83,7 +85,7 @@ void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const
#endif
last_direction_bits ^= changed_dir;
if (correction == 0) return;
if (!correction && !residual_error) return;
#ifdef BACKLASH_SMOOTHING_MM
// The segment proportion is a value greater than 0.0 indicating how much residual_error
@@ -91,39 +93,28 @@ void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const
// smoothing distance. Since the computation of this proportion involves a floating point
// division, defer computation until needed.
float segment_proportion = 0;
// Residual error carried forward across multiple segments, so correction can be applied
// to segments where there is no direction change.
static xyz_long_t residual_error{0};
#else
// No direction change, no correction.
if (!changed_dir) return;
// No leftover residual error from segment to segment
xyz_long_t residual_error{0};
#endif
const float f_corr = float(correction) / 255.0f;
const float f_corr = float(correction) / all_on;
LOOP_LINEAR_AXES(axis) {
LOOP_NUM_AXES(axis) {
if (distance_mm[axis]) {
const bool reversing = TEST(dm,axis);
const bool reverse = TEST(dm, axis);
// When an axis changes direction, add axis backlash to the residual error
if (TEST(changed_dir, axis))
residual_error[axis] += (reversing ? -f_corr : f_corr) * distance_mm[axis] * planner.settings.axis_steps_per_mm[axis];
residual_error[axis] += (reverse ? -f_corr : f_corr) * distance_mm[axis] * planner.settings.axis_steps_per_mm[axis];
// Decide how much of the residual error to correct in this segment
int32_t error_correction = residual_error[axis];
if (reverse != (error_correction < 0))
error_correction = 0; // Don't take up any backlash in this segment, as it would subtract steps
#ifdef BACKLASH_SMOOTHING_MM
if (error_correction && smoothing_mm != 0) {
// 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);
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
// Take up a portion of the residual_error in this segment
if (segment_proportion == 0) segment_proportion = _MIN(1.0f, block->millimeters / smoothing_mm);
error_correction = CEIL(segment_proportion * error_correction);
}
#endif
@@ -153,6 +144,57 @@ void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const
}
}
int32_t Backlash::get_applied_steps(const AxisEnum axis) {
if (axis >= NUM_AXES) return 0;
const bool reverse = TEST(last_direction_bits, axis);
const int32_t residual_error_axis = residual_error[axis];
// At startup it is assumed the last move was forwards. So the applied
// steps will always be a non-positive number.
if (!reverse) return -residual_error_axis;
const float f_corr = float(correction) / all_on;
const int32_t full_error_axis = -f_corr * distance_mm[axis] * planner.settings.axis_steps_per_mm[axis];
return full_error_axis - residual_error_axis;
}
class Backlash::StepAdjuster {
private:
xyz_long_t applied_steps;
public:
StepAdjuster() {
LOOP_NUM_AXES(axis) applied_steps[axis] = backlash.get_applied_steps((AxisEnum)axis);
}
~StepAdjuster() {
// after backlash compensation parameter changes, ensure applied step count does not change
LOOP_NUM_AXES(axis) residual_error[axis] += backlash.get_applied_steps((AxisEnum)axis) - applied_steps[axis];
}
};
#if ENABLED(BACKLASH_GCODE)
void Backlash::set_correction_uint8(const uint8_t v) {
StepAdjuster adjuster;
correction = v;
}
void Backlash::set_distance_mm(const AxisEnum axis, const float v) {
StepAdjuster adjuster;
distance_mm[axis] = v;
}
#ifdef BACKLASH_SMOOTHING_MM
void Backlash::set_smoothing_mm(const float v) {
StepAdjuster adjuster;
smoothing_mm = v;
}
#endif
#endif
#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
#include "../module/probe.h"

View File

@@ -24,21 +24,22 @@
#include "../inc/MarlinConfigPre.h"
#include "../module/planner.h"
constexpr uint8_t all_on = 0xFF, all_off = 0x00;
class Backlash {
public:
static constexpr uint8_t all_on = 0xFF, all_off = 0x00;
private:
static axis_bits_t last_direction_bits;
static xyz_long_t residual_error;
#if ENABLED(BACKLASH_GCODE)
static xyz_float_t distance_mm;
static uint8_t correction;
static xyz_float_t distance_mm;
#ifdef BACKLASH_SMOOTHING_MM
static float smoothing_mm;
#endif
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;
static constexpr uint8_t correction = (BACKLASH_CORRECTION) * all_on;
static const xyz_float_t distance_mm;
#ifdef BACKLASH_SMOOTHING_MM
static constexpr float smoothing_mm = BACKLASH_SMOOTHING_MM;
@@ -46,14 +47,14 @@ public:
#endif
#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
private:
static xyz_float_t measured_mm;
static xyz_uint8_t measured_count;
public:
static void measure_with_probe();
static xyz_float_t measured_mm;
static xyz_uint8_t measured_count;
#endif
static inline float get_measurement(const AxisEnum a) {
class StepAdjuster;
public:
static float get_measurement(const AxisEnum a) {
UNUSED(a);
// Return the measurement averaged over all readings
return TERN(MEASURE_BACKLASH_WHEN_PROBING
@@ -62,16 +63,34 @@ public:
);
}
static inline bool has_measurement(const AxisEnum a) {
static bool has_measurement(const AxisEnum a) {
UNUSED(a);
return TERN0(MEASURE_BACKLASH_WHEN_PROBING, measured_count[a] > 0);
}
static inline bool has_any_measurement() {
static 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 axis_bits_t dm, block_t * const block);
static 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);
static int32_t get_applied_steps(const AxisEnum axis);
#if ENABLED(BACKLASH_GCODE)
static void set_correction_uint8(const uint8_t v);
static uint8_t get_correction_uint8() { return correction; }
static void set_correction(const float v) { set_correction_uint8(_MAX(0, _MIN(1.0, v)) * all_on + 0.5f); }
static float get_correction() { return float(get_correction_uint8()) / all_on; }
static void set_distance_mm(const AxisEnum axis, const float v);
static float get_distance_mm(const AxisEnum axis) {return distance_mm[axis];}
#ifdef BACKLASH_SMOOTHING_MM
static void set_smoothing_mm(const float v);
static float get_smoothing_mm() {return smoothing_mm;}
#endif
#endif
#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
static void measure_with_probe();
#endif
};
extern Backlash backlash;

View File

@@ -35,14 +35,19 @@
#include "../../../lcd/extui/ui_api.h"
#endif
xy_pos_t bilinear_grid_spacing, bilinear_start;
xy_float_t bilinear_grid_factor;
bed_mesh_t z_values;
LevelingBilinear bedlevel;
xy_pos_t LevelingBilinear::grid_spacing,
LevelingBilinear::grid_start;
xy_float_t LevelingBilinear::grid_factor;
bed_mesh_t LevelingBilinear::z_values;
xy_pos_t LevelingBilinear::cached_rel;
xy_int8_t LevelingBilinear::cached_g;
/**
* Extrapolate a single point from its neighbors
*/
static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) {
void LevelingBilinear::extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) {
if (!isnan(z_values[x][y])) return;
if (DEBUGGING(LEVELING)) {
DEBUG_ECHOPGM("Extrapolate [");
@@ -92,11 +97,26 @@ static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t
#endif
#endif
void LevelingBilinear::reset() {
grid_start.reset();
grid_spacing.reset();
GRID_LOOP(x, y) {
z_values[x][y] = NAN;
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, 0));
}
}
void LevelingBilinear::set_grid(const xy_pos_t& _grid_spacing, const xy_pos_t& _grid_start) {
grid_spacing = _grid_spacing;
grid_start = _grid_start;
grid_factor = grid_spacing.reciprocal();
}
/**
* Fill in the unprobed points (corners of circular print surface)
* using linear extrapolation, away from the center.
*/
void extrapolate_unprobed_bed_level() {
void LevelingBilinear::extrapolate_unprobed_bed_level() {
#ifdef HALF_IN_X
constexpr uint8_t ctrx2 = 0, xend = GRID_MAX_POINTS_X - 1;
#else
@@ -131,35 +151,31 @@ void extrapolate_unprobed_bed_level() {
#endif
extrapolate_one_point(x2, y2, -1, -1); // right-above - -
}
}
void print_bilinear_leveling_grid() {
void LevelingBilinear::print_leveling_grid(const bed_mesh_t* _z_values /*= NULL*/) {
// print internal grid(s) or just the one passed as a parameter
SERIAL_ECHOLNPGM("Bilinear Leveling Grid:");
print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 3,
[](const uint8_t ix, const uint8_t iy) { return z_values[ix][iy]; }
);
print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 3, _z_values ? *_z_values[0] : z_values[0]);
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
if (!_z_values) {
SERIAL_ECHOLNPGM("Subdivided with CATMULL ROM Leveling Grid:");
print_2d_array(ABL_GRID_POINTS_VIRT_X, ABL_GRID_POINTS_VIRT_Y, 5, z_values_virt[0]);
}
#endif
}
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
#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];
xy_pos_t bilinear_grid_spacing_virt;
xy_float_t bilinear_grid_factor_virt;
void print_bilinear_leveling_grid_virt() {
SERIAL_ECHOLNPGM("Subdivided with CATMULL ROM Leveling Grid:");
print_2d_array(ABL_GRID_POINTS_VIRT_X, ABL_GRID_POINTS_VIRT_Y, 5,
[](const uint8_t ix, const uint8_t iy) { return z_values_virt[ix][iy]; }
);
}
float LevelingBilinear::z_values_virt[ABL_GRID_POINTS_VIRT_X][ABL_GRID_POINTS_VIRT_Y];
xy_pos_t LevelingBilinear::grid_spacing_virt;
xy_float_t LevelingBilinear::grid_factor_virt;
#define LINEAR_EXTRAPOLATION(E, I) ((E) * 2 - (I))
float bed_level_virt_coord(const uint8_t x, const uint8_t y) {
float LevelingBilinear::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.
@@ -204,7 +220,7 @@ void print_bilinear_leveling_grid() {
return z_values[x - 1][y - 1];
}
static float bed_level_virt_cmr(const float p[4], const uint8_t i, const float t) {
float LevelingBilinear::bed_level_virt_cmr(const float p[4], const uint8_t i, const float t) {
return (
p[i-1] * -t * sq(1 - t)
+ p[i] * (2 - 5 * sq(t) + 3 * t * sq(t))
@@ -213,7 +229,7 @@ void print_bilinear_leveling_grid() {
) * 0.5f;
}
static float bed_level_virt_2cmr(const uint8_t x, const uint8_t y, const_float_t tx, const_float_t ty) {
float LevelingBilinear::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) {
@@ -224,9 +240,9 @@ void print_bilinear_leveling_grid() {
return bed_level_virt_cmr(row, 1, tx);
}
void bed_level_virt_interpolate() {
bilinear_grid_spacing_virt = bilinear_grid_spacing / (BILINEAR_SUBDIVISIONS);
bilinear_grid_factor_virt = bilinear_grid_spacing_virt.reciprocal();
void LevelingBilinear::bed_level_virt_interpolate() {
grid_spacing_virt = grid_spacing / (BILINEAR_SUBDIVISIONS);
grid_factor_virt = grid_spacing_virt.reciprocal();
LOOP_L_N(y, GRID_MAX_POINTS_Y)
LOOP_L_N(x, GRID_MAX_POINTS_X)
LOOP_L_N(ty, BILINEAR_SUBDIVISIONS)
@@ -242,40 +258,42 @@ void print_bilinear_leveling_grid() {
);
}
}
#endif // ABL_BILINEAR_SUBDIVISION
// Refresh after other values have been updated
void refresh_bed_level() {
bilinear_grid_factor = bilinear_grid_spacing.reciprocal();
void LevelingBilinear::refresh_bed_level() {
TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate());
cached_rel.x = cached_rel.y = -999.999;
cached_g.x = cached_g.y = -99;
}
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
#define ABL_BG_SPACING(A) bilinear_grid_spacing_virt.A
#define ABL_BG_FACTOR(A) bilinear_grid_factor_virt.A
#define ABL_BG_SPACING(A) grid_spacing_virt.A
#define ABL_BG_FACTOR(A) grid_factor_virt.A
#define ABL_BG_POINTS_X ABL_GRID_POINTS_VIRT_X
#define ABL_BG_POINTS_Y ABL_GRID_POINTS_VIRT_Y
#define ABL_BG_GRID(X,Y) z_values_virt[X][Y]
#else
#define ABL_BG_SPACING(A) bilinear_grid_spacing.A
#define ABL_BG_FACTOR(A) bilinear_grid_factor.A
#define ABL_BG_SPACING(A) grid_spacing.A
#define ABL_BG_FACTOR(A) grid_factor.A
#define ABL_BG_POINTS_X GRID_MAX_POINTS_X
#define ABL_BG_POINTS_Y GRID_MAX_POINTS_Y
#define ABL_BG_GRID(X,Y) z_values[X][Y]
#endif
// Get the Z adjustment for non-linear bed leveling
float bilinear_z_offset(const xy_pos_t &raw) {
float LevelingBilinear::get_z_correction(const xy_pos_t &raw) {
static float z1, d2, z3, d4, L, D;
static xy_pos_t prev { -999.999, -999.999 }, ratio;
static xy_pos_t ratio;
// Whole units for the grid line indices. Constrained within bounds.
static xy_int8_t thisg, nextg, lastg { -99, -99 };
static xy_int8_t thisg, nextg;
// XY relative to the probed area
xy_pos_t rel = raw - bilinear_start.asFloat();
xy_pos_t rel = raw - grid_start.asFloat();
#if ENABLED(EXTRAPOLATE_BEYOND_GRID)
#define FAR_EDGE_OR_BOX 2 // Keep using the last grid box
@@ -283,8 +301,8 @@ float bilinear_z_offset(const xy_pos_t &raw) {
#define FAR_EDGE_OR_BOX 1 // Just use the grid far edge
#endif
if (prev.x != rel.x) {
prev.x = rel.x;
if (cached_rel.x != rel.x) {
cached_rel.x = rel.x;
ratio.x = rel.x * ABL_BG_FACTOR(x);
const float gx = constrain(FLOOR(ratio.x), 0, ABL_BG_POINTS_X - (FAR_EDGE_OR_BOX));
ratio.x -= gx; // Subtract whole to get the ratio within the grid box
@@ -298,10 +316,10 @@ float bilinear_z_offset(const xy_pos_t &raw) {
nextg.x = _MIN(thisg.x + 1, ABL_BG_POINTS_X - 1);
}
if (prev.y != rel.y || lastg.x != thisg.x) {
if (cached_rel.y != rel.y || cached_g.x != thisg.x) {
if (prev.y != rel.y) {
prev.y = rel.y;
if (cached_rel.y != rel.y) {
cached_rel.y = rel.y;
ratio.y = rel.y * ABL_BG_FACTOR(y);
const float gy = constrain(FLOOR(ratio.y), 0, ABL_BG_POINTS_Y - (FAR_EDGE_OR_BOX));
ratio.y -= gy;
@@ -315,8 +333,8 @@ float bilinear_z_offset(const xy_pos_t &raw) {
nextg.y = _MIN(thisg.y + 1, ABL_BG_POINTS_Y - 1);
}
if (lastg != thisg) {
lastg = thisg;
if (cached_g != thisg) {
cached_g = thisg;
// Z at the box corners
z1 = ABL_BG_GRID(thisg.x, thisg.y); // left-front
d2 = ABL_BG_GRID(thisg.x, nextg.y) - z1; // left-back (delta)
@@ -336,8 +354,8 @@ float bilinear_z_offset(const xy_pos_t &raw) {
/*
static float last_offset = 0;
if (ABS(last_offset - offset) > 0.2) {
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("Sudden Shift at x=", rel.x, " / ", grid_spacing.x, " -> thisg.x=", thisg.x);
SERIAL_ECHOLNPGM(" y=", rel.y, " / ", 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);
@@ -350,13 +368,13 @@ float bilinear_z_offset(const xy_pos_t &raw) {
#if IS_CARTESIAN && DISABLED(SEGMENT_LEVELED_MOVES)
#define CELL_INDEX(A,V) ((V - bilinear_start.A) * ABL_BG_FACTOR(A))
#define CELL_INDEX(A,V) ((V - grid_start.A) * ABL_BG_FACTOR(A))
/**
* 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 LevelingBilinear::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) };
@@ -384,7 +402,7 @@ float bilinear_z_offset(const xy_pos_t &raw) {
// Split on the X grid line
CBI(x_splits, gc.x);
end = destination;
destination.x = bilinear_start.x + ABL_BG_SPACING(x) * gc.x;
destination.x = grid_start.x + ABL_BG_SPACING(x) * gc.x;
normalized_dist = (destination.x - current_position.x) / (end.x - current_position.x);
destination.y = LINE_SEGMENT_END(y);
}
@@ -393,7 +411,7 @@ float bilinear_z_offset(const xy_pos_t &raw) {
// Split on the Y grid line
CBI(y_splits, gc.y);
end = destination;
destination.y = bilinear_start.y + ABL_BG_SPACING(y) * gc.y;
destination.y = grid_start.y + ABL_BG_SPACING(y) * gc.y;
normalized_dist = (destination.y - current_position.y) / (end.y - current_position.y);
destination.x = LINE_SEGMENT_END(x);
}
@@ -409,11 +427,11 @@ float bilinear_z_offset(const xy_pos_t &raw) {
destination.e = LINE_SEGMENT_END(e);
// Do the split and look for more borders
bilinear_line_to_destination(scaled_fr_mm_s, x_splits, y_splits);
line_to_destination(scaled_fr_mm_s, x_splits, y_splits);
// Restore destination from stack
destination = end;
bilinear_line_to_destination(scaled_fr_mm_s, x_splits, y_splits);
line_to_destination(scaled_fr_mm_s, x_splits, y_splits);
}
#endif // IS_CARTESIAN && !SEGMENT_LEVELED_MOVES

View File

@@ -0,0 +1,70 @@
/**
* 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"
class LevelingBilinear {
public:
static bed_mesh_t z_values;
static xy_pos_t grid_spacing, grid_start;
private:
static xy_float_t grid_factor;
static xy_pos_t cached_rel;
static xy_int8_t cached_g;
static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir);
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
#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)
static float z_values_virt[ABL_GRID_POINTS_VIRT_X][ABL_GRID_POINTS_VIRT_Y];
static xy_pos_t grid_spacing_virt;
static xy_float_t grid_factor_virt;
static float bed_level_virt_coord(const uint8_t x, const uint8_t y);
static float bed_level_virt_cmr(const float p[4], const uint8_t i, const float t);
static float bed_level_virt_2cmr(const uint8_t x, const uint8_t y, const_float_t tx, const_float_t ty);
static void bed_level_virt_interpolate();
#endif
public:
static void reset();
static void set_grid(const xy_pos_t& _grid_spacing, const xy_pos_t& _grid_start);
static void extrapolate_unprobed_bed_level();
static void print_leveling_grid(const bed_mesh_t* _z_values = NULL);
static void refresh_bed_level();
static bool has_mesh() { return !!grid_spacing.x; }
static bool mesh_is_valid() { return has_mesh(); }
static float get_mesh_x(const uint8_t i) { return grid_start.x + i * grid_spacing.x; }
static float get_mesh_y(const uint8_t j) { return grid_start.y + j * grid_spacing.y; }
static float get_z_correction(const xy_pos_t &raw);
static constexpr float get_z_offset() { return 0.0f; }
#if IS_CARTESIAN && DISABLED(SEGMENT_LEVELED_MOVES)
static void line_to_destination(const_feedRate_t scaled_fr_mm_s, uint16_t x_splits=0xFFFF, uint16_t y_splits=0xFFFF);
#endif
};
extern LevelingBilinear bedlevel;

View File

@@ -0,0 +1,195 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2022 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(BD_SENSOR)
#include "../../../MarlinCore.h"
#include "../../../gcode/gcode.h"
#include "../../../module/settings.h"
#include "../../../module/motion.h"
#include "../../../module/planner.h"
#include "../../../module/stepper.h"
#include "../../../module/probe.h"
#include "../../../module/temperature.h"
#include "../../../module/endstops.h"
#include "../../babystep.h"
// I2C software Master library for segment bed heating and bed distance sensor
#include <Panda_segmentBed_I2C.h>
#include "bdl.h"
BDS_Leveling bdl;
//#define DEBUG_OUT_BD
// M102 S-5 Read raw Calibrate data
// M102 S-6 Start Calibrate
// M102 S4 Set the adjustable Z height value (e.g., 'M102 S4' means it will do adjusting while the Z height <= 0.4mm , disable with 'M102 S0'.)
// M102 S-1 Read sensor information
#define MAX_BD_HEIGHT 4.0f
#define CMD_START_READ_CALIBRATE_DATA 1017
#define CMD_END_READ_CALIBRATE_DATA 1018
#define CMD_START_CALIBRATE 1019
#define CMD_END_CALIBRATE 1021
#define CMD_READ_VERSION 1016
I2C_SegmentBED BD_I2C_SENSOR;
#define BD_SENSOR_I2C_ADDR 0x3C
int8_t BDS_Leveling::config_state;
uint8_t BDS_Leveling::homing;
void BDS_Leveling::echo_name() { SERIAL_ECHOPGM("Bed Distance Leveling"); }
void BDS_Leveling::init(uint8_t _sda, uint8_t _scl, uint16_t delay_s) {
int ret = BD_I2C_SENSOR.i2c_init(_sda, _scl, BD_SENSOR_I2C_ADDR, delay_s);
if (ret != 1) SERIAL_ECHOLNPGM("BD_I2C_SENSOR Init Fail return code:", ret);
config_state = 0;
}
float BDS_Leveling::read() {
const uint16_t tmp = BD_I2C_SENSOR.BD_i2c_read();
float BD_z = NAN;
if (BD_I2C_SENSOR.BD_Check_OddEven(tmp) && (tmp & 0x3FF) < 1020)
BD_z = (tmp & 0x3FF) / 100.0f;
return BD_z;
}
void BDS_Leveling::process() {
//if (config_state == 0) return;
static millis_t next_check_ms = 0; // starting at T=0
static float z_pose = 0.0f;
const millis_t ms = millis();
if (ELAPSED(ms, next_check_ms)) { // timed out (or first run)
next_check_ms = ms + (config_state < 0 ? 1000 : 100); // check at 1Hz or 10Hz
unsigned short tmp = 0;
const float cur_z = planner.get_axis_position_mm(Z_AXIS); //current_position.z
static float old_cur_z = cur_z,
old_buf_z = current_position.z;
tmp = BD_I2C_SENSOR.BD_i2c_read();
if (BD_I2C_SENSOR.BD_Check_OddEven(tmp) && (tmp & 0x3FF) < 1020) {
const float z_sensor = (tmp & 0x3FF) / 100.0f;
if (cur_z < 0) config_state = 0;
//float abs_z = current_position.z > cur_z ? (current_position.z - cur_z) : (cur_z - current_position.z);
if ( cur_z < config_state * 0.1f
&& config_state > 0
&& old_cur_z == cur_z
&& old_buf_z == current_position.z
&& z_sensor < (MAX_BD_HEIGHT)
) {
babystep.set_mm(Z_AXIS, cur_z - z_sensor);
#if ENABLED(DEBUG_OUT_BD)
SERIAL_ECHOLNPGM("BD:", z_sensor, ", Z:", cur_z, "|", current_position.z);
#endif
}
else {
babystep.set_mm(Z_AXIS, 0);
//if (old_cur_z <= cur_z) Z_DIR_WRITE(!INVERT_Z_DIR);
stepper.set_directions();
}
old_cur_z = cur_z;
old_buf_z = current_position.z;
endstops.bdp_state_update(z_sensor <= 0.01f);
//endstops.update();
}
else
stepper.set_directions();
#if ENABLED(DEBUG_OUT_BD)
SERIAL_ECHOLNPGM("BD:", tmp & 0x3FF, ", Z:", cur_z, "|", current_position.z);
if (BD_I2C_SENSOR.BD_Check_OddEven(tmp) == 0) SERIAL_ECHOLNPGM("errorCRC");
#endif
if ((tmp & 0x3FF) > 1020) {
BD_I2C_SENSOR.BD_i2c_stop();
safe_delay(10);
}
// read raw calibrate data
if (config_state == -5) {
BD_I2C_SENSOR.BD_i2c_write(CMD_START_READ_CALIBRATE_DATA);
safe_delay(1000);
for (int i = 0; i < MAX_BD_HEIGHT * 10; i++) {
tmp = BD_I2C_SENSOR.BD_i2c_read();
SERIAL_ECHOLNPGM("Calibrate data:", i, ",", tmp & 0x3FF, ", check:", BD_I2C_SENSOR.BD_Check_OddEven(tmp));
safe_delay(500);
}
config_state = 0;
BD_I2C_SENSOR.BD_i2c_write(CMD_END_READ_CALIBRATE_DATA);
safe_delay(500);
}
else if (config_state <= -6) { // Start Calibrate
safe_delay(100);
if (config_state == -6) {
//BD_I2C_SENSOR.BD_i2c_write(1019); // begin calibrate
//delay(1000);
gcode.stepper_inactive_time = SEC_TO_MS(60 * 5);
gcode.process_subcommands_now(F("M17 Z"));
gcode.process_subcommands_now(F("G1 Z0.0"));
z_pose = 0;
safe_delay(1000);
BD_I2C_SENSOR.BD_i2c_write(CMD_START_CALIBRATE); // Begin calibrate
SERIAL_ECHOLNPGM("Begin calibrate");
safe_delay(2000);
config_state = -7;
}
else if (planner.get_axis_position_mm(Z_AXIS) < 10.0f) {
if (z_pose >= MAX_BD_HEIGHT) {
BD_I2C_SENSOR.BD_i2c_write(CMD_END_CALIBRATE); // End calibrate
SERIAL_ECHOLNPGM("End calibrate data");
z_pose = 7;
config_state = 0;
safe_delay(1000);
}
else {
float tmp_k = 0;
char tmp_1[30];
sprintf_P(tmp_1, PSTR("G1 Z%d.%d"), int(z_pose), int(int(z_pose * 10) % 10));
gcode.process_subcommands_now(tmp_1);
SERIAL_ECHO(tmp_1);
SERIAL_ECHOLNPGM(" ,Z:", current_position.z);
while (tmp_k < (z_pose - 0.1f)) {
tmp_k = planner.get_axis_position_mm(Z_AXIS);
safe_delay(1);
}
safe_delay(800);
tmp = (z_pose + 0.0001f) * 10;
BD_I2C_SENSOR.BD_i2c_write(tmp);
SERIAL_ECHOLNPGM("w:", tmp, ",Zpose:", z_pose);
z_pose += 0.1001f;
//queue.enqueue_now_P(PSTR("G90"));
}
}
}
}
}
#endif // BD_SENSOR

View File

@@ -0,0 +1,36 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2022 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 <stdint.h>
class BDS_Leveling {
public:
static int8_t config_state;
static uint8_t homing;
static void echo_name();
static void init(uint8_t _sda, uint8_t _scl, uint16_t delay_s);
static void process();
static float read();
};
extern BDS_Leveling bdl;

View File

@@ -47,14 +47,11 @@
#endif
bool leveling_is_valid() {
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());
return TERN1(HAS_MESH, bedlevel.mesh_is_valid());
}
/**
* Turn bed leveling on or off, fixing the current
* position as-needed.
* Turn bed leveling on or off, correcting the current position.
*
* Disable: Current position = physical position
* Enable: Current position = "unleveled" physical position
@@ -65,30 +62,25 @@ void set_bed_leveling_enabled(const bool enable/*=true*/) {
if (can_change && enable != planner.leveling_active) {
auto _report_leveling = []{
if (DEBUGGING(LEVELING)) {
if (planner.leveling_active)
DEBUG_POS("Leveling ON", current_position);
else
DEBUG_POS("Leveling OFF", current_position);
}
};
_report_leveling();
planner.synchronize();
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
// Force bilinear_z_offset to re-calculate next time
const xyz_pos_t reset { -9999.999, -9999.999, 0 };
(void)bilinear_z_offset(reset);
#endif
if (planner.leveling_active) { // leveling from on to off
if (DEBUGGING(LEVELING)) DEBUG_POS("Leveling ON", current_position);
// change unleveled current_position to physical current_position without moving steppers.
planner.apply_leveling(current_position);
planner.leveling_active = false; // disable only AFTER calling apply_leveling
if (DEBUGGING(LEVELING)) DEBUG_POS("...Now OFF", current_position);
}
else { // leveling from off to on
if (DEBUGGING(LEVELING)) DEBUG_POS("Leveling OFF", current_position);
planner.leveling_active = true; // enable BEFORE calling unapply_leveling, otherwise ignored
// change physical current_position to unleveled current_position without moving steppers.
planner.unapply_leveling(current_position);
if (DEBUGGING(LEVELING)) DEBUG_POS("...Now ON", current_position);
}
// Get the corrected leveled / unleveled position
planner.apply_modifiers(current_position); // Physical position with all modifiers
planner.leveling_active ^= true; // Toggle leveling between apply and unapply
planner.unapply_modifiers(current_position); // Logical position with modifiers removed
sync_plan_position();
_report_leveling();
}
}
@@ -122,23 +114,9 @@ TemporaryBedLevelingState::TemporaryBedLevelingState(const bool enable) : saved(
*/
void reset_bed_level() {
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("reset_bed_level");
#if ENABLED(AUTO_BED_LEVELING_UBL)
ubl.reset();
#else
set_bed_leveling_enabled(false);
#if ENABLED(MESH_BED_LEVELING)
mbl.reset();
#elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
bilinear_start.reset();
bilinear_grid_spacing.reset();
GRID_LOOP(x, y) {
z_values[x][y] = NAN;
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, 0));
}
#elif ABL_PLANAR
planner.bed_level_matrix.set_to_identity();
#endif
#endif
IF_DISABLED(AUTO_BED_LEVELING_UBL, set_bed_leveling_enabled(false));
TERN_(HAS_MESH, bedlevel.reset());
TERN_(ABL_PLANAR, planner.bed_level_matrix.set_to_identity());
}
#if EITHER(AUTO_BED_LEVELING_BILINEAR, MESH_BED_LEVELING)
@@ -156,7 +134,7 @@ void reset_bed_level() {
/**
* Print calibration results for plotting or manual frame adjustment.
*/
void print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, element_2d_fn fn) {
void print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, const float *values) {
#ifndef SCAD_MESH_OUTPUT
LOOP_L_N(x, sx) {
serial_spaces(precision + (x < 10 ? 3 : 2));
@@ -176,7 +154,7 @@ void reset_bed_level() {
#endif
LOOP_L_N(x, sx) {
SERIAL_CHAR(' ');
const float offset = fn(x, y);
const float offset = values[x * sy + y];
if (!isnan(offset)) {
if (offset >= 0) SERIAL_CHAR('+');
SERIAL_ECHO_F(offset, int(precision));

View File

@@ -62,16 +62,13 @@ class TemporaryBedLevelingState {
typedef float bed_mesh_t[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
#include "abl/abl.h"
#include "abl/bbl.h"
#elif ENABLED(AUTO_BED_LEVELING_UBL)
#include "ubl/ubl.h"
#elif ENABLED(MESH_BED_LEVELING)
#include "mbl/mesh_bed_leveling.h"
#endif
#define Z_VALUES(X,Y) Z_VALUES_ARR[X][Y]
#define _GET_MESH_POS(M) { _GET_MESH_X(M.a), _GET_MESH_Y(M.b) }
#if EITHER(AUTO_BED_LEVELING_BILINEAR, MESH_BED_LEVELING)
#include <stdint.h>
@@ -81,7 +78,7 @@ class TemporaryBedLevelingState {
/**
* Print calibration results for plotting or manual frame adjustment.
*/
void print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, element_2d_fn fn);
void print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, const float *values);
#endif
@@ -92,7 +89,7 @@ class TemporaryBedLevelingState {
bool valid() const { return pos.x >= 0 && pos.y >= 0; }
#if ENABLED(AUTO_BED_LEVELING_UBL)
xy_pos_t meshpos() {
return { ubl.mesh_index_to_xpos(pos.x), ubl.mesh_index_to_ypos(pos.y) };
return { bedlevel.get_mesh_x(pos.x), bedlevel.get_mesh_y(pos.y) };
}
#endif
operator xy_int8_t&() { return pos; }

View File

@@ -32,7 +32,7 @@
#include "../../../lcd/extui/ui_api.h"
#endif
mesh_bed_leveling mbl;
mesh_bed_leveling bedlevel;
float mesh_bed_leveling::z_offset,
mesh_bed_leveling::z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y],
@@ -125,9 +125,7 @@
void mesh_bed_leveling::report_mesh() {
SERIAL_ECHOPAIR_F(STRINGIFY(GRID_MAX_POINTS_X) "x" STRINGIFY(GRID_MAX_POINTS_Y) " mesh. Z offset: ", z_offset, 5);
SERIAL_ECHOLNPGM("\nMeasured points:");
print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 5,
[](const uint8_t ix, const uint8_t iy) { return z_values[ix][iy]; }
);
print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 5, z_values[0]);
}
#endif // MESH_BED_LEVELING

View File

@@ -34,9 +34,6 @@ enum MeshLevelingState : char {
#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
class mesh_bed_leveling {
public:
@@ -56,9 +53,11 @@ public:
return false;
}
static bool mesh_is_valid() { return has_mesh(); }
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) {
static 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
@@ -70,6 +69,9 @@ public:
set_z(px, py, z);
}
static float get_mesh_x(const uint8_t i) { return index_to_xpos[i]; }
static float get_mesh_y(const uint8_t i) { return index_to_ypos[i]; }
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_CELLS_X - 1);
@@ -78,10 +80,10 @@ public:
int8_t cy = (y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST);
return constrain(cy, 0, GRID_MAX_CELLS_Y - 1);
}
static inline xy_int8_t cell_indexes(const_float_t x, const_float_t y) {
static 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 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_t x) {
int8_t px = (x - (MESH_MIN_X) + 0.5f * (MESH_X_DIST)) * RECIPROCAL(MESH_X_DIST);
@@ -91,10 +93,10 @@ public:
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;
}
static inline xy_int8_t probe_indexes(const_float_t x, const_float_t y) {
static 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 xy_int8_t probe_indexes(const xy_pos_t &xy) { return probe_indexes(xy.x, xy.y); }
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),
@@ -102,12 +104,9 @@ public:
return z1 + delta_a * delta_z;
}
static float get_z(const xy_pos_t &pos
OPTARG(ENABLE_LEVELING_FADE_HEIGHT, const_float_t factor=1.0f)
) {
#if DISABLED(ENABLE_LEVELING_FADE_HEIGHT)
constexpr float factor = 1.0f;
#endif
static float get_z_offset() { return z_offset; }
static float get_z_correction(const xy_pos_t &pos) {
const xy_int8_t ind = cell_indexes(pos);
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],
@@ -115,7 +114,7 @@ public:
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 + zf * factor;
return zf;
}
#if IS_CARTESIAN && DISABLED(SEGMENT_LEVELED_MOVES)
@@ -123,4 +122,4 @@ public:
#endif
};
extern mesh_bed_leveling mbl;
extern mesh_bed_leveling bedlevel;

View File

@@ -26,7 +26,7 @@
#include "../bedlevel.h"
unified_bed_leveling ubl;
unified_bed_leveling bedlevel;
#include "../../../MarlinCore.h"
#include "../../../gcode/gcode.h"
@@ -180,10 +180,8 @@ void unified_bed_leveling::display_map(const uint8_t map_type) {
SERIAL_EOL();
serial_echo_column_labels(eachsp - 2);
}
else {
SERIAL_ECHOPGM(" for ");
SERIAL_ECHOPGM_P(csv ? PSTR("CSV:\n") : PSTR("LCD:\n"));
}
else
SERIAL_ECHOPGM(" for ", csv ? F("CSV:\n") : F("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
@@ -213,10 +211,10 @@ void unified_bed_leveling::display_map(const uint8_t map_type) {
// TODO: Display on Graphical LCD
}
else if (isnan(f))
SERIAL_ECHOPGM_P(human ? PSTR(" . ") : PSTR("NAN"));
SERIAL_ECHOF(human ? F(" . ") : F("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 (human && f >= 0) SERIAL_CHAR(f > 0 ? '+' : ' '); // Display sign also for positive numbers (' ' for 0)
SERIAL_DECIMAL(f); // Positive: 5 digits, Negative: 6 digits
}
if (csv && i < (GRID_MAX_POINTS_X) - 1) SERIAL_CHAR('\t');
@@ -281,10 +279,10 @@ bool unified_bed_leveling::sanity_check() {
}
#endif
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
process_subcommands_now(FPSTR(G28_STR)); // Home
process_subcommands_now(F(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];
@@ -292,9 +290,9 @@ bool unified_bed_leveling::sanity_check() {
queue.inject(umw_gcode);
}
process_subcommands_now_P(PSTR("G29A\nG29F10\n" // Set UBL Active & Fade 10
"M140S0\nM104S0\n" // Turn off heaters
"M500")); // Store settings
process_subcommands_now(F("G29A\nG29F10\n" // Set UBL Active & Fade 10
"M140S0\nM104S0\n" // Turn off heaters
"M500")); // Store settings
}
#endif // UBL_MESH_WIZARD

View File

@@ -70,20 +70,19 @@ private:
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;
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
static bool G29_parse_parameters() _O0;
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 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) {
static 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();
#if ENABLED(UBL_DEVEL_DEBUGGING)
static void g29_what_command();
@@ -98,17 +97,18 @@ public:
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 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 smart_fill_mesh();
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 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;
@@ -120,11 +120,11 @@ public:
static const float _mesh_index_to_xpos[GRID_MAX_POINTS_X],
_mesh_index_to_ypos[GRID_MAX_POINTS_Y];
#if HAS_LCD_MENU
#if HAS_MARLINUI_MENU
static bool lcd_map_control;
static void steppers_were_disabled();
#else
static inline void steppers_were_disabled() {}
static void steppers_were_disabled() {}
#endif
static volatile int16_t encoder_diff; // Volatile because buttons may change it at interrupt time
@@ -157,10 +157,10 @@ public:
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) {
static 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 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);
@@ -170,7 +170,7 @@ public:
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) {
static xy_int8_t closest_indexes(const xy_pos_t &xy) {
return { closest_x_index(xy.x), closest_y_index(xy.y) };
}
@@ -203,7 +203,7 @@ public:
* 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) {
static 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)) {
@@ -215,7 +215,7 @@ public:
return _UBL_OUTER_Z_RAISE;
}
const float xratio = (rx0 - mesh_index_to_xpos(x1_i)) * RECIPROCAL(MESH_X_DIST),
const float xratio = (rx0 - get_mesh_x(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
@@ -226,7 +226,7 @@ public:
//
// 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) {
static 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)) {
if (DEBUGGING(LEVELING)) {
@@ -238,7 +238,7 @@ public:
return _UBL_OUTER_Z_RAISE;
}
const float yratio = (ry0 - mesh_index_to_ypos(y1_i)) * RECIPROCAL(MESH_Y_DIST),
const float yratio = (ry0 - get_mesh_y(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
@@ -264,16 +264,17 @@ public:
return UBL_Z_RAISE_WHEN_OFF_MESH;
#endif
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);
const uint8_t mx = _MIN(cx, (GRID_MAX_POINTS_X) - 2) + 1, my = _MIN(cy, (GRID_MAX_POINTS_Y) - 2) + 1,
x0 = get_mesh_x(cx), x1 = get_mesh_x(cx + 1);
const float z1 = calc_z0(rx0, x0, z_values[cx][cy], x1, z_values[mx][cy]),
z2 = calc_z0(rx0, x0, z_values[cx][my], x1, z_values[mx][my]);
float z0 = calc_z0(ry0, get_mesh_y(cy), z1, get_mesh_y(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 (isnan(z0)) { // If part of the Mesh is undefined, it will show up as NAN
z0 = 0.0; // in 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
// needed to complete the height correction.
if (DEBUGGING(MESH_ADJUST)) DEBUG_ECHOLNPGM("??? Yikes! NAN in ");
}
@@ -285,12 +286,14 @@ public:
return z0;
}
static inline float get_z_correction(const xy_pos_t &pos) { return get_z_correction(pos.x, pos.y); }
static 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) {
static constexpr float get_z_offset() { return 0.0f; }
static float get_mesh_x(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) {
static float get_mesh_y(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);
}
@@ -300,18 +303,14 @@ public:
static void line_to_destination_cartesian(const_feedRate_t scaled_fr_mm_s, const uint8_t e);
#endif
static inline bool mesh_is_valid() {
static 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;
#define _GET_MESH_X(I) ubl.mesh_index_to_xpos(I)
#define _GET_MESH_Y(J) ubl.mesh_index_to_ypos(J)
#define Z_VALUES_ARR ubl.z_values
extern unified_bed_leveling bedlevel;
// Prevent debugging propagating to other files
#include "../../../core/debug_out.h"

View File

@@ -31,7 +31,6 @@
#include "../../../libs/hex_print.h"
#include "../../../module/settings.h"
#include "../../../lcd/marlinui.h"
#include "../../../module/stepper.h"
#include "../../../module/planner.h"
#include "../../../module/motion.h"
#include "../../../module/probe.h"
@@ -57,7 +56,7 @@
#define UBL_G29_P31
#if HAS_LCD_MENU
#if HAS_MARLINUI_MENU
bool unified_bed_leveling::lcd_map_control = false;
@@ -316,7 +315,43 @@ void unified_bed_leveling::G29() {
planner.synchronize();
// Send 'N' to force homing before G29 (internal only)
if (axes_should_home() || parser.seen_test('N')) gcode.home_all_axes();
TERN_(HAS_MULTI_HOTEND, if (active_extruder) tool_change(0));
TERN_(HAS_MULTI_HOTEND, if (active_extruder != 0) tool_change(0, true));
// Position bed horizontally and Z probe vertically.
#if defined(SAFE_BED_LEVELING_START_X) || defined(SAFE_BED_LEVELING_START_Y) || defined(SAFE_BED_LEVELING_START_Z) \
|| defined(SAFE_BED_LEVELING_START_I) || defined(SAFE_BED_LEVELING_START_J) || defined(SAFE_BED_LEVELING_START_K) \
|| defined(SAFE_BED_LEVELING_START_U) || defined(SAFE_BED_LEVELING_START_V) || defined(SAFE_BED_LEVELING_START_W)
xyze_pos_t safe_position = current_position;
#ifdef SAFE_BED_LEVELING_START_X
safe_position.x = SAFE_BED_LEVELING_START_X;
#endif
#ifdef SAFE_BED_LEVELING_START_Y
safe_position.y = SAFE_BED_LEVELING_START_Y;
#endif
#ifdef SAFE_BED_LEVELING_START_Z
safe_position.z = SAFE_BED_LEVELING_START_Z;
#endif
#ifdef SAFE_BED_LEVELING_START_I
safe_position.i = SAFE_BED_LEVELING_START_I;
#endif
#ifdef SAFE_BED_LEVELING_START_J
safe_position.j = SAFE_BED_LEVELING_START_J;
#endif
#ifdef SAFE_BED_LEVELING_START_K
safe_position.k = SAFE_BED_LEVELING_START_K;
#endif
#ifdef SAFE_BED_LEVELING_START_U
safe_position.u = SAFE_BED_LEVELING_START_U;
#endif
#ifdef SAFE_BED_LEVELING_START_V
safe_position.v = SAFE_BED_LEVELING_START_V;
#endif
#ifdef SAFE_BED_LEVELING_START_W
safe_position.w = SAFE_BED_LEVELING_START_W;
#endif
do_blocking_move_to(safe_position);
#endif
}
// Invalidate one or more nearby mesh points, possibly all.
@@ -346,13 +381,14 @@ void unified_bed_leveling::G29() {
if (parser.seen('Q')) {
const int16_t test_pattern = parser.has_value() ? parser.value_int() : -99;
if (!WITHIN(test_pattern, -1, 2)) {
SERIAL_ECHOLNPGM("Invalid test_pattern value. (-1 to 2)\n");
if (!WITHIN(test_pattern, TERN0(UBL_DEVEL_DEBUGGING, -1), 2)) {
SERIAL_ECHOLNPGM("?Invalid (Q) test pattern. (" TERN(UBL_DEVEL_DEBUGGING, "-1", "0") " to 2)\n");
return;
}
SERIAL_ECHOLNPGM("Loading test_pattern values.\n");
SERIAL_ECHOLNPGM("Applying test pattern.\n");
switch (test_pattern) {
default:
case -1: TERN_(UBL_DEVEL_DEBUGGING, g29_eeprom_dump()); break;
case 0:
@@ -366,13 +402,13 @@ void unified_bed_leveling::G29() {
case 1:
LOOP_L_N(x, GRID_MAX_POINTS_X) { // Create a diagonal line several Mesh cells thick that is raised
const uint8_t x2 = x + (x < (GRID_MAX_POINTS_Y) - 1 ? 1 : -1);
z_values[x][x] += 9.999f;
z_values[x][x + (x < (GRID_MAX_POINTS_Y) - 1) ? 1 : -1] += 9.999f; // We want the altered line several mesh points thick
z_values[x][x2] += 9.999f; // We want the altered line several mesh points thick
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(x, x, z_values[x][x]);
ExtUI::onMeshUpdate(x, (x + (x < (GRID_MAX_POINTS_Y) - 1) ? 1 : -1), z_values[x][x + (x < (GRID_MAX_POINTS_Y) - 1) ? 1 : -1]);
ExtUI::onMeshUpdate(x, (x2), z_values[x][x2]);
#endif
}
break;
@@ -442,7 +478,7 @@ void unified_bed_leveling::G29() {
#endif // HAS_BED_PROBE
case 2: {
#if HAS_LCD_MENU
#if HAS_MARLINUI_MENU
//
// Manually Probe Mesh in areas that can't be reached by the probe
//
@@ -554,7 +590,7 @@ void unified_bed_leveling::G29() {
}
case 4: // Fine Tune (i.e., Edit) the Mesh
#if HAS_LCD_MENU
#if HAS_MARLINUI_MENU
fine_tune_mesh(param.XY_pos, parser.seen_test('T'));
#else
SERIAL_ECHOLNPGM("?P4 is only available when an LCD is present.");
@@ -645,7 +681,7 @@ void unified_bed_leveling::G29() {
LEAVE:
#if HAS_LCD_MENU
#if HAS_MARLINUI_MENU
ui.reset_alert_level();
ui.quick_feedback();
ui.reset_status();
@@ -656,13 +692,13 @@ void unified_bed_leveling::G29() {
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Z Probe End Script: ", Z_PROBE_END_SCRIPT);
if (probe_deployed) {
planner.synchronize();
gcode.process_subcommands_now_P(PSTR(Z_PROBE_END_SCRIPT));
gcode.process_subcommands_now(F(Z_PROBE_END_SCRIPT));
}
#else
UNUSED(probe_deployed);
#endif
TERN_(HAS_MULTI_HOTEND, tool_change(old_tool_index));
TERN_(HAS_MULTI_HOTEND, if (old_tool_index != 0) tool_change(old_tool_index));
return;
}
@@ -724,7 +760,9 @@ void unified_bed_leveling::shift_mesh_height() {
void unified_bed_leveling::probe_entire_mesh(const xy_pos_t &nearby, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) {
probe.deploy(); // Deploy before ui.capture() to allow for PAUSE_BEFORE_DEPLOY_STOW
TERN_(HAS_LCD_MENU, ui.capture());
TERN_(HAS_MARLINUI_MENU, ui.capture());
TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart());
TERN_(DWIN_LCD_PROUI, DWIN_LevelingStart());
save_ubl_active_state_and_disable(); // No bed level correction so only raw data is obtained
uint8_t count = GRID_MAX_POINTS;
@@ -736,9 +774,9 @@ void unified_bed_leveling::shift_mesh_height() {
const uint8_t point_num = (GRID_MAX_POINTS - count) + 1;
SERIAL_ECHOLNPGM("Probing mesh point ", point_num, "/", GRID_MAX_POINTS, ".");
TERN_(HAS_STATUS_MESSAGE, ui.status_printf_P(0, PSTR(S_FMT " %i/%i"), GET_TEXT(MSG_PROBING_POINT), point_num, int(GRID_MAX_POINTS)));
TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT(MSG_PROBING_POINT), point_num, int(GRID_MAX_POINTS)));
#if HAS_LCD_MENU
#if HAS_MARLINUI_MENU
if (ui.button_pressed()) {
ui.quick_feedback(false); // Preserve button state for click-and-hold
SERIAL_ECHOLNPGM("\nMesh only partially populated.\n");
@@ -746,6 +784,7 @@ void unified_bed_leveling::shift_mesh_height() {
ui.quick_feedback();
ui.release();
probe.stow(); // Release UI before stow to allow for PAUSE_BEFORE_DEPLOY_STOW
TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone());
return restore_ubl_active_state_and_leave();
}
#endif
@@ -773,9 +812,9 @@ void unified_bed_leveling::shift_mesh_height() {
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_FINISH));
// Release UI during stow to allow for PAUSE_BEFORE_DEPLOY_STOW
TERN_(HAS_LCD_MENU, ui.release());
TERN_(HAS_MARLINUI_MENU, ui.release());
probe.stow();
TERN_(HAS_LCD_MENU, ui.capture());
TERN_(HAS_MARLINUI_MENU, ui.capture());
probe.move_z_after_probing();
@@ -785,20 +824,23 @@ void unified_bed_leveling::shift_mesh_height() {
constrain(nearby.x - probe.offset_xy.x, MESH_MIN_X, MESH_MAX_X),
constrain(nearby.y - probe.offset_xy.y, MESH_MIN_Y, MESH_MAX_Y)
);
TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone());
TERN_(DWIN_LCD_PROUI, DWIN_LevelingDone());
}
#endif // HAS_BED_PROBE
void set_message_with_feedback(PGM_P const msg_P) {
#if HAS_LCD_MENU
ui.set_status_P(msg_P);
void set_message_with_feedback(FSTR_P const fstr) {
#if HAS_MARLINUI_MENU
ui.set_status(fstr);
ui.quick_feedback();
#else
UNUSED(msg_P);
UNUSED(fstr);
#endif
}
#if HAS_LCD_MENU
#if HAS_MARLINUI_MENU
typedef void (*clickFunc_t)();
@@ -850,7 +892,7 @@ void set_message_with_feedback(PGM_P const msg_P) {
planner.synchronize();
SERIAL_ECHOPGM("Place shim under nozzle");
LCD_MESSAGEPGM(MSG_UBL_BC_INSERT);
LCD_MESSAGE(MSG_UBL_BC_INSERT);
ui.return_to_status();
echo_and_take_a_measurement();
@@ -859,7 +901,7 @@ void set_message_with_feedback(PGM_P const msg_P) {
planner.synchronize();
SERIAL_ECHOPGM("Remove shim");
LCD_MESSAGEPGM(MSG_UBL_BC_REMOVE);
LCD_MESSAGE(MSG_UBL_BC_REMOVE);
echo_and_take_a_measurement();
const float z2 = measure_point_with_encoder();
@@ -884,6 +926,7 @@ void set_message_with_feedback(PGM_P const msg_P) {
*/
void unified_bed_leveling::manually_probe_remaining_mesh(const xy_pos_t &pos, const_float_t z_clearance, const_float_t thick, const bool do_ubl_mesh_map) {
ui.capture();
TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart());
save_ubl_active_state_and_disable(); // No bed level correction so only raw data is obtained
do_blocking_move_to_xy_z(current_position, z_clearance);
@@ -897,15 +940,11 @@ void set_message_with_feedback(PGM_P const msg_P) {
// It doesn't matter if the probe can't reach the NAN location. This is a manual probe.
if (!location.valid()) continue;
const xyz_pos_t ppos = {
mesh_index_to_xpos(lpos.x),
mesh_index_to_ypos(lpos.y),
z_clearance
};
const xyz_pos_t ppos = { get_mesh_x(lpos.x), get_mesh_y(lpos.y), z_clearance };
if (!position_is_reachable(ppos)) break; // SHOULD NOT OCCUR (find_closest_mesh_point only returns reachable points)
LCD_MESSAGEPGM(MSG_UBL_MOVING_TO_NEXT);
LCD_MESSAGE(MSG_UBL_MOVING_TO_NEXT);
do_blocking_move_to(ppos);
do_z_clearance(z_clearance);
@@ -917,11 +956,11 @@ void set_message_with_feedback(PGM_P const msg_P) {
if (parser.seen_test('B')) {
SERIAL_ECHOPGM("Place Shim & Measure");
LCD_MESSAGEPGM(MSG_UBL_BC_INSERT);
LCD_MESSAGE(MSG_UBL_BC_INSERT);
}
else {
SERIAL_ECHOPGM("Measure");
LCD_MESSAGEPGM(MSG_UBL_BC_INSERT2);
LCD_MESSAGE(MSG_UBL_BC_INSERT2);
}
const float z_step = 0.01f; // 0.01mm per encoder tick, occasionally step
@@ -947,6 +986,8 @@ void set_message_with_feedback(PGM_P const msg_P) {
restore_ubl_active_state_and_leave();
do_blocking_move_to_xy_z(pos, Z_CLEARANCE_DEPLOY_PROBE);
TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone());
}
/**
@@ -974,7 +1015,7 @@ void set_message_with_feedback(PGM_P const msg_P) {
save_ubl_active_state_and_disable();
LCD_MESSAGEPGM(MSG_UBL_FINE_TUNE_MESH);
LCD_MESSAGE(MSG_UBL_FINE_TUNE_MESH);
ui.capture(); // Take over control of the LCD encoder
do_blocking_move_to_xy_z(pos, Z_CLEARANCE_BETWEEN_PROBES); // Move to the given XY with probe clearance
@@ -994,11 +1035,7 @@ void set_message_with_feedback(PGM_P const msg_P) {
done_flags.mark(lpos); // Mark this location as 'adjusted' so a new
// location is used on the next loop
const xyz_pos_t raw = {
mesh_index_to_xpos(lpos.x),
mesh_index_to_ypos(lpos.y),
Z_CLEARANCE_BETWEEN_PROBES
};
const xyz_pos_t raw = { get_mesh_x(lpos.x), get_mesh_y(lpos.y), Z_CLEARANCE_BETWEEN_PROBES };
if (!position_is_reachable(raw)) break; // SHOULD NOT OCCUR (find_closest_mesh_point_of_type only returns reachable)
@@ -1039,7 +1076,7 @@ void set_message_with_feedback(PGM_P const msg_P) {
if (_click_and_hold([]{
ui.return_to_status();
do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES);
set_message_with_feedback(GET_TEXT(MSG_EDITING_STOPPED));
set_message_with_feedback(GET_TEXT_F(MSG_EDITING_STOPPED));
})) break;
// TODO: Disable leveling here so the Z value becomes the 'native' Z value.
@@ -1060,7 +1097,7 @@ void set_message_with_feedback(PGM_P const msg_P) {
do_blocking_move_to_xy_z(pos, Z_CLEARANCE_BETWEEN_PROBES);
LCD_MESSAGEPGM(MSG_UBL_DONE_EDITING_MESH);
LCD_MESSAGE(MSG_UBL_DONE_EDITING_MESH);
SERIAL_ECHOLNPGM("Done Editing Mesh");
if (lcd_map_control)
@@ -1069,7 +1106,7 @@ void set_message_with_feedback(PGM_P const msg_P) {
ui.return_to_status();
}
#endif // HAS_LCD_MENU
#endif // HAS_MARLINUI_MENU
/**
* Parse and validate most G29 parameters, store for use by G29 functions.
@@ -1077,7 +1114,7 @@ void set_message_with_feedback(PGM_P const msg_P) {
bool unified_bed_leveling::G29_parse_parameters() {
bool err_flag = false;
set_message_with_feedback(GET_TEXT(MSG_UBL_DOING_G29));
set_message_with_feedback(GET_TEXT_F(MSG_UBL_DOING_G29));
param.C_constant = 0;
param.R_repetition = 0;
@@ -1200,7 +1237,7 @@ void unified_bed_leveling::save_ubl_active_state_and_disable() {
ubl_state_recursion_chk++;
if (ubl_state_recursion_chk != 1) {
SERIAL_ECHOLNPGM("save_ubl_active_state_and_disabled() called multiple times in a row.");
set_message_with_feedback(GET_TEXT(MSG_UBL_SAVE_ERROR));
set_message_with_feedback(GET_TEXT_F(MSG_UBL_SAVE_ERROR));
return;
}
#endif
@@ -1209,15 +1246,16 @@ void unified_bed_leveling::save_ubl_active_state_and_disable() {
}
void unified_bed_leveling::restore_ubl_active_state_and_leave() {
TERN_(HAS_LCD_MENU, ui.release());
TERN_(HAS_MARLINUI_MENU, ui.release());
#if ENABLED(UBL_DEVEL_DEBUGGING)
if (--ubl_state_recursion_chk) {
SERIAL_ECHOLNPGM("restore_ubl_active_state_and_leave() called too many times.");
set_message_with_feedback(GET_TEXT(MSG_UBL_RESTORE_ERROR));
set_message_with_feedback(GET_TEXT_F(MSG_UBL_RESTORE_ERROR));
return;
}
#endif
set_bed_leveling_enabled(ubl_state_at_invocation);
TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone());
}
mesh_index_pair unified_bed_leveling::find_furthest_invalid_mesh_point() {
@@ -1230,7 +1268,7 @@ mesh_index_pair unified_bed_leveling::find_furthest_invalid_mesh_point() {
if (!isnan(z_values[i][j])) continue; // Skip valid mesh points
// Skip unreachable points
if (!probe.can_reach(mesh_index_to_xpos(i), mesh_index_to_ypos(j)))
if (!probe.can_reach(get_mesh_x(i), get_mesh_y(j)))
continue;
found_a_NAN = true;
@@ -1282,11 +1320,11 @@ mesh_index_pair unified_bed_leveling::find_furthest_invalid_mesh_point() {
static bool test_func(uint8_t i, uint8_t j, void *data) {
find_closest_t *d = (find_closest_t*)data;
if ( d->type == CLOSEST || d->type == (isnan(ubl.z_values[i][j]) ? INVALID : REAL)
if ( d->type == CLOSEST || d->type == (isnan(bedlevel.z_values[i][j]) ? INVALID : REAL)
|| (d->type == SET_IN_BITMAP && !d->done_flags->marked(i, j))
) {
// Found a Mesh Point of the specified type!
const xy_pos_t mpos = { ubl.mesh_index_to_xpos(i), ubl.mesh_index_to_ypos(j) };
const xy_pos_t mpos = { bedlevel.get_mesh_x(i), bedlevel.get_mesh_y(j) };
// If using the probe as the reference there are some unreachable locations.
// Also for round beds, there are grid points outside the bed the nozzle can't reach.
@@ -1330,7 +1368,7 @@ mesh_index_pair unified_bed_leveling::find_closest_mesh_point_of_type(const Mesh
|| (type == SET_IN_BITMAP && !done_flags->marked(i, j))
) {
// Found a Mesh Point of the specified type!
const xy_pos_t mpos = { mesh_index_to_xpos(i), mesh_index_to_ypos(j) };
const xy_pos_t mpos = { get_mesh_x(i), get_mesh_y(j) };
// If using the probe as the reference there are some unreachable locations.
// Also for round beds, there are grid points outside the bed the nozzle can't reach.
@@ -1386,10 +1424,10 @@ typedef struct { uint8_t sx, ex, sy, ey; bool yfirst; } smart_fill_info;
void unified_bed_leveling::smart_fill_mesh() {
static const smart_fill_info
info0 PROGMEM = { 0, GRID_MAX_POINTS_X, 0, GRID_MAX_POINTS_Y - 2, false }, // Bottom of the mesh looking up
info1 PROGMEM = { 0, GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y - 1, 0, false }, // Top of the mesh looking down
info2 PROGMEM = { 0, GRID_MAX_POINTS_X - 2, 0, GRID_MAX_POINTS_Y, true }, // Left side of the mesh looking right
info3 PROGMEM = { GRID_MAX_POINTS_X - 1, 0, 0, GRID_MAX_POINTS_Y, true }; // Right side of the mesh looking left
info0 PROGMEM = { 0, GRID_MAX_POINTS_X, 0, (GRID_MAX_POINTS_Y) - 2, false }, // Bottom of the mesh looking up
info1 PROGMEM = { 0, GRID_MAX_POINTS_X, (GRID_MAX_POINTS_Y) - 1, 0, false }, // Top of the mesh looking down
info2 PROGMEM = { 0, (GRID_MAX_POINTS_X) - 2, 0, GRID_MAX_POINTS_Y, true }, // Left side of the mesh looking right
info3 PROGMEM = { (GRID_MAX_POINTS_X) - 1, 0, 0, GRID_MAX_POINTS_Y, true }; // Right side of the mesh looking left
static const smart_fill_info * const info[] PROGMEM = { &info0, &info1, &info2, &info3 };
LOOP_L_N(i, COUNT(info)) {
@@ -1438,7 +1476,7 @@ void unified_bed_leveling::smart_fill_mesh() {
if (do_3_pt_leveling) {
SERIAL_ECHOLNPGM("Tilting mesh (1/3)");
TERN_(HAS_STATUS_MESSAGE, ui.status_printf_P(0, PSTR(S_FMT " 1/3"), GET_TEXT(MSG_LCD_TILTING_MESH)));
TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " 1/3"), GET_TEXT(MSG_LCD_TILTING_MESH)));
measured_z = probe.probe_at_point(points[0], PROBE_PT_RAISE, param.V_verbosity);
if (isnan(measured_z))
@@ -1457,7 +1495,7 @@ void unified_bed_leveling::smart_fill_mesh() {
if (!abort_flag) {
SERIAL_ECHOLNPGM("Tilting mesh (2/3)");
TERN_(HAS_STATUS_MESSAGE, ui.status_printf_P(0, PSTR(S_FMT " 2/3"), GET_TEXT(MSG_LCD_TILTING_MESH)));
TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " 2/3"), GET_TEXT(MSG_LCD_TILTING_MESH)));
measured_z = probe.probe_at_point(points[1], PROBE_PT_RAISE, param.V_verbosity);
#ifdef VALIDATE_MESH_TILT
@@ -1477,7 +1515,7 @@ void unified_bed_leveling::smart_fill_mesh() {
if (!abort_flag) {
SERIAL_ECHOLNPGM("Tilting mesh (3/3)");
TERN_(HAS_STATUS_MESSAGE, ui.status_printf_P(0, PSTR(S_FMT " 3/3"), GET_TEXT(MSG_LCD_TILTING_MESH)));
TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " 3/3"), GET_TEXT(MSG_LCD_TILTING_MESH)));
measured_z = probe.probe_at_point(points[2], PROBE_PT_LAST_STOW, param.V_verbosity);
#ifdef VALIDATE_MESH_TILT
@@ -1518,7 +1556,7 @@ void unified_bed_leveling::smart_fill_mesh() {
if (!abort_flag) {
SERIAL_ECHOLNPGM("Tilting mesh point ", point_num, "/", total_points, "\n");
TERN_(HAS_STATUS_MESSAGE, ui.status_printf_P(0, PSTR(S_FMT " %i/%i"), GET_TEXT(MSG_LCD_TILTING_MESH), point_num, total_points));
TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT(MSG_LCD_TILTING_MESH), point_num, total_points));
measured_z = probe.probe_at_point(rpos, parser.seen_test('E') ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); // TODO: Needs error handling
@@ -1578,9 +1616,7 @@ void unified_bed_leveling::smart_fill_mesh() {
matrix_3x3 rotation = matrix_3x3::create_look_at(vector_3(lsf_results.A, lsf_results.B, 1));
GRID_LOOP(i, j) {
float mx = mesh_index_to_xpos(i),
my = mesh_index_to_ypos(j),
mz = z_values[i][j];
float mx = get_mesh_x(i), my = get_mesh_y(j), mz = z_values[i][j];
if (DEBUGGING(LEVELING)) {
DEBUG_ECHOPAIR_F("before rotation = [", mx, 7);
@@ -1609,7 +1645,7 @@ void unified_bed_leveling::smart_fill_mesh() {
}
if (DEBUGGING(LEVELING)) {
rotation.debug(PSTR("rotation matrix:\n"));
rotation.debug(F("rotation matrix:\n"));
DEBUG_ECHOPAIR_F("LSF Results A=", lsf_results.A, 7);
DEBUG_ECHOPAIR_F(" B=", lsf_results.B, 7);
DEBUG_ECHOLNPAIR_F(" D=", lsf_results.D, 7);
@@ -1636,14 +1672,14 @@ void unified_bed_leveling::smart_fill_mesh() {
auto normed = [&](const xy_pos_t &pos, const_float_t zadd) {
return normal.x * pos.x + normal.y * pos.y + zadd;
};
auto debug_pt = [](PGM_P const pre, const xy_pos_t &pos, const_float_t zadd) {
d_from(); SERIAL_ECHOPGM_P(pre);
auto debug_pt = [](FSTR_P const pre, const xy_pos_t &pos, const_float_t zadd) {
d_from(); SERIAL_ECHOF(pre);
DEBUG_ECHO_F(normed(pos, zadd), 6);
DEBUG_ECHOLNPAIR_F(" Z error = ", zadd - get_z_correction(pos), 6);
};
debug_pt(PSTR("1st point: "), probe_pt[0], normal.z * z1);
debug_pt(PSTR("2nd point: "), probe_pt[1], normal.z * z2);
debug_pt(PSTR("3rd point: "), probe_pt[2], normal.z * z3);
debug_pt(F("1st point: "), probe_pt[0], normal.z * z1);
debug_pt(F("2nd point: "), probe_pt[1], normal.z * z2);
debug_pt(F("3rd point: "), probe_pt[2], normal.z * z3);
d_from(); DEBUG_ECHOPGM("safe home with Z=");
DEBUG_ECHOLNPAIR_F("0 : ", normed(safe_homing_xy, 0), 6);
d_from(); DEBUG_ECHOPGM("safe home with Z=");
@@ -1677,18 +1713,18 @@ void unified_bed_leveling::smart_fill_mesh() {
xy_pos_t ppos;
LOOP_L_N(ix, GRID_MAX_POINTS_X) {
ppos.x = mesh_index_to_xpos(ix);
ppos.x = get_mesh_x(ix);
LOOP_L_N(iy, GRID_MAX_POINTS_Y) {
ppos.y = mesh_index_to_ypos(iy);
ppos.y = get_mesh_y(iy);
if (isnan(z_values[ix][iy])) {
// undefined mesh point at (ppos.x,ppos.y), compute weighted LSF from original valid mesh points.
incremental_LSF_reset(&lsf_results);
xy_pos_t rpos;
LOOP_L_N(jx, GRID_MAX_POINTS_X) {
rpos.x = mesh_index_to_xpos(jx);
rpos.x = get_mesh_x(jx);
LOOP_L_N(jy, GRID_MAX_POINTS_Y) {
if (TEST(bitmap[jx], jy)) {
rpos.y = mesh_index_to_ypos(jy);
rpos.y = get_mesh_y(jy);
const float rz = z_values[jx][jy],
w = 1.0f + weight_scaled / (rpos - ppos).magnitude();
incremental_WLSF(&lsf_results, rpos, rz, w);
@@ -1747,7 +1783,7 @@ void unified_bed_leveling::smart_fill_mesh() {
SERIAL_ECHOPGM("X-Axis Mesh Points at: ");
LOOP_L_N(i, GRID_MAX_POINTS_X) {
SERIAL_ECHO_F(LOGICAL_X_POSITION(mesh_index_to_xpos(i)), 3);
SERIAL_ECHO_F(LOGICAL_X_POSITION(get_mesh_x(i)), 3);
SERIAL_ECHOPGM(" ");
serial_delay(25);
}
@@ -1755,7 +1791,7 @@ void unified_bed_leveling::smart_fill_mesh() {
SERIAL_ECHOPGM("Y-Axis Mesh Points at: ");
LOOP_L_N(i, GRID_MAX_POINTS_Y) {
SERIAL_ECHO_F(LOGICAL_Y_POSITION(mesh_index_to_ypos(i)), 3);
SERIAL_ECHO_F(LOGICAL_Y_POSITION(get_mesh_y(i)), 3);
SERIAL_ECHOPGM(" ");
serial_delay(25);
}

View File

@@ -26,7 +26,6 @@
#include "../bedlevel.h"
#include "../../../module/planner.h"
#include "../../../module/stepper.h"
#include "../../../module/motion.h"
#if ENABLED(DELTA)
@@ -36,8 +35,18 @@
#include "../../../MarlinCore.h"
#include <math.h>
//#define DEBUG_UBL_MOTION
#define DEBUG_OUT ENABLED(DEBUG_UBL_MOTION)
#include "../../../core/debug_out.h"
#if !UBL_SEGMENTED
// TODO: The first and last parts of a move might result in very short segment(s)
// after getting split on the cell boundary, so moves like that should not
// get split. This will be most common for moves that start/end near the
// corners of cells. To fix the issue, simply check if the start/end of the line
// is very close to a cell boundary in advance and don't split the line there.
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
@@ -76,8 +85,8 @@
#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),
const float xratio = (end.x - get_mesh_x(iend.x)) * RECIPROCAL(MESH_X_DIST),
yratio = (end.y - get_mesh_y(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]);
@@ -139,7 +148,7 @@
icell.y += ineg.y; // Line going down? Just go to the bottom.
while (icell.y != iend.y + ineg.y) {
icell.y += iadd.y;
const float next_mesh_line_y = mesh_index_to_ypos(icell.y);
const float next_mesh_line_y = get_mesh_y(icell.y);
/**
* Skip the calculations for an infinite slope.
@@ -155,7 +164,7 @@
// Replace NAN corrections with 0.0 to prevent NAN propagation.
if (isnan(z0)) z0 = 0.0;
dest.y = mesh_index_to_ypos(icell.y);
dest.y = get_mesh_y(icell.y);
/**
* Without this check, it's possible to generate a zero length move, as in the case where
@@ -176,7 +185,9 @@
dest.z += z0;
planner.buffer_segment(dest, scaled_fr_mm_s, extruder);
} //else printf("FIRST MOVE PRUNED ");
}
else
DEBUG_ECHOLNPGM("[ubl] skip Y segment");
}
// At the final destination? Usually not, but when on a Y Mesh Line it's completed.
@@ -196,7 +207,7 @@
while (icell.x != iend.x + ineg.x) {
icell.x += iadd.x;
dest.x = mesh_index_to_xpos(icell.x);
dest.x = get_mesh_x(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(dest.y, icell.x, icell.y)
@@ -225,7 +236,9 @@
dest.z += z0;
if (!planner.buffer_segment(dest, scaled_fr_mm_s, extruder)) break;
} //else printf("FIRST MOVE PRUNED ");
}
else
DEBUG_ECHOLNPGM("[ubl] skip Y segment");
}
if (xy_pos_t(current_position) != xy_pos_t(end))
@@ -245,8 +258,8 @@
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);
const float next_mesh_line_x = get_mesh_x(icell.x + iadd.x),
next_mesh_line_y = get_mesh_y(icell.y + iadd.y);
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
@@ -340,7 +353,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
@@ -360,11 +373,12 @@
#endif
NOLESS(segments, 1U); // Must have at least one segment
const float inv_segments = 1.0f / segments, // Reciprocal to save calculation
segment_xyz_mm = SQRT(cart_xy_mm_2 + sq(total.z)) * inv_segments; // Length of each segment
const float inv_segments = 1.0f / segments; // Reciprocal to save calculation
// Add hints to help optimize the move
PlannerHints hints(SQRT(cart_xy_mm_2 + sq(total.z)) * inv_segments); // Length of each segment
#if ENABLED(SCARA_FEEDRATE_SCALING)
const float inv_duration = scaled_fr_mm_s / segment_xyz_mm;
hints.inv_duration = scaled_fr_mm_s / hints.millimeters;
#endif
xyze_float_t diff = total * inv_segments;
@@ -378,13 +392,9 @@
if (!planner.leveling_active || !planner.leveling_active_at_z(destination.z)) {
while (--segments) {
raw += diff;
planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, segment_xyz_mm
OPTARG(SCARA_FEEDRATE_SCALING, inv_duration)
);
planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints);
}
planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, segment_xyz_mm
OPTARG(SCARA_FEEDRATE_SCALING, inv_duration)
);
planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, hints);
return false; // Did not set current from destination
}
@@ -423,7 +433,7 @@
if (isnan(z_x0y1)) z_x0y1 = 0; // in order to avoid isnan tests per cell,
if (isnan(z_x1y1)) z_x1y1 = 0; // thus guessing zero for undefined points
const xy_pos_t pos = { mesh_index_to_xpos(icell.x), mesh_index_to_ypos(icell.y) };
const xy_pos_t pos = { get_mesh_x(icell.x), get_mesh_y(icell.y) };
xy_pos_t cell = raw - pos;
const float z_xmy0 = (z_x1y0 - z_x0y0) * RECIPROCAL(MESH_X_DIST), // z slope per x along y0 (lower left to lower right)
@@ -450,13 +460,10 @@
if (--segments == 0) raw = destination; // if this is last segment, use destination for exact
const float z_cxcy = (z_cxy0 + z_cxym * cell.y) // interpolated mesh z height along cell.x at cell.y
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
* fade_scaling_factor // apply fade factor to interpolated mesh height
#endif
;
TERN_(ENABLE_LEVELING_FADE_HEIGHT, * fade_scaling_factor); // apply fade factor to interpolated height
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) );
planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints);
raw.z = oldz;
if (segments == 0) // done with last segment

View File

@@ -28,7 +28,12 @@
BLTouch bltouch;
bool BLTouch::last_written_mode; // Initialized by settings.load, 0 = Open Drain; 1 = 5V Drain
bool BLTouch::od_5v_mode; // Initialized by settings.load, 0 = Open Drain; 1 = 5V Drain
#ifdef BLTOUCH_HS_MODE
bool BLTouch::high_speed_mode; // Initialized by settings.load, 0 = Low Speed; 1 = High Speed
#else
constexpr bool BLTouch::high_speed_mode;
#endif
#include "../module/servo.h"
#include "../module/probe.h"
@@ -40,7 +45,7 @@ void stop();
bool BLTouch::command(const BLTCommand cmd, const millis_t &ms) {
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("BLTouch Command :", cmd);
MOVE_SERVO(Z_PROBE_SERVO_NR, cmd);
servo[Z_PROBE_SERVO_NR].move(cmd);
safe_delay(_MAX(ms, (uint32_t)BLTOUCH_DELAY)); // BLTOUCH_DELAY is also the *minimum* delay
return triggered();
}
@@ -63,18 +68,17 @@ void BLTouch::init(const bool set_voltage/*=false*/) {
#else
if (DEBUGGING(LEVELING)) {
DEBUG_ECHOLNPGM("last_written_mode - ", last_written_mode);
DEBUG_ECHOLNPGM("config mode - "
#if ENABLED(BLTOUCH_SET_5V_MODE)
"BLTOUCH_SET_5V_MODE"
#else
"OD"
#endif
);
}
#ifdef DEBUG_OUT
if (DEBUGGING(LEVELING)) {
PGMSTR(mode0, "OD");
PGMSTR(mode1, "5V");
DEBUG_ECHOPGM("BLTouch Mode: ");
DEBUG_ECHOPGM_P(bltouch.od_5v_mode ? mode1 : mode0);
DEBUG_ECHOLNPGM(" (Default " TERN(BLTOUCH_SET_5V_MODE, "5V", "OD") ")");
}
#endif
const bool should_set = last_written_mode != ENABLED(BLTOUCH_SET_5V_MODE);
const bool should_set = od_5v_mode != ENABLED(BLTOUCH_SET_5V_MODE);
#endif
@@ -107,11 +111,8 @@ bool BLTouch::deploy_proc() {
// Last attempt to DEPLOY
if (_deploy_query_alarm()) {
// The deploy might have failed or the probe is actually triggered (nozzle too low?) again
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("BLTouch Recovery Failed");
SERIAL_ERROR_MSG(STR_STOP_BLTOUCH); // Tell the user something is wrong, needs action
stop(); // but it's not too bad, no need to kill, allow restart
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("BLTouch Deploy Failed");
probe.probe_error_stop(); // Something is wrong, needs action, but not too bad, allow restart
return true; // Tell our caller we goofed in case he cares to know
}
}
@@ -149,12 +150,8 @@ bool BLTouch::stow_proc() {
// But one more STOW will catch that
// Last attempt to STOW
if (_stow_query_alarm()) { // so if there is now STILL an ALARM condition:
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("BLTouch Recovery Failed");
SERIAL_ERROR_MSG(STR_STOP_BLTOUCH); // Tell the user something is wrong, needs action
stop(); // but it's not too bad, no need to kill, allow restart
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("BLTouch Stow Failed");
probe.probe_error_stop(); // Something is wrong, needs action, but not too bad, allow restart
return true; // Tell our caller we goofed in case he cares to know
}
}
@@ -193,7 +190,7 @@ void BLTouch::mode_conv_proc(const bool M5V) {
_mode_store();
if (M5V) _set_5V_mode(); else _set_OD_mode();
_stow();
last_written_mode = M5V;
od_5v_mode = M5V;
}
#endif // BLTOUCH

View File

@@ -23,10 +23,6 @@
#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;
@@ -70,8 +66,17 @@ typedef unsigned char BLTCommand;
class BLTouch {
public:
static void init(const bool set_voltage=false);
static bool last_written_mode; // Initialized by settings.load, 0 = Open Drain; 1 = 5V Drain
static bool od_5v_mode; // Initialized by settings.load, 0 = Open Drain; 1 = 5V Drain
#ifdef BLTOUCH_HS_MODE
static bool high_speed_mode; // Initialized by settings.load, 0 = Low Speed; 1 = High Speed
#else
static constexpr bool high_speed_mode = false;
#endif
static float z_extra_clearance() { return high_speed_mode ? 7 : 0; }
// DEPLOY and STOW are wrapped for error handling - these are used by homing and by probing
static bool deploy() { return deploy_proc(); }

View File

@@ -46,7 +46,7 @@ void CancelObject::set_active_object(const int8_t obj) {
#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));
ui.status_printf(0, F(S_FMT " %i"), GET_TEXT(MSG_PRINTING_OBJECT), int(active_object));
else
ui.reset_status();
#endif

View File

@@ -32,10 +32,10 @@ public:
static void cancel_object(const int8_t obj);
static void uncancel_object(const int8_t obj);
static void report();
static inline bool is_canceled(const int8_t obj) { return TEST(canceled, obj); }
static inline void clear_active_object() { set_active_object(-1); }
static inline void cancel_active_object() { cancel_object(active_object); }
static inline void reset() { canceled = 0x0000; object_count = 0; clear_active_object(); }
static bool is_canceled(const int8_t obj) { return TEST(canceled, obj); }
static void clear_active_object() { set_active_object(-1); }
static void cancel_active_object() { cancel_object(active_object); }
static void reset() { canceled = 0x0000; object_count = 0; clear_active_object(); }
};
extern CancelObject cancelable;

View File

@@ -39,7 +39,6 @@ CaseLight caselight;
bool CaseLight::on = CASE_LIGHT_DEFAULT_ON;
#if CASE_LIGHT_IS_COLOR_LED
#include "leds/leds.h"
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
@@ -65,12 +64,22 @@ void CaseLight::update(const bool sflag) {
#endif
#if CASE_LIGHT_IS_COLOR_LED
leds.set_color(LEDColor(color.r, color.g, color.b OPTARG(HAS_WHITE_LED, color.w), n10ct));
#if ENABLED(CASE_LIGHT_USE_NEOPIXEL)
if (on)
// Use current color of (NeoPixel) leds and new brightness level
leds.set_color(LEDColor(leds.color.r, leds.color.g, leds.color.b OPTARG(HAS_WHITE_LED, leds.color.w) OPTARG(NEOPIXEL_LED, n10ct)));
else
// Switch off leds
leds.set_off();
#else
// Use CaseLight color (CASE_LIGHT_DEFAULT_COLOR) and new brightness level
leds.set_color(LEDColor(color.r, color.g, color.b OPTARG(HAS_WHITE_LED, color.w) OPTARG(NEOPIXEL_LED, n10ct)));
#endif
#else // !CASE_LIGHT_IS_COLOR_LED
#if CASELIGHT_USES_BRIGHTNESS
if (pin_is_pwm())
analogWrite(pin_t(CASE_LIGHT_PIN), (
hal.set_pwm_duty(pin_t(CASE_LIGHT_PIN), (
#if CASE_LIGHT_MAX_PWM == 255
n10ct
#else

View File

@@ -27,10 +27,6 @@
#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;
@@ -49,8 +45,8 @@ public:
}
static void update(const bool sflag);
static inline void update_brightness() { update(false); }
static inline void update_enabled() { update(true); }
static void update_brightness() { update(false); }
static void update_enabled() { update(true); }
#if ENABLED(CASE_LIGHT_IS_COLOR_LED)
private:

View File

@@ -58,7 +58,7 @@ void ControllerFan::update() {
// - 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)));
const ena_mask_t axis_mask = TERN(CONTROLLER_FAN_USE_Z_ONLY, _BV(Z_AXIS), (ena_mask_t)~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)
@@ -72,9 +72,14 @@ void ControllerFan::update() {
? settings.active_speed : settings.idle_speed
);
// Allow digital or PWM fan output (see M42 handling)
WRITE(CONTROLLER_FAN_PIN, speed);
analogWrite(pin_t(CONTROLLER_FAN_PIN), speed);
#if ENABLED(FAN_SOFT_PWM)
thermalManager.soft_pwm_controller_speed = speed;
#else
if (PWM_PIN(CONTROLLER_FAN_PIN))
hal.set_pwm_duty(pin_t(CONTROLLER_FAN_PIN), speed);
else
WRITE(CONTROLLER_FAN_PIN, speed > 0);
#endif
}
}

View File

@@ -60,9 +60,9 @@ class ControllerFan {
#else
static const controllerFan_settings_t &settings;
#endif
static inline bool state() { return speed > 0; }
static inline void init() { reset(); }
static inline void reset() { TERN_(CONTROLLER_FAN_EDITABLE, settings = controllerFan_defaults); }
static bool state() { return speed > 0; }
static void init() { reset(); }
static void reset() { TERN_(CONTROLLER_FAN_EDITABLE, settings = controllerFan_defaults); }
static void setup();
static void update();
};

View File

@@ -11,7 +11,6 @@
#include "dac_dac084s085.h"
#include "../../MarlinCore.h"
#include "../../module/stepper.h"
#include "../../HAL/shared/Delay.h"
dac084s085::dac084s085() { }

View File

@@ -29,7 +29,6 @@
#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;
@@ -60,7 +59,7 @@ int StepperDAC::init() {
}
void StepperDAC::set_current_value(const uint8_t channel, uint16_t val) {
if (!dac_present) return;
if (!(dac_present && channel < LOGICAL_AXES)) return;
NOMORE(val, uint16_t(DAC_STEPPER_MAX));
@@ -85,13 +84,11 @@ void StepperDAC::print_values() {
if (!dac_present) return;
SERIAL_ECHO_MSG("Stepper current values in % (Amps):");
SERIAL_ECHO_START();
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
LOOP_LOGICAL_AXES(a) {
SERIAL_CHAR(' ', IAXIS_CHAR(a), ':');
SERIAL_ECHO(dac_perc(a));
SERIAL_ECHOPGM_P(PSTR(" ("), dac_amps(AxisEnum(a)), PSTR(")"));
}
#if HAS_EXTRUDERS
SERIAL_ECHOLNPGM_P(SP_E_LBL, dac_perc(E_AXIS), PSTR(" ("), dac_amps(E_AXIS), PSTR(")"));
#endif

View File

@@ -31,9 +31,13 @@
// Settings for the I2C based DIGIPOT (MCP4018) based on WT150
#define DIGIPOT_A4988_Rsx 0.250
#define DIGIPOT_A4988_Vrefmax 1.666
#define DIGIPOT_MCP4018_MAX_VALUE 127
#ifndef DIGIPOT_A4988_Rsx
#define DIGIPOT_A4988_Rsx 0.250
#endif
#ifndef DIGIPOT_A4988_Vrefmax
#define DIGIPOT_A4988_Vrefmax 1.666
#endif
#define DIGIPOT_MCP4018_MAX_VALUE 127
#define DIGIPOT_A4988_Itripmax(Vref) ((Vref) / (8.0 * DIGIPOT_A4988_Rsx))

View File

@@ -52,13 +52,13 @@ namespace DirectStepping {
volatile bool SerialPageManager<Cfg>::fatal_error;
template<typename Cfg>
volatile PageState SerialPageManager<Cfg>::page_states[Cfg::NUM_PAGES];
volatile PageState SerialPageManager<Cfg>::page_states[Cfg::PAGE_COUNT];
template<typename Cfg>
volatile bool SerialPageManager<Cfg>::page_states_dirty;
template<typename Cfg>
uint8_t SerialPageManager<Cfg>::pages[Cfg::NUM_PAGES][Cfg::PAGE_SIZE];
uint8_t SerialPageManager<Cfg>::pages[Cfg::PAGE_COUNT][Cfg::PAGE_SIZE];
template<typename Cfg>
uint8_t SerialPageManager<Cfg>::checksum;
@@ -74,7 +74,7 @@ namespace DirectStepping {
template <typename Cfg>
void SerialPageManager<Cfg>::init() {
for (int i = 0 ; i < Cfg::NUM_PAGES ; i++)
for (int i = 0 ; i < Cfg::PAGE_COUNT ; i++)
page_states[i] = PageState::FREE;
fatal_error = false;
@@ -143,14 +143,16 @@ namespace DirectStepping {
// 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;
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;
@@ -161,11 +163,10 @@ namespace DirectStepping {
return true;
}
case State::UNFAIL:
if (c == 0) {
if (c == 0)
set_page_state(write_page_idx, PageState::FREE);
} else {
else
fatal_error = true;
}
state = State::MONITOR;
return true;
}
@@ -174,7 +175,7 @@ namespace DirectStepping {
template <typename Cfg>
void SerialPageManager<Cfg>::write_responses() {
if (fatal_error) {
kill(GET_TEXT(MSG_BAD_PAGE));
kill(GET_TEXT_F(MSG_BAD_PAGE));
return;
}
@@ -183,10 +184,10 @@ namespace DirectStepping {
SERIAL_CHAR(Cfg::CONTROL_CHAR);
constexpr int state_bits = 2;
constexpr int n_bytes = Cfg::NUM_PAGES >> state_bits;
constexpr int n_bytes = Cfg::PAGE_COUNT >> state_bits;
volatile uint8_t bits_b[n_bytes] = { 0 };
for (page_idx_t i = 0 ; i < Cfg::NUM_PAGES ; i++) {
for (page_idx_t i = 0 ; i < Cfg::PAGE_COUNT ; i++) {
bits_b[i >> state_bits] |= page_states[i] << ((i * state_bits) & 0x7);
}

View File

@@ -68,10 +68,10 @@ namespace DirectStepping {
static State state;
static volatile bool fatal_error;
static volatile PageState page_states[Cfg::NUM_PAGES];
static volatile PageState page_states[Cfg::PAGE_COUNT];
static volatile bool page_states_dirty;
static uint8_t pages[Cfg::NUM_PAGES][Cfg::PAGE_SIZE];
static uint8_t pages[Cfg::PAGE_COUNT][Cfg::PAGE_SIZE];
static uint8_t checksum;
static write_byte_idx_t write_byte_idx;
static page_idx_t write_page_idx;
@@ -87,8 +87,8 @@ namespace DirectStepping {
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 PAGE_COUNT = num_pages;
static constexpr int AXIS_COUNT = num_axes;
static constexpr int BITS_SEGMENT = bits_segment;
static constexpr int DIRECTIONAL = dir ? 1 : 0;
static constexpr int SEGMENTS = segments;
@@ -96,10 +96,10 @@ namespace DirectStepping {
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;
static constexpr int PAGE_SIZE = (AXIS_COUNT * 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;
typedef typename TypeSelector<(PAGE_COUNT>256), uint16_t, uint8_t>::type page_idx_t;
};
template <uint8_t num_pages>

View File

@@ -41,7 +41,9 @@ extern bool wait_for_user, wait_for_heatup;
void quickresume_stepper();
#endif
void HAL_reboot();
#if ENABLED(SOFT_RESET_VIA_SERIAL)
void HAL_reboot();
#endif
class EmergencyParser {
@@ -199,7 +201,7 @@ public:
case EP_M112: killed_by_M112 = true; break;
case EP_M410: quickstop_by_M410 = true; break;
#if ENABLED(HOST_PROMPT_SUPPORT)
case EP_M876SN: host_response_handler(M876_reason); break;
case EP_M876SN: hostui.handle_response(M876_reason); break;
#endif
#if ENABLED(REALTIME_REPORTING_COMMANDS)
case EP_GRBL_STATUS: report_current_position_moving(); break;

View File

@@ -0,0 +1,236 @@
/**
* 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/MarlinConfigPre.h"
#if ENABLED(EASYTHREED_UI)
#include "easythreed_ui.h"
#include "pause.h"
#include "../module/temperature.h"
#include "../module/printcounter.h"
#include "../sd/cardreader.h"
#include "../gcode/queue.h"
#include "../module/motion.h"
#include "../module/planner.h"
#include "../MarlinCore.h"
EasythreedUI easythreed_ui;
#define BTN_DEBOUNCE_MS 20
void EasythreedUI::init() {
SET_INPUT_PULLUP(BTN_HOME); SET_OUTPUT(BTN_HOME_GND);
SET_INPUT_PULLUP(BTN_FEED); SET_OUTPUT(BTN_FEED_GND);
SET_INPUT_PULLUP(BTN_RETRACT); SET_OUTPUT(BTN_RETRACT_GND);
SET_INPUT_PULLUP(BTN_PRINT);
SET_OUTPUT(EASYTHREED_LED_PIN);
}
void EasythreedUI::run() {
blinkLED();
loadButton();
printButton();
}
enum LEDInterval : uint16_t {
LED_OFF = 0,
LED_ON = 4000,
LED_BLINK_0 = 2500,
LED_BLINK_1 = 1500,
LED_BLINK_2 = 1000,
LED_BLINK_3 = 800,
LED_BLINK_4 = 500,
LED_BLINK_5 = 300,
LED_BLINK_6 = 150,
LED_BLINK_7 = 50
};
uint16_t blink_interval_ms = LED_ON; // Status LED on Start button
void EasythreedUI::blinkLED() {
static millis_t prev_blink_interval_ms = 0, blink_start_ms = 0;
if (blink_interval_ms == LED_OFF) { WRITE(EASYTHREED_LED_PIN, HIGH); return; } // OFF
if (blink_interval_ms >= LED_ON) { WRITE(EASYTHREED_LED_PIN, LOW); return; } // ON
const millis_t ms = millis();
if (prev_blink_interval_ms != blink_interval_ms) {
prev_blink_interval_ms = blink_interval_ms;
blink_start_ms = ms;
}
if (PENDING(ms, blink_start_ms + blink_interval_ms))
WRITE(EASYTHREED_LED_PIN, LOW);
else if (PENDING(ms, blink_start_ms + 2 * blink_interval_ms))
WRITE(EASYTHREED_LED_PIN, HIGH);
else
blink_start_ms = ms;
}
//
// Filament Load/Unload Button
// Load/Unload buttons are a 3 position switch with a common center ground.
//
void EasythreedUI::loadButton() {
if (printingIsActive()) return;
enum FilamentStatus : uint8_t { FS_IDLE, FS_PRESS, FS_CHECK, FS_PROCEED };
static uint8_t filament_status = FS_IDLE;
static millis_t filament_time = 0;
switch (filament_status) {
case FS_IDLE:
if (!READ(BTN_RETRACT) || !READ(BTN_FEED)) { // If feed/retract switch is toggled...
filament_status++; // ...proceed to next test.
filament_time = millis();
}
break;
case FS_PRESS:
if (ELAPSED(millis(), filament_time + BTN_DEBOUNCE_MS)) { // After a short debounce delay...
if (!READ(BTN_RETRACT) || !READ(BTN_FEED)) { // ...if switch still toggled...
thermalManager.setTargetHotend(EXTRUDE_MINTEMP + 10, 0); // Start heating up
blink_interval_ms = LED_BLINK_7; // Set the LED to blink fast
filament_status++;
}
else
filament_status = FS_IDLE; // Switch not toggled long enough
}
break;
case FS_CHECK:
if (READ(BTN_RETRACT) && READ(BTN_FEED)) { // Switch in center position (stop)
blink_interval_ms = LED_ON; // LED on steady
filament_status = FS_IDLE;
thermalManager.disable_all_heaters();
}
else if (thermalManager.hotEnoughToExtrude(0)) { // Is the hotend hot enough to move material?
filament_status++; // Proceed to feed / retract.
blink_interval_ms = LED_BLINK_5; // Blink ~3 times per second
}
break;
case FS_PROCEED: {
// Feed or Retract just once. Hard abort all moves and return to idle on swicth release.
static bool flag = false;
if (READ(BTN_RETRACT) && READ(BTN_FEED)) { // Switch in center position (stop)
flag = false; // Restore flag to false
filament_status = FS_IDLE; // Go back to idle state
quickstop_stepper(); // Hard-stop all the steppers ... now!
thermalManager.disable_all_heaters(); // And disable all the heaters
blink_interval_ms = LED_ON;
}
else if (!flag) {
flag = true;
queue.inject(!READ(BTN_RETRACT) ? F("G91\nG0 E10 F180\nG0 E-120 F180\nM104 S0") : F("G91\nG0 E100 F120\nM104 S0"));
}
} break;
}
}
#if HAS_STEPPER_RESET
void disableStepperDrivers();
#endif
//
// Print Start/Pause/Resume Button
//
void EasythreedUI::printButton() {
enum KeyStatus : uint8_t { KS_IDLE, KS_PRESS, KS_PROCEED };
static uint8_t key_status = KS_IDLE;
static millis_t key_time = 0;
enum PrintFlag : uint8_t { PF_START, PF_PAUSE, PF_RESUME };
static PrintFlag print_key_flag = PF_START;
const millis_t ms = millis();
switch (key_status) {
case KS_IDLE:
if (!READ(BTN_PRINT)) { // Print/Pause/Resume button pressed?
key_time = ms; // Save start time
key_status++; // Go to debounce test
}
break;
case KS_PRESS:
if (ELAPSED(ms, key_time + BTN_DEBOUNCE_MS)) // Wait for debounce interval to expire
key_status = READ(BTN_PRINT) ? KS_IDLE : KS_PROCEED; // Proceed if still pressed
break;
case KS_PROCEED:
if (!READ(BTN_PRINT)) break; // Wait for the button to be released
key_status = KS_IDLE; // Ready for the next press
if (PENDING(ms, key_time + 1200 - BTN_DEBOUNCE_MS)) { // Register a press < 1.2 seconds
switch (print_key_flag) {
case PF_START: { // The "Print" button starts an SD card print
if (printingIsActive()) break; // Already printing? (find another line that checks for 'is planner doing anything else right now?')
blink_interval_ms = LED_BLINK_2; // Blink the indicator LED at 1 second intervals
print_key_flag = PF_PAUSE; // The "Print" button now pauses the print
card.mount(); // Force SD card to mount - now!
if (!card.isMounted) { // Failed to mount?
blink_interval_ms = LED_OFF; // Turn off LED
print_key_flag = PF_START;
return; // Bail out
}
card.ls(); // List all files to serial output
const uint16_t filecnt = card.countFilesInWorkDir(); // Count printable files in cwd
if (filecnt == 0) return; // None are printable?
card.selectFileByIndex(filecnt); // Select the last file according to current sort options
card.openAndPrintFile(card.filename); // Start printing it
break;
}
case PF_PAUSE: { // Pause printing (not currently firing)
if (!printingIsActive()) break;
blink_interval_ms = LED_ON; // Set indicator to steady ON
queue.inject(F("M25")); // Queue Pause
print_key_flag = PF_RESUME; // The "Print" button now resumes the print
break;
}
case PF_RESUME: { // Resume printing
if (printingIsActive()) break;
blink_interval_ms = LED_BLINK_2; // Blink the indicator LED at 1 second intervals
queue.inject(F("M24")); // Queue resume
print_key_flag = PF_PAUSE; // The "Print" button now pauses the print
break;
}
}
}
else { // Register a longer press
if (print_key_flag == PF_START && !printingIsActive()) { // While not printing, this moves Z up 10mm
blink_interval_ms = LED_ON;
queue.inject(F("G91\nG0 Z10 F600\nG90")); // Raise Z soon after returning to main loop
}
else { // While printing, cancel print
card.abortFilePrintSoon(); // There is a delay while the current steps play out
blink_interval_ms = LED_OFF; // Turn off LED
}
planner.synchronize(); // Wait for commands already in the planner to finish
TERN_(HAS_STEPPER_RESET, disableStepperDrivers()); // Disable all steppers - now!
print_key_flag = PF_START; // The "Print" button now starts a new print
}
break;
}
}
#endif // EASYTHREED_UI

View File

@@ -0,0 +1,35 @@
/**
* 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
class EasythreedUI {
public:
static void init();
static void run();
private:
static void blinkLED();
static void loadButton();
static void printButton();
};
extern EasythreedUI easythreed_ui;

View File

@@ -49,7 +49,7 @@ void I2CPositionEncoder::init(const uint8_t address, const AxisEnum axis) {
initialized = true;
SERIAL_ECHOLNPGM("Setting up encoder on ", AS_CHAR(axis_codes[encoderAxis]), " axis, addr = ", address);
SERIAL_ECHOLNPGM("Setting up encoder on ", AS_CHAR(AXIS_CHAR(encoderAxis)), " axis, addr = ", address);
position = get_position();
}
@@ -67,7 +67,7 @@ void I2CPositionEncoder::update() {
/*
if (trusted) { //commented out as part of the note below
trusted = false;
SERIAL_ECHOLNPGM("Fault detected on ", AS_CHAR(axis_codes[encoderAxis]), " axis encoder. Disengaging error correction until module is trusted again.");
SERIAL_ECHOLNPGM("Fault detected on ", AS_CHAR(AXIS_CHAR(encoderAxis)), " axis encoder. Disengaging error correction until module is trusted again.");
}
*/
return;
@@ -92,7 +92,7 @@ void I2CPositionEncoder::update() {
if (millis() - lastErrorTime > I2CPE_TIME_TRUSTED) {
trusted = true;
SERIAL_ECHOLNPGM("Untrusted encoder module on ", AS_CHAR(axis_codes[encoderAxis]), " axis has been fault-free for set duration, reinstating error correction.");
SERIAL_ECHOLNPGM("Untrusted encoder module on ", AS_CHAR(AXIS_CHAR(encoderAxis)), " axis has been fault-free for set duration, reinstating error correction.");
//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
@@ -153,7 +153,7 @@ void I2CPositionEncoder::update() {
#ifdef I2CPE_ERR_THRESH_ABORT
if (ABS(error) > I2CPE_ERR_THRESH_ABORT * planner.settings.axis_steps_per_mm[encoderAxis]) {
//kill(PSTR("Significant Error"));
//kill(F("Significant Error"));
SERIAL_ECHOLNPGM("Axis error over threshold, aborting!", error);
safe_delay(5000);
}
@@ -172,7 +172,7 @@ 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_CHAR(axis_codes[encoderAxis]);
SERIAL_CHAR(AXIS_CHAR(encoderAxis));
SERIAL_ECHOLNPGM(" : CORRECT ERR ", errorP * planner.mm_per_step[encoderAxis], "mm");
babystep.add_steps(encoderAxis, -LROUND(errorP));
errPrstIdx = 0;
@@ -192,7 +192,7 @@ 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_CHAR(axis_codes[encoderAxis]);
SERIAL_CHAR(AXIS_CHAR(encoderAxis));
SERIAL_ECHOLNPGM(" : LARGE ERR ", error, "; diffSum=", diffSum);
errorCount++;
nextErrorCountTime = ms + I2CPE_ERR_CNT_DEBOUNCE_MS;
@@ -212,7 +212,7 @@ void I2CPositionEncoder::set_homed() {
homed = trusted = true;
#ifdef I2CPE_DEBUG
SERIAL_CHAR(axis_codes[encoderAxis]);
SERIAL_CHAR(AXIS_CHAR(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_CHAR(axis_codes[encoderAxis]);
SERIAL_CHAR(AXIS_CHAR(encoderAxis));
SERIAL_ECHOLNPGM(" axis encoder unhomed.");
#endif
}
@@ -231,8 +231,8 @@ void I2CPositionEncoder::set_unhomed() {
bool I2CPositionEncoder::passes_test(const bool report) {
if (report) {
if (H != I2CPE_MAG_SIG_GOOD) SERIAL_ECHOPGM("Warning. ");
SERIAL_CHAR(axis_codes[encoderAxis]);
serial_ternary(H == I2CPE_MAG_SIG_BAD, PSTR(" axis "), PSTR("magnetic strip "), PSTR("encoder "));
SERIAL_CHAR(AXIS_CHAR(encoderAxis));
serial_ternary(H == I2CPE_MAG_SIG_BAD, F(" axis "), F("magnetic strip "), F("encoder "));
switch (H) {
case I2CPE_MAG_SIG_GOOD:
case I2CPE_MAG_SIG_MID:
@@ -252,7 +252,7 @@ float I2CPositionEncoder::get_axis_error_mm(const bool report) {
error = ABS(diff) > 10000 ? 0 : diff; // Huge error is a bad reading
if (report) {
SERIAL_CHAR(axis_codes[encoderAxis]);
SERIAL_CHAR(AXIS_CHAR(encoderAxis));
SERIAL_ECHOLNPGM(" axis target=", target, "mm; actual=", actual, "mm; err=", error, "mm");
}
@@ -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_CHAR(axis_codes[encoderAxis]);
SERIAL_CHAR(AXIS_CHAR(encoderAxis));
SERIAL_ECHOLNPGM(" axis encoder not active!");
}
return 0;
@@ -287,7 +287,7 @@ int32_t I2CPositionEncoder::get_axis_error_steps(const bool report) {
errorPrev = error;
if (report) {
SERIAL_CHAR(axis_codes[encoderAxis]);
SERIAL_CHAR(AXIS_CHAR(encoderAxis));
SERIAL_ECHOLNPGM(" axis target=", target, "; actual=", encoderCountInStepperTicksScaled, "; err=", error);
}
@@ -337,7 +337,7 @@ bool I2CPositionEncoder::test_axis() {
ec = false;
xyze_pos_t startCoord, endCoord;
LOOP_LINEAR_AXES(a) {
LOOP_NUM_AXES(a) {
startCoord[a] = planner.get_axis_position_mm((AxisEnum)a);
endCoord[a] = planner.get_axis_position_mm((AxisEnum)a);
}
@@ -395,7 +395,7 @@ void I2CPositionEncoder::calibrate_steps_mm(const uint8_t iter) {
travelDistance = endDistance - startDistance;
xyze_pos_t startCoord, endCoord;
LOOP_LINEAR_AXES(a) {
LOOP_NUM_AXES(a) {
startCoord[a] = planner.get_axis_position_mm((AxisEnum)a);
endCoord[a] = planner.get_axis_position_mm((AxisEnum)a);
}
@@ -489,7 +489,7 @@ void I2CPositionEncodersMgr::init() {
encoders[i].set_stepper_ticks(I2CPE_ENC_1_TICKS_REV);
#endif
#ifdef I2CPE_ENC_1_INVERT
encoders[i].set_inverted(I2CPE_ENC_1_INVERT);
encoders[i].set_inverted(ENABLED(I2CPE_ENC_1_INVERT));
#endif
#ifdef I2CPE_ENC_1_EC_METHOD
encoders[i].set_ec_method(I2CPE_ENC_1_EC_METHOD);
@@ -518,7 +518,7 @@ void I2CPositionEncodersMgr::init() {
encoders[i].set_stepper_ticks(I2CPE_ENC_2_TICKS_REV);
#endif
#ifdef I2CPE_ENC_2_INVERT
encoders[i].set_inverted(I2CPE_ENC_2_INVERT);
encoders[i].set_inverted(ENABLED(I2CPE_ENC_2_INVERT));
#endif
#ifdef I2CPE_ENC_2_EC_METHOD
encoders[i].set_ec_method(I2CPE_ENC_2_EC_METHOD);
@@ -547,7 +547,7 @@ void I2CPositionEncodersMgr::init() {
encoders[i].set_stepper_ticks(I2CPE_ENC_3_TICKS_REV);
#endif
#ifdef I2CPE_ENC_3_INVERT
encoders[i].set_inverted(I2CPE_ENC_3_INVERT);
encoders[i].set_inverted(ENABLED(I2CPE_ENC_3_INVERT));
#endif
#ifdef I2CPE_ENC_3_EC_METHOD
encoders[i].set_ec_method(I2CPE_ENC_3_EC_METHOD);
@@ -576,7 +576,7 @@ void I2CPositionEncodersMgr::init() {
encoders[i].set_stepper_ticks(I2CPE_ENC_4_TICKS_REV);
#endif
#ifdef I2CPE_ENC_4_INVERT
encoders[i].set_inverted(I2CPE_ENC_4_INVERT);
encoders[i].set_inverted(ENABLED(I2CPE_ENC_4_INVERT));
#endif
#ifdef I2CPE_ENC_4_EC_METHOD
encoders[i].set_ec_method(I2CPE_ENC_4_EC_METHOD);
@@ -605,7 +605,7 @@ void I2CPositionEncodersMgr::init() {
encoders[i].set_stepper_ticks(I2CPE_ENC_5_TICKS_REV);
#endif
#ifdef I2CPE_ENC_5_INVERT
encoders[i].set_inverted(I2CPE_ENC_5_INVERT);
encoders[i].set_inverted(ENABLED(I2CPE_ENC_5_INVERT));
#endif
#ifdef I2CPE_ENC_5_EC_METHOD
encoders[i].set_ec_method(I2CPE_ENC_5_EC_METHOD);
@@ -634,7 +634,7 @@ void I2CPositionEncodersMgr::init() {
encoders[i].set_stepper_ticks(I2CPE_ENC_6_TICKS_REV);
#endif
#ifdef I2CPE_ENC_6_INVERT
encoders[i].set_inverted(I2CPE_ENC_6_INVERT);
encoders[i].set_inverted(ENABLED(I2CPE_ENC_6_INVERT));
#endif
#ifdef I2CPE_ENC_6_EC_METHOD
encoders[i].set_ec_method(I2CPE_ENC_6_EC_METHOD);
@@ -657,7 +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_CHAR(axis_codes[encoders[idx].get_axis()], ' ');
SERIAL_CHAR(AXIS_CHAR(encoders[idx).get_axis()], ' ');
for (uint8_t j = 31; j > 0; j--)
SERIAL_ECHO((bool)(0x00000001 & (raw_count >> j)));
@@ -712,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_CHAR(axis_codes[encoders[idx].get_axis()]);
SERIAL_CHAR(AXIS_CHAR(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));
}
@@ -756,7 +756,7 @@ int8_t I2CPositionEncodersMgr::parse() {
if (!parser.has_value()) {
SERIAL_ECHOLNPGM("?A seen, but no address specified! [30-200]");
return I2CPE_PARSE_ERR;
};
}
I2CPE_addr = parser.value_byte();
if (!WITHIN(I2CPE_addr, 30, 200)) { // reserve the first 30 and last 55
@@ -775,7 +775,7 @@ int8_t I2CPositionEncodersMgr::parse() {
if (!parser.has_value()) {
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) {
@@ -791,7 +791,7 @@ int8_t I2CPositionEncodersMgr::parse() {
I2CPE_anyaxis = parser.seen_axis();
return I2CPE_PARSE_OK;
};
}
/**
* M860: Report the position(s) of position encoder module(s).
@@ -814,7 +814,7 @@ void I2CPositionEncodersMgr::M860() {
if (I2CPE_idx == 0xFF) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen_test(axis_codes[i])) {
if (!I2CPE_anyaxis || parser.seen_test(AXIS_CHAR(i))) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) report_position(idx, hasU, hasO);
}
@@ -841,7 +841,7 @@ void I2CPositionEncodersMgr::M861() {
if (I2CPE_idx == 0xFF) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) report_status(idx);
}
@@ -869,7 +869,7 @@ void I2CPositionEncodersMgr::M862() {
if (I2CPE_idx == 0xFF) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) test_axis(idx);
}
@@ -900,7 +900,7 @@ void I2CPositionEncodersMgr::M863() {
if (I2CPE_idx == 0xFF) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) calibrate_steps_mm(idx, iterations);
}
@@ -934,7 +934,7 @@ void I2CPositionEncodersMgr::M864() {
if (!parser.has_value()) {
SERIAL_ECHOLNPGM("?S seen, but no address specified! [30-200]");
return;
};
}
newAddress = parser.value_byte();
if (!WITHIN(newAddress, 30, 200)) {
@@ -976,7 +976,7 @@ void I2CPositionEncodersMgr::M865() {
if (!I2CPE_addr) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) report_module_firmware(encoders[idx].get_address());
}
@@ -1007,7 +1007,7 @@ void I2CPositionEncodersMgr::M866() {
if (I2CPE_idx == 0xFF) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) {
if (hasR)
@@ -1045,7 +1045,7 @@ void I2CPositionEncodersMgr::M867() {
if (I2CPE_idx == 0xFF) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) {
const bool ena = onoff == -1 ? !encoders[I2CPE_idx].get_ec_enabled() : !!onoff;
@@ -1081,7 +1081,7 @@ void I2CPositionEncodersMgr::M868() {
if (I2CPE_idx == 0xFF) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) {
if (newThreshold != -9999)
@@ -1115,7 +1115,7 @@ void I2CPositionEncodersMgr::M869() {
if (I2CPE_idx == 0xFF) {
LOOP_LOGICAL_AXES(i) {
if (!I2CPE_anyaxis || parser.seen(axis_codes[i])) {
if (!I2CPE_anyaxis || parser.seen(AXIS_CHAR(i))) {
const uint8_t idx = idx_from_axis(AxisEnum(i));
if ((int8_t)idx >= 0) report_error(idx);
}

View File

@@ -261,32 +261,32 @@ class I2CPositionEncodersMgr {
static void report_error_count(const int8_t idx, const AxisEnum axis) {
CHECK_IDX();
SERIAL_ECHOLNPGM("Error count on ", AS_CHAR(axis_codes[axis]), " axis is ", encoders[idx].get_error_count());
SERIAL_ECHOLNPGM("Error count on ", AS_CHAR(AXIS_CHAR(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_ECHOLNPGM("Error count on ", AS_CHAR(axis_codes[axis]), " axis has been reset.");
SERIAL_ECHOLNPGM("Error count on ", AS_CHAR(AXIS_CHAR(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_ECHOPGM("Error correction on ", AS_CHAR(axis_codes[axis]));
SERIAL_ECHOPGM("Error correction on ", AS_CHAR(AXIS_CHAR(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_ECHOLNPGM("Error correct threshold for ", AS_CHAR(axis_codes[axis]), " axis set to ", newThreshold, "mm.");
SERIAL_ECHOLNPGM("Error correct threshold for ", AS_CHAR(AXIS_CHAR(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_ECHOLNPGM("Error correct threshold for ", AS_CHAR(axis_codes[axis]), " axis is ", threshold, "mm.");
SERIAL_ECHOLNPGM("Error correct threshold for ", AS_CHAR(AXIS_CHAR(axis)), " axis is ", threshold, "mm.");
}
static int8_t idx_from_axis(const AxisEnum axis) {

View File

@@ -0,0 +1,207 @@
/**
* 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/>.
*
*/
/**
* fancheck.cpp - fan tachometer check
*/
#include "../inc/MarlinConfig.h"
#if HAS_FANCHECK
#include "fancheck.h"
#include "../module/temperature.h"
#if HAS_AUTO_FAN && EXTRUDER_AUTO_FAN_SPEED != 255 && DISABLED(FOURWIRES_FANS)
bool FanCheck::measuring = false;
#endif
Flags<TACHO_COUNT> FanCheck::tacho_state;
uint16_t FanCheck::edge_counter[TACHO_COUNT];
uint8_t FanCheck::rps[TACHO_COUNT];
FanCheck::TachoError FanCheck::error = FanCheck::TachoError::NONE;
bool FanCheck::enabled;
void FanCheck::init() {
#define _TACHINIT(N) TERN(E##N##_FAN_TACHO_PULLUP, SET_INPUT_PULLUP, TERN(E##N##_FAN_TACHO_PULLDOWN, SET_INPUT_PULLDOWN, SET_INPUT))(E##N##_FAN_TACHO_PIN)
#if HAS_E0_FAN_TACHO
_TACHINIT(0);
#endif
#if HAS_E1_FAN_TACHO
_TACHINIT(1);
#endif
#if HAS_E2_FAN_TACHO
_TACHINIT(2);
#endif
#if HAS_E3_FAN_TACHO
_TACHINIT(3);
#endif
#if HAS_E4_FAN_TACHO
_TACHINIT(4);
#endif
#if HAS_E5_FAN_TACHO
_TACHINIT(5);
#endif
#if HAS_E6_FAN_TACHO
_TACHINIT(6);
#endif
#if HAS_E7_FAN_TACHO
_TACHINIT(7);
#endif
}
void FanCheck::update_tachometers() {
bool status;
#define _TACHO_CASE(N) case N: status = READ(E##N##_FAN_TACHO_PIN); break;
LOOP_L_N(f, TACHO_COUNT) {
switch (f) {
#if HAS_E0_FAN_TACHO
_TACHO_CASE(0)
#endif
#if HAS_E1_FAN_TACHO
_TACHO_CASE(1)
#endif
#if HAS_E2_FAN_TACHO
_TACHO_CASE(2)
#endif
#if HAS_E3_FAN_TACHO
_TACHO_CASE(3)
#endif
#if HAS_E4_FAN_TACHO
_TACHO_CASE(4)
#endif
#if HAS_E5_FAN_TACHO
_TACHO_CASE(5)
#endif
#if HAS_E6_FAN_TACHO
_TACHO_CASE(6)
#endif
#if HAS_E7_FAN_TACHO
_TACHO_CASE(7)
#endif
default: continue;
}
if (status != tacho_state[f]) {
if (measuring) ++edge_counter[f];
tacho_state.set(f, status);
}
}
}
void FanCheck::compute_speed(uint16_t elapsedTime) {
static uint8_t errors_count[TACHO_COUNT];
static uint8_t fan_reported_errors_msk = 0;
uint8_t fan_error_msk = 0;
LOOP_L_N(f, TACHO_COUNT) {
switch (f) {
TERN_(HAS_E0_FAN_TACHO, case 0:)
TERN_(HAS_E1_FAN_TACHO, case 1:)
TERN_(HAS_E2_FAN_TACHO, case 2:)
TERN_(HAS_E3_FAN_TACHO, case 3:)
TERN_(HAS_E4_FAN_TACHO, case 4:)
TERN_(HAS_E5_FAN_TACHO, case 5:)
TERN_(HAS_E6_FAN_TACHO, case 6:)
TERN_(HAS_E7_FAN_TACHO, case 7:)
// Compute fan speed
rps[f] = edge_counter[f] * float(250) / elapsedTime;
edge_counter[f] = 0;
// Check fan speed
constexpr int8_t max_extruder_fan_errors = TERN(HAS_PWMFANCHECK, 10000, 5000) / Temperature::fan_update_interval_ms;
if (rps[f] >= 20 || TERN0(HAS_AUTO_FAN, thermalManager.autofan_speed[f] == 0))
errors_count[f] = 0;
else if (errors_count[f] < max_extruder_fan_errors)
++errors_count[f];
else if (enabled)
SBI(fan_error_msk, f);
break;
}
}
// Drop the error when all fans are ok
if (!fan_error_msk && error == TachoError::REPORTED) error = TachoError::FIXED;
if (error == TachoError::FIXED && !printJobOngoing() && !printingIsPaused()) {
error = TachoError::NONE; // if the issue has been fixed while the printer is idle, reenable immediately
ui.reset_alert_level();
}
if (fan_error_msk & ~fan_reported_errors_msk) {
// Handle new faults only
LOOP_L_N(f, TACHO_COUNT) if (TEST(fan_error_msk, f)) report_speed_error(f);
}
fan_reported_errors_msk = fan_error_msk;
}
void FanCheck::report_speed_error(uint8_t fan) {
if (printJobOngoing()) {
if (error == TachoError::NONE) {
if (thermalManager.degTargetHotend(fan) != 0) {
kill(GET_TEXT_F(MSG_FAN_SPEED_FAULT));
error = TachoError::REPORTED;
}
else
error = TachoError::DETECTED; // Plans error for next processed command
}
}
else if (!printingIsPaused()) {
thermalManager.setTargetHotend(0, fan); // Always disable heating
if (error == TachoError::NONE) error = TachoError::REPORTED;
}
SERIAL_ERROR_MSG(STR_ERR_FANSPEED, fan);
LCD_ALERTMESSAGE(MSG_FAN_SPEED_FAULT);
}
void FanCheck::print_fan_states() {
LOOP_L_N(s, 2) {
LOOP_L_N(f, TACHO_COUNT) {
switch (f) {
TERN_(HAS_E0_FAN_TACHO, case 0:)
TERN_(HAS_E1_FAN_TACHO, case 1:)
TERN_(HAS_E2_FAN_TACHO, case 2:)
TERN_(HAS_E3_FAN_TACHO, case 3:)
TERN_(HAS_E4_FAN_TACHO, case 4:)
TERN_(HAS_E5_FAN_TACHO, case 5:)
TERN_(HAS_E6_FAN_TACHO, case 6:)
TERN_(HAS_E7_FAN_TACHO, case 7:)
SERIAL_ECHOPGM("E", f);
if (s == 0)
SERIAL_ECHOPGM(":", 60 * rps[f], " RPM ");
else
SERIAL_ECHOPGM("@:", TERN(HAS_AUTO_FAN, thermalManager.autofan_speed[f], 255), " ");
break;
}
}
}
SERIAL_EOL();
}
#if ENABLED(AUTO_REPORT_FANS)
AutoReporter<FanCheck::AutoReportFan> FanCheck::auto_reporter;
void FanCheck::AutoReportFan::report() { print_fan_states(); }
#endif
#endif // HAS_FANCHECK

View File

@@ -0,0 +1,89 @@
/**
* 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/MarlinConfig.h"
#if HAS_FANCHECK
#include "../MarlinCore.h"
#include "../lcd/marlinui.h"
#if ENABLED(AUTO_REPORT_FANS)
#include "../libs/autoreport.h"
#endif
#if ENABLED(PARK_HEAD_ON_PAUSE)
#include "../gcode/queue.h"
#endif
/**
* fancheck.h
*/
#define TACHO_COUNT TERN(HAS_E7_FAN_TACHO, 8, TERN(HAS_E6_FAN_TACHO, 7, TERN(HAS_E5_FAN_TACHO, 6, TERN(HAS_E4_FAN_TACHO, 5, TERN(HAS_E3_FAN_TACHO, 4, TERN(HAS_E2_FAN_TACHO, 3, TERN(HAS_E1_FAN_TACHO, 2, 1)))))))
class FanCheck {
private:
enum class TachoError : uint8_t { NONE, DETECTED, REPORTED, FIXED };
#if HAS_PWMFANCHECK
static bool measuring; // For future use (3 wires PWM controlled fans)
#else
static constexpr bool measuring = true;
#endif
static Flags<TACHO_COUNT> tacho_state;
static uint16_t edge_counter[TACHO_COUNT];
static uint8_t rps[TACHO_COUNT];
static TachoError error;
static void report_speed_error(uint8_t fan);
public:
static bool enabled;
static void init();
static void update_tachometers();
static void compute_speed(uint16_t elapsedTime);
static void print_fan_states();
#if HAS_PWMFANCHECK
static void toggle_measuring() { measuring = !measuring; }
static bool is_measuring() { return measuring; }
#endif
static void check_deferred_error() {
if (error == TachoError::DETECTED) {
error = TachoError::REPORTED;
TERN(PARK_HEAD_ON_PAUSE, queue.inject(F("M125")), kill(GET_TEXT_F(MSG_FAN_SPEED_FAULT)));
}
}
#if ENABLED(AUTO_REPORT_FANS)
struct AutoReportFan { static void report(); };
static AutoReporter<AutoReportFan> auto_reporter;
#endif
};
extern FanCheck fan_check;
#endif // HAS_FANCHECK

View File

@@ -41,9 +41,9 @@ public:
FilamentWidthSensor() { init(); }
static void init();
static inline void enable(const bool ena) { enabled = ena; }
static void enable(const bool ena) { enabled = ena; }
static inline void set_delay_cm(const uint8_t cm) {
static void set_delay_cm(const uint8_t cm) {
meas_delay_cm = _MIN(cm, MAX_MEASUREMENT_DELAY);
}
@@ -67,18 +67,18 @@ public:
}
// Convert raw measurement to mm
static inline float raw_to_mm(const uint16_t v) { return v * 5.0f * RECIPROCAL(float(MAX_RAW_THERMISTOR_VALUE)); }
static inline float raw_to_mm() { return raw_to_mm(raw); }
static float raw_to_mm(const uint16_t v) { return v * float(ADC_VREF) * RECIPROCAL(float(MAX_RAW_THERMISTOR_VALUE)); }
static float raw_to_mm() { return raw_to_mm(raw); }
// A scaled reading is ready
// Divide to get to 0-16384 range since we used 1/128 IIR filter approach
static inline void reading_ready() { raw = accum >> 10; }
static void reading_ready() { raw = accum >> 10; }
// Update mm from the raw measurement
static inline void update_measured_mm() { measured_mm = raw_to_mm(); }
static void update_measured_mm() { measured_mm = raw_to_mm(); }
// Update ring buffer used to delay filament measurements
static inline void advance_e(const_float_t e_move) {
static void advance_e(const_float_t e_move) {
// Increment counters with the E distance
e_count += e_move;
@@ -106,7 +106,7 @@ public:
}
// Dynamically set the volumetric multiplier based on the delayed width measurement.
static inline void update_volumetric() {
static void update_volumetric() {
if (enabled) {
int8_t read_index = index_r - meas_delay_cm;
if (read_index < 0) read_index += MMD_CM; // Loop around buffer if needed

View File

@@ -34,7 +34,6 @@ FWRetract fwretract; // Single instance - this calls the constructor
#include "../module/motion.h"
#include "../module/planner.h"
#include "../module/stepper.h"
#include "../gcode/gcode.h"
@@ -45,7 +44,7 @@ FWRetract fwretract; // Single instance - this calls the constructor
// private:
#if HAS_MULTI_EXTRUDER
bool FWRetract::retracted_swap[EXTRUDERS]; // Which extruders are swap-retracted
Flags<EXTRUDERS> FWRetract::retracted_swap; // Which extruders are swap-retracted
#endif
// public:
@@ -56,7 +55,7 @@ fwretract_settings_t FWRetract::settings; // M207 S F Z W, M208 S F
bool FWRetract::autoretract_enabled; // M209 S - Autoretract switch
#endif
bool FWRetract::retracted[EXTRUDERS]; // Which extruders are currently retracted
Flags<EXTRUDERS> FWRetract::retracted; // Which extruders are currently retracted
float FWRetract::current_retract[EXTRUDERS], // Retract value used by planner
FWRetract::current_hop;
@@ -73,10 +72,10 @@ void FWRetract::reset() {
settings.swap_retract_recover_feedrate_mm_s = RETRACT_RECOVER_FEEDRATE_SWAP;
current_hop = 0.0;
LOOP_L_N(i, EXTRUDERS) {
retracted[i] = false;
E_TERN_(retracted_swap[i] = false);
current_retract[i] = 0.0;
retracted.reset();
EXTRUDER_LOOP() {
E_TERN_(retracted_swap.clear(e));
current_retract[e] = 0.0;
}
}
@@ -111,10 +110,10 @@ void FWRetract::retract(const bool retracting E_OPTARG(bool swapping/*=false*/))
" swapping ", swapping,
" active extruder ", active_extruder
);
LOOP_L_N(i, EXTRUDERS) {
SERIAL_ECHOLNPGM("retracted[", i, "] ", AS_DIGIT(retracted[i]));
EXTRUDER_LOOP() {
SERIAL_ECHOLNPGM("retracted[", e, "] ", AS_DIGIT(retracted[e]));
#if HAS_MULTI_EXTRUDER
SERIAL_ECHOLNPGM("retracted_swap[", i, "] ", AS_DIGIT(retracted_swap[i]));
SERIAL_ECHOLNPGM("retracted_swap[", e, "] ", AS_DIGIT(retracted_swap[e]));
#endif
}
SERIAL_ECHOLNPGM("current_position.z ", current_position.z);
@@ -173,21 +172,21 @@ void FWRetract::retract(const bool retracting E_OPTARG(bool swapping/*=false*/))
TERN_(RETRACT_SYNC_MIXING, mixer.T(old_mixing_tool)); // Restore original mixing tool
retracted[active_extruder] = retracting; // Active extruder now retracted / recovered
retracted.set(active_extruder, retracting); // Active extruder now retracted / recovered
// If swap retract/recover update the retracted_swap flag too
#if HAS_MULTI_EXTRUDER
if (swapping) retracted_swap[active_extruder] = retracting;
if (swapping) retracted_swap.set(active_extruder, retracting);
#endif
/* // debugging
SERIAL_ECHOLNPGM("retracting ", AS_DIGIT(retracting));
SERIAL_ECHOLNPGM("swapping ", AS_DIGIT(swapping));
SERIAL_ECHOLNPGM("active_extruder ", active_extruder);
LOOP_L_N(i, EXTRUDERS) {
SERIAL_ECHOLNPGM("retracted[", i, "] ", AS_DIGIT(retracted[i]));
EXTRUDER_LOOP() {
SERIAL_ECHOLNPGM("retracted[", e, "] ", AS_DIGIT(retracted[e]));
#if HAS_MULTI_EXTRUDER
SERIAL_ECHOLNPGM("retracted_swap[", i, "] ", AS_DIGIT(retracted_swap[i]));
SERIAL_ECHOLNPGM("retracted_swap[", e, "] ", AS_DIGIT(retracted_swap[e]));
#endif
}
SERIAL_ECHOLNPGM("current_position.z ", current_position.z);

View File

@@ -43,7 +43,7 @@ typedef struct {
class FWRetract {
private:
#if HAS_MULTI_EXTRUDER
static bool retracted_swap[EXTRUDERS]; // Which extruders are swap-retracted
static Flags<EXTRUDERS> retracted_swap; // Which extruders are swap-retracted
#endif
public:
@@ -55,7 +55,7 @@ public:
static constexpr bool autoretract_enabled = false;
#endif
static bool retracted[EXTRUDERS]; // Which extruders are currently retracted
static Flags<EXTRUDERS> retracted; // Which extruders are currently retracted
static float current_retract[EXTRUDERS], // Retract value used by planner
current_hop; // Hop value used by planner
@@ -63,9 +63,7 @@ public:
static void reset();
static void refresh_autoretract() {
LOOP_L_N(i, EXTRUDERS) retracted[i] = false;
}
static void refresh_autoretract() { retracted.reset(); }
static void enable_autoretract(const bool enable) {
#if ENABLED(FWRETRACT_AUTORETRACT)

View File

@@ -24,10 +24,10 @@
#if ENABLED(HOST_ACTION_COMMANDS)
#include "host_actions.h"
//#define DEBUG_HOST_ACTIONS
#include "host_actions.h"
#if ENABLED(ADVANCED_PAUSE_FEATURE)
#include "pause.h"
#include "../gcode/queue.h"
@@ -37,37 +37,54 @@
#include "runout.h"
#endif
void host_action(PGM_P const pstr, const bool eol) {
HostUI hostui;
void HostUI::action(FSTR_P const fstr, const bool eol) {
PORT_REDIRECT(SerialMask::All);
SERIAL_ECHOPGM("//action:");
SERIAL_ECHOPGM_P(pstr);
SERIAL_ECHOF(fstr);
if (eol) SERIAL_EOL();
}
#ifdef ACTION_ON_KILL
void host_action_kill() { host_action(PSTR(ACTION_ON_KILL)); }
void HostUI::kill() { action(F(ACTION_ON_KILL)); }
#endif
#ifdef ACTION_ON_PAUSE
void host_action_pause(const bool eol/*=true*/) { host_action(PSTR(ACTION_ON_PAUSE), eol); }
void HostUI::pause(const bool eol/*=true*/) { action(F(ACTION_ON_PAUSE), eol); }
#endif
#ifdef ACTION_ON_PAUSED
void host_action_paused(const bool eol/*=true*/) { host_action(PSTR(ACTION_ON_PAUSED), eol); }
void HostUI::paused(const bool eol/*=true*/) { action(F(ACTION_ON_PAUSED), eol); }
#endif
#ifdef ACTION_ON_RESUME
void host_action_resume() { host_action(PSTR(ACTION_ON_RESUME)); }
void HostUI::resume() { action(F(ACTION_ON_RESUME)); }
#endif
#ifdef ACTION_ON_RESUMED
void host_action_resumed() { host_action(PSTR(ACTION_ON_RESUMED)); }
void HostUI::resumed() { action(F(ACTION_ON_RESUMED)); }
#endif
#ifdef ACTION_ON_CANCEL
void host_action_cancel() { host_action(PSTR(ACTION_ON_CANCEL)); }
void HostUI::cancel() { action(F(ACTION_ON_CANCEL)); }
#endif
#ifdef ACTION_ON_START
void host_action_start() { host_action(PSTR(ACTION_ON_START)); }
void HostUI::start() { action(F(ACTION_ON_START)); }
#endif
#if ENABLED(G29_RETRY_AND_RECOVER)
#ifdef ACTION_ON_G29_RECOVER
void HostUI::g29_recover() { action(F(ACTION_ON_G29_RECOVER)); }
#endif
#ifdef ACTION_ON_G29_FAILURE
void HostUI::g29_failure() { action(F(ACTION_ON_G29_FAILURE)); }
#endif
#endif
#ifdef SHUTDOWN_ACTION
void HostUI::shutdown() { action(F(SHUTDOWN_ACTION)); }
#endif
#if ENABLED(HOST_PROMPT_SUPPORT)
PromptReason HostUI::host_prompt_reason = PROMPT_NOT_DEFINED;
PGMSTR(CONTINUE_STR, "Continue");
PGMSTR(DISMISS_STR, "Dismiss");
@@ -75,64 +92,64 @@ void host_action(PGM_P const pstr, const bool eol) {
extern bool wait_for_user;
#endif
PromptReason host_prompt_reason = PROMPT_NOT_DEFINED;
void host_action_notify(const char * const message) {
void HostUI::notify(const char * const cstr) {
PORT_REDIRECT(SerialMask::All);
host_action(PSTR("notification "), false);
SERIAL_ECHOLN(message);
action(F("notification "), false);
SERIAL_ECHOLN(cstr);
}
void host_action_notify_P(PGM_P const message) {
void HostUI::notify_P(PGM_P const pstr) {
PORT_REDIRECT(SerialMask::All);
host_action(PSTR("notification "), false);
SERIAL_ECHOLNPGM_P(message);
action(F("notification "), false);
SERIAL_ECHOLNPGM_P(pstr);
}
void host_action_prompt(PGM_P const ptype, const bool eol=true) {
void HostUI::prompt(FSTR_P const ptype, const bool eol/*=true*/) {
PORT_REDIRECT(SerialMask::All);
host_action(PSTR("prompt_"), false);
SERIAL_ECHOPGM_P(ptype);
action(F("prompt_"), false);
SERIAL_ECHOF(ptype);
if (eol) SERIAL_EOL();
}
void host_action_prompt_plus(PGM_P const ptype, PGM_P const pstr, const char extra_char='\0') {
host_action_prompt(ptype, false);
void HostUI::prompt_plus(FSTR_P const ptype, FSTR_P const fstr, const char extra_char/*='\0'*/) {
prompt(ptype, false);
PORT_REDIRECT(SerialMask::All);
SERIAL_CHAR(' ');
SERIAL_ECHOPGM_P(pstr);
SERIAL_ECHOF(fstr);
if (extra_char != '\0') SERIAL_CHAR(extra_char);
SERIAL_EOL();
}
void host_action_prompt_begin(const PromptReason reason, PGM_P const pstr, const char extra_char/*='\0'*/) {
host_action_prompt_end();
void HostUI::prompt_begin(const PromptReason reason, FSTR_P const fstr, const char extra_char/*='\0'*/) {
prompt_end();
host_prompt_reason = reason;
host_action_prompt_plus(PSTR("begin"), pstr, extra_char);
prompt_plus(F("begin"), fstr, extra_char);
}
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 HostUI::prompt_button(FSTR_P const fstr) { prompt_plus(F("button"), fstr); }
void HostUI::prompt_end() { prompt(F("end")); }
void HostUI::prompt_show() { prompt(F("show")); }
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 HostUI::_prompt_show(FSTR_P const btn1, FSTR_P const btn2) {
if (btn1) prompt_button(btn1);
if (btn2) prompt_button(btn2);
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 HostUI::prompt_do(const PromptReason reason, FSTR_P const fstr, FSTR_P const btn1/*=nullptr*/, FSTR_P const btn2/*=nullptr*/) {
prompt_begin(reason, fstr);
_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 HostUI::prompt_do(const PromptReason reason, FSTR_P const fstr, const char extra_char, FSTR_P const btn1/*=nullptr*/, FSTR_P const btn2/*=nullptr*/) {
prompt_begin(reason, fstr, extra_char);
_prompt_show(btn1, btn2);
}
void filament_load_host_prompt() {
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
);
}
#if ENABLED(ADVANCED_PAUSE_FEATURE)
void HostUI::filament_load_prompt() {
const bool disable_to_continue = TERN0(HAS_FILAMENT_SENSOR, runout.filament_ran_out);
prompt_do(PROMPT_FILAMENT_RUNOUT, F("Paused"), F("PurgeMore"),
disable_to_continue ? F("DisableRunout") : FPSTR(CONTINUE_STR)
);
}
#endif
//
// Handle responses from the host, such as:
@@ -141,7 +158,7 @@ void host_action(PGM_P const pstr, const bool eol) {
// - Resume Print response
// - Dismissal of info
//
void host_response_handler(const uint8_t response) {
void HostUI::handle_response(const uint8_t response) {
const PromptReason hpr = host_prompt_reason;
host_prompt_reason = PROMPT_NOT_DEFINED; // Reset now ahead of logic
switch (hpr) {

View File

@@ -24,34 +24,8 @@
#include "../inc/MarlinConfigPre.h"
#include "../HAL/shared/Marduino.h"
void host_action(PGM_P const pstr, const bool eol=true);
#ifdef ACTION_ON_KILL
void host_action_kill();
#endif
#ifdef ACTION_ON_PAUSE
void host_action_pause(const bool eol=true);
#endif
#ifdef ACTION_ON_PAUSED
void host_action_paused(const bool eol=true);
#endif
#ifdef ACTION_ON_RESUME
void host_action_resume();
#endif
#ifdef ACTION_ON_RESUMED
void host_action_resumed();
#endif
#ifdef ACTION_ON_CANCEL
void host_action_cancel();
#endif
#ifdef ACTION_ON_START
void host_action_start();
#endif
#if ENABLED(HOST_PROMPT_SUPPORT)
extern const char CONTINUE_STR[], DISMISS_STR[];
enum PromptReason : uint8_t {
PROMPT_NOT_DEFINED,
PROMPT_FILAMENT_RUNOUT,
@@ -61,21 +35,80 @@ void host_action(PGM_P const pstr, const bool eol=true);
PROMPT_INFO
};
extern PromptReason host_prompt_reason;
void host_response_handler(const uint8_t response);
void host_action_notify(const char * const message);
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, 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);
}
void filament_load_host_prompt();
#endif
class HostUI {
public:
static void action(FSTR_P const fstr, const bool eol=true);
#ifdef ACTION_ON_KILL
static void kill();
#endif
#ifdef ACTION_ON_PAUSE
static void pause(const bool eol=true);
#endif
#ifdef ACTION_ON_PAUSED
static void paused(const bool eol=true);
#endif
#ifdef ACTION_ON_RESUME
static void resume();
#endif
#ifdef ACTION_ON_RESUMED
static void resumed();
#endif
#ifdef ACTION_ON_CANCEL
static void cancel();
#endif
#ifdef ACTION_ON_START
static void start();
#endif
#ifdef SHUTDOWN_ACTION
static void shutdown();
#endif
#if ENABLED(G29_RETRY_AND_RECOVER)
#ifdef ACTION_ON_G29_RECOVER
static void g29_recover();
#endif
#ifdef ACTION_ON_G29_FAILURE
static void g29_failure();
#endif
#endif
#if ENABLED(HOST_PROMPT_SUPPORT)
private:
static void prompt(FSTR_P const ptype, const bool eol=true);
static void prompt_plus(FSTR_P const ptype, FSTR_P const fstr, const char extra_char='\0');
static void prompt_show();
static void _prompt_show(FSTR_P const btn1, FSTR_P const btn2);
public:
static PromptReason host_prompt_reason;
static void handle_response(const uint8_t response);
static void notify_P(PGM_P const message);
static void notify(FSTR_P const fmsg) { notify_P(FTOP(fmsg)); }
static void notify(const char * const message);
static void prompt_begin(const PromptReason reason, FSTR_P const fstr, const char extra_char='\0');
static void prompt_button(FSTR_P const fstr);
static void prompt_end();
static void prompt_do(const PromptReason reason, FSTR_P const pstr, FSTR_P const btn1=nullptr, FSTR_P const btn2=nullptr);
static void prompt_do(const PromptReason reason, FSTR_P const pstr, const char extra_char, FSTR_P const btn1=nullptr, FSTR_P const btn2=nullptr);
static void prompt_open(const PromptReason reason, FSTR_P const pstr, FSTR_P const btn1=nullptr, FSTR_P const btn2=nullptr) {
if (host_prompt_reason == PROMPT_NOT_DEFINED) prompt_do(reason, pstr, btn1, btn2);
}
#if ENABLED(ADVANCED_PAUSE_FEATURE)
static void filament_load_prompt();
#endif
#endif
};
extern HostUI hostui;
extern const char CONTINUE_STR[], DISMISS_STR[];

View File

@@ -0,0 +1,91 @@
/**
* 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/>.
*
*/
/**
* Hotend Idle Timeout
* Prevent filament in the nozzle from charring and causing a critical jam.
*/
#include "../inc/MarlinConfig.h"
#if ENABLED(HOTEND_IDLE_TIMEOUT)
#include "hotend_idle.h"
#include "../gcode/gcode.h"
#include "../module/temperature.h"
#include "../module/motion.h"
#include "../module/planner.h"
#include "../lcd/marlinui.h"
extern HotendIdleProtection hotend_idle;
millis_t HotendIdleProtection::next_protect_ms = 0;
void HotendIdleProtection::check_hotends(const millis_t &ms) {
bool do_prot = false;
HOTEND_LOOP() {
const bool busy = (TERN0(HAS_RESUME_CONTINUE, wait_for_user) || planner.has_blocks_queued());
if (thermalManager.degHotend(e) >= (HOTEND_IDLE_MIN_TRIGGER) && !busy) {
do_prot = true; break;
}
}
if (bool(next_protect_ms) != do_prot)
next_protect_ms = do_prot ? ms + hp_interval : 0;
}
void HotendIdleProtection::check_e_motion(const millis_t &ms) {
static float old_e_position = 0;
if (old_e_position != current_position.e) {
old_e_position = current_position.e; // Track filament motion
if (next_protect_ms) // If some heater is on then...
next_protect_ms = ms + hp_interval; // ...delay the timeout till later
}
}
void HotendIdleProtection::check() {
const millis_t ms = millis(); // Shared millis
check_hotends(ms); // Any hotends need protection?
check_e_motion(ms); // Motion will protect them
// Hot and not moving for too long...
if (next_protect_ms && ELAPSED(ms, next_protect_ms))
timed_out();
}
// Lower (but don't raise) hotend / bed temperatures
void HotendIdleProtection::timed_out() {
next_protect_ms = 0;
SERIAL_ECHOLNPGM("Hotend Idle Timeout");
LCD_MESSAGE(MSG_HOTEND_IDLE_TIMEOUT);
HOTEND_LOOP() {
if ((HOTEND_IDLE_NOZZLE_TARGET) < thermalManager.degTargetHotend(e))
thermalManager.setTargetHotend(HOTEND_IDLE_NOZZLE_TARGET, e);
}
#if HAS_HEATED_BED
if ((HOTEND_IDLE_BED_TARGET) < thermalManager.degTargetBed())
thermalManager.setTargetBed(HOTEND_IDLE_BED_TARGET);
#endif
}
#endif // HOTEND_IDLE_TIMEOUT

View File

@@ -21,25 +21,17 @@
*/
#pragma once
#include "../../../inc/MarlinConfigPre.h"
#include "../core/millis_t.h"
extern xy_pos_t bilinear_grid_spacing, bilinear_start;
extern xy_float_t bilinear_grid_factor;
extern bed_mesh_t z_values;
float bilinear_z_offset(const xy_pos_t &raw);
class HotendIdleProtection {
public:
static void check();
private:
static constexpr millis_t hp_interval = SEC_TO_MS(HOTEND_IDLE_TIMEOUT_SEC);
static millis_t next_protect_ms;
static void check_hotends(const millis_t &ms);
static void check_e_motion(const millis_t &ms);
static void timed_out();
};
void extrapolate_unprobed_bed_level();
void print_bilinear_leveling_grid();
void refresh_bed_level();
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
void print_bilinear_leveling_grid_virt();
void bed_level_virt_interpolate();
#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);
#endif
#define _GET_MESH_X(I) float(bilinear_start.x + (I) * bilinear_grid_spacing.x)
#define _GET_MESH_Y(J) float(bilinear_start.y + (J) * bilinear_grid_spacing.y)
#define Z_VALUES_ARR z_values
extern HotendIdleProtection hotend_idle;

View File

@@ -68,13 +68,13 @@ Joystick joystick;
void Joystick::report() {
SERIAL_ECHOPGM("Joystick");
#if HAS_JOY_ADC_X
SERIAL_ECHOPGM_P(SP_X_STR, JOY_X(x.raw));
SERIAL_ECHOPGM_P(SP_X_STR, JOY_X(x.getraw()));
#endif
#if HAS_JOY_ADC_Y
SERIAL_ECHOPGM_P(SP_Y_STR, JOY_Y(y.raw));
SERIAL_ECHOPGM_P(SP_Y_STR, JOY_Y(y.getraw()));
#endif
#if HAS_JOY_ADC_Z
SERIAL_ECHOPGM_P(SP_Z_STR, JOY_Z(z.raw));
SERIAL_ECHOPGM_P(SP_Z_STR, JOY_Z(z.getraw()));
#endif
#if HAS_JOY_ADC_EN
SERIAL_ECHO_TERNARY(READ(JOY_EN_PIN), " EN=", "HIGH (dis", "LOW (en", "abled)");
@@ -91,29 +91,29 @@ Joystick joystick;
if (READ(JOY_EN_PIN)) return;
#endif
auto _normalize_joy = [](float &axis_jog, const int16_t raw, const int16_t (&joy_limits)[4]) {
auto _normalize_joy = [](float &axis_jog, const raw_adc_t raw, const raw_adc_t (&joy_limits)[4]) {
if (WITHIN(raw, joy_limits[0], joy_limits[3])) {
// within limits, check deadzone
if (raw > joy_limits[2])
axis_jog = (raw - joy_limits[2]) / float(joy_limits[3] - joy_limits[2]);
else if (raw < joy_limits[1])
axis_jog = (raw - joy_limits[1]) / float(joy_limits[1] - joy_limits[0]); // negative value
axis_jog = int16_t(raw - joy_limits[1]) / float(joy_limits[1] - joy_limits[0]); // negative value
// Map normal to jog value via quadratic relationship
axis_jog = SIGN(axis_jog) * sq(axis_jog);
}
};
#if HAS_JOY_ADC_X
static constexpr int16_t joy_x_limits[4] = JOY_X_LIMITS;
_normalize_joy(norm_jog.x, JOY_X(x.raw), joy_x_limits);
static constexpr raw_adc_t joy_x_limits[4] = JOY_X_LIMITS;
_normalize_joy(norm_jog.x, JOY_X(x.getraw()), joy_x_limits);
#endif
#if HAS_JOY_ADC_Y
static constexpr int16_t joy_y_limits[4] = JOY_Y_LIMITS;
_normalize_joy(norm_jog.y, JOY_Y(y.raw), joy_y_limits);
static constexpr raw_adc_t joy_y_limits[4] = JOY_Y_LIMITS;
_normalize_joy(norm_jog.y, JOY_Y(y.getraw()), joy_y_limits);
#endif
#if HAS_JOY_ADC_Z
static constexpr int16_t joy_z_limits[4] = JOY_Z_LIMITS;
_normalize_joy(norm_jog.z, JOY_Z(z.raw), joy_z_limits);
static constexpr raw_adc_t joy_z_limits[4] = JOY_Z_LIMITS;
_normalize_joy(norm_jog.z, JOY_Z(z.getraw()), joy_z_limits);
#endif
}
@@ -163,7 +163,7 @@ Joystick joystick;
// norm_jog values of [-1 .. 1] maps linearly to [-feedrate .. feedrate]
xyz_float_t move_dist{0};
float hypot2 = 0;
LOOP_LINEAR_AXES(i) if (norm_jog[i]) {
LOOP_NUM_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]);
}
@@ -172,8 +172,9 @@ Joystick joystick;
current_position += move_dist;
apply_motion_limits(current_position);
const float length = sqrt(hypot2);
PlannerHints hints(length);
injecting_now = true;
planner.buffer_line(current_position, length / seg_time, active_extruder, length);
planner.buffer_line(current_position, length / seg_time, active_extruder, hints);
injecting_now = false;
}
}

View File

@@ -42,7 +42,7 @@
#include "pca9533.h"
#endif
#if ENABLED(CASE_LIGHT_USE_RGB_LED)
#if EITHER(CASE_LIGHT_USE_RGB_LED, CASE_LIGHT_USE_NEOPIXEL)
#include "../../feature/caselight.h"
#endif
@@ -95,6 +95,10 @@ void LEDLights::set_color(const LEDColor &incol
#endif
#endif
#if BOTH(CASE_LIGHT_MENU, CASE_LIGHT_USE_NEOPIXEL)
// Update brightness only if caselight is ON or switching leds off
if (caselight.on || incol.is_off())
#endif
neo.set_brightness(incol.i);
#if ENABLED(NEOPIXEL_IS_SEQUENTIAL)
@@ -106,6 +110,10 @@ void LEDLights::set_color(const LEDColor &incol
}
#endif
#if BOTH(CASE_LIGHT_MENU, CASE_LIGHT_USE_NEOPIXEL)
// Update color only if caselight is ON or switching leds off
if (caselight.on || incol.is_off())
#endif
neo.set_color(neocolor);
#endif
@@ -121,11 +129,11 @@ 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), c); \
else \
WRITE(RGB_LED_##C##_PIN, c ? HIGH : LOW); \
#define _UPDATE_RGBW(C,c) do { \
if (PWM_PIN(RGB_LED_##C##_PIN)) \
hal.set_pwm_duty(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);
@@ -150,7 +158,7 @@ void LEDLights::set_color(const LEDColor &incol
void LEDLights::toggle() { if (lights_on) set_off(); else update(); }
#endif
#ifdef LED_BACKLIGHT_TIMEOUT
#if LED_POWEROFF_TIMEOUT > 0
millis_t LEDLights::led_off_time; // = 0
@@ -170,9 +178,9 @@ void LEDLights::set_color(const LEDColor &incol
#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)
NEO2_USER_PRESET_RED, NEO2_USER_PRESET_GREEN, NEO2_USER_PRESET_BLUE
OPTARG(HAS_WHITE_LED2, NEO2_USER_PRESET_WHITE)
OPTARG(NEOPIXEL_LED, NEO2_USER_PRESET_BRIGHTNESS)
);
#endif

View File

@@ -54,6 +54,8 @@ typedef struct LEDColor {
OPTARG(NEOPIXEL_LED, i(NEOPIXEL_BRIGHTNESS))
{}
LEDColor(const LEDColor&) = default;
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)) {}
@@ -68,11 +70,6 @@ typedef struct LEDColor {
return *this;
}
LEDColor& operator=(const LEDColor &right) {
if (this != &right) memcpy(this, &right, sizeof(LEDColor));
return *this;
}
bool operator==(const LEDColor &right) {
if (this == &right) return true;
return 0 == memcmp(this, &right, sizeof(LEDColor));
@@ -118,7 +115,7 @@ public:
OPTARG(NEOPIXEL_IS_SEQUENTIAL, bool isSequence=false)
);
static inline void set_color(uint8_t r, uint8_t g, uint8_t b
static 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)
@@ -126,23 +123,23 @@ public:
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()); }
static inline void set_green() { set_color(LEDColorGreen()); }
static inline void set_white() { set_color(LEDColorWhite()); }
static void set_off() { set_color(LEDColorOff()); }
static void set_green() { set_color(LEDColorGreen()); }
static void set_white() { set_color(LEDColorWhite()); }
#if ENABLED(LED_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()); }
static void set_default() { set_color(defaultLEDColor); }
static void set_red() { set_color(LEDColorRed()); }
static void set_orange() { set_color(LEDColorOrange()); }
static void set_yellow() { set_color(LEDColorYellow()); }
static void set_blue() { set_color(LEDColorBlue()); }
static void set_indigo() { set_color(LEDColorIndigo()); }
static void set_violet() { set_color(LEDColorViolet()); }
#endif
#if ENABLED(PRINTER_EVENT_LEDS)
static inline LEDColor get_color() { return lights_on ? color : LEDColorOff(); }
static LEDColor get_color() { return lights_on ? color : LEDColorOff(); }
#endif
#if ANY(LED_CONTROL_MENU, PRINTER_EVENT_LEDS, CASE_LIGHT_IS_COLOR_LED)
@@ -154,15 +151,15 @@ public:
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); }
static void update() { set_color(color); }
#endif
#ifdef LED_BACKLIGHT_TIMEOUT
#if LED_POWEROFF_TIMEOUT > 0
private:
static millis_t led_off_time;
public:
static inline void reset_timeout(const millis_t &ms) {
led_off_time = ms + LED_BACKLIGHT_TIMEOUT;
static void reset_timeout(const millis_t &ms) {
led_off_time = ms + LED_POWEROFF_TIMEOUT;
if (!lights_on) update();
}
static void update_timeout(const bool power_on);
@@ -181,7 +178,7 @@ extern LEDLights leds;
static void set_color(const LEDColor &color);
static inline void set_color(uint8_t r, uint8_t g, uint8_t b
static 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)
) {
@@ -191,26 +188,26 @@ extern LEDLights leds;
));
}
static inline void set_off() { set_color(LEDColorOff()); }
static inline void set_green() { set_color(LEDColorGreen()); }
static inline void set_white() { set_color(LEDColorWhite()); }
static void set_off() { set_color(LEDColorOff()); }
static void set_green() { set_color(LEDColorGreen()); }
static 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()); }
static void set_default() { set_color(defaultLEDColor); }
static void set_red() { set_color(LEDColorRed()); }
static void set_orange() { set_color(LEDColorOrange()); }
static void set_yellow() { set_color(LEDColorYellow()); }
static void set_blue() { set_color(LEDColorBlue()); }
static void set_indigo() { set_color(LEDColorIndigo()); }
static 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); }
static void update() { set_color(color); }
#endif
};

View File

@@ -35,7 +35,7 @@
#endif
Marlin_NeoPixel neo;
int8_t Marlin_NeoPixel::neoindex;
pixel_index_t Marlin_NeoPixel::neoindex;
Adafruit_NeoPixel Marlin_NeoPixel::adaneo1(NEOPIXEL_PIXELS, NEOPIXEL_PIN, NEOPIXEL_TYPE + NEO_KHZ800);
#if CONJOINED_NEOPIXEL
@@ -44,14 +44,14 @@ Adafruit_NeoPixel Marlin_NeoPixel::adaneo1(NEOPIXEL_PIXELS, NEOPIXEL_PIN, NEOPIX
#ifdef NEOPIXEL_BKGD_INDEX_FIRST
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++)
void Marlin_NeoPixel::set_background_color(const uint8_t r, const uint8_t g, const uint8_t b, const 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]);
set_background_color(background_color);
}
#endif
@@ -108,7 +108,7 @@ void Marlin_NeoPixel::init() {
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))
(255, 255, 255, 255))
);
}
@@ -116,7 +116,7 @@ void Marlin_NeoPixel::init() {
Marlin_NeoPixel2 neo2;
int8_t Marlin_NeoPixel2::neoindex;
pixel_index_t Marlin_NeoPixel2::neoindex;
Adafruit_NeoPixel Marlin_NeoPixel2::adaneo(NEOPIXEL2_PIXELS, NEOPIXEL2_PIN, NEOPIXEL2_TYPE);
void Marlin_NeoPixel2::set_color(const uint32_t color) {

View File

@@ -63,7 +63,13 @@
#endif
// ------------------------
// Function prototypes
// Types
// ------------------------
typedef IF<(TERN0(NEOPIXEL_LED, NEOPIXEL_PIXELS > 127)), int16_t, int8_t>::type pixel_index_t;
// ------------------------
// Classes
// ------------------------
class Marlin_NeoPixel {
@@ -74,7 +80,7 @@ private:
#endif
public:
static int8_t neoindex;
static pixel_index_t neoindex;
static void init();
static void set_color_startup(const uint32_t c);
@@ -82,16 +88,17 @@ public:
static void set_color(const uint32_t c);
#ifdef NEOPIXEL_BKGD_INDEX_FIRST
static void set_background_color(uint8_t r, uint8_t g, uint8_t b, uint8_t w);
static void set_background_color(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t w);
static void set_background_color(const uint8_t (&rgbw)[4]) { set_background_color(rgbw[0], rgbw[1], rgbw[2], rgbw[3]); }
static void reset_background_color();
#endif
static inline void begin() {
static void begin() {
adaneo1.begin();
TERN_(CONJOINED_NEOPIXEL, adaneo2.begin());
}
static inline void set_pixel_color(const uint16_t n, const uint32_t c) {
static void set_pixel_color(const uint16_t n, const uint32_t c) {
#if ENABLED(NEOPIXEL2_INSERIES)
if (n >= NEOPIXEL_PIXELS) adaneo2.setPixelColor(n - (NEOPIXEL_PIXELS), c);
else adaneo1.setPixelColor(n, c);
@@ -101,12 +108,12 @@ public:
#endif
}
static inline void set_brightness(const uint8_t b) {
static void set_brightness(const uint8_t b) {
adaneo1.setBrightness(b);
TERN_(CONJOINED_NEOPIXEL, adaneo2.setBrightness(b));
}
static inline void show() {
static 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();
@@ -122,11 +129,18 @@ public:
}
// Accessors
static inline uint16_t pixels() { return adaneo1.numPixels() * TERN1(NEOPIXEL2_INSERIES, 2); }
static uint16_t pixels() { return adaneo1.numPixels() * TERN1(NEOPIXEL2_INSERIES, 2); }
static inline uint8_t brightness() { return adaneo1.getBrightness(); }
static uint32_t pixel_color(const uint16_t n) {
#if ENABLED(NEOPIXEL2_INSERIES)
if (n >= NEOPIXEL_PIXELS) return adaneo2.getPixelColor(n - (NEOPIXEL_PIXELS));
#endif
return adaneo1.getPixelColor(n);
}
static inline uint32_t Color(uint8_t r, uint8_t g, uint8_t b OPTARG(HAS_WHITE_LED, uint8_t w)) {
static uint8_t brightness() { return adaneo1.getBrightness(); }
static 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));
}
};
@@ -150,25 +164,26 @@ extern Marlin_NeoPixel neo;
static Adafruit_NeoPixel adaneo;
public:
static int8_t neoindex;
static pixel_index_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() {
static void begin() { adaneo.begin(); }
static void set_pixel_color(const uint16_t n, const uint32_t c) { adaneo.setPixelColor(n, c); }
static void set_brightness(const uint8_t b) { adaneo.setBrightness(b); }
static 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)) {
static uint16_t pixels() { return adaneo.numPixels();}
static uint32_t pixel_color(const uint16_t n) { return adaneo.getPixelColor(n); }
static uint8_t brightness() { return adaneo.getBrightness(); }
static 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));
}
};

View File

@@ -36,32 +36,32 @@ private:
static bool leds_off_after_print;
#endif
static inline void set_done() { TERN(LED_COLOR_PRESETS, leds.set_default(), leds.set_off()); }
static 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 LEDColor onHotendHeatingStart() { old_intensity = 0; return leds.get_color(); }
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 LEDColor onBedHeatingStart() { old_intensity = 127; return leds.get_color(); }
static void onBedHeating(const celsius_t start, const celsius_t current, const celsius_t target);
#endif
#if HAS_HEATED_CHAMBER
static inline LEDColor onChamberHeatingStart() { old_intensity = 127; return leds.get_color(); }
static 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); }
static void onHeatingDone() { leds.set_white(); }
static void onPidTuningDone(LEDColor c) { leds.set_color(c); }
#endif
#if ENABLED(SDSUPPORT)
static inline void onPrintCompleted() {
static void onPrintCompleted() {
leds.set_green();
#if HAS_LEDS_OFF_FLAG
leds_off_after_print = true;
@@ -71,7 +71,7 @@ public:
#endif
}
static inline void onResumeAfterWait() {
static void onResumeAfterWait() {
#if HAS_LEDS_OFF_FLAG
if (leds_off_after_print) {
set_done();

View File

@@ -44,7 +44,6 @@
#include "max7219.h"
#include "../module/planner.h"
#include "../module/stepper.h"
#include "../MarlinCore.h"
#include "../HAL/shared/Delay.h"
@@ -52,6 +51,7 @@
#define HAS_SIDE_BY_SIDE 1
#endif
#define _ROT ((MAX7219_ROTATE + 360) % 360)
#if _ROT == 0 || _ROT == 180
#define MAX7219_X_LEDS TERN(HAS_SIDE_BY_SIDE, 8, MAX7219_LINES)
#define MAX7219_Y_LEDS TERN(HAS_SIDE_BY_SIDE, MAX7219_LINES, 8)
@@ -62,6 +62,15 @@
#error "MAX7219_ROTATE must be a multiple of +/- 90°."
#endif
#ifdef MAX7219_DEBUG_PROFILE
CodeProfiler::Mode CodeProfiler::mode = ACCUMULATE_AVERAGE;
uint8_t CodeProfiler::instance_count = 0;
uint32_t CodeProfiler::last_calc_time = micros();
uint8_t CodeProfiler::time_fraction = 0;
uint32_t CodeProfiler::total_time = 0;
uint16_t CodeProfiler::call_count = 0;
#endif
Max7219 max7219;
uint8_t Max7219::led_line[MAX7219_LINES]; // = { 0 };
@@ -69,7 +78,7 @@ uint8_t Max7219::suspended; // = 0;
#define LINE_REG(Q) (max7219_reg_digit0 + ((Q) & 0x7))
#if _ROT == 0 || _ROT == 270
#if (_ROT == 0 || _ROT == 270) == DISABLED(MAX7219_REVERSE_EACH)
#define _LED_BIT(Q) (7 - ((Q) & 0x7))
#else
#define _LED_BIT(Q) ((Q) & 0x7)
@@ -124,11 +133,10 @@ uint8_t Max7219::suspended; // = 0;
#define SIG_DELAY() DELAY_NS(250)
#endif
void Max7219::error(const char * const func, const int32_t v1, const int32_t v2/*=-1*/) {
void Max7219::error(FSTR_P const func, const int32_t v1, const int32_t v2/*=-1*/) {
#if ENABLED(MAX7219_ERRORS)
SERIAL_ECHOPGM("??? Max7219::");
SERIAL_ECHOPGM_P(func);
SERIAL_CHAR('(');
SERIAL_ECHOF(func, AS_CHAR('('));
SERIAL_ECHO(v1);
if (v2 > 0) SERIAL_ECHOPGM(", ", v2);
SERIAL_CHAR(')');
@@ -267,26 +275,27 @@ void Max7219::set(const uint8_t line, const uint8_t bits) {
#endif // MAX7219_NUMERIC
// Modify a single LED bit and send the changed line
void Max7219::led_set(const uint8_t x, const uint8_t y, const bool on) {
if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(PSTR("led_set"), x, y);
void Max7219::led_set(const uint8_t x, const uint8_t y, const bool on, uint8_t * const rcm/*=nullptr*/) {
if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(F("led_set"), x, y);
if (BIT_7219(x, y) == on) return;
XOR_7219(x, y);
refresh_unit_line(LED_IND(x, y));
if (rcm != nullptr) *rcm |= _BV(LED_IND(x, y) & 0x07);
}
void Max7219::led_on(const uint8_t x, const uint8_t y) {
if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(PSTR("led_on"), x, y);
led_set(x, y, true);
void Max7219::led_on(const uint8_t x, const uint8_t y, uint8_t * const rcm/*=nullptr*/) {
if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(F("led_on"), x, y);
led_set(x, y, true, rcm);
}
void Max7219::led_off(const uint8_t x, const uint8_t y) {
if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(PSTR("led_off"), x, y);
led_set(x, y, false);
void Max7219::led_off(const uint8_t x, const uint8_t y, uint8_t * const rcm/*=nullptr*/) {
if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(F("led_off"), x, y);
led_set(x, y, false, rcm);
}
void Max7219::led_toggle(const uint8_t x, const uint8_t y) {
if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(PSTR("led_toggle"), x, y);
led_set(x, y, !BIT_7219(x, y));
void Max7219::led_toggle(const uint8_t x, const uint8_t y, uint8_t * const rcm/*=nullptr*/) {
if (x >= MAX7219_X_LEDS || y >= MAX7219_Y_LEDS) return error(F("led_toggle"), x, y);
led_set(x, y, !BIT_7219(x, y), rcm);
}
void Max7219::send_row(const uint8_t row) {
@@ -328,13 +337,13 @@ void Max7219::fill() {
}
void Max7219::clear_row(const uint8_t row) {
if (row >= MAX7219_Y_LEDS) return error(PSTR("clear_row"), row);
if (row >= MAX7219_Y_LEDS) return error(F("clear_row"), row);
LOOP_L_N(x, MAX7219_X_LEDS) CLR_7219(x, row);
send_row(row);
}
void Max7219::clear_column(const uint8_t col) {
if (col >= MAX7219_X_LEDS) return error(PSTR("set_column"), col);
if (col >= MAX7219_X_LEDS) return error(F("set_column"), col);
LOOP_L_N(y, MAX7219_Y_LEDS) CLR_7219(col, y);
send_column(col);
}
@@ -345,7 +354,7 @@ void Max7219::clear_column(const uint8_t col) {
* once with a single call to the function (if rotated 90° or 270°).
*/
void Max7219::set_row(const uint8_t row, const uint32_t val) {
if (row >= MAX7219_Y_LEDS) return error(PSTR("set_row"), row);
if (row >= MAX7219_Y_LEDS) return error(F("set_row"), row);
uint32_t mask = _BV32(MAX7219_X_LEDS - 1);
LOOP_L_N(x, MAX7219_X_LEDS) {
if (val & mask) SET_7219(x, row); else CLR_7219(x, row);
@@ -360,7 +369,7 @@ void Max7219::set_row(const uint8_t row, const uint32_t val) {
* once with a single call to the function (if rotated 0° or 180°).
*/
void Max7219::set_column(const uint8_t col, const uint32_t val) {
if (col >= MAX7219_X_LEDS) return error(PSTR("set_column"), col);
if (col >= MAX7219_X_LEDS) return error(F("set_column"), col);
uint32_t mask = _BV32(MAX7219_Y_LEDS - 1);
LOOP_L_N(y, MAX7219_Y_LEDS) {
if (val & mask) SET_7219(col, y); else CLR_7219(col, y);
@@ -371,56 +380,56 @@ void Max7219::set_column(const uint8_t col, const uint32_t val) {
void Max7219::set_rows_16bits(const uint8_t y, uint32_t val) {
#if MAX7219_X_LEDS == 8
if (y > MAX7219_Y_LEDS - 2) return error(PSTR("set_rows_16bits"), y, val);
if (y > MAX7219_Y_LEDS - 2) return error(F("set_rows_16bits"), y, val);
set_row(y + 1, val); val >>= 8;
set_row(y + 0, val);
#else // at least 16 bits on each row
if (y > MAX7219_Y_LEDS - 1) return error(PSTR("set_rows_16bits"), y, val);
if (y > MAX7219_Y_LEDS - 1) return error(F("set_rows_16bits"), y, val);
set_row(y, val);
#endif
}
void Max7219::set_rows_32bits(const uint8_t y, uint32_t val) {
#if MAX7219_X_LEDS == 8
if (y > MAX7219_Y_LEDS - 4) return error(PSTR("set_rows_32bits"), y, val);
if (y > MAX7219_Y_LEDS - 4) return error(F("set_rows_32bits"), y, val);
set_row(y + 3, val); val >>= 8;
set_row(y + 2, val); val >>= 8;
set_row(y + 1, val); val >>= 8;
set_row(y + 0, val);
#elif MAX7219_X_LEDS == 16
if (y > MAX7219_Y_LEDS - 2) return error(PSTR("set_rows_32bits"), y, val);
if (y > MAX7219_Y_LEDS - 2) return error(F("set_rows_32bits"), y, val);
set_row(y + 1, val); val >>= 16;
set_row(y + 0, val);
#else // at least 24 bits on each row. In the 3 matrix case, just display the low 24 bits
if (y > MAX7219_Y_LEDS - 1) return error(PSTR("set_rows_32bits"), y, val);
if (y > MAX7219_Y_LEDS - 1) return error(F("set_rows_32bits"), y, val);
set_row(y, val);
#endif
}
void Max7219::set_columns_16bits(const uint8_t x, uint32_t val) {
#if MAX7219_Y_LEDS == 8
if (x > MAX7219_X_LEDS - 2) return error(PSTR("set_columns_16bits"), x, val);
if (x > MAX7219_X_LEDS - 2) return error(F("set_columns_16bits"), x, val);
set_column(x + 0, val); val >>= 8;
set_column(x + 1, val);
#else // at least 16 bits in each column
if (x > MAX7219_X_LEDS - 1) return error(PSTR("set_columns_16bits"), x, val);
if (x > MAX7219_X_LEDS - 1) return error(F("set_columns_16bits"), x, val);
set_column(x, val);
#endif
}
void Max7219::set_columns_32bits(const uint8_t x, uint32_t val) {
#if MAX7219_Y_LEDS == 8
if (x > MAX7219_X_LEDS - 4) return error(PSTR("set_rows_32bits"), x, val);
if (x > MAX7219_X_LEDS - 4) return error(F("set_rows_32bits"), x, val);
set_column(x + 3, val); val >>= 8;
set_column(x + 2, val); val >>= 8;
set_column(x + 1, val); val >>= 8;
set_column(x + 0, val);
#elif MAX7219_Y_LEDS == 16
if (x > MAX7219_X_LEDS - 2) return error(PSTR("set_rows_32bits"), x, val);
if (x > MAX7219_X_LEDS - 2) return error(F("set_rows_32bits"), x, val);
set_column(x + 1, val); val >>= 16;
set_column(x + 0, val);
#else // at least 24 bits on each row. In the 3 matrix case, just display the low 24 bits
if (x > MAX7219_X_LEDS - 1) return error(PSTR("set_rows_32bits"), x, val);
if (x > MAX7219_X_LEDS - 1) return error(F("set_rows_32bits"), x, val);
set_column(x, val);
#endif
}
@@ -449,7 +458,7 @@ void Max7219::register_setup() {
pulse_load(); // Tell the chips to load the clocked out data
}
#ifdef MAX7219_INIT_TEST
#if MAX7219_INIT_TEST
uint8_t test_mode = 0;
millis_t next_patt_ms;
@@ -537,13 +546,9 @@ void Max7219::init() {
register_setup();
LOOP_LE_N(i, 7) { // Empty registers to turn all LEDs off
led_line[i] = 0x00;
send(max7219_reg_digit0 + i, 0);
pulse_load(); // Tell the chips to load the clocked out data
}
clear();
#ifdef MAX7219_INIT_TEST
#if MAX7219_INIT_TEST
start_test_pattern();
#endif
}
@@ -555,41 +560,55 @@ void Max7219::init() {
*/
// Apply changes to update a marker
void Max7219::mark16(const uint8_t pos, const uint8_t v1, const uint8_t v2) {
void Max7219::mark16(const uint8_t pos, const uint8_t v1, const uint8_t v2, uint8_t * const rcm/*=nullptr*/) {
#if MAX7219_X_LEDS > 8 // At least 16 LEDs on the X-Axis. Use single line.
led_off(v1 & 0xF, pos);
led_on(v2 & 0xF, pos);
led_off(v1 & 0xF, pos, rcm);
led_on(v2 & 0xF, pos, rcm);
#elif MAX7219_Y_LEDS > 8 // At least 16 LEDs on the Y-Axis. Use a single column.
led_off(pos, v1 & 0xF);
led_on(pos, v2 & 0xF);
led_off(pos, v1 & 0xF, rcm);
led_on(pos, v2 & 0xF, rcm);
#else // Single 8x8 LED matrix. Use two lines to get 16 LEDs.
led_off(v1 & 0x7, pos + (v1 >= 8));
led_on(v2 & 0x7, pos + (v2 >= 8));
led_off(v1 & 0x7, pos + (v1 >= 8), rcm);
led_on(v2 & 0x7, pos + (v2 >= 8), rcm);
#endif
}
// Apply changes to update a tail-to-head range
void Max7219::range16(const uint8_t y, const uint8_t ot, const uint8_t nt, const uint8_t oh, const uint8_t nh) {
void Max7219::range16(const uint8_t y, const uint8_t ot, const uint8_t nt, const uint8_t oh,
const uint8_t nh, uint8_t * const rcm/*=nullptr*/) {
#if MAX7219_X_LEDS > 8 // At least 16 LEDs on the X-Axis. Use single line.
if (ot != nt) for (uint8_t n = ot & 0xF; n != (nt & 0xF) && n != (nh & 0xF); n = (n + 1) & 0xF)
led_off(n & 0xF, y);
led_off(n & 0xF, y, rcm);
if (oh != nh) for (uint8_t n = (oh + 1) & 0xF; n != ((nh + 1) & 0xF); n = (n + 1) & 0xF)
led_on(n & 0xF, y);
led_on(n & 0xF, y, rcm);
#elif MAX7219_Y_LEDS > 8 // At least 16 LEDs on the Y-Axis. Use a single column.
if (ot != nt) for (uint8_t n = ot & 0xF; n != (nt & 0xF) && n != (nh & 0xF); n = (n + 1) & 0xF)
led_off(y, n & 0xF);
led_off(y, n & 0xF, rcm);
if (oh != nh) for (uint8_t n = (oh + 1) & 0xF; n != ((nh + 1) & 0xF); n = (n + 1) & 0xF)
led_on(y, n & 0xF);
led_on(y, n & 0xF, rcm);
#else // Single 8x8 LED matrix. Use two lines to get 16 LEDs.
if (ot != nt) for (uint8_t n = ot & 0xF; n != (nt & 0xF) && n != (nh & 0xF); n = (n + 1) & 0xF)
led_off(n & 0x7, y + (n >= 8));
led_off(n & 0x7, y + (n >= 8), rcm);
if (oh != nh) for (uint8_t n = (oh + 1) & 0xF; n != ((nh + 1) & 0xF); n = (n + 1) & 0xF)
led_on(n & 0x7, y + (n >= 8));
led_on(n & 0x7, y + (n >= 8), rcm);
#endif
}
// Apply changes to update a quantity
void Max7219::quantity16(const uint8_t pos, const uint8_t ov, const uint8_t nv) {
void Max7219::quantity(const uint8_t pos, const uint8_t ov, const uint8_t nv, uint8_t * const rcm/*=nullptr*/) {
for (uint8_t i = _MIN(nv, ov); i < _MAX(nv, ov); i++)
led_set(
#if MAX7219_X_LEDS >= MAX7219_Y_LEDS
i, pos // Single matrix or multiple matrices in Landscape
#else
pos, i // Multiple matrices in Portrait
#endif
, nv >= ov
, rcm
);
}
void Max7219::quantity16(const uint8_t pos, const uint8_t ov, const uint8_t nv, uint8_t * const rcm/*=nullptr*/) {
for (uint8_t i = _MIN(nv, ov); i < _MAX(nv, ov); i++)
led_set(
#if MAX7219_X_LEDS > 8 // At least 16 LEDs on the X-Axis. Use single line.
@@ -600,6 +619,7 @@ void Max7219::quantity16(const uint8_t pos, const uint8_t ov, const uint8_t nv)
i >> 1, pos + (i & 1)
#endif
, nv >= ov
, rcm
);
}
@@ -637,16 +657,20 @@ void Max7219::idle_tasks() {
register_setup();
}
#ifdef MAX7219_INIT_TEST
#if MAX7219_INIT_TEST
if (test_mode) {
run_test_pattern();
return;
}
#endif
// suspend updates and record which lines have changed for batching later
suspended++;
uint8_t row_change_mask = 0x00;
#if ENABLED(MAX7219_DEBUG_PRINTER_ALIVE)
if (do_blink) {
led_toggle(MAX7219_X_LEDS - 1, MAX7219_Y_LEDS - 1);
led_toggle(MAX7219_X_LEDS - 1, MAX7219_Y_LEDS - 1, &row_change_mask);
next_blink = ms + 1000;
}
#endif
@@ -656,7 +680,7 @@ void Max7219::idle_tasks() {
static int16_t last_head_cnt = 0xF, last_tail_cnt = 0xF;
if (last_head_cnt != head || last_tail_cnt != tail) {
range16(MAX7219_DEBUG_PLANNER_HEAD, last_tail_cnt, tail, last_head_cnt, head);
range16(MAX7219_DEBUG_PLANNER_HEAD, last_tail_cnt, tail, last_head_cnt, head, &row_change_mask);
last_head_cnt = head;
last_tail_cnt = tail;
}
@@ -666,7 +690,7 @@ void Max7219::idle_tasks() {
#ifdef MAX7219_DEBUG_PLANNER_HEAD
static int16_t last_head_cnt = 0x1;
if (last_head_cnt != head) {
mark16(MAX7219_DEBUG_PLANNER_HEAD, last_head_cnt, head);
mark16(MAX7219_DEBUG_PLANNER_HEAD, last_head_cnt, head, &row_change_mask);
last_head_cnt = head;
}
#endif
@@ -674,7 +698,7 @@ void Max7219::idle_tasks() {
#ifdef MAX7219_DEBUG_PLANNER_TAIL
static int16_t last_tail_cnt = 0x1;
if (last_tail_cnt != tail) {
mark16(MAX7219_DEBUG_PLANNER_TAIL, last_tail_cnt, tail);
mark16(MAX7219_DEBUG_PLANNER_TAIL, last_tail_cnt, tail, &row_change_mask);
last_tail_cnt = tail;
}
#endif
@@ -685,11 +709,26 @@ void Max7219::idle_tasks() {
static int16_t last_depth = 0;
const int16_t current_depth = (head - tail + BLOCK_BUFFER_SIZE) & (BLOCK_BUFFER_SIZE - 1) & 0xF;
if (current_depth != last_depth) {
quantity16(MAX7219_DEBUG_PLANNER_QUEUE, last_depth, current_depth);
quantity16(MAX7219_DEBUG_PLANNER_QUEUE, last_depth, current_depth, &row_change_mask);
last_depth = current_depth;
}
#endif
#ifdef MAX7219_DEBUG_PROFILE
static uint8_t last_time_fraction = 0;
const uint8_t current_time_fraction = (uint16_t(CodeProfiler::get_time_fraction()) * MAX7219_NUMBER_UNITS + 8) / 16;
if (current_time_fraction != last_time_fraction) {
quantity(MAX7219_DEBUG_PROFILE, last_time_fraction, current_time_fraction, &row_change_mask);
last_time_fraction = current_time_fraction;
}
#endif
// batch line updates
suspended--;
if (!suspended)
LOOP_L_N(i, 8) if (row_change_mask & _BV(i))
refresh_line(i);
// After resume() automatically do a refresh()
if (suspended == 0x80) {
suspended = 0;

View File

@@ -42,10 +42,11 @@
* a Max7219_Set_Row(). The opposite is true for rotations of 0 or 180 degrees.
*/
#include "../inc/MarlinConfig.h"
#ifndef MAX7219_ROTATE
#define MAX7219_ROTATE 0
#endif
#define _ROT ((MAX7219_ROTATE + 360) % 360)
#ifndef MAX7219_NUMBER_UNITS
#define MAX7219_NUMBER_UNITS 1
@@ -71,6 +72,67 @@
#define max7219_reg_shutdown 0x0C
#define max7219_reg_displayTest 0x0F
#ifdef MAX7219_DEBUG_PROFILE
// This class sums up the amount of time for which its instances exist.
// By default there is one instantiated for the duration of the idle()
// function. But an instance can be created in any code block to measure
// the time spent from the point of instantiation until the CPU leaves
// block. Be careful about having multiple instances of CodeProfiler as
// it does not guard against double counting. In general mixing ISR and
// non-ISR use will require critical sections but note that mode setting
// is atomic so the total or average times can safely be read if you set
// mode to FREEZE first.
class CodeProfiler {
public:
enum Mode : uint8_t { ACCUMULATE_AVERAGE, ACCUMULATE_TOTAL, FREEZE };
private:
static Mode mode;
static uint8_t instance_count;
static uint32_t last_calc_time;
static uint32_t total_time;
static uint8_t time_fraction;
static uint16_t call_count;
uint32_t start_time;
public:
CodeProfiler() : start_time(micros()) { instance_count++; }
~CodeProfiler() {
instance_count--;
if (mode == FREEZE) return;
call_count++;
const uint32_t now = micros();
total_time += now - start_time;
if (mode == ACCUMULATE_TOTAL) return;
// update time_fraction every hundred milliseconds
if (instance_count == 0 && ELAPSED(now, last_calc_time + 100000)) {
time_fraction = total_time * 128 / (now - last_calc_time);
last_calc_time = now;
total_time = 0;
}
}
static void set_mode(Mode _mode) { mode = _mode; }
static void reset() {
time_fraction = 0;
last_calc_time = micros();
total_time = 0;
call_count = 0;
}
// returns fraction of total time which was measured, scaled from 0 to 128
static uint8_t get_time_fraction() { return time_fraction; }
// returns total time in microseconds
static uint32_t get_total_time() { return total_time; }
static uint16_t get_call_count() { return call_count; }
};
#endif
class Max7219 {
public:
static uint8_t led_line[MAX7219_LINES];
@@ -86,13 +148,13 @@ public:
static void send(const uint8_t reg, const uint8_t data);
// Refresh all units
static inline void refresh() { for (uint8_t i = 0; i < 8; i++) refresh_line(i); }
static void refresh() { for (uint8_t i = 0; i < 8; i++) refresh_line(i); }
// Suspend / resume updates to the LED unit
// Use these methods to speed up multiple changes
// or to apply updates from interrupt context.
static inline void suspend() { suspended++; }
static inline void resume() { suspended--; suspended |= 0x80; }
static void suspend() { suspended++; }
static void resume() { suspended--; suspended |= 0x80; }
// Update a single native line on all units
static void refresh_line(const uint8_t line);
@@ -108,10 +170,10 @@ public:
#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);
static void led_off(const uint8_t x, const uint8_t y);
static void led_toggle(const uint8_t x, const uint8_t y);
static void led_set(const uint8_t x, const uint8_t y, const bool on, uint8_t * const rcm=nullptr);
static void led_on(const uint8_t x, const uint8_t y, uint8_t * const rcm=nullptr);
static void led_off(const uint8_t x, const uint8_t y, uint8_t * const rcm=nullptr);
static void led_toggle(const uint8_t x, const uint8_t y, uint8_t * const rcm=nullptr);
// Set all LEDs in a single column
static void set_column(const uint8_t col, const uint32_t val);
@@ -140,16 +202,17 @@ public:
private:
static uint8_t suspended;
static void error(const char * const func, const int32_t v1, const int32_t v2=-1);
static void error(FSTR_P const func, const int32_t v1, const int32_t v2=-1);
static void noop();
static void set(const uint8_t line, const uint8_t bits);
static void send_row(const uint8_t row);
static void send_column(const uint8_t col);
static void mark16(const uint8_t y, const uint8_t v1, const uint8_t v2);
static void range16(const uint8_t y, const uint8_t ot, const uint8_t nt, const uint8_t oh, const uint8_t nh);
static void quantity16(const uint8_t y, const uint8_t ov, const uint8_t nv);
static void mark16(const uint8_t y, const uint8_t v1, const uint8_t v2, uint8_t * const rcm=nullptr);
static void range16(const uint8_t y, const uint8_t ot, const uint8_t nt, const uint8_t oh, const uint8_t nh, uint8_t * const rcm=nullptr);
static void quantity(const uint8_t y, const uint8_t ov, const uint8_t nv, uint8_t * const rcm=nullptr);
static void quantity16(const uint8_t y, const uint8_t ov, const uint8_t nv, uint8_t * const rcm=nullptr);
#ifdef MAX7219_INIT_TEST
#if MAX7219_INIT_TEST
static void test_pattern();
static void run_test_pattern();
static void start_test_pattern();

View File

@@ -26,7 +26,7 @@
* Algorithm & Implementation: Scott Mudge - mail@scottmudge.com
* Date: Dec. 2020
*
* Character Frequencies from ~30 MB of comment-stripped gcode:
* Character Frequencies from ~30 MB of comment-stripped G-code:
* '1' -> 4451136 '4' -> 1353273 '\n' -> 1087683 '-' -> 90242
* '0' -> 4253577 '9' -> 1352147 'G' -> 1075806 'Z' -> 34109
* ' ' -> 3053297 '3' -> 1262929 'X' -> 975742 'M' -> 11879
@@ -169,10 +169,9 @@ void MeatPack::handle_command(const MeatPack_Command c) {
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 " ");
SERIAL_ECHOPGM("[MP] " MeatPack_ProtocolVersion " ");
serialprint_onoff(TEST(state, MPConfig_Bit_Active));
SERIAL_ECHOPGM_P(TEST(state, MPConfig_Bit_NoSpaces) ? PSTR(" NSP\n") : PSTR(" ESP\n"));
SERIAL_ECHOF(TEST(state, MPConfig_Bit_NoSpaces) ? F(" NSP\n") : F(" ESP\n"));
}
/**

View File

@@ -29,7 +29,7 @@
* Specifically optimized for 3D printing G-Code, this is a zero-cost data compression method
* which packs ~180-190% more data into the same amount of bytes going to the CNC controller.
* As a majority of G-Code can be represented by a restricted alphabet, I performed histogram
* analysis on a wide variety of 3D printing gcode samples, and found ~93% of all gcode could
* analysis on a wide variety of 3D printing G-code samples, and found ~93% of all G-code could
* be represented by the same 15-character alphabet.
*
* This allowed me to design a system of packing 2 8-bit characters into a single byte, assuming
@@ -38,7 +38,7 @@
*
* Combined with some logic to allow commingling of full-width characters outside of this 15-
* character alphabet (at the cost of an extra 8-bits per full-width character), and by stripping
* out unnecessary comments, the end result is gcode which is roughly half the original size.
* out unnecessary comments, the end result is G-code which is roughly half the original size.
*
* Why did I do this? I noticed micro-stuttering and other data-bottleneck issues while printing
* objects with high curvature, especially at high speeds. There is also the issue of the limited

View File

@@ -63,7 +63,7 @@ void Mixer::normalize(const uint8_t tool_index) {
#ifdef MIXER_NORMALIZER_DEBUG
SERIAL_ECHOPGM("Mixer: Old relation : [ ");
MIXER_STEPPER_LOOP(i) {
SERIAL_ECHO_F(collector[i] / csum, 3);
SERIAL_DECIMAL(collector[i] / csum);
SERIAL_CHAR(' ');
}
SERIAL_ECHOLNPGM("]");

View File

@@ -126,7 +126,7 @@ class Mixer {
static mixer_perc_t mix[MIXING_STEPPERS]; // Scratch array for the Mix in proportion to 100
static inline void copy_mix_to_color(mixer_comp_t (&tcolor)[MIXING_STEPPERS]) {
static void copy_mix_to_color(mixer_comp_t (&tcolor)[MIXING_STEPPERS]) {
// Scale each component to the largest one in terms of COLOR_A_MASK
// So the largest component will be COLOR_A_MASK and the other will be in proportion to it
const float scale = (COLOR_A_MASK) * RECIPROCAL(_MAX(
@@ -145,7 +145,7 @@ class Mixer {
#endif
}
static inline void update_mix_from_vtool(const uint8_t j=selected_vtool) {
static void update_mix_from_vtool(const uint8_t j=selected_vtool) {
float ctot = 0;
MIXER_STEPPER_LOOP(i) ctot += color[j][i];
//MIXER_STEPPER_LOOP(i) mix[i] = 100.0f * color[j][i] / ctot;
@@ -165,7 +165,7 @@ class Mixer {
#if HAS_DUAL_MIXING
// Update the virtual tool from an edited mix
static inline void update_vtool_from_mix() {
static void update_vtool_from_mix() {
copy_mix_to_color(color[selected_vtool]);
TERN_(GRADIENT_MIX, refresh_gradient());
// MIXER_STEPPER_LOOP(i) collector[i] = mix[i];
@@ -182,7 +182,7 @@ class Mixer {
// Update the current mix from the gradient for a given 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_t z) {
static void gradient_control(const_float_t z) {
if (gradient.enabled) {
if (z >= gradient.end_z)
T(gradient.end_vtool);
@@ -191,7 +191,7 @@ class Mixer {
}
}
static inline void update_mix_from_gradient() {
static void update_mix_from_gradient() {
float ctot = 0;
MIXER_STEPPER_LOOP(i) ctot += gradient.color[i];
MIXER_STEPPER_LOOP(i) mix[i] = (mixer_perc_t)CEIL(100.0f * gradient.color[i] / ctot);

View File

@@ -24,9 +24,9 @@
#if HAS_PRUSA_MMU1
#include "../MarlinCore.h"
#include "../module/planner.h"
#include "../module/stepper.h"
#include "../../MarlinCore.h"
#include "../../module/planner.h"
#include "../../module/stepper.h"
void mmu_init() {
SET_OUTPUT(E_MUX0_PIN);

View File

@@ -51,7 +51,7 @@ When done, the MMU sends
- MMU => 'ok\n'
We don't wait for a response here but immediately continue with the next gcode which should
We don't wait for a response here but immediately continue with the next G-code which should
be one or more extruder moves to feed the filament into the hotend.

View File

@@ -54,7 +54,7 @@ MMU2 mmu2;
#define MMU_CMD_TIMEOUT 45000UL // 45s timeout for mmu commands (except P0)
#define MMU_P0_TIMEOUT 3000UL // Timeout for P0 command: 3seconds
#define MMU2_COMMAND(S) tx_str_P(PSTR(S "\n"))
#define MMU2_COMMAND(S) tx_str(F(S "\n"))
#if ENABLED(MMU_EXTRUDER_SENSOR)
uint8_t mmu_idl_sens = 0;
@@ -143,6 +143,11 @@ uint8_t MMU2::get_current_tool() {
#define FILAMENT_PRESENT() (READ(FIL_RUNOUT1_PIN) != FIL_RUNOUT1_STATE)
#endif
void mmu2_attn_buzz(const bool two=false) {
BUZZ(200, 404);
if (two) { BUZZ(10, 0); BUZZ(200, 404); }
}
void MMU2::mmu_loop() {
switch (state) {
@@ -229,17 +234,17 @@ void MMU2::mmu_loop() {
if (cmd) {
if (WITHIN(cmd, MMU_CMD_T0, MMU_CMD_T0 + EXTRUDERS - 1)) {
// tool change
int filament = cmd - MMU_CMD_T0;
const int filament = cmd - MMU_CMD_T0;
DEBUG_ECHOLNPGM("MMU <= T", filament);
tx_printf_P(PSTR("T%d\n"), filament);
tx_printf(F("T%d\n"), filament);
TERN_(MMU_EXTRUDER_SENSOR, mmu_idl_sens = 1); // enable idler sensor, if any
state = 3; // wait for response
}
else if (WITHIN(cmd, MMU_CMD_L0, MMU_CMD_L0 + EXTRUDERS - 1)) {
// load
int filament = cmd - MMU_CMD_L0;
const int filament = cmd - MMU_CMD_L0;
DEBUG_ECHOLNPGM("MMU <= L", filament);
tx_printf_P(PSTR("L%d\n"), filament);
tx_printf(F("L%d\n"), filament);
state = 3; // wait for response
}
else if (cmd == MMU_CMD_C0) {
@@ -257,9 +262,9 @@ void MMU2::mmu_loop() {
}
else if (WITHIN(cmd, MMU_CMD_E0, MMU_CMD_E0 + EXTRUDERS - 1)) {
// eject filament
int filament = cmd - MMU_CMD_E0;
const int filament = cmd - MMU_CMD_E0;
DEBUG_ECHOLNPGM("MMU <= E", filament);
tx_printf_P(PSTR("E%d\n"), filament);
tx_printf(F("E%d\n"), filament);
state = 3; // wait for response
}
else if (cmd == MMU_CMD_R0) {
@@ -270,9 +275,9 @@ void MMU2::mmu_loop() {
}
else if (WITHIN(cmd, MMU_CMD_F0, MMU_CMD_F0 + EXTRUDERS - 1)) {
// filament type
int filament = cmd - MMU_CMD_F0;
const int filament = cmd - MMU_CMD_F0;
DEBUG_ECHOLNPGM("MMU <= F", filament, " ", cmd_arg);
tx_printf_P(PSTR("F%d %d\n"), filament, cmd_arg);
tx_printf(F("F%d %d\n"), filament, cmd_arg);
state = 3; // wait for response
}
@@ -356,13 +361,15 @@ void MMU2::mmu_loop() {
*/
bool MMU2::rx_start() {
// check for start message
return rx_str_P(PSTR("start\n"));
return rx_str(F("start\n"));
}
/**
* Check if the data received ends with the given string.
*/
bool MMU2::rx_str_P(const char *str) {
bool MMU2::rx_str(FSTR_P fstr) {
PGM_P pstr = FTOP(fstr);
uint8_t i = strlen(rx_buffer);
while (MMU2_SERIAL.available()) {
@@ -375,14 +382,14 @@ bool MMU2::rx_str_P(const char *str) {
}
rx_buffer[i] = '\0';
uint8_t len = strlen_P(str);
uint8_t len = strlen_P(pstr);
if (i < len) return false;
str += len;
pstr += len;
while (len--) {
char c0 = pgm_read_byte(str--), c1 = rx_buffer[i--];
char c0 = pgm_read_byte(pstr--), c1 = rx_buffer[i--];
if (c0 == c1) continue;
if (c0 == '\r' && c1 == '\n') continue; // match cr as lf
if (c0 == '\n' && c1 == '\r') continue; // match lf as cr
@@ -394,19 +401,19 @@ bool MMU2::rx_str_P(const char *str) {
/**
* Transfer data to MMU, no argument
*/
void MMU2::tx_str_P(const char *str) {
void MMU2::tx_str(FSTR_P fstr) {
clear_rx_buffer();
uint8_t len = strlen_P(str);
LOOP_L_N(i, len) MMU2_SERIAL.write(pgm_read_byte(str++));
PGM_P pstr = FTOP(fstr);
while (const char c = pgm_read_byte(pstr)) { MMU2_SERIAL.write(c); pstr++; }
prev_request = millis();
}
/**
* Transfer data to MMU, single argument
*/
void MMU2::tx_printf_P(const char *format, int argument = -1) {
void MMU2::tx_printf(FSTR_P format, int argument = -1) {
clear_rx_buffer();
uint8_t len = sprintf_P(tx_buffer, format, argument);
const uint8_t len = sprintf_P(tx_buffer, FTOP(format), argument);
LOOP_L_N(i, len) MMU2_SERIAL.write(tx_buffer[i]);
prev_request = millis();
}
@@ -414,9 +421,9 @@ void MMU2::tx_printf_P(const char *format, int argument = -1) {
/**
* Transfer data to MMU, two arguments
*/
void MMU2::tx_printf_P(const char *format, int argument1, int argument2) {
void MMU2::tx_printf(FSTR_P format, int argument1, int argument2) {
clear_rx_buffer();
uint8_t len = sprintf_P(tx_buffer, format, argument1, argument2);
const uint8_t len = sprintf_P(tx_buffer, FTOP(format), argument1, argument2);
LOOP_L_N(i, len) MMU2_SERIAL.write(tx_buffer[i]);
prev_request = millis();
}
@@ -433,7 +440,7 @@ void MMU2::clear_rx_buffer() {
* Check if we received 'ok' from MMU
*/
bool MMU2::rx_ok() {
if (rx_str_P(PSTR("ok\n"))) {
if (rx_str(F("ok\n"))) {
prev_P0_request = millis();
return true;
}
@@ -446,12 +453,12 @@ bool MMU2::rx_ok() {
void MMU2::check_version() {
if (buildnr < MMU_REQUIRED_FW_BUILDNR) {
SERIAL_ERROR_MSG("Invalid MMU2 firmware. Version >= " STRINGIFY(MMU_REQUIRED_FW_BUILDNR) " required.");
kill(GET_TEXT(MSG_KILL_MMU2_FIRMWARE));
kill(GET_TEXT_F(MSG_KILL_MMU2_FIRMWARE));
}
}
static void mmu2_not_responding() {
LCD_MESSAGEPGM(MSG_MMU2_NOT_RESPONDING);
LCD_MESSAGE(MSG_MMU2_NOT_RESPONDING);
BUZZ(100, 659);
BUZZ(200, 698);
BUZZ(100, 659);
@@ -487,7 +494,7 @@ static void mmu2_not_responding() {
if (index != extruder) {
stepper.disable_extruder();
ui.status_printf_P(0, GET_TEXT(MSG_MMU2_LOADING_FILAMENT), int(index + 1));
ui.status_printf(0, GET_TEXT_F(MSG_MMU2_LOADING_FILAMENT), int(index + 1));
command(MMU_CMD_T0 + index);
manage_response(true, true);
@@ -523,7 +530,7 @@ static void mmu2_not_responding() {
while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100);
load_filament_to_nozzle(index);
#else
BUZZ(400, 40);
ERR_BUZZ();
#endif
} break;
@@ -542,7 +549,7 @@ static void mmu2_not_responding() {
active_extruder = 0;
}
#else
BUZZ(400, 40);
ERR_BUZZ();
#endif
} break;
@@ -573,7 +580,7 @@ static void mmu2_not_responding() {
command(MMU_CMD_U0);
manage_response(true, true);
}
ui.status_printf_P(0, GET_TEXT(MSG_MMU2_LOADING_FILAMENT), int(index + 1));
ui.status_printf(0, GET_TEXT_F(MSG_MMU2_LOADING_FILAMENT), int(index + 1));
mmu_loading_flag = true;
command(MMU_CMD_T0 + index);
manage_response(true, true);
@@ -611,7 +618,7 @@ static void mmu2_not_responding() {
while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100);
load_filament_to_nozzle(index);
#else
BUZZ(400, 40);
ERR_BUZZ();
#endif
} break;
@@ -631,7 +638,7 @@ static void mmu2_not_responding() {
extruder = index;
active_extruder = 0;
#else
BUZZ(400, 40);
ERR_BUZZ();
#endif
} break;
@@ -671,7 +678,7 @@ static void mmu2_not_responding() {
if (index != extruder) {
stepper.disable_extruder();
ui.status_printf_P(0, GET_TEXT(MSG_MMU2_LOADING_FILAMENT), int(index + 1));
ui.status_printf(0, GET_TEXT_F(MSG_MMU2_LOADING_FILAMENT), int(index + 1));
command(MMU_CMD_T0 + index);
manage_response(true, true);
command(MMU_CMD_C0);
@@ -705,7 +712,7 @@ static void mmu2_not_responding() {
while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100);
load_filament_to_nozzle(index);
#else
BUZZ(400, 40);
ERR_BUZZ();
#endif
} break;
@@ -724,7 +731,7 @@ static void mmu2_not_responding() {
extruder = index;
active_extruder = 0;
#else
BUZZ(400, 40);
ERR_BUZZ();
#endif
} break;
@@ -808,26 +815,27 @@ void MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) {
if (turn_off_nozzle && resume_hotend_temp) {
thermalManager.setTargetHotend(resume_hotend_temp, active_extruder);
LCD_MESSAGEPGM(MSG_HEATING);
BUZZ(200, 40);
LCD_MESSAGE(MSG_HEATING);
ERR_BUZZ();
while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(1000);
}
if (move_axes && all_axes_homed()) {
LCD_MESSAGEPGM(MSG_MMU2_RESUMING);
BUZZ(198, 404); BUZZ(4, 0); BUZZ(198, 404);
LCD_MESSAGE(MSG_MMU2_RESUMING);
mmu2_attn_buzz(true);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
if (move_axes && all_axes_homed()) {
// 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));
}
else {
BUZZ(198, 404); BUZZ(4, 0); BUZZ(198, 404);
LCD_MESSAGEPGM(MSG_MMU2_RESUMING);
}
#pragma GCC diagnostic pop
}
}
}
@@ -842,7 +850,7 @@ void MMU2::set_filament_type(const uint8_t index, const uint8_t filamentType) {
}
void MMU2::filament_runout() {
queue.inject_P(PSTR(MMU2_FILAMENT_RUNOUT_SCRIPT));
queue.inject(F(MMU2_FILAMENT_RUNOUT_SCRIPT));
planner.synchronize();
}
@@ -853,7 +861,7 @@ void MMU2::filament_runout() {
if (cmd == MMU_CMD_NONE && last_cmd == MMU_CMD_C0) {
if (present && !mmu2s_triggered) {
DEBUG_ECHOLNPGM("MMU <= 'A'");
tx_str_P(PSTR("A\n"));
tx_str(F("A\n"));
}
// Slowly spin the extruder during C0
else {
@@ -896,7 +904,7 @@ void MMU2::load_filament(const uint8_t index) {
command(MMU_CMD_L0 + index);
manage_response(false, false);
BUZZ(200, 404);
mmu2_attn_buzz();
}
/**
@@ -907,8 +915,8 @@ bool MMU2::load_filament_to_nozzle(const uint8_t index) {
if (!_enabled) return false;
if (thermalManager.tooColdToExtrude(active_extruder)) {
BUZZ(200, 404);
LCD_ALERTMESSAGEPGM(MSG_HOTEND_TOO_COLD);
mmu2_attn_buzz();
LCD_ALERTMESSAGE(MSG_HOTEND_TOO_COLD);
return false;
}
@@ -922,7 +930,7 @@ bool MMU2::load_filament_to_nozzle(const uint8_t index) {
extruder = index;
active_extruder = 0;
load_to_nozzle();
BUZZ(200, 404);
mmu2_attn_buzz();
}
return success;
}
@@ -931,7 +939,7 @@ bool MMU2::load_filament_to_nozzle(const uint8_t index) {
* Load filament to nozzle of multimaterial printer
*
* This function is used only after T? (user select filament) and M600 (change filament).
* It is not used after T0 .. T4 command (select filament), in such case, gcode is responsible for loading
* It is not used after T0 .. T4 command (select filament), in such case, G-code is responsible for loading
* filament to nozzle.
*/
void MMU2::load_to_nozzle() {
@@ -943,12 +951,12 @@ bool MMU2::eject_filament(const uint8_t index, const bool recover) {
if (!_enabled) return false;
if (thermalManager.tooColdToExtrude(active_extruder)) {
BUZZ(200, 404);
LCD_ALERTMESSAGEPGM(MSG_HOTEND_TOO_COLD);
mmu2_attn_buzz();
LCD_ALERTMESSAGE(MSG_HOTEND_TOO_COLD);
return false;
}
LCD_MESSAGEPGM(MSG_MMU2_EJECTING_FILAMENT);
LCD_MESSAGE(MSG_MMU2_EJECTING_FILAMENT);
stepper.enable_extruder();
current_position.e -= MMU2_FILAMENTCHANGE_EJECT_FEED;
@@ -958,13 +966,12 @@ bool MMU2::eject_filament(const uint8_t index, const bool recover) {
manage_response(false, false);
if (recover) {
LCD_MESSAGEPGM(MSG_MMU2_EJECT_RECOVER);
BUZZ(200, 404);
TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, PSTR("MMU2 Eject Recover"), CONTINUE_STR));
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(PSTR("MMU2 Eject Recover")));
LCD_MESSAGE(MSG_MMU2_EJECT_RECOVER);
mmu2_attn_buzz();
TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_do(PROMPT_USER_CONTINUE, F("MMU2 Eject Recover"), FPSTR(CONTINUE_STR)));
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired(F("MMU2 Eject Recover")));
TERN_(HAS_RESUME_CONTINUE, wait_for_user_response());
BUZZ(200, 404);
BUZZ(200, 404);
mmu2_attn_buzz(true);
command(MMU_CMD_R0);
manage_response(false, false);
@@ -977,7 +984,7 @@ bool MMU2::eject_filament(const uint8_t index, const bool recover) {
set_runout_valid(false);
BUZZ(200, 404);
mmu2_attn_buzz();
stepper.disable_extruder();
@@ -992,8 +999,8 @@ bool MMU2::unload() {
if (!_enabled) return false;
if (thermalManager.tooColdToExtrude(active_extruder)) {
BUZZ(200, 404);
LCD_ALERTMESSAGEPGM(MSG_HOTEND_TOO_COLD);
mmu2_attn_buzz();
LCD_ALERTMESSAGE(MSG_HOTEND_TOO_COLD);
return false;
}
@@ -1003,7 +1010,7 @@ bool MMU2::unload() {
command(MMU_CMD_U0);
manage_response(false, true);
BUZZ(200, 404);
mmu2_attn_buzz();
// no active tool
extruder = MMU2_NO_TOOL;
@@ -1024,8 +1031,7 @@ void MMU2::execute_extruder_sequence(const E_Step * sequence, int steps) {
const float es = pgm_read_float(&(step->extrude));
const feedRate_t fr_mm_m = pgm_read_float(&(step->feedRate));
DEBUG_ECHO_START();
DEBUG_ECHOLNPGM("E step ", es, "/", fr_mm_m);
DEBUG_ECHO_MSG("E step ", es, "/", fr_mm_m);
current_position.e += es;
line_to_current_position(MMM_TO_MMS(fr_mm_m));

View File

@@ -43,7 +43,7 @@ public:
static void init();
static void reset();
static inline bool enabled() { return _enabled; }
static bool enabled() { return _enabled; }
static void mmu_loop();
static void tool_change(const uint8_t index);
static void tool_change(const char *special);
@@ -57,10 +57,10 @@ public:
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, const int argument);
static void tx_printf_P(const char *format, const int argument1, const int argument2);
static bool rx_str(FSTR_P fstr);
static void tx_str(FSTR_P fstr);
static void tx_printf(FSTR_P ffmt, const int argument);
static void tx_printf(FSTR_P ffmt, const int argument1, const int argument2);
static void clear_rx_buffer();
static bool rx_ok();
@@ -99,7 +99,7 @@ private:
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) {
static void set_runout_valid(const bool valid) {
finda_runout_valid = valid;
#if HAS_FILAMENT_SENSOR
if (valid) runout.reset();

View File

@@ -40,7 +40,7 @@ uint32_t Password::value, Password::value_entry;
//
void Password::lock_machine() {
is_locked = true;
TERN_(HAS_LCD_MENU, authenticate_user(ui.status_screen, screen_password_entry));
TERN_(HAS_MARLINUI_MENU, authenticate_user(ui.status_screen, screen_password_entry));
}
//
@@ -55,7 +55,7 @@ void Password::authentication_check() {
is_locked = true;
SERIAL_ECHOLNPGM(STR_WRONG_PASSWORD);
}
TERN_(HAS_LCD_MENU, authentication_done());
TERN_(HAS_MARLINUI_MENU, authentication_done());
}
#endif // PASSWORD_FEATURE

View File

@@ -33,7 +33,7 @@ public:
static void lock_machine();
static void authentication_check();
#if HAS_LCD_MENU
#if HAS_MARLINUI_MENU
static void access_menu_password();
static void authentication_done();
static void media_gatekeeper();

View File

@@ -35,11 +35,18 @@
#include "../gcode/gcode.h"
#include "../module/motion.h"
#include "../module/planner.h"
#include "../module/stepper.h"
#include "../module/printcounter.h"
#include "../module/temperature.h"
#include "../core/serial.h"
#if HAS_EXTRUDERS
#include "../module/stepper.h"
#endif
#if ENABLED(AUTO_BED_LEVELING_UBL)
#include "bedlevel/bedlevel.h"
#endif
#if ENABLED(FWRETRACT)
#include "fwretract.h"
#endif
@@ -54,13 +61,13 @@
#if ENABLED(EXTENSIBLE_UI)
#include "../lcd/extui/ui_api.h"
#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
#include "../lcd/e3v2/enhanced/dwin.h"
#elif ENABLED(DWIN_LCD_PROUI)
#include "../lcd/e3v2/proui/dwin.h"
#endif
#include "../lcd/marlinui.h"
#if HAS_BUZZER
#if HAS_SOUND
#include "../libs/buzzer.h"
#endif
@@ -95,10 +102,10 @@ fil_change_settings_t fc_settings[EXTRUDERS];
#define _PMSG(L) L##_LCD
#endif
#if HAS_BUZZER
#if HAS_SOUND
static void impatient_beep(const int8_t max_beep_count, const bool restart=false) {
if (TERN0(HAS_LCD_MENU, pause_mode == PAUSE_MODE_PAUSE_PRINT)) return;
if (TERN0(HAS_MARLINUI_MENU, pause_mode == PAUSE_MODE_PAUSE_PRINT)) return;
static millis_t next_buzz = 0;
static int8_t runout_beep = 0;
@@ -195,11 +202,11 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load
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")));
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired(F("Load Filament")));
#if ENABLED(HOST_PROMPT_SUPPORT)
const char tool = '0' + TERN0(MULTI_FILAMENT_SENSOR, active_extruder);
host_prompt_do(PROMPT_USER_CONTINUE, PSTR("Load Filament T"), tool, CONTINUE_STR);
hostui.prompt_do(PROMPT_USER_CONTINUE, F("Load Filament T"), tool, FPSTR(CONTINUE_STR));
#endif
while (wait_for_user) {
@@ -253,9 +260,8 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load
if (show_lcd) ui.pause_show_message(PAUSE_MESSAGE_PURGE);
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));
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_FILAMENT_CHANGE_PURGE)));
TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_do(PROMPT_USER_CONTINUE, GET_TEXT_F(MSG_FILAMENT_CHANGE_PURGE), FPSTR(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);
@@ -272,14 +278,14 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load
unscaled_e_move(purge_length, ADVANCED_PAUSE_PURGE_FEEDRATE);
}
TERN_(HOST_PROMPT_SUPPORT, filament_load_host_prompt()); // Initiate another host prompt.
TERN_(HOST_PROMPT_SUPPORT, hostui.filament_load_prompt()); // Initiate another host prompt.
#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;
#if EITHER(HAS_LCD_MENU, DWIN_CREALITY_LCD_ENHANCED)
#if EITHER(HAS_MARLINUI_MENU, DWIN_LCD_PROUI)
ui.pause_show_message(PAUSE_MESSAGE_OPTION); // Also sets PAUSE_RESPONSE_WAIT_FOR
#else
pause_menu_response = PAUSE_RESPONSE_WAIT_FOR;
@@ -293,7 +299,7 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load
} while (TERN0(M600_PURGE_MORE_RESUMABLE, pause_menu_response == PAUSE_RESPONSE_EXTRUDE_MORE));
#endif
TERN_(HOST_PROMPT_SUPPORT, host_action_prompt_end());
TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_end());
return true;
}
@@ -399,13 +405,14 @@ bool pause_print(const_float_t retract, const xyz_pos_t &park_point, const bool
#if ENABLED(HOST_ACTION_COMMANDS)
#ifdef ACTION_ON_PAUSED
host_action_paused();
hostui.paused();
#elif defined(ACTION_ON_PAUSE)
host_action_pause();
hostui.pause();
#endif
#endif
TERN_(HOST_PROMPT_SUPPORT, host_prompt_open(PROMPT_INFO, PSTR("Pause"), DISMISS_STR));
TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_open(PROMPT_INFO, F("Pause"), FPSTR(DISMISS_STR)));
TERN_(DWIN_LCD_PROUI, DWIN_Print_Pause());
// Indicate that the printer is paused
++did_pause_print;
@@ -443,7 +450,15 @@ bool pause_print(const_float_t retract, const xyz_pos_t &park_point, const bool
// Initial retract before move to filament change position
if (retract && thermalManager.hotEnoughToExtrude(active_extruder)) {
DEBUG_ECHOLNPGM("... retract:", retract);
#if ENABLED(AUTO_BED_LEVELING_UBL)
const bool leveling_was_enabled = planner.leveling_active; // save leveling state
set_bed_leveling_enabled(false); // turn off leveling
#endif
unscaled_e_move(retract, PAUSE_PARK_RETRACT_FEEDRATE);
TERN_(AUTO_BED_LEVELING_UBL, set_bed_leveling_enabled(leveling_was_enabled)); // restore leveling
}
// If axes don't need to home then the nozzle can park
@@ -488,7 +503,7 @@ void show_continue_prompt(const bool is_reload) {
ui.pause_show_message(is_reload ? PAUSE_MESSAGE_INSERT : PAUSE_MESSAGE_WAITING);
SERIAL_ECHO_START();
SERIAL_ECHOPGM_P(is_reload ? PSTR(_PMSG(STR_FILAMENT_CHANGE_INSERT) "\n") : PSTR(_PMSG(STR_FILAMENT_CHANGE_WAIT) "\n"));
SERIAL_ECHOF(is_reload ? F(_PMSG(STR_FILAMENT_CHANGE_INSERT) "\n") : F(_PMSG(STR_FILAMENT_CHANGE_WAIT) "\n"));
}
void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep_count/*=0*/ DXC_ARGS) {
@@ -514,8 +529,8 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep
// Wait for filament insert by user and press button
KEEPALIVE_STATE(PAUSED_FOR_USER);
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)));
TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_do(PROMPT_USER_CONTINUE, GET_TEXT_F(MSG_NOZZLE_PARKED), FPSTR(CONTINUE_STR)));
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_NOZZLE_PARKED)));
wait_for_user = true; // LCD click or M108 will clear this
while (wait_for_user) {
impatient_beep(max_beep_count);
@@ -530,17 +545,17 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep
ui.pause_show_message(PAUSE_MESSAGE_HEAT);
SERIAL_ECHO_MSG(_PMSG(STR_FILAMENT_CHANGE_HEAT));
TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, GET_TEXT(MSG_HEATER_TIMEOUT), GET_TEXT(MSG_REHEAT)));
TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_do(PROMPT_USER_CONTINUE, GET_TEXT_F(MSG_HEATER_TIMEOUT), GET_TEXT_F(MSG_REHEAT)));
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(GET_TEXT(MSG_HEATER_TIMEOUT)));
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_HEATER_TIMEOUT)));
TERN_(HAS_RESUME_CONTINUE, wait_for_user_response(0, true)); // Wait for LCD click or M108
TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_INFO, GET_TEXT(MSG_REHEATING)));
TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_do(PROMPT_INFO, GET_TEXT_F(MSG_REHEATING)));
TERN_(EXTENSIBLE_UI, ExtUI::onStatusChanged_P(GET_TEXT(MSG_REHEATING)));
TERN_(EXTENSIBLE_UI, ExtUI::onStatusChanged(GET_TEXT_F(MSG_REHEATING)));
TERN_(DWIN_CREALITY_LCD_ENHANCED, ui.set_status_P(GET_TEXT(MSG_REHEATING)));
TERN_(DWIN_LCD_PROUI, LCD_MESSAGE(MSG_REHEATING));
// Re-enable the heaters if they timed out
HOTEND_LOOP() thermalManager.reset_hotend_idle_timer(e);
@@ -556,9 +571,9 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep
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)));
TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_do(PROMPT_USER_CONTINUE, GET_TEXT_F(MSG_REHEATDONE), FPSTR(CONTINUE_STR)));
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_REHEATDONE)));
TERN_(DWIN_LCD_PROUI, LCD_MESSAGE(MSG_REHEATDONE));
IF_DISABLED(PAUSE_REHEAT_FAST_RESUME, wait_for_user = true);
@@ -655,9 +670,16 @@ void resume_print(const_float_t slow_load_length/*=0*/, const_float_t fast_load_
prepare_internal_move_to_destination(NOZZLE_PARK_Z_FEEDRATE);
}
#if ENABLED(AUTO_BED_LEVELING_UBL)
const bool leveling_was_enabled = planner.leveling_active; // save leveling state
set_bed_leveling_enabled(false); // turn off leveling
#endif
// Unretract
unscaled_e_move(PAUSE_PARK_RETRACT_LENGTH, feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE));
TERN_(AUTO_BED_LEVELING_UBL, set_bed_leveling_enabled(leveling_was_enabled)); // restore leveling
// Intelligent resuming
#if ENABLED(FWRETRACT)
// If retracted before goto pause
@@ -667,8 +689,9 @@ void resume_print(const_float_t slow_load_length/*=0*/, const_float_t fast_load_
// If resume_position is negative
if (resume_position.e < 0) unscaled_e_move(resume_position.e, feedRate_t(PAUSE_PARK_RETRACT_FEEDRATE));
#if ADVANCED_PAUSE_RESUME_PRIME != 0
unscaled_e_move(ADVANCED_PAUSE_RESUME_PRIME, feedRate_t(ADVANCED_PAUSE_PURGE_FEEDRATE));
#ifdef ADVANCED_PAUSE_RESUME_PRIME
if (ADVANCED_PAUSE_RESUME_PRIME != 0)
unscaled_e_move(ADVANCED_PAUSE_RESUME_PRIME, feedRate_t(ADVANCED_PAUSE_PURGE_FEEDRATE));
#endif
// Now all extrusion positions are resumed and ready to be confirmed
@@ -678,14 +701,14 @@ void resume_print(const_float_t slow_load_length/*=0*/, const_float_t fast_load_
ui.pause_show_message(PAUSE_MESSAGE_STATUS);
#ifdef ACTION_ON_RESUMED
host_action_resumed();
hostui.resumed();
#elif defined(ACTION_ON_RESUME)
host_action_resume();
hostui.resume();
#endif
--did_pause_print;
TERN_(HOST_PROMPT_SUPPORT, host_prompt_open(PROMPT_INFO, PSTR("Resuming"), DISMISS_STR));
TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_open(PROMPT_INFO, F("Resuming"), FPSTR(DISMISS_STR)));
// Resume the print job timer if it was running
if (print_job_timer.isPaused()) print_job_timer.start();
@@ -705,9 +728,13 @@ void resume_print(const_float_t slow_load_length/*=0*/, const_float_t fast_load_
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());
#if ENABLED(DWIN_LCD_PROUI)
DWIN_Print_Resume();
HMI_ReturnScreen();
#else
ui.reset_status();
ui.return_to_status();
#endif
}
#endif // ADVANCED_PAUSE_FEATURE

View File

@@ -73,17 +73,10 @@ extern fil_change_settings_t fc_settings[EXTRUDERS];
extern uint8_t did_pause_print;
#if ENABLED(DUAL_X_CARRIAGE)
#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
#define DXC_PARAMS OPTARG(DUAL_X_CARRIAGE, const int8_t DXC_ext=-1)
#define DXC_ARGS OPTARG(DUAL_X_CARRIAGE, const int8_t DXC_ext)
#define DXC_PASS OPTARG(DUAL_X_CARRIAGE, DXC_ext)
#define DXC_SAY OPTARG(DUAL_X_CARRIAGE, " dxc:", int(DXC_ext))
// Pause the print. If unload_length is set, do a Filament Unload
bool pause_print(

View File

@@ -24,10 +24,14 @@
* power.cpp - power control
*/
#include "../inc/MarlinConfig.h"
#include "../inc/MarlinConfigPre.h"
#if EITHER(PSU_CONTROL, AUTO_POWER_CONTROL)
#include "power.h"
#include "../module/stepper.h"
#include "../module/planner.h"
#include "../module/stepper/indirection.h" // for restore_stepper_drivers
#include "../module/temperature.h"
#include "../MarlinCore.h"
#if ENABLED(PS_OFF_SOUND)
@@ -38,12 +42,11 @@
#include "../gcode/gcode.h"
#endif
#if EITHER(PSU_CONTROL, AUTO_POWER_CONTROL)
Power powerManager;
bool Power::psu_on;
#if ENABLED(AUTO_POWER_CONTROL)
#include "../module/stepper.h"
#include "../module/temperature.h"
#if BOTH(USE_CONTROLLER_FAN, AUTO_POWER_CONTROLLERFAN)
@@ -75,6 +78,10 @@ void Power::power_on() {
if (psu_on) return;
#if EITHER(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN)
cancelAutoPowerOff();
#endif
OUT_WRITE(PS_ON_PIN, PSU_ACTIVE_STATE);
psu_on = true;
safe_delay(PSU_POWERUP_DELAY);
@@ -82,20 +89,23 @@ void Power::power_on() {
TERN_(HAS_TRINAMIC_CONFIG, safe_delay(PSU_POWERUP_DELAY));
#ifdef PSU_POWERUP_GCODE
GcodeSuite::process_subcommands_now_P(PSTR(PSU_POWERUP_GCODE));
gcode.process_subcommands_now(F(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() {
SERIAL_ECHOLNPGM(STR_POWEROFF);
TERN_(HAS_SUICIDE, suicide());
if (!psu_on) return;
#ifdef PSU_POWEROFF_GCODE
GcodeSuite::process_subcommands_now_P(PSTR(PSU_POWEROFF_GCODE));
gcode.process_subcommands_now(F(PSU_POWEROFF_GCODE));
#endif
#if ENABLED(PS_OFF_SOUND)
@@ -104,8 +114,57 @@ void Power::power_off() {
OUT_WRITE(PS_ON_PIN, !PSU_ACTIVE_STATE);
psu_on = false;
#if EITHER(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN)
cancelAutoPowerOff();
#endif
}
#if EITHER(AUTO_POWER_CONTROL, POWER_OFF_WAIT_FOR_COOLDOWN)
bool Power::is_cooling_needed() {
#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;
}
#endif
#if EITHER(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN)
#if ENABLED(POWER_OFF_TIMER)
millis_t Power::power_off_time = 0;
void Power::setPowerOffTimer(const millis_t delay_ms) { power_off_time = millis() + delay_ms; }
#endif
#if ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN)
bool Power::power_off_on_cooldown = false;
void Power::setPowerOffOnCooldown(const bool ena) { power_off_on_cooldown = ena; }
#endif
void Power::cancelAutoPowerOff() {
TERN_(POWER_OFF_TIMER, power_off_time = 0);
TERN_(POWER_OFF_WAIT_FOR_COOLDOWN, power_off_on_cooldown = false);
}
void Power::checkAutoPowerOff() {
if (TERN1(POWER_OFF_TIMER, !power_off_time) && TERN1(POWER_OFF_WAIT_FOR_COOLDOWN, !power_off_on_cooldown)) return;
if (TERN0(POWER_OFF_WAIT_FOR_COOLDOWN, power_off_on_cooldown && is_cooling_needed())) return;
if (TERN0(POWER_OFF_TIMER, power_off_time && PENDING(millis(), power_off_time))) return;
power_off();
}
#endif // POWER_OFF_TIMER || POWER_OFF_WAIT_FOR_COOLDOWN
#if ENABLED(AUTO_POWER_CONTROL)
@@ -149,19 +208,7 @@ void Power::power_off() {
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;
return is_cooling_needed();
}
/**
@@ -193,7 +240,6 @@ void Power::power_off() {
/**
* 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);

View File

@@ -25,7 +25,7 @@
* power.h - power control
*/
#if ENABLED(AUTO_POWER_CONTROL)
#if EITHER(AUTO_POWER_CONTROL, POWER_OFF_TIMER)
#include "../core/millis_t.h"
#endif
@@ -37,20 +37,36 @@ class Power {
static void power_on();
static void power_off();
#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 EITHER(POWER_OFF_TIMER, POWER_OFF_WAIT_FOR_COOLDOWN)
#if ENABLED(POWER_OFF_TIMER)
static millis_t power_off_time;
static void setPowerOffTimer(const millis_t delay_ms);
#endif
#if ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN)
static bool power_off_on_cooldown;
static void setPowerOffOnCooldown(const bool ena);
#endif
static void cancelAutoPowerOff();
static void checkAutoPowerOff();
#endif
#if ENABLED(AUTO_POWER_CONTROL)
static void check(const bool pause);
#if ENABLED(AUTO_POWER_CONTROL) && POWER_OFF_DELAY > 0
static void power_off_soon();
#else
static void power_off_soon() { power_off(); }
#endif
private:
static millis_t lastPowerOn;
static bool is_power_needed();
#if ENABLED(AUTO_POWER_CONTROL)
static void check(const bool pause);
#endif
private:
static millis_t lastPowerOn;
static bool is_power_needed();
static bool is_cooling_needed();
#elif ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN)
private:
static bool is_cooling_needed();
#endif
};
extern Power powerManager;

View File

@@ -26,7 +26,7 @@
#include "power_monitor.h"
#if HAS_LCD_MENU
#if HAS_MARLINUI_MENU
#include "../lcd/marlinui.h"
#include "../lcd/lcdprint.h"
#endif
@@ -53,7 +53,7 @@ PowerMonitor power_monitor; // Single instance - this calls the constructor
void PowerMonitor::draw_current() {
const float amps = getAmps();
lcd_put_u8str(amps < 100 ? ftostr31ns(amps) : ui16tostr4rj((uint16_t)amps));
lcd_put_wchar('A');
lcd_put_lchar('A');
}
#endif
@@ -61,7 +61,7 @@ PowerMonitor power_monitor; // Single instance - this calls the constructor
void PowerMonitor::draw_voltage() {
const float volts = getVolts();
lcd_put_u8str(volts < 100 ? ftostr31ns(volts) : ui16tostr4rj((uint16_t)volts));
lcd_put_wchar('V');
lcd_put_lchar('V');
}
#endif
@@ -69,7 +69,7 @@ PowerMonitor power_monitor; // Single instance - this calls the constructor
void PowerMonitor::draw_power() {
const float power = getPower();
lcd_put_u8str(power < 100 ? ftostr31ns(power) : ui16tostr4rj((uint16_t)power));
lcd_put_wchar('W');
lcd_put_lchar('W');
}
#endif

View File

@@ -32,7 +32,7 @@ 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);
filter_buf += (uint32_t(sample) << K_SCALE) - (filter_buf >> K_VALUE);
}
void capture() {
value = filter_buf * (SCALE * (1.0f / (1UL << (PM_K_VALUE + PM_K_SCALE))));

View File

@@ -54,6 +54,10 @@ uint32_t PrintJobRecovery::cmd_sdpos, // = 0
#include "../module/temperature.h"
#include "../core/serial.h"
#if HOMING_Z_WITH_PROBE
#include "../module/probe.h"
#endif
#if ENABLED(FWRETRACT)
#include "fwretract.h"
#endif
@@ -104,13 +108,18 @@ void PrintJobRecovery::changed() {
*
* If a saved state exists send 'M1000 S' to initiate job recovery.
*/
void PrintJobRecovery::check() {
bool PrintJobRecovery::check() {
//if (!card.isMounted()) card.mount();
bool success = false;
if (card.isMounted()) {
load();
if (!valid()) return cancel();
queue.inject_P(PSTR("M1000S"));
success = valid();
if (!success)
cancel();
else
queue.inject(F("M1000S"));
}
return success;
}
/**
@@ -130,7 +139,7 @@ void PrintJobRecovery::load() {
(void)file.read(&info, sizeof(info));
close();
}
debug(PSTR("Load"));
debug(F("Load"));
}
/**
@@ -178,7 +187,8 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW
info.valid_foot = info.valid_head;
// Machine state
info.current_position = current_position;
// info.sdpos and info.current_position are pre-filled from the Stepper ISR
info.feedrate = uint16_t(MMS_TO_MMM(feedrate_mm_s));
info.zraise = zraise;
info.flag.raised = raised; // Was Z raised before power-off?
@@ -191,7 +201,7 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW
#if DISABLED(NO_VOLUMETRICS)
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];
EXTRUDER_LOOP() info.filament_size[e] = planner.filament_size[e];
#else
if (parser.volumetric_enabled) info.filament_size[0] = planner.filament_size[active_extruder];
#endif
@@ -244,7 +254,7 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW
#if POWER_LOSS_RETRACT_LEN
// Retract filament now
gcode.process_subcommands_now_P(PSTR("G1 F3000 E-" STRINGIFY(POWER_LOSS_RETRACT_LEN)));
gcode.process_subcommands_now(F("G1 F3000 E-" STRINGIFY(POWER_LOSS_RETRACT_LEN)));
#endif
#if POWER_LOSS_ZRAISE
@@ -265,6 +275,10 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW
#endif
#endif // POWER_LOSS_PIN
#if PIN_EXISTS(POWER_LOSS) || ENABLED(DEBUG_POWER_LOSS_RECOVERY)
/**
* An outage was detected by a sensor pin.
* - If not SD printing, let the machine turn off on its own with no "KILL" screen
@@ -273,7 +287,7 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW
* - If backup power is available Retract E and Raise Z
* - Go to the KILL screen
*/
void PrintJobRecovery::_outage() {
void PrintJobRecovery::_outage(TERN_(DEBUG_POWER_LOSS_RECOVERY, const bool simulated/*=false*/)) {
#if ENABLED(BACKUP_POWER_SUPPLY)
static bool lock = false;
if (lock) return; // No re-entrance from idle() during retract_and_lift()
@@ -301,17 +315,23 @@ void PrintJobRecovery::save(const bool force/*=false*/, const float zraise/*=POW
retract_and_lift(zraise);
#endif
kill(GET_TEXT(MSG_OUTAGE_RECOVERY));
if (TERN0(DEBUG_POWER_LOSS_RECOVERY, simulated)) {
card.fileHasFinished();
current_position.reset();
sync_plan_position();
}
else
kill(GET_TEXT_F(MSG_OUTAGE_RECOVERY));
}
#endif
#endif // POWER_LOSS_PIN || DEBUG_POWER_LOSS_RECOVERY
/**
* Save the recovery info the recovery file
*/
void PrintJobRecovery::write() {
debug(PSTR("Write"));
debug(F("Write"));
open(false);
file.seekSet(0);
@@ -337,7 +357,7 @@ void PrintJobRecovery::resume() {
#if HAS_LEVELING
// Make sure leveling is off before any G92 and G28
gcode.process_subcommands_now_P(PSTR("M420 S0 Z0"));
gcode.process_subcommands_now(F("M420 S0 Z0"));
#endif
#if HAS_HEATED_BED
@@ -373,7 +393,7 @@ void PrintJobRecovery::resume() {
// establish the current position as best we can.
//
gcode.process_subcommands_now_P(PSTR("G92.9E0")); // Reset E to 0
gcode.process_subcommands_now(F("G92.9E0")); // Reset E to 0
#if Z_HOME_TO_MAX
@@ -390,14 +410,12 @@ void PrintJobRecovery::resume() {
#if ENABLED(POWER_LOSS_RECOVER_ZHOME) && defined(POWER_LOSS_ZHOME_POS)
#define HOMING_Z_DOWN 1
#else
#define HOME_XY_ONLY 1
#endif
float z_now = info.flag.raised ? z_raised : z_print;
// Reset E to 0 and set Z to the real position
#if HOME_XY_ONLY
#if !HOMING_Z_DOWN
// Set Z to the real position
sprintf_P(cmd, PSTR("G92.9Z%s"), dtostrf(z_now, 1, 3, str_1));
gcode.process_subcommands_now(cmd);
#endif
@@ -409,15 +427,15 @@ void PrintJobRecovery::resume() {
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
// Home XY with no Z raise
gcode.process_subcommands_now(F("G28R0XY")); // 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));
const xy_pos_t p = xy_pos_t(POWER_LOSS_ZHOME_POS) TERN_(HOMING_Z_WITH_PROBE, - probe.offset_xy);
sprintf_P(cmd, PSTR("G1X%sY%sF1000\nG28HZ"), dtostrf(p.x, 1, 3, str_1), dtostrf(p.y, 1, 3, str_2));
gcode.process_subcommands_now(cmd);
#endif
@@ -431,7 +449,7 @@ void PrintJobRecovery::resume() {
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
#if !HOMING_Z_DOWN
// 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);
@@ -448,7 +466,7 @@ void PrintJobRecovery::resume() {
// Recover volumetric extrusion state
#if DISABLED(NO_VOLUMETRICS)
#if HAS_MULTI_EXTRUDER
for (int8_t e = 0; e < EXTRUDERS; e++) {
EXTRUDER_LOOP() {
sprintf_P(cmd, PSTR("M200T%iD%s"), e, dtostrf(info.filament_size[e], 1, 3, str_1));
gcode.process_subcommands_now(cmd);
}
@@ -498,10 +516,10 @@ void PrintJobRecovery::resume() {
// Restore retract and hop state from an active `G10` command
#if ENABLED(FWRETRACT)
LOOP_L_N(e, EXTRUDERS) {
EXTRUDER_LOOP() {
if (info.retract[e] != 0.0) {
fwretract.current_retract[e] = info.retract[e];
fwretract.retracted[e] = true;
fwretract.retracted.set(e);
}
}
fwretract.current_hop = info.retract_hop;
@@ -513,17 +531,17 @@ void PrintJobRecovery::resume() {
// 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"));
gcode.process_subcommands_now(F("G1F3000E" STRINGIFY(POWER_LOSS_RETRACT_LEN)));
#endif
// 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));
sprintf_P(cmd, PSTR("G1F3000E%d"), (POWER_LOSS_PURGE_LEN) + (POWER_LOSS_RETRACT_LEN));
gcode.process_subcommands_now(cmd);
#endif
#if ENABLED(NOZZLE_CLEAN_FEATURE)
gcode.process_subcommands_now_P(PSTR("G12"));
gcode.process_subcommands_now(F("G12"));
#endif
// Move back over to the saved XY
@@ -549,7 +567,7 @@ void PrintJobRecovery::resume() {
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);
LOOP_NUM_AXES(i) update_workspace_offset((AxisEnum)i);
#endif
// Relative axis modes
@@ -575,8 +593,8 @@ void PrintJobRecovery::resume() {
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
void PrintJobRecovery::debug(PGM_P const prefix) {
DEBUG_ECHOPGM_P(prefix);
void PrintJobRecovery::debug(FSTR_P const prefix) {
DEBUG_ECHOF(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) {
@@ -599,7 +617,7 @@ void PrintJobRecovery::resume() {
#if HAS_HOME_OFFSET
DEBUG_ECHOPGM("home_offset: ");
LOOP_LINEAR_AXES(i) {
LOOP_NUM_AXES(i) {
if (i) DEBUG_CHAR(',');
DEBUG_DECIMAL(info.home_offset[i]);
}
@@ -608,7 +626,7 @@ void PrintJobRecovery::resume() {
#if HAS_POSITION_SHIFT
DEBUG_ECHOPGM("position_shift: ");
LOOP_LINEAR_AXES(i) {
LOOP_NUM_AXES(i) {
if (i) DEBUG_CHAR(',');
DEBUG_DECIMAL(info.position_shift[i]);
}
@@ -621,7 +639,7 @@ void PrintJobRecovery::resume() {
#if DISABLED(NO_VOLUMETRICS)
DEBUG_ECHOPGM("filament_size:");
LOOP_L_N(i, EXTRUDERS) DEBUG_ECHOLNPGM(" ", info.filament_size[i]);
EXTRUDER_LOOP() DEBUG_ECHOLNPGM(" ", info.filament_size[e]);
DEBUG_EOL();
#endif
@@ -653,7 +671,7 @@ void PrintJobRecovery::resume() {
#if ENABLED(FWRETRACT)
DEBUG_ECHOPGM("retract: ");
for (int8_t e = 0; e < EXTRUDERS; e++) {
EXTRUDER_LOOP() {
DEBUG_ECHO(info.retract[e]);
if (e < EXTRUDERS - 1) DEBUG_CHAR(',');
}

View File

@@ -152,7 +152,7 @@ class PrintJobRecovery {
static void init();
static void prepare();
static inline void setup() {
static void setup() {
#if PIN_EXISTS(POWER_LOSS)
#if ENABLED(POWER_LOSS_PULLUP)
SET_INPUT_PULLUP(POWER_LOSS_PIN);
@@ -165,28 +165,28 @@ class PrintJobRecovery {
}
// Track each command's file offsets
static inline uint32_t command_sdpos() { return sdpos[queue_index_r]; }
static inline void commit_sdpos(const uint8_t index_w) { sdpos[index_w] = cmd_sdpos; }
static uint32_t command_sdpos() { return sdpos[queue_index_r]; }
static void commit_sdpos(const uint8_t index_w) { sdpos[index_w] = cmd_sdpos; }
static bool enabled;
static void enable(const bool onoff);
static void changed();
static inline bool exists() { return card.jobRecoverFileExists(); }
static inline void open(const bool read) { card.openJobRecoveryFile(read); }
static inline void close() { file.close(); }
static bool exists() { return card.jobRecoverFileExists(); }
static void open(const bool read) { card.openJobRecoveryFile(read); }
static void close() { file.close(); }
static void check();
static bool check();
static void resume();
static void purge();
static inline void cancel() { purge(); IF_DISABLED(NO_SD_AUTOSTART, card.autofile_begin()); }
static void cancel() { purge(); }
static void load();
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() {
static void outage() {
static constexpr uint8_t OUTAGE_THRESHOLD = 3;
static uint8_t outage_counter = 0;
if (enabled && READ(POWER_LOSS_PIN) == POWER_LOSS_STATE) {
@@ -199,14 +199,14 @@ class PrintJobRecovery {
#endif
// The referenced file exists
static inline bool interrupted_file_exists() { return card.fileExists(info.sd_filename); }
static bool interrupted_file_exists() { return card.fileExists(info.sd_filename); }
static inline bool valid() { return info.valid() && interrupted_file_exists(); }
static bool valid() { return info.valid() && interrupted_file_exists(); }
#if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
static void debug(PGM_P const prefix);
static void debug(FSTR_P const prefix);
#else
static inline void debug(PGM_P const) {}
static void debug(FSTR_P const) {}
#endif
private:
@@ -216,9 +216,9 @@ class PrintJobRecovery {
static void retract_and_lift(const_float_t zraise);
#endif
#if PIN_EXISTS(POWER_LOSS)
#if PIN_EXISTS(POWER_LOSS) || ENABLED(DEBUG_POWER_LOSS_RECOVERY)
friend class GcodeSuite;
static void _outage();
static void _outage(TERN_(DEBUG_POWER_LOSS_RECOVERY, const bool simulated=false));
#endif
};

View File

@@ -22,36 +22,54 @@
#include "../inc/MarlinConfigPre.h"
#if ENABLED(PROBE_TEMP_COMPENSATION)
#if HAS_PTC
//#define DEBUG_PTC // Print extra debug output with 'M871'
#include "probe_temp_comp.h"
#include <math.h>
#include "../module/temperature.h"
ProbeTempComp temp_comp;
ProbeTempComp ptc;
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(PTC_PROBE)
constexpr int16_t z_offsets_probe_default[PTC_PROBE_COUNT] = PTC_PROBE_ZOFFS;
int16_t ProbeTempComp::z_offsets_probe[PTC_PROBE_COUNT] = PTC_PROBE_ZOFFS;
#endif
#if ENABLED(USE_TEMP_EXT_COMPENSATION)
int16_t ProbeTempComp::z_offsets_ext[cali_info_init[TSI_EXT].measurements]; // = {0}
#if ENABLED(PTC_BED)
constexpr int16_t z_offsets_bed_default[PTC_BED_COUNT] = PTC_BED_ZOFFS;
int16_t ProbeTempComp::z_offsets_bed[PTC_BED_COUNT] = PTC_BED_ZOFFS;
#endif
#if ENABLED(PTC_HOTEND)
constexpr int16_t z_offsets_hotend_default[PTC_HOTEND_COUNT] = PTC_HOTEND_ZOFFS;
int16_t ProbeTempComp::z_offsets_hotend[PTC_HOTEND_COUNT] = PTC_HOTEND_ZOFFS;
#endif
int16_t *ProbeTempComp::sensor_z_offsets[TSI_COUNT] = {
ProbeTempComp::z_offsets_probe, ProbeTempComp::z_offsets_bed
OPTARG(USE_TEMP_EXT_COMPENSATION, ProbeTempComp::z_offsets_ext)
#if ENABLED(PTC_PROBE)
ProbeTempComp::z_offsets_probe,
#endif
#if ENABLED(PTC_BED)
ProbeTempComp::z_offsets_bed,
#endif
#if ENABLED(PTC_HOTEND)
ProbeTempComp::z_offsets_hotend,
#endif
};
const temp_calib_t ProbeTempComp::cali_info[TSI_COUNT] = {
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;
constexpr temp_calib_t ProbeTempComp::cali_info[TSI_COUNT];
uint8_t ProbeTempComp::calib_idx; // = 0
float ProbeTempComp::init_measurement; // = 0.0
bool ProbeTempComp::enabled = true;
void ProbeTempComp::reset() {
TERN_(PTC_PROBE, LOOP_L_N(i, PTC_PROBE_COUNT) z_offsets_probe[i] = z_offsets_probe_default[i]);
TERN_(PTC_BED, LOOP_L_N(i, PTC_BED_COUNT) z_offsets_bed[i] = z_offsets_bed_default[i]);
TERN_(PTC_HOTEND, LOOP_L_N(i, PTC_HOTEND_COUNT) z_offsets_hotend[i] = z_offsets_hotend_default[i]);
}
void ProbeTempComp::clear_offsets(const TempSensorID tsi) {
LOOP_L_N(i, cali_info[tsi].measurements)
@@ -69,19 +87,26 @@ void ProbeTempComp::print_offsets() {
LOOP_L_N(s, TSI_COUNT) {
celsius_t temp = cali_info[s].start_temp;
for (int16_t i = -1; i < cali_info[s].measurements; ++i) {
SERIAL_ECHOPGM_P(s == TSI_BED ? PSTR("Bed") :
#if ENABLED(USE_TEMP_EXT_COMPENSATION)
s == TSI_EXT ? PSTR("Extruder") :
#endif
PSTR("Probe")
SERIAL_ECHOF(
TERN_(PTC_BED, s == TSI_BED ? F("Bed") :)
TERN_(PTC_HOTEND, s == TSI_EXT ? F("Extruder") :)
F("Probe")
);
SERIAL_ECHOLNPGM(
" temp: ", temp,
"C; Offset: ", i < 0 ? 0.0f : sensor_z_offsets[s][i], " um"
);
temp += cali_info[s].temp_res;
temp += cali_info[s].temp_resolution;
}
}
#if ENABLED(DEBUG_PTC)
float meas[4] = { 0, 0, 0, 0 };
compensate_measurement(TSI_PROBE, 27.5, meas[0]);
compensate_measurement(TSI_PROBE, 32.5, meas[1]);
compensate_measurement(TSI_PROBE, 77.5, meas[2]);
compensate_measurement(TSI_PROBE, 82.5, meas[3]);
SERIAL_ECHOLNPGM("DEBUG_PTC 27.5:", meas[0], " 32.5:", meas[1], " 77.5:", meas[2], " 82.5:", meas[3]);
#endif
}
void ProbeTempComp::prepare_new_calibration(const_float_t init_meas_z) {
@@ -90,28 +115,20 @@ void ProbeTempComp::prepare_new_calibration(const_float_t init_meas_z) {
}
void ProbeTempComp::push_back_new_measurement(const TempSensorID tsi, const_float_t meas_z) {
switch (tsi) {
case TSI_PROBE:
case TSI_BED:
//case TSI_EXT:
if (calib_idx >= cali_info[tsi].measurements) return;
sensor_z_offsets[tsi][calib_idx++] = static_cast<int16_t>(meas_z * 1000.0f - init_measurement * 1000.0f);
default: break;
}
if (calib_idx >= cali_info[tsi].measurements) return;
sensor_z_offsets[tsi][calib_idx++] = static_cast<int16_t>((meas_z - init_measurement) * 1000.0f);
}
bool ProbeTempComp::finish_calibration(const TempSensorID tsi) {
if (tsi != TSI_PROBE && tsi != TSI_BED) return false;
if (calib_idx < 3) {
SERIAL_ECHOLNPGM("!Insufficient measurements (min. 3).");
if (!calib_idx) {
SERIAL_ECHOLNPGM("!No measurements.");
clear_offsets(tsi);
return false;
}
const uint8_t measurements = cali_info[tsi].measurements;
const celsius_t start_temp = cali_info[tsi].start_temp,
res_temp = cali_info[tsi].temp_res;
res_temp = cali_info[tsi].temp_resolution;
int16_t * const data = sensor_z_offsets[tsi];
// Extrapolate
@@ -120,16 +137,15 @@ bool ProbeTempComp::finish_calibration(const TempSensorID tsi) {
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 celsius_float_t temp = start_temp + float(calib_idx) * res_temp;
const celsius_float_t temp = start_temp + float(calib_idx + 1) * res_temp;
data[calib_idx] = static_cast<int16_t>(k * temp + d);
}
}
else {
// Simply use the last measured value for higher temperatures
SERIAL_ECHOPGM("Failed to extrapolate");
const int16_t last_val = data[calib_idx];
const int16_t last_val = data[calib_idx-1];
for (; calib_idx < measurements; ++calib_idx)
data[calib_idx] = last_val;
}
@@ -147,7 +163,7 @@ bool ProbeTempComp::finish_calibration(const TempSensorID tsi) {
// Restrict the max. offset difference between two probings
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);
clear_offsets(tsi);
return false;
}
}
@@ -155,56 +171,60 @@ bool ProbeTempComp::finish_calibration(const TempSensorID tsi) {
return true;
}
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);
void ProbeTempComp::apply_compensation(float &meas_z) {
if (!enabled) return;
TERN_(PTC_BED, compensate_measurement(TSI_BED, thermalManager.degBed(), meas_z));
TERN_(PTC_PROBE, compensate_measurement(TSI_PROBE, thermalManager.degProbe(), meas_z));
TERN_(PTC_HOTEND, compensate_measurement(TSI_EXT, thermalManager.degHotend(0), meas_z));
}
float ProbeTempComp::get_offset_for_temperature(const TempSensorID tsi, const celsius_t temp) {
void ProbeTempComp::compensate_measurement(const TempSensorID tsi, const celsius_t temp, float &meas_z) {
const uint8_t measurements = cali_info[tsi].measurements;
const celsius_t start_temp = cali_info[tsi].start_temp,
res_temp = cali_info[tsi].temp_res;
res_temp = cali_info[tsi].temp_resolution,
end_temp = start_temp + measurements * res_temp;
const int16_t * const data = sensor_z_offsets[tsi];
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]) });
// Given a data index, return { celsius, zoffset } in the form { x, y }
auto tpoint = [&](uint8_t i) -> xy_float_t {
return xy_float_t({ static_cast<float>(start_temp) + i * res_temp, i ? static_cast<float>(data[i - 1]) : 0.0f });
};
// Interpolate Z based on a temperature being within a given range
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;
// zoffs1 + zoffset_per_toffset * toffset
return p1.y + (p2.y - p1.y) / (p2.x - p1.x) * (x - p1.x);
};
// Linear interpolation
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]);
#if PTC_LINEAR_EXTRAPOLATION
if (temp < start_temp)
offset = linear_interp(temp, tpoint(0), tpoint(PTC_LINEAR_EXTRAPOLATION));
else if (temp >= end_temp)
offset = linear_interp(temp, tpoint(measurements - PTC_LINEAR_EXTRAPOLATION), tpoint(measurements));
#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));
if (temp < start_temp)
offset = 0.0f;
else if (temp >= end_temp)
offset = static_cast<float>(data[measurements - 1]);
#endif
else
offset = linear_interp(temp, point(idx), point(idx + 1));
else {
// Linear interpolation
const int8_t idx = static_cast<int8_t>((temp - start_temp) / res_temp);
offset = linear_interp(temp, tpoint(idx), tpoint(idx + 1));
}
// return offset in mm
return offset / 1000.0f;
// convert offset to mm and apply it
meas_z -= offset / 1000.0f;
}
bool ProbeTempComp::linear_regression(const TempSensorID tsi, float &k, float &d) {
if (tsi != TSI_PROBE && tsi != TSI_BED) return false;
if (!WITHIN(calib_idx, 2, cali_info[tsi].measurements)) return false;
if (!WITHIN(calib_idx, 1, cali_info[tsi].measurements)) return false;
const celsius_t start_temp = cali_info[tsi].start_temp,
res_temp = cali_info[tsi].temp_res;
res_temp = cali_info[tsi].temp_resolution;
const int16_t * const data = sensor_z_offsets[tsi];
float sum_x = start_temp,
@@ -234,4 +254,4 @@ bool ProbeTempComp::linear_regression(const TempSensorID tsi, float &k, float &d
return true;
}
#endif // PROBE_TEMP_COMPENSATION
#endif // HAS_PTC

View File

@@ -24,19 +24,22 @@
#include "../inc/MarlinConfig.h"
enum TempSensorID : uint8_t {
TSI_PROBE,
TSI_BED,
#if ENABLED(USE_TEMP_EXT_COMPENSATION)
#if ENABLED(PTC_PROBE)
TSI_PROBE,
#endif
#if ENABLED(PTC_BED)
TSI_BED,
#endif
#if ENABLED(PTC_HOTEND)
TSI_EXT,
#endif
TSI_COUNT
};
typedef struct {
uint8_t measurements; // Max. number of measurements to be stored (35 - 80°C)
celsius_t temp_res, // Resolution in °C between measurements
start_temp, // Base measurement; z-offset == 0
end_temp;
uint8_t measurements; // Max. number of measurements to be stored (35 - 80°C)
celsius_t temp_resolution, // Resolution in °C between measurements
start_temp; // Base measurement; z-offset == 0
} temp_calib_t;
/**
@@ -45,89 +48,55 @@ typedef struct {
* 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 const temp_calib_t cali_info[TSI_COUNT];
static constexpr temp_calib_t cali_info[TSI_COUNT] = {
#if ENABLED(PTC_PROBE)
{ PTC_PROBE_COUNT, PTC_PROBE_RES, PTC_PROBE_START }, // Probe
#endif
#if ENABLED(PTC_BED)
{ PTC_BED_COUNT, PTC_BED_RES, PTC_BED_START }, // Bed
#endif
#if ENABLED(PTC_HOTEND)
{ PTC_HOTEND_COUNT, PTC_HOTEND_RES, PTC_HOTEND_START }, // Extruder
#endif
};
// Where to park nozzle to wait for probe cooldown
static constexpr xyz_pos_t park_point = PTC_PARK_POS;
// 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)
z_offsets_bed[cali_info_init[TSI_BED].measurements]; // (µm)
#if ENABLED(USE_TEMP_EXT_COMPENSATION)
static int16_t z_offsets_ext[cali_info_init[TSI_EXT].measurements]; // (µm)
static int16_t *sensor_z_offsets[TSI_COUNT];
#if ENABLED(PTC_PROBE)
static int16_t z_offsets_probe[PTC_PROBE_COUNT]; // (µm)
#endif
#if ENABLED(PTC_BED)
static int16_t z_offsets_bed[PTC_BED_COUNT]; // (µm)
#endif
#if ENABLED(PTC_HOTEND)
static int16_t z_offsets_hotend[PTC_HOTEND_COUNT]; // (µm)
#endif
static inline void reset_index() { calib_idx = 0; };
static inline uint8_t get_index() { return calib_idx; }
static void clear_offsets(const TempSensorID tsi);
static inline void clear_all_offsets() {
clear_offsets(TSI_BED);
clear_offsets(TSI_PROBE);
TERN_(USE_TEMP_EXT_COMPENSATION, clear_offsets(TSI_EXT));
static void reset_index() { calib_idx = 0; };
static uint8_t get_index() { return calib_idx; }
static void reset();
static void clear_all_offsets() {
TERN_(PTC_PROBE, clear_offsets(TSI_PROBE));
TERN_(PTC_BED, clear_offsets(TSI_BED));
TERN_(PTC_HOTEND, 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_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 celsius_t temp, float &meas_z);
static void set_enabled(const bool ena) { enabled = ena; }
// Apply all temperature compensation adjustments
static void apply_compensation(float &meas_z);
private:
static uint8_t calib_idx;
static bool enabled;
static void clear_offsets(const TempSensorID tsi);
/**
* Base value. Temperature compensation values will be deltas
@@ -135,13 +104,13 @@ class ProbeTempComp {
*/
static float init_measurement;
static float get_offset_for_temperature(const TempSensorID tsi, const celsius_t temp);
/**
* Fit a linear function in measured temperature offsets
* to allow generating values of higher temperatures.
*/
static bool linear_regression(const TempSensorID tsi, float &k, float &d);
static void compensate_measurement(const TempSensorID tsi, const celsius_t temp, float &meas_z);
};
extern ProbeTempComp temp_comp;
extern ProbeTempComp ptc;

View File

@@ -38,8 +38,8 @@ 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() {
static void reset() { index = 0; }
static bool is_active() {
LOOP_L_N(i, index) if (marker[i].counter) return true;
return false;
}

View File

@@ -68,8 +68,8 @@ bool FilamentMonitorBase::enabled = true,
#if ENABLED(EXTENSIBLE_UI)
#include "../lcd/extui/ui_api.h"
#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
#include "../lcd/e3v2/enhanced/dwin.h"
#elif ENABLED(DWIN_LCD_PROUI)
#include "../lcd/e3v2/proui/dwin.h"
#endif
void event_filament_runout(const uint8_t extruder) {
@@ -88,7 +88,7 @@ void event_filament_runout(const uint8_t extruder) {
#endif
TERN_(EXTENSIBLE_UI, ExtUI::onFilamentRunout(ExtUI::getTool(extruder)));
TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_FilamentRunout(extruder));
TERN_(DWIN_LCD_PROUI, DWIN_FilamentRunout(extruder));
#if ANY(HOST_PROMPT_SUPPORT, HOST_ACTION_COMMANDS, MULTI_FILAMENT_SENSOR)
const char tool = '0' + TERN0(MULTI_FILAMENT_SENSOR, extruder);
@@ -96,8 +96,7 @@ void event_filament_runout(const uint8_t extruder) {
//action:out_of_filament
#if ENABLED(HOST_PROMPT_SUPPORT)
host_action_prompt_begin(PROMPT_FILAMENT_RUNOUT, PSTR("FilamentRunout T"), tool);
host_action_prompt_show();
hostui.prompt_do(PROMPT_FILAMENT_RUNOUT, F("FilamentRunout T"), tool); //action:out_of_filament
#endif
const bool run_runout_script = !runout.host_handling;
@@ -109,18 +108,18 @@ void event_filament_runout(const uint8_t extruder) {
|| TERN0(ADVANCED_PAUSE_FEATURE, strstr(FILAMENT_RUNOUT_SCRIPT, "M25"))
)
) {
host_action_paused(false);
hostui.paused(false);
}
else {
// Legacy Repetier command for use until newer version supports standard dialog
// To be removed later when pause command also triggers dialog
#ifdef ACTION_ON_FILAMENT_RUNOUT
host_action(PSTR(ACTION_ON_FILAMENT_RUNOUT " T"), false);
hostui.action(F(ACTION_ON_FILAMENT_RUNOUT " T"), false);
SERIAL_CHAR(tool);
SERIAL_EOL();
#endif
host_action_pause(false);
hostui.pause(false);
}
SERIAL_ECHOPGM(" " ACTION_REASON_ON_FILAMENT_RUNOUT " ");
SERIAL_CHAR(tool);
@@ -140,7 +139,7 @@ void event_filament_runout(const uint8_t extruder) {
SERIAL_ECHOPGM("Runout Command: ");
SERIAL_ECHOLNPGM(FILAMENT_RUNOUT_SCRIPT);
#endif
queue.inject_P(PSTR(FILAMENT_RUNOUT_SCRIPT));
queue.inject(F(FILAMENT_RUNOUT_SCRIPT));
#endif
}
}

View File

@@ -83,30 +83,30 @@ class TFilamentMonitor : public FilamentMonitorBase {
static sensor_t sensor;
public:
static inline void setup() {
static void setup() {
sensor.setup();
reset();
}
static inline void reset() {
static void reset() {
filament_ran_out = false;
response.reset();
}
// Call this method when filament is present,
// so the response can reset its counter.
static inline void filament_present(const uint8_t extruder) {
static void filament_present(const uint8_t extruder) {
response.filament_present(extruder);
}
#if HAS_FILAMENT_RUNOUT_DISTANCE
static inline float& runout_distance() { return response.runout_distance_mm; }
static inline void set_runout_distance(const_float_t mm) { response.runout_distance_mm = mm; }
static float& runout_distance() { return response.runout_distance_mm; }
static void set_runout_distance(const_float_t mm) { response.runout_distance_mm = mm; }
#endif
// Handle a block completion. RunoutResponseDelayed uses this to
// add up the length of filament moved while the filament is out.
static inline void block_completed(const block_t * const b) {
static void block_completed(const block_t * const b) {
if (enabled) {
response.block_completed(b);
sensor.block_completed(b);
@@ -114,7 +114,7 @@ class TFilamentMonitor : public FilamentMonitorBase {
}
// Give the response a chance to update its counter.
static inline void run() {
static void run() {
if (enabled && !filament_ran_out && (printingIsActive() || did_pause_print)) {
TERN_(HAS_FILAMENT_RUNOUT_DISTANCE, cli()); // Prevent RunoutResponseDelayed::block_completed from accumulating here
response.run();
@@ -168,12 +168,12 @@ class FilamentSensorBase {
* Called by FilamentSensorSwitch::run when filament is detected.
* Called by FilamentSensorEncoder::block_completed when motion is detected.
*/
static inline void filament_present(const uint8_t extruder) {
static void filament_present(const uint8_t extruder) {
runout.filament_present(extruder); // ...which calls response.filament_present(extruder)
}
public:
static inline void setup() {
static void setup() {
#define _INIT_RUNOUT_PIN(P,S,U,D) do{ if (ENABLED(U)) SET_INPUT_PULLUP(P); else if (ENABLED(D)) SET_INPUT_PULLDOWN(P); else SET_INPUT(P); }while(0)
#define INIT_RUNOUT_PIN(N) _INIT_RUNOUT_PIN(FIL_RUNOUT##N##_PIN, FIL_RUNOUT##N##_STATE, FIL_RUNOUT##N##_PULLUP, FIL_RUNOUT##N##_PULLDOWN)
#if NUM_RUNOUT_SENSORS >= 1
@@ -205,14 +205,14 @@ class FilamentSensorBase {
}
// Return a bitmask of runout pin states
static inline uint8_t poll_runout_pins() {
static uint8_t poll_runout_pins() {
#define _OR_RUNOUT(N) | (READ(FIL_RUNOUT##N##_PIN) ? _BV((N) - 1) : 0)
return (0 REPEAT_1(NUM_RUNOUT_SENSORS, _OR_RUNOUT));
#undef _OR_RUNOUT
}
// Return a bitmask of runout flag states (1 bits always indicates runout)
static inline uint8_t poll_runout_states() {
static uint8_t poll_runout_states() {
return poll_runout_pins() ^ uint8_t(0
#if NUM_RUNOUT_SENSORS >= 1
| (FIL_RUNOUT1_STATE ? 0 : _BV(1 - 1))
@@ -254,7 +254,7 @@ class FilamentSensorBase {
private:
static uint8_t motion_detected;
static inline void poll_motion_sensor() {
static void poll_motion_sensor() {
static uint8_t old_state;
const uint8_t new_state = poll_runout_pins(),
change = old_state ^ new_state;
@@ -273,7 +273,7 @@ class FilamentSensorBase {
}
public:
static inline void block_completed(const block_t * const b) {
static void block_completed(const block_t * const b) {
// If the sensor wheel has moved since the last call to
// this method reset the runout counter for the extruder.
if (TEST(motion_detected, b->extruder))
@@ -283,7 +283,7 @@ class FilamentSensorBase {
motion_detected = 0;
}
static inline void run() { poll_motion_sensor(); }
static void run() { poll_motion_sensor(); }
};
#else
@@ -294,7 +294,7 @@ class FilamentSensorBase {
*/
class FilamentSensorSwitch : public FilamentSensorBase {
private:
static inline bool poll_runout_state(const uint8_t extruder) {
static bool poll_runout_state(const uint8_t extruder) {
const uint8_t runout_states = poll_runout_states();
#if MULTI_FILAMENT_SENSOR
if ( !TERN0(DUAL_X_CARRIAGE, idex_is_duplicating())
@@ -307,9 +307,9 @@ class FilamentSensorBase {
}
public:
static inline void block_completed(const block_t * const) {}
static void block_completed(const block_t * const) {}
static inline void run() {
static void run() {
LOOP_L_N(s, NUM_RUNOUT_SENSORS) {
const bool out = poll_runout_state(s);
if (!out) filament_present(s);
@@ -317,7 +317,7 @@ class FilamentSensorBase {
static uint8_t was_out; // = 0
if (out != TEST(was_out, s)) {
TBI(was_out, s);
SERIAL_ECHOLNPGM_P(PSTR("Filament Sensor "), '0' + s, out ? PSTR(" OUT") : PSTR(" IN"));
SERIAL_ECHOLNF(F("Filament Sensor "), AS_DIGIT(s), out ? F(" OUT") : F(" IN"));
}
#endif
}
@@ -341,34 +341,34 @@ class FilamentSensorBase {
public:
static float runout_distance_mm;
static inline void reset() {
static void reset() {
LOOP_L_N(i, NUM_RUNOUT_SENSORS) filament_present(i);
}
static inline void run() {
static void run() {
#if ENABLED(FILAMENT_RUNOUT_SENSOR_DEBUG)
static millis_t t = 0;
const millis_t ms = millis();
if (ELAPSED(ms, t)) {
t = millis() + 1000UL;
LOOP_L_N(i, NUM_RUNOUT_SENSORS)
SERIAL_ECHOPGM_P(i ? PSTR(", ") : PSTR("Remaining mm: "), runout_mm_countdown[i]);
SERIAL_ECHOF(i ? F(", ") : F("Remaining mm: "), runout_mm_countdown[i]);
SERIAL_EOL();
}
#endif
}
static inline uint8_t has_run_out() {
static uint8_t has_run_out() {
uint8_t runout_flags = 0;
LOOP_L_N(i, NUM_RUNOUT_SENSORS) if (runout_mm_countdown[i] < 0) SBI(runout_flags, i);
return runout_flags;
}
static inline void filament_present(const uint8_t extruder) {
static void filament_present(const uint8_t extruder) {
runout_mm_countdown[extruder] = runout_distance_mm;
}
static inline void block_completed(const block_t * const b) {
static void block_completed(const block_t * const b) {
if (b->steps.x || b->steps.y || b->steps.z || did_pause_print) { // Allow pause purge move to re-trigger runout state
// Only trigger on extrusion with XYZ movement to allow filament change and retract/recover.
const uint8_t e = b->extruder;
@@ -389,23 +389,23 @@ class FilamentSensorBase {
static int8_t runout_count[NUM_RUNOUT_SENSORS];
public:
static inline void reset() {
static void reset() {
LOOP_L_N(i, NUM_RUNOUT_SENSORS) filament_present(i);
}
static inline void run() {
static void run() {
LOOP_L_N(i, NUM_RUNOUT_SENSORS) if (runout_count[i] >= 0) runout_count[i]--;
}
static inline uint8_t has_run_out() {
static uint8_t has_run_out() {
uint8_t runout_flags = 0;
LOOP_L_N(i, NUM_RUNOUT_SENSORS) if (runout_count[i] < 0) SBI(runout_flags, i);
return runout_flags;
}
static inline void block_completed(const block_t * const) { }
static void block_completed(const block_t * const) { }
static inline void filament_present(const uint8_t extruder) {
static void filament_present(const uint8_t extruder) {
runout_count[extruder] = runout_threshold;
}
};

View File

@@ -27,65 +27,29 @@
#include "solenoid.h"
#include "../module/motion.h" // for active_extruder
// PARKING_EXTRUDER options alter the default behavior of solenoids, this ensures compliance of M380-381
#if ENABLED(PARKING_EXTRUDER)
#include "../module/tool_change.h"
#endif
#define HAS_SOLENOID(N) (HAS_SOLENOID_##N && (ENABLED(MANUAL_SOLENOID_CONTROL) || N < EXTRUDERS))
#include "../module/tool_change.h"
// Used primarily with MANUAL_SOLENOID_CONTROL
static void set_solenoid(const uint8_t num, const bool active) {
const uint8_t value = active ? PE_MAGNET_ON_STATE : !PE_MAGNET_ON_STATE;
static void set_solenoid(const uint8_t num, const uint8_t state) {
#define _SOL_CASE(N) case N: TERN_(HAS_SOLENOID_##N, OUT_WRITE(SOL##N##_PIN, state)); break;
switch (num) {
case 0: OUT_WRITE(SOL0_PIN, value); break;
#if HAS_SOLENOID(1)
case 1: OUT_WRITE(SOL1_PIN, value); break;
#endif
#if HAS_SOLENOID(2)
case 2: OUT_WRITE(SOL2_PIN, value); break;
#endif
#if HAS_SOLENOID(3)
case 3: OUT_WRITE(SOL3_PIN, value); break;
#endif
#if HAS_SOLENOID(4)
case 4: OUT_WRITE(SOL4_PIN, value); break;
#endif
#if HAS_SOLENOID(5)
case 5: OUT_WRITE(SOL5_PIN, value); break;
#endif
REPEAT(8, _SOL_CASE)
default: SERIAL_ECHO_MSG(STR_INVALID_SOLENOID); break;
}
#if ENABLED(PARKING_EXTRUDER)
if (!active && active_extruder == num) // If active extruder's solenoid is disabled, carriage is considered parked
if (state == LOW && active_extruder == num) // If active extruder's solenoid is disabled, carriage is considered parked
parking_extruder_set_parked(true);
#endif
}
void enable_solenoid(const uint8_t num) { set_solenoid(num, true); }
void disable_solenoid(const uint8_t num) { set_solenoid(num, false); }
void enable_solenoid_on_active_extruder() { enable_solenoid(active_extruder); }
// PARKING_EXTRUDER options alter the default behavior of solenoids to ensure compliance of M380-381
void enable_solenoid(const uint8_t num) { set_solenoid(num, TERN1(PARKING_EXTRUDER, PE_MAGNET_ON_STATE)); }
void disable_solenoid(const uint8_t num) { set_solenoid(num, TERN0(PARKING_EXTRUDER, !PE_MAGNET_ON_STATE)); }
void disable_all_solenoids() {
disable_solenoid(0);
#if HAS_SOLENOID(1)
disable_solenoid(1);
#endif
#if HAS_SOLENOID(2)
disable_solenoid(2);
#endif
#if HAS_SOLENOID(3)
disable_solenoid(3);
#endif
#if HAS_SOLENOID(4)
disable_solenoid(4);
#endif
#if HAS_SOLENOID(5)
disable_solenoid(5);
#endif
#define _SOL_DISABLE(N) TERN_(HAS_SOLENOID_##N, disable_solenoid(N));
REPEAT(8, _SOL_DISABLE)
}
#endif // EXT_SOLENOID || MANUAL_SOLENOID_CONTROL

View File

@@ -21,7 +21,6 @@
*/
#pragma once
void enable_solenoid_on_active_extruder();
void disable_all_solenoids();
void enable_solenoid(const uint8_t num);
void disable_solenoid(const uint8_t num);

View File

@@ -39,17 +39,26 @@
#endif
SpindleLaser cutter;
uint8_t SpindleLaser::power;
#if ENABLED(LASER_FEATURE)
cutter_test_pulse_t SpindleLaser::testPulse = 50; // Test fire Pulse time ms value.
#endif
bool SpindleLaser::isReady; // Ready to apply power setting from the UI to OCR
cutter_power_t SpindleLaser::menuPower, // Power set via LCD menu in PWM, PERCENT, or RPM
SpindleLaser::unitPower; // LCD status power in PWM, PERCENT, or RPM
bool SpindleLaser::enable_state; // Virtual enable state, controls enable pin if present and or apply power if > 0
uint8_t SpindleLaser::power, // Actual power output 0-255 ocr or "0 = off" > 0 = "on"
SpindleLaser::last_power_applied; // = 0 // Basic power state tracking
#if ENABLED(MARLIN_DEV_MODE)
cutter_frequency_t SpindleLaser::frequency; // PWM frequency setting; range: 2K - 50K
#if ENABLED(LASER_FEATURE)
cutter_test_pulse_t SpindleLaser::testPulse = 50; // (ms) Test fire pulse default duration
uint8_t SpindleLaser::last_block_power; // = 0 // Track power changes for dynamic inline power
feedRate_t SpindleLaser::feedrate_mm_m = 1500,
SpindleLaser::last_feedrate_mm_m; // = 0 // (mm/min) Track feedrate changes for dynamic power
#endif
bool SpindleLaser::isReadyForUI = false; // Ready to apply power setting from the UI to OCR
CutterMode SpindleLaser::cutter_mode = CUTTER_MODE_STANDARD; // Default is standard mode
constexpr cutter_cpower_t SpindleLaser::power_floor;
cutter_power_t SpindleLaser::menuPower = 0, // Power value via LCD menu in PWM, PERCENT, or RPM based on configured format set by CUTTER_POWER_UNIT.
SpindleLaser::unitPower = 0; // Unit power is in PWM, PERCENT, or RPM based on CUTTER_POWER_UNIT.
cutter_frequency_t SpindleLaser::frequency; // PWM frequency setting; range: 2K - 50K
#define SPINDLE_LASER_PWM_OFF TERN(SPINDLE_LASER_PWM_INVERT, 255, 0)
/**
@@ -57,20 +66,20 @@ cutter_power_t SpindleLaser::menuPower, // Power s
*/
void SpindleLaser::init() {
#if ENABLED(SPINDLE_SERVO)
MOVE_SERVO(SPINDLE_SERVO_NR, SPINDLE_SERVO_MIN);
#else
servo[SPINDLE_SERVO_NR].move(SPINDLE_SERVO_MIN);
#elif PIN_EXISTS(SPINDLE_LASER_ENA)
OUT_WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_STATE); // Init spindle to off
#endif
#if ENABLED(SPINDLE_CHANGE_DIR)
OUT_WRITE(SPINDLE_DIR_PIN, SPINDLE_INVERT_DIR ? 255 : 0); // Init rotation to clockwise (M3)
OUT_WRITE(SPINDLE_DIR_PIN, SPINDLE_INVERT_DIR); // Init rotation to clockwise (M3)
#endif
#if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
frequency = SPINDLE_LASER_FREQUENCY;
hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_FREQUENCY);
#endif
#if ENABLED(SPINDLE_LASER_USE_PWM)
SET_PWM(SPINDLE_LASER_PWM_PIN);
analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // Set to lowest speed
#endif
#if ENABLED(HAL_CAN_SET_PWM_FREQ) && defined(SPINDLE_LASER_FREQUENCY)
set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_FREQUENCY);
TERN_(MARLIN_DEV_MODE, frequency = SPINDLE_LASER_FREQUENCY);
hal.set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // Set to lowest speed
#endif
#if ENABLED(AIR_EVACUATION)
OUT_WRITE(AIR_EVACUATION_PIN, !AIR_EVACUATION_ACTIVE); // Init Vacuum/Blower OFF
@@ -78,9 +87,7 @@ void SpindleLaser::init() {
#if ENABLED(AIR_ASSIST)
OUT_WRITE(AIR_ASSIST_PIN, !AIR_ASSIST_ACTIVE); // Init Air Assist OFF
#endif
#if ENABLED(I2C_AMMETER)
ammeter.init(); // Init I2C Ammeter
#endif
TERN_(I2C_AMMETER, ammeter.init()); // Init I2C Ammeter
}
#if ENABLED(SPINDLE_LASER_USE_PWM)
@@ -90,56 +97,63 @@ void SpindleLaser::init() {
* @param ocr Power value
*/
void SpindleLaser::_set_ocr(const uint8_t ocr) {
#if NEEDS_HARDWARE_PWM && SPINDLE_LASER_FREQUENCY
set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), TERN(MARLIN_DEV_MODE, frequency, SPINDLE_LASER_FREQUENCY));
set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF);
#else
analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF);
#if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency);
#endif
hal.set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF);
}
void SpindleLaser::set_ocr(const uint8_t ocr) {
WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_STATE); // Cutter ON
#if PIN_EXISTS(SPINDLE_LASER_ENA)
WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_STATE); // Cutter ON
#endif
_set_ocr(ocr);
}
void SpindleLaser::ocr_off() {
WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_STATE); // Cutter OFF
#if PIN_EXISTS(SPINDLE_LASER_ENA)
WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_STATE); // Cutter OFF
#endif
_set_ocr(0);
}
#endif // SPINDLE_LASER_USE_PWM
/**
* Apply power for laser/spindle
* Apply power for Laser or Spindle
*
* Apply cutter power value for PWM, Servo, and on/off pin.
*
* @param opwr Power value. Range 0 to MAX. When 0 disable spindle/laser.
* @param opwr Power value. Range 0 to MAX.
*/
void SpindleLaser::apply_power(const uint8_t opwr) {
static uint8_t last_power_applied = 0;
if (opwr == last_power_applied) return;
last_power_applied = opwr;
power = opwr;
#if ENABLED(SPINDLE_LASER_USE_PWM)
if (cutter.unitPower == 0 && CUTTER_UNIT_IS(RPM)) {
ocr_off();
isReady = false;
}
else if (ENABLED(CUTTER_POWER_RELATIVE) || enabled()) {
set_ocr(power);
isReady = true;
}
else {
ocr_off();
isReady = false;
}
#elif ENABLED(SPINDLE_SERVO)
MOVE_SERVO(SPINDLE_SERVO_NR, power);
#else
WRITE(SPINDLE_LASER_ENA_PIN, enabled() ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE);
isReady = true;
#endif
if (enabled() || opwr == 0) { // 0 check allows us to disable where no ENA pin exists
// Test and set the last power used to improve performance
if (opwr == last_power_applied) return;
last_power_applied = opwr;
// Handle PWM driven or just simple on/off
#if ENABLED(SPINDLE_LASER_USE_PWM)
if (CUTTER_UNIT_IS(RPM) && unitPower == 0)
ocr_off();
else if (ENABLED(CUTTER_POWER_RELATIVE) || enabled() || opwr == 0) {
set_ocr(opwr);
isReadyForUI = true;
}
else
ocr_off();
#elif ENABLED(SPINDLE_SERVO)
MOVE_SERVO(SPINDLE_SERVO_NR, power);
#else
WRITE(SPINDLE_LASER_ENA_PIN, enabled() ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE);
isReadyForUI = true;
#endif
}
else {
#if PIN_EXISTS(SPINDLE_LASER_ENA)
WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_STATE);
#endif
isReadyForUI = false; // Only used for UI display updates.
TERN_(SPINDLE_LASER_USE_PWM, ocr_off());
}
}
#if ENABLED(SPINDLE_CHANGE_DIR)
@@ -163,8 +177,8 @@ void SpindleLaser::apply_power(const uint8_t opwr) {
#if ENABLED(AIR_ASSIST)
// Enable / disable air assist
void SpindleLaser::air_assist_enable() { WRITE(AIR_ASSIST_PIN, AIR_ASSIST_PIN); } // Turn ON
void SpindleLaser::air_assist_disable() { WRITE(AIR_ASSIST_PIN, !AIR_ASSIST_PIN); } // Turn OFF
void SpindleLaser::air_assist_enable() { WRITE(AIR_ASSIST_PIN, AIR_ASSIST_ACTIVE); } // Turn ON
void SpindleLaser::air_assist_disable() { WRITE(AIR_ASSIST_PIN, !AIR_ASSIST_ACTIVE); } // Turn OFF
void SpindleLaser::air_assist_toggle() { TOGGLE(AIR_ASSIST_PIN); } // Toggle state
#endif

View File

@@ -30,98 +30,102 @@
#include "spindle_laser_types.h"
#if USE_BEEPER
#if HAS_BEEPER
#include "../libs/buzzer.h"
#endif
#if ENABLED(LASER_POWER_INLINE)
#include "../module/planner.h"
#endif
// Inline laser power
#include "../module/planner.h"
#define PCT_TO_PWM(X) ((X) * 255 / 100)
#define PCT_TO_SERVO(X) ((X) * 180 / 100)
#ifndef SPEED_POWER_INTERCEPT
#define SPEED_POWER_INTERCEPT 0
#endif
// #define _MAP(N,S1,S2,D1,D2) ((N)*_MAX((D2)-(D1),0)/_MAX((S2)-(S1),1)+(D1))
// Laser/Cutter operation mode
enum CutterMode : int8_t {
CUTTER_MODE_ERROR = -1,
CUTTER_MODE_STANDARD, // M3 power is applied directly and waits for planner moves to sync.
CUTTER_MODE_CONTINUOUS, // M3 or G1/2/3 move power is controlled within planner blocks, set with 'M3 I', cleared with 'M5 I'.
CUTTER_MODE_DYNAMIC // M4 laser power is proportional to the feed rate, set with 'M4 I', cleared with 'M5 I'.
};
class SpindleLaser {
public:
static constexpr float
min_pct = TERN(CUTTER_POWER_RELATIVE, 0, TERN(SPINDLE_FEATURE, round(100.0f * (SPEED_POWER_MIN) / (SPEED_POWER_MAX)), SPEED_POWER_MIN)),
max_pct = TERN(SPINDLE_FEATURE, 100, SPEED_POWER_MAX);
static CutterMode cutter_mode;
static const inline uint8_t pct_to_ocr(const_float_t pct) { return uint8_t(PCT_TO_PWM(pct)); }
static constexpr uint8_t pct_to_ocr(const_float_t pct) { return uint8_t(PCT_TO_PWM(pct)); }
// cpower = configured values (e.g., SPEED_POWER_MAX)
// Convert configured power range to a percentage
static const inline uint8_t cpwr_to_pct(const cutter_cpower_t cpwr) {
constexpr cutter_cpower_t power_floor = TERN(CUTTER_POWER_RELATIVE, SPEED_POWER_MIN, 0),
power_range = SPEED_POWER_MAX - power_floor;
return cpwr ? round(100.0f * (cpwr - power_floor) / power_range) : 0;
static constexpr cutter_cpower_t power_floor = TERN(CUTTER_POWER_RELATIVE, SPEED_POWER_MIN, 0);
static constexpr uint8_t cpwr_to_pct(const cutter_cpower_t cpwr) {
return cpwr ? round(100.0f * (cpwr - power_floor) / (SPEED_POWER_MAX - power_floor)) : 0;
}
// Convert a cpower (e.g., SPEED_POWER_STARTUP) to unit power (upwr, upower),
// which can be PWM, Percent, Servo angle, or RPM (rel/abs).
static const inline cutter_power_t cpwr_to_upwr(const cutter_cpower_t cpwr) { // STARTUP power to Unit power
const cutter_power_t upwr = (
// Convert config defines from RPM to %, angle or PWM when in Spindle mode
// and convert from PERCENT to PWM when in Laser mode
static constexpr cutter_power_t cpwr_to_upwr(const cutter_cpower_t cpwr) { // STARTUP power to Unit power
return (
#if ENABLED(SPINDLE_FEATURE)
// Spindle configured values are in RPM
// Spindle configured define values are in RPM
#if CUTTER_UNIT_IS(RPM)
cpwr // to RPM
#elif CUTTER_UNIT_IS(PERCENT) // to PCT
cpwr_to_pct(cpwr)
#elif CUTTER_UNIT_IS(SERVO) // to SERVO angle
PCT_TO_SERVO(cpwr_to_pct(cpwr))
#else // to PWM
PCT_TO_PWM(cpwr_to_pct(cpwr))
cpwr // to same
#elif CUTTER_UNIT_IS(PERCENT)
cpwr_to_pct(cpwr) // to Percent
#elif CUTTER_UNIT_IS(SERVO)
PCT_TO_SERVO(cpwr_to_pct(cpwr)) // to SERVO angle
#else
PCT_TO_PWM(cpwr_to_pct(cpwr)) // to PWM
#endif
#else
// Laser configured values are in PCT
// Laser configured define values are in Percent
#if CUTTER_UNIT_IS(PWM255)
PCT_TO_PWM(cpwr)
PCT_TO_PWM(cpwr) // to PWM
#else
cpwr // to RPM/PCT
cpwr // to same
#endif
#endif
);
return upwr;
}
static const cutter_power_t mpower_min() { return cpwr_to_upwr(SPEED_POWER_MIN); }
static const cutter_power_t mpower_max() { return cpwr_to_upwr(SPEED_POWER_MAX); }
static constexpr cutter_power_t mpower_min() { return cpwr_to_upwr(SPEED_POWER_MIN); }
static constexpr cutter_power_t mpower_max() { return cpwr_to_upwr(SPEED_POWER_MAX); }
#if ENABLED(LASER_FEATURE)
static cutter_test_pulse_t testPulse; // Test fire Pulse ms value
static cutter_test_pulse_t testPulse; // (ms) Test fire pulse duration
static uint8_t last_block_power; // Track power changes for dynamic power
static feedRate_t feedrate_mm_m, last_feedrate_mm_m; // (mm/min) Track feedrate changes for dynamic power
static bool laser_feedrate_changed() {
const bool changed = last_feedrate_mm_m != feedrate_mm_m;
if (changed) last_feedrate_mm_m = feedrate_mm_m;
return changed;
}
#endif
static bool isReady; // Ready to apply power setting from the UI to OCR
static uint8_t power;
static bool isReadyForUI; // Ready to apply power setting from the UI to OCR
static bool enable_state;
static uint8_t power,
last_power_applied; // Basic power state tracking
#if ENABLED(MARLIN_DEV_MODE)
static cutter_frequency_t frequency; // Set PWM frequency; range: 2K-50K
#endif
static cutter_frequency_t frequency; // Set PWM frequency; range: 2K-50K
static cutter_power_t menuPower, // Power as set via LCD menu in PWM, Percentage or RPM
unitPower; // Power as displayed status in PWM, Percentage or RPM
static void init();
#if ENABLED(MARLIN_DEV_MODE)
static inline void refresh_frequency() { set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); }
#if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
static void refresh_frequency() { hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); }
#endif
// Modifying this function should update everywhere
static inline bool enabled(const cutter_power_t opwr) { return opwr > 0; }
static inline bool enabled() { return enabled(power); }
static bool enabled(const cutter_power_t opwr) { return opwr > 0; }
static bool enabled() { return enable_state; }
static void apply_power(const uint8_t inpow);
FORCE_INLINE static void refresh() { apply_power(power); }
FORCE_INLINE static void set_power(const uint8_t upwr) { power = upwr; refresh(); }
#if ENABLED(SPINDLE_LASER_USE_PWM)
@@ -132,13 +136,12 @@ public:
public:
static void set_ocr(const uint8_t ocr);
static inline void ocr_set_power(const uint8_t ocr) { power = ocr; set_ocr(ocr); }
static void ocr_off();
/**
* Update output for power->OCR translation
*/
static inline uint8_t upower_to_ocr(const cutter_power_t upwr) {
static uint8_t upower_to_ocr(const cutter_power_t upwr) {
return uint8_t(
#if CUTTER_UNIT_IS(PWM255)
upwr
@@ -150,201 +153,179 @@ public:
);
}
/**
* Correct power to configured range
*/
static inline cutter_power_t power_to_range(const cutter_power_t pwr) {
return power_to_range(pwr, _CUTTER_POWER(CUTTER_POWER_UNIT));
}
static inline cutter_power_t power_to_range(const cutter_power_t pwr, const uint8_t pwrUnit) {
if (pwr <= 0) return 0;
cutter_power_t upwr;
switch (pwrUnit) {
case _CUTTER_POWER_PWM255:
upwr = cutter_power_t(
(pwr < pct_to_ocr(min_pct)) ? pct_to_ocr(min_pct) // Use minimum if set below
: (pwr > pct_to_ocr(max_pct)) ? pct_to_ocr(max_pct) // Use maximum if set above
: pwr
);
break;
case _CUTTER_POWER_PERCENT:
upwr = cutter_power_t(
(pwr < min_pct) ? min_pct // Use minimum if set below
: (pwr > max_pct) ? max_pct // Use maximum if set above
: pwr // PCT
);
break;
case _CUTTER_POWER_RPM:
upwr = cutter_power_t(
(pwr < SPEED_POWER_MIN) ? SPEED_POWER_MIN // Use minimum if set below
: (pwr > SPEED_POWER_MAX) ? SPEED_POWER_MAX // Use maximum if set above
: pwr // Calculate OCR value
);
break;
default: break;
}
return upwr;
}
#endif // SPINDLE_LASER_USE_PWM
/**
* Enable/Disable spindle/laser
* @param enable true = enable; false = disable
* Correct power to configured range
*/
static inline void set_enabled(const bool enable) {
uint8_t value = 0;
if (enable) {
#if ENABLED(SPINDLE_LASER_USE_PWM)
if (power)
value = power;
else if (unitPower)
value = upower_to_ocr(cpwr_to_upwr(SPEED_POWER_STARTUP));
#else
value = 255;
#endif
static cutter_power_t power_to_range(const cutter_power_t pwr, const uint8_t pwrUnit=_CUTTER_POWER(CUTTER_POWER_UNIT)) {
static constexpr float
min_pct = TERN(CUTTER_POWER_RELATIVE, 0, TERN(SPINDLE_FEATURE, round(100.0f * (SPEED_POWER_MIN) / (SPEED_POWER_MAX)), SPEED_POWER_MIN)),
max_pct = TERN(SPINDLE_FEATURE, 100, SPEED_POWER_MAX);
if (pwr <= 0) return 0;
cutter_power_t upwr;
switch (pwrUnit) {
case _CUTTER_POWER_PWM255: { // PWM
const uint8_t pmin = pct_to_ocr(min_pct), pmax = pct_to_ocr(max_pct);
upwr = cutter_power_t(constrain(pwr, pmin, pmax));
} break;
case _CUTTER_POWER_PERCENT: // Percent
upwr = cutter_power_t(constrain(pwr, min_pct, max_pct));
break;
case _CUTTER_POWER_RPM: // Calculate OCR value
upwr = cutter_power_t(constrain(pwr, SPEED_POWER_MIN, SPEED_POWER_MAX));
break;
default: break;
}
set_power(value);
return upwr;
}
static inline void disable() { isReady = false; set_enabled(false); }
/**
* Wait for spindle to spin up or spin down
* Enable Laser or Spindle output.
* It's important to prevent changing the power output value during inline cutter operation.
* Inline power is adjusted in the planner to support LASER_TRAP_POWER and CUTTER_MODE_DYNAMIC mode.
*
* @param on true = state to on; false = state to off.
* This method accepts one of the following control states:
*
* - For CUTTER_MODE_STANDARD the cutter power is either full on/off or ocr-based and it will apply
* SPEED_POWER_STARTUP if no value is assigned.
*
* - For CUTTER_MODE_CONTINUOUS inline and power remains where last set and the cutter output enable flag is set.
*
* - CUTTER_MODE_DYNAMIC is also inline-based and it just sets the enable output flag.
*
* - For CUTTER_MODE_ERROR set the output enable_state flag directly and set power to 0 for any mode.
* This mode allows a global power shutdown action to occur.
*/
static inline void power_delay(const bool on) {
#if DISABLED(LASER_POWER_INLINE)
safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
static void set_enabled(bool enable) {
switch (cutter_mode) {
case CUTTER_MODE_STANDARD:
apply_power(enable ? TERN(SPINDLE_LASER_USE_PWM, (power ?: (unitPower ? upower_to_ocr(cpwr_to_upwr(SPEED_POWER_STARTUP)) : 0)), 255) : 0);
break;
case CUTTER_MODE_CONTINUOUS:
TERN_(LASER_FEATURE, set_inline_enabled(enable));
break;
case CUTTER_MODE_DYNAMIC:
TERN_(LASER_FEATURE, set_inline_enabled(enable));
break;
case CUTTER_MODE_ERROR: // Error mode, no enable and kill power.
enable = false;
apply_power(0);
}
#if SPINDLE_LASER_ENA_PIN
WRITE(SPINDLE_LASER_ENA_PIN, enable ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE);
#endif
enable_state = enable;
}
static void disable() { isReadyForUI = false; set_enabled(false); }
// Wait for spindle/laser to startup or shutdown
static void power_delay(const bool on) {
safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
}
#if ENABLED(SPINDLE_CHANGE_DIR)
static void set_reverse(const bool reverse);
static bool is_reverse() { return READ(SPINDLE_DIR_PIN) == SPINDLE_INVERT_DIR; }
#else
static inline void set_reverse(const bool) {}
static void set_reverse(const bool) {}
static bool is_reverse() { return false; }
#endif
#if ENABLED(AIR_EVACUATION)
static void air_evac_enable(); // Turn On Cutter Vacuum or Laser Blower motor
static void air_evac_disable(); // Turn Off Cutter Vacuum or Laser Blower motor
static void air_evac_toggle(); // Toggle Cutter Vacuum or Laser Blower motor
static inline bool air_evac_state() { // Get current state
static void air_evac_enable(); // Turn On Cutter Vacuum or Laser Blower motor
static void air_evac_disable(); // Turn Off Cutter Vacuum or Laser Blower motor
static void air_evac_toggle(); // Toggle Cutter Vacuum or Laser Blower motor
static bool air_evac_state() { // Get current state
return (READ(AIR_EVACUATION_PIN) == AIR_EVACUATION_ACTIVE);
}
#endif
#if ENABLED(AIR_ASSIST)
static void air_assist_enable(); // Turn on air assist
static void air_assist_disable(); // Turn off air assist
static void air_assist_toggle(); // Toggle air assist
static inline bool air_assist_state() { // Get current state
static void air_assist_enable(); // Turn on air assist
static void air_assist_disable(); // Turn off air assist
static void air_assist_toggle(); // Toggle air assist
static bool air_assist_state() { // Get current state
return (READ(AIR_ASSIST_PIN) == AIR_ASSIST_ACTIVE);
}
#endif
#if HAS_LCD_MENU
static inline void enable_with_dir(const bool reverse) {
isReady = true;
const uint8_t ocr = TERN(SPINDLE_LASER_USE_PWM, upower_to_ocr(menuPower), 255);
if (menuPower)
power = ocr;
else
menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP);
unitPower = menuPower;
set_reverse(reverse);
set_enabled(true);
}
FORCE_INLINE static void enable_forward() { enable_with_dir(false); }
FORCE_INLINE static void enable_reverse() { enable_with_dir(true); }
FORCE_INLINE static void enable_same_dir() { enable_with_dir(is_reverse()); }
#if HAS_MARLINUI_MENU
#if ENABLED(SPINDLE_FEATURE)
static void enable_with_dir(const bool reverse) {
isReadyForUI = true;
const uint8_t ocr = TERN(SPINDLE_LASER_USE_PWM, upower_to_ocr(menuPower), 255);
if (menuPower)
power = ocr;
else
menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP);
unitPower = menuPower;
set_reverse(reverse);
set_enabled(true);
}
FORCE_INLINE static void enable_forward() { enable_with_dir(false); }
FORCE_INLINE static void enable_reverse() { enable_with_dir(true); }
FORCE_INLINE static void enable_same_dir() { enable_with_dir(is_reverse()); }
#endif // SPINDLE_FEATURE
#if ENABLED(SPINDLE_LASER_USE_PWM)
static inline void update_from_mpower() {
if (isReady) power = upower_to_ocr(menuPower);
static void update_from_mpower() {
if (isReadyForUI) power = upower_to_ocr(menuPower);
unitPower = menuPower;
}
#endif
#if ENABLED(LASER_FEATURE)
// Toggle the laser on/off with menuPower. Apply SPEED_POWER_STARTUP if it was 0 on entry.
static void menu_set_enabled(const bool state) {
set_enabled(state);
if (state) {
if (!menuPower) menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP);
power = upower_to_ocr(menuPower);
apply_power(power);
} else
apply_power(0);
}
/**
* Test fire the laser using the testPulse ms duration
* Also fires with any PWM power that was previous set
* If not set defaults to 80% power
*/
static inline void test_fire_pulse() {
TERN_(USE_BEEPER, buzzer.tone(30, 3000));
enable_forward(); // Turn Laser on (Spindle speak but same funct)
delay(testPulse); // Delay for time set by user in pulse ms menu screen.
disable(); // Turn laser off
static void test_fire_pulse() {
BUZZ(30, 3000);
cutter_mode = CUTTER_MODE_STANDARD; // Menu needs standard mode.
menu_set_enabled(true); // Laser On
delay(testPulse); // Delay for time set by user in pulse ms menu screen.
menu_set_enabled(false); // Laser Off
}
#endif
#endif // LASER_FEATURE
#endif // HAS_LCD_MENU
#endif // HAS_MARLINUI_MENU
#if ENABLED(LASER_POWER_INLINE)
/**
* Inline power adds extra fields to the planner block
* to handle laser power and scale to movement speed.
*/
#if ENABLED(LASER_FEATURE)
// Force disengage planner power control
static inline void inline_disable() {
isReady = false;
unitPower = 0;
planner.laser_inline.status.isPlanned = false;
planner.laser_inline.status.isEnabled = false;
planner.laser_inline.power = 0;
// Dynamic mode rate calculation
static uint8_t calc_dynamic_power() {
if (feedrate_mm_m > 65535) return 255; // Too fast, go always on
uint16_t rate = uint16_t(feedrate_mm_m); // 16 bits from the G-code parser float input
rate >>= 8; // Take the G-code input e.g. F40000 and shift off the lower bits to get an OCR value from 1-255
return uint8_t(rate);
}
// Inline modes of all other functions; all enable planner inline power control
static inline void set_inline_enabled(const bool enable) {
if (enable)
inline_power(255);
else {
isReady = false;
unitPower = menuPower = 0;
planner.laser_inline.status.isPlanned = false;
TERN(SPINDLE_LASER_USE_PWM, inline_ocr_power, inline_power)(0);
}
}
static void set_inline_enabled(const bool enable) { planner.laser_inline.status.isEnabled = enable; }
// Set the power for subsequent movement blocks
static void inline_power(const cutter_power_t upwr) {
unitPower = menuPower = upwr;
#if ENABLED(SPINDLE_LASER_USE_PWM)
#if ENABLED(SPEED_POWER_RELATIVE) && !CUTTER_UNIT_IS(RPM) // relative mode does not turn laser off at 0, except for RPM
planner.laser_inline.status.isEnabled = true;
planner.laser_inline.power = upower_to_ocr(upwr);
isReady = true;
#else
inline_ocr_power(upower_to_ocr(upwr));
#endif
#else
planner.laser_inline.status.isEnabled = enabled(upwr);
planner.laser_inline.power = upwr;
isReady = enabled(upwr);
#endif
static void inline_power(const cutter_power_t cpwr) {
TERN(SPINDLE_LASER_USE_PWM, power = planner.laser_inline.power = cpwr, planner.laser_inline.power = cpwr > 0 ? 255 : 0);
}
static inline void inline_direction(const bool) { /* never */ }
#endif // LASER_FEATURE
#if ENABLED(SPINDLE_LASER_USE_PWM)
static inline void inline_ocr_power(const uint8_t ocrpwr) {
isReady = ocrpwr > 0;
planner.laser_inline.status.isEnabled = ocrpwr > 0;
planner.laser_inline.power = ocrpwr;
}
#endif
#endif // LASER_POWER_INLINE
static inline void kill() {
TERN_(LASER_POWER_INLINE, inline_disable());
disable();
}
static void kill() { disable(); }
};
extern SpindleLaser cutter;

View File

@@ -28,12 +28,34 @@
#include "../inc/MarlinConfigPre.h"
#define MSG_CUTTER(M) _MSG_CUTTER(M)
#ifndef SPEED_POWER_INTERCEPT
#define SPEED_POWER_INTERCEPT 0
#endif
#if ENABLED(SPINDLE_FEATURE)
#define _MSG_CUTTER(M) MSG_SPINDLE_##M
#ifndef SPEED_POWER_MIN
#define SPEED_POWER_MIN 5000
#endif
#ifndef SPEED_POWER_MAX
#define SPEED_POWER_MAX 30000
#endif
#ifndef SPEED_POWER_STARTUP
#define SPEED_POWER_STARTUP 25000
#endif
#else
#define _MSG_CUTTER(M) MSG_LASER_##M
#ifndef SPEED_POWER_MIN
#define SPEED_POWER_MIN 0
#endif
#ifndef SPEED_POWER_MAX
#define SPEED_POWER_MAX 255
#endif
#ifndef SPEED_POWER_STARTUP
#define SPEED_POWER_STARTUP 255
#endif
#endif
#define MSG_CUTTER(M) _MSG_CUTTER(M)
typedef IF<(SPEED_POWER_MAX > 255), uint16_t, uint8_t>::type cutter_cpower_t;
@@ -52,12 +74,10 @@ typedef IF<(SPEED_POWER_MAX > 255), uint16_t, uint8_t>::type cutter_cpower_t;
#endif
#endif
typedef uint16_t cutter_frequency_t;
#if ENABLED(LASER_FEATURE)
typedef uint16_t cutter_test_pulse_t;
#define CUTTER_MENU_PULSE_TYPE uint16_3
#endif
#if ENABLED(MARLIN_DEV_MODE)
typedef uint16_t cutter_frequency_t;
#define CUTTER_MENU_FREQUENCY_TYPE uint16_5
#endif

View File

@@ -28,11 +28,11 @@
static uint32_t axis_plug_backward = 0;
void stepper_driver_backward_error(PGM_P str) {
void stepper_driver_backward_error(FSTR_P const fstr) {
SERIAL_ERROR_START();
SERIAL_ECHOPGM_P(str);
SERIAL_ECHOF(fstr);
SERIAL_ECHOLNPGM(" driver is backward!");
ui.status_printf_P(2, PSTR(S_FMT S_FMT), str, GET_TEXT(MSG_DRIVER_BACKWARD));
ui.status_printf(2, F(S_FMT S_FMT), FTOP(fstr), GET_TEXT(MSG_DRIVER_BACKWARD));
}
void stepper_driver_backward_check() {
@@ -45,7 +45,7 @@ void stepper_driver_backward_check() {
delay(20); \
if (READ(AXIS##_ENABLE_PIN) == false) { \
SBI(axis_plug_backward, BIT); \
stepper_driver_backward_error(PSTR(STRINGIFY(AXIS))); \
stepper_driver_backward_error(F(STRINGIFY(AXIS))); \
} \
}while(0)
@@ -65,15 +65,18 @@ void stepper_driver_backward_check() {
TEST_BACKWARD(I, 8);
TEST_BACKWARD(J, 9);
TEST_BACKWARD(K, 10);
TEST_BACKWARD(U, 11);
TEST_BACKWARD(V, 12);
TEST_BACKWARD(W, 13);
TEST_BACKWARD(E0, 11);
TEST_BACKWARD(E1, 12);
TEST_BACKWARD(E2, 13);
TEST_BACKWARD(E3, 14);
TEST_BACKWARD(E4, 15);
TEST_BACKWARD(E5, 16);
TEST_BACKWARD(E6, 17);
TEST_BACKWARD(E7, 18);
TEST_BACKWARD(E0, 14);
TEST_BACKWARD(E1, 15);
TEST_BACKWARD(E2, 16);
TEST_BACKWARD(E3, 17);
TEST_BACKWARD(E4, 18);
TEST_BACKWARD(E5, 19);
TEST_BACKWARD(E6, 20);
TEST_BACKWARD(E7, 21);
if (!axis_plug_backward)
WRITE(SAFE_POWER_PIN, HIGH);
@@ -82,12 +85,12 @@ void stepper_driver_backward_check() {
void stepper_driver_backward_report() {
if (!axis_plug_backward) return;
auto _report_if_backward = [](PGM_P axis, uint8_t bit) {
auto _report_if_backward = [](FSTR_P const axis, uint8_t bit) {
if (TEST(axis_plug_backward, bit))
stepper_driver_backward_error(axis);
};
#define REPORT_BACKWARD(axis, bit) TERN_(HAS_##axis##_ENABLE, _report_if_backward(PSTR(STRINGIFY(axis)), bit))
#define REPORT_BACKWARD(axis, bit) TERN_(HAS_##axis##_ENABLE, _report_if_backward(F(STRINGIFY(axis)), bit))
REPORT_BACKWARD(X, 0);
REPORT_BACKWARD(X2, 1);
@@ -103,15 +106,18 @@ void stepper_driver_backward_report() {
REPORT_BACKWARD(I, 8);
REPORT_BACKWARD(J, 9);
REPORT_BACKWARD(K, 10);
REPORT_BACKWARD(U, 11);
REPORT_BACKWARD(V, 12);
REPORT_BACKWARD(W, 13);
REPORT_BACKWARD(E0, 11);
REPORT_BACKWARD(E1, 12);
REPORT_BACKWARD(E2, 13);
REPORT_BACKWARD(E3, 14);
REPORT_BACKWARD(E4, 15);
REPORT_BACKWARD(E5, 16);
REPORT_BACKWARD(E6, 17);
REPORT_BACKWARD(E7, 18);
REPORT_BACKWARD(E0, 14);
REPORT_BACKWARD(E1, 15);
REPORT_BACKWARD(E2, 16);
REPORT_BACKWARD(E3, 17);
REPORT_BACKWARD(E4, 18);
REPORT_BACKWARD(E5, 19);
REPORT_BACKWARD(E6, 20);
REPORT_BACKWARD(E7, 21);
}
#endif // HAS_DRIVER_SAFE_POWER_PROTECT

View File

@@ -33,17 +33,12 @@
#include "../gcode/gcode.h"
#if ENABLED(TMC_DEBUG)
#include "../module/planner.h"
#include "../libs/hex_print.h"
#if ENABLED(MONITOR_DRIVER_STATUS)
static uint16_t report_tmc_status_interval; // = 0
#endif
#endif
#if HAS_LCD_MENU
#include "../module/stepper.h"
#endif
/**
* Check for over temperature or short to ground error flags.
* Report and log warning of overtemperature condition.
@@ -212,7 +207,7 @@
if (data.is_ot) SERIAL_ECHOLNPGM("overtemperature");
if (data.is_s2g) SERIAL_ECHOLNPGM("coil short circuit");
TERN_(TMC_DEBUG, tmc_report_all());
kill(PSTR("Driver error"));
kill(F("Driver error"));
}
#endif
@@ -421,16 +416,26 @@
if (monitor_tmc_driver(stepperI, need_update_error_counters, need_debug_reporting))
step_current_down(stepperI);
#endif
#if AXIS_IS_TMC(J)
if (monitor_tmc_driver(stepperJ, need_update_error_counters, need_debug_reporting))
step_current_down(stepperJ);
#endif
#if AXIS_IS_TMC(K)
if (monitor_tmc_driver(stepperK, need_update_error_counters, need_debug_reporting))
step_current_down(stepperK);
#endif
#if AXIS_IS_TMC(U)
if (monitor_tmc_driver(stepperU, need_update_error_counters, need_debug_reporting))
step_current_down(stepperU);
#endif
#if AXIS_IS_TMC(V)
if (monitor_tmc_driver(stepperV, need_update_error_counters, need_debug_reporting))
step_current_down(stepperV);
#endif
#if AXIS_IS_TMC(W)
if (monitor_tmc_driver(stepperW, need_update_error_counters, need_debug_reporting))
step_current_down(stepperW);
#endif
#if AXIS_IS_TMC(E0)
(void)monitor_tmc_driver(stepperE0, need_update_error_counters, need_debug_reporting);
@@ -472,12 +477,8 @@
void tmc_set_report_interval(const uint16_t update_interval) {
if ((report_tmc_status_interval = update_interval))
SERIAL_ECHOLNPGM("axis:pwm_scale"
#if HAS_STEALTHCHOP
"/curr_scale"
#endif
#if HAS_STALLGUARD
"/mech_load"
#endif
TERN_(HAS_STEALTHCHOP, "/curr_scale")
TERN_(HAS_STALLGUARD, "/mech_load")
"|flags|warncount"
);
}
@@ -561,7 +562,7 @@
};
template<class TMC>
static void print_vsense(TMC &st) { SERIAL_ECHOPGM_P(st.vsense() ? PSTR("1=.18") : PSTR("0=.325")); }
static void print_vsense(TMC &st) { SERIAL_ECHOF(st.vsense() ? F("1=.18") : F("0=.325")); }
#if HAS_DRIVER(TMC2130) || HAS_DRIVER(TMC5130)
static void _tmc_status(TMC2130Stepper &st, const TMC_debug_enum i) {
@@ -732,7 +733,7 @@
SERIAL_ECHO(st.cs());
SERIAL_ECHOPGM("/31");
break;
case TMC_VSENSE: SERIAL_ECHOPGM_P(st.vsense() ? PSTR("1=.165") : PSTR("0=.310")); break;
case TMC_VSENSE: SERIAL_ECHOF(st.vsense() ? F("1=.165") : F("0=.310")); break;
case TMC_MICROSTEPS: SERIAL_ECHO(st.microsteps()); break;
//case TMC_OTPW: serialprint_truefalse(st.otpw()); break;
//case TMC_OTPW_TRIGGERED: serialprint_truefalse(st.getOTPW()); break;
@@ -815,6 +816,15 @@
#if AXIS_IS_TMC(K)
if (k) tmc_status(stepperK, n);
#endif
#if AXIS_IS_TMC(U)
if (u) tmc_status(stepperU, n);
#endif
#if AXIS_IS_TMC(V)
if (v) tmc_status(stepperV, n);
#endif
#if AXIS_IS_TMC(W)
if (w) tmc_status(stepperW, n);
#endif
if (TERN0(HAS_EXTRUDERS, e)) {
#if AXIS_IS_TMC(E0)
@@ -889,6 +899,15 @@
#if AXIS_IS_TMC(K)
if (k) tmc_parse_drv_status(stepperK, n);
#endif
#if AXIS_IS_TMC(U)
if (u) tmc_parse_drv_status(stepperU, n);
#endif
#if AXIS_IS_TMC(V)
if (v) tmc_parse_drv_status(stepperV, n);
#endif
#if AXIS_IS_TMC(W)
if (w) tmc_parse_drv_status(stepperW, n);
#endif
if (TERN0(HAS_EXTRUDERS, e)) {
#if AXIS_IS_TMC(E0)
@@ -1094,6 +1113,15 @@
#if AXIS_IS_TMC(K)
if (k) tmc_get_registers(stepperK, n);
#endif
#if AXIS_IS_TMC(U)
if (u) tmc_get_registers(stepperU, n);
#endif
#if AXIS_IS_TMC(V)
if (v) tmc_get_registers(stepperV, n);
#endif
#if AXIS_IS_TMC(W)
if (w) tmc_get_registers(stepperW, n);
#endif
if (TERN0(HAS_EXTRUDERS, e)) {
#if AXIS_IS_TMC(E0)
@@ -1184,69 +1212,6 @@
#endif // USE_SENSORLESS
#if HAS_TMC_SPI
#define SET_CS_PIN(st) OUT_WRITE(st##_CS_PIN, HIGH)
void tmc_init_cs_pins() {
#if AXIS_HAS_SPI(X)
SET_CS_PIN(X);
#endif
#if AXIS_HAS_SPI(Y)
SET_CS_PIN(Y);
#endif
#if AXIS_HAS_SPI(Z)
SET_CS_PIN(Z);
#endif
#if AXIS_HAS_SPI(X2)
SET_CS_PIN(X2);
#endif
#if AXIS_HAS_SPI(Y2)
SET_CS_PIN(Y2);
#endif
#if AXIS_HAS_SPI(Z2)
SET_CS_PIN(Z2);
#endif
#if AXIS_HAS_SPI(Z3)
SET_CS_PIN(Z3);
#endif
#if AXIS_HAS_SPI(Z4)
SET_CS_PIN(Z4);
#endif
#if AXIS_HAS_SPI(I)
SET_CS_PIN(I);
#endif
#if AXIS_HAS_SPI(J)
SET_CS_PIN(J);
#endif
#if AXIS_HAS_SPI(K)
SET_CS_PIN(K);
#endif
#if AXIS_HAS_SPI(E0)
SET_CS_PIN(E0);
#endif
#if AXIS_HAS_SPI(E1)
SET_CS_PIN(E1);
#endif
#if AXIS_HAS_SPI(E2)
SET_CS_PIN(E2);
#endif
#if AXIS_HAS_SPI(E3)
SET_CS_PIN(E3);
#endif
#if AXIS_HAS_SPI(E4)
SET_CS_PIN(E4);
#endif
#if AXIS_HAS_SPI(E5)
SET_CS_PIN(E5);
#endif
#if AXIS_HAS_SPI(E6)
SET_CS_PIN(E6);
#endif
#if AXIS_HAS_SPI(E7)
SET_CS_PIN(E7);
#endif
}
#endif // HAS_TMC_SPI
template<typename TMC>
static bool test_connection(TMC &st) {
SERIAL_ECHOPGM("Testing ");
@@ -1256,15 +1221,14 @@ static bool test_connection(TMC &st) {
if (test_result > 0) SERIAL_ECHOPGM("Error: All ");
const char *stat;
FSTR_P stat;
switch (test_result) {
default:
case 0: stat = PSTR("OK"); break;
case 1: stat = PSTR("HIGH"); break;
case 2: stat = PSTR("LOW"); break;
case 0: stat = F("OK"); break;
case 1: stat = F("HIGH"); break;
case 2: stat = F("LOW"); break;
}
SERIAL_ECHOPGM_P(stat);
SERIAL_EOL();
SERIAL_ECHOLNF(stat);
return test_result;
}
@@ -1314,6 +1278,15 @@ void test_tmc_connection(LOGICAL_AXIS_ARGS(const bool)) {
#if AXIS_IS_TMC(K)
if (k) axis_connection += test_connection(stepperK);
#endif
#if AXIS_IS_TMC(U)
if (u) axis_connection += test_connection(stepperU);
#endif
#if AXIS_IS_TMC(V)
if (v) axis_connection += test_connection(stepperV);
#endif
#if AXIS_IS_TMC(W)
if (w) axis_connection += test_connection(stepperW);
#endif
if (TERN0(HAS_EXTRUDERS, e)) {
#if AXIS_IS_TMC(E0)
@@ -1342,7 +1315,79 @@ void test_tmc_connection(LOGICAL_AXIS_ARGS(const bool)) {
#endif
}
if (axis_connection) LCD_MESSAGEPGM(MSG_ERROR_TMC);
if (axis_connection) LCD_MESSAGE(MSG_ERROR_TMC);
}
#endif // HAS_TRINAMIC_CONFIG
#if HAS_TMC_SPI
#define SET_CS_PIN(st) OUT_WRITE(st##_CS_PIN, HIGH)
void tmc_init_cs_pins() {
#if AXIS_HAS_SPI(X)
SET_CS_PIN(X);
#endif
#if AXIS_HAS_SPI(Y)
SET_CS_PIN(Y);
#endif
#if AXIS_HAS_SPI(Z)
SET_CS_PIN(Z);
#endif
#if AXIS_HAS_SPI(X2)
SET_CS_PIN(X2);
#endif
#if AXIS_HAS_SPI(Y2)
SET_CS_PIN(Y2);
#endif
#if AXIS_HAS_SPI(Z2)
SET_CS_PIN(Z2);
#endif
#if AXIS_HAS_SPI(Z3)
SET_CS_PIN(Z3);
#endif
#if AXIS_HAS_SPI(Z4)
SET_CS_PIN(Z4);
#endif
#if AXIS_HAS_SPI(I)
SET_CS_PIN(I);
#endif
#if AXIS_HAS_SPI(J)
SET_CS_PIN(J);
#endif
#if AXIS_HAS_SPI(K)
SET_CS_PIN(K);
#endif
#if AXIS_HAS_SPI(U)
SET_CS_PIN(U);
#endif
#if AXIS_HAS_SPI(V)
SET_CS_PIN(V);
#endif
#if AXIS_HAS_SPI(W)
SET_CS_PIN(W);
#endif
#if AXIS_HAS_SPI(E0)
SET_CS_PIN(E0);
#endif
#if AXIS_HAS_SPI(E1)
SET_CS_PIN(E1);
#endif
#if AXIS_HAS_SPI(E2)
SET_CS_PIN(E2);
#endif
#if AXIS_HAS_SPI(E3)
SET_CS_PIN(E3);
#endif
#if AXIS_HAS_SPI(E4)
SET_CS_PIN(E4);
#endif
#if AXIS_HAS_SPI(E5)
SET_CS_PIN(E5);
#endif
#if AXIS_HAS_SPI(E6)
SET_CS_PIN(E6);
#endif
#if AXIS_HAS_SPI(E7)
SET_CS_PIN(E7);
#endif
}
#endif // HAS_TMC_SPI

View File

@@ -45,6 +45,12 @@ constexpr uint16_t _tmc_thrs(const uint16_t msteps, const uint32_t thrs, const u
return 12650000UL * msteps / (256 * thrs * spmm);
}
typedef struct {
uint8_t toff;
int8_t hend;
uint8_t hstrt;
} chopper_timing_t;
template<char AXIS_LETTER, char DRIVER_ID>
class TMCStorage {
protected:
@@ -58,13 +64,13 @@ class TMCStorage {
uint8_t otpw_count = 0,
error_count = 0;
bool flag_otpw = false;
inline bool getOTPW() { return flag_otpw; }
inline void clear_otpw() { flag_otpw = 0; }
bool getOTPW() { return flag_otpw; }
void clear_otpw() { flag_otpw = 0; }
#endif
inline uint16_t getMilliamps() { return val_mA; }
uint16_t getMilliamps() { return val_mA; }
inline void printLabel() {
void printLabel() {
SERIAL_CHAR(AXIS_LETTER);
if (DRIVER_ID > '0') SERIAL_CHAR(DRIVER_ID);
}
@@ -91,55 +97,61 @@ class TMCMarlin : public TMC, public TMCStorage<AXIS_LETTER, DRIVER_ID> {
TMCMarlin(const uint16_t CS, const float RS, const uint16_t pinMOSI, const uint16_t pinMISO, const uint16_t pinSCK, const uint8_t axis_chain_index) :
TMC(CS, RS, pinMOSI, pinMISO, pinSCK, axis_chain_index)
{}
inline uint16_t rms_current() { return TMC::rms_current(); }
inline void rms_current(uint16_t mA) {
uint16_t rms_current() { return TMC::rms_current(); }
void rms_current(uint16_t mA) {
this->val_mA = mA;
TMC::rms_current(mA);
}
inline void rms_current(const uint16_t mA, const float mult) {
void rms_current(const uint16_t mA, const float mult) {
this->val_mA = mA;
TMC::rms_current(mA, mult);
}
inline uint16_t get_microstep_counter() { return TMC::MSCNT(); }
uint16_t get_microstep_counter() { return TMC::MSCNT(); }
#if HAS_STEALTHCHOP
inline bool get_stealthChop() { return this->en_pwm_mode(); }
inline bool get_stored_stealthChop() { return this->stored.stealthChop_enabled; }
inline void refresh_stepping_mode() { this->en_pwm_mode(this->stored.stealthChop_enabled); }
inline void set_stealthChop(const bool stch) { this->stored.stealthChop_enabled = stch; refresh_stepping_mode(); }
inline bool toggle_stepping_mode() { set_stealthChop(!this->stored.stealthChop_enabled); return get_stealthChop(); }
bool get_stealthChop() { return this->en_pwm_mode(); }
bool get_stored_stealthChop() { return this->stored.stealthChop_enabled; }
void refresh_stepping_mode() { this->en_pwm_mode(this->stored.stealthChop_enabled); }
void set_stealthChop(const bool stch) { this->stored.stealthChop_enabled = stch; refresh_stepping_mode(); }
bool toggle_stepping_mode() { set_stealthChop(!this->stored.stealthChop_enabled); return get_stealthChop(); }
#endif
void set_chopper_times(const chopper_timing_t &ct) {
TMC::toff(ct.toff);
TMC::hysteresis_end(ct.hend);
TMC::hysteresis_start(ct.hstrt);
}
#if ENABLED(HYBRID_THRESHOLD)
uint32_t get_pwm_thrs() {
return _tmc_thrs(this->microsteps(), this->TPWMTHRS(), planner.settings.axis_steps_per_mm[AXIS_ID]);
}
void set_pwm_thrs(const uint32_t thrs) {
TMC::TPWMTHRS(_tmc_thrs(this->microsteps(), thrs, planner.settings.axis_steps_per_mm[AXIS_ID]));
TERN_(HAS_LCD_MENU, this->stored.hybrid_thrs = thrs);
TERN_(HAS_MARLINUI_MENU, this->stored.hybrid_thrs = thrs);
}
#endif
#if USE_SENSORLESS
inline int16_t homing_threshold() { return TMC::sgt(); }
int16_t homing_threshold() { return TMC::sgt(); }
void homing_threshold(int16_t sgt_val) {
sgt_val = (int16_t)constrain(sgt_val, sgt_min, sgt_max);
TMC::sgt(sgt_val);
TERN_(HAS_LCD_MENU, this->stored.homing_thrs = sgt_val);
TERN_(HAS_MARLINUI_MENU, this->stored.homing_thrs = sgt_val);
}
#if ENABLED(SPI_ENDSTOPS)
bool test_stall_status();
#endif
#endif
#if HAS_LCD_MENU
inline void refresh_stepper_current() { rms_current(this->val_mA); }
#if HAS_MARLINUI_MENU
void refresh_stepper_current() { rms_current(this->val_mA); }
#if ENABLED(HYBRID_THRESHOLD)
inline void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); }
void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); }
#endif
#if USE_SENSORLESS
inline void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); }
void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); }
#endif
#endif
@@ -161,39 +173,45 @@ class TMCMarlin<TMC2208Stepper, AXIS_LETTER, DRIVER_ID, AXIS_ID> : public TMC220
{}
uint16_t rms_current() { return TMC2208Stepper::rms_current(); }
inline void rms_current(const uint16_t mA) {
void rms_current(const uint16_t mA) {
this->val_mA = mA;
TMC2208Stepper::rms_current(mA);
}
inline void rms_current(const uint16_t mA, const float mult) {
void rms_current(const uint16_t mA, const float mult) {
this->val_mA = mA;
TMC2208Stepper::rms_current(mA, mult);
}
inline uint16_t get_microstep_counter() { return TMC2208Stepper::MSCNT(); }
uint16_t get_microstep_counter() { return TMC2208Stepper::MSCNT(); }
#if HAS_STEALTHCHOP
inline bool get_stealthChop() { return !this->en_spreadCycle(); }
inline bool get_stored_stealthChop() { return this->stored.stealthChop_enabled; }
inline void refresh_stepping_mode() { this->en_spreadCycle(!this->stored.stealthChop_enabled); }
inline void set_stealthChop(const bool stch) { this->stored.stealthChop_enabled = stch; refresh_stepping_mode(); }
inline bool toggle_stepping_mode() { set_stealthChop(!this->stored.stealthChop_enabled); return get_stealthChop(); }
bool get_stealthChop() { return !this->en_spreadCycle(); }
bool get_stored_stealthChop() { return this->stored.stealthChop_enabled; }
void refresh_stepping_mode() { this->en_spreadCycle(!this->stored.stealthChop_enabled); }
void set_stealthChop(const bool stch) { this->stored.stealthChop_enabled = stch; refresh_stepping_mode(); }
bool toggle_stepping_mode() { set_stealthChop(!this->stored.stealthChop_enabled); return get_stealthChop(); }
#endif
void set_chopper_times(const chopper_timing_t &ct) {
TMC2208Stepper::toff(ct.toff);
TMC2208Stepper::hysteresis_end(ct.hend);
TMC2208Stepper::hysteresis_start(ct.hstrt);
}
#if ENABLED(HYBRID_THRESHOLD)
uint32_t get_pwm_thrs() {
return _tmc_thrs(this->microsteps(), this->TPWMTHRS(), planner.settings.axis_steps_per_mm[AXIS_ID]);
}
void set_pwm_thrs(const uint32_t thrs) {
TMC2208Stepper::TPWMTHRS(_tmc_thrs(this->microsteps(), thrs, planner.settings.axis_steps_per_mm[AXIS_ID]));
TERN_(HAS_LCD_MENU, this->stored.hybrid_thrs = thrs);
TERN_(HAS_MARLINUI_MENU, this->stored.hybrid_thrs = thrs);
}
#endif
#if HAS_LCD_MENU
inline void refresh_stepper_current() { rms_current(this->val_mA); }
#if HAS_MARLINUI_MENU
void refresh_stepper_current() { rms_current(this->val_mA); }
#if ENABLED(HYBRID_THRESHOLD)
inline void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); }
void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); }
#endif
#endif
};
@@ -209,50 +227,56 @@ class TMCMarlin<TMC2209Stepper, AXIS_LETTER, DRIVER_ID, AXIS_ID> : public TMC220
{}
uint8_t get_address() { return slave_address; }
uint16_t rms_current() { return TMC2209Stepper::rms_current(); }
inline void rms_current(const uint16_t mA) {
void rms_current(const uint16_t mA) {
this->val_mA = mA;
TMC2209Stepper::rms_current(mA);
}
inline void rms_current(const uint16_t mA, const float mult) {
void rms_current(const uint16_t mA, const float mult) {
this->val_mA = mA;
TMC2209Stepper::rms_current(mA, mult);
}
inline uint16_t get_microstep_counter() { return TMC2209Stepper::MSCNT(); }
uint16_t get_microstep_counter() { return TMC2209Stepper::MSCNT(); }
#if HAS_STEALTHCHOP
inline bool get_stealthChop() { return !this->en_spreadCycle(); }
inline bool get_stored_stealthChop() { return this->stored.stealthChop_enabled; }
inline void refresh_stepping_mode() { this->en_spreadCycle(!this->stored.stealthChop_enabled); }
inline void set_stealthChop(const bool stch) { this->stored.stealthChop_enabled = stch; refresh_stepping_mode(); }
inline bool toggle_stepping_mode() { set_stealthChop(!this->stored.stealthChop_enabled); return get_stealthChop(); }
bool get_stealthChop() { return !this->en_spreadCycle(); }
bool get_stored_stealthChop() { return this->stored.stealthChop_enabled; }
void refresh_stepping_mode() { this->en_spreadCycle(!this->stored.stealthChop_enabled); }
void set_stealthChop(const bool stch) { this->stored.stealthChop_enabled = stch; refresh_stepping_mode(); }
bool toggle_stepping_mode() { set_stealthChop(!this->stored.stealthChop_enabled); return get_stealthChop(); }
#endif
void set_chopper_times(const chopper_timing_t &ct) {
TMC2209Stepper::toff(ct.toff);
TMC2209Stepper::hysteresis_end(ct.hend);
TMC2209Stepper::hysteresis_start(ct.hstrt);
}
#if ENABLED(HYBRID_THRESHOLD)
uint32_t get_pwm_thrs() {
return _tmc_thrs(this->microsteps(), this->TPWMTHRS(), planner.settings.axis_steps_per_mm[AXIS_ID]);
}
void set_pwm_thrs(const uint32_t thrs) {
TMC2209Stepper::TPWMTHRS(_tmc_thrs(this->microsteps(), thrs, planner.settings.axis_steps_per_mm[AXIS_ID]));
TERN_(HAS_LCD_MENU, this->stored.hybrid_thrs = thrs);
TERN_(HAS_MARLINUI_MENU, this->stored.hybrid_thrs = thrs);
}
#endif
#if USE_SENSORLESS
inline int16_t homing_threshold() { return TMC2209Stepper::SGTHRS(); }
int16_t homing_threshold() { return TMC2209Stepper::SGTHRS(); }
void homing_threshold(int16_t sgt_val) {
sgt_val = (int16_t)constrain(sgt_val, sgt_min, sgt_max);
TMC2209Stepper::SGTHRS(sgt_val);
TERN_(HAS_LCD_MENU, this->stored.homing_thrs = sgt_val);
TERN_(HAS_MARLINUI_MENU, this->stored.homing_thrs = sgt_val);
}
#endif
#if HAS_LCD_MENU
inline void refresh_stepper_current() { rms_current(this->val_mA); }
#if HAS_MARLINUI_MENU
void refresh_stepper_current() { rms_current(this->val_mA); }
#if ENABLED(HYBRID_THRESHOLD)
inline void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); }
void refresh_hybrid_thrs() { set_pwm_thrs(this->stored.hybrid_thrs); }
#endif
#if USE_SENSORLESS
inline void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); }
void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); }
#endif
#endif
@@ -269,27 +293,33 @@ class TMCMarlin<TMC2660Stepper, AXIS_LETTER, DRIVER_ID, AXIS_ID> : public TMC266
TMCMarlin(const uint16_t CS, const float RS, const uint16_t pinMOSI, const uint16_t pinMISO, const uint16_t pinSCK, const uint8_t) :
TMC2660Stepper(CS, RS, pinMOSI, pinMISO, pinSCK)
{}
inline uint16_t rms_current() { return TMC2660Stepper::rms_current(); }
inline void rms_current(const uint16_t mA) {
uint16_t rms_current() { return TMC2660Stepper::rms_current(); }
void rms_current(const uint16_t mA) {
this->val_mA = mA;
TMC2660Stepper::rms_current(mA);
}
inline uint16_t get_microstep_counter() { return TMC2660Stepper::mstep(); }
uint16_t get_microstep_counter() { return TMC2660Stepper::mstep(); }
void set_chopper_times(const chopper_timing_t &ct) {
TMC2660Stepper::toff(ct.toff);
TMC2660Stepper::hysteresis_end(ct.hend);
TMC2660Stepper::hysteresis_start(ct.hstrt);
}
#if USE_SENSORLESS
inline int16_t homing_threshold() { return TMC2660Stepper::sgt(); }
int16_t homing_threshold() { return TMC2660Stepper::sgt(); }
void homing_threshold(int16_t sgt_val) {
sgt_val = (int16_t)constrain(sgt_val, sgt_min, sgt_max);
TMC2660Stepper::sgt(sgt_val);
TERN_(HAS_LCD_MENU, this->stored.homing_thrs = sgt_val);
TERN_(HAS_MARLINUI_MENU, this->stored.homing_thrs = sgt_val);
}
#endif
#if HAS_LCD_MENU
inline void refresh_stepper_current() { rms_current(this->val_mA); }
#if HAS_MARLINUI_MENU
void refresh_stepper_current() { rms_current(this->val_mA); }
#if USE_SENSORLESS
inline void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); }
void refresh_homing_thrs() { homing_threshold(this->stored.homing_thrs); }
#endif
#endif
@@ -297,43 +327,6 @@ class TMCMarlin<TMC2660Stepper, AXIS_LETTER, DRIVER_ID, AXIS_ID> : public TMC266
sgt_max = 63;
};
template<typename TMC>
void tmc_print_current(TMC &st) {
st.printLabel();
SERIAL_ECHOLNPGM(" driver current: ", st.getMilliamps());
}
#if ENABLED(MONITOR_DRIVER_STATUS)
template<typename TMC>
void tmc_report_otpw(TMC &st) {
st.printLabel();
SERIAL_ECHOPGM(" temperature prewarn triggered: ");
serialprint_truefalse(st.getOTPW());
SERIAL_EOL();
}
template<typename TMC>
void tmc_clear_otpw(TMC &st) {
st.clear_otpw();
st.printLabel();
SERIAL_ECHOLNPGM(" prewarn flag cleared");
}
#endif
#if ENABLED(HYBRID_THRESHOLD)
template<typename TMC>
void tmc_print_pwmthrs(TMC &st) {
st.printLabel();
SERIAL_ECHOLNPGM(" stealthChop max speed: ", st.get_pwm_thrs());
}
#endif
#if USE_SENSORLESS
template<typename TMC>
void tmc_print_sgt(TMC &st) {
st.printLabel();
SERIAL_ECHOPGM(" homing sensitivity: ");
SERIAL_PRINTLN(st.homing_threshold(), PrintBase::Dec);
}
#endif
void monitor_tmc_drivers();
void test_tmc_connection(LOGICAL_AXIS_DECL(const bool, true));
@@ -355,7 +348,7 @@ void test_tmc_connection(LOGICAL_AXIS_DECL(const bool, true));
#if USE_SENSORLESS
// Track enabled status of stealthChop and only re-enable where applicable
struct sensorless_t { bool LINEAR_AXIS_ARGS(), x2, y2, z2, z3, z4; };
struct sensorless_t { bool NUM_AXIS_ARGS(), x2, y2, z2, z3, z4; };
#if ENABLED(IMPROVE_HOMING_RELIABILITY)
extern millis_t sg_guard_period;
@@ -389,8 +382,8 @@ void test_tmc_connection(LOGICAL_AXIS_DECL(const bool, true));
#endif // USE_SENSORLESS
#endif // HAS_TRINAMIC_CONFIG
#if HAS_TMC_SPI
void tmc_init_cs_pins();
#endif
#endif // HAS_TRINAMIC_CONFIG

View File

@@ -28,13 +28,24 @@
#include <Wire.h>
#include "../libs/hex_print.h"
TWIBus i2c;
TWIBus::TWIBus() {
#if I2C_SLAVE_ADDRESS == 0
Wire.begin(); // No address joins the BUS as the master
#if PINS_EXIST(I2C_SCL, I2C_SDA) && DISABLED(SOFT_I2C_EEPROM)
Wire.setSDA(pin_t(I2C_SDA_PIN));
Wire.setSCL(pin_t(I2C_SCL_PIN));
#endif
Wire.begin(); // No address joins the BUS as the master
#else
Wire.begin(I2C_SLAVE_ADDRESS); // Join the bus as a slave
Wire.begin(I2C_SLAVE_ADDRESS); // Join the bus as a slave
#endif
reset();
}
@@ -45,33 +56,32 @@ void TWIBus::reset() {
}
void TWIBus::address(const uint8_t adr) {
if (!WITHIN(adr, 8, 127)) {
if (!WITHIN(adr, 8, 127))
SERIAL_ECHO_MSG("Bad I2C address (8-127)");
}
addr = adr;
debug(PSTR("address"), adr);
debug(F("address"), adr);
}
void TWIBus::addbyte(const char c) {
if (buffer_s >= COUNT(buffer)) return;
buffer[buffer_s++] = c;
debug(PSTR("addbyte"), c);
debug(F("addbyte"), c);
}
void TWIBus::addbytes(char src[], uint8_t bytes) {
debug(PSTR("addbytes"), bytes);
debug(F("addbytes"), bytes);
while (bytes--) addbyte(*src++);
}
void TWIBus::addstring(char str[]) {
debug(PSTR("addstring"), str);
debug(F("addstring"), str);
while (char c = *str++) addbyte(c);
}
void TWIBus::send() {
debug(PSTR("send"), addr);
debug(F("send"), addr);
Wire.beginTransmission(I2C_ADDRESS(addr));
Wire.write(buffer, buffer_s);
@@ -81,21 +91,60 @@ void TWIBus::send() {
}
// static
void TWIBus::echoprefix(uint8_t bytes, const char pref[], uint8_t adr) {
void TWIBus::echoprefix(uint8_t bytes, FSTR_P const pref, uint8_t adr) {
SERIAL_ECHO_START();
SERIAL_ECHOPGM_P(pref);
SERIAL_ECHOF(pref);
SERIAL_ECHOPGM(": from:", adr, " bytes:", bytes, " data:");
}
// static
void TWIBus::echodata(uint8_t bytes, const char pref[], uint8_t adr) {
void TWIBus::echodata(uint8_t bytes, FSTR_P const pref, uint8_t adr, const uint8_t style/*=0*/) {
union TwoBytesToInt16 { uint8_t bytes[2]; int16_t integervalue; };
TwoBytesToInt16 ConversionUnion;
echoprefix(bytes, pref, adr);
while (bytes-- && Wire.available()) SERIAL_CHAR(Wire.read());
while (bytes-- && Wire.available()) {
int value = Wire.read();
switch (style) {
// Style 1, HEX DUMP
case 1:
SERIAL_CHAR(hex_nybble((value & 0xF0) >> 4));
SERIAL_CHAR(hex_nybble(value & 0x0F));
if (bytes) SERIAL_CHAR(' ');
break;
// Style 2, signed two byte integer (int16)
case 2:
if (bytes == 1)
ConversionUnion.bytes[1] = (uint8_t)value;
else if (bytes == 0) {
ConversionUnion.bytes[0] = (uint8_t)value;
// Output value in base 10 (standard decimal)
SERIAL_ECHO(ConversionUnion.integervalue);
}
break;
// Style 3, unsigned byte, base 10 (uint8)
case 3:
SERIAL_ECHO(value);
if (bytes) SERIAL_CHAR(' ');
break;
// Default style (zero), raw serial output
default:
// This can cause issues with some serial consoles, Pronterface is an example where things go wrong
SERIAL_CHAR(value);
break;
}
}
SERIAL_EOL();
}
void TWIBus::echobuffer(const char pref[], uint8_t adr) {
echoprefix(buffer_s, pref, adr);
void TWIBus::echobuffer(FSTR_P const prefix, uint8_t adr) {
echoprefix(buffer_s, prefix, adr);
LOOP_L_N(i, buffer_s) SERIAL_CHAR(buffer[i]);
SERIAL_EOL();
}
@@ -103,22 +152,22 @@ void TWIBus::echobuffer(const char pref[], uint8_t adr) {
bool TWIBus::request(const uint8_t bytes) {
if (!addr) return false;
debug(PSTR("request"), bytes);
debug(F("request"), bytes);
// requestFrom() is a blocking function
if (Wire.requestFrom(I2C_ADDRESS(addr), bytes) == 0) {
debug("request fail", I2C_ADDRESS(addr));
debug(F("request fail"), I2C_ADDRESS(addr));
return false;
}
return true;
}
void TWIBus::relay(const uint8_t bytes) {
debug(PSTR("relay"), bytes);
void TWIBus::relay(const uint8_t bytes, const uint8_t style/*=0*/) {
debug(F("relay"), bytes);
if (request(bytes))
echodata(bytes, PSTR("i2c-reply"), addr);
echodata(bytes, F("i2c-reply"), addr, style);
}
uint8_t TWIBus::capture(char *dst, const uint8_t bytes) {
@@ -127,7 +176,7 @@ uint8_t TWIBus::capture(char *dst, const uint8_t bytes) {
while (count < bytes && Wire.available())
dst[count++] = Wire.read();
debug(PSTR("capture"), count);
debug(F("capture"), count);
return count;
}
@@ -140,12 +189,12 @@ void TWIBus::flush() {
#if I2C_SLAVE_ADDRESS > 0
void TWIBus::receive(uint8_t bytes) {
debug(PSTR("receive"), bytes);
echodata(bytes, PSTR("i2c-receive"), 0);
debug(F("receive"), bytes);
echodata(bytes, F("i2c-receive"), 0);
}
void TWIBus::reply(char str[]/*=nullptr*/) {
debug(PSTR("reply"), str);
debug(F("reply"), str);
if (str) {
reset();
@@ -170,18 +219,16 @@ void TWIBus::flush() {
#if ENABLED(DEBUG_TWIBUS)
// static
void TWIBus::prefix(const char func[]) {
SERIAL_ECHOPGM("TWIBus::");
SERIAL_ECHOPGM_P(func);
SERIAL_ECHOPGM(": ");
void TWIBus::prefix(FSTR_P const func) {
SERIAL_ECHOPGM("TWIBus::", func, ": ");
}
void TWIBus::debug(const char func[], uint32_t adr) {
void TWIBus::debug(FSTR_P const func, uint32_t adr) {
if (DEBUGGING(INFO)) { prefix(func); SERIAL_ECHOLN(adr); }
}
void TWIBus::debug(const char func[], char c) {
void TWIBus::debug(FSTR_P const func, char c) {
if (DEBUGGING(INFO)) { prefix(func); SERIAL_ECHOLN(c); }
}
void TWIBus::debug(const char func[], char str[]) {
void TWIBus::debug(FSTR_P const func, char str[]) {
if (DEBUGGING(INFO)) { prefix(func); SERIAL_ECHOLN(str); }
}

View File

@@ -142,7 +142,7 @@ class TWIBus {
*
* @param bytes the number of bytes to request
*/
static void echoprefix(uint8_t bytes, const char prefix[], uint8_t adr);
static void echoprefix(uint8_t bytes, FSTR_P const prefix, uint8_t adr);
/**
* @brief Echo data on the bus to serial
@@ -150,8 +150,9 @@ class TWIBus {
* to serial in a parser-friendly format.
*
* @param bytes the number of bytes to request
* @param style Output format for the bytes, 0 = Raw byte [default], 1 = Hex characters, 2 = uint16_t
*/
static void echodata(uint8_t bytes, const char prefix[], uint8_t adr);
static void echodata(uint8_t bytes, FSTR_P const prefix, uint8_t adr, const uint8_t style=0);
/**
* @brief Echo data in the buffer to serial
@@ -160,7 +161,7 @@ class TWIBus {
*
* @param bytes the number of bytes to request
*/
void echobuffer(const char prefix[], uint8_t adr);
void echobuffer(FSTR_P const prefix, uint8_t adr);
/**
* @brief Request data from the slave device and wait.
@@ -192,10 +193,11 @@ class TWIBus {
* @brief Request data from the slave device, echo to serial.
* @details Request a number of bytes from a slave device and output
* the returned data to serial in a parser-friendly format.
* @style Output format for the bytes, 0 = raw byte [default], 1 = Hex characters, 2 = uint16_t
*
* @param bytes the number of bytes to request
*/
void relay(const uint8_t bytes);
void relay(const uint8_t bytes, const uint8_t style=0);
#if I2C_SLAVE_ADDRESS > 0
@@ -237,17 +239,16 @@ class TWIBus {
* @brief Prints a debug message
* @details Prints a simple debug message "TWIBus::function: value"
*/
static void prefix(const char func[]);
static void debug(const char func[], uint32_t adr);
static void debug(const char func[], char c);
static void debug(const char func[], char adr[]);
static inline void debug(const char func[], uint8_t v) { debug(func, (uint32_t)v); }
static void prefix(FSTR_P const func);
static void debug(FSTR_P const func, uint32_t adr);
static void debug(FSTR_P const func, char c);
static void debug(FSTR_P const func, char adr[]);
#else
static inline void debug(const char[], uint32_t) {}
static inline void debug(const char[], char) {}
static inline void debug(const char[], char[]) {}
static inline void debug(const char[], uint8_t) {}
static void debug(FSTR_P const, uint32_t) {}
static void debug(FSTR_P const, char) {}
static void debug(FSTR_P const, char[]) {}
#endif
static void debug(FSTR_P const func, uint8_t v) { debug(func, (uint32_t)v); }
};
extern TWIBus i2c;

View File

@@ -0,0 +1,67 @@
/**
* 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(X_AXIS_TWIST_COMPENSATION)
#include "x_twist.h"
#include "../module/probe.h"
XATC xatc;
bool XATC::enabled;
float XATC::spacing, XATC::start;
xatc_array_t XATC::z_offset; // Initialized by settings.load()
void XATC::reset() {
constexpr float xzo[] = XATC_Z_OFFSETS;
static_assert(COUNT(xzo) == XATC_MAX_POINTS, "XATC_Z_OFFSETS is the wrong size.");
COPY(z_offset, xzo);
start = probe.min_x();
spacing = (probe.max_x() - start) / (XATC_MAX_POINTS - 1);
enabled = true;
}
void XATC::print_points() {
SERIAL_ECHOLNPGM(" X-Twist Correction:");
LOOP_L_N(x, XATC_MAX_POINTS) {
SERIAL_CHAR(' ');
if (!isnan(z_offset[x]))
serial_offset(z_offset[x]);
else
LOOP_L_N(i, 6) SERIAL_CHAR(i ? '=' : ' ');
}
SERIAL_EOL();
}
float lerp(const_float_t t, const_float_t a, const_float_t b) { return a + t * (b - a); }
float XATC::compensation(const xy_pos_t &raw) {
if (!enabled) return 0;
if (NEAR_ZERO(spacing)) return 0;
float t = (raw.x - start) / spacing;
const int i = constrain(FLOOR(t), 0, XATC_MAX_POINTS - 2);
t -= i;
return lerp(t, z_offset[i], z_offset[i + 1]);
}
#endif // X_AXIS_TWIST_COMPENSATION

View File

@@ -0,0 +1,40 @@
/**
* 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"
typedef float xatc_array_t[XATC_MAX_POINTS];
class XATC {
static bool enabled;
public:
static float spacing, start;
static xatc_array_t z_offset;
static void reset();
static void set_enabled(const bool ena) { enabled = ena; }
static float compensation(const xy_pos_t &raw);
static void print_points();
};
extern XATC xatc;

View File

@@ -33,35 +33,35 @@
ZStepperAlign z_stepper_align;
xy_pos_t ZStepperAlign::xy[NUM_Z_STEPPER_DRIVERS];
xy_pos_t ZStepperAlign::xy[NUM_Z_STEPPERS];
#if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS)
xy_pos_t ZStepperAlign::stepper_xy[NUM_Z_STEPPER_DRIVERS];
#if HAS_Z_STEPPER_ALIGN_STEPPER_XY
xy_pos_t ZStepperAlign::stepper_xy[NUM_Z_STEPPERS];
#endif
void ZStepperAlign::reset_to_default() {
#ifdef Z_STEPPER_ALIGN_XY
constexpr xy_pos_t xy_init[] = Z_STEPPER_ALIGN_XY;
static_assert(COUNT(xy_init) == NUM_Z_STEPPER_DRIVERS,
static_assert(COUNT(xy_init) == NUM_Z_STEPPERS,
"Z_STEPPER_ALIGN_XY requires "
#if NUM_Z_STEPPER_DRIVERS == 4
#if NUM_Z_STEPPERS == 4
"four {X,Y} entries (Z, Z2, Z3, and Z4)."
#elif NUM_Z_STEPPER_DRIVERS == 3
#elif NUM_Z_STEPPERS == 3
"three {X,Y} entries (Z, Z2, and Z3)."
#else
"two {X,Y} entries (Z and Z2)."
#endif
);
#define VALIDATE_ALIGN_POINT(N) static_assert(N >= NUM_Z_STEPPER_DRIVERS || Probe::build_time::can_reach(xy_init[N]), \
#define VALIDATE_ALIGN_POINT(N) static_assert(N >= NUM_Z_STEPPERS || Probe::build_time::can_reach(xy_init[N]), \
"Z_STEPPER_ALIGN_XY point " STRINGIFY(N) " is not reachable with the default NOZZLE_TO_PROBE offset and PROBING_MARGIN.")
VALIDATE_ALIGN_POINT(0); VALIDATE_ALIGN_POINT(1); VALIDATE_ALIGN_POINT(2); VALIDATE_ALIGN_POINT(3);
#else // !Z_STEPPER_ALIGN_XY
const xy_pos_t xy_init[] = {
#if NUM_Z_STEPPER_DRIVERS >= 3 // First probe point...
#if NUM_Z_STEPPERS >= 3 // First probe point...
#if !Z_STEPPERS_ORIENTATION
{ probe.min_x(), probe.min_y() }, // SW
#elif Z_STEPPERS_ORIENTATION == 1
@@ -73,7 +73,7 @@ void ZStepperAlign::reset_to_default() {
#else
#error "Z_STEPPERS_ORIENTATION must be from 0 to 3 (first point SW, NW, NE, SE)."
#endif
#if NUM_Z_STEPPER_DRIVERS == 4 // 3 more points...
#if NUM_Z_STEPPERS == 4 // 3 more points...
#if !Z_STEPPERS_ORIENTATION
{ probe.min_x(), probe.max_y() }, { probe.max_x(), probe.max_y() }, { probe.max_x(), probe.min_y() } // SW
#elif Z_STEPPERS_ORIENTATION == 1
@@ -103,14 +103,14 @@ void ZStepperAlign::reset_to_default() {
COPY(xy, xy_init);
#if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS)
#if HAS_Z_STEPPER_ALIGN_STEPPER_XY
constexpr xy_pos_t stepper_xy_init[] = Z_STEPPER_ALIGN_STEPPER_XY;
static_assert(
COUNT(stepper_xy_init) == NUM_Z_STEPPER_DRIVERS,
COUNT(stepper_xy_init) == NUM_Z_STEPPERS,
"Z_STEPPER_ALIGN_STEPPER_XY requires "
#if NUM_Z_STEPPER_DRIVERS == 4
#if NUM_Z_STEPPERS == 4
"four {X,Y} entries (Z, Z2, Z3, and Z4)."
#elif NUM_Z_STEPPER_DRIVERS == 3
#elif NUM_Z_STEPPERS == 3
"three {X,Y} entries (Z, Z2, and Z3)."
#endif
);

View File

@@ -29,10 +29,10 @@
class ZStepperAlign {
public:
static xy_pos_t xy[NUM_Z_STEPPER_DRIVERS];
static xy_pos_t xy[NUM_Z_STEPPERS];
#if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS)
static xy_pos_t stepper_xy[NUM_Z_STEPPER_DRIVERS];
#if HAS_Z_STEPPER_ALIGN_STEPPER_XY
static xy_pos_t stepper_xy[NUM_Z_STEPPERS];
#endif
static void reset_to_default();