Built-in project · Arduino Uno R3

Arcade claw machine on an Arduino Uno

A complete claw machine you can watch: a stepper gantry with home switches, winch, servo claw and prizes in the cabinet, a coin switch, countdown on an LCD, marquee lights, sound, and a chute beam that knows when you win. The whole build, an Arduino Uno and 11 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Advanced Runs in your browser. Free, and no account needed.

Build it as the Claw Machine Kit: the parts in numbered bags and a booklet that checks every step. Retails for about $71, free on Mokxi.

Arcade claw machine · Arduino Uno R3live0.000 s 0.00x
Click the coin switch, steer the claw over a prize with the joystick, then press Drop.
The circuit itself, running here on the simulator. Press what can be pressed; click anything else to open it in the editor.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Arcade claw machine: the whole game, the way a real cabinet runs it.
//
//   pin 2   X home switch            pin 3   Y home switch
//   pin 4   coin switch              pin 5   Drop button
//   pin 6   winch AIN2 (DRV8833)     pin 7   winch AIN1
//   pin 8   piezo speaker            pin 9   prize chute beam
//   pin 10  Y driver DIR (A4988)     pin 11  Y driver STEP
//   pin 12  X driver DIR (A4988)     pin 13  X driver STEP
//   A0, A1  joystick Y and X         A2      claw servo
//   A3      marquee (8 NeoPixels)    A4, A5  LCD SDA and SCL (I2C, 0x27)
//
// Drop a coin in (click the coin switch), steer the claw with the joystick
// before the clock runs out, then press Drop. The claw lowers, grabs, lifts,
// carries the prize back to the chute and lets go. If the beam across the
// chute is broken, you won. In the simulator the cabinet does all of that:
// the claw goes where the steppers take it, and a prize it lets go of over the
// chute falls through the beam.
//
// # The game is a state machine
//
// A cabinet is always in exactly one state, and each state knows what ends it:
//
//   ATTRACT  lights chase, the screen asks for a coin
//   CREDIT   a coin went in: a short fanfare, then the clock starts
//   PLAY     the joystick drives the gantry, the clock counts down
//   DROP     the winch lowers the open claw
//   GRAB     the claw closes, then the winch lifts
//   RETURN   the gantry carries the claw home, over the chute
//   RELEASE  the claw opens and the chute beam is watched
//   WIN or LOSE, then back to CREDIT if there are credits left, or ATTRACT
//
// loop() never waits. It reads the inputs, runs the code for the current
// state, steps the motors that are due a step, and comes straight back. That
// is why the joystick, the clock, the lights and the sounds all keep going at
// once.
//
// # Steppers, drivers and home
//
// Each axis is a NEMA 17 on an A4988. The sketch gives each driver two wires:
// DIR says which way, and each pulse on STEP is one step (full steps here, as
// the three mode pins are tied to ground). A stepper has no idea where it is, so the
// sketch counts steps, and at power-up it drives each axis toward its home
// switch until the switch closes. That corner is zero, and the chute sits
// under it, as in most cabinets.
//
// In the simulator the cabinet's carriage presses each lever when it gets
// home, as a real one does. If a switch never closes, the sketch stops after a
// full length of travel and calls that home. A real machine would stop there
// with a fault instead, since a switch that never closes is a broken switch.
//
// # Grip strength, honestly
//
// Many real claw machines let the operator choose how hard the claw grips,
// and some let them set a payout rate: the board counts plays and only gives
// a full-strength grip once every so many. Between those plays the claw
// closes softly and a prize tends to slip. That is the reason a claw can feel
// weak. It is a setting, and it is right here:
//
//   PLAYS_PER_STRONG_GRIP  how many plays share one strong grip
//   STRONG_GRIP, SOFT_GRIP how far the servo closes on each kind of play
//
// On a servo claw, strength is how far past the prize the servo is told to
// close. Set PLAYS_PER_STRONG_GRIP to 1 and every play is a strong one.
//
// # The winch
//
// The winch is a small gear motor on a DRV8833. It runs for a set time down
// and up. Real cabinets usually stop the claw at the top with a switch; this
// build has used every pin on the Uno, so it is timed, as many home builds
// are.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Adafruit_NeoPixel.h>
#include <Servo.h>

// ---------------------------------------------------------------- pins

const int X_HOME = 2;
const int Y_HOME = 3;
const int COIN = 4;
const int DROP = 5;
const int WINCH_B = 6;
const int WINCH_A = 7;
const int PIEZO = 8;
const int CHUTE = 9;
const int Y_DIR = 10;
const int Y_STEP = 11;
const int X_DIR = 12;
const int X_STEP = 13;
const int JOY_Y = A0;
const int JOY_X = A1;
const int CLAW = A2;
const int MARQUEE = A3;

// ---------------------------------------------------------------- settings

const int PLAYS_PER_STRONG_GRIP = 4;  // the operator's payout setting
const int STRONG_GRIP = 150;          // servo degrees: closed hard
const int SOFT_GRIP = 110;            // servo degrees: closed gently
const int CLAW_OPEN = 30;

const int PLAY_SECONDS = 20;
const long X_TRAVEL = 1000;           // steps from home to the far side
const long Y_TRAVEL = 800;
const int MAX_SPEED = 800;            // steps a second at full stick
const int HOME_SPEED = 900;
const int DEAD_ZONE = 120;

const unsigned long WINCH_DOWN_MS = 1400;
const unsigned long WINCH_UP_MS = 1400;
const unsigned long CLOSE_MS = 700;
const unsigned long CHUTE_WAIT_MS = 2500;

// ---------------------------------------------------------------- parts

LiquidCrystal_I2C lcd(0x27, 16, 2);
Adafruit_NeoPixel marquee(8, MARQUEE, NEO_GRB + NEO_KHZ800);
Servo claw;

struct Axis {
  int dirPin;
  int stepPin;
  int homePin;
  long travel;
  long pos;
  int dir;
  unsigned long lastStep;
};

Axis xAxis = {X_DIR, X_STEP, X_HOME, X_TRAVEL, 0, 0, 0};
Axis yAxis = {Y_DIR, Y_STEP, Y_HOME, Y_TRAVEL, 0, 0, 0};

enum State { ATTRACT, CREDIT, PLAY, DROPPING, GRAB, RETURN, RELEASE, WIN, LOSE };
State state = ATTRACT;
unsigned long stateStarted = 0;

int credits = 0;
int playsSinceStrong = 0;
bool strongPlay = false;
int secondsShown = -1;
bool coinWas = false;
bool dropWas = false;
unsigned long lastCoin = 0;
int coinsPending = 0;
bool dropPending = false;
unsigned long lastLights = 0;
int lightFrame = 0;

// ---------------------------------------------------------------- helpers

bool pressed(int pin) { return digitalRead(pin) == LOW; }

// One step on one axis, toward home (-1) or away from it (+1).
void stepOnce(Axis &a, int dir) {
  if (dir != a.dir) {
    digitalWrite(a.dirPin, dir > 0 ? HIGH : LOW);
    a.dir = dir;
    delayMicroseconds(2);  // the A4988 wants DIR settled before STEP
  }
  digitalWrite(a.stepPin, HIGH);
  delayMicroseconds(3);
  digitalWrite(a.stepPin, LOW);
  a.pos += dir;
}

// Step an axis at `speed` steps a second (negative is toward home), if a step
// is due. The home switch and the far end of travel both stop it.
void drive(Axis &a, int speed) {
  if (speed == 0) return;
  unsigned long now = micros();
  unsigned long interval = 1000000UL / (unsigned long)abs(speed);
  if (now - a.lastStep < interval) return;
  a.lastStep = now;
  if (speed < 0) {
    if (pressed(a.homePin)) {
      a.pos = 0;  // the switch is the truth: this is zero
      return;
    }
    if (a.pos <= 0) return;
    stepOnce(a, -1);
  } else {
    if (a.pos >= a.travel) return;
    stepOnce(a, +1);
  }
}

// Joystick reading to a signed speed, with a dead zone in the middle.
int stickSpeed(int reading) {
  int offset = reading - 512;
  if (abs(offset) < DEAD_ZONE) return 0;
  int speed = map(abs(offset), DEAD_ZONE, 512, 150, MAX_SPEED);
  return offset < 0 ? -speed : speed;
}

void winch(int direction) {
  digitalWrite(WINCH_A, direction > 0 ? HIGH : LOW);  // down
  digitalWrite(WINCH_B, direction < 0 ? HIGH : LOW);  // up
}

// A coin can drop through at any moment, and a coin switch only closes for a
// few tens of milliseconds. So anything that takes a while (a screen redraw,
// a jingle) keeps checking the coin switch and the Drop button, and loop()
// acts on what was caught.
void pollCoin() {
  bool now = pressed(COIN);
  if (now && !coinWas && millis() - lastCoin > 80) {
    lastCoin = millis();
    coinsPending++;
  }
  coinWas = now;
  bool drop = pressed(DROP);
  if (drop && !dropWas) dropPending = true;
  dropWas = drop;
}

void wait(unsigned long ms) {
  unsigned long start = millis();
  while (millis() - start < ms) pollCoin();
}

void printLine(const char *text) {
  while (*text) {
    lcd.write(*text++);
    pollCoin();
  }
}

void show(const char *top, const char *bottom) {
  lcd.clear();
  lcd.setCursor(0, 0);
  printLine(top);
  lcd.setCursor(0, 1);
  printLine(bottom);
}

void enter(State next) {
  state = next;
  stateStarted = millis();
  secondsShown = -1;
}

unsigned long inState() { return millis() - stateStarted; }

// Bring an axis home at power-up: toward the switch until it closes, or one
// full length of travel if it never does.
void home(Axis &a, const char *name) {
  a.pos = a.travel;
  long budget = a.travel + 50;
  while (!pressed(a.homePin) && budget-- > 0) {
    stepOnce(a, -1);
    delayMicroseconds(1000000UL / HOME_SPEED);
  }
  Serial.print(name);
  Serial.println(pressed(a.homePin) ? " home: switch closed" : " home: end of travel, no switch seen");
  a.pos = 0;
}

// ---------------------------------------------------------------- sound

void beep(unsigned int hz, unsigned long ms) { tone(PIEZO, hz, ms); }

void jingle(const unsigned int *notes, int count, unsigned long each) {
  for (int i = 0; i < count; i++) {
    tone(PIEZO, notes[i], each - 20);
    wait(each);
  }
}

const unsigned int START_NOTES[] = {523, 659, 784, 1047};
const unsigned int WIN_NOTES[] = {784, 988, 1175, 1568, 1175, 1568};
const unsigned int LOSE_NOTES[] = {392, 330, 262};

// ---------------------------------------------------------------- lights

uint32_t wheel(uint8_t pos) {
  if (pos < 85) return marquee.Color(255 - pos * 3, pos * 3, 0);
  if (pos < 170) {
    pos -= 85;
    return marquee.Color(0, 255 - pos * 3, pos * 3);
  }
  pos -= 170;
  return marquee.Color(pos * 3, 0, 255 - pos * 3);
}

void lights() {
  if (millis() - lastLights < 50) return;
  lastLights = millis();
  lightFrame++;
  for (int i = 0; i < 8; i++) {
    uint32_t c = 0;
    switch (state) {
      case ATTRACT:  // a slow rainbow chase
        c = wheel((uint8_t)(i * 32 + lightFrame * 4));
        break;
      case CREDIT:
      case WIN:  // everything flashing
        c = (lightFrame / 3) % 2 ? wheel((uint8_t)(i * 32)) : 0;
        break;
      case PLAY:  // one bright dot running along cool blue
        c = (i == lightFrame / 2 % 8) ? marquee.Color(255, 255, 255) : marquee.Color(0, 40, 120);
        break;
      case LOSE:
        c = marquee.Color(120, 0, 0);
        break;
      default:  // the claw is working: warm amber
        c = marquee.Color(160, 90, 0);
        break;
    }
    marquee.setPixelColor(i, c);
  }
  marquee.show();
}

// ---------------------------------------------------------------- setup

void setup() {
  Serial.begin(115200);
  pinMode(X_DIR, OUTPUT);
  pinMode(X_STEP, OUTPUT);
  pinMode(Y_DIR, OUTPUT);
  pinMode(Y_STEP, OUTPUT);
  pinMode(WINCH_A, OUTPUT);
  pinMode(WINCH_B, OUTPUT);
  pinMode(X_HOME, INPUT_PULLUP);
  pinMode(Y_HOME, INPUT_PULLUP);
  pinMode(DROP, INPUT_PULLUP);
  pinMode(COIN, INPUT_PULLUP);
  pinMode(CHUTE, INPUT);
  winch(0);

  claw.attach(CLAW);
  claw.write(CLAW_OPEN);
  marquee.begin();
  marquee.show();
  lcd.init();
  lcd.backlight();
  show("  CLAW MACHINE  ", "   homing...    ");

  Serial.println("Claw machine: homing X and Y");
  delay(10);  // the drivers ignore STEP for a moment after they wake
  home(xAxis, "X");
  home(yAxis, "Y");
  Serial.println("Ready: drop in a coin");
  enter(ATTRACT);
}

// ---------------------------------------------------------------- loop

void loop() {
  // The coin switch and the Drop button, as edges: one press is one event.
  pollCoin();
  while (coinsPending > 0) {
    coinsPending--;
    if (credits < 9) credits++;
    beep(1319, 90);
    Serial.print("Coin in: credits ");
    Serial.println(credits);
    secondsShown = -1;  // redraw whatever screen is up
  }
  // A press counts during play, or in the moment just before it starts.
  bool dropPress = false;
  if (state == PLAY) {
    dropPress = dropPending;
    dropPending = false;
  } else if (state != CREDIT) {
    dropPending = false;
  }

  switch (state) {
    case ATTRACT:
      if (secondsShown != (int)(inState() / 1000) % 2) {
        secondsShown = (inState() / 1000) % 2;
        show("  CLAW MACHINE  ", secondsShown ? "                " : "  INSERT  COIN  ");
      }
      if (credits > 0) {
        credits--;
        enter(CREDIT);
        show("   GET READY!   ", "  steer + DROP  ");
        Serial.println("Credit: get ready");
        jingle(START_NOTES, 4, 120);
      }
      break;

    case CREDIT:
      if (inState() > 800) {
        strongPlay = ++playsSinceStrong >= PLAYS_PER_STRONG_GRIP;
        if (strongPlay) playsSinceStrong = 0;
        Serial.print("Play: ");
        Serial.print(PLAY_SECONDS);
        Serial.println(" seconds on the clock");
        enter(PLAY);
      }
      break;

    case PLAY: {
      drive(xAxis, stickSpeed(analogRead(JOY_X)));
      drive(yAxis, stickSpeed(analogRead(JOY_Y)));
      int left = PLAY_SECONDS - (int)(inState() / 1000);
      if (left != secondsShown) {
        secondsShown = left;
        show("TIME     CRED   ", "move, then DROP");
        lcd.setCursor(5, 0);
        lcd.print(left);
        lcd.setCursor(14, 0);
        lcd.print(credits);
        if (left <= 5 && left > 0) beep(1760, 60);
      }
      if (dropPress || left <= 0) {
        Serial.println(dropPress ? "Drop!" : "Time is up: dropping");
        show("   GOOD LUCK!   ", "                ");
        beep(880, 120);
        winch(+1);
        enter(DROPPING);
      }
      break;
    }

    case DROPPING:
      if (inState() >= WINCH_DOWN_MS) {
        winch(0);
        claw.write(strongPlay ? STRONG_GRIP : SOFT_GRIP);
        Serial.println(strongPlay ? "Grab: strong grip" : "Grab: soft grip");
        enter(GRAB);
      }
      break;

    case GRAB:
      if (inState() >= CLOSE_MS) winch(-1);
      if (inState() >= CLOSE_MS + WINCH_UP_MS) {
        winch(0);
        Serial.println("Return: carrying it to the chute");
        enter(RETURN);
      }
      break;

    case RETURN:
      drive(xAxis, -HOME_SPEED);
      drive(yAxis, -HOME_SPEED);
      if (xAxis.pos <= 0 && yAxis.pos <= 0) {
        claw.write(CLAW_OPEN);
        Serial.println("Release: over the chute");
        enter(RELEASE);
      }
      break;

    case RELEASE:
      if (digitalRead(CHUTE) == HIGH) {
        Serial.println("WIN: the chute beam was broken");
        show("*** WINNER! *** ", " prize in chute ");
        playsSinceStrong = 0;
        enter(WIN);
        jingle(WIN_NOTES, 6, 110);
      } else if (inState() >= CHUTE_WAIT_MS) {
        Serial.println("No prize this time");
        show("   SO CLOSE!    ", "   try again    ");
        enter(LOSE);
        jingle(LOSE_NOTES, 3, 220);
      }
      break;

    case WIN:
    case LOSE:
      if (inState() >= 2500) {
        if (credits > 0) {
          credits--;
          enter(CREDIT);
          show("   GET READY!   ", "  steer + DROP  ");
          jingle(START_NOTES, 4, 120);
        } else {
          Serial.println("Ready: drop in a coin");
          enter(ATTRACT);
        }
      }
      break;
  }

  lights();
}

How it is wired

47 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.

  • Ground: Arduino Uno R3 pin GND
  • Arduino Uno R3 pin 13; Stepper driver, A4988 (1) pin STEP
  • Arduino Uno R3 pin 12; Stepper driver, A4988 (1) pin DIR
  • Arduino Uno R3 pin 11; Stepper driver, A4988 (2) pin STEP
  • Arduino Uno R3 pin 10; Stepper driver, A4988 (2) pin DIR
  • Arduino Uno R3 pin 9; Claw machine cabinet, stepper gantry pin BEAM; Resistor, 100k Ω pin 1
  • Arduino Uno R3 pin 8; Piezo speaker pin 1
  • Arduino Uno R3 pin 7; Motor driver, DRV8833 pin AIN1
  • Arduino Uno R3 pin 6; Motor driver, DRV8833 pin AIN2
  • Arduino Uno R3 pin 5; Arcade button pin 1
  • Arduino Uno R3 pin 4; Limit switch, lever microswitch pin NO
  • Arduino Uno R3 pin 3; Claw machine cabinet, stepper gantry pin YHOME
  • Arduino Uno R3 pin 2; Claw machine cabinet, stepper gantry pin XHOME
  • Arduino Uno R3 pin A0; Thumb stick pin VRY
  • Arduino Uno R3 pin A1; Thumb stick pin VRX
  • Arduino Uno R3 pin A2; Claw machine cabinet, stepper gantry pin CLAW
  • Arduino Uno R3 pin A3; Addressable RGB LEDs pin DIN
  • Arduino Uno R3 pin A4; Character LCD with I2C backpack pin SDA
  • Arduino Uno R3 pin A5; Character LCD with I2C backpack pin SCL
  • 5 V: Claw machine cabinet, stepper gantry pin VCC; Motor driver, DRV8833 pin VM; Motor driver, DRV8833 pin SLP
  • Ground: Claw machine cabinet, stepper gantry pin GND; Arcade button pin 2; Motor driver, DRV8833 pin GND; Limit switch, lever microswitch pin COM; Resistor, 100k Ω pin 2; Piezo speaker pin 2
  • Ground: Stepper driver, A4988 (1) pin MS1; Stepper driver, A4988 (1) pin MS2; Stepper driver, A4988 (1) pin MS3
  • Stepper driver, A4988 (1) pin RST; Stepper driver, A4988 (1) pin SLP
  • 12 V: Stepper driver, A4988 (1) pin VMOT
  • Stepper driver, A4988 (1) pin 2B; Claw machine cabinet, stepper gantry pin XA1
  • Stepper driver, A4988 (1) pin 2A; Claw machine cabinet, stepper gantry pin XA2
  • Stepper driver, A4988 (1) pin 1A; Claw machine cabinet, stepper gantry pin XB1
  • Stepper driver, A4988 (1) pin 1B; Claw machine cabinet, stepper gantry pin XB2
  • Supply: Stepper driver, A4988 (1) pin VDD
  • Ground: Stepper driver, A4988 (1) pin GND
  • Ground: Thumb stick pin GND
  • Supply: Thumb stick pin VCC
  • Ground: Stepper driver, A4988 (2) pin MS1; Stepper driver, A4988 (2) pin MS2; Stepper driver, A4988 (2) pin MS3
  • Stepper driver, A4988 (2) pin RST; Stepper driver, A4988 (2) pin SLP
  • 12 V: Stepper driver, A4988 (2) pin VMOT
  • Stepper driver, A4988 (2) pin 2B; Claw machine cabinet, stepper gantry pin YA1
  • Stepper driver, A4988 (2) pin 2A; Claw machine cabinet, stepper gantry pin YA2
  • Stepper driver, A4988 (2) pin 1A; Claw machine cabinet, stepper gantry pin YB1
  • Stepper driver, A4988 (2) pin 1B; Claw machine cabinet, stepper gantry pin YB2
  • Supply: Stepper driver, A4988 (2) pin VDD
  • Ground: Stepper driver, A4988 (2) pin GND
  • Claw machine cabinet, stepper gantry pin W+; Motor driver, DRV8833 pin AOUT1
  • Claw machine cabinet, stepper gantry pin W-; Motor driver, DRV8833 pin AOUT2
  • Ground: Character LCD with I2C backpack pin GND
  • Supply: Character LCD with I2C backpack pin VCC
  • Supply: Addressable RGB LEDs pin VDD
  • Ground: Addressable RGB LEDs pin VSS

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.