Pump Control State Machine w/LCD Button Shield [solved]

Hey guys!
So I finally got my LCD&button shield to power a 12V pump when I press a button, but only if I hold the button down.
I’m having trouble getting it to continue running. I think this is because I need to be able to read the “right” button on my LCD shield in order to run the state of RUNNING continuously. That way, if the button was pressed, and it’s state is HIGH, I can tell it to keep in the RUNNING state, unless I press the “left” button.

The reason I have a digitalWrite(transOutPin, HIGH) and digitalWrite(transOutPin, LOW) right after eachother is so that I can control the rate of the flow from the pump by turning it on and off quickly, and use the “up” and “down” buttons to change the relative time of on/off to achieve different flow rates.

Basically, I’m having trouble keeping this loop going, because when it gets to the digitalWrite(transOutPin, LOW) the pump turns off.

Any ideas?

/*********************

  Example code for the Adafruit RGB Character LCD Shield and Library

  This code displays text on the shield, and also reads the buttons on the keypad.
  When a button is pressed, the backlight changes color.

**********************/

// include the library code:
#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>


// The shield uses the I2C SCL and SDA pins. On classic Arduinos
// this is Analog 4 and 5 so you can't use those for analogRead() anymore
// However, you can connect other I2C sensors to the I2C bus and share
// the I2C bus.
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

// These #defines make it easy to set the backlight color
#define RED 0x1  //makes blue LCD backlight turn on
int transOutPin = 5;       // the number of the output pin that controls TIP120 transistor
enum state {
  SPLASH_SCREEN,
  WAITING_FOR_SELECTION,
  RUNNING
};

state current_state;
int PUMP_OFF_TIME_MS = 1; // UI doesn't change this
int PUMP_ON_TIME_MS = 100; // Default flow rate
int MAX_PUMP_ON_TIME_MS = 10000;
int MIN_PUMP_ON_TIME_MS = 100;
int PUMP_ON_TIME_STEP_SIZE = 100; // Go up/down 25ms at a time

void setup() {
  // Debugging output
  Serial.begin(9600);
  // set up the LCD's number of columns and rows:
  // Print a message to the LCD. We track how long it takes since
  // this library has been optimized a bit and we're proud of it :)
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("Welcome to the");
  lcd.setCursor(0, 1);
  lcd.print("NVC Suite v2.0");
  pinMode(transOutPin, OUTPUT);
}
uint8_t i = 0;


void loop() {

  if (millis() < 3000)
  {
    // do nothing
  }
  else
  {
    if (current_state == SPLASH_SCREEN)
    {
      // Switch to user selection state
      current_state = WAITING_FOR_SELECTION;
      lcd.clear();
    }
    else if (current_state == WAITING_FOR_SELECTION)
    {
      // Display current speed setting
      String message = "Set pump speed: ";
      lcd.setCursor(0, 0);
      lcd.print(message);
      message = (String)PUMP_ON_TIME_MS + " ms";
      lcd.setCursor(0, 1);
      lcd.print(message);

      uint8_t buttons = lcd.readButtons();

      if (buttons) {
        lcd.clear();
        lcd.setCursor(0, 0);
        if (buttons & BUTTON_UP) {
          Serial.println("User pressed up");
          // Increment speed
          PUMP_ON_TIME_MS += PUMP_ON_TIME_STEP_SIZE;

          // Make sure we don't go over the maximum
          if (PUMP_ON_TIME_MS > MAX_PUMP_ON_TIME_MS)
          {
            PUMP_ON_TIME_MS = MAX_PUMP_ON_TIME_MS;
          }

        }
        else if (buttons & BUTTON_DOWN) {
          Serial.println("User pressed down");
          // Decrement speed
          PUMP_ON_TIME_MS -= PUMP_ON_TIME_STEP_SIZE;

          // Make sure we don't go under the minimum
          if (PUMP_ON_TIME_MS < MIN_PUMP_ON_TIME_MS)
          {
            PUMP_ON_TIME_MS = MIN_PUMP_ON_TIME_MS;
          }
        }

        else if (buttons & BUTTON_SELECT) {
          current_state = RUNNING;
          Serial.println("User pressed select");
          lcd.print("Press RIGHT to");
          lcd.setCursor(0, 1);
          lcd.print("to begin coating");

        }
      }
    }

    else if (current_state == RUNNING) {
      uint8_t buttons = lcd.readButtons();
      if (buttons) {

        if (buttons & BUTTON_RIGHT) {


          // Display running text
          lcd.clear();
          lcd.setCursor(0, 0);
          String message = "Running!";
          lcd.print(message);
          String messagespeed = "Pump on time: ";
          messagespeed = (String) PUMP_ON_TIME_MS + " ms";
          lcd.setCursor(8, 1);
          lcd.print(messagespeed);
          lcd.setCursor(0, 1);
          lcd.print("Speed: ");
          Serial.println("User pressed start");
          // Run pump
          digitalWrite(transOutPin, HIGH);
          //how long its on
          delay(PUMP_ON_TIME_MS);
          digitalWrite(transOutPin, LOW);
          //how long its off
          delay(PUMP_OFF_TIME_MS);
          int pumpState = digitalRead(transOutPin);
          // print out the state of pin 5:
          Serial.println(pumpState);

        }

        else if (buttons & BUTTON_LEFT) {
          Serial.println("User pressed stop");
          digitalWrite(transOutPin, LOW);
          current_state = WAITING_FOR_SELECTION;
        }
      }
    }
  }
}

After reading through your code, the biggest gotcha I see is how you handle time.

Time functions on Arduino are millis() and micros(), both return unsigned long values.

You can look at signed integers like measuring rods; + in one direction, - in the other, measure to the ends. But, go a little + of the + end and you wind up in the - end.

You can look at unsigned integers like circles, like round clocks. If it is 2 (end time) and I subtract 5 (start time) by moving the hour hand 5 to the left, the result is how many hours went by between 5 and 2 -- the hand points to 9. If it is 7 and I subtract 4, the result is 3. Both are correct using the same method as long as I don't try to measure more than 11 hours difference.

8 bit unsigned measures up to 255, can do 1/4 second in millis. 16 bit is good to 65535, can do a full minute in millis. 32 bit can count 49.71~ days in millis (over 4 billion).

But it needs to be expressed as (end time - start time) to get the difference and if() on that, not whether or not the end is more than the start as in

if (millis() > start time + some_value) // which will bug on rollover

Hopefully forum notifications won't fail me and I can help to clean up that code a bit. I've seen lots worse, I've written lots worse, but we want to move forward, right?

I was wondering why the pump sounded the same at 1000,5000,10000 for PUMP_TIME_ON.
Thank you!
I guess my biggest issue then is going to be getting small enough “PUMP_TIME_OFF” to really control the flow rate?

For your convenience, here is the Library code that makes the buttons work!
I think that if I knew how to read the right button state as high, i could make it run continuously as long as the loop kept checking that the state is high. I just don’t know how to do that after reading the library.

/*************************************************** 
  This is a library for the Adafruit RGB 16x2 LCD Shield 
  Pick one up at the Adafruit shop!
  ---------> http://http://www.adafruit.com/products/714

  The shield uses I2C to communicate, 2 pins are required to  
  interface
  Adafruit invests time and resources providing this open source code, 
  please support Adafruit and open-source hardware by purchasing 
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.  
  BSD license, all text above must be included in any redistribution
 ****************************************************/

#ifndef Adafruit_RGBLCDShield_h
#define Adafruit_RGBLCDShield_h

#include <inttypes.h>
#include "Print.h"
#include <utility/Adafruit_MCP23017.h>

// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80

// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00

// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00

// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00

#define BUTTON_UP 0x08
#define BUTTON_DOWN 0x04
#define BUTTON_LEFT 0x10
#define BUTTON_RIGHT 0x02
#define BUTTON_SELECT 0x01


class Adafruit_RGBLCDShield : public Print {
public:
  Adafruit_RGBLCDShield();

  void init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable,
	    uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
	    uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7);
    
  void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS);

  void clear();
  void home();

  void noDisplay();
  void display();
  void noBlink();
  void blink();
  void noCursor();
  void cursor();
  void scrollDisplayLeft();
  void scrollDisplayRight();
  void leftToRight();
  void rightToLeft();
  void autoscroll();
  void noAutoscroll();
  
  // only if using backpack
  void setBacklight(uint8_t status); 

  void createChar(uint8_t, uint8_t[]);
  void setCursor(uint8_t, uint8_t); 
#if ARDUINO >= 100
  virtual size_t write(uint8_t);
#else
  virtual void write(uint8_t);
#endif
  void command(uint8_t);
  uint8_t readButtons();

private:
  void send(uint8_t, uint8_t);
  void write4bits(uint8_t);
  void write8bits(uint8_t);
  void pulseEnable();
  void _digitalWrite(uint8_t, uint8_t);
  void _pinMode(uint8_t, uint8_t);

  uint8_t _rs_pin; // LOW: command.  HIGH: character.
  uint8_t _rw_pin; // LOW: write to LCD.  HIGH: read from LCD.
  uint8_t _enable_pin; // activated by a HIGH pulse.
  uint8_t _data_pins[8];
  uint8_t _button_pins[5];
  uint8_t _displayfunction;
  uint8_t _displaycontrol;
  uint8_t _displaymode;

  uint8_t _initialized;

  uint8_t _numlines,_currline;

  uint8_t _i2cAddr;
  Adafruit_MCP23017 _i2c;
};

#endif

Alright, instead, I’ve tried going to the direction based on the Adafruit DC motor control tutorial.

It doesn’t run when PUMP_ON_TIME = 0 (it shouldn’t if the digitalWrite bits = 0), but I’m confused that there is no real difference in motor speed between PUMP_ON_TIME = 5 and PUMP_ON_TIME = 255. I tested this empirically by filling a cup with water, and it took ~30 seconds with both PUMP_ON_TIME = 5 and PUMP_ON_TIME = 255!

Could this just be the nature of my motor? With the DC motors used in the Adafruit tutorial, it seems implied that there is a noticeable difference in speed depending on the bit integer you are writing to the motor.

New code is as follows:

/*********************

  Example code for the Adafruit RGB Character LCD Shield and Library

  This code displays text on the shield, and also reads the buttons on the keypad.
  When a button is pressed, the backlight changes color.

**********************/

// include the library code:
#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>


// The shield uses the I2C SCL and SDA pins. On classic Arduinos
// this is Analog 4 and 5 so you can't use those for analogRead() anymore
// However, you can connect other I2C sensors to the I2C bus and share
// the I2C bus.
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

// These #defines make it easy to set the backlight color
#define RED 0x1  //makes blue LCD backlight turn on
int transOutPin = 5;       // the number of the output pin that controls TIP120 transistor
enum state {
  SPLASH_SCREEN,
  WAITING_FOR_SELECTION,
  RUNNING,
  ON
};

state current_state;
int PUMP_ON_TIME = 0; // Default flow rate
int MAX_PUMP_ON_TIME = 255;
int MIN_PUMP_ON_TIME = 0;
int PUMP_ON_TIME_STEP_SIZE = 5; // Go up/down 5 at a time

void setup() {
  // Debugging output
  Serial.begin(9600);
  // set up the LCD's number of columns and rows:
  // Print a message to the LCD. We track how long it takes since
  // this library has been optimized a bit and we're proud of it :)
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("Welcome to the");
  lcd.setCursor(0, 1);
  lcd.print("NVC Suite v2.0");
  pinMode(transOutPin, OUTPUT);
}
uint8_t i = 0;


void loop() {

  if (millis() < 3000)
  {
    // do nothing
  }
  else
  {
    if (current_state == SPLASH_SCREEN)
    {
      // Switch to user selection state
      current_state = WAITING_FOR_SELECTION;
      lcd.clear();
    }
    else if (current_state == WAITING_FOR_SELECTION)
    {
      // Display current speed setting
      String message = "Set pump speed: ";
      lcd.setCursor(0, 0);
      lcd.print(message);
      message = (String)PUMP_ON_TIME;
      lcd.setCursor(0, 1);
      lcd.print(message);

      uint8_t buttons = lcd.readButtons();

      if (buttons) {
        lcd.clear();
        lcd.setCursor(0, 0);
        if (buttons & BUTTON_UP) {
          Serial.println("User pressed up");
          // Increment speed
          PUMP_ON_TIME += PUMP_ON_TIME_STEP_SIZE;

          // Make sure we don't go over the maximum
          if (PUMP_ON_TIME > MAX_PUMP_ON_TIME)
          {
            PUMP_ON_TIME = MAX_PUMP_ON_TIME;
          }

        }
        else if (buttons & BUTTON_DOWN) {
          Serial.println("User pressed down");
          // Decrement speed
          PUMP_ON_TIME -= PUMP_ON_TIME_STEP_SIZE;

          // Make sure we don't go under the minimum
          if (PUMP_ON_TIME < MIN_PUMP_ON_TIME)
          {
            PUMP_ON_TIME = MIN_PUMP_ON_TIME;
          }
        }

        else if (buttons & BUTTON_SELECT) {
          current_state = RUNNING;
          Serial.println("User pressed select");
          lcd.print("Press RIGHT to");
          lcd.setCursor(0, 1);
          lcd.print("to begin coating");

        }
      }
    }

    else if (current_state == RUNNING) {
      uint8_t buttons = lcd.readButtons();
      if (buttons) {
   
        if (buttons & BUTTON_RIGHT) {
          // Display running text
          lcd.clear();
          lcd.setCursor(0, 0);
          String message = "Running!";
          lcd.print(message);
          String messagespeed = "Speed: ";
          messagespeed = (String) PUMP_ON_TIME;
          lcd.setCursor(8, 1);
          lcd.print(messagespeed);
          lcd.setCursor(0, 1);
          lcd.print("Speed: ");
          Serial.println("User pressed start");
          // Run pump
          digitalWrite(transOutPin, PUMP_ON_TIME);
          int pumpState = digitalRead(transOutPin);
          // print out the state of pin 5:
          Serial.println(pumpState);
        }

      

        else if (buttons & BUTTON_LEFT) {
          Serial.println("User pressed stop");
          digitalWrite(transOutPin, LOW);
          current_state = WAITING_FOR_SELECTION;}
     
        }
      }
  }    }

how do you have everything wired, and which arduino are you using?

Alright, so I quickly realized that I need to be using analogWrite and not digitalWrite.

But this keeps getting weirder. Using analogWrite, my motor just makes a tone, like a beep, that gets louder and louder with increased PWM. I have no idea why.

What is going on?!

Qdeathstar: how do you have everything wired, and which arduino are you using?

Using an Arduino Uno REV3. Here is a Fritzing diagram I cooked up. Excuse the barrel jack red and black wires, my barrel jack hooks up on the side as shown and not to the back like the stock barrel jack in Fritzing.

|500x369

Edit.
I’m an idiot, the sound isn’t from the Arduino board (obviously) but from the pump running at too low of a power and not turning.
Past about ~180 it starts running the motor!
Thanks, I think I figured this problem out!!!