Controls both heat and pump pressure based on a temperature probe and a scale- ie, it does temperature and flow profiling. Should work with any vibratory pump machine.

Dependencies:   Adafruit_RTCLib FastPWM TSI mbed

Files at this revision

API Documentation at this revision

Comitter:
jzeeff
Date:
Fri Aug 09 20:36:28 2013 +0000
Parent:
0:24cdf76455c4
Child:
2:22d9c714b511
Commit message:
various improvements

Changed in this revision

main.cpp Show annotated file Show diff for this revision Revisions of this file
--- a/main.cpp	Wed Aug 07 16:10:11 2013 +0000
+++ b/main.cpp	Fri Aug 09 20:36:28 2013 +0000
@@ -1,5 +1,6 @@
 
 // Program to control espresso maker boiler temperatures
+// Similar to PID, but uses a flexible open loop table during brew
 // Used with a Gaggia Classic, FreeScale FRDM-KL25Z computer, PT1000 RTD, SSR
 // Jon Zeeff, 2013
 // Public Domain
@@ -7,8 +8,8 @@
 #include "mbed.h"
 #include "TSISensor.h"
 
-DigitalOut ssr(PTA1);
-AnalogIn   adc(PTE20);
+DigitalOut ssr(PTA1);       // Solid State Relay
+AnalogIn   adc(PTE20);      // A/D converter reads temperature
 
 #define OFF 0
 #define RED 1
@@ -17,46 +18,55 @@
 #define WHITE 4
 #define YELLOW 5
 
-// RTD ohms
+// PT1000 RTD ohms
 // 1360 ohms = 94C
 // 1000 ohms = too cold (0C)
 // 1520 ohms = too hot (136C)
 
-// note: assume a 2K divider resistor, a PT1000 RTD and a 16 bit A/D result
-// use this formula (RTD_OHMS/(RTD_OHMS+2000)) * 65536
+// note: assume a 2.2K divider resistor, a PT1000 RTD and a 16 bit A/D result
+// use this formula: (RTD_OHMS/(RTD_OHMS+2200)) * 65536
 
-// desired A/D value for boiler temp
-#define TARGET_TEMP 25600       // CHANGE THIS  
+// desired A/D value for boiler temp while idling
+// note: there is an offset between boiler wall temp sensors and actual water temp
+#define TARGET_TEMP 25850       // CHANGE THIS  
 
-// table of % power level vs time in seconds into brew cycle (including preheat)
+// table of adjustments (degrees C) to TARGET_TEMP vs time (seconds) into brew cycle (including preheat period)
+// the idea is that extra heat is needed as cool water comes into the boiler during brew
+// note: if you alternate very high and negative values here, you get the equivalent of open loop/PWM/temp surfing
+
 const int table[40] = {
     // preheat up to 10 seconds
-    0,0,100,100,100,100,0,0,0,0,                   // CHANGE THIS
+    0,0,0,0,0,0,18,18,18,18,                            // CHANGE THIS
     // brewing (pump is on) up to 30 seconds
-    65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,  // CHANGE THIS
-    65,65,65,65,65,65,65,65,65,65,65,65,65,65,65
+    18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,       // CHANGE THIS
+    18,18,18,18,18,18,18,18,18,18,18,18,18,18,18
 }; 
 
-// these probably don't need to be changed
-#define CLOSE 50                // how close in A/D value before switching to proportional control
-#define INITIAL_POWER  .03      // initial guess for steady state power needed (try .03)
+// these probably don't need to be changed if you are using a Gaggia Classic
+#define CLOSE 30                // how close in A/D value before switching to proportional control
+#define INITIAL_POWER  .05      // initial guess for steady state power needed (try .05 = 5%)
 #define MIN_TEMP 21000          // below this is an error
-#define MAX_TEMP 29000          // above this is an error
+#define MAX_TEMP 218000          // above this is an error
+#define ROOM_TEMP 22000         // A/D at standard ambient room temp
+#define MAX_ROOM_TEMP 22500     // above this means ambient isn't valid
 #define SLEEP_TIME 3600         // turn off heat after this many seconds
 #define BREW_TIME 30            // max brew time
 #define BREW_PREHEAT 10         // max preheat time
-
+#define AD_PER_DEGREE 44        // how many A/D counts equal a 1 degree C change 
 #define debug if (1) printf     // use if (1) or if (0)
 
 void brew(void);
 void set_color(int color);
 unsigned read_ad(void);
-   
+
+unsigned ambient_temp;           // room or water tank temp
+double heat = INITIAL_POWER;     // initial fractional heat needed while idle
+     
 int main()
 {
-    unsigned ambient_temp = read_ad();      // save temp on startup (future enhancements)
     time_t prev_time = 0;
     TSISensor tsi;                          // used as a start button
+    ambient_temp = read_ad();               // save temp on startup 
      
     set_time(0);                            // start clock at zero
    
@@ -80,12 +90,11 @@
             set_color(RED);                  // set LED to red
         } else {   // close to target temp
             // learning mode - adjust heat, the fraction of time power should be on
-            static double heat = INITIAL_POWER;     // initial fractional heat needed while idle
-            
+           
             if (temp > TARGET_TEMP)          // adjust best guess for % heat needed
-                  heat *= .99;
+                  heat *= .98;
             else
-                  heat *= 1.01;
+                  heat *= 1.02;
                   
             debug("learned heat = %F, temp = %u\r\n",heat, temp);
             ssr = 1;            // turn on heater for PWM
@@ -109,54 +118,67 @@
             while (time(NULL) < wakeup_time)    // wait till tomorrow
                    wait(1);
             set_time(0);                        // clock runs zero to 24 hours
-            wakeup_time = (24 * 60 * 60);       // no 15 min offset needed now
+            wakeup_time = (24 * 60 * 60);       // no 20 min offset needed now
+            ambient_temp = read_ad();           // save temp on startup  
         }
       
         // check for errors (incorrect boiler temp can be dangerous)
         if (temp > MAX_TEMP || temp < MIN_TEMP) {
             ssr = 0;            // turn off heater
             set_color(YELLOW);  // set LED to indicate error
+            debug("error A/D = %u\r\n",temp);
             for (;;);           // reset needed to exit this
         }
 
-    if (time(NULL) > prev_time) debug("A/D value = %u\r\n",temp);
-    prev_time = time(NULL);
+        if (time(NULL) > prev_time) debug("A/D value = %u\r\n",temp);  // every second
+        prev_time = time(NULL);
 
-    } // while
+    } // for (;;)
 
 } // main()
 
 
 //=================================================================
 // This subroutine is called when the button is pressed, 10 seconds
-// before the pump is started.  It does open loop power/temp control.
+// before the pump is started.  It does open loop PWM power/heat control.
 //=================================================================
 
 void brew(void)
 {
-    time_t start_time = time(NULL);
-    
+    unsigned start_time = time(NULL);
     #define brew_time (time(NULL) - start_time)
- 
-    debug("preheat/brew start\r\n");
+    
+    double adjust = 1;              // default is no adjustment
+    
+    // adjust for tank temp (assumed to be equal to ambient at startup)
+    if (ambient_temp < MAX_ROOM_TEMP)    // sanity check
+       adjust = (double)(ROOM_TEMP - TARGET_TEMP) / (double)(ambient_temp - TARGET_TEMP); 
+   
+    debug("preheat/brew start, adjust = %F\r\n", adjust);
     set_color(WHITE);  
             
-    while (brew_time < BREW_TIME + BREW_PREHEAT) {   // loop for 40 seconds
-
-        if (brew_time >= BREW_PREHEAT)
+    for (;;) {
+        unsigned prev_brew_time = 0;
+        
+        if (brew_time >= BREW_PREHEAT + BREW_TIME)
+           break;
+ 
+        if (brew_time == BREW_PREHEAT)
            set_color(BLUE);    // set LED color to blue for start brew/pump now
 
-        // PWM power level over .5 second
-        ssr = 1;  // turn on heater
-        wait(table[brew_time] / 200.0);
-        ssr = 0;  // turn off heater
-        wait((100 - table[brew_time]) / 200.0);
+        // bang/bang temp to the table value
+        if (read_ad() < (TARGET_TEMP + (table[brew_time] * AD_PER_DEGREE)) * adjust)
+           ssr = 1;             // heater on
+        else
+           ssr = 0;             // heater off
         
-        debug("power level %u = %u %%, temp = %u\r\n",brew_time,table[brew_time],read_ad());
+        if (brew_time != prev_brew_time)
+           debug("target temp %u = %u, temp = %u\r\n",brew_time,table[brew_time],read_ad());
+        prev_brew_time = brew_time;
 
-    } // while
-
-    set_color(OFF);
+    } // for
+  
+    ssr = 0;
     debug("brew done\r\n");
 
 } // brew()
@@ -205,15 +227,23 @@
 
 unsigned read_ad(void) 
 {
-unsigned a, b, c;
+
+uint32_t sum=0;
+int i;
 
-    a = adc.read_u16();
+for (i = 0; i < 3; ++i) {    // average multiple for more accuracy
+    unsigned a, b, c;
+    
+    a = adc.read_u16();     // take median of 3 values
     b = adc.read_u16();
     c = adc.read_u16();
     
-    if ((a >= b && a <= c) || (a >= c && a <= b)) return a;
-    if ((b >= a && b <= c) || (b >= c && b <= a)) return b;
-    return c;
+    if ((a >= b && a <= c) || (a >= c && a <= b)) sum += a;
+    else if ((b >= a && b <= c) || (b >= c && b <= a)) sum += b;
+    else sum += c;
+} // for
+
+return sum / 3;
     
 }  // read_ad()
  
\ No newline at end of file