PID Controlled Exhaust Fan (Quiet!)

Update 2025-11-17: Now a PI controller (proportional + integral) instead of just proportional

Update 2025-11-22: M141 macro & auto-stop after a print has finished
Update 2025-11-23: bugfixes to CHAMBER_CONFIG, updated tuning values


I think we are all frustrated with the exhaust_fan! It is loud, and the watermark controller leaves horizontal banding on the prints!

This overhaul allows the fan to be quieter, actually throttle, and even reach 100% fan in the rare cases where it is necessary!


Implementation

You will need to edit both printer.cfg and macro.cfg, and optionally change your Starting G-Code to have chamber temperature controlled by your cliser

Macro.cfg

Replace your START_PRINT with

Modified START_PRINT macro
[gcode_macro START_PRINT]
gcode:
    {% if printer.save_variables.variables.was_interrupted|float == True %}
        PRINT_END #Clear the last printed power-off information
        CHANG_ROOT_MAIN
        SET_DISPLAY_TEXT
    {% endif %}
    
    {% set cur_target = printer.save_variables.variables.ch_target|default(0.0) %}
    {% if cur_target <= 0 %}
        SAVE_VARIABLE VARIABLE=ch_target VALUE=32.0
        RESPOND PREFIX="chamber" MSG="START_PRINT: chamber target defaulted to 32C"
    {% else %}
        RESPOND PREFIX="chamber" MSG="START_PRINT: chamber target kept at {cur_target}C"
    {% endif %}
    CHAMBER_ON

    CLEAR_PAUSE
    M117 Clean Nozzle
    CLEAN_NOZZLE
    SET_GCODE_OFFSET Z=0
    M117 Eddy Calibration
    Z_OFFSET_CALIBRATION METHOD=force_overlay BED_TEMP={printer.heater_bed.target}
    SET_GCODE_VARIABLE MACRO=_global_var VARIABLE=has_z_offset_calibrated VALUE=True
    G28 Z
    M117 Mesh Calibration
    BED_MESH_CALIBRATE
    SET_GCODE_VARIABLE MACRO=_global_var VARIABLE=has_z_offset_calibrated VALUE=False
    save_last_file
    M117 {last_file}

Printer.cfg

  1. Remove the entire [temperature_fan exhaust_fan section]
  2. Add the following
Additions to printer.cfg
[temperature_sensor chamber_temp]
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PC4
min_temp: 0
max_temp: 80
[fan_generic exhaust_fan]
pin: PB0
tachometer_pin: PB1
tachometer_ppr: 1
tachometer_poll_interval: 0.0046
cycle_time: 0.01
max_power: 1.0
kick_start_time: 0.80
[save_variables]
filename: ~/variables.cfg


[gcode_macro CHAMBER_LOOP]
gcode:
    {% set temp = printer["temperature_sensor chamber_temp"].temperature %}
    {% set target = printer.save_variables.variables.ch_target|default(32.0) %}
    {% set kp = printer.save_variables.variables.ch_kp|default(0.15) %}
    {% set ki = printer.save_variables.variables.ch_ki|default(0.020) %}
    {% set minfan = printer.save_variables.variables.ch_minfan|default(0.15) %}
    {% set maxfan = printer.save_variables.variables.ch_maxfan|default(1.00) %}
    {% set i_max = printer.save_variables.variables.ch_i_max|default(0.6) %}
    {% set i_primed = printer.save_variables.variables.ch_i_primed|default(0) %}
    {% set auto_off_delta = 3.0 %}
    {% set i_acc = printer.save_variables.variables.ch_integral|default(0.0) %}

    {% set error = temp - target %}
    {% set i_acc = i_acc + (error * ki) %}

    {% if i_acc > i_max %}
        {% set i_acc = i_max %}
        RESPOND PREFIX="ch" MSG="Accumulator clamped Ki={ki}"
    {% endif %}
    {% if i_acc < (0 - i_max) %}
        {% set i_acc = 0 - i_max %}
        RESPOND PREFIX="ch" MSG="Accumulator clamped Ki={ki}"
    {% endif %}

    {% if i_primed == 0 and ( error < +1.0 and error > -1.0 ) %}
        {% set i_acc = 0 %}
        {% set i_primed = 1 %}
        RESPOND PREFIX="ch" MSG="Target reached"
    {% endif %}
    {% if i_primed == 1 and error <= (0 - auto_off_delta) %}
        CHAMBER_OFF
    {% endif %}
    
    {% set raw = (error * kp) + i_acc %}

    {% if raw < 0 %}
        {% set duty = 0 %}
    {% elif raw > maxfan %}
        {% set duty = maxfan %}
        RESPOND PREFIX="ch" MSG="Fan at maximum S={duty}"
    {% else %}
        {% set duty = raw %}
    {% endif %}

    {% if duty < minfan and duty > 0 %}
        {% set duty = 0 %}
    {% endif %}

    SET_FAN_SPEED FAN=exhaust_fan SPEED={duty}

    SAVE_VARIABLE VARIABLE=ch_last_duty VALUE={duty}
    SAVE_VARIABLE VARIABLE=ch_integral  VALUE={i_acc}
    SAVE_VARIABLE VARIABLE=ch_i_primed  VALUE={i_primed}

    UPDATE_DELAYED_GCODE ID=CHAMBER_TIMER DURATION=60


[gcode_macro CHAMBER_ON]
gcode:
    SAVE_VARIABLE VARIABLE=ch_integral VALUE=0.0
    SAVE_VARIABLE VARIABLE=ch_i_primed VALUE=0
    UPDATE_DELAYED_GCODE ID=CHAMBER_TIMER DURATION=60
    RESPOND PREFIX="ch" MSG="Chamber control on."
    

[gcode_macro CHAMBER_OFF]
gcode:
    UPDATE_DELAYED_GCODE ID=CHAMBER_TIMER DURATION=0
    SET_FAN_SPEED FAN=exhaust_fan SPEED=0
    SAVE_VARIABLE VARIABLE=ch_integral VALUE=0
    RESPOND PREFIX="ch" MSG="Chamber control off, I reset."
    CH_STATUS_STOP


[gcode_macro CHAMBER_CONFIG]
gcode:
    {% set old_target = printer.save_variables.variables.ch_target|default(32.0) %}
    {% set old_kp     = printer.save_variables.variables.ch_kp|default(0.30) %}
    {% set old_minfan = printer.save_variables.variables.ch_minfan|default(0.40) %}
    {% set old_maxfan = printer.save_variables.variables.ch_maxfan|default(1.00) %}
    {% set old_ki     = printer.save_variables.variables.ch_ki|default(0.00) %}
    {% set old_i_max  = printer.save_variables.variables.ch_i_max|default(0.30) %}

    {% set target = params.TARGET|default(old_target)|float %}
    {% set kp     = params.KP|default(old_kp)|float %}
    {% set minfan = params.MINFAN|default(old_minfan)|float %}
    {% set maxfan = params.MAXFAN|default(old_maxfan)|float %}
    {% set ki     = params.KI|default(old_ki)|float %}
    {% set i_max  = params.I_MAX|default(old_i_max)|float %}

    SAVE_VARIABLE VARIABLE=ch_target  VALUE={target}
    SAVE_VARIABLE VARIABLE=ch_kp      VALUE={kp}
    SAVE_VARIABLE VARIABLE=ch_minfan  VALUE={minfan}
    SAVE_VARIABLE VARIABLE=ch_maxfan  VALUE={maxfan}
    SAVE_VARIABLE VARIABLE=ch_ki      VALUE={ki}
    SAVE_VARIABLE VARIABLE=ch_i_max   VALUE={i_max}

    RESPOND PREFIX="ch" MSG="Config updated: T={target} Kp={kp} Ki={ki} clamp={i_max} min={minfan} max={maxfan}"


[gcode_macro CH_STATUS]
gcode:
    {% set temp_raw = printer["temperature_sensor chamber_temp"].temperature %}
    {% set target_raw = printer.save_variables.variables.ch_target|default(32.0) %}
    {% set kp = printer.save_variables.variables.ch_kp|default(0.15) %}
    {% set error_raw = (temp_raw - target_raw) * kp %}
    {% set i_raw = printer.save_variables.variables.ch_integral|default(0.00) %}

    {% set temp_1 = (temp_raw * 10) // 1 %}
    {% set temp = temp_1 / 10 %}
    {% set target_1 = (target_raw * 10) // 1 %}
    {% set target = target_1 / 10 %}
    {% set error_3 = (error_raw * 1000) // 1 %}
    {% set error = error_3 / 1000 %}
    {% set i_3 = (i_raw * 1000) // 1 %}
    {% set i_val = i_3 / 1000 %}

    {% set duty = printer.save_variables.variables.ch_last_duty|default(0.0) %}
    {% set fan_i = (duty * 100) // 1 %}

    {% set rpm_raw = printer["fan_generic exhaust_fan"].rpm|default(0) %}
    {% set rpm_i = (rpm_raw * 1) // 1 %}
    {% set rpm = rpm_i %}

    RESPOND PREFIX="ch" MSG="T={target} A={temp} P={error} I={i_val} F={fan_i}% R={rpm}"


[delayed_gcode CHAMBER_TIMER]
gcode:
    CHAMBER_LOOP


[gcode_macro CH_STATUS_LOOP]
gcode:
    CH_STATUS
    UPDATE_DELAYED_GCODE ID=CH_STATUS_TIMER DURATION=60


[delayed_gcode CH_STATUS_TIMER]
gcode:
    CH_STATUS_LOOP


[gcode_macro CH_STATUS_STOP]
gcode:
    RESPOND PREFIX="ch" MSG="Live status stopped."
    UPDATE_DELAYED_GCODE ID=CH_STATUS_TIMER DURATION=0


[gcode_macro CH_RESET_I]
gcode:
    SAVE_VARIABLE VARIABLE=ch_integral VALUE=0
    RESPOND PREFIX="ch" MSG="Integral reset."


[gcode_macro M141]
gcode:
    {% set s = params.S|default(0)|float %}

    SAVE_VARIABLE VARIABLE=ch_target VALUE={s}
    RESPOND PREFIX="ch" MSG="M141: chamber target set to {s}C"

    {% if s <= 0 %}
        CHAMBER_OFF
    {% else %}
        CHAMBER_ON
    {% endif %}


[gcode_macro M191]
gcode:
    {% set s = params.S|default(-9999)|float %}
    {% set r = params.R|default(-9999)|float %}

    {% if r != -9999 %}
        {% set target = r %}
    {% else %}
        {% set target = s %}
    {% endif %}

    SAVE_VARIABLE VARIABLE=ch_target VALUE={target}
    RESPOND PREFIX="ch" MSG="M191: target={target}C"

    {% if target <= 0 %}
        CHAMBER_OFF
    {% else %}
        CHAMBER_ON

        {% if r != -9999 %}
            {% set tol = 1.0 %}
            {% set t_min = target - tol %}
            {% set t_max = target + tol %}
            TEMPERATURE_WAIT SENSOR="temperature_sensor chamber_temp" MIN={t_min} MAX={t_max}
        {% else %}
            TEMPERATURE_WAIT SENSOR="temperature_sensor chamber_temp" MIN={target}
        {% endif %}
    {% endif %}

Starting G-Code

This part is optional, as the default behavior with just the above Klipper modifications results in the same behavior as stock (exhaust fan regulates at 32C, just better).

If you want slicer control to set other chambers, see Chamber temperature control - #5 by PicobelloBV and PrusaSlicer profile for Sovol Zero! - #13 by rpcyan for OrcaSlicer and PrusaSlicer/SuperSlicer implementations.


Adjusting Chamber temperature via Console

To set other temperatures, use any M141 / M191 command:

M141 S33 ; set and continue
M191 S33 ; set and wait
M191 R33 ; set and wait (either heating or cooling; tolerance of +/-1 deg)

or

CHAMBER_CONFIG TARGET=33

(I tried to preserve the old dashboard card control, but I don’t think that’s possible given the limitations below)


Tuning

These values work well for me, and are set as defaults, so hopefully additional tuning is not required.

The integral term should really help cope with different filament, bed, and room temperatures. But I admit I am hot a high temperature filament guy (yet).

  • Chamber Temp = 32°C
  • Kp = 0.20
  • Ki = 0.10
  • Minimum fan = 0.14
  • I_max = 0.80
  • kick_start_time = 0.80

All of these parameters, except the fan’s kick time, are configurable with CHAMBER_CONFIG and can be changed on the fly without editing the configuration file & restarting the firmware. These y will also persist across prints by being stored in saved_variables.cfg (where other unrelated variables are also stored)

To Tune:

  1. run CH_STATUS_LOOP in the console
  2. Use Kp to adjust your gain until you reach an oscillation, then halve it (rule of thumb)
    1. This will likely undershoot & that’s OK, let Ki fix the remainder
  3. Slowly increase Ki to reduce error
  4. Adjust min_fan if you notice stall conditions

This is a classic PI controller (no Derivative term is needed for something this slow) and there are plenty of online tutorials out there to assist. I used CH_STATUS_LOOP and pasted the contents into a spreadsheet for plotting. This is admittedly tedious, but frankly chamber temp is pretty forgiving of imperfect tuning.

I’ll continue to post my coefficients refinements here, especially as I try other filaments.


Chamber Temperatures

According to the Chamber Heating Module for Zero product page

  • ≤30°C for PLA. HP-PLA, PLA-CF (door & lid open)
  • 50°C for PETG, PETG-CF
    • This is different than generic online online advice of keeping the chamber below 45°C to avoid heat creep related problems late in the print. But presumably those printers don’t have the zero’s powerful extruder fan
    • @Liberty4Ever has noted that PETG clogs occur when the chamber reaches 61°C
  • 60°C for ABS, ASA, PA/PC

Limitations

Sovol’s embedded / ultra-stripped-down implementation of Klipper did not make this easy to implement. Specifically:

  • The built-in watermark control method could be somewhat improved with a smaller delta to reduce banding, but this would mean the fan would continue to be loud and bang-bang even quicker
  • The built-in PID control method is entirely broken and does NOT follow proper PID behavior at all. It isn’t even proportional!
    • Best I can determine is that the fan_speed is not proportional to the error signal (chamber-target) , but simply to just the chamber temperature. While a moderately successful gain could be found under narrow circumstances, a change in filament or even the room’s temperature throws this off. It just isn’t worth pursuing
  • While the Temperature dashboard is similar to before, the “Exhaust Fan” had to be replaced with “Chamber Temp”, losing the target temperature reporting and setting. Fan speed and RPM are also missing.
    • The Exhaust Fan percentage and RPM are viewable in the Miscellaneous card
    • any speed adjustments made here are overridden by the chamber controller at the next loop cycle
  • Variables (like the integral accumulator) are not persistent and instead have to be written to the filesystem to be called upon from one loop execution to the next
  • no default Klipper or custom auto-PID tuning macro
  • Chamber thermistor suffers from poor placement, airflow, wall’s thermal inertia, and power supply heat, per Chamber Temperature Sensor Whacked?

Known bugs

  • Chamber cooling does not always continue after print has finished
  • The reported RPM is quite inaccurate - 35% speed reported about 6500 RPM, and higher speed report less RPM, not more, with 100% being around around 1020 RPM

To-do

  • Plotting and/or logging without copy-pasting from console
  • Stall detection & automatic increase of minfan

Chamber Heater

While this is about the exhaust fan, not the chamber heater, they are obviously related. I hope this may be a jumping off point for those struggling with the new chamber heater accessory (honestly, I’m on the fence as to whether I’ll purchase one when they’re back in stock)


Nevermore or other chamber re-circulation filters

I have not yet implemented this on my printer. Anyone who has should expect to have different chamber characteristics and should perform a manual tuning outlined above.

Given the issues with the chassis temperature sensor, I would expect additional re-circulatory air flow, whether from a filter or a heater, will help improve the chassis temperature sensor. I would also expect it to slow down the initial bed heating.


Changelog

  • 2025-11-17
    • increased loop period from 1s to 10s. chamber moves slowly, and I was getting very quick fan pulses yesterday that made me worried I’d burnout the fan in the long term
    • integral windup protection
    • removed offset/trim value; deprecated by integral term
    • automatic integral reset when off
    • shorter console status lines
  • 2025-11-22
    • Changed loop period from 10s to 5s for convenience
    • Added M141 & M191 like @PicobelloBV did along with corresponding Starting G-code Chamber Temperature Control
    • Reset integral accumulator once target temperature is initially reached (“primed”) to avoid windup
    • Modified START_PRINT macro to maintain stock 32C behavior without blocking other desired temperatures being issued from the slicer
  • 2025-11-22 continued
    • Actually increased loop period to 60s to reduce filesystem writes - don’t want to wear out the onboard eMMC!
    • some bugfixes (accumulator was broken)
    • stop the chamber controller once a print has ended and it has cooled down a few degrees
    • added some console messages when the various conditionals are reached
  • 2025-11-23
    • Fixed CHAMBER_CONFIG bug where changes would not save
    • Updated Ki to be much stronger since polling is less frequent; other values tweaked
4 Likes

bump for major update.

Let me know how this works for you!

2 Likes

Works great for me. Printing PETG enclosed with 50 c max on the chamber.

Thank you

2 Likes

I have updated start print macro and printer.cfg, now it does throttle nicely when I sent M141 commands. But I get errors when using M191:

Also it seems to send M191 commands when starting a print, cancelling the print with an error. Not sure where that command comes from, I did not see M191 in the START_PRINT.

Any ideas where I might have made a mistake?

Typo on my part, try replacing the M141 & M191 macros with this. I made the following changes:

  • Fixed the min/max bug
  • If the print bed isn’t already on, turn it on
    • OrcaSlicer sends its chamber “Activate temperature control” command before the starting gcode, so if you set the temp that way it’ll never actually begin the print, just start controlling the exhaust fan without any heat load.
    • Gueses the desired print bed temp based on solely the chamber setpoint
      • bed = 0.5 * chamber * chamber - 3 * chamber + 110, which hits these three key points:
      • PLA 65° & 30°, PETG 85° & 50°, and ASA 110° & 60°
      • Limits bed to 115°C in case the bed hasn’t been tuned
    • The actual desired bed temp will still be set, but not until the chamber’s gotten within tolerance and the Starting G-Code is finally executed
  • Widened starting tolerance to ±10°
    • Excessive waiting without the extruder’s heat load and fans mixing the air isn’t ideal behavior, especially as chamber temp is less important when so close to the bed
    • Matter of preference, and filament, ±15° may be better for PETG, and ±5° may be desired for the the higher temp folks,
    • ±1°with still air and the poor physical chamber_temp sensor placement is just silly
    • This starts a PLA print at 20° chamber, and PETG at 40°, for their respective 30°C and 50°C targets, which seems like a reasonable all-rounder behavior
    • The actual PID loop remains tolerance much tighter, ±1°C or so, this just dictates how long to wait initially
  • Refactored M191 as a “waiting wrapper” around M141 to reduce code duplication
[gcode_macro M141]
gcode:
    {% set s = params.S|default(0)|float %}

    SAVE_VARIABLE VARIABLE=ch_target VALUE={s}

    {% if s <= 0 %}
        CHAMBER_OFF
        RESPOND PREFIX="ch" MSG="M141: target={s}C (off)"
    {% else %}
        {% set bed_target_cur = printer.heater_bed.target|default(0.0) %}

        {% if bed_target_cur <= 0 %}
            {% set bed_est = (0.050 * s * s) - (3.0 * s) + 110.0 %}
            {% set bed_round_src = bed_est + 0.5 %}
            {% set bed_temp = bed_round_src // 1 %}
            {% if bed_temp > 115 %}
                {% set bed_temp = 115 %}
            {% endif %}
            M140 S{bed_temp}
            RESPOND PREFIX="ch" MSG="M141: bed not set, guessing {bed_temp}C"
            {% set bed_report = bed_temp %}
        {% else %}
            {% set bed_1 = bed_target_cur * 1 %}
            {% set bed_report = bed_1 // 1 %}
        {% endif %}

        CHAMBER_ON
        RESPOND PREFIX="ch" MSG="M141: target={s}C bed={bed_report}C"
    {% endif %}

[gcode_macro M191]
gcode:
    {% set s = params.S|default(-9999)|float %}
    {% set r = params.R|default(-9999)|float %}

    {% if r != -9999 %}
        {% set target = r %}
    {% else %}
        {% set target = s %}
    {% endif %}

    {% set tol = 10.0 %}
    {% set t_min = target - tol %}
    {% set t_max = target + tol %}

    M141 S{target}

    {% if target > 0 %}
        {% if r != -9999 %}
            RESPOND PREFIX="ch" MSG="M191 R: target={target}C tol +/-{tol}C"
            TEMPERATURE_WAIT SENSOR="temperature_sensor chamber_temp" MINIMUM={t_min} MAXIMUM={t_max}
        {% else %}
            RESPOND PREFIX="ch" MSG="M191 S: target={target}C tol -{tol}C"
            TEMPERATURE_WAIT SENSOR="temperature_sensor chamber_temp" MINIMUM={t_min}
        {% endif %}
    {% endif %}
1 Like

Thanks for the help, it is working again :slight_smile:

Regarding chamber temperatures, I’d also like to cite @Fabio 's experience that PETG clogs at 55°C, which is considerably less than @Liberty4Ever 's 61°C that I initially quoted. This further corroborates the chamber heater suggested 50°C for PETG.

1 Like

Bugfix: Incorrect RPM

The reported exhaust_fan RPM was bogus and reporting slower values when set above 35% because the poll interval was incorrect.

[fan_generic exhaust_fan]
pin: PB0
tachometer_pin: PB1
tachometer_ppr: 1
tachometer_poll_interval: 0.0019 #was 0.0046
cycle_time: 0.05 #was 0.01
max_power: 1.0
kick_start_time: 0.3

I checked the whole range for aliasing or other errors, and I got the expected smooth quadratic fan curve. This can now report up to ~15.8 krpm, so a bit of headroom over my 100% 14.2 krpm for hardware variation.

This means the stock/typical 80% fan speed is actually 12 krpm.

Lowest fan speed

Lengthening the cycle time ( reducing the PWM frequency) to from 0.01s to 0.05s (100Hz to 20Hz) achieves a slightly lower minimum fan speed. I’m now able to get 2 krpm (12%), whereas before the best I could achieve was 14%.

Even longer cycle times don’t help.

Kick

Surprisingly, adjusting kick_start_time doesn’t influence the minimum achieved speed. I can set it to 0, or remove the line altogether, and 12% still works. I suspect the firmware is adding a kick of its own though, since I see it report 3krpm for a moment before it settles in.

Of course, no kick isn’t robust, so here are various durations & achieved (& reported) speeds if you want to determine your own trade-offs. While there’s a clear audible difference between actual response time and reported peak RPM, that’s harder to quantify.

kick [s] peak revs [krpm] throttle [%] peak SPL [dB(A)] notes
0.0 3 13% 38
0.2 3 13% 38 imperceptible kick
0.3 3.5 14% 38 hardly perceptible kick
0.4 5 21% 39
1.1 8 40% 44 stock/waterfall min
2.3 11 71% 52
3.0 12 80% 54 stock/waterfall max
4.6 14 100% 58 actual max

I’ll use 0.3s for now, but I wonder if deliberately leaving it long may help with the PID’s initial overshoot…

SPL decibel

I placed a cheap decibel meter about 3ft away in front, door and lid closed, to record the steady state intensity for a given RPM. The Zero, without exhaust, idles at 37dB(A), which is the same as my background environment when the printer is off.

Nice and i am currently just replacing them with alix press fan , the smaller one for the mainboard is swapped , still working on custom holder yet its working more quite

Working on buck converted for the exhaust fan till i find a replacement ( accidentally ordered a 12v )

I have a voron fan for the back i am working on next designing it from other design and making it partially a mk4s super sized version

I’ll try an beat that noise sound

@Rooroo4u I’m not sure I follow all the changes you’re making. Photos or links would be helpful. I’m reluctant to make hardware changes since, while they may be quieter, the replacements may not provide adequate cooling.

Assuming they are powerful enough, my software changes above should work, although perhaps with different tuning.

Stirring the air

Now that I’m trying to reach >50°C I can see the frustration!

I’m happy to report that stirring the air (recirculation) helps a lot. Using the nozzle cooling fan (mainboard fan0), and to a lesser extent the heat break fan (toolhead fan1) speeds up the process & achieves a higher ceiling.

These fans effectively already kick in during a M141, but during the print itself. With M191’s wait, there is no fan activity the temperature is reached. Which may never even happen - without the fans, it took me nearly an hour to even reach 30°C

Once I work out some logic, I’ll add these (with variables/flags to disable) to the above macros. But for now:

SET_FAN_SPEED FAN=fan0 SPEED=1 # nozzle cooling fan 100%
M104 S46 # heat break fan turns on above 45C, doesn't cook filament

Larger recirculation

I still haven’t gotten around to a Nevermore / fan3 setup, but when I do, there’s really only two possibilities:

  • slower
    • since it is mixing all the chamber air, both above and below the bed, it ultimately just has more work to do
  • faster
    • The bed is only hitting its ~250W power limit for a few minutes
    • The rest of the time (30-60min), it is at the thermal setpoint, drawing maybe 20% / 50W
    • More mixing means the bed is cooler longer, ultimately putting more heat into the chamber itself

Regardless, it is convenient to use the already-existing extruder fans.