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:
Fri Aug 09 20:36:28 2013 +0000
Revision:
1:b5abc8ddd567
Parent:
0:24cdf76455c4
Child:
2:22d9c714b511
various improvements

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 1:b5abc8ddd567 3 // Similar to PID, but uses a flexible open 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 1:b5abc8ddd567 21 // PT1000 RTD ohms
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 1:b5abc8ddd567 27 // use this formula: (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 1:b5abc8ddd567 31 #define TARGET_TEMP 25850 // CHANGE THIS
jzeeff 0:24cdf76455c4 32
jzeeff 1:b5abc8ddd567 33 // table of adjustments (degrees C) to TARGET_TEMP vs time (seconds) into brew cycle (including preheat period)
jzeeff 1:b5abc8ddd567 34 // the idea is that extra heat is needed as cool water comes into the boiler during brew
jzeeff 1:b5abc8ddd567 35 // note: if you alternate very high and negative values here, you get the equivalent of open loop/PWM/temp surfing
jzeeff 1:b5abc8ddd567 36
jzeeff 0:24cdf76455c4 37 const int table[40] = {
jzeeff 0:24cdf76455c4 38 // preheat up to 10 seconds
jzeeff 1:b5abc8ddd567 39 0,0,0,0,0,0,18,18,18,18, // CHANGE THIS
jzeeff 0:24cdf76455c4 40 // brewing (pump is on) up to 30 seconds
jzeeff 1:b5abc8ddd567 41 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, // CHANGE THIS
jzeeff 1:b5abc8ddd567 42 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18
jzeeff 0:24cdf76455c4 43 };
jzeeff 0:24cdf76455c4 44
jzeeff 1:b5abc8ddd567 45 // these probably don't need to be changed if you are using a Gaggia Classic
jzeeff 1:b5abc8ddd567 46 #define CLOSE 30 // how close in A/D value before switching to proportional control
jzeeff 1:b5abc8ddd567 47 #define INITIAL_POWER .05 // initial guess for steady state power needed (try .05 = 5%)
jzeeff 0:24cdf76455c4 48 #define MIN_TEMP 21000 // below this is an error
jzeeff 1:b5abc8ddd567 49 #define MAX_TEMP 218000 // above this is an error
jzeeff 1:b5abc8ddd567 50 #define ROOM_TEMP 22000 // A/D at standard ambient room temp
jzeeff 1:b5abc8ddd567 51 #define MAX_ROOM_TEMP 22500 // above this means ambient isn't valid
jzeeff 0:24cdf76455c4 52 #define SLEEP_TIME 3600 // turn off heat after this many seconds
jzeeff 0:24cdf76455c4 53 #define BREW_TIME 30 // max brew time
jzeeff 0:24cdf76455c4 54 #define BREW_PREHEAT 10 // max preheat time
jzeeff 1:b5abc8ddd567 55 #define AD_PER_DEGREE 44 // how many A/D counts equal a 1 degree C change
jzeeff 0:24cdf76455c4 56 #define debug if (1) printf // use if (1) or if (0)
jzeeff 0:24cdf76455c4 57
jzeeff 0:24cdf76455c4 58 void brew(void);
jzeeff 0:24cdf76455c4 59 void set_color(int color);
jzeeff 0:24cdf76455c4 60 unsigned read_ad(void);
jzeeff 1:b5abc8ddd567 61
jzeeff 1:b5abc8ddd567 62 unsigned ambient_temp; // room or water tank temp
jzeeff 1:b5abc8ddd567 63 double heat = INITIAL_POWER; // initial fractional heat needed while idle
jzeeff 1:b5abc8ddd567 64
jzeeff 0:24cdf76455c4 65 int main()
jzeeff 0:24cdf76455c4 66 {
jzeeff 0:24cdf76455c4 67 time_t prev_time = 0;
jzeeff 0:24cdf76455c4 68 TSISensor tsi; // used as a start button
jzeeff 1:b5abc8ddd567 69 ambient_temp = read_ad(); // save temp on startup
jzeeff 0:24cdf76455c4 70
jzeeff 0:24cdf76455c4 71 set_time(0); // start clock at zero
jzeeff 0:24cdf76455c4 72
jzeeff 0:24cdf76455c4 73 debug("starting A/D value/temp = %u\r\n",ambient_temp);
jzeeff 0:24cdf76455c4 74
jzeeff 0:24cdf76455c4 75 // loop forever, controlling boiler temperature
jzeeff 0:24cdf76455c4 76
jzeeff 0:24cdf76455c4 77 for (;;) {
jzeeff 0:24cdf76455c4 78 unsigned temp;
jzeeff 0:24cdf76455c4 79
jzeeff 0:24cdf76455c4 80 // read temp from A/D
jzeeff 0:24cdf76455c4 81 // note: in A/D counts, not degrees
jzeeff 0:24cdf76455c4 82 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 0:24cdf76455c4 117 set_color(OFF);
jzeeff 0:24cdf76455c4 118 while (time(NULL) < wakeup_time) // wait till tomorrow
jzeeff 0:24cdf76455c4 119 wait(1);
jzeeff 0:24cdf76455c4 120 set_time(0); // clock runs zero to 24 hours
jzeeff 1:b5abc8ddd567 121 wakeup_time = (24 * 60 * 60); // no 20 min offset needed now
jzeeff 1:b5abc8ddd567 122 ambient_temp = read_ad(); // save temp on startup
jzeeff 0:24cdf76455c4 123 }
jzeeff 0:24cdf76455c4 124
jzeeff 0:24cdf76455c4 125 // check for errors (incorrect boiler temp can be dangerous)
jzeeff 0:24cdf76455c4 126 if (temp > MAX_TEMP || temp < MIN_TEMP) {
jzeeff 0:24cdf76455c4 127 ssr = 0; // turn off heater
jzeeff 0:24cdf76455c4 128 set_color(YELLOW); // set LED to indicate error
jzeeff 1:b5abc8ddd567 129 debug("error A/D = %u\r\n",temp);
jzeeff 0:24cdf76455c4 130 for (;;); // reset needed to exit this
jzeeff 0:24cdf76455c4 131 }
jzeeff 0:24cdf76455c4 132
jzeeff 1:b5abc8ddd567 133 if (time(NULL) > prev_time) debug("A/D value = %u\r\n",temp); // every second
jzeeff 1:b5abc8ddd567 134 prev_time = time(NULL);
jzeeff 0:24cdf76455c4 135
jzeeff 1:b5abc8ddd567 136 } // for (;;)
jzeeff 0:24cdf76455c4 137
jzeeff 0:24cdf76455c4 138 } // main()
jzeeff 0:24cdf76455c4 139
jzeeff 0:24cdf76455c4 140
jzeeff 0:24cdf76455c4 141 //=================================================================
jzeeff 0:24cdf76455c4 142 // This subroutine is called when the button is pressed, 10 seconds
jzeeff 1:b5abc8ddd567 143 // before the pump is started. It does open loop PWM power/heat control.
jzeeff 0:24cdf76455c4 144 //=================================================================
jzeeff 0:24cdf76455c4 145
jzeeff 0:24cdf76455c4 146 void brew(void)
jzeeff 0:24cdf76455c4 147 {
jzeeff 1:b5abc8ddd567 148 unsigned start_time = time(NULL);
jzeeff 0:24cdf76455c4 149 #define brew_time (time(NULL) - start_time)
jzeeff 1:b5abc8ddd567 150
jzeeff 1:b5abc8ddd567 151 double adjust = 1; // default is no adjustment
jzeeff 1:b5abc8ddd567 152
jzeeff 1:b5abc8ddd567 153 // adjust for 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 1:b5abc8ddd567 161 unsigned prev_brew_time = 0;
jzeeff 1:b5abc8ddd567 162
jzeeff 1:b5abc8ddd567 163 if (brew_time >= BREW_PREHEAT + BREW_TIME)
jzeeff 1:b5abc8ddd567 164 break;
jzeeff 1:b5abc8ddd567 165
jzeeff 1:b5abc8ddd567 166 if (brew_time == BREW_PREHEAT)
jzeeff 0:24cdf76455c4 167 set_color(BLUE); // set LED color to blue for start brew/pump now
jzeeff 0:24cdf76455c4 168
jzeeff 1:b5abc8ddd567 169 // bang/bang temp to the table value
jzeeff 1:b5abc8ddd567 170 if (read_ad() < (TARGET_TEMP + (table[brew_time] * AD_PER_DEGREE)) * adjust)
jzeeff 1:b5abc8ddd567 171 ssr = 1; // heater on
jzeeff 1:b5abc8ddd567 172 else
jzeeff 1:b5abc8ddd567 173 ssr = 0; // heater off
jzeeff 0:24cdf76455c4 174
jzeeff 1:b5abc8ddd567 175 if (brew_time != prev_brew_time)
jzeeff 1:b5abc8ddd567 176 debug("target temp %u = %u, temp = %u\r\n",brew_time,table[brew_time],read_ad());
jzeeff 1:b5abc8ddd567 177 prev_brew_time = brew_time;
jzeeff 0:24cdf76455c4 178
jzeeff 1:b5abc8ddd567 179 } // for
jzeeff 1:b5abc8ddd567 180
jzeeff 1:b5abc8ddd567 181 ssr = 0;
jzeeff 0:24cdf76455c4 182 debug("brew done\r\n");
jzeeff 0:24cdf76455c4 183
jzeeff 0:24cdf76455c4 184 } // brew()
jzeeff 0:24cdf76455c4 185
jzeeff 0:24cdf76455c4 186
jzeeff 0:24cdf76455c4 187 // =============================================
jzeeff 0:24cdf76455c4 188 // set multi color LED state
jzeeff 0:24cdf76455c4 189 // =============================================
jzeeff 0:24cdf76455c4 190
jzeeff 0:24cdf76455c4 191 DigitalOut r (LED_RED);
jzeeff 0:24cdf76455c4 192 DigitalOut g (LED_GREEN);
jzeeff 0:24cdf76455c4 193 DigitalOut b (LED_BLUE);
jzeeff 0:24cdf76455c4 194
jzeeff 0:24cdf76455c4 195 void set_color(int color)
jzeeff 0:24cdf76455c4 196 {
jzeeff 0:24cdf76455c4 197
jzeeff 0:24cdf76455c4 198 // turn off
jzeeff 0:24cdf76455c4 199 r = g = b = 1;
jzeeff 0:24cdf76455c4 200
jzeeff 0:24cdf76455c4 201 switch (color) {
jzeeff 0:24cdf76455c4 202 case OFF:
jzeeff 0:24cdf76455c4 203 break;
jzeeff 0:24cdf76455c4 204 case GREEN:
jzeeff 0:24cdf76455c4 205 g = 0;
jzeeff 0:24cdf76455c4 206 break;
jzeeff 0:24cdf76455c4 207 case BLUE:
jzeeff 0:24cdf76455c4 208 b = 0;
jzeeff 0:24cdf76455c4 209 break;
jzeeff 0:24cdf76455c4 210 case RED:
jzeeff 0:24cdf76455c4 211 r = 0;
jzeeff 0:24cdf76455c4 212 break;
jzeeff 0:24cdf76455c4 213 case YELLOW:
jzeeff 0:24cdf76455c4 214 r = g = 0;
jzeeff 0:24cdf76455c4 215 break;
jzeeff 0:24cdf76455c4 216 case WHITE:
jzeeff 0:24cdf76455c4 217 r = g = b = 0;
jzeeff 0:24cdf76455c4 218 break;
jzeeff 0:24cdf76455c4 219 } // switch
jzeeff 0:24cdf76455c4 220
jzeeff 0:24cdf76455c4 221 } // set_color()
jzeeff 0:24cdf76455c4 222
jzeeff 0:24cdf76455c4 223 //=======================================
jzeeff 0:24cdf76455c4 224 // read A/D value from RTD
jzeeff 0:24cdf76455c4 225 // median for accuracy
jzeeff 0:24cdf76455c4 226 //=======================================
jzeeff 0:24cdf76455c4 227
jzeeff 0:24cdf76455c4 228 unsigned read_ad(void)
jzeeff 0:24cdf76455c4 229 {
jzeeff 1:b5abc8ddd567 230
jzeeff 1:b5abc8ddd567 231 uint32_t sum=0;
jzeeff 1:b5abc8ddd567 232 int i;
jzeeff 0:24cdf76455c4 233
jzeeff 1:b5abc8ddd567 234 for (i = 0; i < 3; ++i) { // average multiple for more accuracy
jzeeff 1:b5abc8ddd567 235 unsigned a, b, c;
jzeeff 1:b5abc8ddd567 236
jzeeff 1:b5abc8ddd567 237 a = adc.read_u16(); // take median of 3 values
jzeeff 0:24cdf76455c4 238 b = adc.read_u16();
jzeeff 0:24cdf76455c4 239 c = adc.read_u16();
jzeeff 0:24cdf76455c4 240
jzeeff 1:b5abc8ddd567 241 if ((a >= b && a <= c) || (a >= c && a <= b)) sum += a;
jzeeff 1:b5abc8ddd567 242 else if ((b >= a && b <= c) || (b >= c && b <= a)) sum += b;
jzeeff 1:b5abc8ddd567 243 else sum += c;
jzeeff 1:b5abc8ddd567 244 } // for
jzeeff 1:b5abc8ddd567 245
jzeeff 1:b5abc8ddd567 246 return sum / 3;
jzeeff 0:24cdf76455c4 247
jzeeff 0:24cdf76455c4 248 } // read_ad()
jzeeff 0:24cdf76455c4 249