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@0:24cdf76455c4, 2013-08-07 (annotated)
- Committer:
- jzeeff
- Date:
- Wed Aug 07 16:10:11 2013 +0000
- Revision:
- 0:24cdf76455c4
- Child:
- 1:b5abc8ddd567
Initial version of espresso machine boiler temp controller
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 | 0:24cdf76455c4 | 3 | // Used with a Gaggia Classic, FreeScale FRDM-KL25Z computer, PT1000 RTD, SSR |
jzeeff | 0:24cdf76455c4 | 4 | // Jon Zeeff, 2013 |
jzeeff | 0:24cdf76455c4 | 5 | // Public Domain |
jzeeff | 0:24cdf76455c4 | 6 | |
jzeeff | 0:24cdf76455c4 | 7 | #include "mbed.h" |
jzeeff | 0:24cdf76455c4 | 8 | #include "TSISensor.h" |
jzeeff | 0:24cdf76455c4 | 9 | |
jzeeff | 0:24cdf76455c4 | 10 | DigitalOut ssr(PTA1); |
jzeeff | 0:24cdf76455c4 | 11 | AnalogIn adc(PTE20); |
jzeeff | 0:24cdf76455c4 | 12 | |
jzeeff | 0:24cdf76455c4 | 13 | #define OFF 0 |
jzeeff | 0:24cdf76455c4 | 14 | #define RED 1 |
jzeeff | 0:24cdf76455c4 | 15 | #define GREEN 2 |
jzeeff | 0:24cdf76455c4 | 16 | #define BLUE 3 |
jzeeff | 0:24cdf76455c4 | 17 | #define WHITE 4 |
jzeeff | 0:24cdf76455c4 | 18 | #define YELLOW 5 |
jzeeff | 0:24cdf76455c4 | 19 | |
jzeeff | 0:24cdf76455c4 | 20 | // RTD ohms |
jzeeff | 0:24cdf76455c4 | 21 | // 1360 ohms = 94C |
jzeeff | 0:24cdf76455c4 | 22 | // 1000 ohms = too cold (0C) |
jzeeff | 0:24cdf76455c4 | 23 | // 1520 ohms = too hot (136C) |
jzeeff | 0:24cdf76455c4 | 24 | |
jzeeff | 0:24cdf76455c4 | 25 | // note: assume a 2K divider resistor, a PT1000 RTD and a 16 bit A/D result |
jzeeff | 0:24cdf76455c4 | 26 | // use this formula (RTD_OHMS/(RTD_OHMS+2000)) * 65536 |
jzeeff | 0:24cdf76455c4 | 27 | |
jzeeff | 0:24cdf76455c4 | 28 | // desired A/D value for boiler temp |
jzeeff | 0:24cdf76455c4 | 29 | #define TARGET_TEMP 25600 // CHANGE THIS |
jzeeff | 0:24cdf76455c4 | 30 | |
jzeeff | 0:24cdf76455c4 | 31 | // table of % power level vs time in seconds into brew cycle (including preheat) |
jzeeff | 0:24cdf76455c4 | 32 | const int table[40] = { |
jzeeff | 0:24cdf76455c4 | 33 | // preheat up to 10 seconds |
jzeeff | 0:24cdf76455c4 | 34 | 0,0,100,100,100,100,0,0,0,0, // CHANGE THIS |
jzeeff | 0:24cdf76455c4 | 35 | // brewing (pump is on) up to 30 seconds |
jzeeff | 0:24cdf76455c4 | 36 | 65,65,65,65,65,65,65,65,65,65,65,65,65,65,65, // CHANGE THIS |
jzeeff | 0:24cdf76455c4 | 37 | 65,65,65,65,65,65,65,65,65,65,65,65,65,65,65 |
jzeeff | 0:24cdf76455c4 | 38 | }; |
jzeeff | 0:24cdf76455c4 | 39 | |
jzeeff | 0:24cdf76455c4 | 40 | // these probably don't need to be changed |
jzeeff | 0:24cdf76455c4 | 41 | #define CLOSE 50 // how close in A/D value before switching to proportional control |
jzeeff | 0:24cdf76455c4 | 42 | #define INITIAL_POWER .03 // initial guess for steady state power needed (try .03) |
jzeeff | 0:24cdf76455c4 | 43 | #define MIN_TEMP 21000 // below this is an error |
jzeeff | 0:24cdf76455c4 | 44 | #define MAX_TEMP 29000 // above this is an error |
jzeeff | 0:24cdf76455c4 | 45 | #define SLEEP_TIME 3600 // turn off heat after this many seconds |
jzeeff | 0:24cdf76455c4 | 46 | #define BREW_TIME 30 // max brew time |
jzeeff | 0:24cdf76455c4 | 47 | #define BREW_PREHEAT 10 // max preheat time |
jzeeff | 0:24cdf76455c4 | 48 | |
jzeeff | 0:24cdf76455c4 | 49 | #define debug if (1) printf // use if (1) or if (0) |
jzeeff | 0:24cdf76455c4 | 50 | |
jzeeff | 0:24cdf76455c4 | 51 | void brew(void); |
jzeeff | 0:24cdf76455c4 | 52 | void set_color(int color); |
jzeeff | 0:24cdf76455c4 | 53 | unsigned read_ad(void); |
jzeeff | 0:24cdf76455c4 | 54 | |
jzeeff | 0:24cdf76455c4 | 55 | int main() |
jzeeff | 0:24cdf76455c4 | 56 | { |
jzeeff | 0:24cdf76455c4 | 57 | unsigned ambient_temp = read_ad(); // save temp on startup (future enhancements) |
jzeeff | 0:24cdf76455c4 | 58 | time_t prev_time = 0; |
jzeeff | 0:24cdf76455c4 | 59 | TSISensor tsi; // used as a start button |
jzeeff | 0:24cdf76455c4 | 60 | |
jzeeff | 0:24cdf76455c4 | 61 | set_time(0); // start clock at zero |
jzeeff | 0:24cdf76455c4 | 62 | |
jzeeff | 0:24cdf76455c4 | 63 | debug("starting A/D value/temp = %u\r\n",ambient_temp); |
jzeeff | 0:24cdf76455c4 | 64 | |
jzeeff | 0:24cdf76455c4 | 65 | // loop forever, controlling boiler temperature |
jzeeff | 0:24cdf76455c4 | 66 | |
jzeeff | 0:24cdf76455c4 | 67 | for (;;) { |
jzeeff | 0:24cdf76455c4 | 68 | unsigned temp; |
jzeeff | 0:24cdf76455c4 | 69 | |
jzeeff | 0:24cdf76455c4 | 70 | // read temp from A/D |
jzeeff | 0:24cdf76455c4 | 71 | // note: in A/D counts, not degrees |
jzeeff | 0:24cdf76455c4 | 72 | temp = read_ad(); |
jzeeff | 0:24cdf76455c4 | 73 | |
jzeeff | 0:24cdf76455c4 | 74 | // bang/bang when far away, PWM to learned value when close |
jzeeff | 0:24cdf76455c4 | 75 | if (temp > TARGET_TEMP + CLOSE) { |
jzeeff | 0:24cdf76455c4 | 76 | ssr = 0; // turn off heater |
jzeeff | 0:24cdf76455c4 | 77 | set_color(GREEN); // set LED to green |
jzeeff | 0:24cdf76455c4 | 78 | } else if (temp < TARGET_TEMP - CLOSE) { |
jzeeff | 0:24cdf76455c4 | 79 | ssr = 1; // turn on heater |
jzeeff | 0:24cdf76455c4 | 80 | set_color(RED); // set LED to red |
jzeeff | 0:24cdf76455c4 | 81 | } else { // close to target temp |
jzeeff | 0:24cdf76455c4 | 82 | // learning mode - adjust heat, the fraction of time power should be on |
jzeeff | 0:24cdf76455c4 | 83 | static double heat = INITIAL_POWER; // initial fractional heat needed while idle |
jzeeff | 0:24cdf76455c4 | 84 | |
jzeeff | 0:24cdf76455c4 | 85 | if (temp > TARGET_TEMP) // adjust best guess for % heat needed |
jzeeff | 0:24cdf76455c4 | 86 | heat *= .99; |
jzeeff | 0:24cdf76455c4 | 87 | else |
jzeeff | 0:24cdf76455c4 | 88 | heat *= 1.01; |
jzeeff | 0:24cdf76455c4 | 89 | |
jzeeff | 0:24cdf76455c4 | 90 | debug("learned heat = %F, temp = %u\r\n",heat, temp); |
jzeeff | 0:24cdf76455c4 | 91 | ssr = 1; // turn on heater for PWM |
jzeeff | 0:24cdf76455c4 | 92 | set_color(RED); |
jzeeff | 0:24cdf76455c4 | 93 | wait(heat); |
jzeeff | 0:24cdf76455c4 | 94 | ssr = 0; // turn off heater |
jzeeff | 0:24cdf76455c4 | 95 | set_color(GREEN); |
jzeeff | 0:24cdf76455c4 | 96 | wait(1-heat); |
jzeeff | 0:24cdf76455c4 | 97 | } // if |
jzeeff | 0:24cdf76455c4 | 98 | |
jzeeff | 0:24cdf76455c4 | 99 | // the user must press a button 10 seconds prior to brewing to start preheat |
jzeeff | 0:24cdf76455c4 | 100 | if (tsi.readPercentage() > .5) |
jzeeff | 0:24cdf76455c4 | 101 | brew(); |
jzeeff | 0:24cdf76455c4 | 102 | |
jzeeff | 0:24cdf76455c4 | 103 | // check for idle, sleep till tomorrow if it occurs |
jzeeff | 0:24cdf76455c4 | 104 | if (time(NULL) > SLEEP_TIME){ // save power |
jzeeff | 0:24cdf76455c4 | 105 | static time_t wakeup_time = (24 * 60 * 60) - (20 * 60); // 24 hours minus 20 min |
jzeeff | 0:24cdf76455c4 | 106 | |
jzeeff | 0:24cdf76455c4 | 107 | ssr = 0; // turn off heater |
jzeeff | 0:24cdf76455c4 | 108 | set_color(OFF); |
jzeeff | 0:24cdf76455c4 | 109 | while (time(NULL) < wakeup_time) // wait till tomorrow |
jzeeff | 0:24cdf76455c4 | 110 | wait(1); |
jzeeff | 0:24cdf76455c4 | 111 | set_time(0); // clock runs zero to 24 hours |
jzeeff | 0:24cdf76455c4 | 112 | wakeup_time = (24 * 60 * 60); // no 15 min offset needed now |
jzeeff | 0:24cdf76455c4 | 113 | } |
jzeeff | 0:24cdf76455c4 | 114 | |
jzeeff | 0:24cdf76455c4 | 115 | // check for errors (incorrect boiler temp can be dangerous) |
jzeeff | 0:24cdf76455c4 | 116 | if (temp > MAX_TEMP || temp < MIN_TEMP) { |
jzeeff | 0:24cdf76455c4 | 117 | ssr = 0; // turn off heater |
jzeeff | 0:24cdf76455c4 | 118 | set_color(YELLOW); // set LED to indicate error |
jzeeff | 0:24cdf76455c4 | 119 | for (;;); // reset needed to exit this |
jzeeff | 0:24cdf76455c4 | 120 | } |
jzeeff | 0:24cdf76455c4 | 121 | |
jzeeff | 0:24cdf76455c4 | 122 | if (time(NULL) > prev_time) debug("A/D value = %u\r\n",temp); |
jzeeff | 0:24cdf76455c4 | 123 | prev_time = time(NULL); |
jzeeff | 0:24cdf76455c4 | 124 | |
jzeeff | 0:24cdf76455c4 | 125 | } // while |
jzeeff | 0:24cdf76455c4 | 126 | |
jzeeff | 0:24cdf76455c4 | 127 | } // main() |
jzeeff | 0:24cdf76455c4 | 128 | |
jzeeff | 0:24cdf76455c4 | 129 | |
jzeeff | 0:24cdf76455c4 | 130 | //================================================================= |
jzeeff | 0:24cdf76455c4 | 131 | // This subroutine is called when the button is pressed, 10 seconds |
jzeeff | 0:24cdf76455c4 | 132 | // before the pump is started. It does open loop power/temp control. |
jzeeff | 0:24cdf76455c4 | 133 | //================================================================= |
jzeeff | 0:24cdf76455c4 | 134 | |
jzeeff | 0:24cdf76455c4 | 135 | void brew(void) |
jzeeff | 0:24cdf76455c4 | 136 | { |
jzeeff | 0:24cdf76455c4 | 137 | time_t start_time = time(NULL); |
jzeeff | 0:24cdf76455c4 | 138 | |
jzeeff | 0:24cdf76455c4 | 139 | #define brew_time (time(NULL) - start_time) |
jzeeff | 0:24cdf76455c4 | 140 | |
jzeeff | 0:24cdf76455c4 | 141 | debug("preheat/brew start\r\n"); |
jzeeff | 0:24cdf76455c4 | 142 | set_color(WHITE); |
jzeeff | 0:24cdf76455c4 | 143 | |
jzeeff | 0:24cdf76455c4 | 144 | while (brew_time < BREW_TIME + BREW_PREHEAT) { // loop for 40 seconds |
jzeeff | 0:24cdf76455c4 | 145 | |
jzeeff | 0:24cdf76455c4 | 146 | if (brew_time >= BREW_PREHEAT) |
jzeeff | 0:24cdf76455c4 | 147 | set_color(BLUE); // set LED color to blue for start brew/pump now |
jzeeff | 0:24cdf76455c4 | 148 | |
jzeeff | 0:24cdf76455c4 | 149 | // PWM power level over .5 second |
jzeeff | 0:24cdf76455c4 | 150 | ssr = 1; // turn on heater |
jzeeff | 0:24cdf76455c4 | 151 | wait(table[brew_time] / 200.0); |
jzeeff | 0:24cdf76455c4 | 152 | ssr = 0; // turn off heater |
jzeeff | 0:24cdf76455c4 | 153 | wait((100 - table[brew_time]) / 200.0); |
jzeeff | 0:24cdf76455c4 | 154 | |
jzeeff | 0:24cdf76455c4 | 155 | debug("power level %u = %u %%, temp = %u\r\n",brew_time,table[brew_time],read_ad()); |
jzeeff | 0:24cdf76455c4 | 156 | |
jzeeff | 0:24cdf76455c4 | 157 | } // while |
jzeeff | 0:24cdf76455c4 | 158 | |
jzeeff | 0:24cdf76455c4 | 159 | set_color(OFF); |
jzeeff | 0:24cdf76455c4 | 160 | debug("brew done\r\n"); |
jzeeff | 0:24cdf76455c4 | 161 | |
jzeeff | 0:24cdf76455c4 | 162 | } // brew() |
jzeeff | 0:24cdf76455c4 | 163 | |
jzeeff | 0:24cdf76455c4 | 164 | |
jzeeff | 0:24cdf76455c4 | 165 | // ============================================= |
jzeeff | 0:24cdf76455c4 | 166 | // set multi color LED state |
jzeeff | 0:24cdf76455c4 | 167 | // ============================================= |
jzeeff | 0:24cdf76455c4 | 168 | |
jzeeff | 0:24cdf76455c4 | 169 | DigitalOut r (LED_RED); |
jzeeff | 0:24cdf76455c4 | 170 | DigitalOut g (LED_GREEN); |
jzeeff | 0:24cdf76455c4 | 171 | DigitalOut b (LED_BLUE); |
jzeeff | 0:24cdf76455c4 | 172 | |
jzeeff | 0:24cdf76455c4 | 173 | void set_color(int color) |
jzeeff | 0:24cdf76455c4 | 174 | { |
jzeeff | 0:24cdf76455c4 | 175 | |
jzeeff | 0:24cdf76455c4 | 176 | // turn off |
jzeeff | 0:24cdf76455c4 | 177 | r = g = b = 1; |
jzeeff | 0:24cdf76455c4 | 178 | |
jzeeff | 0:24cdf76455c4 | 179 | switch (color) { |
jzeeff | 0:24cdf76455c4 | 180 | case OFF: |
jzeeff | 0:24cdf76455c4 | 181 | break; |
jzeeff | 0:24cdf76455c4 | 182 | case GREEN: |
jzeeff | 0:24cdf76455c4 | 183 | g = 0; |
jzeeff | 0:24cdf76455c4 | 184 | break; |
jzeeff | 0:24cdf76455c4 | 185 | case BLUE: |
jzeeff | 0:24cdf76455c4 | 186 | b = 0; |
jzeeff | 0:24cdf76455c4 | 187 | break; |
jzeeff | 0:24cdf76455c4 | 188 | case RED: |
jzeeff | 0:24cdf76455c4 | 189 | r = 0; |
jzeeff | 0:24cdf76455c4 | 190 | break; |
jzeeff | 0:24cdf76455c4 | 191 | case YELLOW: |
jzeeff | 0:24cdf76455c4 | 192 | r = g = 0; |
jzeeff | 0:24cdf76455c4 | 193 | break; |
jzeeff | 0:24cdf76455c4 | 194 | case WHITE: |
jzeeff | 0:24cdf76455c4 | 195 | r = g = b = 0; |
jzeeff | 0:24cdf76455c4 | 196 | break; |
jzeeff | 0:24cdf76455c4 | 197 | } // switch |
jzeeff | 0:24cdf76455c4 | 198 | |
jzeeff | 0:24cdf76455c4 | 199 | } // set_color() |
jzeeff | 0:24cdf76455c4 | 200 | |
jzeeff | 0:24cdf76455c4 | 201 | //======================================= |
jzeeff | 0:24cdf76455c4 | 202 | // read A/D value from RTD |
jzeeff | 0:24cdf76455c4 | 203 | // median for accuracy |
jzeeff | 0:24cdf76455c4 | 204 | //======================================= |
jzeeff | 0:24cdf76455c4 | 205 | |
jzeeff | 0:24cdf76455c4 | 206 | unsigned read_ad(void) |
jzeeff | 0:24cdf76455c4 | 207 | { |
jzeeff | 0:24cdf76455c4 | 208 | unsigned a, b, c; |
jzeeff | 0:24cdf76455c4 | 209 | |
jzeeff | 0:24cdf76455c4 | 210 | a = adc.read_u16(); |
jzeeff | 0:24cdf76455c4 | 211 | b = adc.read_u16(); |
jzeeff | 0:24cdf76455c4 | 212 | c = adc.read_u16(); |
jzeeff | 0:24cdf76455c4 | 213 | |
jzeeff | 0:24cdf76455c4 | 214 | if ((a >= b && a <= c) || (a >= c && a <= b)) return a; |
jzeeff | 0:24cdf76455c4 | 215 | if ((b >= a && b <= c) || (b >= c && b <= a)) return b; |
jzeeff | 0:24cdf76455c4 | 216 | return c; |
jzeeff | 0:24cdf76455c4 | 217 | |
jzeeff | 0:24cdf76455c4 | 218 | } // read_ad() |
jzeeff | 0:24cdf76455c4 | 219 |