Skip to content

Commit

Permalink
✨ PELTIER_BED (MarlinFirmware#27334)
Browse files Browse the repository at this point in the history
Co-authored-by: Scott Lahteine <[email protected]>
  • Loading branch information
pbmgit and thinkyhead authored Oct 6, 2024
1 parent e310f20 commit 37bc552
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 50 deletions.
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 @@ -1882,7 +1882,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 @@ -1908,38 +1908,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 @@ -2945,6 +2970,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 @@ -3926,6 +3955,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 @@ -4392,15 +4424,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 @@ -4418,12 +4450,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 @@ -4450,6 +4482,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 @@ -4481,15 +4524,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

0 comments on commit 37bc552

Please sign in to comment.