mirror of
https://github.com/MarlinFirmware/Marlin.git
synced 2025-02-16 22:37:35 +00:00
✨ PELTIER_BED (#27334)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
This commit is contained in:
parent
e310f20279
commit
37bc552f3b
6 changed files with 144 additions and 54 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
@ -1908,39 +1908,64 @@ 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;
|
||||
|
||||
// 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;
|
||||
|
||||
#else // !PIDTEMPBED && !BED_LIMIT_SWITCHING
|
||||
|
||||
// Simple (noisy) "bang-bang" bed heating
|
||||
temp_bed.soft_pwm_amount = temp_bed.is_below_target() ? MAX_BED_POWER >> 1 : 0;
|
||||
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
temp_bed.soft_pwm_amount = 0;
|
||||
WRITE_HEATER_BED(LOW);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (is_bed_preheating()) {
|
||||
temp_bed.soft_pwm_amount = MAX_BED_POWER >> 1;
|
||||
break;
|
||||
}
|
||||
|
||||
#if ENABLED(PIDTEMPBED)
|
||||
|
||||
//
|
||||
// PID Bed Heating
|
||||
//
|
||||
temp_bed.soft_pwm_amount = WITHIN(temp_bed.celsius, BED_MINTEMP, BED_MAXTEMP) ? (int)get_pid_output_bed() >> 1 : 0;
|
||||
|
||||
#else // !PIDTEMPBED
|
||||
|
||||
//
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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*/)
|
||||
) {
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue