LCD becoming garbled when turning rotary encoder

I am trying to piece together something to control how much water to fill a container with using a rotary encoder, water solenoid, and flow meter. The problem I am having right now is the LCD gets garbled messages if I turn the rotary encoder with the flow meter code on. If I comment out the flow meter code everything else works correctly and the display doesn’t get messed up.

Here is my code:

/*
Arduino Connections:

*** LCD ***
- 1  (Gnd) to GND
- 2  (Vcc) to +5V
- 3  (VO) to Potentiometer Center (Wiper)
- 4  (RS) to digital pin 7
- 5  (R/W) to ground
- 6  (Enable) pin to digital pin 8
- 11 (D4) to digital pin 9
- 12 (D5) to digital pin 10
- 13 (D6) to digital pin 11
- 14 (D7) to digital pin 12
- 10K Potentiometer one end to +5V one to GND

*** Rotary Encoder ***
- 1 (B) to Digital Pin 2 (YEL)
- 2 (Common) to GND (BLK)
- 3 (A) to Digital Pin 4 (GRN)
- 5 (SwitchA) to Ground
- 6 (SwitchB) to A0

*** Flow Meter ***
- Yellow (Signal) to Digital Pin 3 (Interrupt)
- Black to GND
- Red to +5V
 */

// 20x4 LCD Setup
#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

// Encoder Pins and default value setup
#define encoder0PinA  2
#define encoder0PinB  4
#define encoderInterrupt 0
volatile float encoder0Pos = 0;

// Button Varibles
const int buttonPin = A1;     // the number of the pushbutton pin
 
const int BUTTON1 = 1; // Rotary Encoder - 67 ohms = Set Value
const int BUTTON2 = 2; // Red 1 - 384 ohms = Reset Desired and Set values
const int BUTTON3 = 3; // Red 2 - 4.8K = Add 2.5 gallons to Desired
const int BUTTON4 = 4; // Red 3 - 5.5k (Larger variance would be better) = Reset Total Amount/
 
const int BUTTON1LOW = 1012;  // Tolerance is +/- 4 of what I was reading
const int BUTTON1HIGH = 1020;
const int BUTTON2LOW = 982;
const int BUTTON2HIGH = 990;
const int BUTTON3LOW = 686;
const int BUTTON3HIGH = 694;
const int BUTTON4LOW = 655;
const int BUTTON4HIGH = 663;

  // Variables will change:
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin

 // the following variables are long's because the time, measured in miliseconds,
 // will quickly become a bigger number than can be stored in an int.
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers
 
// Water values
volatile float desiredFillAmount = 0; // Default value for how much to fill the kettles

// Water flow meter
byte sensorInterrupt = 1;  // 0 = pin 2; 1 = pin 3
byte sensorPin       = 3;

volatile byte pulseCount; 
unsigned long oldTime;

// The hall-effect flow sensor outputs approximately 4.5 pulses per second per
// liter/minute of flow.
float calibrationFactor = 4.5;

float flowRate;
unsigned int flowMilliLitres;
unsigned long totalMilliLitresA;
unsigned long totalMilliLitresB;

float flowRateGallons;
float flowGallons;
float totalGallons;
float literToGallons = 3.78541; // Liters in a gallon


void setup() { 
  // LCD Setup / Default Displays
  lcd.begin(20, 4);
  lcd.setCursor(0,0);
  lcd.print("Desired: 0.00   Gal");
  lcd.setCursor(0,1);
  lcd.print("    Set: 0.00   Gal");
  lcd.setCursor(0,2);
  lcd.print("  Total: 0.00   Gal"); 
  lcd.setCursor(0,3);
  lcd.print(" Rate: 0.00   G/min");
  
  // Encoder setup
  pinMode(encoder0PinA, INPUT); 
  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
  pinMode(encoder0PinB, INPUT); 
  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor
  attachInterrupt(encoderInterrupt, doEncoderA, CHANGE);  // encoder pin on interrupt 0 - pin 2

  // Button Setup
   pinMode(buttonPin, INPUT);

  // Flow Meter setup
  pinMode(sensorPin, INPUT);
  digitalWrite(sensorPin, HIGH);

  pulseCount        = 0;
  flowRate          = 0.0;
  oldTime           = 0;
  
  flowRateGallons  = 0.0;
  flowGallons      = 0;
  totalGallons     = 0;
  
  // The Hall-effect sensor is connected to pin 3 which uses interrupt 1.
  // Configured to trigger on a FALLING state change (transition from HIGH
  // state to LOW state)
  attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
}

void loop() {

// Start Button Code  
   int reading = analogRead(buttonPin);
   int tmpButtonState = LOW;             // the current reading from the input pin
   
   if(reading>BUTTON4LOW && reading<BUTTON4HIGH){
     tmpButtonState = BUTTON4; //Read switch 4
   }else if(reading>BUTTON3LOW && reading<BUTTON3HIGH){
     tmpButtonState = BUTTON3; //Read switch 3
   }else if(reading>BUTTON2LOW && reading<BUTTON2HIGH){
     tmpButtonState = BUTTON2; //Read switch 2
   }else if(reading>BUTTON1LOW && reading<BUTTON1HIGH){
     tmpButtonState = BUTTON1; //Read switch 1
   }else{
     tmpButtonState = LOW; //No button is pressed;
   }

   if (tmpButtonState != lastButtonState) {
     lastDebounceTime = millis();
   } 

   if ((millis() - lastDebounceTime) > debounceDelay) {
     buttonState = tmpButtonState;
   }
   lastButtonState = tmpButtonState;
   
   switch(buttonState){
     case BUTTON1: // Rotary Encoder (Set)
       desiredFillAmount = encoder0Pos; // Set the desired amount to what the encoder is at
       lcd.setCursor(13,1);
       lcd.print("   ");
       lcd.setCursor(9,1);
       lcd.print(desiredFillAmount);
     break;
     case BUTTON2: // Red 1 - Reset Desired & Set amount
       desiredFillAmount = 0; // Set the desired back at 0
       encoder0Pos = 0; // Reset encoder count
       lcd.setCursor(9,0);
       lcd.print(encoder0Pos);
       lcd.setCursor(9,1);
       lcd.print(desiredFillAmount);
     break;
     case BUTTON3: // Add 2.5 Gallons to the desired amount to help count faster
     // This doesn't work correctly and needs a delay or something so it only adds 2.5 per press..might just remove for now
       encoder0Pos += 2.5;
       lcd.setCursor(9,0);
       lcd.print(encoder0Pos);
     break;
     case BUTTON4: // Reset current amount to 0
       totalGallons = 0;
       lcd.setCursor(9,2);
       lcd.print(totalGallons);
     break;
   }
// End Button Code

// Start Flow Meter Code

  if((millis() - oldTime) > 1000)    // Only process counters once per second
  { 
    // Disable the interrupt while calculating flow rate and sending the value to
    // the host
    detachInterrupt(sensorInterrupt);
    
    // Because this loop may not complete in exactly 1 second intervals we calculate
    // the number of milliseconds that have passed since the last execution and use
    // that to scale the output. We also apply the calibrationFactor to scale the output
    // based on the number of pulses per second per units of measure (litres/minute in
    // this case) coming from the sensor.
    flowRate = ((1000.0 / (millis() - oldTime)) * pulseCount) / calibrationFactor;
    flowRateGallons = (flowRate / literToGallons);
    
    // Note the time this processing pass was executed. Note that because we've
    // disabled interrupts the millis() function won't actually be incrementing right
    // at this point, but it will still return the value it was set to just before
    // interrupts went away.
    oldTime = millis();
    
    // Reset the pulse counter so we can start incrementing again
    pulseCount = 0;
    
    // Enable the interrupt again now that we've finished sending output
    attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
    
    // Divide the flow rate in litres/minute by 60 to determine how many litres have
    // passed through the sensor in this 1 second interval, then multiply by 1000 to
    // convert to millilitres.
    flowMilliLitres = (flowRate / 60) * 1000;
    
    // Add the millilitres passed in this second to the cumulative total
    flowGallons = (flowRateGallons / 60) * literToGallons; 
    totalGallons += flowGallons;

//Output in gallons
    lcd.setCursor(9, 2);
    lcd.print(totalGallons / literToGallons);   // Total amount
    lcd.setCursor(7, 3);
    lcd.print(flowRateGallons);   // Current flow rate
    lcd.print(" ");

  }
  
// End Flow Meter Code
}



// Start encoder read function
void doEncoderA(){

  // look for a low-to-high on channel A
  if (digitalRead(encoder0PinA) == HIGH) { 
    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinB) == LOW) {  
      encoder0Pos = encoder0Pos + .004;         // CW
    } 
    else {
      encoder0Pos = encoder0Pos - .004;         // CCW
    }
  }
  else   // must be a high-to-low edge on channel A                                       
  { 
    // check channel B to see which way encoder is turning  
    if (digitalRead(encoder0PinB) == HIGH) {   
      encoder0Pos = encoder0Pos + .004;          // CW
    } 
    else {
      encoder0Pos = encoder0Pos - .004;          // CCW
    }
  }
  if(encoder0Pos < 0) {
    encoder0Pos = 0;
  }
  lcd.setCursor(13,0);
  lcd.print("  ");
  lcd.setCursor(9,0);
  lcd.print(encoder0Pos);         
}
// End encoder read function


// Start Flow Meter Function
void pulseCounter()
{
  // Increment the pulse counter
  pulseCount++;
}
// End Flow Meter function

For one thing you're not supposed to have print statements in an Interrupt Service Routine .

It just so happens that print statements in the Arduino library use an interrupt service routine and since the AVR doesn’t allow nested interrupts, any interrupt must wait until the current interrupt is finished before being serviced.

In short, one should never issue a print statement from within an interrupt service routine on the Arduino platform. This implementation was the root cause of a rather nasty hanging bug in Akiba’s ChibiArduino product, and it was a tricky one to track down.

A couple of pearls of wisdom to pass along:

Keep ISR implementations as short as possible. Do not perform expensive operations (eg, logging, printing, etc) within an ISR. If you find your Arduino code hanging randomly, print statements may be to blame. If you do use Arduino Serial.print(), set the baud rate as high as possible.

Thanks for the quick reply. It looks like that was the issue. I moved the print to the loop and it appears to be fixed. Still a ways to go cleaning code up and adding the solenoid but so far it appears to be working.

Also a random question of semantics. I can’t seem to decide the best wording for the display. Currently it is:
Desired: (Value the rotary encoder is at)
Set: (Value you have set)
Total: (Cumulative total that has went through the flow meter)
Rate (Current flow rate through flow meter)
Any opinions on clearer wording?

/*
Removed due to message length but is in original post
 */

// 20x4 LCD Setup
#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

// Encoder Pins and default value setup
#define encoder0PinA  2
#define encoder0PinB  4
#define encoderInterrupt 0
volatile float encoder0Pos = 0;

// Button Varibles
const int buttonPin = A1;     // the number of the pushbutton pin

const int BUTTON1 = 1; // Rotary Encoder - 67 ohms = Set Value
const int BUTTON2 = 2; // Red 1 - 384 ohms = Reset Desired and Set values
const int BUTTON3 = 3; // Red 2 - 4.8K = Add 2.5 gallons to Desired
const int BUTTON4 = 4; // Red 3 - 5.5k (Larger variance would be better) = Reset Total Amount/
const int BUTTON1LOW = 1012;  // Tolerance is +/- 4 of what I read
const int BUTTON1HIGH = 1020;
const int BUTTON2LOW = 982;
const int BUTTON2HIGH = 990;
const int BUTTON3LOW = 686;
const int BUTTON3HIGH = 694;
const int BUTTON4LOW = 655;
const int BUTTON4HIGH = 663;
int buttonState;
int lastButtonState = LOW;
 // the following variables are long's because the time, measured in miliseconds,
 // will quickly become a bigger number than can be stored in an int.
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers
unsigned long buttonTime;


// Water values
volatile float desiredFillAmount = 0; // Default value for how much to fill the kettles

// Water flow meter
byte sensorInterrupt = 1;  // 0 = pin 2; 1 = pin 3
byte sensorPin       = 3;

volatile byte pulseCount; 
unsigned long oldTime;

// The hall-effect flow sensor outputs approximately 4.5 pulses per second per
// liter/minute of flow.
float calibrationFactor = 4.5;

float flowRate;
float flowMilliLitres;
float flowRateGallons;
float flowGallons;
float totalGallons;
float literToGallons = 3.78541; // Liters in a gallon


void setup() { 
  // LCD Setup / Default Displays
  lcd.begin(20, 4);
  lcd.setCursor(0,0);
  lcd.print("Desired: 0.00   Gal");
  lcd.setCursor(0,1);
  lcd.print("    Set: 0.00   Gal");
  lcd.setCursor(0,2);
  lcd.print("  Total: 0.00   Gal"); 
  lcd.setCursor(0,3);
  lcd.print("Rate: 0.00  Gal/min");
  
  // Encoder setup
  pinMode(encoder0PinA, INPUT); 
  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
  pinMode(encoder0PinB, INPUT); 
  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor
  attachInterrupt(encoderInterrupt, doEncoderA, CHANGE);  // encoder pin on interrupt 0 - pin 2

  // Button Setup
  pinMode(buttonPin, INPUT);
  buttonTime = 0;
  
  // Flow Meter setup
  pinMode(sensorPin, INPUT);
  digitalWrite(sensorPin, HIGH);

  pulseCount        = 0;
  flowRate          = 0.0;
  oldTime           = 0;
  
  flowRateGallons  = 0.0;
  flowGallons      = 0;
  totalGallons     = 0;
  
  // The Hall-effect sensor is connected to pin 3 which uses interrupt 1.
  // Configured to trigger on a FALLING state change (transition from HIGH
  // state to LOW state)
  attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
}

void loop() {
// Start Rotary Encoder Code
  lcd.setCursor(13,0);
  lcd.print("  ");
  lcd.setCursor(9,0);
  lcd.print(encoder0Pos);    
// End Rotary Encoder Code

// Start Button Code  
   int reading = analogRead(buttonPin);
   int tmpButtonState = LOW;             // the current reading from the input pin
   
   if(reading>BUTTON4LOW && reading<BUTTON4HIGH){
     tmpButtonState = BUTTON4; //Read switch 4
   }else if(reading>BUTTON3LOW && reading<BUTTON3HIGH){
     tmpButtonState = BUTTON3; //Read switch 3
   }else if(reading>BUTTON2LOW && reading<BUTTON2HIGH){
     tmpButtonState = BUTTON2; //Read switch 2
   }else if(reading>BUTTON1LOW && reading<BUTTON1HIGH){
     tmpButtonState = BUTTON1; //Read switch 1
   }else{
     tmpButtonState = LOW; //No button is pressed;
   }

   if (tmpButtonState != lastButtonState) {
     lastDebounceTime = millis();
   } 

   if ((millis() - lastDebounceTime) > debounceDelay) {
     buttonState = tmpButtonState;
   }
   lastButtonState = tmpButtonState;
   
   switch(buttonState){
     case BUTTON1: // Rotary Encoder (Set)
       desiredFillAmount = encoder0Pos; // Set the desired amount to what the encoder is at
       lcd.setCursor(13,1);
       lcd.print("   ");
       lcd.setCursor(9,1);
       lcd.print(desiredFillAmount);
     break;
     case BUTTON2: // Red 1 - Reset Desired & Set amount
       desiredFillAmount = 0; // Set the desired back at 0
       encoder0Pos = 0; // Reset encoder count
       lcd.setCursor(9,0);
       lcd.print(encoder0Pos);
       if(encoder0Pos < 10) {
         lcd.print("  ");
       }
       lcd.setCursor(9,1);
       lcd.print(desiredFillAmount);
       if(encoder0Pos < 10) {
         lcd.print("  ");
       }
     break;
     case BUTTON3: // Add 1.25 Gallons to the desired amount to help count faster
       if((millis() - buttonTime) > 500) {   // Only process once per second
         encoder0Pos += 1.25;
         lcd.setCursor(9,0);
         lcd.print(encoder0Pos);
         if(encoder0Pos < 10) {
           lcd.print("  ");
         }
         buttonTime = millis();
       }
     break;
     case BUTTON4: // Reset current amount to 0
       totalGallons = 0;
       lcd.setCursor(9,2);
       lcd.print(totalGallons);
     break;
   }
// End Button Code

// Start Flow Meter Code
  if((millis() - oldTime) > 1000)    // Only process counters once per second
  { 
    // Disable the interrupt while calculating flow rate and sending the value to
    // the host
    detachInterrupt(sensorInterrupt);
    
    // Because this loop may not complete in exactly 1 second intervals we calculate
    // the number of milliseconds that have passed since the last execution and use
    // that to scale the output. We also apply the calibrationFactor to scale the output
    // based on the number of pulses per second per units of measure (litres/minute in
    // this case) coming from the sensor.
    flowRate = ((1000.0 / (millis() - oldTime)) * pulseCount) / calibrationFactor;
    flowRateGallons = (flowRate / literToGallons);
    
    // Note the time this processing pass was executed. Note that because we've
    // disabled interrupts the millis() function won't actually be incrementing right
    // at this point, but it will still return the value it was set to just before
    // interrupts went away.
    oldTime = millis();
    
    // Reset the pulse counter so we can start incrementing again
    pulseCount = 0;
    
    // Enable the interrupt again now that we've finished sending output
    attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
    
    // Divide the flow rate in litres/minute by 60 to determine how many litres have
    // passed through the sensor in this 1 second interval, then multiply by 
    // literToGallons convert to gallons
    // Add the gallons passed in this second to the cumulative total
    flowGallons = (flowRateGallons / 60) * literToGallons; 
    totalGallons += flowGallons;

    lcd.setCursor(9, 2);
    lcd.print(totalGallons / literToGallons);   // Total amount
    lcd.setCursor(6, 3);
    lcd.print(flowRateGallons);   // Current flow rate
    lcd.print(" ");
  }
  
// End Flow Meter Code
}



// Start encoder read function
void doEncoderA(){

  // look for a low-to-high on channel A
  if (digitalRead(encoder0PinA) == HIGH) { 
    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinB) == LOW) {  
      encoder0Pos += .004;         // CW
    } 
    else {
      encoder0Pos -= .004;         // CCW
    }
  }
  else   // must be a high-to-low edge on channel A                                       
  { 
    // check channel B to see which way encoder is turning  
    if (digitalRead(encoder0PinB) == HIGH) {   
      encoder0Pos += .004;          // CW
    } 
    else {
      encoder0Pos -= .004;          // CCW
    }
  }
  if(encoder0Pos < 0) {
    encoder0Pos = 0;
  }     
}
// End encoder read function

// Start Flow Meter Function
void pulseCounter()
{
  // Increment the pulse counter
  pulseCount++;
}
// End Flow Meter function

Your comments suggest you're disabling interrupts but in fact you're detaching and reattaching your handler. Disabling and re-enabling would make more sense.

You're doing an lcd print in the interrupt handler and also in the main context. That's a recipe for disaster since the lcd library is unlikely to be re-entrant. Do the lcd output in the main context only just have the interrupt handler save the required values to volatile variables which are read and displayed in the main context. Remember to disable interrupts while access the volatile variables in the main context.

I can only tell you what conventional practice is in the industry.

Desired: (Encoder Count) ==========>TARGETcount (Desired and Set seem to be the same thing , don't they? Set: (ENDCOUNT)================>SETPOINT Total: (Cumulative) ==============> RUNCOUNT (total is redundant. If it is cumulative it must be the total.) Rate ( self explanatory )============>FLOWRATE ("Current" is redundant . If it wasn't current it wouldn't be displayed). Any opinions on clearer wording? ^_^

feel free to click my Karma button if you found my input helpful....

I did a quick test run earlier to try it out but it appears it isn't counting correctly now. It never has been completely accurate but it was close enough to working correctly. Now it is jumping around and not going up/down as it should.

Would it work if I took it off an interrupt?

Edit: I found another rotary encoder method here I'm going to try and see if it works better.

Do you have any decoupling caps on your +5V bus ? (like 1, 10, 100, or 1000uF ?)

No I have no caps on there. Should I add them to some (all?) of the +5V lines?

I did get the rotary encoder method working from the website I linked in my last post. Everything seems to be working great now.

Here is the code in case it might help someone else out (probably as an example of what horrible code looks like):

/*
Had to remove again due to max length
*/

#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

// Rotary Encoder Setup
// Half-step mode?
#define HALF_STEP
// Arduino pins the encoder is attached to. Attach the center to ground.
#define ROTARY_PIN1 4
#define ROTARY_PIN2 5
// define to enable weak pullups.
#define ENABLE_PULLUPS

#define DIR_CCW 0x10
#define DIR_CW 0x20
volatile float encoderCount;

#ifdef HALF_STEP
// Use the half-step state table (emits a code at 00 and 11)
const unsigned char ttable[6][4] = {
  {0x3 , 0x2, 0x1,  0x0}, {0x23, 0x0, 0x1,  0x0},
  {0x13, 0x2, 0x0,  0x0}, {0x3 , 0x5, 0x4,  0x0},
  {0x3 , 0x3, 0x4, 0x10}, {0x3 , 0x5, 0x3, 0x20}
};
#else
// Use the full-step state table (emits a code at 00 only)
const unsigned char ttable[7][4] = {
  {0x0, 0x2, 0x4,  0x0}, {0x3, 0x0, 0x1, 0x10},
  {0x3, 0x2, 0x0,  0x0}, {0x3, 0x2, 0x1,  0x0},
  {0x6, 0x0, 0x4,  0x0}, {0x6, 0x5, 0x0, 0x10},
  {0x6, 0x5, 0x4,  0x0},
};
#endif
volatile unsigned char state = 0;

/* Call this once in setup(). */
void rotary_init() {
  pinMode(ROTARY_PIN1, INPUT);
  pinMode(ROTARY_PIN2, INPUT);
#ifdef ENABLE_PULLUPS
  digitalWrite(ROTARY_PIN1, HIGH);
  digitalWrite(ROTARY_PIN2, HIGH);
#endif
}

/* Read input pins and process for events. Call this either from a
 * loop or an interrupt (eg pin change or timer).
 *
 * Returns 0 on no event, otherwise 0x80 or 0x40 depending on the direction.
 */
unsigned char rotary_process() {
  unsigned char pinstate = (digitalRead(ROTARY_PIN2) << 1) | digitalRead(ROTARY_PIN1);
  state = ttable[state & 0xf][pinstate];
  return (state & 0x30);
}

// Button Varibles
const int buttonPin = A1;     // the number of the pushbutton pin
const int BUTTON1 = 1; // Rotary Encoder - 67 ohms = Set Value
const int BUTTON2 = 2; // Red 1 - 384 ohms = Reset Desired and Set values
const int BUTTON3 = 3; // Red 2 - 4.8K = Add 2.5 gallons to Desired
const int BUTTON4 = 4; // Red 3 - 5.5k (Larger variance would be better) = Reset Total Amount/
const int BUTTON1LOW = 1012;  // Tolerance is +/- 4 of what I read
const int BUTTON1HIGH = 1020;
const int BUTTON2LOW = 982;
const int BUTTON2HIGH = 990;
const int BUTTON3LOW = 686;
const int BUTTON3HIGH = 694;
const int BUTTON4LOW = 655;
const int BUTTON4HIGH = 663;
int buttonState;
int lastButtonState = LOW;
 // the following variables are long's because the time, measured in miliseconds,
 // will quickly become a bigger number than can be stored in an int.
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers
unsigned long buttonTime;

// Water values
volatile float desiredFillAmount = 0; // Default value for how much to fill the kettles

// Water flow meter
byte sensorInterrupt = 1;  // 0 = pin 2; 1 = pin 3
byte sensorPin       = 3;

volatile byte pulseCount; 
unsigned long oldTime;

// The hall-effect flow sensor outputs approximately 4.5 pulses per second per
// liter/minute of flow.
float calibrationFactor = 4.5;

float flowRate;
float flowMilliLitres;
float flowRateGallons;
float flowGallons;
float totalGallons;
float literToGallons = 3.78541; // Liters in a gallon

void setup() { 
  // LCD Setup / Default Displays
  lcd.begin(20, 4);
  lcd.setCursor(0,0);
  lcd.print("  TARGET: 0.00  GAL");
  lcd.setCursor(0,1);
  lcd.print("SETPOINT: 0.00  GAL");
  lcd.setCursor(0,2);
  lcd.print("   TOTAL: 0.00  GAL"); 
  lcd.setCursor(0,3);
  lcd.print("RATE: 0.00  GAL/MIN");
  
  // Encoder setup
  encoderCount = 0;
  rotary_init();
  
  // Button Setup
  pinMode(buttonPin, INPUT);
  buttonTime = 0;
  
  // Flow Meter setup
  pinMode(sensorPin, INPUT);
  digitalWrite(sensorPin, HIGH);

  pulseCount        = 0;
  flowRate          = 0.0;
  oldTime           = 0;
  
  flowRateGallons  = 0.0;
  flowGallons      = 0;
  totalGallons     = 0;
  
  // The Hall-effect sensor is connected to pin 3 which uses interrupt 1.
  // Configured to trigger on a FALLING state change (transition from HIGH
  // state to LOW state)
  attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
}

void loop() {
// Start Rotary Encoder Code
  unsigned char result = rotary_process();
  if (result) {
  if(result == 16) { encoderCount += .005; }
  if(result == 32) { encoderCount -= .005; }
  if(encoderCount < 0 ) { encoderCount = 0; } 
  lcd.setCursor(14,0);
  lcd.print(" ");
  lcd.setCursor(10,0);
  lcd.print(encoderCount);
  }
// End Rotary Encoder Code

// Start Button Code  
   int reading = analogRead(buttonPin);
   int tmpButtonState = LOW;             // the current reading from the input pin
   
   if(reading>BUTTON4LOW && reading<BUTTON4HIGH) {
     tmpButtonState = BUTTON4; //Read switch 4
   }else if(reading>BUTTON3LOW && reading<BUTTON3HIGH) {
     tmpButtonState = BUTTON3; //Read switch 3
   }else if(reading>BUTTON2LOW && reading<BUTTON2HIGH) {
     tmpButtonState = BUTTON2; //Read switch 2
   }else if(reading>BUTTON1LOW && reading<BUTTON1HIGH) {
     tmpButtonState = BUTTON1; //Read switch 1
   }else{
     tmpButtonState = LOW; //No button is pressed;
   }

   if (tmpButtonState != lastButtonState) {
     lastDebounceTime = millis();
   } 

   if ((millis() - lastDebounceTime) > debounceDelay) {
     buttonState = tmpButtonState;
   }
   lastButtonState = tmpButtonState;
   
   switch(buttonState){
     case BUTTON1: // Rotary Encoder (Set)
       desiredFillAmount = encoderCount; // Set the desired amount to what the encoder is at
       lcd.setCursor(14,1);
       lcd.print(" ");
       lcd.setCursor(10,1);
       lcd.print(desiredFillAmount);
     break;
     case BUTTON2: // Red 1 - Reset Desired & Set amount
       desiredFillAmount = 0; // Set the desired back at 0
       encoderCount = 0; // Reset encoder count
       lcd.setCursor(10,0);
       lcd.print(encoderCount);
       if(encoderCount < 10) {
         lcd.print("  ");
       }
       lcd.setCursor(10,1);
       lcd.print(desiredFillAmount);
       if(encoderCount < 10) {
         lcd.print("  ");
       }
     break;
     case BUTTON3: // Add 1.25 Gallons to the desired amount to help count faster
       if((millis() - buttonTime) > 500) {   // Count every .5 seconds
         encoderCount += 1.25;
         lcd.setCursor(10,0);
         lcd.print(encoderCount);
         if(encoderCount < 10) {
           lcd.print("  ");
         }
         buttonTime = millis();
       }
     break;
     case BUTTON4: // Reset current amount to 0
       totalGallons = 0;
       lcd.setCursor(10,2);
       lcd.print(totalGallons);
     break;
   }
// End Button Code

// Start Flow Meter Code
  if((millis() - oldTime) > 1000)    // Only process counters once per second
  { 
    // Disable the interrupt while calculating flow rate and sending the value to
    // the host
    detachInterrupt(sensorInterrupt);
    
    // Because this loop may not complete in exactly 1 second intervals we calculate
    // the number of milliseconds that have passed since the last execution and use
    // that to scale the output. We also apply the calibrationFactor to scale the output
    // based on the number of pulses per second per units of measure (litres/minute in
    // this case) coming from the sensor.
    flowRate = ((1000.0 / (millis() - oldTime)) * pulseCount) / calibrationFactor;
    flowRateGallons = (flowRate / literToGallons);
    
    // Note the time this processing pass was executed. Note that because we've
    // disabled interrupts the millis() function won't actually be incrementing right
    // at this point, but it will still return the value it was set to just before
    // interrupts went away.
    oldTime = millis();
    
    // Reset the pulse counter so we can start incrementing again
    pulseCount = 0;
    
    // Enable the interrupt again now that we've finished sending output
    attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
    
    // Divide the flow rate in litres/minute by 60 to determine how many litres have
    // passed through the sensor in this 1 second interval, then multiply by 
    // literToGallons convert to gallons
    // Add the gallons passed in this second to the cumulative total
    flowGallons = (flowRateGallons / 60) * literToGallons; 
    totalGallons += flowGallons;

    lcd.setCursor(10, 2);
    lcd.print(totalGallons / literToGallons);   // Total amount
    lcd.setCursor(6, 3);
    lcd.print(flowRateGallons);   // Current flow rate
    lcd.print(" ");
  }
  
// End Flow Meter Code
}

// Start encoder read function

// End encoder read function

// Start Flow Meter Function
void pulseCounter()
{
  pulseCount++;
}
// End Flow Meter function