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
Diff: main.cpp
- Revision:
- 3:eb60e36b03f6
- Parent:
- 2:22d9c714b511
- Child:
- 4:3d661b485d59
--- a/main.cpp Sun Aug 11 20:39:57 2013 +0000 +++ b/main.cpp Thu Aug 29 14:55:52 2013 +0000 @@ -1,23 +1,12 @@ // Program to control espresso maker boiler temperatures -// Similar to multiple PID control, but uses a flexible open or closed loop table during brew -// Used with a Gaggia Classic, FreeScale FRDM-KL25Z computer, PT1000 RTD, SSR +// Similar to multiple PID control (pre-brew, brew and steam), +// but uses a flexible open and closed loop table during brew +// Used with a Gaggia Classic, FreeScale FRDM-KL25Z computer, PT1000 RTD, heater +// See www.coffeegeeks.com for discussion // Jon Zeeff, 2013 // Public Domain -#include "mbed.h" -#include "TSISensor.h" - -DigitalOut ssr(PTA1); // Solid State Relay -AnalogIn adc(PTE20); // A/D converter reads temperature - -#define OFF 0 -#define RED 1 -#define GREEN 2 -#define BLUE 3 -#define WHITE 4 -#define YELLOW 5 - // PT1000 RTD ohms (use Google to find a full table) // 1360 ohms = 94C // 1000 ohms = too cold (0C) @@ -27,111 +16,180 @@ // use this formula: A/D = (RTD_OHMS/(RTD_OHMS+2200)) * 65536 // 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 25900 // CHANGE THIS +// note: there is usually some offset between boiler wall temp sensors and actual water temp +#define TARGET_TEMP 25440 // CHANGE THIS // 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. // Extra heat is provided by a higher than normal boiler wall temp. -// NOTE: the decimal portion of the value is used as the PWM value to be applied if more heat is needed. +// NOTE: the fractional portion of the value is used as the PWM value to be applied if more heat is needed. // This can prevent overshoot. +// Example: 5.3 means that the boiler wall should be 5 degrees C above normal at this time point. If not, apply 30% power. +// Example: 99.99 means (roughly) that the heater should be completely on for the 1 second period +// Note: heat takes at least 4 seconds before it is seen by the sensor const double table[40] = { // preheat up to 10 seconds - 0,0,0,0,18.99,18.99,0,0,0,0, // CHANGE THIS + 0.0,0.0,0.0,0.0,0.0,0.0,0.0,99.9,99.9,99.20, // CHANGE THIS // brewing (pump is on) up to 30 seconds - 18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7,18.7, // CHANGE THIS - 15.7,15.7,15.7,15.7,15.7,15.7,15.7,15.7,15.7,15.7,15.7,0,0,0,0 // CHANGE THIS -}; + 99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20, // CHANGE THIS + 99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,0,0,0,0 // CHANGE THIS +}; + +const double pump_table[40] = { + // during pre-brew period + 0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0, // CHANGE THIS + // brewing up to 30 seconds + 0.0,0.0,99.99,99.99,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20, // CHANGE THIS + 99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,99.20,0,0,0,0 // CHANGE THIS +}; // 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 learned value control -#define INITIAL_POWER .05 // initial guess for steady state power needed (try .05 = 5%) +#define CLOSE 60 // how close in A/D value before switching to learned value control +#define GAIN .01 // how fast to adjust +#define INITIAL_POWER .03 // initial guess for steady state heater power needed (try .03 = 3%) #define MIN_TEMP 21000 // below this is an error -#define MAX_TEMP 29000 // above this is an error +#define MAX_TEMP 29500 // above this is an error +#define STEAM_TEMP 28000 // boiler temp while steaming #define ROOM_TEMP 22000 // A/D value 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 SLEEP_TIME 7200 // 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); +#include "mbed.h" +#include "TSISensor.h" // touch sensor +#include "DS1307.h" // real-time clock +#include "FastPWM.h" // better PWM routine for pump control + +#define OFF 0 +#define RED 1 +#define GREEN 2 +#define BLUE 3 +#define WHITE 4 +#define YELLOW 5 +#define AQUA 6 +#define PINK 7 + +#define ON 1 +#define OFF 0 -unsigned ambient_temp; // room or water tank temp -double heat = INITIAL_POWER; // initial fractional heat needed while idle - -int main() +DigitalOut heater(PTD7); // Solid State Relay - PTD6&7 have high drive capability +FastPWM pump(PTD4); // Solid State Relay - PTD4 can do PWM @ 10K hz +AnalogIn boiler(PTE20); // A/D converter reads temperature on boiler +AnalogIn group(PTE22); // A/D for group basket temp +DigitalOut led_green(LED_GREEN); +I2C gI2c(PTE0, PTE1); // SDA, SCL - use pullups +RtcDs1307 rtclock(gI2c); // DS1307 is a real time clock chip +Serial pc(USBTX, USBRX); // Serial to pc connection + +void brew(void); +void led_color(int color); +unsigned read_temp(AnalogIn adc); +void steam(int seconds); + +unsigned ambient_temp; // room or water tank temp (startup) +double heat = INITIAL_POWER; // initial fractional heat needed while idle +unsigned boiler_log[40]; // record boiler temp during brew +unsigned group_log[40]; // record basket temp during brew + + +int main() // start of program { time_t prev_time = 0; - TSISensor tsi; // used as a start button - ambient_temp = read_ad(); // save temp on startup - + TSISensor tsi; // used as a brew start button + ambient_temp = read_temp(boiler); // save temp on startup + set_time(0); // start clock at zero - +#if 0 + DateTime dt = gRtc.now(); + + debug("%u/%u/%02u %2u:%02u:%02u\r\n" + ,dt.month(),dt.day(),dt.year() + ,dt.hour(),dt.minute(),dt.second()); +#endif debug("starting A/D value/temp = %u\r\n",ambient_temp); - -// loop forever, controlling boiler temperature + + pump.period_ms(500); // period of PWM signal + //pump = 100; // duty cycle. For DC, use 6 msec pulses + + if (pc.readable()) // clear any data on serial port + pc.getc(); + + if (ambient_temp < MAX_ROOM_TEMP) + steam(180); // do accelerated warmup by overheating + + // loop forever, controlling boiler temperature for (;;) { // read temp from A/D // note: in A/D counts, not degrees - unsigned temp = read_ad(); - + unsigned temp = read_temp(boiler); + // bang/bang when far away, PWM to learned value when close if (temp > TARGET_TEMP + CLOSE) { - ssr = 0; // turn off heater - set_color(GREEN); // set LED to green + heater = OFF; // turn off heater + led_color(GREEN); // set LED to green } else if (temp < TARGET_TEMP - CLOSE) { - ssr = 1; // turn on heater - set_color(RED); // set LED to red + heater = ON; // turn on heater + led_color(RED); // set LED to red } else { // close to target temp // learning mode - adjust heat, the fraction of time power should be on - - if (temp > TARGET_TEMP) // adjust best guess for % heat needed - heat *= .98; + + if (temp > TARGET_TEMP) // adjust best guess for % heat needed + heat *= (1-GAIN); else - heat *= 1.02; - - debug("learned heat = %F, temp = %u\r\n",heat, temp); - ssr = 1; // turn on heater for PWM - set_color(RED); - wait(heat); - ssr = 0; // turn off heater - set_color(GREEN); - wait(1-heat); + heat *= (1+GAIN); + + heater = ON; // turn on heater for PWM + led_color(RED); + wait(heat * 2.7); // 1.7 to reduce interaction with 50/60Hz power + heater = OFF; // turn off heater + led_color(GREEN); + wait((1-heat) * 2.7); // total time is 2.7 seconds } // if // the user must press a button 10 seconds prior to brewing to start preheat if (tsi.readPercentage() > .5) brew(); - - // check for idle, sleep till tomorrow if it occurs - if (time(NULL) > SLEEP_TIME){ // save power - static time_t wakeup_time = (24 * 60 * 60) - (20 * 60); // 24 hours minus 20 min + + // if they signaled for steam + if (tsi.readPercentage() > .2 && tsi.readPercentage() < .5) + steam(120); - ssr = 0; // turn off heater - set_color(OFF); - printf("sleep\r\n"); + if (pc.readable()){ // Check if data is available on serial port. + pc.getc(); + // debug, print out temp log + int i; + for (i = 0; i < 40; ++i) + printf("log %d: %u %u\r\n",i,boiler_log[i],group_log[i]); + } // if + + // check for idle shutdown, sleep till tomorrow if it occurs + if (time(NULL) > SLEEP_TIME) { // save power + static time_t wakeup_time = (24 * 60 * 60) - (20 * 60); // 24 hours minus 20 min + + heater = OFF; // turn off heater + led_color(OFF); + printf("sleep\r\n"); while (time(NULL) < wakeup_time) // wait till tomorrow - wait(1); + wait(1); set_time(0); // clock runs zero to 24 hours wakeup_time = (24 * 60 * 60); // no 20 min offset needed now - ambient_temp = read_ad(); // save temp on startup + ambient_temp = read_temp(boiler); // 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 + heater = OFF; // turn off heater + led_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); // every second + if (time(NULL) > prev_time) debug("A/D value = %u %u, heat = %F\r\n",temp,read_temp(group),heat); // once per second prev_time = time(NULL); } // for (;;) @@ -141,54 +199,87 @@ //================================================================= // This subroutine is called when the button is pressed, 10 seconds -// before the pump is started. It does open loop PWM power/heat control. +// before the pump is started. It does both open loop and closed +// loop PWM power/heat control. //================================================================= void brew(void) { unsigned start_time = time(NULL); - + double adjust = 1; // default is no adjustment - + // adjust for higher or lower 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); - + // add in "heat"?? + //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); - + led_color(WHITE); + + unsigned prev_brew_time = 999; + unsigned brew_time; + double pwm; + for (;;) { - unsigned brew_time; - static unsigned prev_brew_time; - - brew_time = time(NULL) - start_time; - + brew_time = time(NULL) - start_time; // seconds into cycle + if (brew_time >= BREW_PREHEAT + BREW_TIME) - break; - + break; // brew is done + if (brew_time == BREW_PREHEAT) - set_color(BLUE); // set LED color to blue for start brew/pump now - - double pwm = table[brew_time] - (int)table[brew_time]; // decimal part only - + led_color(BLUE); // set LED color to blue for start brew/pump now + + //pump = pump_table[brew_time]; // duty cycle + + pwm = table[brew_time] - (int)table[brew_time]; // decimal part only + // if too cold, apply the PWM value, if too hot, do nothing - if (read_ad() < (TARGET_TEMP + (table[brew_time] * AD_PER_DEGREE)) * adjust) { - ssr = 1; - wait(pwm / 2); - ssr = 0; - wait((1 - pwm) / 2); + if (read_temp(boiler) < (TARGET_TEMP + (table[brew_time] * AD_PER_DEGREE)) * adjust) { + if (pwm > 0) { + heater = ON; + wait(pwm/2); + heater = OFF; + wait((1 - pwm)/2); + } } // if PWM - - if (brew_time != prev_brew_time) // print status every second - debug("target temp %u = %F, temp = %u\r\n",brew_time,table[brew_time],read_ad()); - prev_brew_time = brew_time; + + if (brew_time != prev_brew_time){ // every second + group_log[brew_time] = read_temp(group); // record group temp + boiler_log[brew_time] = read_temp(boiler); // record boiler temp + debug("target temp %u = %F, temp = %u %u\r\n",brew_time,table[brew_time],read_temp(boiler),read_temp(group)); + prev_brew_time = brew_time; + } } // for - - ssr = 0; + debug("brew done\r\n"); + +} // brew() -} // brew() +//=========================================================== +// control to a higher steam temperature for n seconds +//=========================================================== + +void steam(int seconds) +{ + unsigned start_time = time(NULL); + + debug("steam start, time = %d\r\n", seconds); + + while (time(NULL) - start_time < seconds) { + if (read_temp(boiler) > STEAM_TEMP) { + heater = OFF; // turn off heater + led_color(AQUA); // set LED to aqua + } else { + heater = ON; // turn on heater + led_color(PINK); // set LED to pink + } + } // while + + heater = OFF; // turn off + +} // steam() // ============================================= @@ -199,58 +290,65 @@ DigitalOut g (LED_GREEN); DigitalOut b (LED_BLUE); -void set_color(int color) +void led_color(int color) { - // turn off -r = g = b = 1; + r = g = b = 1; -switch (color) { - case OFF: - break; - case GREEN: - g = 0; - break; - case BLUE: - b = 0; - break; - case RED: - r = 0; - break; - case YELLOW: - r = g = 0; - break; - case WHITE: - r = g = b = 0; - break; - } // switch - - } // set_color() + switch (color) { + case OFF: + break; + case GREEN: + g = 0; + break; + case BLUE: + b = 0; + break; + case RED: + r = 0; + break; + case YELLOW: + r = g = 0; + break; + case AQUA: + b = g = 0; + break; + case PINK: + r = b = 0; + break; + case WHITE: + r = g = b = 0; + break; + } // switch + +} // led_color() //======================================= // read A/D value from RTD -// median for accuracy +// median and average for accuracy //======================================= -unsigned read_ad(void) +unsigned read_temp(AnalogIn adc) { + uint32_t sum=0; + int i; -uint32_t sum=0; -int i; + for (i = 0; i < 33; ++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(); -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)) sum += a; - else if ((b >= a && b <= c) || (b >= c && b <= a)) sum += b; - else sum += c; -} // for + 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 + return sum / 33; + +} // read_temp() + + + +