Projects

Systems & Hardware

Proximity-Activated Candy Dispenser

ECE203, Princeton · Spring 2026 · with Katie Lee
Arduino · C++ · VCNL4010 · NE555 timer · Servo

Engineering
C++ArduinoCircuit Debugging

Katie Lee and I built this for Princeton's ECE203: a hand-proximity-triggered candy dispenser that watches for a hand with an IR sensor, holds its own cooldown on an analog timer, and dispenses through a servo swing. The build is simple. The debugging wasn't.

The full build on the bench: cardboard chassis, breadboard, oscilloscope, and Arduino, with the blue LED lit to show the system at rest.
Idle state. Blue LED on, servo at 0°, proximity sensor reading its baseline.

Five parts share one 5V rail: a VCNL4010 proximity sensor, an Arduino Uno running the decision logic, an NE555 timer holding a hardware cooldown so a lingering hand can't re-trigger mid-cycle, a servo swinging 0° to 90° and back to dispense, and two LEDs, red mid-cycle and blue at reset, that report system state without anyone needing to open the Serial Monitor.

Hand-drawn block diagram showing signal flow from the VCNL4010 sensor through the Arduino Uno to the servo motor, LEDs, and 555 timer IC.
Signal flow: sensor in, Arduino as the decision point, servo and LEDs out, 555 timer holding the cooldown.
Full circuit diagram built in Wokwi, showing the Arduino Uno, VCNL4010 breakout, NE555 timer breakout, servo, and LED wiring on a breadboard.
Full circuit, verified in Wokwi before it went on the breadboard.

First fault was the sensor: a flat, unchanging proximity reading no matter how close a hand got, even though the Arduino still recognized it on the bus. Our first suspicion was a short from soldering. What we'd actually left out was pull-up resistors: without 4.7kΩ pull-ups on SDA and SCL, the Arduino couldn't resolve a clean high from a clean low on the I²C lines, so the readings never moved. Adding them turned a dead trace into a sensor that held a stable ~2,180 counts at rest and spiked past 3,000 the moment a hand came into range, comfortably clear of the ~2,800–3,000 threshold we settled on.

Close-up of the breadboard with the red and blue feedback LEDs lit.
The feedback pair on the breadboard: red mid-cycle, blue at reset.

Second fault was the 555 timer, and it took longer to pin down because it wasn't consistent. Some cycles the ~3.3-second cooldown, T = 1.1 × 100kΩ × 30μF, ran and reset cleanly; other times the timer output latched high and never returned, locking the system out of its own reset. We went through it point by point with a multimeter, at different points suspecting a dead capacitor and a floating trigger pin. Both turned out to be real: swapping the capacitor and adding a 10kΩ pull-up to the trigger pin fixed it.

Serial Plotter capture showing two full activation cycles: the proximity trace rising past the detection threshold in sync with the red LED signal going high and the blue LED signal going low.
Two full activation cycles on the Serial Plotter. Proximity crosses the threshold, the red and blue LED signals flip in step, and the ~3-second cooldown holds identically each time.
The servo mechanism mid-rotation at roughly 45 degrees, held in a hand.
Servo mid-swing, roughly 45°.
The servo mechanism at roughly 90 degrees of rotation, dispensing a mint through the cardboard chassis.
Full swing, ~90°, dispensing.

Neither fault announced which layer it lived in. A sensor reading nothing could be a bad connection, a bad component, or a bad protocol configuration, and only one of those was true here; a timer that runs correctly most of the time looks like intermittent bad luck until you've ruled out the capacitor and the floating pin separately. What actually worked was checking each layer in isolation before touching the code: retrace the physical wiring, cross-check it against expected circuit behavior, and only then look at logic.

Given more time, the LEDs would wire directly to the 555 output instead of routing through the Arduino, making the feedback fully hardware-driven and independent of software state. The fixed timing resistor would become a potentiometer so the cooldown is tunable without reflashing. And the cardboard chassis, right for a prototype built against a deadline, would become an enclosure that shields the sensor from ambient light instead of one taped together from whatever was on the bench.

Arduino sketch
#include <Wire.h>
#include <Servo.h>
#include "Adafruit_VCNL4010.h"

Adafruit_VCNL4010 vcnl;
Servo myServo;

const int SERVO_PIN = 9;
const int LED_BLUE_PIN = 5;
const int LED_RED_PIN = 6;
const int TIMER_OUT_PIN = 3;
const int TIMER_IN_PIN = 4;

const uint16_t PROX_THRESHOLD = 3000;
const int      SERVO_OPEN_DEG = 90;
const int      SERVO_CLOSE_DEG = 0;
const int      DISPENSE_MS = 600;

bool inCooldown = false;

void setup() {
  Serial.begin(9600);
  delay(1000);

  Wire.begin();
  Wire.setClock(100000);

  pinMode(LED_BLUE_PIN, OUTPUT);
  pinMode(LED_RED_PIN, OUTPUT);
  pinMode(TIMER_OUT_PIN, OUTPUT);
  pinMode(TIMER_IN_PIN, INPUT);
  digitalWrite(TIMER_OUT_PIN, LOW);

  myServo.attach(SERVO_PIN);
  myServo.write(SERVO_CLOSE_DEG);

  if (!vcnl.begin()) {
    while (1) {
      digitalWrite(LED_BLUE_PIN, HIGH);
      digitalWrite(LED_RED_PIN, LOW);
      delay(300);
      digitalWrite(LED_BLUE_PIN, LOW);
      digitalWrite(LED_RED_PIN, HIGH);
      delay(300);
    }
  }

  digitalWrite(LED_BLUE_PIN, HIGH);
  digitalWrite(LED_RED_PIN, LOW);
}

void sweepTo(int targetDeg) {
  myServo.write(targetDeg);
  delay(100);
}

void dispense() {
  inCooldown = true;

  digitalWrite(LED_BLUE_PIN, LOW);
  digitalWrite(LED_RED_PIN, HIGH);

  digitalWrite(TIMER_OUT_PIN, HIGH);
  delay(10);
  digitalWrite(TIMER_OUT_PIN, LOW);

  sweepTo(SERVO_OPEN_DEG);
  delay(DISPENSE_MS);
  sweepTo(SERVO_CLOSE_DEG);

  while (inCooldown && digitalRead(TIMER_IN_PIN) == HIGH) {
    delay(50);
  }

  inCooldown = false;
  digitalWrite(LED_RED_PIN, LOW);
  digitalWrite(LED_BLUE_PIN, HIGH);
}

void loop() {
  if (!inCooldown && vcnl.readProximity() > PROX_THRESHOLD) {
    dispense();
  }

  delay(50);
}
All projects