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()