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
- Committer:
- jzeeff
- Date:
- 2013-08-29
- Revision:
- 3:eb60e36b03f6
- Parent:
- 2:22d9c714b511
- Child:
- 4:3d661b485d59
File content as of revision 3:eb60e36b03f6:
// Program to control espresso maker boiler temperatures // 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 // PT1000 RTD ohms (use Google to find a full table) // 1360 ohms = 94C // 1000 ohms = too cold (0C) // 1520 ohms = too hot (136C) // note: assume a 2.2K divider resistor, a PT1000 RTD and a 16 bit A/D result // use this formula: A/D = (RTD_OHMS/(RTD_OHMS+2200)) * 65536 // desired A/D value for boiler temp while idling // 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 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,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 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 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 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 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) #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 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 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); 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_temp(boiler); // bang/bang when far away, PWM to learned value when close if (temp > TARGET_TEMP + CLOSE) { heater = OFF; // turn off heater led_color(GREEN); // set LED to green } else if (temp < TARGET_TEMP - CLOSE) { 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 *= (1-GAIN); else 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(); // if they signaled for steam if (tsi.readPercentage() > .2 && tsi.readPercentage() < .5) steam(120); 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); set_time(0); // clock runs zero to 24 hours wakeup_time = (24 * 60 * 60); // no 20 min offset needed now 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) { 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 %u, heat = %F\r\n",temp,read_temp(group),heat); // once per second prev_time = time(NULL); } // for (;;) } // main() //================================================================= // This subroutine is called when the button is pressed, 10 seconds // 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) // 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); led_color(WHITE); unsigned prev_brew_time = 999; unsigned brew_time; double pwm; for (;;) { brew_time = time(NULL) - start_time; // seconds into cycle if (brew_time >= BREW_PREHEAT + BREW_TIME) break; // brew is done if (brew_time == BREW_PREHEAT) 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_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){ // 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 debug("brew done\r\n"); } // 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() // ============================================= // set multi color LED state // ============================================= DigitalOut r (LED_RED); DigitalOut g (LED_GREEN); DigitalOut b (LED_BLUE); void led_color(int color) { // turn off 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 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 and average for accuracy //======================================= unsigned read_temp(AnalogIn adc) { 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(); 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 / 33; } // read_temp()