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
main.cpp
- Committer:
- jzeeff
- Date:
- 2013-08-11
- Revision:
- 2:22d9c714b511
- Parent:
- 1:b5abc8ddd567
- Child:
- 3:eb60e36b03f6
File content as of revision 2:22d9c714b511:
// 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
// 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)
// 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 an offset between boiler wall temp sensors and actual water temp
#define TARGET_TEMP 25900 // 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.
// This can prevent overshoot.
const double table[40] = {
// preheat up to 10 seconds
0,0,0,0,18.99,18.99,0,0,0,0, // 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
};
// 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 MIN_TEMP 21000 // below this is an error
#define MAX_TEMP 29000 // above this is an error
#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 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()
{
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
debug("starting A/D value/temp = %u\r\n",ambient_temp);
// loop forever, controlling boiler temperature
for (;;) {
// read temp from A/D
// note: in A/D counts, not degrees
unsigned temp = read_ad();
// 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
} else if (temp < TARGET_TEMP - CLOSE) {
ssr = 1; // turn on heater
set_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;
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);
} // 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
ssr = 0; // turn off heater
set_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_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); // every 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 open 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);
debug("preheat/brew start, adjust = %F\r\n", adjust);
set_color(WHITE);
for (;;) {
unsigned brew_time;
static unsigned prev_brew_time;
brew_time = time(NULL) - start_time;
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
double 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 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;
} // for
ssr = 0;
debug("brew done\r\n");
} // brew()
// =============================================
// set multi color LED state
// =============================================
DigitalOut r (LED_RED);
DigitalOut g (LED_GREEN);
DigitalOut b (LED_BLUE);
void set_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 WHITE:
r = g = b = 0;
break;
} // switch
} // set_color()
//=======================================
// read A/D value from RTD
// median for accuracy
//=======================================
unsigned read_ad(void)
{
uint32_t sum=0;
int i;
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
return sum / 3;
} // read_ad()