Pages: [1]   Go Down
Author Topic: Coffee grinder timer  (Read 1174 times)
0 Members and 1 Guest are viewing this topic.
San Francisco
Offline Offline
Jr. Member
**
Karma: 0
Posts: 92
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi folks,

My coffee grinder timer project is done and I thought I'd post some info about it.

First, some background on the motivation.  When making espresso, to keep things fresh and consistent, people generally grind the coffee right before "pulling a shot," and try to use the same amount of coffee grounds every time.  A good coffee grinder should output grounds at a consistent rate, so a precisely timed grind can produces a consistent quantity of ground coffee.  28 seconds works for me for most roasts I get, whether Caffe Trieste, Blue Bottle, or Graffeo.

The timer mounts to the left side of the coffee grinder with Velcro tape.  The back has a power input socket and main switch, a controlled output outlet (for the grinder, turned "on"), the USB port and *duino power switch.  On the front, a scroll wheel adjusts a displayed time, and two buttons can trigger a timed grind (countdown) or free run (count up).

The buttons light up when the grinder is triggered, depending on which was clicked, and the display shows a "chase" animation cycle while it counts up or down.

This started as a learning project to figure out techniques I need for a bunch of projects, in no particular order:
* More complicated Arduino programming, esp. modes and fast execution-- loop() here runs at ~2500Hz, and the program has all the "polish" I could think of,
* EAGLE CAD-- I know enough now to be dangerous,
* Homemade PCB fabrication-- won't solder more than three components to perf/strip/protoboard ever again,
* How to "case up" and really finish a project-- still not sure how, but at least I finished this one, and nobody has to know there's a breadboard inside...smiley

This uses an iDuino from Fundamental Logic, chosen for its built-in USB and breadboard factor.  The iDuino can be powered from an internal DC source or from its USB port, depending on the switch on the rear.

I originally wanted to make a single board that the iDuino would slot into, but I couldn't figure out the layout, and it worked well separating the circuit into three sections: control, relay, and *duino.  I put female headers on the relay and control boards, with the iDuino on a little breadboard so it will be easy to change things around later.


Logged

My Arduino blog: http://jmsarduino.blogspot.com
Comprehensive (?) Arduino-compatible board list: http://tinyurl.com/allarduinos

San Francisco
Offline Offline
Jr. Member
**
Karma: 0
Posts: 92
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

/* Grinder Timer V2
 
 Replacement of my first grinder timer.
 Uses a scroll wheel to select time, a 2-digit display, and buttons for triggering timed countdown
 or free count up.
 
 by Jeff Saltzman
 
 Interface with a scroll controller from a Panasonic VCR remote and a 2-digit 7-segment LED display from a Sony DigiBeta deck.
 This will become the next version of the coffee grinder timer with the addition of two buttons and an output to a relay or triac
 
 v.1: 11/12/2008:  Just starting:  first task:  count
   second task:  mess w/LED... that'll eat a lot of pins...:o
 11/15/2008:  Counting 0-99 on the display!  Will keep code verbose for now
   Got the scroller working-- simple!  Adding different modes...
 11/22/2008:  Get anlog pin to switch multiple inputs
   Build complete countDown program
   Program enhancements:
   Chase and time remaining display during countDown
   No scroll during override
   Max override time
   Flash max time when max time reached
   Flash ct when countDown complete
   Do not show leading 0 when count time is single digits
   Chase and time elapsed display during override                    
   Lit buttons for countDown grinding versus override
   Store delay in EEPROM so it will have the same count as last time                    
 11/22/2008:  Test with triac output: shuffle pins go use 13 for the trigger
 11/30/2008:    Fixing code thanks to suggestions in the forum:
   Set multiPin's pullup resistor so the "extra" resistor is not needed per http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1226896251/3#3
 12/13/2008:  Major revision: use shift registers for displaying the digits
 12/14/2008:  Add proper third override mode: dial, count down, override (count up)
   Check button lights w/logic of registers: add 128 to byte for button light
   Check multi-button input: cancel mode when hitting other button
 12/30/2008:  Build display+button board
   Refine display+button board EAGLE layout
   Etch PCB, clean PCB, drill PCB
   Build board
 12/31/2008:  Troubleshoot board and program
   Infinite looping when counting down from >32s
   HW Testing:
     Segment 1D not lit
     Chase animation incorrect
     Button 2 lights?
     Button 1 accidentally triggers
     No button 2 trigger
 1/1/2009:  Build relay circuit onto outlet
   Capacitor squeal-- replace
   Replace M with F headers on display board
   Build power circuit-- swich & reg >> BB
   Print cutting guides
   Cut box
   Make standoffs for control board
   Mount everything
   * FINAL *
 1/2/2009:  GRIND ME SOME COFFEE!


 All code released under
 Creative Commons Attribution-Noncommercial-Share Alike 3.0

*/

#include <EEPROM.h>

// Shift register control pins
int latchPin = 8;
int clockPin = 12;
int dataPin = 11;

// Input button pins
int btn1Pin = 6;
int btn2Pin = 5;
int btnUpPin = 4;
int btnDnPin = 3;

// Button states
int btn1Val = 0;                   // Value of button 1
int btn2Val = 0;                   // Value of button 2
int upVal = 0;                     // Value of up scroller
int dnVal = 0;                     // Value of down scroller

int btn1Last = HIGH;               // Last value for button 1
int btn2Last = HIGH;               // Last value for button 2
int upLast = 0;                    // Last value for scroll up
int dnLast = 0;                    // Last value for scroll down

// Trigger pin
int triggerPin = 2;                // Pin to trigger output relay
int triggerVal = 0;                // Value of the trigger pin

// Vars for debouncing
long time1 = 0;                    // The last time button 1 was toggled
long time2 = 0;                    // The last time button 2 was toggled
long timeUp = 0;                   // The last time up button was toggled
long timeDn = 0;                   // The last time down button was toggled
long debounce = 50;                // The debounce time for the buttons
long chaseTime = 0;                // Time of last chase step

// Timing the grind...
long grindStartTime = 0;           // The time the countDown or override started
long grindTimeMax = 60;            // Longest the grinder can run
int grindTimeMin = 3;             // Shortest duration for a timed grind

// Program vars
int mode = 0;                      // 0: dialing, 1: counting down, 2: counting up, 3: setup
long ct = 28;                       // Count value: the desired time
int ctRead = 0;                    // Count value read from the EEPROM
long longClick = 3000;              // ms duration to click and hold button 1 for setup mode
int chaseDelay = 42;               // ms duration of each chase segment

// Program status vars
int countDown = 0;                 // Countdown value
int countUp = 0;                   // Count up value
boolean setupMax = false;              // Whether setting max time or not (not == setting min)
int chaseVal = 0;                  // 0-7: chase position

int i;                             // For keeping track of things
int j;                             // For keeping track of...

long freqCounter = 0;               // Timing test counter
long freqTime = 0;                 // Last time frequency was reported

// Data for digits 0-8 to shift gfedcba: 7 BITS ONLY
//byte digs[] = {
//  B0111111, B0000110, B1011011, B1001111, B1100110, B1101101, B1111101, B0000111, B1111111, B1101111 };
// old:  gfedcba
// new1: cedgafb
// new1: bfagdec
byte digs1[] = {
  B1110111, B1000001, B0111101, B1011101, B1001011, B1011110, B1111110, B1000101, B1111111, B1011111 };
byte digs2[] = {
  B1110111, B1000001, B1011110, B1011101, B1101001, B0111101, B0111111, B1010001, B1111111, B1111101 };

// Chase pattern, 2-segments clockwise
byte ch1[] =  {
  B1000001, B1010000, B0010000, B0000000, B0000000, B0000000, B0000100, B0000101 };
byte ch2[] =  {
  B0000000, B0000000, B0000100, B0000110, B0100010, B0110000, B0100000, B0000000 };  

// Data to display
byte D1;            // First digit to display
byte D2;            // Second digit to display

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-


void setup() {

  Serial.begin(9600);

  // Set trigger output pin
  pinMode(triggerPin, OUTPUT);
  digitalWrite(triggerPin, LOW);
  
  // Set shift register output pins
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  // Set input buttons
  pinMode(btn1Pin, INPUT);
  pinMode(btn2Pin, INPUT);
  pinMode(btnUpPin, INPUT);
  pinMode(btnDnPin, INPUT);

  // Turn on pullup resistors for the pins    
  digitalWrite(btn1Pin, HIGH);
  digitalWrite(btn2Pin, HIGH);
  digitalWrite(btnUpPin, HIGH);
  digitalWrite(btnDnPin, HIGH);

  // Get ct from the EEPROM
  ctRead = EEPROM.read(0);
  delay(100);
  ct = ctRead;

}


//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-


void loop() {

  // Frequency counter: how many times/sec does the loop run?
  freqCounter += 1;
  if ((millis()-freqTime) > 5000) {
    Serial.print(freqCounter/5);
    Serial.println(" Hz");
    freqCounter = 0;
    freqTime = millis();
  }
    
  // Read control button inputs
  btn1Val = digitalRead(btn1Pin);
  btn2Val = digitalRead(btn2Pin);
  upVal = digitalRead(btnUpPin);
  dnVal = digitalRead(btnDnPin);

  // Debounce and process button 1:  countdown OR setup
  if (btn1Val == 1 && btn1Last == 0 && millis() - time1 > debounce) {
    if (mode == 0) {            // Button 1 switching from mode 0 (dialing) to mode 1 (timed grind)
      Serial.println("B1:  Mode 1: countdown");
      mode = 1;
      runGrinder(true);
      grindStartTime = millis();
      EEPROM.write(0, int(ct));      // Write ct to the EEPROM
      delay(20);
    }
    else if (mode == 1 || mode == 2) {       // Button 1 switching from modes 1 or 2 to mode 0 (dialing)
      Serial.println("B1:  Mode 0: wait");
      mode = 0;
      runGrinder(false);
    }
    time1 = millis();
  }
  btn1Last = btn1Val;

  // Debounce and process button 2:  override
  if (btn2Val == 1 && btn2Last == 0 && millis() - time2 > debounce) {
    if (mode == 0) {            // Button 2 switching from mode 0 (dialing) to mode 2 (count up/override)
      Serial.println("B2:  Mode 2: count up/override");
      mode = 2;
      runGrinder(true);
      grindStartTime = millis();
    }
    else if (mode == 2 || mode == 1) {      // Button 2 switching off the relay and not chasing
      Serial.println("B2:  Mode 0");
      mode = 0;
      runGrinder(false);
    }
    time2 = millis();
  }
  btn2Last = btn2Val;


  //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  //-=-= MODES =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

  if (mode == 0) {
    // Debounce and process up and down buttons:  scroll
    if (upVal == 1 && upLast == 0 && millis() - timeUp > debounce) {
      if ((millis()-timeUp) < 120) ct+=2;
      else ct+=1;
      Serial.print("UP: ");Serial.print(ct);Serial.print("  (");Serial.print(millis()-timeUp);Serial.println(")");
      timeUp = millis();
    }
    upLast = upVal;

    if (dnVal == 1 && dnLast == 0 && millis() - timeDn > debounce) {
      if ((millis()-timeDn) < 120) ct-=2;
      else ct-=1;
      Serial.print("DN: ");Serial.print(ct);Serial.print("  (");Serial.print(millis()-timeDn);Serial.println(")");
      timeDn = millis();
    }
    dnLast = dnVal;

Logged

My Arduino blog: http://jmsarduino.blogspot.com
Comprehensive (?) Arduino-compatible board list: http://tinyurl.com/allarduinos

San Francisco
Offline Offline
Jr. Member
**
Karma: 0
Posts: 92
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


    ct = max(min(ct, grindTimeMax), grindTimeMin);            // Limit count value
    // Show the chosen count
    displayNumber(ct, 0, 0);
  }

  // Countdown mode
  else if (mode == 1) {
    if ( (millis() - grindStartTime) <  ct*1000 ) {
      countDown = ct - int( (millis() - grindStartTime)/1000 ) ;
      if ( ((millis() - grindStartTime) - ((ct-countDown)*1000)) > 500 ) {
        displayChase(chaseVal, 1, 0);
        if ( millis() - chaseTime > chaseDelay ) {
          chaseVal++;
          if (chaseVal == smiley-cool chaseVal = 0;
          chaseTime = millis();
        }
      }
      else {
        displayNumber(countDown, 1, 0);
        chaseVal = 3;
      }
    }
    // Stop running if countdown gets to 0
    else {                        
      Serial.println("Countdown done");
      mode = 0;
      runGrinder(false);
      countDown = ct;
      // Flash that count time has been reached
      for (j=0; j<3; j++) {
        clearDisplay();
        delay(150);
        displayNumber(ct, 1, 0);
        delay(300);
      }
    }
  }

  // Countup mode
  else if (mode == 2) {
    // Display chase and the override duration
    countUp = int((millis()-grindStartTime)/1000);
    // Display chase if it's not time to show the countup time      
    if ( ((millis() - grindStartTime) - long(countUp)*1000) < 800 ) {
      displayChase(chaseVal, 0, 1);
      if ( millis() - chaseTime > chaseDelay ) {
        chaseVal++;
        if (chaseVal == smiley-cool chaseVal = 0;
        chaseTime = millis();
      }
    }
    else {
      displayNumber(1+countUp, 0, 1);
      chaseVal = 3;
    }
    // Stop grinding if max time is reached
    if ( (millis() - grindStartTime ) > grindTimeMax*1000 ) {
      mode = 0;
      runGrinder(false);
      // Flash that max time has been reached
      for (j=0; j<6; j++) {
        clearDisplay();
        delay(150);
        displayNumber(grindTimeMax, 0, 1);
        delay(300);
      }
    }
  }
  
  // Setup min/max mode
  else if (mode == 3) {
    // Debounce and process up and down buttons:  scroll

    if (upVal == 1 && upLast == 0 && millis() - timeUp > debounce) {
      if (setupMax == false) grindTimeMin = max(2, grindTimeMin+1);
      else grindTimeMax = min(99, grindTimeMax+1);
      timeUp = millis();
    }
    upLast = upVal;
    if (dnVal == 1 && dnLast == 0 && millis() - timeDn > debounce) {
      if (setupMax == false) grindTimeMin = max(2, grindTimeMin-1);
      else grindTimeMax = max(grindTimeMin, min(99, grindTimeMax-1));
      timeDn = millis();
    }
    dnLast = dnVal;
    // Show the chosen count
    if (setupMax == false) displayNumber(grindTimeMin, 1, 1);
    else displayNumber(grindTimeMax, 1, 1);
  }

}  


//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
//-=-=-=- Program functions -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-


// Turn the grinder/power on or off
void runGrinder(boolean grinderState) {
  digitalWrite(triggerPin, grinderState);
}


// Write two digits to the 14-segment display
void displayNumber(int number, boolean btn1, boolean btn2) {
  number = max(0, min(number, 99));
  if (number < 10) D1 = 0;
  else D1 = digs2[number/10];   // First digit: tens
  D1 = D1 << 1;
  D2 = digs1[number%10];        // Second digit: ones
  // Flip extra bits to turn on button LEDs
  if (btn1) D1=D1+B00000001;
  if (btn2) D2=D2+B10000000;
  // Write the bytes
  digitalWrite(latchPin, LOW);  // Unlatch the shift registers
  shiftOut(D1);                 // Send first digit to 595
  shiftOut(D2);                 // Send second digit to 595: first gets shifted left
  digitalWrite(latchPin, HIGH); // Re-latch the shift registers
}


// Display a frame of the chase cycle
void displayChase(int x, boolean btn1, boolean btn2) {
  x = max(0, min(x, 7));
  D1 = ch2
  • ;         // First digit
 D1 = D1 << 1;
  D2 = ch1
  • ;         // Second digit
 // Flip extra bits to turn on button LEDs
  if (btn1) D1=D1+B00000001;
  if (btn2) D2=D2+B10000000;
  // Write the bytes
  digitalWrite(latchPin, LOW);  // Unlatch the shift registers
  shiftOut(D1);                 // Send first digit to 595
  shiftOut(D2);                 // Send second digit to 595: first gets shifted left  
  digitalWrite(latchPin, HIGH); // Re-latch the shift registers
}


// Clear the display: turn off all segments
void clearDisplay() {
  digitalWrite(latchPin, LOW);  // Unlatch the shift registers
  shiftOut(0);  shiftOut(0);
  digitalWrite(latchPin, HIGH); // Re-latch the shift registers
}


// Write a byte to the shift register
void shiftOut(byte d) {
  digitalWrite(dataPin, LOW);              
  for (int i=7; i>=0; i--)  {                  // Step through the bits
    digitalWrite(clockPin, LOW);
    digitalWrite(dataPin, (d & (1<<i)) );  // Pin HIGH if the bit matches the byte's bit
    digitalWrite(clockPin, HIGH);
    digitalWrite(dataPin, LOW);
  }
  digitalWrite(clockPin, LOW);
}
Logged

My Arduino blog: http://jmsarduino.blogspot.com
Comprehensive (?) Arduino-compatible board list: http://tinyurl.com/allarduinos

Bristol, UK
Offline Offline
Edison Member
*
Karma: 0
Posts: 1197
Exhibitor at UK Maker Faire
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Good stuff!  Better coffee through the application of Arduino!
Logged

Pages: [1]   Go Up
Jump to: