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

Revision:
5:0393adfdd439
Parent:
4:3d661b485d59
Child:
6:56b205b46b42
--- a/main.cpp	Wed Oct 02 13:38:23 2013 +0000
+++ b/main.cpp	Mon Apr 14 20:35:30 2014 +0000
@@ -19,7 +19,10 @@
 
 // desired A/D value for boiler temp while idling
 // note: there is usually some offset between boiler wall temp sensors and actual water temp  (10-15C?)
-#define TARGET_OHMS 1400       // Desired PT1000 RTD Ohms - CHANGE THIS 
+#define TARGET_OHMS 1400       // Desired PT1000 RTD Ohms / boiler temp - CHANGE THIS 
+
+#define BREW_TIME 44            // max brew time
+#define BREW_PREHEAT 6          // max preheat time (when to open brew valve)
 
 // Table of adjustments (degrees C) to TARGET_TEMP and heat 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.
@@ -30,46 +33,55 @@
 // Example: 99.99 means (roughly) that the heater should be completely on for the 1 second period
 // Note: heat on a Gaggia Classic takes about 4 seconds before it is seen by the sensor
 
-#define BREW_TIME 44            // max brew time
-#define BREW_PREHEAT 6          // max preheat time
-
-const double table[BREW_TIME+BREW_PREHEAT] = {
-    // preheat up to 6 seconds
-    0,0,0,0,99.99,99.99,                  // CHANGE THIS
-    // brewing (pump is on) up to 30 seconds
-    0,0,0,0,0,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.40,99.35,99.35, // CHANGE THIS
-    99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35, // CHANGE THIS
+const double table[BREW_TIME+BREW_PREHEAT] = {  // CHANGE THIS
+    0,0,0,0,                      // nothing (pumo is off)
+    99.99,99.99,                  // step heat up before flow
+    0,0,0,0,                      // filling portafilter
+    0,99.35,99.35,                // preinfusion
+    99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.40,99.35,99.35, 
+    99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35, 
     99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35,99.35
 };
 
-// pump power over time for flush or preinfusion or pressure profiling
-const double pump_table[BREW_TIME+BREW_PREHEAT] = {
-    // during pre-brew period
-    0,0,0,0,.80,.80,                                    // CHANGE THIS
-    // brewing up to 30 seconds
-    .85,.90,1.0,0,0,0,0,0,.80,1.0,1.0,1.0,1.0,1.0,1.0,1.0,    // CHANGE THIS
+// pump power over time for preinfusion/slow ramp/pressure profiling
+// range: 0 to 1
+const double pump_table[BREW_TIME+BREW_PREHEAT] = {   // CHANGE THIS
+    0,0,0,0,                                // nothing (pump is off)
+    .45,.45,                                // hold low pressure until valve is opened          
+    .45,.55,.65,.75,                        // ramp pressure up slowly and fill portafilter
+    0,0,0,                                  // preinfusion delay
+    .75,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,    // brew
     1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,    // CHANGE THIS
-    .85,.85,.85,.85,.85,.85,.85,.85,.85,.85
+    1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
 };
 
-// desired total weight of espresso over brew period in grams
-const int scale_table[BREW_TIME+BREW_PREHEAT] = {
-   2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,  
-   60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60  
+// table for flow profiling
+// desired flow rate of espresso in grams/sec for each second of brew
+// starts when the total grams in the cup achieves the first entry, not time zero
+const double scale_table[BREW_TIME+BREW_PREHEAT] = {
+    1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,
+    1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,
+    1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,
+    1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,
+    1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75,1.75
 };
 
+#define END_GRAMS 30            // end when this many grams total (in 27 secs?)
+#define FLOW_PERIOD 23.0        // desired shot duration (not used)
+#define START_FLOW_PROF  14     // when (seconds) to start flow profiling
+
 // these probably don't need to be changed if you are using a Gaggia Classic
 #define AD_PER_DEGREE 43        // how many A/D counts equal a 1 degree C change 
-#define AD_PER_GRAM 76.75       // how many A/D count equal 1 gram of weight
+#define AD_PER_GRAM 76.70       // how many A/D count equal 1 gram of weight
 #define CLOSE 60                // how close in A/D value before switching to learned value control
-#define GAIN .01                // how fast to adjust (eg 1% percent per 2.7s control period) 
+#define GAIN .01                // how fast to adjust heat(eg 1% percent per 2.7s control period) 
 #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 29700          // above this is an error
 #define STEAM_TEMP 28000        // boiler temp while steaming
 #define ROOM_TEMP 21707         // A/D value at standard ambient room temp 23C
 #define MAX_ROOM_TEMP (ROOM_TEMP + (10 * AD_PER_DEGREE))     // above this means ambient isn't valid
-#define SLEEP_PERIOD (3*3600)   // turn off heat after this many seconds
+#define SLEEP_PERIOD (4*3600)   // turn off heat after this many seconds
 #define WAKEUP_TIME 12          // time in 0-23 hours, GMT to wake up. 99 to disable.  Example: 12 for noon GMT
 
 #define TARGET_TEMP ((TARGET_OHMS*65536)/(TARGET_OHMS+2200))   // how hot the boiler should be in A/D
@@ -103,14 +115,19 @@
 I2C        gI2c(PTE0, PTE1);    // SDA, SCL - use pullups somewhere
 RtcDs1307  rtclock(gI2c);       // DS1307 is a real time clock chip
 Serial     pc(USBTX, USBRX);    // Serial to pc connection
+Serial     bluetooth(PTC4,PTC3); // Serial via wireless TX,RX
 TSISensor  tsi;                 // used as a brew start button
 AnalogIn   scale(PTC2);         // A/D converter reads scale
 
 void brew(void);
 void led_color(int color);
 unsigned read_temp(int device);
+int read_scale(void);
+int read_scale2(void);
+int read_scale3(void);
 unsigned read_ad(AnalogIn adc);
 void steam(int seconds);
+inline int median(int a, int b, int c);
 
 unsigned ambient_temp;          // room or water tank temp (startup)
 double heat = INITIAL_POWER;    // initial fractional heat needed while idle
@@ -118,13 +135,16 @@
 unsigned group_log[BREW_TIME+BREW_PREHEAT];         // record basket temp during brew
 int scale_log[BREW_TIME+BREW_PREHEAT];        // record weight during brew
 
+uint16_t slog[5000];
+int scount=0;
+
 int main()                      // start of program
 {
     time_t prev_time = 0;
 
     led_color(OFF);
-  
-    wait(1);                            // let settle
+
+    wait(1);                      // let settle
     ambient_temp = read_temp(BOILER);   // save temp on startup
 
 #if 0
@@ -136,17 +156,14 @@
           ,dt.month(),dt.day(),dt.year()
           ,dt.hour(),dt.minute(),dt.second());
     set_time(0);                        // set active clock
- 
+
     debug("starting A/D value/temp = %u %u\r\n",ambient_temp,read_temp(GROUP));
 
     pump = 0;               // duty cycle.
     pump.period_us(410);    // period of PWM signal in us
-      
-    if (pc.readable())      // clear any data on serial port
-        pc.getc();
 
     if (ambient_temp < MAX_ROOM_TEMP)
-        steam(5 * 60);      // do accelerated warmup by overheating for awhile
+        steam(7 * 60);      // do accelerated warmup by overheating for awhile
 
     // loop forever, controlling boiler temperature
 
@@ -190,33 +207,43 @@
         //if (tsi.readPercentage() > .1 && tsi.readPercentage() < .5)
         //    steam(120);
 
-        if (pc.readable()) {   // Check if data is available on serial port.
-            pc.getc();
-            // debug, print out brew temp log
+        char key = 0;
+        if (pc.readable())    // Check if data is available on serial port.
+            key = pc.getc();
+
+        if (key == 'l') {    // debug, print out brew temp log
             int i;
             for (i = 0; i < BREW_TIME+BREW_PREHEAT; ++i)
                 printf("log %d: %u %u %d\r\n",i,boiler_log[i],group_log[i],scale_log[i]);
+            for (i = 0; i < scount; ++i)
+                printf("%u\r\n",slog[i]);
         } // if
 
+        if (key == 'p') {    // cycle pump for flush
+            pump = 1;
+            wait(5);
+            pump = 0;
+        }
+
         // check for idle shutdown, sleep till tomorrow am if it occurs
-        if (time(NULL) > SLEEP_PERIOD) {    // save power
+        if (time(NULL) > SLEEP_PERIOD || key == 'i') {    // save power
             heater = OFF;                   // turn off heater
             led_color(OFF);
             printf("sleep\r\n");
-            
+
             for (;;) {                      // loop till wakeup in the morning
                 DateTime dt;
-                
-                if (pc.readable())          // user wakeup
-                   break;
-                   
+
+                if (pc.readable() && pc.getc() == ' ')          // user wakeup
+                    break;
+
                 dt = rtclock.now();         // read real time clock
                 if (dt.hour() == WAKEUP_TIME && dt.minute() == 0)   // GMT time to wake up
-                   break;   
-                   
+                    break;
+
                 wait(30);
             } // for
-            
+
             set_time(0);                        // reset active timer
             debug("exit idle\r\n");
             ambient_temp = read_temp(BOILER);   // save temp on startup
@@ -231,8 +258,8 @@
             temp = read_temp(BOILER);
         }
 
-        if (time(NULL) > prev_time) 
-           debug("A/D value = %u  %u, heat = %F, scale = %u\r\n",temp,read_temp(GROUP),heat,read_ad(scale));  // once per second
+        if (time(NULL) > prev_time)
+            debug("A/D value = %u  %u, heat = %F, scale = %u\r\n",temp,read_temp(GROUP),heat,read_ad(scale));  // once per second
         prev_time = time(NULL);
 
     } // for (;;)
@@ -240,8 +267,14 @@
 } // main()
 
 
+// turn off the heater
+void heater_off(void)
+{
+    heater = OFF;
+}
+
 //=================================================================
-// This subroutine is called when the button is pressed, 10 seconds
+// This subroutine is called when the button is pressed, n seconds
 // before the pump is started.  It does both open loop and closed
 // loop PWM power/heat control.
 //=================================================================
@@ -251,76 +284,117 @@
     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"??
+    // add in "heat" value as a measure of ambient??
     //if (ambient_temp < MAX_ROOM_TEMP)    // sanity check
     //    adjust = (double)(ROOM_TEMP - TARGET_TEMP) / (double)(ambient_temp - TARGET_TEMP);
- 
+
     led_color(WHITE);
 
-    unsigned brew_time;     // in seconds
-    double pwm;
-    unsigned temp;
-    unsigned scale_zero = read_ad(scale);
-    int grams;
-    
-    debug("preheat/brew start, adjust = %F, zero = %u\r\n", adjust,scale_zero);
+    unsigned brew_time;         // seconds since start of brew
+    unsigned flow_time = 0;     // clock that runs once flow starts
+    double target_pump = 1;     // current value of pump power for desired flow
+    double grams;
+    double prev_grams = 0;
 
-    for (brew_time = 0; brew_time < BREW_PREHEAT + BREW_TIME; ++brew_time) {  // loop until end of brew
-  
+    wait(.5);                          // stabilize
+    int scale_zero = read_scale2();    // weight of empty cup
+    debug("preheat/brew start, adjust = %f, scale zero = %u counts\r\n", adjust,scale_zero);
+
+    Timeout heater_timer;   // used to schedule off time
+
+    Timer timer;            // used to keep syncronized, 1 update per second
+    timer.start();
+
+    for (brew_time = 0; brew_time < BREW_PREHEAT + BREW_TIME;) {  // loop until end of brew
+
         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 or on/off of pump for this period
-
-        pwm = table[brew_time] - (int)table[brew_time];    // decimal part only
-        
-        temp = read_temp(BOILER);
-
+        // *** heat control
+        unsigned temp = read_temp(BOILER);     // minimal time needed for this
         // if too cold, apply the PWM value, if too hot, do nothing
-        if (temp < (TARGET_TEMP + (table[brew_time] * AD_PER_DEGREE)) * adjust) {
-            if (pwm > 0.0 && pwm <= 1.0) {              
+        if (temp < TARGET_TEMP + (table[brew_time] * AD_PER_DEGREE)) {
+            double pwm = table[brew_time] - (int)table[brew_time];    // decimal part of brew heat
+            // adjust?
+            if (pwm > 0.0 && pwm <= 1.0) {
                 heater = ON;
-                wait(pwm);
+                heater_timer.attach(&heater_off, pwm); // schedule turn off
+            } else
                 heater = OFF;
-                pwm = 1 - pwm;
-                if (pwm > 0.0 && pwm <= 1.0)
-                   wait(pwm);
-            } else
-                wait(1.0);
+        } // if
+
+        // *** pump power control
+
+#define MIN_PUMP .4         // below this is effectively zero        
+        grams = ((double)read_scale2() - scale_zero) / AD_PER_GRAM;  // current espresso weight
+        if (grams < 0)              // clip impossible result
+            grams = 0;
+        double delta_grams = grams - prev_grams;
+        if (delta_grams < 0)
+           delta_grams = 0;
+        prev_grams = grams;
+
+        if (brew_time >= START_FLOW_PROF) {  // start flow profiling at specified time
+            ++flow_time;                // seconds of significant flow
+            // adjust flow rate by changing pump power
+            // Proportional control
+            #define UP_GAIN .25
+            #define DOWN_GAIN 1.2
+            
+            double error = (delta_grams - scale_table[flow_time]) / scale_table[flow_time];
+            
+            if (error > 0)
+               target_pump /= 1 + (error * DOWN_GAIN);  // too fast
+            else
+               target_pump +=  -error * UP_GAIN;      // too slow
+
+            if (target_pump > pump_table[brew_time])    // clip to max allowed
+                target_pump = pump_table[brew_time];
+            else if (target_pump < 0)                   // clip to min
+                target_pump = 0;
         } else
-            wait(1.0);
+            target_pump = pump_table[brew_time];        // use pump power profiling
+    
+        pump = MIN_PUMP + (1 - MIN_PUMP) * target_pump; // use the flow profiling value
 
+        debug("time = %u %u, grams = %F, delta = %F, target_pump = %F\r\n",brew_time,flow_time,grams,delta_grams,target_pump);
+        //debug("target temp %u = %f, temp = %u %u\r\n",brew_time,table[brew_time],read_temp(BOILER),read_temp(GROUP));
+
+        // record values for debugging and graphing
         group_log[brew_time] = read_temp(GROUP);    // record group temp
         boiler_log[brew_time] = temp;               // record boiler temp
-        grams = ((double)read_ad(scale) - scale_zero) / AD_PER_GRAM;
-        scale_log[brew_time] = grams;
-        
-        if (grams < 2)      // scale clock only starts when it hits two grams
-           scale_time = 0;
-        else
-           ++scale_time;
-           
-        //if (grams > scale_table[scale_time])
-        //else   
-        
-        //debug("target temp %u = %F, temp = %u %u\r\n",brew_time,table[brew_time],read_temp(BOILER),read_temp(GROUP));
-   
+        scale_log[brew_time] = grams;               // record espresso weight
+
         // early exit if final weight reached
-        if (grams >= scale_table[BREW_TIME+BREW_PREHEAT-1]) 
-           break;
-        
-        // early exit based on user input
-        if (tsi.readPercentage() > .1 && tsi.readPercentage() < .5)
-           break;
+        if (grams >= END_GRAMS)
+            break;
+
+        // early exit based on user input (read twice for noise)
+        //if (brew_time > 10 && tsi.readPercentage() > .1 && tsi.readPercentage() < .5) {
+        //    wait_ms(5);
+        //    if (tsi.readPercentage() > .1 && tsi.readPercentage() < .5)
+        //       break;
+        //}
+
+        // wait till next second
+        ++brew_time;
+        int ms = (brew_time * 1000) - timer.read_ms();
+        if (ms > 0)
+            wait_ms(ms);
+
+        // cleanup
+        heater = OFF;                // should be off already, but just in case
+        heater_timer.detach();       // disable off timer
 
     } // for
 
     // shut down
     led_color(OFF);
-    debug("brew done\r\n");
     pump = OFF;
     heater = OFF;
+    debug("brew done, time = %u, grams = %f, target_pump = %F\r\n",brew_time, grams, target_pump);
+
 } // brew()
 
 //===========================================================
@@ -341,10 +415,10 @@
             heater = ON;        // turn on heater
             led_color(PINK);    // set LED to pink
         }
-        
+
         if (tsi.readPercentage() > .5)  // abort steam
-           break;
-           
+            break;
+
     } // while
 
     heater = OFF;    // turn off
@@ -404,45 +478,40 @@
 DigitalOut x7(PTD5);
 DigitalOut x8(PTD6);
 DigitalOut x9(PTD1);
-DigitalOut x10(PTC0);
+//DigitalOut x11(PTC3);
 #endif
 
 //=======================================
 // A/D routines
 //=======================================
 
-DigitalOut ad_power(PTB9);   // used to turn on/off power to resistors
+DigitalOut ad_power(PTB9);  // used to turn on/off power to resistors (self heating)
+AnalogIn   boiler(PTE20);   // A/D converter reads temperature on boiler
+AnalogIn   group(PTE22);    // A/D for group basket temp
+AnalogIn   vref(PTE29);     // A/D for A/D power supply (ad_power)
 
-AnalogIn   boiler(PTE20);           // A/D converter reads temperature on boiler
-AnalogIn   group(PTE22);            // A/D for group basket temp
-AnalogIn   vref(PTE29);             // A/D for A/D power supply (ad_power)
+inline int median(int a, int b, int c)
+{
+    if ((a >= b && a <= c) || (a >= c && a <= b)) return a;
+    else if ((b >= a && b <= c) || (b >= c && b <= a)) return b;
+    else return c;
+} // median()
 
 
+// heavily averaged A/D reading
+
 unsigned read_ad(AnalogIn adc)
 {
     uint32_t sum=0;
-    int i;
- 
-    adc.read_u16();                 // throw away one
-  
-    #define COUNT 77                // number of samples to average
-   
-    for (i = 0; i < COUNT; ++i) {   // average multiple for more accuracy
-        uint16_t a, b, c;
+
+#define COUNT 77                    // number of samples to average
 
-        a = adc.read_u16();         // take median of 3 values to filter noise
-        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
+    for (int i = 0; i < COUNT; ++i)     // average multiple for more accuracy
+        sum += median(adc.read_u16(),adc.read_u16(),adc.read_u16());
 
     return sum / COUNT;
- 
-}  // read_temp()
+
+}  // read_ad()
 
 
 // read a temperature in A/D counts
@@ -450,27 +519,207 @@
 
 unsigned read_temp(int device)
 {
-unsigned value;
-unsigned max;       // A/D reading for the vref supply voltage
+    unsigned value;
+    unsigned max;       // A/D reading for the vref supply voltage
 
-   // send power to analog resistors only when needed
-   // this limits self heating
-  
+    // send power to analog resistors only when needed
+    // this limits self heating
+
     ad_power = 1;             // turn on supply voltage
     max = read_ad(vref);      // read supply voltage
-     
-    if (device == BOILER)  
-       value = (read_ad(boiler) * 65536) / max;     // scale to vref
+
+    if (device == BOILER)
+        value = (read_ad(boiler) * 65536) / max;     // scale to vref
     else
-       value = (read_ad(group) * 65536) / max;      // scale to vref
-    
+        value = (read_ad(group) * 65536) / max;      // scale to vref
+
     ad_power = 0;
 
     return value;
-} // read_temp()      
-     
+} // read_temp()
 
 
+// scale
+#define FILTER .99
+#define SLEW 10
+
+// average scale value over 800 msec
+// approx 6.6 samples/msec
+
+int read_scale2()
+{
+    int sum=0, count=0;
+    int raw, prev_raw;
+
+    Timer t;
+    t.start();
+    prev_raw = scale.read_u16();
+
+    scount = 0;
+
+    for (count = 0; t.read_ms() < 800; ++count) {
+        raw = scale.read_u16();
+
+        slog[scount] = raw;            // log it
+        if (++scount >= 5000)
+            scount = 5000;
+
+        // clip to slew rate limits
+        if (raw > prev_raw + SLEW)
+            raw = prev_raw + SLEW;
+        else if (raw < prev_raw - SLEW)
+            raw = prev_raw - SLEW;
+
+        prev_raw = raw;
+
+        sum += raw;
+    } // for
+
+    return sum / count;
+}
+
+// take average of scale A/D max and min over multiple inflection points
+// this reduces oscillation noise
+
+int read_scale()
+{
+    int value, prev_value=0, prev_prev_value, max1, max2, min;
+    unsigned tmp;
+    scount = 0;
+
+    Timer t;
+    Timer total;
+    t.start();
+    total.start();
+
+    // note: the effectiveness of this HF noise filter is highly dependent on sample rate
+
+#define update_value() {\
+       tmp = scale.read_u16(); \
+       slog[scount++] = tmp; \
+       value = FILTER * value + (1.0-FILTER) * tmp;  \
+       prev_prev_value = prev_value; \
+       prev_value = value; }
+
+    // get a good filtered value
+    value = scale.read_u16();
+    for (int i = 0; i < 50; ++i)
+        update_value();
+
+    // wait for upward slope
+    while (value < prev_value || prev_value < prev_prev_value)
+        update_value();
+
+    // delay for 2 msec
+    for (t.reset(); t.read_ms()< 2;)
+        update_value();
+
+    // find local max
+    for (;;) {
+        value = FILTER * value + (1.0-FILTER) * scale.read_u16();   // IIR filter
+        if (prev_value > value && prev_value > prev_prev_value) {
+            max1 = prev_value;
+            break;
+        }
+        prev_prev_value = prev_value;
+        prev_value = value;
+    } // for
+
+    // delay for 2 msec
+    for (t.reset(); t.read_ms()< 2;)
+        update_value();
+
+    // find local min
+    for (;;) {
+        value = FILTER * value + (1.0-FILTER) * scale.read_u16();   // IIR filter
+        if (prev_value < value && prev_value < prev_prev_value) {
+            min = prev_value;
+            break;
+        }
+        prev_prev_value = prev_value;
+        prev_value = value;
+    } // for
+
+    // delay for 2 msec
+    for (t.reset(); t.read_ms()< 2;)
+        update_value();
+
+    // find another max
+    for (;;) {
+        value = FILTER * value + (1.0-FILTER) * scale.read_u16();   // IIR filter
+        if (prev_value > value && prev_value > prev_prev_value) {
+            max2 = prev_value;
+            break;
+        }
+        prev_prev_value = prev_value;
+        prev_value = value;
+    }  // for
+
+    //debug("read scale in %d msec, %d %d %d\r\n",total.read_ms(),max1, max2, min);
+
+    return (((max1 + max2) / 2) + min) / 2;
+} // read_scale()
+
+int read_scale3()
+{
+    int max=0, min=65535;
+    int value, prev_raw;
+
+    scount = 0;
+
+// note: the effectiveness of this HF noise filter is highly dependent on sample rate
+// about 6.6 samples/msec
+
+    Timer t;
+    t.start();
+
+    prev_raw = value = scale.read_u16();
+
+    while (t.read_ms() < 40) {       // 25 msec should always include a full oscillation
+        int raw = scale.read_u16();
+        slog[scount++] = raw;
+
+        // clip to slew rate limits
+        if (raw > prev_raw + SLEW)
+            raw = prev_raw + SLEW;
+        else if (raw < prev_raw - SLEW)
+            raw = prev_raw - SLEW;
+
+        prev_raw = raw;
+
+        value = FILTER * value + (1.0-FILTER) * raw;
+
+        if (scount > 50) {   // only start after enough data
+            if (value > max)
+                max = value;
+            if (value < min)
+                min = value;
+        } // if
+    } // while
+
+    //debug("%d values, %d %d\r\n",scount,max,min);
+    return (max + min) / 2;
+
+} // read_scale3()
 
 
+// flow meter sends a pulse every .5 ml of flow
 
+InterruptIn flow_meter(PTA4);  // digital input pin
+Timer flow_timer;
+int flow_period;        // time between pulses in usec
+
+void flow_pulse()      // interrupt routine
+{
+    static int prev_time = 0;
+    int pulse_time = flow_timer.read_us();
+
+    flow_period = pulse_time - prev_time;
+    prev_time = pulse_time;
+}
+
+void flow_setup()
+{
+    flow_timer.start();
+    flow_meter.rise(&flow_pulse);  // attach the address of the flip function to the rising edge
+}  // flow_setup()