Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: Adafruit_RTCLib FastPWM TSI mbed
Diff: main.cpp
- Revision:
- 1:b5abc8ddd567
- Parent:
- 0:24cdf76455c4
- Child:
- 2:22d9c714b511
--- 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