diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 64187f41bfcb..b9ed44dffac0 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -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 diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 0cd5a6433cf9..f3a5dbcb9c38 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -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 diff --git a/Marlin/src/inc/Conditionals-5-post.h b/Marlin/src/inc/Conditionals-5-post.h index df6efb1ed630..0cbe0176e136 100644 --- a/Marlin/src/inc/Conditionals-5-post.h +++ b/Marlin/src/inc/Conditionals-5-post.h @@ -3039,7 +3039,7 @@ #endif /** - * Heated bed requires settings + * Heated Bed required settings */ #if HAS_HEATED_BED #ifndef MIN_BED_POWER @@ -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 /** diff --git a/Marlin/src/inc/Warnings.cpp b/Marlin/src/inc/Warnings.cpp index f10408dd9c44..0ccc9d6acf68 100644 --- a/Marlin/src/inc/Warnings.cpp +++ b/Marlin/src/inc/Warnings.cpp @@ -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 diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp index 5c11df2c6a27..000f9bccb417 100644 --- a/Marlin/src/module/temperature.cpp +++ b/Marlin/src/module/temperature.cpp @@ -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) @@ -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); } @@ -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 @@ -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 @@ -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) @@ -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 @@ -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*/) ) { @@ -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 diff --git a/Marlin/src/module/temperature.h b/Marlin/src/module/temperature.h index bb38922d7e34..67276e4d8a23 100644 --- a/Marlin/src/module/temperature.h +++ b/Marlin/src/module/temperature.h @@ -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