# Manual heater PID constant derivation by example

**URL:** <https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486>\
**Category:** Configs\
**Created:** [April 3, 2022, 10:10pm UTC](https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486 "2022-04-03T22:10:35Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![ReXT3D](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/rext3d/32/200_2.png) [@ReXT3D](https://klipper.discourse.group/u/ReXT3D)\
**Post date:** [April 3, 2022, 10:10pm UTC](https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486/1 "2022-04-03T22:10:35Z")

</div>

**Background**

Many people (myself included in my early 3D printing days) implicitly expect off-the-shelf near “perfection” of the heater performance of their printer. It takes time and practice to learn and appreciate that PID controller tuning is a largely empirical process without a one size fits all solution. Different printer firmware uses different implementations of the PID controllers and different methodologies to derive (approximate) the process PID constants. When this is combined with a practically infinite number of printer designs and configurations, corner cases exist where the predefined solutions can struggle or falter. The purpose of this post is to document a reasonably simple to follow approach that will allow people to experiment with manual tuning of the Klipper PID controller in those corner cases.

**Basic Process Steps**

- Establish typical average PWM drive value for the heater being characterized
- Reconfigure Klipper `printer.cfg` to allow manual open-loop control of the heater
- Perform an open-loop step response test of the ‘process’
- Use the data stored in `klippy.log` to model key process parameters: process gain, process time constant, process dead time
- Use the key process parameters to derive Klipper PID constants using one of the many available tuning techniques, such as Ziegler-Nichols, Cohen-Coon, etc.
- Verify heater response and stability using the manually derived PID constants

**Detailed Procedure**

The following summary outlines the steps that I took to manually derive the PID constants for my heavily modified CR-10S Pro heated bed.

1. Establish typical average PWM value for heater when printing. For example, set bed to 70C, allow it to come up to temperature and stabilize, then note the average PWM value. This is your `<TARGET_PWM>` value. In my example we will use 25% or `<TARGET_PWM>` value of 0.25, because this is roughly the average PWM value that my bed uses to control at around 70 degrees C. This will give process gain specific to your selected target temperature.

2. Modify your Klipper printer configuration in `printer.cfg` to comment out the `[heater_bed]` block. Define new `[fan_generic]` block using the same output pin. This will allow you to enter PWM drive values for the bed manually in an open-loop fashion. Define new `[temperature_sensor]` block. This will allow you to monitor and log the bed temperature. In my example using a Duet 3 Mini 5+ the configuration is as follows:

3. Restart Klipper and home your printer - homing will activate the steppers and ensures that Klipper data logging remains active throughout this exercise. Then set initial bed PWM by entering the following Klipper console commands at the same time (web interfaces like Fluidd allow multi-line console commands to be sent at once):

4. Once bed temperature is completely stable and no longer changes, increase the bed PWM by entering the following Klipper console commands at the same time:

5. Enter the following command: `SET_FAN_SPEED FAN=bed_power SPEED=0` then restore your original Klipper printer configuration in `printer.cfg`.

6. Download `klippy.log` and locate the heater\_bed PWM drive commands and heater\_bed temperature log. You will need to extract the following column entries from the log file:

7. Create a spreadsheet with the data extracted above. The spreadsheet will need TIME, POWER and TEMP columns. For example, it may look like this:

8. Open [https://pidtuner.com/](https://pidtuner.com/) in your browser and complete the initial three steps of the process:

9. Download the Control Specialists Loop Tuning Calculator spreadsheet from [https://controlspecialists.co.uk/wp-content/uploads/2017/08/Control-Specialists-Ltd-Loop-Tuning-Calculations-V4.5.xlsx](https://controlspecialists.co.uk/wp-content/uploads/2017/08/Control-Specialists-Ltd-Loop-Tuning-Calculations-V4.5.xlsx). Alternatively, if you would like to derive the PID constants by yourself, there are many references and papers available that describe how to obtain the PID constants from process parameters.

10. Enter the process model parameters k, tao and theta into KP, TC and DT cells of the SELF REGULATING LOOP RULES spreadsheet as highlighted below. If desired, adjust the SAFETY FACTOR. Collect values of P (Gain), I (sec/rep) and D (secs) from the PID row of the chosen tuning technique (for example COHEN COON as highlighted below). NOTE: The calculated values of the PID constants are very sensitive to the value of the process dead time (theta or DT), because dead time is “the enemy” of closed loop control. It is therefore very important to establish the dead time as accurately as possible. The value can be confirmed manually by looking at the raw data - see **Process Parameters** section below.

11. Calculate your PID constants to use in Klipper `printer.cfg` as follows:

12. Test your new PID constants by modifying `printer.cfg` and heating the bead to a desired temperature. Note the behaviour of the temperature and especially PWM power commanded by the Klipper PID loop. Below is a comparison of the PID loop performance of my printer bed with Klipper auto-tuned PID constants and PID constants derived manually as described above:

`// Klipper auto-tune PID parameters: pid_Kp = 70.14 pid_Ki = 1.218 pid_Kd = 1010.1`

 ![image](https://global.discourse-cdn.com/free1/uploads/klipper/original/2X/0/0bbd5a91877abdf37e392733489d8e737b83bbca.png)

`// Manual Cohen-Coon PID parameters: pid_Kp = 211.7 pid_Ki = 15.68 pid_Kd = 423.4`

 ![image](https://global.discourse-cdn.com/free1/uploads/klipper/original/2X/1/1450cbcf7617a0e9f31a49a4f17563437dbf0392.png)

It is worth noting that the manually tuned PID constants result in substantially tighter control, as evidenced by virtually no temperature overshoot (0.1 deg.C in practice) and very aggressive power control. As shown above I obtained those using SAFETY FACTOR of 1. In some cases such aggressive loop could become unstable resulting in temperature and power oscillations. In those cases a higher safety factor value would be appropriate, for example 1.5. Feel free to experiment with your specific setup - the beauty here is that once you establish the thermal model of your setup, you can derive the PID constants using any technique of your preference and with your chosen safety factor (or effectively stability margin).

**Process Parameters**

The following graph illustrates the meaning of the three key process parameters: gain (change in process variable PV divided by change in controller output CO), time constant (time it takes PV to reach 0.63 of total PV change, shown as tau and calculated from the end of the dead time) and dead time (time it takes from the change in CO to the point where the maximum PV slope line intersects the original level of PV line, shown as td). Unfortunately I do not recall where I came across it, so I am unable to credit the source.

 ![StepTest](https://global.discourse-cdn.com/free1/uploads/klipper/original/2X/7/76a2b423decdf588dcf268f60875f6e3b4404901.png)

**Additional Notes**

I have not spent much time experimenting with the hot end PID constants because in my case the Klipper auto-tuned values work very well. In principle, identical approach can be used for the hot end but it will require some more creativity in editing `printer.cfg` because Klipper will not function without a defined & functional `[extruder]` block.

**Revisions**

2022-04-07: Revised content of steps 7 to 10 in order to fix my incorrect interpretation of `PID_PARAM_BASE`. The overall method and final results are not affected by this revision.

2022-04-11: I seem to have experienced a “senior moment” when compiling step 6. Despite copying the content of my own notes from several months ago, I am unable to reproduce the presence of `Received 746305.064772: {"id": 1750268944, "method": "gcode/script", "params": {"script": "SET_FAN_SPEED FAN=bed_power SPEED=0.25"}}` in the logs. Furthermore, I am not sure how my original logs included these stamps since they originate from `webhooks.py` I therefore revised steps 3 to 6 with a workaround that inserts time stamps in the logs using the `SET_IDLE_TIMEOUT` command.

2023-10-14: Included a note in step 10 to emphasize the high sensitivity of the calculated PID constants to the value of process dead time (theta / DT). Added an illustration visualizing the process parameters for those who wish to derive them manually, instead of using [https://pidtuner.com/](https://pidtuner.com/).

2024-01-14: Revised step 3 to include printer homing to keep data logging active.

---

<div class="post-metadata">

**Author:** ![Venomx99](https://avatars.discourse-cdn.com/v4/letter/v/cdc98d/32.png) [@Venomx99](https://klipper.discourse.group/u/Venomx99)\
**Post date:** [June 24, 2022, 7:21pm UTC](https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486/2 "2022-06-24T19:21:02Z")

</div>

Hi ReXT3D. I’m trying to follow your guide but when I look at my klippy.log there is a large amount of time stats missing. It seems to only record 60s after the timeout command is issued. Is there some setting I need to change to get the klippy.log to record everything?

Below is my klippy.log and the I used.

idle\_timeout: Timeout set to 10022.00 s

idle\_timeout: Timeout set to 10027.00 s

[klippy.log](https://klipper.discourse.group/uploads/short-url/kCtJ1eL7Ywx3nKX6vu0u9NSRz2i.log) (2.7 MB)

---

<div class="post-metadata">

**Author:** ![ReXT3D](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/rext3d/32/200_2.png) [@ReXT3D](https://klipper.discourse.group/u/ReXT3D)\
**Post date:** [June 24, 2022, 8:54pm UTC](https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486/3 "2022-06-24T20:54:42Z")

</div>

Hi and welcome to the community.

The best thing to do to prevent the logging from pausing would be to home the printer just before you issue the timeout command in order to enable the steppers. Another alternative would be to enable the hot end heater at some low temperature (like 50C). Either of these will force Klipper to continue logging every second.

I should perhaps add this to the original procedure…

---

<div class="post-metadata">

**Author:** ![Venomx99](https://avatars.discourse-cdn.com/v4/letter/v/cdc98d/32.png) [@Venomx99](https://klipper.discourse.group/u/Venomx99)\
**Post date:** [June 24, 2022, 9:22pm UTC](https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486/4 "2022-06-24T21:22:25Z")

</div>

Ah, ok. I’ll do that. Thank you.

---

<div class="post-metadata">

**Author:** ![tiduck](https://avatars.discourse-cdn.com/v4/letter/t/977dab/32.png) [@tiduck](https://klipper.discourse.group/u/tiduck)\
**Post date:** [October 14, 2023, 6:36am UTC](https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486/5 "2023-10-14T06:36:37Z")

</div>

Excel Link failure

---

<div class="post-metadata">

**Author:** ![ReXT3D](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/rext3d/32/200_2.png) [@ReXT3D](https://klipper.discourse.group/u/ReXT3D)\
**Post date:** [October 14, 2023, 1:06pm UTC](https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486/6 "2023-10-14T13:06:22Z")

</div>

> [@ReXT3D](#):
>
> [https://controlspecialists.co.uk/wp-content/uploads/2017/08/Control-Specialists-Ltd-Loop-Tuning-Calculations-V4.5.xlsx](https://controlspecialists.co.uk/wp-content/uploads/2017/08/Control-Specialists-Ltd-Loop-Tuning-Calculations-V4.5.xlsx)

Wayback Machine (a.k.a. Internet Archive) is often your friend:

[http://web.archive.org/web/20220407202544/https://controlspecialists.co.uk/wp-content/uploads/2017/08/Control-Specialists-Ltd-Loop-Tuning-Calculations-V4.5.xlsx](http://web.archive.org/web/20220407202544/https://controlspecialists.co.uk/wp-content/uploads/2017/08/Control-Specialists-Ltd-Loop-Tuning-Calculations-V4.5.xlsx)

But I would generally recommend that anyone going to this level of PID loop tuning detail to try doing it by hand 🙂

---

<div class="post-metadata">

**Author:** ![ManjaroBlack](https://avatars.discourse-cdn.com/v4/letter/m/ba9def/32.png) [@ManjaroBlack](https://klipper.discourse.group/u/ManjaroBlack)\
**Post date:** [January 13, 2024, 3:23pm UTC](https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486/7 "2024-01-13T15:23:58Z")

</div>

Ah! I should have read the entire thread first. I too had only 60 seconds of logs for each idle\_timeout. I set my extruder temp to 30C and started again. Logs look to be logging more than 1 minute now. Maybe add this to step 3? Thanks for the tutorial BTW. I will report back my results when I finish.

---

<div class="post-metadata">

**Author:** ![ManjaroBlack](https://avatars.discourse-cdn.com/v4/letter/m/ba9def/32.png) [@ManjaroBlack](https://klipper.discourse.group/u/ManjaroBlack)\
**Post date:** [January 14, 2024, 2:48pm UTC](https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486/8 "2024-01-14T14:48:19Z")

</div>

Success! It is so much better than before, although there is still a big overshoot, it settles down to the setpoint and controls very well. Before it wouldn’t really settle back down to the setpoint and kept adding power.

Before:

```auto
pid_Kp: 28.414
pid_Ki: 0.071
pid_Kd: 2838.589

```

After:

```auto
pid_Kp: 26.78314
pid_Ki: 0.307901
pid_Kd: 347

```

 ![IMG_0139](https://global.discourse-cdn.com/free1/uploads/klipper/original/2X/b/b7ef58632b55e91470cfea5ca36f9fb245e063da.jpeg)

---

<div class="post-metadata">

**Author:** ![ReXT3D](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/rext3d/32/200_2.png) [@ReXT3D](https://klipper.discourse.group/u/ReXT3D)\
**Post date:** [January 14, 2024, 4:24pm UTC](https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486/9 "2024-01-14T16:24:40Z")

</div>

That’s great, glad that manual tuning helped your situation. Also thanks for the reminder about truncated logging - I forgot to revise step 3 but just did it now.

You may be able to bring the degree and duration of overshoot down by additional hand tweaking of the PID constants. For example, you can try to increase your derivative term and then evaluate the results - don’t be shy and double the value, for example. Just make sure you don’t leave the printer unattended when you do this.

I would like to emphasize again how important is the correct value of the process dead time to the values of the PID constants. I personally ALWAYS derive the dead time myself “by hand”.

---

<div class="post-metadata">

**Author:** ![ManjaroBlack](https://avatars.discourse-cdn.com/v4/letter/m/ba9def/32.png) [@ManjaroBlack](https://klipper.discourse.group/u/ManjaroBlack)\
**Post date:** [January 15, 2024, 1:45am UTC](https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486/10 "2024-01-15T01:45:03Z")

</div>

Yes, I looked at the raw data to validate the dead time. I came up with pretty much the same number, but there is a range to choose from. Would longer or shorter in that range be better? I will experiment with larger pid\_Kd values and see what that does.

---

<div class="post-metadata">

**Author:** ![ReXT3D](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/rext3d/32/200_2.png) [@ReXT3D](https://klipper.discourse.group/u/ReXT3D)\
**Post date:** [January 15, 2024, 7:48pm UTC](https://klipper.discourse.group/t/manual-heater-pid-constant-derivation-by-example/2486/11 "2024-01-15T19:48:46Z")

</div>

Longer dead time would generally mean less tight / more relaxed PID control as the control loop needs to “chill out” waiting for the response from the system. I was just recommending that you are not afraid to experiment a bit since tuning control loops is not exact science - most methods are largely empirical.
