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

Revision:
3:eb60e36b03f6
Parent:
2:22d9c714b511
Child:
4:3d661b485d59
--- a/main.cpp	Sun Aug 11 20:39:57 2013 +0000
+++ b/main.cpp	Thu Aug 29 14:55:52 2013 +0000
@@ -1,23 +1,12 @@
 
 // Program to control espresso maker boiler temperatures
-// Similar to multiple PID control, but uses a flexible open or closed loop table during brew
-// Used with a Gaggia Classic, FreeScale FRDM-KL25Z computer, PT1000 RTD, SSR
+// Similar to multiple PID control (pre-brew, brew and steam),
+// but uses a flexible open and closed loop table during brew
+// Used with a Gaggia Classic, FreeScale FRDM-KL25Z computer, PT1000 RTD, heater
+// See www.coffeegeeks.com for discussion
 // Jon Zeeff, 2013
 // Public Domain
 
-#include "mbed.h"
-#include "TSISensor.h"
-
-DigitalOut ssr(PTA1);       // Solid State Relay
-AnalogIn   adc(PTE20);      // A/D converter reads temperature
-
-#define OFF 0
-#define RED 1
-#define GREEN 2
-#define BLUE 3
-#define WHITE 4
-#define YELLOW 5
-
 // PT1000 RTD ohms (use Google to find a full table)
 // 1360 ohms = 94C
 // 1000 ohms = too cold (0C)
@@ -27,111 +16,180 @@
 // use this formula: A/D = (RTD_OHMS/(RTD_OHMS+2200)) * 65536
 
 // 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 25900       // CHANGE THIS  
+// note: there is usually some offset between boiler wall temp sensors and actual water temp
+#define TARGET_TEMP 25440       // CHANGE THIS 
 
 // 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.
 // Extra heat is provided by a higher than normal boiler wall temp.
-// NOTE: the decimal portion of the value is used as the PWM value to be applied if more heat is needed.
+// NOTE: the fractional portion of the value is used as the PWM value to be applied if more heat is needed.
 // This can prevent overshoot.
+// Example: 5.3 means that the boiler wall should be 5 degrees C above normal at this time point.  If not, apply 30% power.
+// Example: 99.99 means (roughly) that the heater should be completely on for the 1 second period
+// Note: heat takes at least 4 seconds before it is seen by the sensor
 
 const double table[40] = {
     // preheat up to 10 seconds
-    0,0,0,0,18.99,18.99,0,0,0,0,                     // CHANGE THIS
+    0.0,0.0,0.0,0.0,0.0,0.0,0.0,99.9,99.9,99.20,                  // CHANGE THIS
     // brewing (pump is on) up to 30 seconds
-    18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,    // CHANGE THIS
-    15.7,15.7,15.7,15.7,15.7,15.7,15.7,15.7,15.7,15.7,15.7,0,0,0,0                 // CHANGE THIS
-}; 
+    99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20, // CHANGE THIS
+    99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,0,0,0,0                  // CHANGE THIS
+};
+
+const double pump_table[40] = {
+    // during pre-brew period
+    0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,                  // CHANGE THIS
+    // brewing up to 30 seconds
+    0.0,0.0,99.99,99.99,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,                // CHANGE THIS
+    99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,0,0,0,0                 // CHANGE THIS
+};
 
 // 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 learned value control
-#define INITIAL_POWER  .05      // initial guess for steady state power needed (try .05 = 5%)
+#define CLOSE 60                // how close in A/D value before switching to learned value control
+#define GAIN .01                // how fast to adjust  
+#define INITIAL_POWER  .03      // initial guess for steady state heater power needed (try .03 = 3%)
 #define MIN_TEMP 21000          // below this is an error
-#define MAX_TEMP 29000          // above this is an error
+#define MAX_TEMP 29500          // above this is an error
+#define STEAM_TEMP 28000        // boiler temp while steaming
 #define ROOM_TEMP 22000         // A/D value 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 SLEEP_TIME 7200         // 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);
+#include "mbed.h"
+#include "TSISensor.h"      // touch sensor
+#include "DS1307.h"         // real-time clock
+#include "FastPWM.h"        // better PWM routine for pump control
+ 
+#define OFF 0
+#define RED 1
+#define GREEN 2
+#define BLUE 3
+#define WHITE 4
+#define YELLOW 5
+#define AQUA 6
+#define PINK 7
+
+#define ON 1
+#define OFF 0
 
-unsigned ambient_temp;           // room or water tank temp
-double heat = INITIAL_POWER;     // initial fractional heat needed while idle
-     
-int main()
+DigitalOut heater(PTD7);        // Solid State Relay - PTD6&7 have high drive capability
+FastPWM    pump(PTD4);          // Solid State Relay - PTD4 can do PWM @ 10K hz
+AnalogIn   boiler(PTE20);       // A/D converter reads temperature on boiler
+AnalogIn   group(PTE22);        // A/D for group basket temp
+DigitalOut led_green(LED_GREEN);
+I2C        gI2c(PTE0, PTE1);    // SDA, SCL - use pullups
+RtcDs1307  rtclock(gI2c);       // DS1307 is a real time clock chip
+Serial     pc(USBTX, USBRX);    // Serial to pc connection
+ 
+void brew(void);
+void led_color(int color);
+unsigned read_temp(AnalogIn adc);
+void steam(int seconds);
+
+unsigned ambient_temp;          // room or water tank temp (startup)
+double heat = INITIAL_POWER;    // initial fractional heat needed while idle
+unsigned boiler_log[40];        // record boiler temp during brew
+unsigned group_log[40];         // record basket temp during brew
+
+
+int main()                      // start of program
 {
     time_t prev_time = 0;
-    TSISensor tsi;                          // used as a start button
-    ambient_temp = read_ad();               // save temp on startup 
-     
+    TSISensor tsi;                          // used as a brew start button
+    ambient_temp = read_temp(boiler);       // save temp on startup
+
     set_time(0);                            // start clock at zero
-   
+#if 0
+    DateTime dt = gRtc.now();
+ 
+    debug("%u/%u/%02u %2u:%02u:%02u\r\n"
+                ,dt.month(),dt.day(),dt.year()
+                ,dt.hour(),dt.minute(),dt.second());
+#endif                
     debug("starting A/D value/temp = %u\r\n",ambient_temp);
-  
-// loop forever, controlling boiler temperature
+    
+    pump.period_ms(500);    // period of PWM signal
+    //pump = 100;           // duty cycle.  For DC, use 6 msec pulses
+    
+    if (pc.readable())      // clear any data on serial port
+       pc.getc(); 
+            
+    if (ambient_temp < MAX_ROOM_TEMP)
+        steam(180);         // do accelerated warmup by overheating
+
+    // loop forever, controlling boiler temperature
 
     for (;;) {
         // read temp from A/D
         // note: in A/D counts, not degrees
-        unsigned temp = read_ad();
-       
+        unsigned temp = read_temp(boiler);
+
         // bang/bang when far away, PWM to learned value when close
         if (temp > TARGET_TEMP + CLOSE) {
-           ssr = 0;                // turn off heater
-           set_color(GREEN);       // set LED to green
+            heater = OFF;                   // turn off heater
+            led_color(GREEN);               // set LED to green
         } else if (temp < TARGET_TEMP - CLOSE) {
-            ssr = 1;                         // turn on heater           
-            set_color(RED);                  // set LED to red
+            heater = ON;                    // turn on heater
+            led_color(RED);                 // set LED to red
         } else {   // close to target temp
             // learning mode - adjust heat, the fraction of time power should be on
-           
-            if (temp > TARGET_TEMP)          // adjust best guess for % heat needed
-                  heat *= .98;
+
+            if (temp > TARGET_TEMP)         // adjust best guess for % heat needed
+                heat *= (1-GAIN);
             else
-                  heat *= 1.02;
-                  
-            debug("learned heat = %F, temp = %u\r\n",heat, temp);
-            ssr = 1;            // turn on heater for PWM
-            set_color(RED);
-            wait(heat);             
-            ssr = 0;            // turn off heater
-            set_color(GREEN);               
-            wait(1-heat);       
+                heat *= (1+GAIN);
+
+            heater = ON;                // turn on heater for PWM
+            led_color(RED);
+            wait(heat * 2.7);           // 1.7 to reduce interaction with 50/60Hz power
+            heater = OFF;               // turn off heater
+            led_color(GREEN);
+            wait((1-heat) * 2.7);       // total time is 2.7 seconds
         } // if
 
         // the user must press a button 10 seconds prior to brewing to start preheat
         if (tsi.readPercentage() > .5)
             brew();
-
-        // check for idle, sleep till tomorrow if it occurs
-        if (time(NULL) > SLEEP_TIME){   // save power
-            static time_t wakeup_time = (24 * 60 * 60) - (20 * 60);  // 24 hours minus 20 min
+            
+        // if they signaled for steam
+        if (tsi.readPercentage() > .2 && tsi.readPercentage() < .5)
+            steam(120);
             
-            ssr = 0;                    // turn off heater
-            set_color(OFF);     
-            printf("sleep\r\n");      
+        if (pc.readable()){    // Check if data is available on serial port.
+            pc.getc(); 
+            // debug, print out temp log
+            int i;
+            for (i = 0; i < 40; ++i)
+                printf("log %d: %u %u\r\n",i,boiler_log[i],group_log[i]);
+        } // if
+            
+        // check for idle shutdown, sleep till tomorrow if it occurs
+        if (time(NULL) > SLEEP_TIME) {  // save power
+            static time_t wakeup_time = (24 * 60 * 60) - (20 * 60);  // 24 hours minus 20 min
+
+            heater = OFF;                    // turn off heater
+            led_color(OFF);
+            printf("sleep\r\n");
             while (time(NULL) < wakeup_time)    // wait till tomorrow
-                   wait(1);
+                wait(1);
             set_time(0);                        // clock runs zero to 24 hours
             wakeup_time = (24 * 60 * 60);       // no 20 min offset needed now
-            ambient_temp = read_ad();           // save temp on startup  
+            ambient_temp = read_temp(boiler);           // 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
+            heater = OFF;            // turn off heater
+            led_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);  // every second
+        if (time(NULL) > prev_time) debug("A/D value = %u %u, heat = %F\r\n",temp,read_temp(group),heat);  // once per second
         prev_time = time(NULL);
 
     } // for (;;)
@@ -141,54 +199,87 @@
 
 //=================================================================
 // This subroutine is called when the button is pressed, 10 seconds
-// before the pump is started.  It does open loop PWM power/heat control.
+// before the pump is started.  It does both open loop and closed 
+// loop PWM power/heat control.
 //=================================================================
 
 void brew(void)
 {
     unsigned start_time = time(NULL);
-    
+ 
     double adjust = 1;              // default is no adjustment
-    
+
     // adjust for higher or lower 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); 
-   
+    // add in "heat"??
+    //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);  
-            
+    led_color(WHITE);
+    
+    unsigned prev_brew_time = 999;
+    unsigned brew_time;
+    double pwm;
+    
     for (;;) {
-        unsigned brew_time;
-        static unsigned prev_brew_time;
-        
-        brew_time = time(NULL) - start_time;
-       
+        brew_time = time(NULL) - start_time;        // seconds into cycle
+
         if (brew_time >= BREW_PREHEAT + BREW_TIME)
-           break;
- 
+            break;                                  // brew is done
+
         if (brew_time == BREW_PREHEAT)
-           set_color(BLUE);    // set LED color to blue for start brew/pump now
-         
-        double pwm = table[brew_time] - (int)table[brew_time];    // decimal part only
- 
+            led_color(BLUE);    // set LED color to blue for start brew/pump now
+
+        //pump = pump_table[brew_time];                      // duty cycle
+        
+        pwm = table[brew_time] - (int)table[brew_time];    // decimal part only
+
         // if too cold, apply the PWM value, if too hot, do nothing
-        if (read_ad() < (TARGET_TEMP + (table[brew_time] * AD_PER_DEGREE)) * adjust) {
-           ssr = 1;      
-           wait(pwm / 2);
-           ssr = 0;
-           wait((1 - pwm) / 2);
+        if (read_temp(boiler) < (TARGET_TEMP + (table[brew_time] * AD_PER_DEGREE)) * adjust) {
+            if (pwm > 0) {
+               heater = ON;
+               wait(pwm/2);
+               heater = OFF;
+               wait((1 - pwm)/2);
+            }
         }  // if PWM
-        
-        if (brew_time != prev_brew_time)     // print status every second
-           debug("target temp %u = %F, temp = %u\r\n",brew_time,table[brew_time],read_ad());
-        prev_brew_time = brew_time;
+
+        if (brew_time != prev_brew_time){                   // every second
+            group_log[brew_time] = read_temp(group);        // record group temp 
+            boiler_log[brew_time] = read_temp(boiler);      // record boiler temp
+            debug("target temp %u = %F, temp = %u %u\r\n",brew_time,table[brew_time],read_temp(boiler),read_temp(group));
+            prev_brew_time = brew_time;
+        }
 
     } // for
-  
-    ssr = 0;
+
     debug("brew done\r\n");
+ 
+} // brew()
 
-} // brew()
+//===========================================================
+// control to a higher steam temperature for n seconds
+//===========================================================
+
+void steam(int seconds)
+{
+    unsigned start_time = time(NULL);
+
+    debug("steam start, time = %d\r\n", seconds);
+
+    while (time(NULL) - start_time < seconds) {
+        if (read_temp(boiler) > STEAM_TEMP) {
+            heater = OFF;            // turn off heater
+            led_color(AQUA);    // set LED to aqua
+        } else {
+            heater = ON;            // turn on heater
+            led_color(PINK);    // set LED to pink
+        }
+    } // while
+
+    heater = OFF;    // turn off
+
+} // steam()
 
 
 // =============================================
@@ -199,58 +290,65 @@
 DigitalOut g (LED_GREEN);
 DigitalOut b (LED_BLUE);
 
-void set_color(int color)
+void led_color(int color)
 {
-
 // turn off
-r = g = b = 1;
+    r = g = b = 1;
 
-switch (color) {
-    case OFF:
-        break;
-    case GREEN:
-        g = 0;
-        break;
-    case BLUE:
-        b = 0;
-        break;
-    case RED:
-        r = 0;
-        break;
-    case YELLOW:
-        r = g = 0;
-        break;
-    case WHITE:
-        r = g = b = 0;
-        break;
- }  // switch
- 
- } // set_color()
+    switch (color) {
+        case OFF:
+            break;
+        case GREEN:
+            g = 0;
+            break;
+        case BLUE:
+            b = 0;
+            break;
+        case RED:
+            r = 0;
+            break;
+        case YELLOW:
+            r = g = 0;
+            break;
+        case AQUA:
+            b = g = 0;
+            break;
+        case PINK:
+            r = b = 0;
+            break;
+        case WHITE:
+            r = g = b = 0;
+            break;
+    }  // switch
+
+} // led_color()
 
 //=======================================
 // read A/D value from RTD
-// median for accuracy
+// median and average for accuracy
 //=======================================
 
-unsigned read_ad(void) 
+unsigned read_temp(AnalogIn adc)
 {
+    uint32_t sum=0;
+    int i;
 
-uint32_t sum=0;
-int i;
+    for (i = 0; i < 33; ++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();
 
-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)) sum += a;
-    else if ((b >= a && b <= c) || (b >= c && b <= a)) sum += b;
-    else sum += c;
-} // for
+        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
+    return sum / 33;
+
+}  // read_temp()
+
+
+
+