PID Autotune: big update, use algorithm from Marlin firmware
authorMichael Moon <triffid.hunter@gmail.com>
Mon, 31 Dec 2012 04:17:00 +0000 (15:17 +1100)
committerMichael Moon <triffid.hunter@gmail.com>
Wed, 2 Jan 2013 00:50:21 +0000 (11:50 +1100)
src/modules/tools/temperaturecontrol/PID_Autotuner.cpp
src/modules/tools/temperaturecontrol/PID_Autotuner.h
src/modules/tools/temperaturecontrol/TemperatureControl.cpp
src/modules/tools/temperaturecontrol/TemperatureControl.h

index 020b96e..a8c70cf 100644 (file)
@@ -16,24 +16,20 @@ void PID_Autotuner::on_module_loaded()
 void PID_Autotuner::begin(TemperatureControl *temp, double target, StreamOutput *stream)
 {
     if (t)
-    {
-        t->target_temperature = 0.0;
-    }
+        t->heater_pin->set(0);
 
     t = temp;
 
-    t->p_factor = 1000000000.0;
-    t->i_factor = 0.0;
-    t->d_factor = 0.0;
-    t->i_max    = 0.0;
+    t->target_temperature = 0.0;
 
-    t->target_temperature = target;
+    target_temperature = target;
 
     for (cycle = 0; cycle < 8; cycle++)
     {
-        cycles[cycle].ticks = 0;
-        cycles[cycle].t_max = 0.0;
-        cycles[cycle].t_min = 1000.0;
+        cycles[cycle].ticks_high = 0;
+        cycles[cycle].ticks_low  = 0;
+        cycles[cycle].t_max      = 0.0;
+        cycles[cycle].t_min      = 1000.0;
     }
     cycle = 0;
 
@@ -41,6 +37,9 @@ void PID_Autotuner::begin(TemperatureControl *temp, double target, StreamOutput
 
     s->printf("%s: Starting PID Autotune\n", t->designator.c_str());
 
+    bias = d = t->heater_pin->max_pwm() >> 1;
+
+    output = true;
     last_output = true;
 }
 
@@ -51,58 +50,89 @@ uint32_t PID_Autotuner::on_tick(uint32_t dummy)
     if (t == NULL)
         return 0;
 
-    if (last_output == false && (t->o > 128))
+    if (t->last_reading > (target_temperature + 0.25))
+        output = false;
+    else if (t->last_reading < (target_temperature - 0.25))
+        output = true;;
+
+    if (last_output == false && output)
     {
-        s->printf("Cycle %d:\n\tMax: %5.1f  Min: %5.1f  time: %3.1fs\n", cycle, cycles[cycle].t_max, cycles[cycle].t_min, cycles[cycle].ticks / 20.0);
+        s->printf("Cycle %d:\n\tMax: %5.1f  Min: %5.1f  high time: %3.1fs  low time: %3.1fs\n", cycle, cycles[cycle].t_max, cycles[cycle].t_min, cycles[cycle].ticks_high / 20.0, cycles[cycle].ticks_low / 20.0);
+
+        // this code taken from http://github.com/ErikZalm/Marlin/blob/Marlin_v1/Marlin/temperature.cpp
+        bias += (d * (cycles[cycle].ticks_high - cycles[cycle].ticks_low) * (1000.0 / 20.0)) / ((cycles[cycle].ticks_high + cycles[cycle].ticks_low) * (1000.0 / 20.0));
+        bias = confine(bias, 20, t->heater_pin->max_pwm() - 20);
+        if (bias > (t->heater_pin->max_pwm() / 2))
+            d = t->heater_pin->max_pwm() - 1 - bias;
+        else
+            d = bias;
+        // end code from Marlin firmware
+
         cycle++;
         if (cycle == PID_AUTOTUNER_CYCLES)
         {
+            t->heater_pin->set(0);
             t->set_desired_temperature(0.0);
             // TODO: finish
             double tmax_avg   = 0.0,
                    tmin_avg   = 0.0,
-                   tcycle_avg = 0.0;
-            for (cycle = 1; cycle < PID_AUTOTUNER_CYCLES; cycle++)
+                   t_high_avg = 0.0,
+                   t_low_avg  = 0.0;
+            for (cycle = PID_AUTOTUNER_CYCLES - 3; cycle < PID_AUTOTUNER_CYCLES; cycle++)
             {
-                tmax_avg += cycles[cycle].t_max;
-                tmin_avg += cycles[cycle].t_min;
-                tcycle_avg += cycles[cycle].ticks;
+                tmax_avg   += cycles[cycle].t_max;
+                tmin_avg   += cycles[cycle].t_min;
+                t_high_avg += cycles[cycle].ticks_high;
+                t_low_avg  += cycles[cycle].ticks_low;
             }
-            tmax_avg /= 7.0;
-            tmin_avg /= 7.0;
-            tcycle_avg /= 7.0;
-            s->printf("Averages over last %d cycles: Max: %5.1fc  Min: %5.1fc  samples: %3.0f\n", PID_AUTOTUNER_CYCLES - 2, tmax_avg, tmin_avg, tcycle_avg);
+            tmax_avg   /= (PID_AUTOTUNER_CYCLES - 1.0);
+            tmin_avg   /= (PID_AUTOTUNER_CYCLES - 1.0);
+            t_high_avg /= (PID_AUTOTUNER_CYCLES - 1.0);
+            t_low_avg  /= (PID_AUTOTUNER_CYCLES - 1.0);
+
+            s->printf("Averages over last %d cycles: Max: %5.1fc  Min: %5.1fc  high samples: %3.0f  low samples: %3.0f\n", 3, tmax_avg, tmin_avg, t_high_avg, t_low_avg);
 
-            // from http://brettbeauregard.com/blog/2012/01/arduino-pid-autotune-library/
-            // TODO: work out why the results of this algorithm are dreadfully poor
-            double ku = 4 * 128 / ((tmax_avg - tmin_avg) * 3.141592653589);
-            double pu = tcycle_avg;
+            // this code taken from http://github.com/ErikZalm/Marlin/blob/Marlin_v1/Marlin/temperature.cpp
+            double ku = (4.0 * d) / (3.141592653589 * (tmax_avg - tmin_avg) / 2.0);
+            double tu = (t_low_avg + t_high_avg) * (1000.0 / 20.0) / 1000.0;
+
+            s->printf("\tku: %g\n\ttu: %g\n", ku, tu);
 
             double kp = 0.6 * ku;
-            double ki = 1.2 * ku / pu;
-            double kd = 0.075 * ku * pu;
+            double ki = 2 * kp / tu / 20.0;
+            double kd = kp * tu / 8.0;
 
-            s->printf("PID Autotune complete. Try M301 S%d P%4g I%4g D%4g\n", t->pool_index, kp, ki, kd);
+            s->printf("\tTrying:\n\tKp: %5.1f\n\tKi: %5.3f\n\tKd: %5.0f\n", kp, ki, kd);
+            // end code from Marlin Firmware
 
             t->p_factor = kp;
             t->i_factor = ki;
             t->d_factor = kd;
-            t->i_max = 128;
 
             t = NULL;
             s = NULL;
 
             return 0;
         }
+        s->printf("Cycle %d:\n\tbias: %4d d: %4d\n", cycle, bias, d);
     }
 
-    last_output = (t->o > 128);
+    int ticks;
 
-    cycles[cycle].ticks++;
+    if (output)
+    {
+        ticks = ++cycles[cycle].ticks_high;
+        t->heater_pin->pwm((t->o = ((bias + d) >> 1)));
+    }
+    else
+    {
+        ticks = ++cycles[cycle].ticks_low;
+        t->heater_pin->set((t->o = 0));
+    }
 
-    if ((cycles[cycle].ticks % 16) == 0)
+    if ((ticks % 16) == 0)
     {
-        s->printf("%s: %5.1f/%5.1f @%d %d %d/8\n", t->designator.c_str(), t->last_reading, t->target_temperature, t->o, (last_output?1:0), cycle);
+        s->printf("%s: %5.1f/%5.1f @%d %d %d/8\n", t->designator.c_str(), t->last_reading, target_temperature, t->o, (output?1:0), cycle);
     }
 
     if (t->last_reading > cycles[cycle].t_max)
@@ -111,5 +141,7 @@ uint32_t PID_Autotuner::on_tick(uint32_t dummy)
     if (t->last_reading < cycles[cycle].t_min)
         cycles[cycle].t_min = t->last_reading;
 
+    last_output = output;
+
     return 0;
 }
index 48e82bb..951dc4e 100644 (file)
@@ -20,15 +20,21 @@ public:
 
     TemperatureControl *t;
 
+    double target_temperature;
+
     int cycle;
+    bool output;
     bool last_output;
     StreamOutput *s;
 
     struct {
         double t_max;
         double t_min;
-        int ticks;
+        int ticks_low;
+        int ticks_high;
     } cycles[PID_AUTOTUNER_CYCLES];
+
+    int bias, d;
 };
 
 #endif /* _PID_AUTOTUNE_H */
index a04efd4..13172dd 100644 (file)
@@ -25,7 +25,6 @@ TemperatureControl::TemperatureControl(uint16_t name){
 void TemperatureControl::on_module_loaded(){
 
     // We start not desiring any temp
-//     this->desired_adc_value = UNDEFINED;
     this->target_temperature = UNDEFINED;
 
     // Settings
@@ -93,11 +92,11 @@ void TemperatureControl::on_config_reload(void* argument){
     this->kernel->adc->enable_pin(this->thermistor_pin);
 
     // Heater pin
-    this->heater_pin     =  this->kernel->config->value(temperature_control_checksum, this->name_checksum, heater_pin_checksum)->required()->as_pin()->as_output();
+    this->heater_pin     =  this->kernel->config->value(temperature_control_checksum, this->name_checksum, heater_pin_checksum)->required()->as_pwm()->as_output();
     this->heater_pin->set(0);
 
     // activate SD-DAC timer
-    this->kernel->slow_ticker->attach(1000, this->heater_pin, &Pin::tick);
+    this->kernel->slow_ticker->attach(1000, this->heater_pin, &Pwm::on_tick);
 
     // PID
     this->p_factor = this->kernel->config->value(temperature_control_checksum, this->name_checksum, p_factor_checksum)->by_default(10 )->as_number();
@@ -108,9 +107,6 @@ void TemperatureControl::on_config_reload(void* argument){
     this->last_reading = 0.0;
 }
 
-//#pragma GCC push_options
-//#pragma GCC optimize ("O0")
-
 void TemperatureControl::on_gcode_received(void* argument)
 {
     Gcode* gcode = static_cast<Gcode*>(argument);
@@ -121,6 +117,35 @@ void TemperatureControl::on_gcode_received(void* argument)
             gcode->stream->printf("%s:%3.1f /%3.1f @%d ", this->designator.c_str(), this->get_temperature(), ((target_temperature == UNDEFINED)?0.0:target_temperature), this->o);
             gcode->add_nl = true;
         }
+        if (gcode->m == 301)
+        {
+            if (gcode->has_letter('S') && (gcode->get_value('S') == this->pool_index))
+            {
+                if (gcode->has_letter('P'))
+                    this->p_factor = gcode->get_value('P');
+                if (gcode->has_letter('I'))
+                    this->i_factor = gcode->get_value('I');
+                if (gcode->has_letter('D'))
+                    this->d_factor = gcode->get_value('D');
+                if (gcode->has_letter('X'))
+                    this->i_max    = gcode->get_value('X');
+            }
+            gcode->stream->printf("%s(S%d): Pf:%g If:%g Df:%g X(I_max):%g Pv:%g Iv:%g Dv:%g O:%d\n", this->designator.c_str(), this->pool_index, this->p_factor, this->i_factor, this->d_factor, this->i_max, this->p, this->i, this->d, o);
+        }
+        if (gcode->m == 303)
+        {
+            if (gcode->has_letter('S') && (gcode->get_value('S') == this->pool_index))
+            {
+                double target = 150.0;
+                if (gcode->has_letter('P'))
+                {
+                    target = gcode->get_value('P');
+                    gcode->stream->printf("Target: %5.1f\n", target);
+                }
+                gcode->stream->printf("Start PID tune, command is %s\n", gcode->command.c_str());
+                this->pool->PIDtuner->begin(this, target, gcode->stream);
+            }
+        }
     }
 }
 
@@ -130,7 +155,6 @@ void TemperatureControl::on_gcode_execute(void* argument){
         // Set temperature without waiting
         if( gcode->m == this->set_m_code && gcode->has_letter('S') )
         {
-            //gcode->stream->printf("setting to %f meaning %u  \r\n", gcode->get_value('S'), this->temperature_to_adc_value( gcode->get_value('S') ) );
             if (gcode->get_value('S') == 0)
             {
                 this->target_temperature = UNDEFINED;
@@ -157,42 +181,14 @@ void TemperatureControl::on_gcode_execute(void* argument){
                 this->waiting = true;
             }
         }
-        if (gcode->m == 301)
-        {
-            if (gcode->has_letter('S') && (gcode->get_value('S') == this->pool_index))
-            {
-                if (gcode->has_letter('P'))
-                    this->p_factor = gcode->get_value('P');
-                if (gcode->has_letter('I'))
-                    this->i_factor = gcode->get_value('I');
-                if (gcode->has_letter('D'))
-                    this->d_factor = gcode->get_value('D');
-                if (gcode->has_letter('X'))
-                    this->i_max    = gcode->get_value('X');
-            }
-            gcode->stream->printf("%s(S%d): Pf:%g If:%g Df:%g X(I_max):%g Pv:%g Iv:%g Dv:%g O:%d\n", this->designator.c_str(), this->pool_index, this->p_factor, this->i_factor, this->d_factor, this->i_max, this->p, this->i, this->d, o);
-        }
-        if (gcode->m == 303)
-        {
-            if (gcode->has_letter('S') && (gcode->get_value('S') == this->pool_index))
-            {
-                double target = 150.0;
-                if (gcode->has_letter('T'))
-                    target = gcode->get_value('T');
-                this->pool->PIDtuner->begin(this, target, gcode->stream);
-            }
-        }
     }
 }
 
-//#pragma GCC pop_options
-
 
 void TemperatureControl::set_desired_temperature(double desired_temperature){
-//     this->desired_adc_value = this->temperature_to_adc_value(desired_temperature);
     target_temperature = desired_temperature;
     if (desired_temperature == 0.0)
-        heater_pin->set(0);
+        heater_pin->set((o = 0));
 }
 
 double TemperatureControl::get_temperature(){
@@ -246,24 +242,26 @@ void TemperatureControl::pid_process(double temperature)
 
     p  = error * p_factor;
     i += (error * this->i_factor);
-    d  = (last_reading - temperature) * this->d_factor;
+    // d was imbued with oldest_raw earlier in new_thermistor_reading
+    d = adc_value_to_temperature(d);
+    d = (d - temperature) * this->d_factor;
 
     if (i > this->i_max)
         i = this->i_max;
     if (i < -this->i_max)
         i = -this->i_max;
 
-    this->o = (p + i + d) * PIN_PWM_MAX / 256;
+    this->o = (p + i + d) * heater_pin->max_pwm() / 256;
 
-    if (this->o >= PIN_PWM_MAX)
+    if (this->o >= heater_pin->max_pwm())
     {
-        i -= (this->o - (PIN_PWM_MAX - 1)) * 256 / PIN_PWM_MAX;
-        this->o = PIN_PWM_MAX - 1;
+        i -= (this->o - (heater_pin->max_pwm() - 1)) * 256 / heater_pin->max_pwm();
+        this->o = heater_pin->max_pwm() - 1;
     }
     if (this->o < 0)
     {
-        if (this->o < -(PIN_PWM_MAX))
-            i += (-(PIN_PWM_MAX) - this->o) * 256 / PIN_PWM_MAX;
+        if (this->o < -(heater_pin->max_pwm()))
+            i += (-(heater_pin->max_pwm()) - this->o) * 256 / heater_pin->max_pwm();
         this->o = 0;
     }
 
@@ -278,6 +276,7 @@ int TemperatureControl::new_thermistor_reading()
         uint16_t l;
         queue.pop_front(l);
         running_total -= l;
+        d = l;
     }
     uint16_t r = last_raw;
     queue.push_back(r);
index 76ff7bf..9a477bd 100644 (file)
@@ -9,6 +9,7 @@
 #define temperaturecontrol_h
 
 #include "libs/Pin.h"
+#include "Pwm.h"
 #include <math.h>
 
 #include "RingBuffer.h"
@@ -93,7 +94,7 @@ class TemperatureControl : public Module {
         uint16_t name_checksum;
 
         Pin* thermistor_pin;
-        Pin* heater_pin;
+        Pwm* heater_pin;
 
         bool waiting;