Build a token vending machine with a keypad and LCD

Drop a printed token in the slot, choose a prize on the keypad and the servo turns a four-compartment drum to the right window. The LCD shows your credit, the prices and what's sold out, and the stock is remembered.
  • Level:Advanced
  • Duration: 2 h 30 min
  • Ages 12+
  • Platform: Arduino

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Build a token vending machine with a keypad and LCD – EEMENTOR STEM™ lesson

A vending machine counts money, takes your choice, checks the price and the stock, and hands out the item. This capstone builds one from the UNO kit and a shoe box. Tokens rolling down the printed chute pass an IR sensor, which counts them. The 1602 LCD shows your credit and runs through the menu. Press 1 to 4 on the keypad: if you have enough tokens and the item isn't sold out, the servo turns a four-compartment drum until your prize faces the window. The UNO remembers how many of each item are left, even after a power cut, and the owner can refill it with a secret code. Use it for a class reward system, a party game or a pocket-money shop.

What you'll learn

  • How to plan a machine with clear steps: wait, pay, choose, deliver
  • How to count objects with an IR sensor
  • How to use the LCD, the keypad and a servo in one sketch
  • How to save numbers in EEPROM so they survive a power cut

What you need

  • UNO R3 board and USB cable (UNO R3 Starter Kit)
  • 1602 LCD display, 10 kΩ potentiometer and 1 resistor, 220 Ω (UNO R3 Starter Kit)
  • 4×4 keypad module (UNO R3 Starter Kit)
  • Servo motor SG90 with horns and screws (UNO R3 Starter Kit)
  • Passive buzzer (UNO R3 Starter Kit)
  • IR obstacle avoidance sensor module (45-in-1 Sensor Kit)
  • A shoe box and small prizes
  • The printed parts from the 3D printing box below (drum, token chute and tokens), or the cardboard version described there
  • Breadboard and jumper wires (UNO R3 Starter Kit)

Wiring

Breadboard wiring diagram: Build a token vending machine with a keypad and LCD
Breadboard view. Check each connection against the wiring table below.
From To
LCD VSS, RW and K GND
LCD VDD 5V
LCD V0 Middle pin (W) of the potentiometer
LCD RS UNO pin 12
LCD E UNO pin 11
LCD D4, D5, D6, D7 UNO pins 5, 4, 3, 2
LCD D0 to D3 Not connected
LCD A (backlight +) 220 Ω resistor, then 5V
Potentiometer outer pins 5V and GND
Keypad R1, R2, R3, R4 UNO pins A0, A1, A2, A3
Keypad C1, C2, C3, C4 UNO pins 10, 8, 7, 6
Servo signal (orange) UNO pin 9
Servo power (red) and GND (brown) 5V and GND
IR sensor OUT UNO pin A4
IR sensor + and GND 5V and GND
Passive buzzer + UNO pin A5
Passive buzzer − GND

The LCD and keypad are wired exactly as in the keypad safe. The analog pins A0 to A5 work as normal digital pins too. Turn the IR sensor's screw so its light comes on only when a token is right in front of it.

Build it in stages

  1. Screen and keys. Wire the LCD and keypad and upload the sketch. The LCD shows "Credit: 0" and the menu. Press 1: "Insert 1 token".
  2. Tokens. Add the IR sensor. Drop a token down the chute: a beep, and the credit goes up.
  3. Drum. Add the servo. Press 1 with enough credit: the drum turns, waits five seconds and turns back.
  4. Box. Find the four angles where each compartment faces the window, put them in ANGLE, and build everything into the box.

The sketch

In the Arduino IDE, open Library Manager and install the Keypad library by Mark Stanley and Alexander Brevig. LiquidCrystal, Servo and EEPROM come with the Arduino IDE.

#include <LiquidCrystal.h>
#include <Keypad.h>
#include <Servo.h>
#include <EEPROM.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const byte ROWS = 4, COLS = 4;
char KEYS[ROWS][COLS] = {{'1', '2', '3', 'A'}, {'4', '5', '6', 'B'}, {'7', '8', '9', 'C'}, {'*', '0', '#', 'D'}};
byte rowPins[ROWS] = {A0, A1, A2, A3};
byte colPins[COLS] = {10, 8, 7, 6};
Keypad keypad = Keypad(makeKeymap(KEYS), rowPins, colPins, ROWS, COLS);
Servo drum;

const int SERVO_PIN = 9, TOKEN_PIN = A4, BUZZER_PIN = A5;
const char* NAMES[4] = {"Sweet", "Sticker", "Mini toy", "Surprise"};
const int PRICE[4] = {1, 1, 2, 3};               // tokens per item
const int ANGLE[4] = {135, 90, 45, 0};            // servo angle that turns each compartment to the window
const int CLOSED_ANGLE = 180;                     // the solid half of the drum faces the window
const int FULL = 5;                               // items per compartment after a refill
const String OWNER_CODE = "2580";                 // press D, the code and # to refill

int credit = 0;
byte stock[4];
String typed = "";
bool ownerMode = false;
bool tokenWasThere = false;
unsigned long tokenSeenAt = 0, lastMenu = 0;
int menuItem = 0;

void show(String line1, String line2) {
  lcd.clear();
  lcd.print(line1);
  lcd.setCursor(0, 1);
  lcd.print(line2);
}

void idle() {                                     // credit on top, the menu below
  String item = String(menuItem + 1) + " " + NAMES[menuItem];
  String right = stock[menuItem] == 0 ? "SOLD" : String(PRICE[menuItem]) + "t";
  while (item.length() + right.length() < 16) item += " ";
  show("Credit: " + String(credit) + (credit == 1 ? " token" : " tokens"), item + right);
}

void saveStock() {
  for (int i = 0; i < 4; i++) EEPROM.update(1 + i, stock[i]);
}

void deliver(int i) {
  credit -= PRICE[i];
  stock[i]--;
  saveStock();
  show("Here comes your", String(NAMES[i]) + "!");
  drum.write(ANGLE[i]);
  tone(BUZZER_PIN, 1500, 120);
  delay(5000);                                    // time to take the prize
  drum.write(CLOSED_ANGLE);
  delay(600);
}

void choose(int i) {
  if (stock[i] == 0) {
    show(String(NAMES[i]) + " is", "sold out, sorry");
    tone(BUZZER_PIN, 300, 300);
  } else if (credit < PRICE[i]) {
    int missing = PRICE[i] - credit;
    show(String(NAMES[i]) + ": " + PRICE[i] + "t", "Insert " + String(missing) + " more");
    tone(BUZZER_PIN, 300, 150);
  } else {
    deliver(i);
    return;
  }
  delay(1800);
}

void setup() {
  lcd.begin(16, 2);
  pinMode(TOKEN_PIN, INPUT);
  drum.attach(SERVO_PIN);
  drum.write(CLOSED_ANGLE);
  if (EEPROM.read(0) != 42) {                     // first start: fill up and mark the EEPROM
    for (int i = 0; i < 4; i++) stock[i] = FULL;
    saveStock();
    EEPROM.update(0, 42);
  }
  for (int i = 0; i < 4; i++) stock[i] = EEPROM.read(1 + i);
  idle();
}

void loop() {
  // a token passing the sensor (OUT goes LOW while something is in front)
  bool tokenThere = digitalRead(TOKEN_PIN) == LOW;
  if (tokenThere && !tokenWasThere && millis() - tokenSeenAt > 150) {
    credit++;
    tokenSeenAt = millis();
    tone(BUZZER_PIN, 2200, 80);
    idle();
  }
  tokenWasThere = tokenThere;

  char key = keypad.getKey();
  if (key) {
    if (ownerMode) {                              // typing the owner code
      if (key == '#') {
        if (typed == OWNER_CODE) {
          for (int i = 0; i < 4; i++) stock[i] = FULL;
          saveStock();
          show("Refilled!", "All items: " + String(FULL));
        } else {
          show("Wrong code", "");
        }
        ownerMode = false;
        typed = "";
        delay(1500);
      } else if (key == '*') {
        ownerMode = false;
        typed = "";
      } else {
        typed += key;
        show("Owner code:", String("****").substring(0, min((int)typed.length(), 4)));
        return;
      }
    } else if (key >= '1' && key <= '4') {
      choose(key - '1');
    } else if (key == 'D') {
      ownerMode = true;
      show("Owner code:", "");
      return;
    }
    idle();
  }

  if (!ownerMode && millis() - lastMenu > 2500) { // show the next item every 2.5 s
    lastMenu = millis();
    menuItem = (menuItem + 1) % 4;
    idle();
  }
}

Use it

  1. The LCD shows your credit and runs through the four items with their prices, or "SOLD" when one is empty.
  2. Drop tokens down the chute. Each one beeps and adds one to your credit.
  3. Press 1, 2, 3 or 4. With enough credit, the drum turns your prize to the window for five seconds. Otherwise the LCD says how many tokens are missing.
  4. To refill, press D, type the owner code and press #. * cancels.

How it works

A vending machine is a list of rules, checked in the right order. When you choose an item, the sketch first checks the stock, then the credit, and only if both are fine does it take the tokens, lower the stock and turn the drum. Doing the checks before changing anything means a failed choice never costs a token.

Tokens are counted at the moment they arrive in front of the sensor: tokenThere is true now but tokenWasThere was false. A token that stays in front for a while still counts once, and the 150 ms pause stops one token from counting twice as it rolls past.

The stock is kept in EEPROM, a small memory in the UNO that keeps its contents without power. The first byte is a marker: if it isn't 42, the EEPROM has never been used, so the sketch fills it up. EEPROM.update() only writes when the value changes, which makes the EEPROM last longer.

The servo only turns 180°, so the drum doesn't spin round. Instead each compartment has its own angle, and a solid part of the drum closes the window between sales.

Try this next

  • Show "Thank you!" and play a little tune after each sale.
  • Add a "change" item: press 0 to get your remaining credit back as a printed ticket on the LCD.
  • Count the sales of each item in EEPROM and show them in owner mode.

Troubleshooting

  • The board restarts when the servo moves: servos draw bursts of current. Use a good USB port or a powered hub, and check that the servo's red wire is on 5V.
  • The LCD lights up but shows nothing, or only black boxes: turn the potentiometer slowly from one end to the other until the text appears.
  • Tokens aren't counted: turn the IR sensor's screw until its light comes on when a token is right in front, and make sure tokens roll past the sensor, not under it.
  • One token counts as two: make the 150 ms pause longer, or slow the tokens down with a gentler slope.
  • The wrong compartment comes to the window: adjust the four ANGLE values one by one.
  • A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

UNO R3 Starter Kit | Arduino IDE Compatible | Free Tutorials $56.00

Shop the kit