1

I have this project called Onionskin Vending machine. The function should be: when a coin is inserted, the pulses from the coin acceptor (which is not accurate sometimes so i didn't use attachInterrupt) is recorded by the Arduino and is shown to the user as Credits. then while still waiting for coin input, it will ask the user to select a product.

EX. Scenario 1: The user drops a 1cent coin, then the machine will show the user its credit as 1cents and will ask to press a button. the user will press a button then the machine will turn on a motor for (X) seconds to dispense product and say thank you then resets again.

EX. Scenario 2: The user drops a 5cent coin, then the machine will show the user its credit as 5cents and will ask to press a button. then the user will drop again a 1cent coin so the machine will show its credit as 6cents and will again ask to press a button. the user will press a button then the machine will turn on a motor for (6X) seconds to dispense product and say thank you then resets again.

Here is the code I wrote so far, but the problem is when activated, it only records the first cent/pulse then goes on waiting for user input to press a button, even if I drop more coins, it will not count the new coins inserted, only always the 1cent / 1 Credit is displayed.

EDIT: There is no target credit, 1cents would do. but if user drop 2cents, then 2 Sets of product should be dispensed upon user selection of product. So target is the arduino should wait for coin slot input while waiting for user input at same time.

#include <Wire.h>  // Comes with Arduino IDE, Required for I2C
#include <LiquidCrystal_I2C.h>  //Call LCD I2C Library

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);   // Set the LCD I2C address Set up data transmission from microcontroller to LCD

int coinPin = 2;            //Defined as the receiving pin from the coin machine.
int coinState = 0;          //Determine the status of coinPin
int credits = 0;            //Define a variable to display the credit.
int delayTime = 0;          //Define delay variable for number of onionskin sheet feed duration
int resetPin = 12;          //Define reset pin for resetting after complete function
int sensorPin = A0; // select the input pin for LDR
int sensorValue = 0; // variable to store the value coming from the sensor
const int laserPin = 6;
const int shortSizeFeedTime = 5000;  //Duration for 2pc onionskin to pass feeder
const int longSizeFeedTime = 7500;   //Duration for 3pc onionskin to pass feeder
const int ledpin = 13;      //define LED Pin at pin13
const int ButtonS = 8;      //Define button for short onionskin
const int ButtonL = 7;      //Define button for long onionskin
const int shortSize = 9;    //Define pin for motor pin shortSize
const int longSize = 10;    //Define pin for motor pin longSize

void setup()
{
  digitalWrite(resetPin, HIGH);
  delay(200);
  pinMode(coinPin, INPUT);     //Determine the function of the buttton pin to the input pin.
  pinMode(resetPin, OUTPUT);  //Set Reset PIN as OUTPUT
  pinMode(shortSize, OUTPUT);
  pinMode(longSize, OUTPUT);
  pinMode(laserPin, OUTPUT);         //Laser PIN
  /*
    lcd.begin(16, 2);         //Schedule your columns and rows. LCD
    lcd.setCursor(4,0);         //Set the cursor's starting position at position(4,0)
    lcd.print("WELCOME");      //Display screen text LCD
    lcd.setCursor(0,1);         //Set the cursor's starting position at position(0,1)
    lcd.print("PLZ  INSERT COIN");      //Display screen text LCD
  */
  Serial.begin(9600);  // Begin Serial Connection to PC

  lcd.begin(16, 2);  // initialize the lcd for 16 chars 2 lines, turn on backlight
  pinMode(ButtonS, INPUT_PULLUP);
  pinMode(ButtonL, INPUT_PULLUP);
  digitalWrite(laserPin, HIGH);// used as a laser trip wire sensor
  start(); //GOTO Start FUNCTION
  //delay(8000);
  // Wait and then tell user they can start the Serial Monitor and type in characters to
  // Display. (Set Serial Monitor option to "No Line Ending")
  //lcd.clear();
  //lcd.setCursor(0,0); //Start at character 0 on line 0
}


void start()
{
  //-------- Write characters on the display ------------------
  // NOTE: Cursor Position: (CHAR, LINE) start at 0
  lcd.clear();
  lcd.setCursor(4, 0); //Start at character 4 on line 0
  lcd.print("WELCOME!");
  delay(1000);
  lcd.setCursor(0, 1);
  lcd.print(" ->Insert Coin<-");
  loop();
}


void loop()
{
  sensorValue = analogRead(sensorPin);//read the status of the LDR attached to the analog pin for laser trip wire
  coinState = digitalRead(coinPin);   //coinPin value In the coinState
  lcd.setCursor(0, 0);        //Set the cursor's starting position at position(0,0)
  while (analogRead(sensorPin) < 1000)
  {
    lcd.clear();
    lcd.print("PLS. REMOVE ANY");
    lcd.setCursor(0, 1);
    lcd.print("OBJECT FRM TRAY!");
    delay(2000);
    start();
  }
  if (coinState == LOW)         //Condition
  {
    credits += 1;          //Calculate the value of the variable. It will increase each time. 5
    delay(500);                        //Delay schedule 500ms.
    lcd.clear();            //Clear command all LCD screen
    lcd.print("CREDIT:");         //Display LCD Display Message
    lcd.setCursor(8, 0);        //Set the cursor's starting position at position(8,0)
    lcd.print(credits);            //Displays the value of variable "credit" on the LCD screen.
    lcd.setCursor(12, 0);        //Set the cursor's starting position at position(8,0)
    lcd.print("PHP");          //Display screen text LCD
    lcd.setCursor(0, 1);
    lcd.print("PRESS TO PROCEED");

    //Wait for button input from user
    while (digitalRead(ButtonS) == HIGH || digitalRead(ButtonL) == HIGH) {

      //---------Servo A----------------
      if (digitalRead(ButtonS) == LOW && sensorValue > 1000)  {
        lcd.clear();
        lcd.setCursor(4, 0);
        lcd.print("VENDING:");
        lcd.setCursor(0, 1);
        lcd.print("LONG ONIONSKIN");
        delayTime = credits * longSizeFeedTime;
        digitalWrite(longSize, HIGH);//Turn on the feeder motor
        delay(delayTime); //calculation time for 2pc of onionskin
        digitalWrite(longSize, LOW);//Turn off the feeder motor
        lcd.clear();
        lcd.setCursor(3, 0);
        lcd.print("THANK YOU!");
        lcd.setCursor(0, 1);
        lcd.print("PLS. COME AGAIN!");
        credits = 0;//set the credits back to zero
        delay(3000);
        digitalWrite(resetPin, LOW);//optional for activation harware reset pin
        start();//GO to start function, kind of reset
        break;//this never actually happens :)
      } // when item is dispensed exit loop and return to wait for coin

      //---------Servo B-------------
      else if (digitalRead(ButtonL) == LOW && sensorValue > 1000) {
        lcd.clear();
        lcd.setCursor(4, 0);
        lcd.print("VENDING:");
        lcd.setCursor(0, 1);
        lcd.print("SHORT ONIONSKIN");
        delayTime = credits * shortSizeFeedTime;//calculation time for 2pc of onionskin
        digitalWrite(shortSize, HIGH);//Turn on the feeder motor
        delay(delayTime);
        digitalWrite(shortSize, LOW);//Turn off the feeder motor
        lcd.clear();
        lcd.setCursor(3, 0);
        lcd.print("THANK YOU!");
        lcd.setCursor(0, 1);
        lcd.print("PLS. COME AGAIN!");
        credits = 0;
        delay(3000);
        digitalWrite(resetPin, LOW);//optional for activation harware reset pin
        start();//GO to start function, kind of reset
        break;//Again, this never happens :)
      }
    }//End While Loop
  }//End If coin detect loop
}//End void loop()

PS: I have read somewhere that using millis could help solve the dual running problem or by using the correct while or switch-case loop. I'm not familiar much about how while, do-while, for, switch loops work.

  • the two scenarios are the same – jsotola Feb 2 '18 at 17:12
  • @jsotola different coin was dropped for each scenario. the computation of product was also different because of the amount inserted – EngrAbbas Feb 3 '18 at 11:35
  • * I'm not familiar much about how while, do-while, for, switch loops work.* You have to learn programming first. You are trying to drive without knowing what a steering wheel is. – user31481 Feb 8 '18 at 7:47
  • Abbas and guys, i was wondering if its possible to remove the button code then make the code wait for a few seconds then it will check if the client dropped 1 cent or 2 cents then it will run specific command per value? i just started learning arduino coding. hope to hear form you soon guys – polu Oct 3 '18 at 1:24
2

Make it a state machine:

Initial state is idle, nothing on the tray, no credit

If a coin is entered then it counts the credits and displays it. (Should always be true)

If a button is pushed then only if credit is sufficient and tray is empty will something be vended (and credit is subtracted), otherwise show error for a few seconds.

Showing an error can be done by setting a global and a start time and duration:

long credit;
enum{noError, insufficientCredit, trayError, thankyou};
int error;
unsigned long messageTimestamp;
unsigned long messageDuration;


void updateLCD(){
    if(millis() - messageTimestamp > messageDuration) 
        error=noError;
    switch(error){
    case noError: break;

    case insufficientCredit: 

        //show insufficient credit error
        break;
    case trayError: 

        //show tray error
        break;
    case thankyou: 

        //show thankyou message
        break;
    }

    if(credit == 0){
        //show attract mode
    }else{
        //show credit
    }
}

You can do something similar for the motor:

bool motorOn;
unsigned long motorTimestamp;
unsigned long motorDuration;

if(motorOn && millis() - motorTimestamp> motorDuration){ 
    motorOn = false;
    digitalWrite(shortSize, LOW);//Turn off the feeder motor
}

this way you can also prevent the vending from starting again while the motor is still running by making an extra check on motorOn.

  • I agree with the state machine idea. Draw a grid showing all possible states (eg. no money, some money, waiting for button, dispensing goods, etc.). Then all possible actions (eg. adding money, pressing button) and work out what needs to be done in each state for each possible action. – Nick Gammon Feb 2 '18 at 20:49
  • Actually there is no amount of target credit, the base credit is 1cent. 1cent is suficient to run the feed motor/dispense product. if you add 2 cents, then you woud have 2 credits and 2 set of product will come out. the problem is with the code that i made, only the 1cent is recognised/counted, its like when it detects a pulse at the coinPin, its counts the first credit then moves on to the next function to wait for user input. what i need is for it to still wait for coin pulse detect while also waiting for the user input. – EngrAbbas Feb 3 '18 at 11:16
  • @ratchet If you would also kindly include the modification/correction in the main code. As I said I'm not familiar much into c++ programming. I don't know also in what part of the code to call the updateLCD() function, also where to insert/replace the solution you posted. – EngrAbbas Feb 3 '18 at 11:42
  • @ratchet freak Hi, any update? I'm still waiting and curious :) Thanks – EngrAbbas Feb 7 '18 at 15:24
  • @EngrAbbas if you want to scale the amount of product dispenced you can scale the motorDuration accordinly – ratchet freak Feb 7 '18 at 15:51
0

After several research here is the final working code and solution to the problem.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Constants

bool userSelect = false;
const int coinPin = 2;      // Defined as the receiving pin from the coin machine.
int coinState = 0;          // Determine the status of coinPin
volatile int credits = 0;   // Define a variable to display the credit.
int delayTime = 0;          // Define delay variable for number of onionskin feed duration
int resetPin = 12;          // Define reset pin for resetting after complete function
int sensorPin = A0;         // select the input pin for LDR
int sensorValue = 0;        // variable to store the value coming from the sensor
const int laserPin = 6;
const int shortSizeFeedTime = 5000;  // Duration for 2pc OnionSkin to pass feeder
const int longSizeFeedTime = 7500;   // Duration for 3pc OnionSkin to pass feeder
const int ledpin = 13;      // Define LED Pin at pin13
const int ButtonS = 4;      // Define button for short Size OnionSkin
const int ButtonL = 5;      // Define button for long Size OnionSkin
const int shortSize = 6;    // Define pin for motor pin shortSize
const int longSize = 7;    // Define pin for motor pin longSize

//I2C pins declaration for LCD
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

//CoinInterrupt counter function
void coinInterrupt() {

  // Each time a pulse is sent from the coin acceptor, interrupt main loop to add 1 cent and flip on the LED
  credits += 1;
  digitalWrite(ledpin, HIGH);
  digitalWrite(ledpin, LOW);
}

//Long OnionSkin Feeder function
void longSkinServe() {
  lcd.clear();
  lcd.setCursor(4, 0);
  lcd.print("VENDING:");
  lcd.setCursor(0, 1);
  lcd.print("LONG ONION SKIN");
  delayTime = credits * longSizeFeedTime;
  digitalWrite(longSize, HIGH);
  delay(delayTime);
  digitalWrite(longSize, LOW);
  lcd.clear();
  lcd.setCursor(3, 0);
  lcd.print("THANK YOU!");
  lcd.setCursor(0, 1);
  lcd.print("PLS. COME AGAIN!");
  delay(3000);
  digitalWrite(resetPin, LOW);
  credits = 0;
  userSelect = false;
}

//Short OnionSkin Feeder function
void shortSkinServe() {
  lcd.clear();
  lcd.setCursor(4, 0);
  lcd.print("VENDING:");
  lcd.setCursor(0, 1);
  lcd.print("SHORT ONION SKIN");
  delayTime = credits * shortSizeFeedTime;
  digitalWrite(shortSize, HIGH);
  delay(delayTime);
  digitalWrite(shortSize, LOW);
  lcd.clear();
  lcd.setCursor(3, 0);
  lcd.print("THANK YOU!");
  lcd.setCursor(0, 1);
  lcd.print("PLS. COME AGAIN!");
  delay(3000);
  digitalWrite(resetPin, LOW);
  credits = 0;
  userSelect = false;
}

void setup() {
  Serial.begin(9600);  // Begin Serial Connection to PC
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);  // initialize the lcd for 16 chars 2 lines, turn on backlight
  lcd.print("STARTING UP...");
  attachInterrupt(digitalPinToInterrupt(coinPin), coinInterrupt, RISING);
  pinMode(ledpin, OUTPUT);
  pinMode(resetPin, OUTPUT);  //Set Reset PIN as OUTPUT
  pinMode(shortSize, OUTPUT);
  pinMode(longSize, OUTPUT);
  //  pinMode(laserPin, OUTPUT);         //Laser PIN
  pinMode(ButtonS, INPUT_PULLUP);
  pinMode(ButtonL, INPUT_PULLUP);
  delay(2000);

}

// Main loop
void loop() {
  if (credits == 0) {//not yet running, there is no credit yet
    lcd.clear();
    lcd.print("ONION SKIN VENDO");
    lcd.setCursor(0, 1);
    lcd.write("-[INSERT COINS]-");
    delay(3000);
    lcd.clear();
    lcd.print("-[INSERT COINS]-");
    lcd.setCursor(1, 1);
    lcd.write("P1 or P5 ONLY");
    Serial.println("No Coin Inserted Yet...Waiting");
    delay (3000);

    //---Wait for Coin/Credit-----------
    while (credits > 0 && userSelect == false) { //if coin inserted stay in servo enable loop

      lcd.clear();            //Clear command all LCD screen
      lcd.print("CREDITS:");         //Display LCD Display Message
      lcd.setCursor(9, 0);        //Set the cursor's starting position at position(8,0)
      lcd.print(credits);            //Displays the value of variable "credit" on the LCD screen.
      lcd.setCursor(12, 0);        //Set the cursor's starting position at position(8,0)
      lcd.print("PHP");          //Display screen text LCD
      lcd.setCursor(0, 1);
      lcd.print("PRESS TO PROCEED");
      Serial.println("Credits:");
      Serial.print(credits);
      if (digitalRead(ButtonS) == HIGH || digitalRead(ButtonL) == HIGH) {

        if (digitalRead(ButtonS) == LOW)  {
          //execute short size motor
          userSelect = true;
          shortSkinServe();
        }
        else if (digitalRead(ButtonL) == LOW)
        {
          userSelect = true;
          longSkinServe();
        }
      }
      delay (80); // for while loop LCD refresh speed control
    }

  }


}

Also here is a circuit diagram.

enter image description here

Some may say that the coding is not efficient as using several if else..if statements, but hey, this solves the problem. Cheers!

protected by Community May 6 at 16:00

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.