Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Marlin Peltier Logic implementation #27334

Merged
merged 9 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Marlin/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,40 @@
//#define BED_LIMIT_SWITCHING // Keep the bed temperature within BED_HYSTERESIS of the target
#endif

/**
* Peltier Bed - Heating and Cooling
*
* A Peltier device transfers heat from one side to the other in proportion to the amount of
* current flowing through the device and the direction of current flow. So the same device
* can both heat and cool.
*
* When "cooling" in addition to rejecting the heat transferred from the hot side to the cold
* side, the dissipated power (voltage * current) must also be rejected. Be sure to set up a
* fan that can be powered in sync with the Peltier unit.
*
* This feature is only set up to run in bang-bang mode because Peltiers don't handle PWM
* well without filter circuitry.
*
* Since existing 3D printers are made to handle relatively high current for the heated bed,
* we can use the heated bed power pins to control the Peltier power using the same G-codes
* as the heated bed (M140, M190, etc.).
*
* A second GPIO pin is required to control current direction.
* Two configurations are possible: Relay and H-Bridge
*
* (At this time only relay is supported. H-bridge requires 4 MOS switches configured in H-Bridge.)
*
* Power is handled by the bang-bang control loop: 0 or 255.
* Cooling applications are more common than heating, so the pin states are commonly:
* LOW = Heating = Relay Energized
* HIGH = Cooling = Relay in "Normal" state
*/
//#define PELTIER_BED
#if ENABLED(PELTIER_BED)
#define PELTIER_DIR_PIN -1 // Relay control pin for Peltier
#define PELTIER_DIR_HEAT_STATE LOW // The relay pin state that causes the Peltier to heat
#endif

// Add 'M190 R T' for more gradual M190 R bed cooling.
//#define BED_ANNEALING_GCODE

Expand Down
2 changes: 1 addition & 1 deletion Marlin/Configuration_adv.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@
//
#if DISABLED(PIDTEMPBED)
#define BED_CHECK_INTERVAL 5000 // (ms) Interval between checks in bang-bang control
#if ENABLED(BED_LIMIT_SWITCHING)
#if ANY(BED_LIMIT_SWITCHING, PELTIER_BED)
#define BED_HYSTERESIS 2 // (°C) Only set the relevant heater state when ABS(T-target) > BED_HYSTERESIS
#endif
#endif
Expand Down
10 changes: 9 additions & 1 deletion Marlin/src/inc/Conditionals-5-post.h
Original file line number Diff line number Diff line change
Expand Up @@ -3039,7 +3039,7 @@
#endif

/**
* Heated bed requires settings
* Heated Bed required settings
*/
#if HAS_HEATED_BED
#ifndef MIN_BED_POWER
Expand All @@ -3049,6 +3049,14 @@
#define MAX_BED_POWER 255
#endif
#define WRITE_HEATER_BED(v) WRITE(HEATER_BED_PIN, (v) ^ ENABLED(HEATER_BED_INVERTING))
#if ENABLED(PELTIER_BED)
/**
* A "Heated Bed" Peltier device needs a direction (heat/cool) to be
* implemented by a relay (single pin) or H-bridge (2 or 4 pin).
* H-Bridge can also perform PWM. (Not recommended for Peltier devices).
*/
#define WRITE_PELTIER_DIR(v) WRITE(PELTIER_DIR_PIN, (v) ? PELTIER_DIR_HEAT_STATE : !PELTIER_DIR_HEAT_STATE)
#endif
#endif

/**
Expand Down
7 changes: 7 additions & 0 deletions Marlin/src/inc/Warnings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -921,3 +921,10 @@
#if defined(ARDUINO_ARCH_HC32) && F_CPU == 200000000
#warning "HC32 clock is assumed to be 200MHz. If this isn't the case for your board please submit a report so we can add support."
#endif

/**
* Peltier with PIDTEMPBED
*/
#if ALL(PELTIER_BED, PIDTEMPBED)
#warning "PELTIER_BED with PIDTEMPBED requires extra circuitry. Use with caution."
#endif
134 changes: 86 additions & 48 deletions Marlin/src/module/temperature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1861,7 +1861,7 @@ void Temperature::mintemp_error(const heater_id_t heater_id OPTARG(ERR_INCLUDE_T
static bool last_pause_state;
#endif

do {
do { // 'break' out of this block

#if DISABLED(PIDTEMPBED)
if (PENDING(ms, next_bed_check_ms)
Expand All @@ -1887,38 +1887,63 @@ void Temperature::mintemp_error(const heater_id_t heater_id OPTARG(ERR_INCLUDE_T
constexpr bool bed_timed_out = false;
#endif

if (!bed_timed_out) {
if (is_bed_preheating()) {
temp_bed.soft_pwm_amount = MAX_BED_POWER >> 1;
}
else {
#if ENABLED(PIDTEMPBED)
temp_bed.soft_pwm_amount = WITHIN(temp_bed.celsius, BED_MINTEMP, BED_MAXTEMP) ? (int)get_pid_output_bed() >> 1 : 0;
#else
// Check if temperature is within the correct band
if (WITHIN(temp_bed.celsius, BED_MINTEMP, BED_MAXTEMP)) {
#if ENABLED(BED_LIMIT_SWITCHING)
if (bed_timed_out) break;

if (is_bed_preheating()) {
temp_bed.soft_pwm_amount = MAX_BED_POWER >> 1;
break;
}

// Range-limited "bang-bang" bed heating
if (temp_bed.is_above_target(BED_HYSTERESIS))
temp_bed.soft_pwm_amount = 0;
else if (temp_bed.is_below_target(BED_HYSTERESIS))
temp_bed.soft_pwm_amount = MAX_BED_POWER >> 1;
#if ENABLED(PIDTEMPBED)

#else // !PIDTEMPBED && !BED_LIMIT_SWITCHING
//
// PID Bed Heating
//
temp_bed.soft_pwm_amount = WITHIN(temp_bed.celsius, BED_MINTEMP, BED_MAXTEMP) ? (int)get_pid_output_bed() >> 1 : 0;

// Simple (noisy) "bang-bang" bed heating
temp_bed.soft_pwm_amount = temp_bed.is_below_target() ? MAX_BED_POWER >> 1 : 0;
#else // !PIDTEMPBED

#endif
}
else {
//
// Range-limited "bang-bang" bed heating
//

// Bed Off if the current bed temperature is outside the allowed range
if (!WITHIN(temp_bed.celsius, BED_MINTEMP, BED_MAXTEMP)) {
temp_bed.soft_pwm_amount = 0;
WRITE_HEATER_BED(LOW);
break;
}

#if ENABLED(PELTIER_BED)
/**
* Peltier bang-bang maintains max bed power but changes
* current direction to switch between heating/cooling.
*/
if (temp_bed.target && temp_bed.is_above_target(BED_HYSTERESIS)) { // Fast Cooling
temp_bed.soft_pwm_amount = MAX_BED_POWER;
temp_bed.peltier_dir_heating = false;
}
else if (temp_bed.is_below_target(BED_HYSTERESIS)) { // Heating
temp_bed.soft_pwm_amount = MAX_BED_POWER;
temp_bed.peltier_dir_heating = true;
}
else
temp_bed.soft_pwm_amount = 0; // Off (ambient cooling)

#else // !PELTIER_BED

#if ENABLED(BED_LIMIT_SWITCHING)
if (temp_bed.is_above_target(BED_HYSTERESIS)) // Cooling (implicit off)
temp_bed.soft_pwm_amount = 0;
WRITE_HEATER_BED(LOW);
}
else if (temp_bed.is_below_target(BED_HYSTERESIS)) // Heating
temp_bed.soft_pwm_amount = MAX_BED_POWER >> 1;
#else // Not bed limit switching
temp_bed.soft_pwm_amount = temp_bed.is_below_target() ? MAX_BED_POWER >> 1 : 0;
#endif
}
}

#endif // !PELTIER_BED

#endif // !PIDTEMPBED

} while (false);
}
Expand Down Expand Up @@ -2924,6 +2949,10 @@ void Temperature::init() {
#endif

#if HAS_HEATED_BED
#if ENABLED(PELTIER_BED)
SET_OUTPUT(PELTIER_DIR_PIN);
OUT_WRITE(PELTIER_DIR_PIN, !PELTIER_DIR_HEAT_STATE);
#endif
#ifdef BOARD_OPENDRAIN_MOSFETS
OUT_WRITE_OD(HEATER_BED_PIN, ENABLED(HEATER_BED_INVERTING));
#else
Expand Down Expand Up @@ -3904,6 +3933,9 @@ void Temperature::isr() {

#if HAS_HEATED_BED
_PWM_MOD(BED, soft_pwm_bed, temp_bed);
#if ENABLED(PELTIER_BED)
WRITE_PELTIER_DIR(temp_bed.peltier_dir_heating);
#endif
#endif

#if HAS_HEATED_CHAMBER
Expand Down Expand Up @@ -4370,15 +4402,15 @@ void Temperature::isr() {
#if HAS_TEMP_SENSOR
/**
* Print a single heater state in the form:
* Bed: " B:nnn.nn /nnn.nn"
* Chamber: " C:nnn.nn /nnn.nn"
* Probe: " P:nnn.nn"
* Cooler: " L:nnn.nn /nnn.nn"
* Board: " M:nnn.nn"
* SoC: " S:nnn.nn"
* Redundant: " R:nnn.nn /nnn.nn"
* Extruder: " T0:nnn.nn /nnn.nn"
* With ADC: " T0:nnn.nn /nnn.nn (nnn.nn)"
* Extruder: " T0:nnn.nn /nnn.nn"
* Bed: " B:nnn.nn /nnn.nn"
* Chamber: " C:nnn.nn /nnn.nn"
* Cooler: " L:nnn.nn /nnn.nn"
* Probe: " P:nnn.nn"
* Board: " M:nnn.nn"
* SoC: " S:nnn.nn"
* Redundant: " R:nnn.nn /nnn.nn"
* With ADC: " T0:nnn.nn /nnn.nn (nnn.nn)"
*/
static void print_heater_state(const heater_id_t e, const_celsius_float_t c, const_celsius_float_t t
OPTARG(SHOW_TEMP_ADC_VALUES, const float r)
Expand All @@ -4396,12 +4428,12 @@ void Temperature::isr() {
#if HAS_TEMP_CHAMBER
case H_CHAMBER: k = 'C'; break;
#endif
#if HAS_TEMP_PROBE
case H_PROBE: k = 'P'; show_t = false; break;
#endif
#if HAS_TEMP_COOLER
case H_COOLER: k = 'L'; break;
#endif
#if HAS_TEMP_PROBE
case H_PROBE: k = 'P'; show_t = false; break;
#endif
#if HAS_TEMP_BOARD
case H_BOARD: k = 'M'; show_t = false; break;
#endif
Expand All @@ -4428,6 +4460,17 @@ void Temperature::isr() {
delay(2);
}

/**
* Print all heater states followed by power data on a single line.
* See print_heater_state for heater output strings.
* Power output strings are in the format:
* Extruder: " @:nnn"
* Bed: " B@:nnn"
* Peltier: " P@:H/C"
* Chamber: " C@:nnn"
* Cooler: " L@:nnn"
* Hotends: " @0:nnn @1:nnn ..."
*/
void Temperature::print_heater_states(const int8_t target_extruder
OPTARG(HAS_TEMP_REDUNDANT, const bool include_r/*=false*/)
) {
Expand Down Expand Up @@ -4459,15 +4502,10 @@ void Temperature::isr() {
HOTEND_LOOP() print_heater_state((heater_id_t)e, degHotend(e), degTargetHotend(e) OPTARG(SHOW_TEMP_ADC_VALUES, rawHotendTemp(e)));
#endif
SString<100> s(F(" @:"), getHeaterPower((heater_id_t)target_extruder));
#if HAS_HEATED_BED
s.append(" B@:", getHeaterPower(H_BED));
#endif
#if HAS_HEATED_CHAMBER
s.append(" C@:", getHeaterPower(H_CHAMBER));
#endif
#if HAS_COOLER
s.append(" C@:", getHeaterPower(H_COOLER));
#endif
TERN_(HAS_HEATED_BED, s.append(F(" B@:"), getHeaterPower(H_BED)));
TERN_(PELTIER_BED, s.append(F(" P@:"), temp_bed.peltier_dir_heating ? 'H' : 'C'));
TERN_(HAS_HEATED_CHAMBER, s.append(F(" C@:"), getHeaterPower(H_CHAMBER)));
TERN_(HAS_COOLER, s.append(F(" L@:"), getHeaterPower(H_COOLER)));
#if HAS_MULTI_HOTEND
HOTEND_LOOP() s.append(F(" @"), e, ':', getHeaterPower((heater_id_t)e));
#endif
Expand Down
3 changes: 3 additions & 0 deletions Marlin/src/module/temperature.h
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,9 @@ typedef struct HeaterInfo : public TempInfo {
uint8_t soft_pwm_amount;
bool is_below_target(const celsius_t offs=0) const { return (target - celsius > offs); } // celsius < target - offs
bool is_above_target(const celsius_t offs=0) const { return (celsius - target > offs); } // celsius > target + offs
#if ENABLED(PELTIER_BED)
bool peltier_dir_heating; // = false
#endif
} heater_info_t;

// A heater with PID stabilization
Expand Down
Loading