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-09
Revision:
1:b5abc8ddd567
Parent:
0:24cdf76455c4
Child:
2:22d9c714b511

File content as of revision 1:b5abc8ddd567:


// 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

#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
// 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: (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 25850       // 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
// 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,0,0,0,0,18,18,18,18,                            // CHANGE THIS
    // brewing (pump is on) up to 30 seconds
    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 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 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()
{
    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 (;;) {
        unsigned temp;

        // read temp from A/D
        // note: in A/D counts, not degrees
        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);           
            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);
    #define brew_time (time(NULL) - start_time)
    
    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);  
            
    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

        // 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
        
        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;

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