Built-in project · Arduino Uno R3

Touchless soap dispenser on an Arduino Uno

An FC-51 infrared obstacle sensor and a pump on a transistor: hold a hand under the spout and it pumps one dose, then waits for the hand to go away. The whole build, an Arduino Uno and 4 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.

Beginner About 20 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $32. On Mokxi they're free.

Step by step, with the wiring and the common mistakes: Arduino IR Obstacle Sensor (FC-51): Wiring, Code

The circuit runs here once the page has loaded.

What you'll learn

  • An infrared obstacle sensor with a digital output
  • Switching a pump with a transistor and a flyback diode
  • One action per event, not one per loop
  • Waiting for a signal to stay clear
  • Difficulty: Beginner
  • Time: About 20 minutes
  • Board: Arduino Uno R3
  • Parts: 6

How it works

An FC-51 infrared sensor under the spout shines infrared forward and watches for it to bounce back. When a hand is close, its OUT pin, on pin 2, goes low.

A small pump hangs off 5V and is switched by a transistor, driven from pin 9 through a 1k resistor. A diode across the pump catches the voltage spike when it stops.

When a hand is detected the sketch pumps one dose for 700 ms and counts it. Then it waits until the hand has been gone for half a second before it will pump again. Otherwise holding a hand there would empty the bottle.

Parts list

6 parts, plus the jumper wires. They retail for about $32; on Mokxi they're free.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Resistor, 1k Ω pin 1Arduino Uno R3 pin 9
Touchless soap dispenser pin OUTArduino Uno R3 pin 2
Touchless soap dispenser pin VCCArduino Uno R3 pin 5V
Touchless soap dispenser pin M+Arduino Uno R3 pin 5V
Diode pin CArduino Uno R3 pin 5V
Touchless soap dispenser pin GNDArduino Uno R3 pin GND
Transistor (NPN or PNP) pin EArduino Uno R3 pin GND
Transistor (NPN or PNP) pin CTouchless soap dispenser pin M-
Diode pin ATouchless soap dispenser pin M-
Resistor, 1k Ω pin 2Transistor (NPN or PNP) pin B

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
// Touchless soap dispenser: an FC-51 infrared obstacle sensor under the spout,
// and a small pump on a transistor.
//
//   pin 2   the FC-51's OUT (low when a hand is in range)
//   pin 9   through 1 k into the transistor's base; the pump hangs off 5 V
//           into the collector, with a diode across it, band to 5 V
//   5V/GND  the FC-51's VCC and GND
//
// The FC-51 shines infrared forward and watches for it to come back. A hand
// a few centimeters under the spout sends plenty back and OUT goes low; with
// the trimmer in the middle a white card trips it at about 13 cm. Skin is
// somewhere between white and black, so the spout's sensor is set to see a
// hand only when it is right underneath.
//
// # One dose per hand
//
// A dispenser that pumps for as long as a hand is there empties the bottle.
// So the sketch pumps one dose, then waits until OUT has been high for half a
// second (the hand has gone) before it will pump again.

const int SENSOR = 2;
const int PUMP = 9;
const unsigned long DOSE_MS = 700;
const unsigned long AWAY_MS = 500;

int doses = 0;

void setup() {
  Serial.begin(115200);
  Serial.println("Mokxi Arduino Uno: touchless soap dispenser");
  pinMode(SENSOR, INPUT);
  pinMode(PUMP, OUTPUT);
  digitalWrite(PUMP, LOW);
}

bool handThere() {
  return digitalRead(SENSOR) == LOW;
}

void loop() {
  if (!handThere()) {
    delay(20);
    return;
  }
  doses++;
  Serial.print("hand: dose ");
  Serial.println(doses);
  digitalWrite(PUMP, HIGH);
  delay(DOSE_MS);
  digitalWrite(PUMP, LOW);

  // Wait for the hand to leave, and stay gone for AWAY_MS.
  unsigned long clear = millis();
  while (millis() - clear < AWAY_MS) {
    if (handThere()) clear = millis();
    delay(10);
  }
  Serial.println("ready");
}

Code walkthrough

The key functions of the sketch, and what each one does.

setup sketch.ino

Starts serial at 115200 baud and prints a startup line. It sets the sensor pin as an input and the pump pin as an output, and makes sure the pump starts off.

void setup() {
  Serial.begin(115200);
  Serial.println("Mokxi Arduino Uno: touchless soap dispenser");
  pinMode(SENSOR, INPUT);
  pinMode(PUMP, OUTPUT);
  digitalWrite(PUMP, LOW);
}

handThere sketch.ino

Returns true when the sensor pin reads LOW. The FC-51 pulls OUT low when it sees something, so LOW means a hand. A small helper like this keeps the main loop easy to read.

bool handThere() {
  return digitalRead(SENSOR) == LOW;
}

loop sketch.ino

If no hand is there, it waits 20 ms and returns. If a hand is there, it adds to doses, prints it, runs the pump for DOSE_MS, then loops until the sensor has stayed clear for AWAY_MS. Any hand seen restarts that timer. Then it prints "ready".

void loop() {
  if (!handThere()) {
    delay(20);
    return;
  }
  doses++;
  Serial.print("hand: dose ");
  Serial.println(doses);
  digitalWrite(PUMP, HIGH);
  delay(DOSE_MS);
  digitalWrite(PUMP, LOW);

  // Wait for the hand to leave, and stay gone for AWAY_MS.
  unsigned long clear = millis();
  while (millis() - clear < AWAY_MS) {
    if (handThere()) clear = millis();
    delay(10);
  }
  Serial.println("ready");
}

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The pump runs again and again This project
    The wait for the hand to leave is what prevents repeats. Check that AWAY_MS was not set to 0.
  • The sensor never triggers This project
    Its OUT goes to pin 2, VCC to 5V and GND to ground. Keep the hand within range under the spout.
  • The pump never runs, or runs all the time. Touchless soap dispenser
    The FC-51 output goes LOW when it sees something, and HIGH when clear. Test it alone first and adjust its trimmer for about 2 cm to 5 cm range. Flip the logic in code if needed.
  • The pump makes the board reset. Touchless soap dispenser
    A pump draws more than a pin gives. Drive it with a transistor or a MOSFET, add a flyback diode across the motor, and power it from a separate supply with the grounds joined.
  • The load never turns on, or is always on, because the legs are in the wrong order. Transistor (NPN or PNP)
    Check the pinout in the datasheet for your part. A 2N2222 or BC547 can have emitter, base, collector in a different order. Flat side orientation is easy to get wrong.
  • The base is wired straight to the pin with no resistor, or the motor has no flyback diode. Transistor (NPN or PNP)
    Put about 1k between the pin and the base. For motors and relays, add a diode across the coil with the stripe toward the positive side.
  • The LED is dim, burns out, or the pin reads wrong because the resistor value is off. Resistor
    Read the color bands again. A 220 ohm to 330 ohm resistor suits a typical LED on 5 V. 10k is a common pull-up or pull-down value. Check with a multimeter if unsure.
  • A pull-up or pull-down resistor is missing, so the input floats and reads random values. Resistor
    Tie the input to 5 V or ground through about 10k. Or use INPUT_PULLUP and wire the switch to ground.

Change it and keep it

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