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@6:56b205b46b42, 2014-07-05 (annotated)
- Committer:
- jzeeff
- Date:
- Sat Jul 05 20:52:06 2014 +0000
- Revision:
- 6:56b205b46b42
- Parent:
- 5:0393adfdd439
Stable version
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 | 5:0393adfdd439 | 22 | #define TARGET_OHMS 1400 // Desired PT1000 RTD Ohms / boiler temp - CHANGE THIS |
jzeeff | 5:0393adfdd439 | 23 | |
jzeeff | 5:0393adfdd439 | 24 | #define BREW_TIME 44 // max brew time |
jzeeff | 5:0393adfdd439 | 25 | #define BREW_PREHEAT 6 // max preheat time (when to open brew valve) |
jzeeff | 1:b5abc8ddd567 | 26 | |
jzeeff | 4:3d661b485d59 | 27 | // Table of adjustments (degrees C) to TARGET_TEMP and heat vs time (seconds) into brew cycle (including preheat period) |
jzeeff | 2:22d9c714b511 | 28 | // The idea is that extra heat is needed as cool water comes into the boiler during brew. |
jzeeff | 2:22d9c714b511 | 29 | // Extra heat is provided by a higher than normal boiler wall temp. |
jzeeff | 3:eb60e36b03f6 | 30 | // NOTE: the fractional portion of the value is used as the PWM value to be applied if more heat is needed. |
jzeeff | 2:22d9c714b511 | 31 | // This can prevent overshoot. |
jzeeff | 3:eb60e36b03f6 | 32 | // 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 | 33 | // Example: 99.99 means (roughly) that the heater should be completely on for the 1 second period |
jzeeff | 4:3d661b485d59 | 34 | // Note: heat on a Gaggia Classic takes about 4 seconds before it is seen by the sensor |
jzeeff | 4:3d661b485d59 | 35 | |
jzeeff | 5:0393adfdd439 | 36 | const double table[BREW_TIME+BREW_PREHEAT] = { // CHANGE THIS |
jzeeff | 5:0393adfdd439 | 37 | 0,0,0,0, // nothing (pumo is off) |
jzeeff | 5:0393adfdd439 | 38 | 99.99,99.99, // step heat up before flow |
jzeeff | 5:0393adfdd439 | 39 | 0,0,0,0, // filling portafilter |
jzeeff | 5:0393adfdd439 | 40 | 0,99.35,99.35, // preinfusion |
jzeeff | 5:0393adfdd439 | 41 | 99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.40,99.35,99.35, |
jzeeff | 5:0393adfdd439 | 42 | 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, |
jzeeff | 4:3d661b485d59 | 43 | 99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35 |
jzeeff | 3:eb60e36b03f6 | 44 | }; |
jzeeff | 3:eb60e36b03f6 | 45 | |
jzeeff | 5:0393adfdd439 | 46 | // pump power over time for preinfusion/slow ramp/pressure profiling |
jzeeff | 5:0393adfdd439 | 47 | // range: 0 to 1 |
jzeeff | 5:0393adfdd439 | 48 | const double pump_table[BREW_TIME+BREW_PREHEAT] = { // CHANGE THIS |
jzeeff | 5:0393adfdd439 | 49 | 0,0,0,0, // nothing (pump is off) |
jzeeff | 5:0393adfdd439 | 50 | .45,.45, // hold low pressure until valve is opened |
jzeeff | 5:0393adfdd439 | 51 | .45,.55,.65,.75, // ramp pressure up slowly and fill portafilter |
jzeeff | 5:0393adfdd439 | 52 | 0,0,0, // preinfusion delay |
jzeeff | 5:0393adfdd439 | 53 | .75,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0, // brew |
jzeeff | 4:3d661b485d59 | 54 | 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 | 5:0393adfdd439 | 55 | 1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 |
jzeeff | 4:3d661b485d59 | 56 | }; |
jzeeff | 4:3d661b485d59 | 57 | |
jzeeff | 5:0393adfdd439 | 58 | // table for flow profiling |
jzeeff | 5:0393adfdd439 | 59 | // desired flow rate of espresso in grams/sec for each second of brew |
jzeeff | 5:0393adfdd439 | 60 | // starts when the total grams in the cup achieves the first entry, not time zero |
jzeeff | 5:0393adfdd439 | 61 | const double scale_table[BREW_TIME+BREW_PREHEAT] = { |
jzeeff | 5:0393adfdd439 | 62 | 1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75, |
jzeeff | 5:0393adfdd439 | 63 | 1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75, |
jzeeff | 5:0393adfdd439 | 64 | 1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75, |
jzeeff | 5:0393adfdd439 | 65 | 1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75, |
jzeeff | 5:0393adfdd439 | 66 | 1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75 |
jzeeff | 3:eb60e36b03f6 | 67 | }; |
jzeeff | 0:24cdf76455c4 | 68 | |
jzeeff | 5:0393adfdd439 | 69 | #define END_GRAMS 30 // end when this many grams total (in 27 secs?) |
jzeeff | 5:0393adfdd439 | 70 | #define FLOW_PERIOD 23.0 // desired shot duration (not used) |
jzeeff | 5:0393adfdd439 | 71 | #define START_FLOW_PROF 14 // when (seconds) to start flow profiling |
jzeeff | 5:0393adfdd439 | 72 | |
jzeeff | 1:b5abc8ddd567 | 73 | // these probably don't need to be changed if you are using a Gaggia Classic |
jzeeff | 4:3d661b485d59 | 74 | #define AD_PER_DEGREE 43 // how many A/D counts equal a 1 degree C change |
jzeeff | 6:56b205b46b42 | 75 | #define AD_PER_GRAM 56.0 // how many A/D count equal 1 gram of weight |
jzeeff | 3:eb60e36b03f6 | 76 | #define CLOSE 60 // how close in A/D value before switching to learned value control |
jzeeff | 5:0393adfdd439 | 77 | #define GAIN .01 // how fast to adjust heat(eg 1% percent per 2.7s control period) |
jzeeff | 3:eb60e36b03f6 | 78 | #define INITIAL_POWER .03 // initial guess for steady state heater power needed (try .03 = 3%) |
jzeeff | 0:24cdf76455c4 | 79 | #define MIN_TEMP 21000 // below this is an error |
jzeeff | 4:3d661b485d59 | 80 | #define MAX_TEMP 29700 // above this is an error |
jzeeff | 3:eb60e36b03f6 | 81 | #define STEAM_TEMP 28000 // boiler temp while steaming |
jzeeff | 4:3d661b485d59 | 82 | #define ROOM_TEMP 21707 // A/D value at standard ambient room temp 23C |
jzeeff | 4:3d661b485d59 | 83 | #define MAX_ROOM_TEMP (ROOM_TEMP + (10 * AD_PER_DEGREE)) // above this means ambient isn't valid |
jzeeff | 6:56b205b46b42 | 84 | #define SLEEP_PERIOD (6*3600) // turn off heat after this many seconds |
jzeeff | 6:56b205b46b42 | 85 | #define WAKEUP_TIME 11 // time in 0-23 hours, GMT to wake up. 99 to disable. Example: 12 for noon GMT |
jzeeff | 4:3d661b485d59 | 86 | |
jzeeff | 4:3d661b485d59 | 87 | #define TARGET_TEMP ((TARGET_OHMS*65536)/(TARGET_OHMS+2200)) // how hot the boiler should be in A/D |
jzeeff | 0:24cdf76455c4 | 88 | #define debug if (1) printf // use if (1) or if (0) |
jzeeff | 0:24cdf76455c4 | 89 | |
jzeeff | 3:eb60e36b03f6 | 90 | #include "mbed.h" |
jzeeff | 3:eb60e36b03f6 | 91 | #include "TSISensor.h" // touch sensor |
jzeeff | 3:eb60e36b03f6 | 92 | #include "DS1307.h" // real-time clock |
jzeeff | 3:eb60e36b03f6 | 93 | #include "FastPWM.h" // better PWM routine for pump control |
jzeeff | 4:3d661b485d59 | 94 | |
jzeeff | 4:3d661b485d59 | 95 | #define BOILER 0 |
jzeeff | 4:3d661b485d59 | 96 | #define GROUP 1 |
jzeeff | 4:3d661b485d59 | 97 | #define SCALE 2 |
jzeeff | 4:3d661b485d59 | 98 | |
jzeeff | 3:eb60e36b03f6 | 99 | #define OFF 0 |
jzeeff | 3:eb60e36b03f6 | 100 | #define RED 1 |
jzeeff | 3:eb60e36b03f6 | 101 | #define GREEN 2 |
jzeeff | 3:eb60e36b03f6 | 102 | #define BLUE 3 |
jzeeff | 3:eb60e36b03f6 | 103 | #define WHITE 4 |
jzeeff | 3:eb60e36b03f6 | 104 | #define YELLOW 5 |
jzeeff | 3:eb60e36b03f6 | 105 | #define AQUA 6 |
jzeeff | 3:eb60e36b03f6 | 106 | #define PINK 7 |
jzeeff | 3:eb60e36b03f6 | 107 | |
jzeeff | 3:eb60e36b03f6 | 108 | #define ON 1 |
jzeeff | 3:eb60e36b03f6 | 109 | #define OFF 0 |
jzeeff | 1:b5abc8ddd567 | 110 | |
jzeeff | 4:3d661b485d59 | 111 | // pin assignments |
jzeeff | 3:eb60e36b03f6 | 112 | DigitalOut heater(PTD7); // Solid State Relay - PTD6&7 have high drive capability |
jzeeff | 3:eb60e36b03f6 | 113 | FastPWM pump(PTD4); // Solid State Relay - PTD4 can do PWM @ 10K hz |
jzeeff | 3:eb60e36b03f6 | 114 | DigitalOut led_green(LED_GREEN); |
jzeeff | 4:3d661b485d59 | 115 | I2C gI2c(PTE0, PTE1); // SDA, SCL - use pullups somewhere |
jzeeff | 3:eb60e36b03f6 | 116 | RtcDs1307 rtclock(gI2c); // DS1307 is a real time clock chip |
jzeeff | 3:eb60e36b03f6 | 117 | Serial pc(USBTX, USBRX); // Serial to pc connection |
jzeeff | 5:0393adfdd439 | 118 | Serial bluetooth(PTC4,PTC3); // Serial via wireless TX,RX |
jzeeff | 4:3d661b485d59 | 119 | TSISensor tsi; // used as a brew start button |
jzeeff | 4:3d661b485d59 | 120 | AnalogIn scale(PTC2); // A/D converter reads scale |
jzeeff | 4:3d661b485d59 | 121 | |
jzeeff | 3:eb60e36b03f6 | 122 | void brew(void); |
jzeeff | 3:eb60e36b03f6 | 123 | void led_color(int color); |
jzeeff | 4:3d661b485d59 | 124 | unsigned read_temp(int device); |
jzeeff | 5:0393adfdd439 | 125 | int read_scale(void); |
jzeeff | 5:0393adfdd439 | 126 | int read_scale2(void); |
jzeeff | 5:0393adfdd439 | 127 | int read_scale3(void); |
jzeeff | 4:3d661b485d59 | 128 | unsigned read_ad(AnalogIn adc); |
jzeeff | 3:eb60e36b03f6 | 129 | void steam(int seconds); |
jzeeff | 5:0393adfdd439 | 130 | inline int median(int a, int b, int c); |
jzeeff | 3:eb60e36b03f6 | 131 | |
jzeeff | 3:eb60e36b03f6 | 132 | unsigned ambient_temp; // room or water tank temp (startup) |
jzeeff | 3:eb60e36b03f6 | 133 | double heat = INITIAL_POWER; // initial fractional heat needed while idle |
jzeeff | 4:3d661b485d59 | 134 | unsigned boiler_log[BREW_TIME+BREW_PREHEAT]; // record boiler temp during brew |
jzeeff | 4:3d661b485d59 | 135 | unsigned group_log[BREW_TIME+BREW_PREHEAT]; // record basket temp during brew |
jzeeff | 4:3d661b485d59 | 136 | int scale_log[BREW_TIME+BREW_PREHEAT]; // record weight during brew |
jzeeff | 3:eb60e36b03f6 | 137 | |
jzeeff | 5:0393adfdd439 | 138 | uint16_t slog[5000]; |
jzeeff | 5:0393adfdd439 | 139 | int scount=0; |
jzeeff | 5:0393adfdd439 | 140 | |
jzeeff | 3:eb60e36b03f6 | 141 | int main() // start of program |
jzeeff | 0:24cdf76455c4 | 142 | { |
jzeeff | 0:24cdf76455c4 | 143 | time_t prev_time = 0; |
jzeeff | 4:3d661b485d59 | 144 | |
jzeeff | 4:3d661b485d59 | 145 | led_color(OFF); |
jzeeff | 5:0393adfdd439 | 146 | |
jzeeff | 6:56b205b46b42 | 147 | wait(1); // let settle |
jzeeff | 4:3d661b485d59 | 148 | ambient_temp = read_temp(BOILER); // save temp on startup |
jzeeff | 3:eb60e36b03f6 | 149 | |
jzeeff | 6:56b205b46b42 | 150 | DateTime dt = rtclock.now(); // check clock value |
jzeeff | 6:56b205b46b42 | 151 | if (dt.year() > 2090 || dt.year() < 2014) { |
jzeeff | 6:56b205b46b42 | 152 | DateTime compiled(__DATE__, __TIME__); // to set RT clock initially |
jzeeff | 6:56b205b46b42 | 153 | rtclock.adjust(compiled); |
jzeeff | 6:56b205b46b42 | 154 | DateTime dt = rtclock.now(); // check again |
jzeeff | 6:56b205b46b42 | 155 | } |
jzeeff | 6:56b205b46b42 | 156 | |
jzeeff | 4:3d661b485d59 | 157 | debug("RTC = %u/%u/%02u %2u:%02u:%02u\r\n" |
jzeeff | 4:3d661b485d59 | 158 | ,dt.month(),dt.day(),dt.year() |
jzeeff | 4:3d661b485d59 | 159 | ,dt.hour(),dt.minute(),dt.second()); |
jzeeff | 4:3d661b485d59 | 160 | set_time(0); // set active clock |
jzeeff | 5:0393adfdd439 | 161 | |
jzeeff | 4:3d661b485d59 | 162 | debug("starting A/D value/temp = %u %u\r\n",ambient_temp,read_temp(GROUP)); |
jzeeff | 4:3d661b485d59 | 163 | |
jzeeff | 6:56b205b46b42 | 164 | pump = 0; // initial duty cycle (pump off) |
jzeeff | 4:3d661b485d59 | 165 | pump.period_us(410); // period of PWM signal in us |
jzeeff | 4:3d661b485d59 | 166 | |
jzeeff | 6:56b205b46b42 | 167 | if (ambient_temp < MAX_ROOM_TEMP) // check for cold boiler |
jzeeff | 5:0393adfdd439 | 168 | steam(7 * 60); // do accelerated warmup by overheating for awhile |
jzeeff | 3:eb60e36b03f6 | 169 | |
jzeeff | 3:eb60e36b03f6 | 170 | // loop forever, controlling boiler temperature |
jzeeff | 0:24cdf76455c4 | 171 | |
jzeeff | 0:24cdf76455c4 | 172 | for (;;) { |
jzeeff | 0:24cdf76455c4 | 173 | // read temp from A/D |
jzeeff | 0:24cdf76455c4 | 174 | // note: in A/D counts, not degrees |
jzeeff | 4:3d661b485d59 | 175 | unsigned temp = read_temp(BOILER); |
jzeeff | 3:eb60e36b03f6 | 176 | |
jzeeff | 0:24cdf76455c4 | 177 | // bang/bang when far away, PWM to learned value when close |
jzeeff | 0:24cdf76455c4 | 178 | if (temp > TARGET_TEMP + CLOSE) { |
jzeeff | 3:eb60e36b03f6 | 179 | heater = OFF; // turn off heater |
jzeeff | 3:eb60e36b03f6 | 180 | led_color(GREEN); // set LED to green |
jzeeff | 4:3d661b485d59 | 181 | wait(.17); |
jzeeff | 0:24cdf76455c4 | 182 | } else if (temp < TARGET_TEMP - CLOSE) { |
jzeeff | 3:eb60e36b03f6 | 183 | heater = ON; // turn on heater |
jzeeff | 3:eb60e36b03f6 | 184 | led_color(RED); // set LED to red |
jzeeff | 4:3d661b485d59 | 185 | wait(.17); |
jzeeff | 0:24cdf76455c4 | 186 | } else { // close to target temp |
jzeeff | 0:24cdf76455c4 | 187 | // learning mode - adjust heat, the fraction of time power should be on |
jzeeff | 3:eb60e36b03f6 | 188 | |
jzeeff | 3:eb60e36b03f6 | 189 | if (temp > TARGET_TEMP) // adjust best guess for % heat needed |
jzeeff | 3:eb60e36b03f6 | 190 | heat *= (1-GAIN); |
jzeeff | 0:24cdf76455c4 | 191 | else |
jzeeff | 3:eb60e36b03f6 | 192 | heat *= (1+GAIN); |
jzeeff | 3:eb60e36b03f6 | 193 | |
jzeeff | 3:eb60e36b03f6 | 194 | heater = ON; // turn on heater for PWM |
jzeeff | 3:eb60e36b03f6 | 195 | led_color(RED); |
jzeeff | 3:eb60e36b03f6 | 196 | wait(heat * 2.7); // 1.7 to reduce interaction with 50/60Hz power |
jzeeff | 3:eb60e36b03f6 | 197 | heater = OFF; // turn off heater |
jzeeff | 3:eb60e36b03f6 | 198 | led_color(GREEN); |
jzeeff | 3:eb60e36b03f6 | 199 | wait((1-heat) * 2.7); // total time is 2.7 seconds |
jzeeff | 0:24cdf76455c4 | 200 | } // if |
jzeeff | 0:24cdf76455c4 | 201 | |
jzeeff | 0:24cdf76455c4 | 202 | // the user must press a button 10 seconds prior to brewing to start preheat |
jzeeff | 4:3d661b485d59 | 203 | if (tsi.readPercentage() > .5) { |
jzeeff | 0:24cdf76455c4 | 204 | brew(); |
jzeeff | 4:3d661b485d59 | 205 | set_time(0); // stay awake for awhile more |
jzeeff | 4:3d661b485d59 | 206 | } |
jzeeff | 4:3d661b485d59 | 207 | |
jzeeff | 3:eb60e36b03f6 | 208 | // if they signaled for steam |
jzeeff | 4:3d661b485d59 | 209 | //if (tsi.readPercentage() > .1 && tsi.readPercentage() < .5) |
jzeeff | 4:3d661b485d59 | 210 | // steam(120); |
jzeeff | 4:3d661b485d59 | 211 | |
jzeeff | 5:0393adfdd439 | 212 | char key = 0; |
jzeeff | 5:0393adfdd439 | 213 | if (pc.readable()) // Check if data is available on serial port. |
jzeeff | 5:0393adfdd439 | 214 | key = pc.getc(); |
jzeeff | 5:0393adfdd439 | 215 | |
jzeeff | 5:0393adfdd439 | 216 | if (key == 'l') { // debug, print out brew temp log |
jzeeff | 3:eb60e36b03f6 | 217 | int i; |
jzeeff | 4:3d661b485d59 | 218 | for (i = 0; i < BREW_TIME+BREW_PREHEAT; ++i) |
jzeeff | 4:3d661b485d59 | 219 | printf("log %d: %u %u %d\r\n",i,boiler_log[i],group_log[i],scale_log[i]); |
jzeeff | 5:0393adfdd439 | 220 | for (i = 0; i < scount; ++i) |
jzeeff | 5:0393adfdd439 | 221 | printf("%u\r\n",slog[i]); |
jzeeff | 3:eb60e36b03f6 | 222 | } // if |
jzeeff | 3:eb60e36b03f6 | 223 | |
jzeeff | 5:0393adfdd439 | 224 | if (key == 'p') { // cycle pump for flush |
jzeeff | 5:0393adfdd439 | 225 | pump = 1; |
jzeeff | 5:0393adfdd439 | 226 | wait(5); |
jzeeff | 5:0393adfdd439 | 227 | pump = 0; |
jzeeff | 5:0393adfdd439 | 228 | } |
jzeeff | 5:0393adfdd439 | 229 | |
jzeeff | 4:3d661b485d59 | 230 | // check for idle shutdown, sleep till tomorrow am if it occurs |
jzeeff | 5:0393adfdd439 | 231 | if (time(NULL) > SLEEP_PERIOD || key == 'i') { // save power |
jzeeff | 4:3d661b485d59 | 232 | heater = OFF; // turn off heater |
jzeeff | 3:eb60e36b03f6 | 233 | led_color(OFF); |
jzeeff | 3:eb60e36b03f6 | 234 | printf("sleep\r\n"); |
jzeeff | 5:0393adfdd439 | 235 | |
jzeeff | 4:3d661b485d59 | 236 | for (;;) { // loop till wakeup in the morning |
jzeeff | 4:3d661b485d59 | 237 | DateTime dt; |
jzeeff | 5:0393adfdd439 | 238 | |
jzeeff | 5:0393adfdd439 | 239 | if (pc.readable() && pc.getc() == ' ') // user wakeup |
jzeeff | 5:0393adfdd439 | 240 | break; |
jzeeff | 5:0393adfdd439 | 241 | |
jzeeff | 4:3d661b485d59 | 242 | dt = rtclock.now(); // read real time clock |
jzeeff | 4:3d661b485d59 | 243 | if (dt.hour() == WAKEUP_TIME && dt.minute() == 0) // GMT time to wake up |
jzeeff | 5:0393adfdd439 | 244 | break; |
jzeeff | 5:0393adfdd439 | 245 | |
jzeeff | 4:3d661b485d59 | 246 | wait(30); |
jzeeff | 4:3d661b485d59 | 247 | } // for |
jzeeff | 5:0393adfdd439 | 248 | |
jzeeff | 4:3d661b485d59 | 249 | set_time(0); // reset active timer |
jzeeff | 4:3d661b485d59 | 250 | debug("exit idle\r\n"); |
jzeeff | 4:3d661b485d59 | 251 | ambient_temp = read_temp(BOILER); // save temp on startup |
jzeeff | 4:3d661b485d59 | 252 | } // if |
jzeeff | 3:eb60e36b03f6 | 253 | |
jzeeff | 0:24cdf76455c4 | 254 | // check for errors (incorrect boiler temp can be dangerous) |
jzeeff | 4:3d661b485d59 | 255 | while (temp > MAX_TEMP || temp < MIN_TEMP) { |
jzeeff | 4:3d661b485d59 | 256 | heater = OFF; // turn off heater |
jzeeff | 4:3d661b485d59 | 257 | led_color(YELLOW); // set LED to indicate error |
jzeeff | 1:b5abc8ddd567 | 258 | debug("error A/D = %u\r\n",temp); |
jzeeff | 4:3d661b485d59 | 259 | wait(60); |
jzeeff | 4:3d661b485d59 | 260 | temp = read_temp(BOILER); |
jzeeff | 0:24cdf76455c4 | 261 | } |
jzeeff | 0:24cdf76455c4 | 262 | |
jzeeff | 5:0393adfdd439 | 263 | if (time(NULL) > prev_time) |
jzeeff | 5:0393adfdd439 | 264 | 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 | 265 | prev_time = time(NULL); |
jzeeff | 0:24cdf76455c4 | 266 | |
jzeeff | 1:b5abc8ddd567 | 267 | } // for (;;) |
jzeeff | 0:24cdf76455c4 | 268 | |
jzeeff | 0:24cdf76455c4 | 269 | } // main() |
jzeeff | 0:24cdf76455c4 | 270 | |
jzeeff | 0:24cdf76455c4 | 271 | |
jzeeff | 5:0393adfdd439 | 272 | // turn off the heater |
jzeeff | 5:0393adfdd439 | 273 | void heater_off(void) |
jzeeff | 5:0393adfdd439 | 274 | { |
jzeeff | 5:0393adfdd439 | 275 | heater = OFF; |
jzeeff | 5:0393adfdd439 | 276 | } |
jzeeff | 5:0393adfdd439 | 277 | |
jzeeff | 0:24cdf76455c4 | 278 | //================================================================= |
jzeeff | 5:0393adfdd439 | 279 | // This subroutine is called when the button is pressed, n seconds |
jzeeff | 4:3d661b485d59 | 280 | // before the pump is started. It does both open loop and closed |
jzeeff | 3:eb60e36b03f6 | 281 | // loop PWM power/heat control. |
jzeeff | 0:24cdf76455c4 | 282 | //================================================================= |
jzeeff | 0:24cdf76455c4 | 283 | |
jzeeff | 0:24cdf76455c4 | 284 | void brew(void) |
jzeeff | 0:24cdf76455c4 | 285 | { |
jzeeff | 1:b5abc8ddd567 | 286 | double adjust = 1; // default is no adjustment |
jzeeff | 3:eb60e36b03f6 | 287 | |
jzeeff | 2:22d9c714b511 | 288 | // adjust for higher or lower tank temp (assumed to be equal to ambient at startup) |
jzeeff | 5:0393adfdd439 | 289 | // add in "heat" value as a measure of ambient?? |
jzeeff | 3:eb60e36b03f6 | 290 | //if (ambient_temp < MAX_ROOM_TEMP) // sanity check |
jzeeff | 3:eb60e36b03f6 | 291 | // adjust = (double)(ROOM_TEMP - TARGET_TEMP) / (double)(ambient_temp - TARGET_TEMP); |
jzeeff | 5:0393adfdd439 | 292 | |
jzeeff | 3:eb60e36b03f6 | 293 | led_color(WHITE); |
jzeeff | 4:3d661b485d59 | 294 | |
jzeeff | 5:0393adfdd439 | 295 | unsigned brew_time; // seconds since start of brew |
jzeeff | 5:0393adfdd439 | 296 | unsigned flow_time = 0; // clock that runs once flow starts |
jzeeff | 5:0393adfdd439 | 297 | double target_pump = 1; // current value of pump power for desired flow |
jzeeff | 5:0393adfdd439 | 298 | double grams; |
jzeeff | 5:0393adfdd439 | 299 | double prev_grams = 0; |
jzeeff | 3:eb60e36b03f6 | 300 | |
jzeeff | 5:0393adfdd439 | 301 | wait(.5); // stabilize |
jzeeff | 5:0393adfdd439 | 302 | int scale_zero = read_scale2(); // weight of empty cup |
jzeeff | 5:0393adfdd439 | 303 | debug("preheat/brew start, adjust = %f, scale zero = %u counts\r\n", adjust,scale_zero); |
jzeeff | 5:0393adfdd439 | 304 | |
jzeeff | 5:0393adfdd439 | 305 | Timeout heater_timer; // used to schedule off time |
jzeeff | 5:0393adfdd439 | 306 | |
jzeeff | 5:0393adfdd439 | 307 | Timer timer; // used to keep syncronized, 1 update per second |
jzeeff | 5:0393adfdd439 | 308 | timer.start(); |
jzeeff | 5:0393adfdd439 | 309 | |
jzeeff | 5:0393adfdd439 | 310 | for (brew_time = 0; brew_time < BREW_PREHEAT + BREW_TIME;) { // loop until end of brew |
jzeeff | 5:0393adfdd439 | 311 | |
jzeeff | 4:3d661b485d59 | 312 | if (brew_time == BREW_PREHEAT) { |
jzeeff | 3:eb60e36b03f6 | 313 | led_color(BLUE); // set LED color to blue for start brew/pump now |
jzeeff | 4:3d661b485d59 | 314 | } |
jzeeff | 3:eb60e36b03f6 | 315 | |
jzeeff | 5:0393adfdd439 | 316 | // *** heat control |
jzeeff | 5:0393adfdd439 | 317 | unsigned temp = read_temp(BOILER); // minimal time needed for this |
jzeeff | 2:22d9c714b511 | 318 | // if too cold, apply the PWM value, if too hot, do nothing |
jzeeff | 5:0393adfdd439 | 319 | if (temp < TARGET_TEMP + (table[brew_time] * AD_PER_DEGREE)) { |
jzeeff | 5:0393adfdd439 | 320 | double pwm = table[brew_time] - (int)table[brew_time]; // decimal part of brew heat |
jzeeff | 5:0393adfdd439 | 321 | // adjust? |
jzeeff | 5:0393adfdd439 | 322 | if (pwm > 0.0 && pwm <= 1.0) { |
jzeeff | 4:3d661b485d59 | 323 | heater = ON; |
jzeeff | 5:0393adfdd439 | 324 | heater_timer.attach(&heater_off, pwm); // schedule turn off |
jzeeff | 5:0393adfdd439 | 325 | } else |
jzeeff | 4:3d661b485d59 | 326 | heater = OFF; |
jzeeff | 5:0393adfdd439 | 327 | } // if |
jzeeff | 5:0393adfdd439 | 328 | |
jzeeff | 5:0393adfdd439 | 329 | // *** pump power control |
jzeeff | 5:0393adfdd439 | 330 | |
jzeeff | 5:0393adfdd439 | 331 | #define MIN_PUMP .4 // below this is effectively zero |
jzeeff | 5:0393adfdd439 | 332 | grams = ((double)read_scale2() - scale_zero) / AD_PER_GRAM; // current espresso weight |
jzeeff | 5:0393adfdd439 | 333 | if (grams < 0) // clip impossible result |
jzeeff | 5:0393adfdd439 | 334 | grams = 0; |
jzeeff | 5:0393adfdd439 | 335 | double delta_grams = grams - prev_grams; |
jzeeff | 5:0393adfdd439 | 336 | if (delta_grams < 0) |
jzeeff | 5:0393adfdd439 | 337 | delta_grams = 0; |
jzeeff | 5:0393adfdd439 | 338 | prev_grams = grams; |
jzeeff | 5:0393adfdd439 | 339 | |
jzeeff | 5:0393adfdd439 | 340 | if (brew_time >= START_FLOW_PROF) { // start flow profiling at specified time |
jzeeff | 5:0393adfdd439 | 341 | ++flow_time; // seconds of significant flow |
jzeeff | 5:0393adfdd439 | 342 | // adjust flow rate by changing pump power |
jzeeff | 5:0393adfdd439 | 343 | // Proportional control |
jzeeff | 5:0393adfdd439 | 344 | #define UP_GAIN .25 |
jzeeff | 5:0393adfdd439 | 345 | #define DOWN_GAIN 1.2 |
jzeeff | 5:0393adfdd439 | 346 | |
jzeeff | 5:0393adfdd439 | 347 | double error = (delta_grams - scale_table[flow_time]) / scale_table[flow_time]; |
jzeeff | 5:0393adfdd439 | 348 | |
jzeeff | 5:0393adfdd439 | 349 | if (error > 0) |
jzeeff | 5:0393adfdd439 | 350 | target_pump /= 1 + (error * DOWN_GAIN); // too fast |
jzeeff | 5:0393adfdd439 | 351 | else |
jzeeff | 5:0393adfdd439 | 352 | target_pump += -error * UP_GAIN; // too slow |
jzeeff | 5:0393adfdd439 | 353 | |
jzeeff | 5:0393adfdd439 | 354 | if (target_pump > pump_table[brew_time]) // clip to max allowed |
jzeeff | 5:0393adfdd439 | 355 | target_pump = pump_table[brew_time]; |
jzeeff | 5:0393adfdd439 | 356 | else if (target_pump < 0) // clip to min |
jzeeff | 5:0393adfdd439 | 357 | target_pump = 0; |
jzeeff | 4:3d661b485d59 | 358 | } else |
jzeeff | 5:0393adfdd439 | 359 | target_pump = pump_table[brew_time]; // use pump power profiling |
jzeeff | 5:0393adfdd439 | 360 | |
jzeeff | 5:0393adfdd439 | 361 | pump = MIN_PUMP + (1 - MIN_PUMP) * target_pump; // use the flow profiling value |
jzeeff | 3:eb60e36b03f6 | 362 | |
jzeeff | 5:0393adfdd439 | 363 | debug("time = %u %u, grams = %F, delta = %F, target_pump = %F\r\n",brew_time,flow_time,grams,delta_grams,target_pump); |
jzeeff | 5:0393adfdd439 | 364 | //debug("target temp %u = %f, temp = %u %u\r\n",brew_time,table[brew_time],read_temp(BOILER),read_temp(GROUP)); |
jzeeff | 5:0393adfdd439 | 365 | |
jzeeff | 5:0393adfdd439 | 366 | // record values for debugging and graphing |
jzeeff | 4:3d661b485d59 | 367 | group_log[brew_time] = read_temp(GROUP); // record group temp |
jzeeff | 4:3d661b485d59 | 368 | boiler_log[brew_time] = temp; // record boiler temp |
jzeeff | 5:0393adfdd439 | 369 | scale_log[brew_time] = grams; // record espresso weight |
jzeeff | 5:0393adfdd439 | 370 | |
jzeeff | 4:3d661b485d59 | 371 | // early exit if final weight reached |
jzeeff | 5:0393adfdd439 | 372 | if (grams >= END_GRAMS) |
jzeeff | 5:0393adfdd439 | 373 | break; |
jzeeff | 5:0393adfdd439 | 374 | |
jzeeff | 5:0393adfdd439 | 375 | // early exit based on user input (read twice for noise) |
jzeeff | 5:0393adfdd439 | 376 | //if (brew_time > 10 && tsi.readPercentage() > .1 && tsi.readPercentage() < .5) { |
jzeeff | 5:0393adfdd439 | 377 | // wait_ms(5); |
jzeeff | 5:0393adfdd439 | 378 | // if (tsi.readPercentage() > .1 && tsi.readPercentage() < .5) |
jzeeff | 5:0393adfdd439 | 379 | // break; |
jzeeff | 5:0393adfdd439 | 380 | //} |
jzeeff | 5:0393adfdd439 | 381 | |
jzeeff | 5:0393adfdd439 | 382 | // wait till next second |
jzeeff | 5:0393adfdd439 | 383 | ++brew_time; |
jzeeff | 5:0393adfdd439 | 384 | int ms = (brew_time * 1000) - timer.read_ms(); |
jzeeff | 5:0393adfdd439 | 385 | if (ms > 0) |
jzeeff | 5:0393adfdd439 | 386 | wait_ms(ms); |
jzeeff | 5:0393adfdd439 | 387 | |
jzeeff | 5:0393adfdd439 | 388 | // cleanup |
jzeeff | 5:0393adfdd439 | 389 | heater = OFF; // should be off already, but just in case |
jzeeff | 5:0393adfdd439 | 390 | heater_timer.detach(); // disable off timer |
jzeeff | 0:24cdf76455c4 | 391 | |
jzeeff | 1:b5abc8ddd567 | 392 | } // for |
jzeeff | 3:eb60e36b03f6 | 393 | |
jzeeff | 4:3d661b485d59 | 394 | // shut down |
jzeeff | 4:3d661b485d59 | 395 | led_color(OFF); |
jzeeff | 4:3d661b485d59 | 396 | pump = OFF; |
jzeeff | 4:3d661b485d59 | 397 | heater = OFF; |
jzeeff | 5:0393adfdd439 | 398 | debug("brew done, time = %u, grams = %f, target_pump = %F\r\n",brew_time, grams, target_pump); |
jzeeff | 5:0393adfdd439 | 399 | |
jzeeff | 3:eb60e36b03f6 | 400 | } // brew() |
jzeeff | 0:24cdf76455c4 | 401 | |
jzeeff | 3:eb60e36b03f6 | 402 | //=========================================================== |
jzeeff | 3:eb60e36b03f6 | 403 | // control to a higher steam temperature for n seconds |
jzeeff | 3:eb60e36b03f6 | 404 | //=========================================================== |
jzeeff | 3:eb60e36b03f6 | 405 | |
jzeeff | 3:eb60e36b03f6 | 406 | void steam(int seconds) |
jzeeff | 3:eb60e36b03f6 | 407 | { |
jzeeff | 3:eb60e36b03f6 | 408 | unsigned start_time = time(NULL); |
jzeeff | 3:eb60e36b03f6 | 409 | |
jzeeff | 3:eb60e36b03f6 | 410 | debug("steam start, time = %d\r\n", seconds); |
jzeeff | 3:eb60e36b03f6 | 411 | |
jzeeff | 3:eb60e36b03f6 | 412 | while (time(NULL) - start_time < seconds) { |
jzeeff | 4:3d661b485d59 | 413 | if (read_temp(BOILER) > STEAM_TEMP) { |
jzeeff | 4:3d661b485d59 | 414 | heater = OFF; // turn off heater |
jzeeff | 3:eb60e36b03f6 | 415 | led_color(AQUA); // set LED to aqua |
jzeeff | 3:eb60e36b03f6 | 416 | } else { |
jzeeff | 4:3d661b485d59 | 417 | heater = ON; // turn on heater |
jzeeff | 3:eb60e36b03f6 | 418 | led_color(PINK); // set LED to pink |
jzeeff | 3:eb60e36b03f6 | 419 | } |
jzeeff | 5:0393adfdd439 | 420 | |
jzeeff | 4:3d661b485d59 | 421 | if (tsi.readPercentage() > .5) // abort steam |
jzeeff | 5:0393adfdd439 | 422 | break; |
jzeeff | 5:0393adfdd439 | 423 | |
jzeeff | 3:eb60e36b03f6 | 424 | } // while |
jzeeff | 3:eb60e36b03f6 | 425 | |
jzeeff | 3:eb60e36b03f6 | 426 | heater = OFF; // turn off |
jzeeff | 3:eb60e36b03f6 | 427 | |
jzeeff | 3:eb60e36b03f6 | 428 | } // steam() |
jzeeff | 0:24cdf76455c4 | 429 | |
jzeeff | 0:24cdf76455c4 | 430 | |
jzeeff | 0:24cdf76455c4 | 431 | // ============================================= |
jzeeff | 0:24cdf76455c4 | 432 | // set multi color LED state |
jzeeff | 0:24cdf76455c4 | 433 | // ============================================= |
jzeeff | 0:24cdf76455c4 | 434 | |
jzeeff | 0:24cdf76455c4 | 435 | DigitalOut r (LED_RED); |
jzeeff | 0:24cdf76455c4 | 436 | DigitalOut g (LED_GREEN); |
jzeeff | 0:24cdf76455c4 | 437 | DigitalOut b (LED_BLUE); |
jzeeff | 0:24cdf76455c4 | 438 | |
jzeeff | 3:eb60e36b03f6 | 439 | void led_color(int color) |
jzeeff | 0:24cdf76455c4 | 440 | { |
jzeeff | 0:24cdf76455c4 | 441 | // turn off |
jzeeff | 3:eb60e36b03f6 | 442 | r = g = b = 1; |
jzeeff | 0:24cdf76455c4 | 443 | |
jzeeff | 3:eb60e36b03f6 | 444 | switch (color) { |
jzeeff | 3:eb60e36b03f6 | 445 | case OFF: |
jzeeff | 3:eb60e36b03f6 | 446 | break; |
jzeeff | 3:eb60e36b03f6 | 447 | case GREEN: |
jzeeff | 3:eb60e36b03f6 | 448 | g = 0; |
jzeeff | 3:eb60e36b03f6 | 449 | break; |
jzeeff | 3:eb60e36b03f6 | 450 | case BLUE: |
jzeeff | 3:eb60e36b03f6 | 451 | b = 0; |
jzeeff | 3:eb60e36b03f6 | 452 | break; |
jzeeff | 3:eb60e36b03f6 | 453 | case RED: |
jzeeff | 3:eb60e36b03f6 | 454 | r = 0; |
jzeeff | 3:eb60e36b03f6 | 455 | break; |
jzeeff | 3:eb60e36b03f6 | 456 | case YELLOW: |
jzeeff | 3:eb60e36b03f6 | 457 | r = g = 0; |
jzeeff | 3:eb60e36b03f6 | 458 | break; |
jzeeff | 3:eb60e36b03f6 | 459 | case AQUA: |
jzeeff | 3:eb60e36b03f6 | 460 | b = g = 0; |
jzeeff | 3:eb60e36b03f6 | 461 | break; |
jzeeff | 3:eb60e36b03f6 | 462 | case PINK: |
jzeeff | 3:eb60e36b03f6 | 463 | r = b = 0; |
jzeeff | 3:eb60e36b03f6 | 464 | break; |
jzeeff | 3:eb60e36b03f6 | 465 | case WHITE: |
jzeeff | 3:eb60e36b03f6 | 466 | r = g = b = 0; |
jzeeff | 3:eb60e36b03f6 | 467 | break; |
jzeeff | 3:eb60e36b03f6 | 468 | } // switch |
jzeeff | 3:eb60e36b03f6 | 469 | |
jzeeff | 3:eb60e36b03f6 | 470 | } // led_color() |
jzeeff | 0:24cdf76455c4 | 471 | |
jzeeff | 4:3d661b485d59 | 472 | #if 1 |
jzeeff | 4:3d661b485d59 | 473 | // reduce noise by making unused A/D into digital outs |
jzeeff | 4:3d661b485d59 | 474 | DigitalOut x1(PTB0); |
jzeeff | 4:3d661b485d59 | 475 | DigitalOut x2(PTB1); |
jzeeff | 4:3d661b485d59 | 476 | DigitalOut x3(PTB2); |
jzeeff | 4:3d661b485d59 | 477 | DigitalOut x4(PTB3); |
jzeeff | 4:3d661b485d59 | 478 | DigitalOut x5(PTE21); |
jzeeff | 4:3d661b485d59 | 479 | DigitalOut x6(PTE23); |
jzeeff | 4:3d661b485d59 | 480 | DigitalOut x7(PTD5); |
jzeeff | 4:3d661b485d59 | 481 | DigitalOut x8(PTD6); |
jzeeff | 4:3d661b485d59 | 482 | DigitalOut x9(PTD1); |
jzeeff | 5:0393adfdd439 | 483 | //DigitalOut x11(PTC3); |
jzeeff | 4:3d661b485d59 | 484 | #endif |
jzeeff | 4:3d661b485d59 | 485 | |
jzeeff | 0:24cdf76455c4 | 486 | //======================================= |
jzeeff | 4:3d661b485d59 | 487 | // A/D routines |
jzeeff | 0:24cdf76455c4 | 488 | //======================================= |
jzeeff | 0:24cdf76455c4 | 489 | |
jzeeff | 5:0393adfdd439 | 490 | DigitalOut ad_power(PTB9); // used to turn on/off power to resistors (self heating) |
jzeeff | 5:0393adfdd439 | 491 | AnalogIn boiler(PTE20); // A/D converter reads temperature on boiler |
jzeeff | 5:0393adfdd439 | 492 | AnalogIn group(PTE22); // A/D for group basket temp |
jzeeff | 5:0393adfdd439 | 493 | AnalogIn vref(PTE29); // A/D for A/D power supply (ad_power) |
jzeeff | 4:3d661b485d59 | 494 | |
jzeeff | 5:0393adfdd439 | 495 | inline int median(int a, int b, int c) |
jzeeff | 5:0393adfdd439 | 496 | { |
jzeeff | 5:0393adfdd439 | 497 | if ((a >= b && a <= c) || (a >= c && a <= b)) return a; |
jzeeff | 5:0393adfdd439 | 498 | else if ((b >= a && b <= c) || (b >= c && b <= a)) return b; |
jzeeff | 5:0393adfdd439 | 499 | else return c; |
jzeeff | 5:0393adfdd439 | 500 | } // median() |
jzeeff | 4:3d661b485d59 | 501 | |
jzeeff | 4:3d661b485d59 | 502 | |
jzeeff | 5:0393adfdd439 | 503 | // heavily averaged A/D reading |
jzeeff | 5:0393adfdd439 | 504 | |
jzeeff | 4:3d661b485d59 | 505 | unsigned read_ad(AnalogIn adc) |
jzeeff | 0:24cdf76455c4 | 506 | { |
jzeeff | 3:eb60e36b03f6 | 507 | uint32_t sum=0; |
jzeeff | 5:0393adfdd439 | 508 | |
jzeeff | 5:0393adfdd439 | 509 | #define COUNT 77 // number of samples to average |
jzeeff | 1:b5abc8ddd567 | 510 | |
jzeeff | 5:0393adfdd439 | 511 | for (int i = 0; i < COUNT; ++i) // average multiple for more accuracy |
jzeeff | 5:0393adfdd439 | 512 | sum += median(adc.read_u16(),adc.read_u16(),adc.read_u16()); |
jzeeff | 1:b5abc8ddd567 | 513 | |
jzeeff | 4:3d661b485d59 | 514 | return sum / COUNT; |
jzeeff | 5:0393adfdd439 | 515 | |
jzeeff | 5:0393adfdd439 | 516 | } // read_ad() |
jzeeff | 4:3d661b485d59 | 517 | |
jzeeff | 4:3d661b485d59 | 518 | |
jzeeff | 4:3d661b485d59 | 519 | // read a temperature in A/D counts |
jzeeff | 4:3d661b485d59 | 520 | // adjust it for vref variations |
jzeeff | 4:3d661b485d59 | 521 | |
jzeeff | 4:3d661b485d59 | 522 | unsigned read_temp(int device) |
jzeeff | 4:3d661b485d59 | 523 | { |
jzeeff | 5:0393adfdd439 | 524 | unsigned value; |
jzeeff | 5:0393adfdd439 | 525 | unsigned max; // A/D reading for the vref supply voltage |
jzeeff | 3:eb60e36b03f6 | 526 | |
jzeeff | 5:0393adfdd439 | 527 | // send power to analog resistors only when needed |
jzeeff | 5:0393adfdd439 | 528 | // this limits self heating |
jzeeff | 5:0393adfdd439 | 529 | |
jzeeff | 4:3d661b485d59 | 530 | ad_power = 1; // turn on supply voltage |
jzeeff | 4:3d661b485d59 | 531 | max = read_ad(vref); // read supply voltage |
jzeeff | 5:0393adfdd439 | 532 | |
jzeeff | 5:0393adfdd439 | 533 | if (device == BOILER) |
jzeeff | 5:0393adfdd439 | 534 | value = (read_ad(boiler) * 65536) / max; // scale to vref |
jzeeff | 4:3d661b485d59 | 535 | else |
jzeeff | 5:0393adfdd439 | 536 | value = (read_ad(group) * 65536) / max; // scale to vref |
jzeeff | 5:0393adfdd439 | 537 | |
jzeeff | 4:3d661b485d59 | 538 | ad_power = 0; |
jzeeff | 4:3d661b485d59 | 539 | |
jzeeff | 4:3d661b485d59 | 540 | return value; |
jzeeff | 5:0393adfdd439 | 541 | } // read_temp() |
jzeeff | 3:eb60e36b03f6 | 542 | |
jzeeff | 3:eb60e36b03f6 | 543 | |
jzeeff | 5:0393adfdd439 | 544 | // scale |
jzeeff | 5:0393adfdd439 | 545 | #define FILTER .99 |
jzeeff | 5:0393adfdd439 | 546 | #define SLEW 10 |
jzeeff | 5:0393adfdd439 | 547 | |
jzeeff | 5:0393adfdd439 | 548 | // average scale value over 800 msec |
jzeeff | 5:0393adfdd439 | 549 | // approx 6.6 samples/msec |
jzeeff | 5:0393adfdd439 | 550 | |
jzeeff | 5:0393adfdd439 | 551 | int read_scale2() |
jzeeff | 5:0393adfdd439 | 552 | { |
jzeeff | 5:0393adfdd439 | 553 | int sum=0, count=0; |
jzeeff | 5:0393adfdd439 | 554 | int raw, prev_raw; |
jzeeff | 5:0393adfdd439 | 555 | |
jzeeff | 5:0393adfdd439 | 556 | Timer t; |
jzeeff | 5:0393adfdd439 | 557 | t.start(); |
jzeeff | 5:0393adfdd439 | 558 | prev_raw = scale.read_u16(); |
jzeeff | 5:0393adfdd439 | 559 | |
jzeeff | 5:0393adfdd439 | 560 | scount = 0; |
jzeeff | 5:0393adfdd439 | 561 | |
jzeeff | 5:0393adfdd439 | 562 | for (count = 0; t.read_ms() < 800; ++count) { |
jzeeff | 5:0393adfdd439 | 563 | raw = scale.read_u16(); |
jzeeff | 5:0393adfdd439 | 564 | |
jzeeff | 5:0393adfdd439 | 565 | slog[scount] = raw; // log it |
jzeeff | 5:0393adfdd439 | 566 | if (++scount >= 5000) |
jzeeff | 5:0393adfdd439 | 567 | scount = 5000; |
jzeeff | 5:0393adfdd439 | 568 | |
jzeeff | 5:0393adfdd439 | 569 | // clip to slew rate limits |
jzeeff | 5:0393adfdd439 | 570 | if (raw > prev_raw + SLEW) |
jzeeff | 5:0393adfdd439 | 571 | raw = prev_raw + SLEW; |
jzeeff | 5:0393adfdd439 | 572 | else if (raw < prev_raw - SLEW) |
jzeeff | 5:0393adfdd439 | 573 | raw = prev_raw - SLEW; |
jzeeff | 5:0393adfdd439 | 574 | |
jzeeff | 5:0393adfdd439 | 575 | prev_raw = raw; |
jzeeff | 5:0393adfdd439 | 576 | |
jzeeff | 5:0393adfdd439 | 577 | sum += raw; |
jzeeff | 5:0393adfdd439 | 578 | } // for |
jzeeff | 5:0393adfdd439 | 579 | |
jzeeff | 5:0393adfdd439 | 580 | return sum / count; |
jzeeff | 5:0393adfdd439 | 581 | } |
jzeeff | 5:0393adfdd439 | 582 | |
jzeeff | 5:0393adfdd439 | 583 | // take average of scale A/D max and min over multiple inflection points |
jzeeff | 5:0393adfdd439 | 584 | // this reduces oscillation noise |
jzeeff | 5:0393adfdd439 | 585 | |
jzeeff | 5:0393adfdd439 | 586 | int read_scale() |
jzeeff | 5:0393adfdd439 | 587 | { |
jzeeff | 5:0393adfdd439 | 588 | int value, prev_value=0, prev_prev_value, max1, max2, min; |
jzeeff | 5:0393adfdd439 | 589 | unsigned tmp; |
jzeeff | 5:0393adfdd439 | 590 | scount = 0; |
jzeeff | 5:0393adfdd439 | 591 | |
jzeeff | 5:0393adfdd439 | 592 | Timer t; |
jzeeff | 5:0393adfdd439 | 593 | Timer total; |
jzeeff | 5:0393adfdd439 | 594 | t.start(); |
jzeeff | 5:0393adfdd439 | 595 | total.start(); |
jzeeff | 5:0393adfdd439 | 596 | |
jzeeff | 5:0393adfdd439 | 597 | // note: the effectiveness of this HF noise filter is highly dependent on sample rate |
jzeeff | 5:0393adfdd439 | 598 | |
jzeeff | 5:0393adfdd439 | 599 | #define update_value() {\ |
jzeeff | 5:0393adfdd439 | 600 | tmp = scale.read_u16(); \ |
jzeeff | 5:0393adfdd439 | 601 | slog[scount++] = tmp; \ |
jzeeff | 5:0393adfdd439 | 602 | value = FILTER * value + (1.0-FILTER) * tmp; \ |
jzeeff | 5:0393adfdd439 | 603 | prev_prev_value = prev_value; \ |
jzeeff | 5:0393adfdd439 | 604 | prev_value = value; } |
jzeeff | 5:0393adfdd439 | 605 | |
jzeeff | 5:0393adfdd439 | 606 | // get a good filtered value |
jzeeff | 5:0393adfdd439 | 607 | value = scale.read_u16(); |
jzeeff | 5:0393adfdd439 | 608 | for (int i = 0; i < 50; ++i) |
jzeeff | 5:0393adfdd439 | 609 | update_value(); |
jzeeff | 5:0393adfdd439 | 610 | |
jzeeff | 5:0393adfdd439 | 611 | // wait for upward slope |
jzeeff | 5:0393adfdd439 | 612 | while (value < prev_value || prev_value < prev_prev_value) |
jzeeff | 5:0393adfdd439 | 613 | update_value(); |
jzeeff | 5:0393adfdd439 | 614 | |
jzeeff | 5:0393adfdd439 | 615 | // delay for 2 msec |
jzeeff | 5:0393adfdd439 | 616 | for (t.reset(); t.read_ms()< 2;) |
jzeeff | 5:0393adfdd439 | 617 | update_value(); |
jzeeff | 5:0393adfdd439 | 618 | |
jzeeff | 5:0393adfdd439 | 619 | // find local max |
jzeeff | 5:0393adfdd439 | 620 | for (;;) { |
jzeeff | 5:0393adfdd439 | 621 | value = FILTER * value + (1.0-FILTER) * scale.read_u16(); // IIR filter |
jzeeff | 5:0393adfdd439 | 622 | if (prev_value > value && prev_value > prev_prev_value) { |
jzeeff | 5:0393adfdd439 | 623 | max1 = prev_value; |
jzeeff | 5:0393adfdd439 | 624 | break; |
jzeeff | 5:0393adfdd439 | 625 | } |
jzeeff | 5:0393adfdd439 | 626 | prev_prev_value = prev_value; |
jzeeff | 5:0393adfdd439 | 627 | prev_value = value; |
jzeeff | 5:0393adfdd439 | 628 | } // for |
jzeeff | 5:0393adfdd439 | 629 | |
jzeeff | 5:0393adfdd439 | 630 | // delay for 2 msec |
jzeeff | 5:0393adfdd439 | 631 | for (t.reset(); t.read_ms()< 2;) |
jzeeff | 5:0393adfdd439 | 632 | update_value(); |
jzeeff | 5:0393adfdd439 | 633 | |
jzeeff | 5:0393adfdd439 | 634 | // find local min |
jzeeff | 5:0393adfdd439 | 635 | for (;;) { |
jzeeff | 5:0393adfdd439 | 636 | value = FILTER * value + (1.0-FILTER) * scale.read_u16(); // IIR filter |
jzeeff | 5:0393adfdd439 | 637 | if (prev_value < value && prev_value < prev_prev_value) { |
jzeeff | 5:0393adfdd439 | 638 | min = prev_value; |
jzeeff | 5:0393adfdd439 | 639 | break; |
jzeeff | 5:0393adfdd439 | 640 | } |
jzeeff | 5:0393adfdd439 | 641 | prev_prev_value = prev_value; |
jzeeff | 5:0393adfdd439 | 642 | prev_value = value; |
jzeeff | 5:0393adfdd439 | 643 | } // for |
jzeeff | 5:0393adfdd439 | 644 | |
jzeeff | 5:0393adfdd439 | 645 | // delay for 2 msec |
jzeeff | 5:0393adfdd439 | 646 | for (t.reset(); t.read_ms()< 2;) |
jzeeff | 5:0393adfdd439 | 647 | update_value(); |
jzeeff | 5:0393adfdd439 | 648 | |
jzeeff | 5:0393adfdd439 | 649 | // find another max |
jzeeff | 5:0393adfdd439 | 650 | for (;;) { |
jzeeff | 5:0393adfdd439 | 651 | value = FILTER * value + (1.0-FILTER) * scale.read_u16(); // IIR filter |
jzeeff | 5:0393adfdd439 | 652 | if (prev_value > value && prev_value > prev_prev_value) { |
jzeeff | 5:0393adfdd439 | 653 | max2 = prev_value; |
jzeeff | 5:0393adfdd439 | 654 | break; |
jzeeff | 5:0393adfdd439 | 655 | } |
jzeeff | 5:0393adfdd439 | 656 | prev_prev_value = prev_value; |
jzeeff | 5:0393adfdd439 | 657 | prev_value = value; |
jzeeff | 5:0393adfdd439 | 658 | } // for |
jzeeff | 5:0393adfdd439 | 659 | |
jzeeff | 5:0393adfdd439 | 660 | //debug("read scale in %d msec, %d %d %d\r\n",total.read_ms(),max1, max2, min); |
jzeeff | 5:0393adfdd439 | 661 | |
jzeeff | 5:0393adfdd439 | 662 | return (((max1 + max2) / 2) + min) / 2; |
jzeeff | 5:0393adfdd439 | 663 | } // read_scale() |
jzeeff | 5:0393adfdd439 | 664 | |
jzeeff | 5:0393adfdd439 | 665 | int read_scale3() |
jzeeff | 5:0393adfdd439 | 666 | { |
jzeeff | 5:0393adfdd439 | 667 | int max=0, min=65535; |
jzeeff | 5:0393adfdd439 | 668 | int value, prev_raw; |
jzeeff | 5:0393adfdd439 | 669 | |
jzeeff | 5:0393adfdd439 | 670 | scount = 0; |
jzeeff | 5:0393adfdd439 | 671 | |
jzeeff | 5:0393adfdd439 | 672 | // note: the effectiveness of this HF noise filter is highly dependent on sample rate |
jzeeff | 5:0393adfdd439 | 673 | // about 6.6 samples/msec |
jzeeff | 5:0393adfdd439 | 674 | |
jzeeff | 5:0393adfdd439 | 675 | Timer t; |
jzeeff | 5:0393adfdd439 | 676 | t.start(); |
jzeeff | 5:0393adfdd439 | 677 | |
jzeeff | 5:0393adfdd439 | 678 | prev_raw = value = scale.read_u16(); |
jzeeff | 5:0393adfdd439 | 679 | |
jzeeff | 5:0393adfdd439 | 680 | while (t.read_ms() < 40) { // 25 msec should always include a full oscillation |
jzeeff | 5:0393adfdd439 | 681 | int raw = scale.read_u16(); |
jzeeff | 5:0393adfdd439 | 682 | slog[scount++] = raw; |
jzeeff | 5:0393adfdd439 | 683 | |
jzeeff | 5:0393adfdd439 | 684 | // clip to slew rate limits |
jzeeff | 5:0393adfdd439 | 685 | if (raw > prev_raw + SLEW) |
jzeeff | 5:0393adfdd439 | 686 | raw = prev_raw + SLEW; |
jzeeff | 5:0393adfdd439 | 687 | else if (raw < prev_raw - SLEW) |
jzeeff | 5:0393adfdd439 | 688 | raw = prev_raw - SLEW; |
jzeeff | 5:0393adfdd439 | 689 | |
jzeeff | 5:0393adfdd439 | 690 | prev_raw = raw; |
jzeeff | 5:0393adfdd439 | 691 | |
jzeeff | 5:0393adfdd439 | 692 | value = FILTER * value + (1.0-FILTER) * raw; |
jzeeff | 5:0393adfdd439 | 693 | |
jzeeff | 5:0393adfdd439 | 694 | if (scount > 50) { // only start after enough data |
jzeeff | 5:0393adfdd439 | 695 | if (value > max) |
jzeeff | 5:0393adfdd439 | 696 | max = value; |
jzeeff | 5:0393adfdd439 | 697 | if (value < min) |
jzeeff | 5:0393adfdd439 | 698 | min = value; |
jzeeff | 5:0393adfdd439 | 699 | } // if |
jzeeff | 5:0393adfdd439 | 700 | } // while |
jzeeff | 5:0393adfdd439 | 701 | |
jzeeff | 5:0393adfdd439 | 702 | //debug("%d values, %d %d\r\n",scount,max,min); |
jzeeff | 5:0393adfdd439 | 703 | return (max + min) / 2; |
jzeeff | 5:0393adfdd439 | 704 | |
jzeeff | 5:0393adfdd439 | 705 | } // read_scale3() |
jzeeff | 3:eb60e36b03f6 | 706 | |
jzeeff | 3:eb60e36b03f6 | 707 | |
jzeeff | 5:0393adfdd439 | 708 | // flow meter sends a pulse every .5 ml of flow |
jzeeff | 4:3d661b485d59 | 709 | |
jzeeff | 5:0393adfdd439 | 710 | InterruptIn flow_meter(PTA4); // digital input pin |
jzeeff | 5:0393adfdd439 | 711 | Timer flow_timer; |
jzeeff | 5:0393adfdd439 | 712 | int flow_period; // time between pulses in usec |
jzeeff | 5:0393adfdd439 | 713 | |
jzeeff | 5:0393adfdd439 | 714 | void flow_pulse() // interrupt routine |
jzeeff | 5:0393adfdd439 | 715 | { |
jzeeff | 5:0393adfdd439 | 716 | static int prev_time = 0; |
jzeeff | 5:0393adfdd439 | 717 | int pulse_time = flow_timer.read_us(); |
jzeeff | 5:0393adfdd439 | 718 | |
jzeeff | 5:0393adfdd439 | 719 | flow_period = pulse_time - prev_time; |
jzeeff | 5:0393adfdd439 | 720 | prev_time = pulse_time; |
jzeeff | 5:0393adfdd439 | 721 | } |
jzeeff | 5:0393adfdd439 | 722 | |
jzeeff | 5:0393adfdd439 | 723 | void flow_setup() |
jzeeff | 5:0393adfdd439 | 724 | { |
jzeeff | 5:0393adfdd439 | 725 | flow_timer.start(); |
jzeeff | 5:0393adfdd439 | 726 | flow_meter.rise(&flow_pulse); // attach the address of the flip function to the rising edge |
jzeeff | 5:0393adfdd439 | 727 | } // flow_setup() |