Teacup_Firmware icon indicating copy to clipboard operation
Teacup_Firmware copied to clipboard

PID autotune

Open Wurstnase opened this issue 8 years ago • 28 comments

https://github.com/Traumflug/Teacup_Firmware/commit/fcec05777

First commit is done.

This is currently not that simple as it will be in near future.

Go into your printer.x.h and enable PID_AUTOTUNE. In the same moment you deactivate any normal control of your heaters. Upload the firmware and connect your host.

With M104 Sxxx you start the autotune, where xxx is the temperature you want to control. Now you just need to wait about 5 to 10 minutes.

In the end you get some numbers. Like: Ku: 1625, Tu: 3904 This is Ku and Tu in 9.7 fixpoint notation.

With Ziegler-Nichols you can now calculate your PID-values. The standard is pretty good so we use in this example: Kp = 0.6_Ku Ki = 2_Kp/Tu Kd = Kp*Tu/8

PID-values are .10 fixpoint. So with Ku: 1625, Tu: 3904 Kp = 0.6 * Ku * 8 = 7800 Ki = 2 * Kp * (2^7) / Tu = 511 Kd = Kp * Tu / 8 / (2^7) = 47580

Wurstnase avatar Apr 10 '16 10:04 Wurstnase

Looks good! And the plan about ideal for making this a button-click later.

Traumflug avatar Apr 10 '16 10:04 Traumflug

Right. Just using this two values in configtool.

Wurstnase avatar Apr 10 '16 10:04 Wurstnase

I'll split out PID-values for each heater without EEPROM (from PID autotune). https://github.com/Traumflug/Teacup_Firmware/commit/74afc4d57a1e

Next step is the implementation in config-tool. After that i will continue the autotune, because this is more or less a step before.

Wurstnase avatar Apr 12 '16 10:04 Wurstnase

One question to it. Is it a printer or a board-property?

Wurstnase avatar Apr 12 '16 13:04 Wurstnase

So far, heaters and sensors are considered to be a board property. Putting PID to printer configuration while keeping sensors and heaters on the board page is probably confusing.

Traumflug avatar Apr 12 '16 13:04 Traumflug

Yes, I thought the same. Where should I move then bang bang and pid controller and later the pid autotune?!?

Wurstnase avatar Apr 12 '16 15:04 Wurstnase

A good place for PID parameters appears to be the temp sensor page. On this page is already some knowledge on which heaters have a temp sensor and which not. Temp sensors without heater are possible, too.

For the autotune checkbox it looks like a good idea to make this the first item on a new page "Calibration" next to "Board" and "Printer".

There is quite some prior art in one of the older branches. There are three branches starting with issue74..., all of which provide some work in the PID area; including a commit adding P, I and D as part of the heater definition. Another branch on the topic is ProportionalBandPID If you could look into this stuff, sort it, pick the useful parts and drop the others it'd be awesome. Such topic branches only make sense if they eventually lead to new code.

Traumflug avatar Apr 12 '16 18:04 Traumflug

This autotuning procedure could be used to generate data to find parameters for a FOPDT (First-Order Plus Deadtime) model. I think that would provide the transfer function that was mentioned in #188. You'd need to run autotuning and report time, heater output setting and the temperature. FOPDT parameter search, however, is too hard to run it on the MCU. You'd need to to it on a PC. That would be semi-auto-tuning :)

rkonklex avatar Apr 15 '16 18:04 rkonklex

@Wurstnase, does this autotune code allow to do a repeatible measurement of how well temperature regulation works? I mean, we have a number of patches now which mean to improve regulation, so I'd like to measure the quality of each of them. Not trivial, because we all have different hardware, so one strategy might work better here while another might work better there. Having an objective measurement procedure would be great.

Traumflug avatar Apr 15 '16 23:04 Traumflug

This code is somehow called Ziegler-Nichols 'classic' method. Currently my testmachine is a hotend on my ground without cooling. So testing up to 80° is the limit. But this algorithm for autotune is nothing new. Marlin implement this first and the results for PID are very repeatable and the controller is really stable. This mechanism just loop X times through a given temperature. So heating up to 200° at given P and heat 3s longer. Cool down to 200° and cool 3s (I need to look how long exactly) longer. Do this twice and reduce or higher P to have a stable control. After some loop we have the timings of the curve, min/max.

Wurstnase avatar Apr 16 '16 05:04 Wurstnase

I take this means a "no, doesn't allow repeatible measurements".

Traumflug avatar Apr 16 '16 14:04 Traumflug

Yes, and I guess that there is no real method.

Wurstnase avatar Apr 16 '16 21:04 Wurstnase

Thanks for your work on the autotuning feature.

Is this branch working for you in this state or are there some missing commits?

I had to apply the attached diff [1] in order to compile.

But then at the end of the procedure, I receive something like:

Ku: 0 Tu: 5199
Ku: 0 Tu: 4265
Ku: 0 Tu: 4712

Thus, Ku is always null. I'm not able to spot a problem in the code at first glance. Is it something missing in the branch or more likely an error on my side?

[1] compile_fix.txt

asnt avatar Apr 28 '16 09:04 asnt

Hi @asnt thanks for testing. Currently the pid-autotune is not finished. The only way, that Ku becomes zero is, when heater_runtime[h].d becomes zero. This should not happen.

Can you please enable debug? You need to add a new DEBUG in debug.h

#ifdef  DEBUG
  #define DEBUG_ECHO       1
  #define DEBUG_INFO       2
  #define DEBUG_ERRORS     4
  #define DEBUG_DRYRUN     8
  #define DEBUG_PID       16
  #define DEBUG_DDA       32
  #define DEBUG_POSITION  64
  #define DEBUG_AUTOTUNE 128
#else
    // by setting these to zero, the compiler should optimise the relevant code out
    #define     DEBUG_PID               0
    #define     DEBUG_DDA               0
    #define     DEBUG_POSITION  0
    #define     DEBUG_ECHO          0
  #define DEBUG_INFO       0
  #define DEBUG_DRYRUN     0
  #define DEBUG_AUTOTUNE 0
#endif

The debug itself should be already in the heater.c -> https://github.com/Traumflug/Teacup_Firmware/commit/fcec05777b3595dd2094656115c8defafcacf5a9#diff-f3f72c353ecceeca9075f2012e2e8d3dR417

Wurstnase avatar Apr 28 '16 10:04 Wurstnase

One idea. I test this code on a 32bit-board. So maybe this line in heater.c needs a change:

uint16_t Ku = (heaters_runtime[h].d << 17) / (201 * ((heaters_runtime[h].max_temp - heaters_runtime[h].min_temp)));

to

uint16_t Ku = (uint16_t)(((uint32_t)heaters_runtime[h].d << 17) / (201 * ((heaters_runtime[h].max_temp - heaters_runtime[h].min_temp))));

Just a guess.

Wurstnase avatar Apr 28 '16 10:04 Wurstnase

Having a 32-bit CPU doesn't guarantee that all math is done in 32 bits anyways, so yes, good idea.

Traumflug avatar Apr 28 '16 11:04 Traumflug

@Traumflug I will make a patch.

To get the PID-values you need one extra value. The tick-time of your heater. On the current master it is 10ms. We are working on a tick of 250ms.

For a slight overswing: heater-tick = 0.01s = 10ms Ku_f = Ku / 128 Tu_f = Tu / 128 ~~P = 0.6 * Ku_f~~ ~~I = 2 * P / Tu_f * 0.01~~ ~~D = P * Tu_f / 8 / 0.01~~

~~DEFAULT_P = P * 1024 * 4~~ ~~DEFAULT_I = I * 1024 * 16~~ ~~DEFAULT_D = D * 1024 * 8~~

Not 100% sure if this is correct, but this is how I would make a first test.

Edit: I must again think about it.

Wurstnase avatar Apr 28 '16 11:04 Wurstnase

Maybe I'm again wrong with the units. I need to sleep now and will verify my formulas later.

Edit: Don't try unit conversion after 11pm, tired and with dimmed light.

Wurstnase avatar Apr 28 '16 21:04 Wurstnase

for 250ms-heater-tick:

P = DEFAULT_P = 0.6 * Ku * PID_SCALE / 128 I = DEFAULT_I = 64 * P / Tu

~~for D I have a mental block with TH_COUNTS...~~ gone

D = DEFAULT_D = TH_COUNTS * P * Tu / 128 * 4

TH_COUNTS could be implemented directly in the PID-algorithm.

Wurstnase avatar Apr 29 '16 07:04 Wurstnase

Thanks @Wurstnase .

I tried the last commits with the 32bit cast. It seems the tuning procedure loops indefinitely. I will investigate further over the weekend.

asnt avatar Apr 29 '16 18:04 asnt

Did you set PID_MAX to 255? Else it could be not reach the target temperature. This is a feature I want to add later for high power hotends (using 12V heater with 24V).

Wurstnase avatar Apr 29 '16 21:04 Wurstnase

It all works fine now.

The 32bit cast does the trick.

The infinite loop was because of PID_AUTOTUNE not enabled in the printer config. Sorry for the noise.

Now, I can compute the Kp and Ki. Not sure about Kd: what is the TH_COUNTS?

asnt avatar May 01 '16 05:05 asnt

TH_COUNTS

But on the current master and experimental, the heater tick is 10ms. So the calculations above don't count. You could also try the latests commits in heaters_intervals.

Wurstnase avatar May 01 '16 11:05 Wurstnase

what is the TH_COUNTS?

TH_COUNTS simply stretches time. Instead of current and last temperature, current and temperature 8 readings back are compared. I think this can go away unless code runs into accuracy issue, then (difference between the last two readings being to small for a meaningful calculation).

Traumflug avatar May 01 '16 13:05 Traumflug

I think this can go away

Yes, when we take the new 250ms-interval-code.

Wurstnase avatar May 01 '16 13:05 Wurstnase

I found some time to test this code.

Issues currently, the temperature values are too rough. For Ku we calculate the temperature difference of min and max. This value could be ~1°. In my tests Ku varied by factor 2, because with rounding and some scalings this can be 1° or 0,5°.

So especially for the pid-autotune it could be recommended to oversample and expand the temperature at least to 12.4 fixpoint.

Wurstnase avatar May 02 '16 06:05 Wurstnase

@Wurstnase In Marlin, Ku = (4 * d) / (pi * (temp_max - temp_min) / 2) In the current Teacup implementation, Ku = (4 * d) / (pi * (temp_max - temp_min)) Is this not the factor two you are describing?

I tested this code against the heaters_interval branch and the autotuning seems fine. With some minor adaptations for the 250ms ticks and with Ku = (d << 16) / (201 * (temp_max - temp_min)) Tu = (t_low + t_high) << 5

asnt avatar May 12 '16 18:05 asnt

Yes, it's wrong for 9.7 fixpoint. But correct is: Ku = (d << 18) / (201 * (temp_max - temp_min))

It's 1/(1/2)!

Wurstnase avatar May 13 '16 09:05 Wurstnase