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
Revision 1:b5abc8ddd567, committed 2013-08-09
- Comitter:
- jzeeff
- Date:
- Fri Aug 09 20:36:28 2013 +0000
- Parent:
- 0:24cdf76455c4
- Child:
- 2:22d9c714b511
- Commit message:
- various improvements
Changed in this revision
main.cpp | Show annotated file Show diff for this revision Revisions of this file |
--- a/main.cpp Wed Aug 07 16:10:11 2013 +0000 +++ b/main.cpp Fri Aug 09 20:36:28 2013 +0000 @@ -1,5 +1,6 @@ // Program to control espresso maker boiler temperatures +// Similar to PID, but uses a flexible open loop table during brew // Used with a Gaggia Classic, FreeScale FRDM-KL25Z computer, PT1000 RTD, SSR // Jon Zeeff, 2013 // Public Domain @@ -7,8 +8,8 @@ #include "mbed.h" #include "TSISensor.h" -DigitalOut ssr(PTA1); -AnalogIn adc(PTE20); +DigitalOut ssr(PTA1); // Solid State Relay +AnalogIn adc(PTE20); // A/D converter reads temperature #define OFF 0 #define RED 1 @@ -17,46 +18,55 @@ #define WHITE 4 #define YELLOW 5 -// RTD ohms +// PT1000 RTD ohms // 1360 ohms = 94C // 1000 ohms = too cold (0C) // 1520 ohms = too hot (136C) -// note: assume a 2K divider resistor, a PT1000 RTD and a 16 bit A/D result -// use this formula (RTD_OHMS/(RTD_OHMS+2000)) * 65536 +// note: assume a 2.2K divider resistor, a PT1000 RTD and a 16 bit A/D result +// use this formula: (RTD_OHMS/(RTD_OHMS+2200)) * 65536 -// desired A/D value for boiler temp -#define TARGET_TEMP 25600 // CHANGE THIS +// desired A/D value for boiler temp while idling +// note: there is an offset between boiler wall temp sensors and actual water temp +#define TARGET_TEMP 25850 // CHANGE THIS -// table of % power level vs time in seconds into brew cycle (including preheat) +// table of adjustments (degrees C) to TARGET_TEMP vs time (seconds) into brew cycle (including preheat period) +// the idea is that extra heat is needed as cool water comes into the boiler during brew +// note: if you alternate very high and negative values here, you get the equivalent of open loop/PWM/temp surfing + const int table[40] = { // preheat up to 10 seconds - 0,0,100,100,100,100,0,0,0,0, // CHANGE THIS + 0,0,0,0,0,0,18,18,18,18, // CHANGE THIS // brewing (pump is on) up to 30 seconds - 65,65,65,65,65,65,65,65,65,65,65,65,65,65,65, // CHANGE THIS - 65,65,65,65,65,65,65,65,65,65,65,65,65,65,65 + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, // CHANGE THIS + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18 }; -// these probably don't need to be changed -#define CLOSE 50 // how close in A/D value before switching to proportional control -#define INITIAL_POWER .03 // initial guess for steady state power needed (try .03) +// these probably don't need to be changed if you are using a Gaggia Classic +#define CLOSE 30 // how close in A/D value before switching to proportional control +#define INITIAL_POWER .05 // initial guess for steady state power needed (try .05 = 5%) #define MIN_TEMP 21000 // below this is an error -#define MAX_TEMP 29000 // above this is an error +#define MAX_TEMP 218000 // above this is an error +#define ROOM_TEMP 22000 // A/D at standard ambient room temp +#define MAX_ROOM_TEMP 22500 // above this means ambient isn't valid #define SLEEP_TIME 3600 // turn off heat after this many seconds #define BREW_TIME 30 // max brew time #define BREW_PREHEAT 10 // max preheat time - +#define AD_PER_DEGREE 44 // how many A/D counts equal a 1 degree C change #define debug if (1) printf // use if (1) or if (0) void brew(void); void set_color(int color); unsigned read_ad(void); - + +unsigned ambient_temp; // room or water tank temp +double heat = INITIAL_POWER; // initial fractional heat needed while idle + int main() { - unsigned ambient_temp = read_ad(); // save temp on startup (future enhancements) time_t prev_time = 0; TSISensor tsi; // used as a start button + ambient_temp = read_ad(); // save temp on startup set_time(0); // start clock at zero @@ -80,12 +90,11 @@ set_color(RED); // set LED to red } else { // close to target temp // learning mode - adjust heat, the fraction of time power should be on - static double heat = INITIAL_POWER; // initial fractional heat needed while idle - + if (temp > TARGET_TEMP) // adjust best guess for % heat needed - heat *= .99; + heat *= .98; else - heat *= 1.01; + heat *= 1.02; debug("learned heat = %F, temp = %u\r\n",heat, temp); ssr = 1; // turn on heater for PWM @@ -109,54 +118,67 @@ while (time(NULL) < wakeup_time) // wait till tomorrow wait(1); set_time(0); // clock runs zero to 24 hours - wakeup_time = (24 * 60 * 60); // no 15 min offset needed now + wakeup_time = (24 * 60 * 60); // no 20 min offset needed now + ambient_temp = read_ad(); // save temp on startup } // check for errors (incorrect boiler temp can be dangerous) if (temp > MAX_TEMP || temp < MIN_TEMP) { ssr = 0; // turn off heater set_color(YELLOW); // set LED to indicate error + debug("error A/D = %u\r\n",temp); for (;;); // reset needed to exit this } - if (time(NULL) > prev_time) debug("A/D value = %u\r\n",temp); - prev_time = time(NULL); + if (time(NULL) > prev_time) debug("A/D value = %u\r\n",temp); // every second + prev_time = time(NULL); - } // while + } // for (;;) } // main() //================================================================= // This subroutine is called when the button is pressed, 10 seconds -// before the pump is started. It does open loop power/temp control. +// before the pump is started. It does open loop PWM power/heat control. //================================================================= void brew(void) { - time_t start_time = time(NULL); - + unsigned start_time = time(NULL); #define brew_time (time(NULL) - start_time) - - debug("preheat/brew start\r\n"); + + double adjust = 1; // default is no adjustment + + // adjust for tank temp (assumed to be equal to ambient at startup) + if (ambient_temp < MAX_ROOM_TEMP) // sanity check + adjust = (double)(ROOM_TEMP - TARGET_TEMP) / (double)(ambient_temp - TARGET_TEMP); + + debug("preheat/brew start, adjust = %F\r\n", adjust); set_color(WHITE); - while (brew_time < BREW_TIME + BREW_PREHEAT) { // loop for 40 seconds - - if (brew_time >= BREW_PREHEAT) + for (;;) { + unsigned prev_brew_time = 0; + + if (brew_time >= BREW_PREHEAT + BREW_TIME) + break; + + if (brew_time == BREW_PREHEAT) set_color(BLUE); // set LED color to blue for start brew/pump now - // PWM power level over .5 second - ssr = 1; // turn on heater - wait(table[brew_time] / 200.0); - ssr = 0; // turn off heater - wait((100 - table[brew_time]) / 200.0); + // bang/bang temp to the table value + if (read_ad() < (TARGET_TEMP + (table[brew_time] * AD_PER_DEGREE)) * adjust) + ssr = 1; // heater on + else + ssr = 0; // heater off - debug("power level %u = %u %%, temp = %u\r\n",brew_time,table[brew_time],read_ad()); + if (brew_time != prev_brew_time) + debug("target temp %u = %u, temp = %u\r\n",brew_time,table[brew_time],read_ad()); + prev_brew_time = brew_time; - } // while - - set_color(OFF); + } // for + + ssr = 0; debug("brew done\r\n"); } // brew() @@ -205,15 +227,23 @@ unsigned read_ad(void) { -unsigned a, b, c; + +uint32_t sum=0; +int i; - a = adc.read_u16(); +for (i = 0; i < 3; ++i) { // average multiple for more accuracy + unsigned a, b, c; + + a = adc.read_u16(); // take median of 3 values b = adc.read_u16(); c = adc.read_u16(); - if ((a >= b && a <= c) || (a >= c && a <= b)) return a; - if ((b >= a && b <= c) || (b >= c && b <= a)) return b; - return c; + if ((a >= b && a <= c) || (a >= c && a <= b)) sum += a; + else if ((b >= a && b <= c) || (b >= c && b <= a)) sum += b; + else sum += c; +} // for + +return sum / 3; } // read_ad() \ No newline at end of file