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@4:3d661b485d59, 2013-10-02 (annotated)
- Committer:
- jzeeff
- Date:
- Wed Oct 02 13:38:23 2013 +0000
- Revision:
- 4:3d661b485d59
- Parent:
- 3:eb60e36b03f6
- Child:
- 5:0393adfdd439
Added initial scale code
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 | 3:eb60e36b03f6 | 3 | // Similar to multiple PID control (pre-brew, brew and steam), |
jzeeff | 3:eb60e36b03f6 | 4 | // but uses a flexible open and closed loop table during brew |
jzeeff | 4:3d661b485d59 | 5 | // Used with a Gaggia Classic, FreeScale FRDM-KL25Z computer, PT1000 RTD, SSR |
jzeeff | 4:3d661b485d59 | 6 | // Can also control the pump |
jzeeff | 3:eb60e36b03f6 | 7 | // See www.coffeegeeks.com for discussion |
jzeeff | 0:24cdf76455c4 | 8 | // Jon Zeeff, 2013 |
jzeeff | 0:24cdf76455c4 | 9 | // Public Domain |
jzeeff | 0:24cdf76455c4 | 10 | |
jzeeff | 4:3d661b485d59 | 11 | // PT1000 RTD ohms (use Google to find a full table, remember to add offset) |
jzeeff | 4:3d661b485d59 | 12 | // 1362 ohms = 94C |
jzeeff | 4:3d661b485d59 | 13 | // 1374 ohms = 97C |
jzeeff | 0:24cdf76455c4 | 14 | // 1000 ohms = too cold (0C) |
jzeeff | 0:24cdf76455c4 | 15 | // 1520 ohms = too hot (136C) |
jzeeff | 0:24cdf76455c4 | 16 | |
jzeeff | 4:3d661b485d59 | 17 | // note: assume a precise 2.2K divider resistor, a PT1000 RTD and a 16 bit A/D result |
jzeeff | 2:22d9c714b511 | 18 | // use this formula: A/D = (RTD_OHMS/(RTD_OHMS+2200)) * 65536 |
jzeeff | 0:24cdf76455c4 | 19 | |
jzeeff | 1:b5abc8ddd567 | 20 | // desired A/D value for boiler temp while idling |
jzeeff | 4:3d661b485d59 | 21 | // note: there is usually some offset between boiler wall temp sensors and actual water temp (10-15C?) |
jzeeff | 4:3d661b485d59 | 22 | #define TARGET_OHMS 1400 // Desired PT1000 RTD Ohms - CHANGE THIS |
jzeeff | 1:b5abc8ddd567 | 23 | |
jzeeff | 4:3d661b485d59 | 24 | // Table of adjustments (degrees C) to TARGET_TEMP and heat vs time (seconds) into brew cycle (including preheat period) |
jzeeff | 2:22d9c714b511 | 25 | // The idea is that extra heat is needed as cool water comes into the boiler during brew. |
jzeeff | 2:22d9c714b511 | 26 | // Extra heat is provided by a higher than normal boiler wall temp. |
jzeeff | 3:eb60e36b03f6 | 27 | // NOTE: the fractional portion of the value is used as the PWM value to be applied if more heat is needed. |
jzeeff | 2:22d9c714b511 | 28 | // This can prevent overshoot. |
jzeeff | 3:eb60e36b03f6 | 29 | // Example: 5.3 means that the boiler wall should be 5 degrees C above normal at this time point. If not, apply 30% power. |
jzeeff | 3:eb60e36b03f6 | 30 | // Example: 99.99 means (roughly) that the heater should be completely on for the 1 second period |
jzeeff | 4:3d661b485d59 | 31 | // Note: heat on a Gaggia Classic takes about 4 seconds before it is seen by the sensor |
jzeeff | 4:3d661b485d59 | 32 | |
jzeeff | 4:3d661b485d59 | 33 | #define BREW_TIME 44 // max brew time |
jzeeff | 4:3d661b485d59 | 34 | #define BREW_PREHEAT 6 // max preheat time |
jzeeff | 2:22d9c714b511 | 35 | |
jzeeff | 4:3d661b485d59 | 36 | const double table[BREW_TIME+BREW_PREHEAT] = { |
jzeeff | 4:3d661b485d59 | 37 | // preheat up to 6 seconds |
jzeeff | 4:3d661b485d59 | 38 | 0,0,0,0,99.99,99.99, // CHANGE THIS |
jzeeff | 0:24cdf76455c4 | 39 | // brewing (pump is on) up to 30 seconds |
jzeeff | 4:3d661b485d59 | 40 | 0,0,0,0,0,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.40,99.35,99.35, // CHANGE THIS |
jzeeff | 4:3d661b485d59 | 41 | 99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35, // CHANGE THIS |
jzeeff | 4:3d661b485d59 | 42 | 99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35 |
jzeeff | 3:eb60e36b03f6 | 43 | }; |
jzeeff | 3:eb60e36b03f6 | 44 | |
jzeeff | 4:3d661b485d59 | 45 | // pump power over time for flush or preinfusion or pressure profiling |
jzeeff | 4:3d661b485d59 | 46 | const double pump_table[BREW_TIME+BREW_PREHEAT] = { |
jzeeff | 3:eb60e36b03f6 | 47 | // during pre-brew period |
jzeeff | 4:3d661b485d59 | 48 | 0,0,0,0,.80,.80, // CHANGE THIS |
jzeeff | 3:eb60e36b03f6 | 49 | // brewing up to 30 seconds |
jzeeff | 4:3d661b485d59 | 50 | .85,.90,1.0,0,0,0,0,0,.80,1.0,1.0,1.0,1.0,1.0,1.0,1.0, // CHANGE THIS |
jzeeff | 4:3d661b485d59 | 51 | 1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0, // CHANGE THIS |
jzeeff | 4:3d661b485d59 | 52 | .85,.85,.85,.85,.85,.85,.85,.85,.85,.85 |
jzeeff | 4:3d661b485d59 | 53 | }; |
jzeeff | 4:3d661b485d59 | 54 | |
jzeeff | 4:3d661b485d59 | 55 | // desired total weight of espresso over brew period in grams |
jzeeff | 4:3d661b485d59 | 56 | const int scale_table[BREW_TIME+BREW_PREHEAT] = { |
jzeeff | 4:3d661b485d59 | 57 | 2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50, |
jzeeff | 4:3d661b485d59 | 58 | 60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60 |
jzeeff | 3:eb60e36b03f6 | 59 | }; |
jzeeff | 0:24cdf76455c4 | 60 | |
jzeeff | 1:b5abc8ddd567 | 61 | // these probably don't need to be changed if you are using a Gaggia Classic |
jzeeff | 4:3d661b485d59 | 62 | #define AD_PER_DEGREE 43 // how many A/D counts equal a 1 degree C change |
jzeeff | 4:3d661b485d59 | 63 | #define AD_PER_GRAM 76.75 // how many A/D count equal 1 gram of weight |
jzeeff | 3:eb60e36b03f6 | 64 | #define CLOSE 60 // how close in A/D value before switching to learned value control |
jzeeff | 4:3d661b485d59 | 65 | #define GAIN .01 // how fast to adjust (eg 1% percent per 2.7s control period) |
jzeeff | 3:eb60e36b03f6 | 66 | #define INITIAL_POWER .03 // initial guess for steady state heater power needed (try .03 = 3%) |
jzeeff | 0:24cdf76455c4 | 67 | #define MIN_TEMP 21000 // below this is an error |
jzeeff | 4:3d661b485d59 | 68 | #define MAX_TEMP 29700 // above this is an error |
jzeeff | 3:eb60e36b03f6 | 69 | #define STEAM_TEMP 28000 // boiler temp while steaming |
jzeeff | 4:3d661b485d59 | 70 | #define ROOM_TEMP 21707 // A/D value at standard ambient room temp 23C |
jzeeff | 4:3d661b485d59 | 71 | #define MAX_ROOM_TEMP (ROOM_TEMP + (10 * AD_PER_DEGREE)) // above this means ambient isn't valid |
jzeeff | 4:3d661b485d59 | 72 | #define SLEEP_PERIOD (3*3600) // turn off heat after this many seconds |
jzeeff | 4:3d661b485d59 | 73 | #define WAKEUP_TIME 12 // time in 0-23 hours, GMT to wake up. 99 to disable. Example: 12 for noon GMT |
jzeeff | 4:3d661b485d59 | 74 | |
jzeeff | 4:3d661b485d59 | 75 | #define TARGET_TEMP ((TARGET_OHMS*65536)/(TARGET_OHMS+2200)) // how hot the boiler should be in A/D |
jzeeff | 0:24cdf76455c4 | 76 | #define debug if (1) printf // use if (1) or if (0) |
jzeeff | 0:24cdf76455c4 | 77 | |
jzeeff | 3:eb60e36b03f6 | 78 | #include "mbed.h" |
jzeeff | 3:eb60e36b03f6 | 79 | #include "TSISensor.h" // touch sensor |
jzeeff | 3:eb60e36b03f6 | 80 | #include "DS1307.h" // real-time clock |
jzeeff | 3:eb60e36b03f6 | 81 | #include "FastPWM.h" // better PWM routine for pump control |
jzeeff | 4:3d661b485d59 | 82 | |
jzeeff | 4:3d661b485d59 | 83 | #define BOILER 0 |
jzeeff | 4:3d661b485d59 | 84 | #define GROUP 1 |
jzeeff | 4:3d661b485d59 | 85 | #define SCALE 2 |
jzeeff | 4:3d661b485d59 | 86 | |
jzeeff | 3:eb60e36b03f6 | 87 | #define OFF 0 |
jzeeff | 3:eb60e36b03f6 | 88 | #define RED 1 |
jzeeff | 3:eb60e36b03f6 | 89 | #define GREEN 2 |
jzeeff | 3:eb60e36b03f6 | 90 | #define BLUE 3 |
jzeeff | 3:eb60e36b03f6 | 91 | #define WHITE 4 |
jzeeff | 3:eb60e36b03f6 | 92 | #define YELLOW 5 |
jzeeff | 3:eb60e36b03f6 | 93 | #define AQUA 6 |
jzeeff | 3:eb60e36b03f6 | 94 | #define PINK 7 |
jzeeff | 3:eb60e36b03f6 | 95 | |
jzeeff | 3:eb60e36b03f6 | 96 | #define ON 1 |
jzeeff | 3:eb60e36b03f6 | 97 | #define OFF 0 |
jzeeff | 1:b5abc8ddd567 | 98 | |
jzeeff | 4:3d661b485d59 | 99 | // pin assignments |
jzeeff | 3:eb60e36b03f6 | 100 | DigitalOut heater(PTD7); // Solid State Relay - PTD6&7 have high drive capability |
jzeeff | 3:eb60e36b03f6 | 101 | FastPWM pump(PTD4); // Solid State Relay - PTD4 can do PWM @ 10K hz |
jzeeff | 3:eb60e36b03f6 | 102 | DigitalOut led_green(LED_GREEN); |
jzeeff | 4:3d661b485d59 | 103 | I2C gI2c(PTE0, PTE1); // SDA, SCL - use pullups somewhere |
jzeeff | 3:eb60e36b03f6 | 104 | RtcDs1307 rtclock(gI2c); // DS1307 is a real time clock chip |
jzeeff | 3:eb60e36b03f6 | 105 | Serial pc(USBTX, USBRX); // Serial to pc connection |
jzeeff | 4:3d661b485d59 | 106 | TSISensor tsi; // used as a brew start button |
jzeeff | 4:3d661b485d59 | 107 | AnalogIn scale(PTC2); // A/D converter reads scale |
jzeeff | 4:3d661b485d59 | 108 | |
jzeeff | 3:eb60e36b03f6 | 109 | void brew(void); |
jzeeff | 3:eb60e36b03f6 | 110 | void led_color(int color); |
jzeeff | 4:3d661b485d59 | 111 | unsigned read_temp(int device); |
jzeeff | 4:3d661b485d59 | 112 | unsigned read_ad(AnalogIn adc); |
jzeeff | 3:eb60e36b03f6 | 113 | void steam(int seconds); |
jzeeff | 3:eb60e36b03f6 | 114 | |
jzeeff | 3:eb60e36b03f6 | 115 | unsigned ambient_temp; // room or water tank temp (startup) |
jzeeff | 3:eb60e36b03f6 | 116 | double heat = INITIAL_POWER; // initial fractional heat needed while idle |
jzeeff | 4:3d661b485d59 | 117 | unsigned boiler_log[BREW_TIME+BREW_PREHEAT]; // record boiler temp during brew |
jzeeff | 4:3d661b485d59 | 118 | unsigned group_log[BREW_TIME+BREW_PREHEAT]; // record basket temp during brew |
jzeeff | 4:3d661b485d59 | 119 | int scale_log[BREW_TIME+BREW_PREHEAT]; // record weight during brew |
jzeeff | 3:eb60e36b03f6 | 120 | |
jzeeff | 3:eb60e36b03f6 | 121 | int main() // start of program |
jzeeff | 0:24cdf76455c4 | 122 | { |
jzeeff | 0:24cdf76455c4 | 123 | time_t prev_time = 0; |
jzeeff | 4:3d661b485d59 | 124 | |
jzeeff | 4:3d661b485d59 | 125 | led_color(OFF); |
jzeeff | 4:3d661b485d59 | 126 | |
jzeeff | 4:3d661b485d59 | 127 | wait(1); // let settle |
jzeeff | 4:3d661b485d59 | 128 | ambient_temp = read_temp(BOILER); // save temp on startup |
jzeeff | 3:eb60e36b03f6 | 129 | |
jzeeff | 3:eb60e36b03f6 | 130 | #if 0 |
jzeeff | 4:3d661b485d59 | 131 | DateTime compiled(__DATE__, __TIME__); // to set RT clock initially |
jzeeff | 4:3d661b485d59 | 132 | rtclock.adjust(compiled); |
jzeeff | 4:3d661b485d59 | 133 | #endif |
jzeeff | 4:3d661b485d59 | 134 | DateTime dt = rtclock.now(); // check clock value |
jzeeff | 4:3d661b485d59 | 135 | debug("RTC = %u/%u/%02u %2u:%02u:%02u\r\n" |
jzeeff | 4:3d661b485d59 | 136 | ,dt.month(),dt.day(),dt.year() |
jzeeff | 4:3d661b485d59 | 137 | ,dt.hour(),dt.minute(),dt.second()); |
jzeeff | 4:3d661b485d59 | 138 | set_time(0); // set active clock |
jzeeff | 3:eb60e36b03f6 | 139 | |
jzeeff | 4:3d661b485d59 | 140 | debug("starting A/D value/temp = %u %u\r\n",ambient_temp,read_temp(GROUP)); |
jzeeff | 4:3d661b485d59 | 141 | |
jzeeff | 4:3d661b485d59 | 142 | pump = 0; // duty cycle. |
jzeeff | 4:3d661b485d59 | 143 | pump.period_us(410); // period of PWM signal in us |
jzeeff | 4:3d661b485d59 | 144 | |
jzeeff | 3:eb60e36b03f6 | 145 | if (pc.readable()) // clear any data on serial port |
jzeeff | 4:3d661b485d59 | 146 | pc.getc(); |
jzeeff | 4:3d661b485d59 | 147 | |
jzeeff | 3:eb60e36b03f6 | 148 | if (ambient_temp < MAX_ROOM_TEMP) |
jzeeff | 4:3d661b485d59 | 149 | steam(5 * 60); // do accelerated warmup by overheating for awhile |
jzeeff | 3:eb60e36b03f6 | 150 | |
jzeeff | 3:eb60e36b03f6 | 151 | // loop forever, controlling boiler temperature |
jzeeff | 0:24cdf76455c4 | 152 | |
jzeeff | 0:24cdf76455c4 | 153 | for (;;) { |
jzeeff | 0:24cdf76455c4 | 154 | // read temp from A/D |
jzeeff | 0:24cdf76455c4 | 155 | // note: in A/D counts, not degrees |
jzeeff | 4:3d661b485d59 | 156 | unsigned temp = read_temp(BOILER); |
jzeeff | 3:eb60e36b03f6 | 157 | |
jzeeff | 0:24cdf76455c4 | 158 | // bang/bang when far away, PWM to learned value when close |
jzeeff | 0:24cdf76455c4 | 159 | if (temp > TARGET_TEMP + CLOSE) { |
jzeeff | 3:eb60e36b03f6 | 160 | heater = OFF; // turn off heater |
jzeeff | 3:eb60e36b03f6 | 161 | led_color(GREEN); // set LED to green |
jzeeff | 4:3d661b485d59 | 162 | wait(.17); |
jzeeff | 0:24cdf76455c4 | 163 | } else if (temp < TARGET_TEMP - CLOSE) { |
jzeeff | 3:eb60e36b03f6 | 164 | heater = ON; // turn on heater |
jzeeff | 3:eb60e36b03f6 | 165 | led_color(RED); // set LED to red |
jzeeff | 4:3d661b485d59 | 166 | wait(.17); |
jzeeff | 0:24cdf76455c4 | 167 | } else { // close to target temp |
jzeeff | 0:24cdf76455c4 | 168 | // learning mode - adjust heat, the fraction of time power should be on |
jzeeff | 3:eb60e36b03f6 | 169 | |
jzeeff | 3:eb60e36b03f6 | 170 | if (temp > TARGET_TEMP) // adjust best guess for % heat needed |
jzeeff | 3:eb60e36b03f6 | 171 | heat *= (1-GAIN); |
jzeeff | 0:24cdf76455c4 | 172 | else |
jzeeff | 3:eb60e36b03f6 | 173 | heat *= (1+GAIN); |
jzeeff | 3:eb60e36b03f6 | 174 | |
jzeeff | 3:eb60e36b03f6 | 175 | heater = ON; // turn on heater for PWM |
jzeeff | 3:eb60e36b03f6 | 176 | led_color(RED); |
jzeeff | 3:eb60e36b03f6 | 177 | wait(heat * 2.7); // 1.7 to reduce interaction with 50/60Hz power |
jzeeff | 3:eb60e36b03f6 | 178 | heater = OFF; // turn off heater |
jzeeff | 3:eb60e36b03f6 | 179 | led_color(GREEN); |
jzeeff | 3:eb60e36b03f6 | 180 | wait((1-heat) * 2.7); // total time is 2.7 seconds |
jzeeff | 0:24cdf76455c4 | 181 | } // if |
jzeeff | 0:24cdf76455c4 | 182 | |
jzeeff | 0:24cdf76455c4 | 183 | // the user must press a button 10 seconds prior to brewing to start preheat |
jzeeff | 4:3d661b485d59 | 184 | if (tsi.readPercentage() > .5) { |
jzeeff | 0:24cdf76455c4 | 185 | brew(); |
jzeeff | 4:3d661b485d59 | 186 | set_time(0); // stay awake for awhile more |
jzeeff | 4:3d661b485d59 | 187 | } |
jzeeff | 4:3d661b485d59 | 188 | |
jzeeff | 3:eb60e36b03f6 | 189 | // if they signaled for steam |
jzeeff | 4:3d661b485d59 | 190 | //if (tsi.readPercentage() > .1 && tsi.readPercentage() < .5) |
jzeeff | 4:3d661b485d59 | 191 | // steam(120); |
jzeeff | 4:3d661b485d59 | 192 | |
jzeeff | 4:3d661b485d59 | 193 | if (pc.readable()) { // Check if data is available on serial port. |
jzeeff | 4:3d661b485d59 | 194 | pc.getc(); |
jzeeff | 4:3d661b485d59 | 195 | // debug, print out brew temp log |
jzeeff | 3:eb60e36b03f6 | 196 | int i; |
jzeeff | 4:3d661b485d59 | 197 | for (i = 0; i < BREW_TIME+BREW_PREHEAT; ++i) |
jzeeff | 4:3d661b485d59 | 198 | printf("log %d: %u %u %d\r\n",i,boiler_log[i],group_log[i],scale_log[i]); |
jzeeff | 3:eb60e36b03f6 | 199 | } // if |
jzeeff | 3:eb60e36b03f6 | 200 | |
jzeeff | 4:3d661b485d59 | 201 | // check for idle shutdown, sleep till tomorrow am if it occurs |
jzeeff | 4:3d661b485d59 | 202 | if (time(NULL) > SLEEP_PERIOD) { // save power |
jzeeff | 4:3d661b485d59 | 203 | heater = OFF; // turn off heater |
jzeeff | 3:eb60e36b03f6 | 204 | led_color(OFF); |
jzeeff | 3:eb60e36b03f6 | 205 | printf("sleep\r\n"); |
jzeeff | 4:3d661b485d59 | 206 | |
jzeeff | 4:3d661b485d59 | 207 | for (;;) { // loop till wakeup in the morning |
jzeeff | 4:3d661b485d59 | 208 | DateTime dt; |
jzeeff | 4:3d661b485d59 | 209 | |
jzeeff | 4:3d661b485d59 | 210 | if (pc.readable()) // user wakeup |
jzeeff | 4:3d661b485d59 | 211 | break; |
jzeeff | 4:3d661b485d59 | 212 | |
jzeeff | 4:3d661b485d59 | 213 | dt = rtclock.now(); // read real time clock |
jzeeff | 4:3d661b485d59 | 214 | if (dt.hour() == WAKEUP_TIME && dt.minute() == 0) // GMT time to wake up |
jzeeff | 4:3d661b485d59 | 215 | break; |
jzeeff | 4:3d661b485d59 | 216 | |
jzeeff | 4:3d661b485d59 | 217 | wait(30); |
jzeeff | 4:3d661b485d59 | 218 | } // for |
jzeeff | 4:3d661b485d59 | 219 | |
jzeeff | 4:3d661b485d59 | 220 | set_time(0); // reset active timer |
jzeeff | 4:3d661b485d59 | 221 | debug("exit idle\r\n"); |
jzeeff | 4:3d661b485d59 | 222 | ambient_temp = read_temp(BOILER); // save temp on startup |
jzeeff | 4:3d661b485d59 | 223 | } // if |
jzeeff | 3:eb60e36b03f6 | 224 | |
jzeeff | 0:24cdf76455c4 | 225 | // check for errors (incorrect boiler temp can be dangerous) |
jzeeff | 4:3d661b485d59 | 226 | while (temp > MAX_TEMP || temp < MIN_TEMP) { |
jzeeff | 4:3d661b485d59 | 227 | heater = OFF; // turn off heater |
jzeeff | 4:3d661b485d59 | 228 | led_color(YELLOW); // set LED to indicate error |
jzeeff | 1:b5abc8ddd567 | 229 | debug("error A/D = %u\r\n",temp); |
jzeeff | 4:3d661b485d59 | 230 | wait(60); |
jzeeff | 4:3d661b485d59 | 231 | temp = read_temp(BOILER); |
jzeeff | 0:24cdf76455c4 | 232 | } |
jzeeff | 0:24cdf76455c4 | 233 | |
jzeeff | 4:3d661b485d59 | 234 | if (time(NULL) > prev_time) |
jzeeff | 4:3d661b485d59 | 235 | debug("A/D value = %u %u, heat = %F, scale = %u\r\n",temp,read_temp(GROUP),heat,read_ad(scale)); // once per second |
jzeeff | 1:b5abc8ddd567 | 236 | prev_time = time(NULL); |
jzeeff | 0:24cdf76455c4 | 237 | |
jzeeff | 1:b5abc8ddd567 | 238 | } // for (;;) |
jzeeff | 0:24cdf76455c4 | 239 | |
jzeeff | 0:24cdf76455c4 | 240 | } // main() |
jzeeff | 0:24cdf76455c4 | 241 | |
jzeeff | 0:24cdf76455c4 | 242 | |
jzeeff | 0:24cdf76455c4 | 243 | //================================================================= |
jzeeff | 0:24cdf76455c4 | 244 | // This subroutine is called when the button is pressed, 10 seconds |
jzeeff | 4:3d661b485d59 | 245 | // before the pump is started. It does both open loop and closed |
jzeeff | 3:eb60e36b03f6 | 246 | // loop PWM power/heat control. |
jzeeff | 0:24cdf76455c4 | 247 | //================================================================= |
jzeeff | 0:24cdf76455c4 | 248 | |
jzeeff | 0:24cdf76455c4 | 249 | void brew(void) |
jzeeff | 0:24cdf76455c4 | 250 | { |
jzeeff | 1:b5abc8ddd567 | 251 | double adjust = 1; // default is no adjustment |
jzeeff | 3:eb60e36b03f6 | 252 | |
jzeeff | 2:22d9c714b511 | 253 | // adjust for higher or lower tank temp (assumed to be equal to ambient at startup) |
jzeeff | 3:eb60e36b03f6 | 254 | // add in "heat"?? |
jzeeff | 3:eb60e36b03f6 | 255 | //if (ambient_temp < MAX_ROOM_TEMP) // sanity check |
jzeeff | 3:eb60e36b03f6 | 256 | // adjust = (double)(ROOM_TEMP - TARGET_TEMP) / (double)(ambient_temp - TARGET_TEMP); |
jzeeff | 4:3d661b485d59 | 257 | |
jzeeff | 3:eb60e36b03f6 | 258 | led_color(WHITE); |
jzeeff | 4:3d661b485d59 | 259 | |
jzeeff | 4:3d661b485d59 | 260 | unsigned brew_time; // in seconds |
jzeeff | 3:eb60e36b03f6 | 261 | double pwm; |
jzeeff | 4:3d661b485d59 | 262 | unsigned temp; |
jzeeff | 4:3d661b485d59 | 263 | unsigned scale_zero = read_ad(scale); |
jzeeff | 4:3d661b485d59 | 264 | int grams; |
jzeeff | 3:eb60e36b03f6 | 265 | |
jzeeff | 4:3d661b485d59 | 266 | debug("preheat/brew start, adjust = %F, zero = %u\r\n", adjust,scale_zero); |
jzeeff | 3:eb60e36b03f6 | 267 | |
jzeeff | 4:3d661b485d59 | 268 | for (brew_time = 0; brew_time < BREW_PREHEAT + BREW_TIME; ++brew_time) { // loop until end of brew |
jzeeff | 4:3d661b485d59 | 269 | |
jzeeff | 4:3d661b485d59 | 270 | if (brew_time == BREW_PREHEAT) { |
jzeeff | 3:eb60e36b03f6 | 271 | led_color(BLUE); // set LED color to blue for start brew/pump now |
jzeeff | 4:3d661b485d59 | 272 | } |
jzeeff | 3:eb60e36b03f6 | 273 | |
jzeeff | 4:3d661b485d59 | 274 | pump = pump_table[brew_time]; // duty cycle or on/off of pump for this period |
jzeeff | 4:3d661b485d59 | 275 | |
jzeeff | 4:3d661b485d59 | 276 | pwm = table[brew_time] - (int)table[brew_time]; // decimal part only |
jzeeff | 3:eb60e36b03f6 | 277 | |
jzeeff | 4:3d661b485d59 | 278 | temp = read_temp(BOILER); |
jzeeff | 3:eb60e36b03f6 | 279 | |
jzeeff | 2:22d9c714b511 | 280 | // if too cold, apply the PWM value, if too hot, do nothing |
jzeeff | 4:3d661b485d59 | 281 | if (temp < (TARGET_TEMP + (table[brew_time] * AD_PER_DEGREE)) * adjust) { |
jzeeff | 4:3d661b485d59 | 282 | if (pwm > 0.0 && pwm <= 1.0) { |
jzeeff | 4:3d661b485d59 | 283 | heater = ON; |
jzeeff | 4:3d661b485d59 | 284 | wait(pwm); |
jzeeff | 4:3d661b485d59 | 285 | heater = OFF; |
jzeeff | 4:3d661b485d59 | 286 | pwm = 1 - pwm; |
jzeeff | 4:3d661b485d59 | 287 | if (pwm > 0.0 && pwm <= 1.0) |
jzeeff | 4:3d661b485d59 | 288 | wait(pwm); |
jzeeff | 4:3d661b485d59 | 289 | } else |
jzeeff | 4:3d661b485d59 | 290 | wait(1.0); |
jzeeff | 4:3d661b485d59 | 291 | } else |
jzeeff | 4:3d661b485d59 | 292 | wait(1.0); |
jzeeff | 3:eb60e36b03f6 | 293 | |
jzeeff | 4:3d661b485d59 | 294 | group_log[brew_time] = read_temp(GROUP); // record group temp |
jzeeff | 4:3d661b485d59 | 295 | boiler_log[brew_time] = temp; // record boiler temp |
jzeeff | 4:3d661b485d59 | 296 | grams = ((double)read_ad(scale) - scale_zero) / AD_PER_GRAM; |
jzeeff | 4:3d661b485d59 | 297 | scale_log[brew_time] = grams; |
jzeeff | 4:3d661b485d59 | 298 | |
jzeeff | 4:3d661b485d59 | 299 | if (grams < 2) // scale clock only starts when it hits two grams |
jzeeff | 4:3d661b485d59 | 300 | scale_time = 0; |
jzeeff | 4:3d661b485d59 | 301 | else |
jzeeff | 4:3d661b485d59 | 302 | ++scale_time; |
jzeeff | 4:3d661b485d59 | 303 | |
jzeeff | 4:3d661b485d59 | 304 | //if (grams > scale_table[scale_time]) |
jzeeff | 4:3d661b485d59 | 305 | //else |
jzeeff | 4:3d661b485d59 | 306 | |
jzeeff | 4:3d661b485d59 | 307 | //debug("target temp %u = %F, temp = %u %u\r\n",brew_time,table[brew_time],read_temp(BOILER),read_temp(GROUP)); |
jzeeff | 4:3d661b485d59 | 308 | |
jzeeff | 4:3d661b485d59 | 309 | // early exit if final weight reached |
jzeeff | 4:3d661b485d59 | 310 | if (grams >= scale_table[BREW_TIME+BREW_PREHEAT-1]) |
jzeeff | 4:3d661b485d59 | 311 | break; |
jzeeff | 4:3d661b485d59 | 312 | |
jzeeff | 4:3d661b485d59 | 313 | // early exit based on user input |
jzeeff | 4:3d661b485d59 | 314 | if (tsi.readPercentage() > .1 && tsi.readPercentage() < .5) |
jzeeff | 4:3d661b485d59 | 315 | break; |
jzeeff | 0:24cdf76455c4 | 316 | |
jzeeff | 1:b5abc8ddd567 | 317 | } // for |
jzeeff | 3:eb60e36b03f6 | 318 | |
jzeeff | 4:3d661b485d59 | 319 | // shut down |
jzeeff | 4:3d661b485d59 | 320 | led_color(OFF); |
jzeeff | 0:24cdf76455c4 | 321 | debug("brew done\r\n"); |
jzeeff | 4:3d661b485d59 | 322 | pump = OFF; |
jzeeff | 4:3d661b485d59 | 323 | heater = OFF; |
jzeeff | 3:eb60e36b03f6 | 324 | } // brew() |
jzeeff | 0:24cdf76455c4 | 325 | |
jzeeff | 3:eb60e36b03f6 | 326 | //=========================================================== |
jzeeff | 3:eb60e36b03f6 | 327 | // control to a higher steam temperature for n seconds |
jzeeff | 3:eb60e36b03f6 | 328 | //=========================================================== |
jzeeff | 3:eb60e36b03f6 | 329 | |
jzeeff | 3:eb60e36b03f6 | 330 | void steam(int seconds) |
jzeeff | 3:eb60e36b03f6 | 331 | { |
jzeeff | 3:eb60e36b03f6 | 332 | unsigned start_time = time(NULL); |
jzeeff | 3:eb60e36b03f6 | 333 | |
jzeeff | 3:eb60e36b03f6 | 334 | debug("steam start, time = %d\r\n", seconds); |
jzeeff | 3:eb60e36b03f6 | 335 | |
jzeeff | 3:eb60e36b03f6 | 336 | while (time(NULL) - start_time < seconds) { |
jzeeff | 4:3d661b485d59 | 337 | if (read_temp(BOILER) > STEAM_TEMP) { |
jzeeff | 4:3d661b485d59 | 338 | heater = OFF; // turn off heater |
jzeeff | 3:eb60e36b03f6 | 339 | led_color(AQUA); // set LED to aqua |
jzeeff | 3:eb60e36b03f6 | 340 | } else { |
jzeeff | 4:3d661b485d59 | 341 | heater = ON; // turn on heater |
jzeeff | 3:eb60e36b03f6 | 342 | led_color(PINK); // set LED to pink |
jzeeff | 3:eb60e36b03f6 | 343 | } |
jzeeff | 4:3d661b485d59 | 344 | |
jzeeff | 4:3d661b485d59 | 345 | if (tsi.readPercentage() > .5) // abort steam |
jzeeff | 4:3d661b485d59 | 346 | break; |
jzeeff | 4:3d661b485d59 | 347 | |
jzeeff | 3:eb60e36b03f6 | 348 | } // while |
jzeeff | 3:eb60e36b03f6 | 349 | |
jzeeff | 3:eb60e36b03f6 | 350 | heater = OFF; // turn off |
jzeeff | 3:eb60e36b03f6 | 351 | |
jzeeff | 3:eb60e36b03f6 | 352 | } // steam() |
jzeeff | 0:24cdf76455c4 | 353 | |
jzeeff | 0:24cdf76455c4 | 354 | |
jzeeff | 0:24cdf76455c4 | 355 | // ============================================= |
jzeeff | 0:24cdf76455c4 | 356 | // set multi color LED state |
jzeeff | 0:24cdf76455c4 | 357 | // ============================================= |
jzeeff | 0:24cdf76455c4 | 358 | |
jzeeff | 0:24cdf76455c4 | 359 | DigitalOut r (LED_RED); |
jzeeff | 0:24cdf76455c4 | 360 | DigitalOut g (LED_GREEN); |
jzeeff | 0:24cdf76455c4 | 361 | DigitalOut b (LED_BLUE); |
jzeeff | 0:24cdf76455c4 | 362 | |
jzeeff | 3:eb60e36b03f6 | 363 | void led_color(int color) |
jzeeff | 0:24cdf76455c4 | 364 | { |
jzeeff | 0:24cdf76455c4 | 365 | // turn off |
jzeeff | 3:eb60e36b03f6 | 366 | r = g = b = 1; |
jzeeff | 0:24cdf76455c4 | 367 | |
jzeeff | 3:eb60e36b03f6 | 368 | switch (color) { |
jzeeff | 3:eb60e36b03f6 | 369 | case OFF: |
jzeeff | 3:eb60e36b03f6 | 370 | break; |
jzeeff | 3:eb60e36b03f6 | 371 | case GREEN: |
jzeeff | 3:eb60e36b03f6 | 372 | g = 0; |
jzeeff | 3:eb60e36b03f6 | 373 | break; |
jzeeff | 3:eb60e36b03f6 | 374 | case BLUE: |
jzeeff | 3:eb60e36b03f6 | 375 | b = 0; |
jzeeff | 3:eb60e36b03f6 | 376 | break; |
jzeeff | 3:eb60e36b03f6 | 377 | case RED: |
jzeeff | 3:eb60e36b03f6 | 378 | r = 0; |
jzeeff | 3:eb60e36b03f6 | 379 | break; |
jzeeff | 3:eb60e36b03f6 | 380 | case YELLOW: |
jzeeff | 3:eb60e36b03f6 | 381 | r = g = 0; |
jzeeff | 3:eb60e36b03f6 | 382 | break; |
jzeeff | 3:eb60e36b03f6 | 383 | case AQUA: |
jzeeff | 3:eb60e36b03f6 | 384 | b = g = 0; |
jzeeff | 3:eb60e36b03f6 | 385 | break; |
jzeeff | 3:eb60e36b03f6 | 386 | case PINK: |
jzeeff | 3:eb60e36b03f6 | 387 | r = b = 0; |
jzeeff | 3:eb60e36b03f6 | 388 | break; |
jzeeff | 3:eb60e36b03f6 | 389 | case WHITE: |
jzeeff | 3:eb60e36b03f6 | 390 | r = g = b = 0; |
jzeeff | 3:eb60e36b03f6 | 391 | break; |
jzeeff | 3:eb60e36b03f6 | 392 | } // switch |
jzeeff | 3:eb60e36b03f6 | 393 | |
jzeeff | 3:eb60e36b03f6 | 394 | } // led_color() |
jzeeff | 0:24cdf76455c4 | 395 | |
jzeeff | 4:3d661b485d59 | 396 | #if 1 |
jzeeff | 4:3d661b485d59 | 397 | // reduce noise by making unused A/D into digital outs |
jzeeff | 4:3d661b485d59 | 398 | DigitalOut x1(PTB0); |
jzeeff | 4:3d661b485d59 | 399 | DigitalOut x2(PTB1); |
jzeeff | 4:3d661b485d59 | 400 | DigitalOut x3(PTB2); |
jzeeff | 4:3d661b485d59 | 401 | DigitalOut x4(PTB3); |
jzeeff | 4:3d661b485d59 | 402 | DigitalOut x5(PTE21); |
jzeeff | 4:3d661b485d59 | 403 | DigitalOut x6(PTE23); |
jzeeff | 4:3d661b485d59 | 404 | DigitalOut x7(PTD5); |
jzeeff | 4:3d661b485d59 | 405 | DigitalOut x8(PTD6); |
jzeeff | 4:3d661b485d59 | 406 | DigitalOut x9(PTD1); |
jzeeff | 4:3d661b485d59 | 407 | DigitalOut x10(PTC0); |
jzeeff | 4:3d661b485d59 | 408 | #endif |
jzeeff | 4:3d661b485d59 | 409 | |
jzeeff | 0:24cdf76455c4 | 410 | //======================================= |
jzeeff | 4:3d661b485d59 | 411 | // A/D routines |
jzeeff | 0:24cdf76455c4 | 412 | //======================================= |
jzeeff | 0:24cdf76455c4 | 413 | |
jzeeff | 4:3d661b485d59 | 414 | DigitalOut ad_power(PTB9); // used to turn on/off power to resistors |
jzeeff | 4:3d661b485d59 | 415 | |
jzeeff | 4:3d661b485d59 | 416 | AnalogIn boiler(PTE20); // A/D converter reads temperature on boiler |
jzeeff | 4:3d661b485d59 | 417 | AnalogIn group(PTE22); // A/D for group basket temp |
jzeeff | 4:3d661b485d59 | 418 | AnalogIn vref(PTE29); // A/D for A/D power supply (ad_power) |
jzeeff | 4:3d661b485d59 | 419 | |
jzeeff | 4:3d661b485d59 | 420 | |
jzeeff | 4:3d661b485d59 | 421 | unsigned read_ad(AnalogIn adc) |
jzeeff | 0:24cdf76455c4 | 422 | { |
jzeeff | 3:eb60e36b03f6 | 423 | uint32_t sum=0; |
jzeeff | 3:eb60e36b03f6 | 424 | int i; |
jzeeff | 4:3d661b485d59 | 425 | |
jzeeff | 4:3d661b485d59 | 426 | adc.read_u16(); // throw away one |
jzeeff | 4:3d661b485d59 | 427 | |
jzeeff | 4:3d661b485d59 | 428 | #define COUNT 77 // number of samples to average |
jzeeff | 4:3d661b485d59 | 429 | |
jzeeff | 4:3d661b485d59 | 430 | for (i = 0; i < COUNT; ++i) { // average multiple for more accuracy |
jzeeff | 4:3d661b485d59 | 431 | uint16_t a, b, c; |
jzeeff | 1:b5abc8ddd567 | 432 | |
jzeeff | 4:3d661b485d59 | 433 | a = adc.read_u16(); // take median of 3 values to filter noise |
jzeeff | 3:eb60e36b03f6 | 434 | b = adc.read_u16(); |
jzeeff | 3:eb60e36b03f6 | 435 | c = adc.read_u16(); |
jzeeff | 0:24cdf76455c4 | 436 | |
jzeeff | 3:eb60e36b03f6 | 437 | if ((a >= b && a <= c) || (a >= c && a <= b)) sum += a; |
jzeeff | 3:eb60e36b03f6 | 438 | else if ((b >= a && b <= c) || (b >= c && b <= a)) sum += b; |
jzeeff | 4:3d661b485d59 | 439 | else sum += c; |
jzeeff | 4:3d661b485d59 | 440 | |
jzeeff | 3:eb60e36b03f6 | 441 | } // for |
jzeeff | 1:b5abc8ddd567 | 442 | |
jzeeff | 4:3d661b485d59 | 443 | return sum / COUNT; |
jzeeff | 4:3d661b485d59 | 444 | |
jzeeff | 4:3d661b485d59 | 445 | } // read_temp() |
jzeeff | 4:3d661b485d59 | 446 | |
jzeeff | 4:3d661b485d59 | 447 | |
jzeeff | 4:3d661b485d59 | 448 | // read a temperature in A/D counts |
jzeeff | 4:3d661b485d59 | 449 | // adjust it for vref variations |
jzeeff | 4:3d661b485d59 | 450 | |
jzeeff | 4:3d661b485d59 | 451 | unsigned read_temp(int device) |
jzeeff | 4:3d661b485d59 | 452 | { |
jzeeff | 4:3d661b485d59 | 453 | unsigned value; |
jzeeff | 4:3d661b485d59 | 454 | unsigned max; // A/D reading for the vref supply voltage |
jzeeff | 3:eb60e36b03f6 | 455 | |
jzeeff | 4:3d661b485d59 | 456 | // send power to analog resistors only when needed |
jzeeff | 4:3d661b485d59 | 457 | // this limits self heating |
jzeeff | 4:3d661b485d59 | 458 | |
jzeeff | 4:3d661b485d59 | 459 | ad_power = 1; // turn on supply voltage |
jzeeff | 4:3d661b485d59 | 460 | max = read_ad(vref); // read supply voltage |
jzeeff | 4:3d661b485d59 | 461 | |
jzeeff | 4:3d661b485d59 | 462 | if (device == BOILER) |
jzeeff | 4:3d661b485d59 | 463 | value = (read_ad(boiler) * 65536) / max; // scale to vref |
jzeeff | 4:3d661b485d59 | 464 | else |
jzeeff | 4:3d661b485d59 | 465 | value = (read_ad(group) * 65536) / max; // scale to vref |
jzeeff | 4:3d661b485d59 | 466 | |
jzeeff | 4:3d661b485d59 | 467 | ad_power = 0; |
jzeeff | 4:3d661b485d59 | 468 | |
jzeeff | 4:3d661b485d59 | 469 | return value; |
jzeeff | 4:3d661b485d59 | 470 | } // read_temp() |
jzeeff | 4:3d661b485d59 | 471 | |
jzeeff | 3:eb60e36b03f6 | 472 | |
jzeeff | 3:eb60e36b03f6 | 473 | |
jzeeff | 3:eb60e36b03f6 | 474 | |
jzeeff | 3:eb60e36b03f6 | 475 | |
jzeeff | 4:3d661b485d59 | 476 |