Use statistics library for average and various monitoring changes.
authorPat Thoyts <patthoyts@users.sourceforge.net>
Tue, 26 May 2015 19:47:43 +0000 (20:47 +0100)
committerPat Thoyts <patthoyts@users.sourceforge.net>
Tue, 26 May 2015 19:47:43 +0000 (20:47 +0100)
Signed-off-by: Pat Thoyts <patthoyts@users.sourceforge.net>
PlateWarmer.ino

index a43cb1e54079c093263739521cd9c9ec92e7011f..c775a9610df65ac5f725e7d81e21e730f0a3a9c2 100644 (file)
  */
 
 #include <Arduino.h>
-#include <EEPROM.h>
-#include <Ports.h> // JeeLib library
+#include <EEPROM.h> // Arduino core
+#include <Ports.h>  // JeeLib library
+#include <PID_v1.h> // Arduino PID library
+#include <Statistic.h> // Statistics library
 #include <avr/pgmspace.h>
 #include <avr/eeprom.h>
+#include <avr/wdt.h>
 
 #define COMMON_CATHODE // This device is common cathode
 
@@ -74,8 +77,6 @@ volatile int current_digit = 0;
 // The timer initial counter value.
 static unsigned int tcnt2 = 0;
 
-static unsigned int led_cnt = 0;
-
 // Flag to make the display flash visibly
 #define STATE_NORMAL 0
 #define STATE_USERPRESS 1
@@ -85,6 +86,47 @@ static byte flash_cntr = 0;
 static long target_temp = 0;
 static long last_target = 0;
 
+class CPID
+{
+public:
+    double mKp, mKi, mKd;
+    double mCp, mCi, mCd;
+    double mSetPoint, mPrevTime, mPrevErr;
+    CPID(double Kp, double Ki, double Kd) : mKp(Kp), mKi(Ki), mKd(Kd) {
+        mSetPoint = mPrevTime = mPrevErr = 0;
+        mCp = mCi = mCd = 0;
+    }
+    void SetPoint(double point) { mSetPoint = point; }
+    double update(double current_time, double current_value) {
+        double err = mSetPoint - current_value;
+        double dt = current_time - mPrevTime;
+        double de = err - mPrevErr;
+        mCi += err * dt;
+        if (mCi < 0.0) mCi = 0.0;
+        else if (mCi > 5000.0) mCi = 5000.0;
+        double D = 0;
+        if (dt > 0) D = de/dt;
+        mPrevTime = current_time;
+        mPrevErr = err;
+        err = (mKp * err) + (mKi * mCi) + (mKd * D);
+        if (err < 0.0) err = 0.0;
+        else if (err > 5000.0) err = 5000.0;
+        return err;
+    }
+    double GetKp() const { return mKp; }
+    double GetKi() const { return mKi; }
+    double GetKd() const { return mKd; }
+};
+
+
+double Input = 0, Output = 0, SetPoint = 0;
+int WindowSize = 5000;
+unsigned long windowStartTime;
+// original 2, 5, 1 massive overshoot and +2 degrees offset
+// 0.072, 14.8, 2.39, DIRECT);
+//PID pid(&Input, &Output, &SetPoint, 1.0, 4.0, 10.0, DIRECT);
+CPID pid(1.0, 0.0, 0.0);
+
 // Serial print using FLASH strings: Serial_print(PSTR(...));
 static void Serial_print(PGM_P s)
 {
@@ -122,9 +164,13 @@ display(int digit, const char *num)
             return;
     }
 
-    // select our digit and configure
-    digitalWrite(select[digit], LOW);
-    set_segments( Digits[num[digit] - 0x30] | ((digit == 1) ? 0x80 : 0) );
+    // do not use digit 0 (only showing tenths of a degree)
+    // The OR'd part selects the decimal point (after digit 2)
+    if (digit != 0) {
+        // select our digit and configure
+        digitalWrite(select[digit], LOW);
+        set_segments( Digits[num[digit] - 0x30] | ((digit == 2) ? 0x80 : 0) );
+    }
 }
 
 // Initialize timer2 as interrupt source
@@ -146,7 +192,8 @@ Timer2Init()
     // Delay is (prescale / cpu_freq * 1e6)
     // [@16MHz with 1024 prescaler yields 75us tick]
     // So set tcnt2 = 256 - (6000us / 75us) for 6ms update
-    tcnt2 = 176;
+    // was 176 for 6ms, now 190 for 5ms
+    tcnt2 = 190;
     TCNT2 = tcnt2;
     // 18.9e: re-enable the timer overflow interrupt flag
     TIMSK2 |= (1 << TOIE2);
@@ -157,11 +204,6 @@ ISR(TIMER2_OVF_vect)
     TCNT2 = tcnt2; // reset the timer
     display(current_digit, Display);
     current_digit = (current_digit + 1) % 4;
-    ++led_cnt;
-    if (led_cnt > 166) {
-        //PORTB ^= _BV(PB5); // toggle pin 13
-        led_cnt = 0;
-    }
 }
 
 // We store the currently set target temperature in the EEPROM
@@ -176,7 +218,7 @@ static long stored_target_read()
             | (EEPROM.read(1) << 8)
             | (EEPROM.read(0));
     }
-    Serial_print(PSTR("eeprom read: "));
+    Serial.print(F("# eeprom read: "));
     Serial.println(t);
     return t;
 }
@@ -187,12 +229,14 @@ static void stored_target_write(long t)
     EEPROM.write(1, (t >> 8) & 0xff);
     EEPROM.write(2, (t >> 16) & 0xff);
     EEPROM.write(3, (t >> 24) & 0xff);
-    Serial_print(PSTR("eeprom write: "));
+    Serial.print(F("# eeprom write: "));
     Serial.println(t);
 }
 
 static void setDisplay(long v)
 {
+    // Only show 1 dp (input is in hundreds)
+    v = (v + 50) / 10; // round to nearest 10th degree
     Display[3] = 0x30 + (v % 10);
     Display[2] = 0x30 + ((v/10) % 10);
     Display[1] = 0x30 + ((v/100) % 10);
@@ -203,7 +247,7 @@ void
 setup()
 {
     // disable TWI, Timer1, SPI and USART0
-    //PRR &= (1<<PRTWI) | (1<<PRTIM1) | (1<<PRSPI);// | (1 << PRUSART0);
+    PRR &= (1<<PRTWI); //| (1<<PRTIM1) | (1<<PRSPI);// | (1 << PRUSART0);
 
     // Enable the internal 1.1V AREF (requires 10nF cap to GND)
     analogReference(INTERNAL);
@@ -225,36 +269,43 @@ setup()
     pinMode(SEL_4, OUTPUT);
     
     Serial.begin(9600);
-    Serial_print(PSTR("Slide Warmer - Copyright (c) 2013 Pat Thoyts\r\n"));
+    Serial.print(F("# Slide Warmer v1.0 - Copyright (c) 2013 Pat Thoyts\r\n"));
     current_digit = 0;
     memset(Display, 0x30, sizeof(Display));
     target_temp = stored_target_read();
     setDisplay(target_temp);
+
+    SetPoint = target_temp;  // PID target
+    windowStartTime = millis();
+    //pid.SetOutputLimits(0, WindowSize);
+    //pid.SetSampleTime(200);
+    //pid.SetMode(AUTOMATIC);
     
+    Serial.print(F("# PID Kp="));
+    Serial.print(pid.GetKp());
+    Serial.print(F(" Ki="));
+    Serial.print(pid.GetKi());
+    Serial.print(F(" Kd="));
+    Serial.println(pid.GetKd());
+
     Timer2Init();
+    wdt_enable(WDTO_1S);
 }
 
-#define AREF 105L
-
 static long getTemperature()
 {
+#define AREF 105L
     long v,v1;
     v = analogRead(LM35_PIN);
-    // v * REFERENCE) / NSTEPS
     v1 = (v * AREF * 100L) / 1024L;
     v = (v * 10000L) / 1024L;
-    Serial.print("[");
-    Serial.print(v);
-    Serial.print(" ");
-    Serial.print(v1);
-    Serial.print("] ");
     return v;
 }
 
 static void setState(byte state)
 {
     display_state = state;
-    Serial_print(PSTR("state: "));
+    Serial_print(PSTR("# set state: "));
     Serial.println(state);
 }
 
@@ -264,92 +315,165 @@ static void incrementTargetTemp(int delta)
     if (target_temp < 1000) target_temp = 1000;
     if (target_temp > 11000) target_temp = 11000;
     setDisplay(target_temp);
-    Serial_print(PSTR("target: "));
+    Serial_print(PSTR("# select target: "));
     Serial.println(target_temp);
 }
 
-MilliTimer timer0, timer1, timer2, timer3;
+static void setHeating(bool on)
+{
+    // setting pin 8 low puts the MOC led _on_
+    if (on) {
+        digitalWrite(MOC_PIN, LOW);
+        digitalWrite(LED_PIN, HIGH);
+    } else {
+        digitalWrite(MOC_PIN, HIGH);
+        digitalWrite(LED_PIN, LOW);
+    }
+}
+
+static void Serial_print_value_space(double value)
+{
+    Serial.print(value);
+    Serial_print(PSTR(" "));
+}
+
+MilliTimer timerPID, timerKeypress, timer2, timerPrint;
 bool keypress_delay = false;
+bool need_debounce = true, debounce_left = false, debounce_right = false;
 
 void
 loop()
 {
-    long v = 0, t[NUMSAMPLES];
     int n;
 
-    if (timer1.poll(100)) {
-        bool left_button = digitalRead(BUTTON_L) != HIGH;
-        bool right_button = digitalRead(BUTTON_R) != HIGH;
-        switch (display_state)
-        {
-            case STATE_NORMAL:
-                if (left_button || right_button) {
-                    setState(STATE_USERPRESS);
-                    timer2.set(1000);
-                }
-                break;
-            case STATE_USERPRESS:
-                if (timer2.poll()) {
+    wdt_reset();
+
+    // key press handling
+    if (timerKeypress.poll(50)) {
+        bool left = digitalRead(BUTTON_L) != HIGH;
+        bool right = digitalRead(BUTTON_R) != HIGH;
+        if (need_debounce) {
+            debounce_left = left;
+            debounce_right = right;
+            need_debounce = false;
+        } else {
+            // debounce: button press must be true on each 50ms pass
+            bool left_button = left && debounce_left;
+            bool right_button = right && debounce_right;
+            need_debounce = true;
+            switch (display_state)
+            {
+                case STATE_NORMAL:
                     if (left_button || right_button) {
-                        setState(STATE_FLASHING);
+                        setState(STATE_USERPRESS);
+                        timer2.set(1000);
+                    }
+                    break;
+                case STATE_USERPRESS:
+                    if (timer2.poll()) {
+                        if (left_button || right_button) {
+                            setState(STATE_FLASHING);
+                            timer2.set(5000);
+                            keypress_delay = true;
+                            setDisplay(target_temp);
+                            last_target = target_temp;
+                        }
+                        else {
+                            setState(STATE_NORMAL);
+                        }
+                    }
+                    break;
+                case STATE_FLASHING:
+                    // avoid accepting presses for the first second
+                    if (!keypress_delay) {
+                        if (left_button)
+                            incrementTargetTemp(-100);
+                        if (right_button)
+                            incrementTargetTemp(100);
+                    }
+                    if (left_button || right_button) // reset timeout
                         timer2.set(5000);
-                        keypress_delay = true;
-                        setDisplay(target_temp);
-                        last_target = target_temp;
+                    if (keypress_delay) {
+                        if (timer2.remaining() < 4000)
+                            keypress_delay = false;
                     }
-                    else {
+                    if (timer2.poll()) {
                         setState(STATE_NORMAL);
+                        if (target_temp != last_target) {
+                            stored_target_write(target_temp);
+                        }
                     }
-                }
-                break;
-            case STATE_FLASHING:
-                // avoid accepting presses for the first second
-                if (!keypress_delay) {
-                    if (left_button)
-                        incrementTargetTemp(-100);
-                    if (right_button)
-                        incrementTargetTemp(100);
-                }
-                if (left_button || right_button) // reset timeout
-                    timer2.set(5000);
-                if (keypress_delay) {
-                    if (timer2.remaining() < 4000)
-                        keypress_delay = false;
-                }
-                if (timer2.poll()) {
-                    setState(STATE_NORMAL);
-                    if (target_temp != last_target) {
-                        stored_target_write(target_temp);
-                    }
-                }
-                break;
+                    break;
+            }
+        }
+    }
+
+    long current_temp = 0;
+    Statistic stats;
+    if (true) { // timerPID.poll(200)) {
+        //long t[NUMSAMPLES];
+        unsigned long now = millis();
+
+        for (n = 0; n < NUMSAMPLES; ++n) {
+            //t[n] = getTemperature();
+            long v = getTemperature();
+            stats.add(v);
+            delay(10);
+        }
+        //for (n = 0 ; n < NUMSAMPLES; ++n) {
+        //    current_temp += t[n];
+        //}
+        //current_temp /= NUMSAMPLES;
+        current_temp = static_cast<long>(stats.average());
+        
+        // Use PID algorithm to determine the 'on' time.
+        SetPoint = target_temp;
+        Input = current_temp;
+        //pid.Compute();
+        pid.SetPoint(target_temp);
+        Output = pid.update(now, current_temp);
+        
+        if (now - windowStartTime > WindowSize) {
+            windowStartTime += WindowSize;
+        }
+        if (Output > now - windowStartTime) {
+            setHeating(true);
+        } else {
+            setHeating(false);
         }
     }
 
-    if (timer0.poll(1000)) {
+    if (timerPrint.poll(1000)) {
+        unsigned long now = millis();
+        String s;
+        char sp = ' ';
+        s.concat(now);
+        s.concat(sp);
+        s.concat(static_cast<int>(SetPoint));
+        s.concat(sp);
+        s.concat(static_cast<int>(Input));
+        s.concat(sp);
+        s.concat(static_cast<int>(Output));
+        s.concat(sp);
+        s.concat(static_cast<int>(stats.pop_stdev()));
+        s.concat(sp);
+        uint8_t sum = 0;
+        for (int n = 0; n < s.length(); ++n) {
+            sum += static_cast<uint8_t>(s[n]);
+        }
+        Serial.print(s);
+        Serial.println(sum, HEX);
+#if 0        
+        Serial.print(now);
+        Serial_print(PSTR(" "));
+        Serial_print_value_space(SetPoint);
+        Serial_print_value_space(Input);
+        Serial_print_value_space(Output);
+        // pin 8 == PORTB pin 0: low for on
+        Serial.println((PORTB&1) ? "off " : "on  ");
+#endif
         if (display_state != STATE_FLASHING) {
-            Serial.print(millis());
-            Serial.print(" ");
-            for (n = 0; n < NUMSAMPLES; ++n) {
-                t[n] = getTemperature();
-                delay(10);
-            }
-            for (n = 0 ; n < NUMSAMPLES; ++n) {
-                v += t[n];
-            }
-            v /= NUMSAMPLES;
-            // setting pin 8 low puts the MOC led _on_
-            if (v < target_temp) {
-                digitalWrite(MOC_PIN, LOW);
-                digitalWrite(LED_PIN, HIGH);
-            } else {
-                digitalWrite(MOC_PIN, HIGH);
-                digitalWrite(LED_PIN, LOW);
-            }
-            // pin 8 == PORTB pin 0: low for on
-            Serial.print((PORTB&1) ? "off " : "on  ");
-            Serial.println(v);
-            setDisplay(v);
+            setDisplay(current_temp);
         }
     }
 }