Build a car park barrier that counts free spaces

An automatic barrier for a model car park: it opens for arriving and leaving cars, counts the free spaces on the LCD and stays shut when the car park is full.
  • Level:Intermediate
  • Duration: 1 h 50 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 car park barrier that counts free spaces – EEMENTOR STEM™ lesson

Car park barriers look simple, but they do several jobs at once: they notice a car, decide whether to let it in, open, wait until the car is safely through, close and keep count. In this capstone you build a working model for toy cars. An ultrasonic sensor watches the entrance, an infrared sensor watches the exit, a servo is the barrier, two LEDs are the traffic light and the LCD shows how many spaces are free.

What you'll learn

  • How to split a bigger program into small functions with one job each
  • How to use two different sensors for the same task
  • How to move a servo slowly and smoothly
  • How to wait for "all clear" before closing a barrier

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)
  • Ultrasonic sensor HC-SR04 (UNO R3 Starter Kit)
  • Servo motor (UNO R3 Starter Kit)
  • Red LED, green LED and 2 resistors, 220 Ω (UNO R3 Starter Kit)
  • IR obstacle avoidance sensor module (45-in-1 Sensor Kit)
  • A drinking straw or a strip of card for the barrier arm, tape, and a toy car
  • Breadboard and jumper wires (UNO R3 Starter Kit)

Before you start

The LCD has 16 connections in a row. If yours has no pins, solder 16 pins from the 40-pin male header in the kit first, so it plugs into the breadboard. Ask an adult to help if you're new to soldering.

Wiring

Breadboard wiring diagram: Build a car park barrier that counts free spaces
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 A (backlight +) 220 Ω resistor, then 5V
Potentiometer outer pins 5V and GND
Ultrasonic sensor VCC and GND 5V and GND
Ultrasonic sensor Trig UNO pin 8
Ultrasonic sensor Echo UNO pin 7
IR obstacle sensor OUT UNO pin 6
IR obstacle sensor + and GND 5V and GND
UNO pin 10 220 Ω resistor, then the red LED's long leg
UNO pin 13 220 Ω resistor, then the green LED's long leg
Both LED short legs GND
Servo orange / yellow wire UNO pin 9
Servo red wire 5V
Servo brown / black wire GND

Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.

Pin 13 is also connected to the small LED marked L on the UNO, so that LED lights together with your green one. If your IR module has a jumper next to the EN pin, leave the jumper in place and leave EN unconnected.

Build it in stages

This is a bigger build than the earlier lessons, so don't wire everything at once. Add one part, test it, then add the next. When something stops working, you know it's the part you just added.

  1. The screen. Wire the LCD and the potentiometer and upload the sketch. It shows "Free spaces: 4".
  2. The barrier. Add the servo and the two LEDs. Tape a straw to the servo horn as the barrier arm. At power-up the arm moves to the closed position and the red LED lights.
  3. The entrance. Add the ultrasonic sensor, pointing at the place where a car waits in front of the barrier. Hold a toy car closer than 10 cm: the barrier opens, and it closes about 1.5 seconds after you take the car away. The count drops to 3.
  4. The exit. Add the IR sensor on the other side of the barrier, pointing across the lane. Keep the lane empty while the UNO starts, because the sketch learns the sensor's resting state at power-up. Move a car past it: the barrier opens and the count goes back up.
  5. Set up your car park. Place the entrance sensor outside the barrier and the exit sensor inside. Drive four cars in and try a fifth.

The sketch

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

// LCD pins: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
Servo gate;

const int TRIG_PIN = 8;
const int ECHO_PIN = 7;
const int EXIT_PIN = 6;
const int SERVO_PIN = 9;
const int RED_LED = 10;
const int GREEN_LED = 13;

const int SPACES = 4;            // size of your car park
const int CAR_DISTANCE = 10;     // a car is waiting when it's closer than this (cm)
const int GATE_CLOSED = 0;       // servo angles: adjust for your barrier arm
const int GATE_OPEN = 90;

int freeSpaces = SPACES;
int gateAngle = GATE_CLOSED;
int exitIdle = HIGH;

long distanceCm() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  long echoTime = pulseIn(ECHO_PIN, HIGH, 25000);   // give up after 25 ms
  if (echoTime == 0) return 999;                    // nothing in range
  return echoTime / 58;
}

bool carAtEntrance() { return distanceCm() < CAR_DISTANCE; }
bool carAtExit()     { return digitalRead(EXIT_PIN) != exitIdle; }

void moveGate(int target) {
  while (gateAngle != target) {
    gateAngle += target > gateAngle ? 1 : -1;       // one degree at a time
    gate.write(gateAngle);
    delay(8);                                       // small steps = smooth movement
  }
}

void showStatus() {
  lcd.clear();
  if (freeSpaces > 0) {
    lcd.print(F("Free spaces: "));
    lcd.print(freeSpaces);
  } else {
    lcd.print(F("Car park FULL"));
  }
}

void letCarThrough(const __FlashStringHelper* message) {
  lcd.setCursor(0, 1);
  lcd.print(message);
  digitalWrite(RED_LED, LOW);
  digitalWrite(GREEN_LED, HIGH);
  moveGate(GATE_OPEN);

  // Close only when both sensors have seen nothing for 1.5 seconds
  unsigned long clearSince = millis();
  while (millis() - clearSince < 1500) {
    if (carAtEntrance() || carAtExit()) clearSince = millis();
    delay(50);
  }

  digitalWrite(GREEN_LED, LOW);
  digitalWrite(RED_LED, HIGH);
  moveGate(GATE_CLOSED);
}

void setup() {
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  pinMode(EXIT_PIN, INPUT);
  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  digitalWrite(RED_LED, HIGH);

  lcd.begin(16, 2);
  gate.attach(SERVO_PIN);
  gate.write(GATE_CLOSED);
  delay(500);
  exitIdle = digitalRead(EXIT_PIN);                 // the exit lane must be empty now
  showStatus();
}

void loop() {
  if (carAtExit()) {
    letCarThrough(F("Goodbye!"));
    if (freeSpaces < SPACES) freeSpaces++;
    showStatus();
  } else if (carAtEntrance()) {
    delay(200);
    if (!carAtEntrance()) return;                   // ignore a single stray reading
    if (freeSpaces > 0) {
      letCarThrough(F("Welcome!"));
      freeSpaces--;
      showStatus();
    } else {
      lcd.setCursor(0, 1);
      lcd.print(F("Please wait"));
      for (int i = 0; i < 6; i++) {                 // flash the red light
        digitalWrite(RED_LED, i % 2);
        delay(250);
      }
      showStatus();
    }
  }
  delay(100);
}

How it works

The sketch is built from small functions that each answer one question or do one job: carAtEntrance(), carAtExit(), moveGate(), showStatus() and letCarThrough(). With those in place, loop() reads almost like a description of the barrier: if a car is at the exit, let it through and add a space; if a car is at the entrance and there's room, let it through and take a space away.

The two sensors work in different ways. The ultrasonic sensor sends a short burst of sound and measures how long the echo takes: sound needs about 58 microseconds to travel one centimetre and back. The infrared sensor only reports "something is there" or "nothing is there". The sketch stores its resting value at power-up and treats any change as a car, so it works with modules that switch to LOW and with modules that switch to HIGH.

The most important part is how the barrier closes. A car that drives in passes the entrance sensor first and the exit sensor a moment later, so letCarThrough() watches both sensors and restarts its 1.5-second timer each time either one sees something. The arm comes down only when the lane has been completely clear for that long, and each car is counted exactly once.

moveGate() moves the servo one degree at a time with a short pause between steps. That is slower than a single write(), but it looks like a real barrier and puts less strain on the servo and the power supply.

Try this next

  • Add the active buzzer and beep while the barrier is moving.
  • Add a button for the attendant that opens the barrier even when the car park is full.
  • Count the total number of cars that entered today and show it on the second line.
  • Store freeSpaces in EEPROM so the count survives a power cut.

Troubleshooting

  • The screen lights up but shows only blocks or nothing: turn the potentiometer slowly from one end to the other.
  • The barrier opens and closes all the time: the ultrasonic sensor sees the table or the barrier arm. Raise it a little, point it away from the arm, or lower CAR_DISTANCE.
  • The barrier never closes: one of the sensors still sees something. Check that the barrier arm doesn't pass in front of a sensor, and turn the small screw on the IR module to shorten its range.
  • The barrier opens at power-up and counts a car leaving: something was in front of the IR sensor when the UNO started. Clear the lane and press the reset button.
  • The arm points the wrong way: change GATE_CLOSED and GATE_OPEN, or pull the servo horn off and push it back on at a different angle.
  • The UNO restarts when the servo moves: use a different USB port or a powered hub.

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