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

Committer:
jzeeff
Date:
Sun Aug 11 20:39:57 2013 +0000
Revision:
2:22d9c714b511
Parent:
1:b5abc8ddd567
Child:
3:eb60e36b03f6
PWM during closed loop brew

Who changed what in which revision?

UserRevisionLine numberNew contents of line
jzeeff 0:24cdf76455c4 1
jzeeff 0:24cdf76455c4 2 // Program to control espresso maker boiler temperatures
jzeeff 2:22d9c714b511 3 // Similar to multiple PID control, but uses a flexible open or closed loop table during brew
jzeeff 0:24cdf76455c4 4 // Used with a Gaggia Classic, FreeScale FRDM-KL25Z computer, PT1000 RTD, SSR
jzeeff 0:24cdf76455c4 5 // Jon Zeeff, 2013
jzeeff 0:24cdf76455c4 6 // Public Domain
jzeeff 0:24cdf76455c4 7
jzeeff 0:24cdf76455c4 8 #include "mbed.h"
jzeeff 0:24cdf76455c4 9 #include "TSISensor.h"
jzeeff 0:24cdf76455c4 10
jzeeff 1:b5abc8ddd567 11 DigitalOut ssr(PTA1); // Solid State Relay
jzeeff 1:b5abc8ddd567 12 AnalogIn adc(PTE20); // A/D converter reads temperature
jzeeff 0:24cdf76455c4 13
jzeeff 0:24cdf76455c4 14 #define OFF 0
jzeeff 0:24cdf76455c4 15 #define RED 1
jzeeff 0:24cdf76455c4 16 #define GREEN 2
jzeeff 0:24cdf76455c4 17 #define BLUE 3
jzeeff 0:24cdf76455c4 18 #define WHITE 4
jzeeff 0:24cdf76455c4 19 #define YELLOW 5
jzeeff 0:24cdf76455c4 20
jzeeff 2:22d9c714b511 21 // PT1000 RTD ohms (use Google to find a full table)
jzeeff 0:24cdf76455c4 22 // 1360 ohms = 94C
jzeeff 0:24cdf76455c4 23 // 1000 ohms = too cold (0C)
jzeeff 0:24cdf76455c4 24 // 1520 ohms = too hot (136C)
jzeeff 0:24cdf76455c4 25
jzeeff 1:b5abc8ddd567 26 // note: assume a 2.2K divider resistor, a PT1000 RTD and a 16 bit A/D result
jzeeff 2:22d9c714b511 27 // use this formula: A/D = (RTD_OHMS/(RTD_OHMS+2200)) * 65536
jzeeff 0:24cdf76455c4 28
jzeeff 1:b5abc8ddd567 29 // desired A/D value for boiler temp while idling
jzeeff 1:b5abc8ddd567 30 // note: there is an offset between boiler wall temp sensors and actual water temp
jzeeff 2:22d9c714b511 31 #define TARGET_TEMP 25900 // CHANGE THIS
jzeeff 1:b5abc8ddd567 32
jzeeff 2:22d9c714b511 33 // Table of adjustments (degrees C) to TARGET_TEMP vs time (seconds) into brew cycle (including preheat period)
jzeeff 2:22d9c714b511 34 // The idea is that extra heat is needed as cool water comes into the boiler during brew.
jzeeff 2:22d9c714b511 35 // Extra heat is provided by a higher than normal boiler wall temp.
jzeeff 2:22d9c714b511 36 // NOTE: the decimal portion of the value is used as the PWM value to be applied if more heat is needed.
jzeeff 2:22d9c714b511 37 // This can prevent overshoot.
jzeeff 2:22d9c714b511 38
jzeeff 2:22d9c714b511 39 const double table[40] = {
jzeeff 0:24cdf76455c4 40 // preheat up to 10 seconds
jzeeff 2:22d9c714b511 41 0,0,0,0,18.99,18.99,0,0,0,0, // CHANGE THIS
jzeeff 0:24cdf76455c4 42 // brewing (pump is on) up to 30 seconds
jzeeff 2:22d9c714b511 43 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
jzeeff 2:22d9c714b511 44 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
jzeeff 0:24cdf76455c4 45 };
jzeeff 0:24cdf76455c4 46
jzeeff 1:b5abc8ddd567 47 // these probably don't need to be changed if you are using a Gaggia Classic
jzeeff 2:22d9c714b511 48 #define CLOSE 30 // how close in A/D value before switching to learned value control
jzeeff 1:b5abc8ddd567 49 #define INITIAL_POWER .05 // initial guess for steady state power needed (try .05 = 5%)
jzeeff 0:24cdf76455c4 50 #define MIN_TEMP 21000 // below this is an error
jzeeff 2:22d9c714b511 51 #define MAX_TEMP 29000 // above this is an error
jzeeff 2:22d9c714b511 52 #define ROOM_TEMP 22000 // A/D value at standard ambient room temp
jzeeff 1:b5abc8ddd567 53 #define MAX_ROOM_TEMP 22500 // above this means ambient isn't valid
jzeeff 0:24cdf76455c4 54 #define SLEEP_TIME 3600 // turn off heat after this many seconds
jzeeff 0:24cdf76455c4 55 #define BREW_TIME 30 // max brew time
jzeeff 0:24cdf76455c4 56 #define BREW_PREHEAT 10 // max preheat time
jzeeff 1:b5abc8ddd567 57 #define AD_PER_DEGREE 44 // how many A/D counts equal a 1 degree C change
jzeeff 0:24cdf76455c4 58 #define debug if (1) printf // use if (1) or if (0)
jzeeff 0:24cdf76455c4 59
jzeeff 0:24cdf76455c4 60 void brew(void);
jzeeff 0:24cdf76455c4 61 void set_color(int color);
jzeeff 0:24cdf76455c4 62 unsigned read_ad(void);
jzeeff 1:b5abc8ddd567 63
jzeeff 1:b5abc8ddd567 64 unsigned ambient_temp; // room or water tank temp
jzeeff 1:b5abc8ddd567 65 double heat = INITIAL_POWER; // initial fractional heat needed while idle
jzeeff 1:b5abc8ddd567 66
jzeeff 0:24cdf76455c4 67 int main()
jzeeff 0:24cdf76455c4 68 {
jzeeff 0:24cdf76455c4 69 time_t prev_time = 0;
jzeeff 0:24cdf76455c4 70 TSISensor tsi; // used as a start button
jzeeff 1:b5abc8ddd567 71 ambient_temp = read_ad(); // save temp on startup
jzeeff 0:24cdf76455c4 72
jzeeff 0:24cdf76455c4 73 set_time(0); // start clock at zero
jzeeff 0:24cdf76455c4 74
jzeeff 0:24cdf76455c4 75 debug("starting A/D value/temp = %u\r\n",ambient_temp);
jzeeff 0:24cdf76455c4 76
jzeeff 0:24cdf76455c4 77 // loop forever, controlling boiler temperature
jzeeff 0:24cdf76455c4 78
jzeeff 0:24cdf76455c4 79 for (;;) {
jzeeff 0:24cdf76455c4 80 // read temp from A/D
jzeeff 0:24cdf76455c4 81 // note: in A/D counts, not degrees
jzeeff 2:22d9c714b511 82 unsigned temp = read_ad();
jzeeff 0:24cdf76455c4 83
jzeeff 0:24cdf76455c4 84 // bang/bang when far away, PWM to learned value when close
jzeeff 0:24cdf76455c4 85 if (temp > TARGET_TEMP + CLOSE) {
jzeeff 0:24cdf76455c4 86 ssr = 0; // turn off heater
jzeeff 0:24cdf76455c4 87 set_color(GREEN); // set LED to green
jzeeff 0:24cdf76455c4 88 } else if (temp < TARGET_TEMP - CLOSE) {
jzeeff 0:24cdf76455c4 89 ssr = 1; // turn on heater
jzeeff 0:24cdf76455c4 90 set_color(RED); // set LED to red
jzeeff 0:24cdf76455c4 91 } else { // close to target temp
jzeeff 0:24cdf76455c4 92 // learning mode - adjust heat, the fraction of time power should be on
jzeeff 1:b5abc8ddd567 93
jzeeff 0:24cdf76455c4 94 if (temp > TARGET_TEMP) // adjust best guess for % heat needed
jzeeff 1:b5abc8ddd567 95 heat *= .98;
jzeeff 0:24cdf76455c4 96 else
jzeeff 1:b5abc8ddd567 97 heat *= 1.02;
jzeeff 0:24cdf76455c4 98
jzeeff 0:24cdf76455c4 99 debug("learned heat = %F, temp = %u\r\n",heat, temp);
jzeeff 0:24cdf76455c4 100 ssr = 1; // turn on heater for PWM
jzeeff 0:24cdf76455c4 101 set_color(RED);
jzeeff 0:24cdf76455c4 102 wait(heat);
jzeeff 0:24cdf76455c4 103 ssr = 0; // turn off heater
jzeeff 0:24cdf76455c4 104 set_color(GREEN);
jzeeff 0:24cdf76455c4 105 wait(1-heat);
jzeeff 0:24cdf76455c4 106 } // if
jzeeff 0:24cdf76455c4 107
jzeeff 0:24cdf76455c4 108 // the user must press a button 10 seconds prior to brewing to start preheat
jzeeff 0:24cdf76455c4 109 if (tsi.readPercentage() > .5)
jzeeff 0:24cdf76455c4 110 brew();
jzeeff 0:24cdf76455c4 111
jzeeff 0:24cdf76455c4 112 // check for idle, sleep till tomorrow if it occurs
jzeeff 0:24cdf76455c4 113 if (time(NULL) > SLEEP_TIME){ // save power
jzeeff 0:24cdf76455c4 114 static time_t wakeup_time = (24 * 60 * 60) - (20 * 60); // 24 hours minus 20 min
jzeeff 0:24cdf76455c4 115
jzeeff 0:24cdf76455c4 116 ssr = 0; // turn off heater
jzeeff 2:22d9c714b511 117 set_color(OFF);
jzeeff 2:22d9c714b511 118 printf("sleep\r\n");
jzeeff 0:24cdf76455c4 119 while (time(NULL) < wakeup_time) // wait till tomorrow
jzeeff 0:24cdf76455c4 120 wait(1);
jzeeff 0:24cdf76455c4 121 set_time(0); // clock runs zero to 24 hours
jzeeff 1:b5abc8ddd567 122 wakeup_time = (24 * 60 * 60); // no 20 min offset needed now
jzeeff 1:b5abc8ddd567 123 ambient_temp = read_ad(); // save temp on startup
jzeeff 0:24cdf76455c4 124 }
jzeeff 0:24cdf76455c4 125
jzeeff 0:24cdf76455c4 126 // check for errors (incorrect boiler temp can be dangerous)
jzeeff 0:24cdf76455c4 127 if (temp > MAX_TEMP || temp < MIN_TEMP) {
jzeeff 0:24cdf76455c4 128 ssr = 0; // turn off heater
jzeeff 0:24cdf76455c4 129 set_color(YELLOW); // set LED to indicate error
jzeeff 1:b5abc8ddd567 130 debug("error A/D = %u\r\n",temp);
jzeeff 0:24cdf76455c4 131 for (;;); // reset needed to exit this
jzeeff 0:24cdf76455c4 132 }
jzeeff 0:24cdf76455c4 133
jzeeff 1:b5abc8ddd567 134 if (time(NULL) > prev_time) debug("A/D value = %u\r\n",temp); // every second
jzeeff 1:b5abc8ddd567 135 prev_time = time(NULL);
jzeeff 0:24cdf76455c4 136
jzeeff 1:b5abc8ddd567 137 } // for (;;)
jzeeff 0:24cdf76455c4 138
jzeeff 0:24cdf76455c4 139 } // main()
jzeeff 0:24cdf76455c4 140
jzeeff 0:24cdf76455c4 141
jzeeff 0:24cdf76455c4 142 //=================================================================
jzeeff 0:24cdf76455c4 143 // This subroutine is called when the button is pressed, 10 seconds
jzeeff 1:b5abc8ddd567 144 // before the pump is started. It does open loop PWM power/heat control.
jzeeff 0:24cdf76455c4 145 //=================================================================
jzeeff 0:24cdf76455c4 146
jzeeff 0:24cdf76455c4 147 void brew(void)
jzeeff 0:24cdf76455c4 148 {
jzeeff 1:b5abc8ddd567 149 unsigned start_time = time(NULL);
jzeeff 1:b5abc8ddd567 150
jzeeff 1:b5abc8ddd567 151 double adjust = 1; // default is no adjustment
jzeeff 1:b5abc8ddd567 152
jzeeff 2:22d9c714b511 153 // adjust for higher or lower tank temp (assumed to be equal to ambient at startup)
jzeeff 1:b5abc8ddd567 154 if (ambient_temp < MAX_ROOM_TEMP) // sanity check
jzeeff 1:b5abc8ddd567 155 adjust = (double)(ROOM_TEMP - TARGET_TEMP) / (double)(ambient_temp - TARGET_TEMP);
jzeeff 1:b5abc8ddd567 156
jzeeff 1:b5abc8ddd567 157 debug("preheat/brew start, adjust = %F\r\n", adjust);
jzeeff 0:24cdf76455c4 158 set_color(WHITE);
jzeeff 0:24cdf76455c4 159
jzeeff 1:b5abc8ddd567 160 for (;;) {
jzeeff 2:22d9c714b511 161 unsigned brew_time;
jzeeff 2:22d9c714b511 162 static unsigned prev_brew_time;
jzeeff 1:b5abc8ddd567 163
jzeeff 2:22d9c714b511 164 brew_time = time(NULL) - start_time;
jzeeff 2:22d9c714b511 165
jzeeff 1:b5abc8ddd567 166 if (brew_time >= BREW_PREHEAT + BREW_TIME)
jzeeff 1:b5abc8ddd567 167 break;
jzeeff 1:b5abc8ddd567 168
jzeeff 1:b5abc8ddd567 169 if (brew_time == BREW_PREHEAT)
jzeeff 0:24cdf76455c4 170 set_color(BLUE); // set LED color to blue for start brew/pump now
jzeeff 2:22d9c714b511 171
jzeeff 2:22d9c714b511 172 double pwm = table[brew_time] - (int)table[brew_time]; // decimal part only
jzeeff 2:22d9c714b511 173
jzeeff 2:22d9c714b511 174 // if too cold, apply the PWM value, if too hot, do nothing
jzeeff 2:22d9c714b511 175 if (read_ad() < (TARGET_TEMP + (table[brew_time] * AD_PER_DEGREE)) * adjust) {
jzeeff 2:22d9c714b511 176 ssr = 1;
jzeeff 2:22d9c714b511 177 wait(pwm / 2);
jzeeff 2:22d9c714b511 178 ssr = 0;
jzeeff 2:22d9c714b511 179 wait((1 - pwm) / 2);
jzeeff 2:22d9c714b511 180 } // if PWM
jzeeff 0:24cdf76455c4 181
jzeeff 2:22d9c714b511 182 if (brew_time != prev_brew_time) // print status every second
jzeeff 2:22d9c714b511 183 debug("target temp %u = %F, temp = %u\r\n",brew_time,table[brew_time],read_ad());
jzeeff 1:b5abc8ddd567 184 prev_brew_time = brew_time;
jzeeff 0:24cdf76455c4 185
jzeeff 1:b5abc8ddd567 186 } // for
jzeeff 1:b5abc8ddd567 187
jzeeff 1:b5abc8ddd567 188 ssr = 0;
jzeeff 0:24cdf76455c4 189 debug("brew done\r\n");
jzeeff 0:24cdf76455c4 190
jzeeff 0:24cdf76455c4 191 } // brew()
jzeeff 0:24cdf76455c4 192
jzeeff 0:24cdf76455c4 193
jzeeff 0:24cdf76455c4 194 // =============================================
jzeeff 0:24cdf76455c4 195 // set multi color LED state
jzeeff 0:24cdf76455c4 196 // =============================================
jzeeff 0:24cdf76455c4 197
jzeeff 0:24cdf76455c4 198 DigitalOut r (LED_RED);
jzeeff 0:24cdf76455c4 199 DigitalOut g (LED_GREEN);
jzeeff 0:24cdf76455c4 200 DigitalOut b (LED_BLUE);
jzeeff 0:24cdf76455c4 201
jzeeff 0:24cdf76455c4 202 void set_color(int color)
jzeeff 0:24cdf76455c4 203 {
jzeeff 0:24cdf76455c4 204
jzeeff 0:24cdf76455c4 205 // turn off
jzeeff 0:24cdf76455c4 206 r = g = b = 1;
jzeeff 0:24cdf76455c4 207
jzeeff 0:24cdf76455c4 208 switch (color) {
jzeeff 0:24cdf76455c4 209 case OFF:
jzeeff 0:24cdf76455c4 210 break;
jzeeff 0:24cdf76455c4 211 case GREEN:
jzeeff 0:24cdf76455c4 212 g = 0;
jzeeff 0:24cdf76455c4 213 break;
jzeeff 0:24cdf76455c4 214 case BLUE:
jzeeff 0:24cdf76455c4 215 b = 0;
jzeeff 0:24cdf76455c4 216 break;
jzeeff 0:24cdf76455c4 217 case RED:
jzeeff 0:24cdf76455c4 218 r = 0;
jzeeff 0:24cdf76455c4 219 break;
jzeeff 0:24cdf76455c4 220 case YELLOW:
jzeeff 0:24cdf76455c4 221 r = g = 0;
jzeeff 0:24cdf76455c4 222 break;
jzeeff 0:24cdf76455c4 223 case WHITE:
jzeeff 0:24cdf76455c4 224 r = g = b = 0;
jzeeff 0:24cdf76455c4 225 break;
jzeeff 0:24cdf76455c4 226 } // switch
jzeeff 0:24cdf76455c4 227
jzeeff 0:24cdf76455c4 228 } // set_color()
jzeeff 0:24cdf76455c4 229
jzeeff 0:24cdf76455c4 230 //=======================================
jzeeff 0:24cdf76455c4 231 // read A/D value from RTD
jzeeff 0:24cdf76455c4 232 // median for accuracy
jzeeff 0:24cdf76455c4 233 //=======================================
jzeeff 0:24cdf76455c4 234
jzeeff 0:24cdf76455c4 235 unsigned read_ad(void)
jzeeff 0:24cdf76455c4 236 {
jzeeff 1:b5abc8ddd567 237
jzeeff 1:b5abc8ddd567 238 uint32_t sum=0;
jzeeff 1:b5abc8ddd567 239 int i;
jzeeff 0:24cdf76455c4 240
jzeeff 1:b5abc8ddd567 241 for (i = 0; i < 3; ++i) { // average multiple for more accuracy
jzeeff 1:b5abc8ddd567 242 unsigned a, b, c;
jzeeff 1:b5abc8ddd567 243
jzeeff 1:b5abc8ddd567 244 a = adc.read_u16(); // take median of 3 values
jzeeff 0:24cdf76455c4 245 b = adc.read_u16();
jzeeff 0:24cdf76455c4 246 c = adc.read_u16();
jzeeff 0:24cdf76455c4 247
jzeeff 1:b5abc8ddd567 248 if ((a >= b && a <= c) || (a >= c && a <= b)) sum += a;
jzeeff 1:b5abc8ddd567 249 else if ((b >= a && b <= c) || (b >= c && b <= a)) sum += b;
jzeeff 1:b5abc8ddd567 250 else sum += c;
jzeeff 1:b5abc8ddd567 251 } // for
jzeeff 1:b5abc8ddd567 252
jzeeff 1:b5abc8ddd567 253 return sum / 3;
jzeeff 0:24cdf76455c4 254
jzeeff 0:24cdf76455c4 255 } // read_ad()
jzeeff 0:24cdf76455c4 256