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
main.cpp@1:b5abc8ddd567, 2013-08-09 (annotated)
- 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?
User | Revision | Line number | New 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 |