minicx
October 4, 2025, 9:23pm
1
I built a custom hotend with a powerful heater cartridge and a small thermal mass in the heater block. This combination created significant temperature oscillations during printing:
Standard PID tuning at 280°C : When printing at 250°C, temperature fluctuations exceeded ±10°C
I tried to implement an adaptive PID that:
Calibrates PID parameters at multiple temperature points (e.g., 210°C, 220°C, 230°C… 280°C)
Stores a lookup table of temperature → (Kp, Ki, Kd) mappings
Dynamically selects PID parameters based on the current target temperature during operation
After calibrating 8 temperature points (210-280°C with 10°C steps) temperature fluctuations reduced from ±10°C to <±3°C across the entire temperature range
The adaptive PID controller uses a “nearest neighbor” approach:
Stores calibrated PID values: pid_table_0: 210.0:29.389:9.796:22.042
At runtime, selects parameters from the closest calibrated temperature
Configuration example:
[extruder]
control: pid
pid_table_0: 210.0:29.389:9.796:22.042
pid_table_1: 220.0:30.123:10.012:22.456
pid_table_2: 230.0:31.456:10.234:23.012
# ... more calibration points
Calibration command:
PID_CALIBRATE HEATER=extruder TARGETS=210,220,230,240,250,260,270,280
Implementation: link
Would this be useful?
6 Likes
minicx:
Would this be useful?
Yes. I’ve been looking for something like that to work with multiple materials.
Right now, I use different printers for different materials.
Interesting, it seems that you can simply interpolate the datapoints to some degree
cardoc
October 5, 2025, 3:28am
4
While useful can’t this be accomplished using macros without changes to klipper code?
I use a macro package (Klipper Printer Additions 3.0b9.1 - More Than Boring Start-G-Code by Christian Vick | Download free STL model | Printables.com ) that accomplishes a similar function with bed mesh VS temperature. It seems it wouldn’t be hard to generate a macro to generate PID values for a range if temperatures and then insert the correct values from the start print macro.
minicx
October 5, 2025, 8:08am
5
Even what you described requires changing the original code too… And don’t you think that tuning the PID in the slicer, rather than in the firmware, is illogical?
minicx
October 5, 2025, 8:12am
6
Something like that?
def _interpolate_pid(self, target_temp):
for i in range(len(self.pid_table) - 1):
t1, kp1, ki1, kd1 = self.pid_table[i]
t2, kp2, ki2, kd2 = self.pid_table[i + 1]
if t1 <= target_temp <= t2:
ratio = (target_temp - t1) / (t2 - t1)
return (
kp1 + (kp2 - kp1) * ratio,
ki1 + (ki2 - ki1) * ratio,
kd1 + (kd2 - kd1) * ratio
)
if target_temp < self.pid_table[0][0]:
return self.pid_table[0][1:]
return self.pid_table[-1][1:]
Is it a good idea to use interpolation in the case of PID?
2 Likes
minicx:
Something like that?
It looks like interpolation.
IDK about good or bad here, but based on points what you showed as example config:
minicx:
Configuration example:
the interpolation error seems to be too small to worry about.
Also, you can always add points and so increase resolution and decrease the error.
(And more sophisticated fit methods would be just an overkill)
Thanks.
1 Like
A linear interpolation is - however within the limits of accuracy (ambient temperature, material, print speed, etc) - an approximation.
But I think it is accurate enough.
1 Like
I’m hoping it’s not just interpolation but actually running multiple PID tuning steps.
I realize that makes the PID turning operation much longer to run but, if it means accurate PID values for different temperatures, then I think it’s worth it.
2 Likes
minicx
October 5, 2025, 9:54pm
10
You’re right - linear interpolation is an approximation, and factors like ambient temperature, airflow, and print speed do affect the optimal PID values. However, for my use case (custom hotend with high power/low thermal mass), even the approximation through linear interpolation showed significant improvement over single-point tuning. The main benefit is that the controller adapts to the drastically different thermal dynamics at 210°C vs 280°C, rather than using parameters optimized for only one temperature.
2 Likes
minicx
October 5, 2025, 9:55pm
11
Yes, it runs full PID autotuning at each temperature point
2 Likes
My 2 cents about implementation.
It seems to me, that adding another pid complicate the code more than it is required. You would have better chances (I think so) if it is incorporated within current one.
The only difference is the allowance to set several value triplets. So simple backward compatible assumption that current triplet is (for example) always at zero temp (or simply min/max), could be enough.
Same for the calibration, if there is only one pid and so the calibration code, you can simply modify it to append/replace 1 value at a time if it is required.
Hope that can slim the actual implementation.
minicx
October 6, 2025, 6:51pm
13
I considered merging into existing ControlPID but chose a separate class to avoid modifying original logic for now.
get
January 29, 2026, 3:41pm
14
Any news on this?
I would like very much to try it
2 Likes
I’d be curious to know how MPC (feed-forward control) would work on your hotend. Have you tried that (it is included in danger klipper) Model Predictive Control - Danger Klipper documentation
minicx
February 5, 2026, 9:02am
16
It is working, so what do you want?
minicx
February 5, 2026, 9:04am
17
I was unaware of this, but I should likely try it, as it is probably a better solution than mine…
it is probably a better solution than mine…
I would not put it that way. From a generic perspective, PID has limitations.
One of the limitations is that tuning is done in specific circumstances, and basically speaking, with specific gain/loss circumstances.
Your approach is completely valid, to make PID fit in a wider temperature range, if it is necessary.
Basically, I would say this is the only solution within the pid loop, I think.
Another one can be to increase PID frequency/decrease command lag, for example, but it should not directly influence your case, I think.
If there is a will to look at the feed-forward, I personally think that a more generic solution can have better versatility: RFC: Cooperative Heaters predictive control by nefelim4ag · Pull Request #6837 · Klipper3d/klipper · GitHub
But from my point of view, it is a little bit out of scope for PID parameter tuning.
Thanks,
-Timofey.
JFYI:
You can enable debug logging by uncommenting and replacing logging.debug → logging.info: klipper/klippy/extras/heaters.py at master · Klipper3d/klipper · GitHub
From my updated understanding, we want the average power to be provided by the Ki, and oscillations dumped by the Kd.
It is hard to guess what is happening without log,
my pure guess is that you can reduce Ki a little, and try to reduce the Kd, and that can help.
So, for example: Kp: 30, Ki: 7, Kd: 15
So, Ki would be laggy, but should still provide average value, where Kd will be less aggressive and should dump itself, I guess.
-Timofey.
master ← nefelim4ag:pid-calibration-analyze
opened 09:57PM - 24 Feb 26 UTC
Right now, some heaters can produce suboptimal pid values after calibration, the… y are suboptimal because they produce large oscillations during standby or printing. Often, it looks like `I` and `D` are large, and decreasing them helps.
That happens, that the calibration of the heater when the time of heating is close (equal) to the time of cooling produces less aggressive PID values.
Those values in the end should be less annoying and less often produce several degrees of oscillations, which can block the machine on `M109` indefinitely.
This PR adds a new param to the `PID_CALIBRATE`, `MAX_POWER`, which allows adjusting power during the calibration.
On top, here is a small analysis section, which advises/guess `MAX_POWER` value to use next.
The point is, reduction might be necessary during calibration, but it is not necessary to reduce power for real printing.
Thanks,
-Timofey
---
Testing:
```
cd ~/klipper
git fetch origin pull/7209/head
git checkout FETCH_HEAD
sudo systemctl restart klipper
```
```
PID_CALIBRATE HEATER=extruder TARGET=235
// PID parameters: pid_Kp=35.189 pid_Ki=9.022 pid_Kd=34.310
```
Graph with default PIDs at steady state:
<img width="1280" height="399" alt="image" src="https://github.com/user-attachments/assets/7e41a534-e874-4110-8129-151efed401da" />
With recommended power:
```
PID_CALIBRATE HEATER=extruder TARGET=235 MAX_POWER=0.599
// PID parameters: pid_Kp=27.901 pid_Ki=6.643 pid_Kd=29.297
```
<img width="1280" height="399" alt="image" src="https://github.com/user-attachments/assets/d2e49089-ddea-42e8-b7d1-57ee4672a769" />
So, for me, there is no significant difference.
@minicx , sorry to bother
Can you give it a try?
master ← nefelim4ag:pid-calibration-imc
opened 06:41PM - 28 Mar 26 UTC
TLDR:
This is an attempt to make one PID calibrator to rule them all.
The ba… sic idea is that one can make a model of the hotend.
We have a gain over the initial heat-up, we have dead time from the relay test, and we can fit the time constant over the initial heat curve to estimate where the heater settles at a specific power.
This data can be used within the internal model control method within the First-Order Plus Dead Time model.
From this data, we can estimate the specific PID values for a specific heater.
So, what it does:
- Records the initial heating curve
- Fit over this curve (it makes calculations more stable over different MAX_POWER values).
- Calculate the gain, where I later used the estimated gain from the fit for the same purposes.
It seems to produce okayish values for the PD, where I is always too small and, as a result, the output PID is sluggish.
As we have relatively short dead time and low inertia on the hotend, there is a "hack" to increase I.
Because normally the output is pretty conservative, because of how the model works (it tries not to overshoot and keep the control smooth), ~~lambda is zeroed to prefer fast responses~~.
One can calibrate with reduced MAX_POWER as in the #7209, to get the initial heating up closer to the asymptote.
It seems to work fine without that.
There are 2 extreme scenarios for the tuning.
One with 100% power (default), and with the least required power.
Least required power should generally provide a better fit, because this is what the model tries to estimate.
One can run the tuned heater at the target temperature, record the average PWM, for example 30%.
That PWM is the least required power to do the test.
It can be long, or it can trigger verify_heater errors, it is recommended to use some margin +5% for example.
<img width="1348" height="528" alt="image" src="https://github.com/user-attachments/assets/5744d1c3-762b-435a-8727-ee124bb972d8" />
Just steady state graphs, extruder:
<img width="1358" height="444" alt="image" src="https://github.com/user-attachments/assets/086af560-db9c-4492-863b-9fc1fbb33855" />
```
Autotune: raw=7.618107/0.450000 | Tu=11.100615/DeadT=1.350078 Gain=1043.620833 tau=105.350765 | Kp=19.188908 Ki=3.553297 Kd=12.870788
Autotune: raw=-7.978697/0.450000 | Tu=11.400670/DeadT=1.350071 Gain=773.799418 tau=70.234177 | Kp=17.308440 Ki=3.205099 Kd=11.572582
Autotune: raw=7.834107/0.450000 | Tu=11.400545/DeadT=1.350055 Gain=597.414908 tau=46.823118 | Kp=15.017210 Ki=2.780851 Kd=9.992967
Autotune: raw=-7.293205/0.450000 | Tu=11.100456/DeadT=1.350052 Gain=483.753974 tau=62.430490 | Kp=24.639525 Ki=4.562698 Kd=16.454413
Autotune: raw=7.834107/0.450000 | Tu=11.400545/DeadT=1.400057 Gain=506.543530 tau=51.938975 | Kp=18.927158 Ki=3.379712 Kd=13.073347
Autotune: final: Kp=18.927158 Ki=3.379712 Kd=13.073347
```
Where one can interpret the Gain and Tau (time constant), like this:
Gain is where the heater will settle with the requested power percentage.
Tau is 63.2%, which is a time where gain will reach ~63.2% of the set temperature.
<img width="2560" height="1472" alt="image" src="https://github.com/user-attachments/assets/c13eb8ef-b13c-485d-9cd3-5b64d194530e" />
Bed:
<img width="1358" height="444" alt="image" src="https://github.com/user-attachments/assets/a8ee92f4-84d3-4043-8013-6632a1c0d983" />
```
Autotune: raw=-6.273359/1.000000 | Tu=161.700000/DeadT=3.150000 Gain=62.092383 tau=70.234177 | Kp=93.620563 Ki=7.430203 Kd=144.218294
Autotune: raw=6.180337/1.000000 | Tu=161.700000/DeadT=3.300000 Gain=46.271708 tau=93.645236 | Kp=159.140931 Ki=12.056131 Kd=258.036022
Autotune: raw=-6.173799/1.000000 | Tu=173.700000/DeadT=3.300000 Gain=56.845383 tau=78.037863 | Kp=108.323635 Ki=8.206336 Kd=175.033169
Autotune: raw=6.260284/1.000000 | Tu=134.100000/DeadT=2.300000 Gain=51.715770 tau=84.380185 | Kp=183.361755 Ki=19.930626 Kd=208.030810
Autotune: final: Kp=183.361755 Ki=19.930626 Kd=208.030810
```
It is possible that it simply overfitted and produced critically dumped results only for me.
So, I will be glad to see the data from others.
Thanks,
-Timofey
---
_P.S.
I would expect that MAX_POWER reduction suggested in #7209, which produces the symmetric relay test, should work fine. But it didn't work for the large/slow heaters, and didn't work for some with fast response times, so... I got myself drawn into the rabbit hole.
Where I hope that now, I have some understanding of WHY it works in a way that it works - I think, it is hard to define magic ZN numbers for every possible case, and the "proper" relay method is more complicated, so here we are._
---
~~Deadtime detection is suboptimal and may be susceptible to the noise of the sensor.~~
I hope the time between the control signal and the peak is a good enough estimation for our short dead time.
---
Links, because I will lose them:
- https://www.ijireeice.com/upload/2016/may-16/IJIREEICE%2047.pdf - IMC-PID
- https://www.researchgate.net/publication/288897834_The_SIMC_method_for_smooth_PID_controller_tuning - Simple IMC PID - Sigurd Skogestad
PID_CALIBRATE .. MAX_POWER=1 - it should be sane
Check how it is holding temperature, for example, 250 °C
Remember the average power from 2
PID_CALIBRATE .. MAX_POWER=<average power +5% or so> - ideal case for calibration, where we have many samples, it is asymptote & etc
Same test, how does it go
Thanks,
-Timofey