Build a security system with an RFID key card

Arm and disarm a two-sensor alarm with an RFID card. It has an exit delay, an entry delay and a siren, like a real home alarm.
  • Level:Intermediate
  • Duration: 1 h 40 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 security system with an RFID key card – EEMENTOR STEM™ lesson

Real alarm systems don't scream the moment you open the door. They give you a few seconds to leave after arming, and a few seconds to disarm when you come home. In this capstone you build that behaviour yourself. An RFID card arms and disarms the system, a PIR sensor watches for movement, a tilt switch detects when a box, drawer or door is moved, and a buzzer is the siren. The secret is a programming pattern used in almost every machine around you: the state machine.

What you'll learn

  • How to plan a program as a state machine
  • How to read an RFID card and compare it with a stored card
  • How to watch two sensors at the same time
  • How to make LEDs blink and a buzzer beep without delay()

What you need

  • UNO R3 board and USB cable (UNO R3 Starter Kit)
  • RC522 RFID module and S50 card (UNO R3 Starter Kit)
  • PIR motion sensor HC-SR501 (UNO R3 Starter Kit)
  • Green LED, red LED and 2 resistors, 220 Ω (UNO R3 Starter Kit)
  • Tilt switch module (45-in-1 Sensor Kit)
  • Active buzzer module (45-in-1 Sensor Kit)
  • Breadboard and jumper wires (UNO R3 Starter Kit)

The five states

At any moment the alarm is in exactly one of five states. Each state has its own lights and sounds, and only a few things can move it to another state.

State What you see and hear What ends it
Disarmed Green LED on Your card starts the exit delay
Exit delay (10 s) Green LED blinks, one tick per second Time runs out: armed. Your card: disarmed
Armed Red LED blinks slowly A sensor triggers: entry delay. Your card: disarmed
Entry delay (10 s) Red LED blinks fast, fast ticks Your card: disarmed. Time runs out: alarm
Alarm Siren and fast red blinking Your card: disarmed

Wiring

The RC522 runs on 3.3 V. Connecting its power pin to 5V can damage it.

Breadboard wiring diagram: Build a security system with an RFID key card
Breadboard view. Check each connection against the wiring table below.
From To
RC522 SDA UNO pin 10
RC522 SCK UNO pin 13
RC522 MOSI UNO pin 11
RC522 MISO UNO pin 12
RC522 RST UNO pin 9
RC522 3.3V UNO 3V3
RC522 GND GND
RC522 IRQ Not connected
PIR OUT UNO pin 2
PIR VCC and GND 5V and GND
Tilt switch module S UNO pin 3
Tilt switch module + and − 5V and GND
Active buzzer module S UNO pin 8
Active buzzer module + and − 5V and GND
UNO pin 5 220 Ω resistor, then the green LED's long leg
UNO pin 6 220 Ω resistor, then the red LED's long leg
Both LED short legs GND

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

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 card reader. Install the MFRC522 library (by GithubCommunity) in the Arduino IDE. Wire only the RC522 and the two LEDs, upload the sketch and open the Serial Monitor at 9600 baud. Hold your card on the reader. The monitor prints a line such as Card: 0x3A, 0x7F, 0x12, 0xC4.
  2. Your card. Copy the four values into the masterCard line and upload again. Now the card starts the exit delay, and the monitor shows the state changing.
  3. The siren. Add the buzzer module. You now hear the ticks during the delays.
  4. The sensors. Add the PIR sensor and the tilt switch. Give the PIR sensor about a minute to settle after power-up. Tape the tilt switch to the thing you want to protect, such as a box lid.
  5. Test it. Arm the system, step out of the sensor's view and wait for the slow red blink. Walk in: fast ticks. Show your card within ten seconds, or the siren starts.

The sketch

#include <SPI.h>
#include <MFRC522.h>

const int SS_PIN = 10;
const int RST_PIN = 9;
const int PIR_PIN = 2;
const int TILT_PIN = 3;
const int GREEN_LED = 5;
const int RED_LED = 6;
const int BUZZER_PIN = 8;

// Replace with the four values of your own card (shown in the Serial Monitor)
byte masterCard[4] = {0x00, 0x00, 0x00, 0x00};

const unsigned long EXIT_DELAY = 10000;    // time to leave after arming
const unsigned long ENTRY_DELAY = 10000;   // time to show the card after a trigger
const unsigned long SIREN_TIME = 60000;    // the siren stops after one minute

const byte DISARMED = 0, EXIT_WAIT = 1, ARMED = 2, ENTRY_WAIT = 3, ALARM = 4;
const char* const STATE_NAMES[] = {"Disarmed", "Exit delay", "Armed", "Entry delay", "ALARM"};

MFRC522 rfid(SS_PIN, RST_PIN);
byte state = DISARMED;
unsigned long stateStart = 0;
int tiltRest = HIGH;

void enter(byte newState) {
  state = newState;
  stateStart = millis();
  Serial.print(F("State: "));
  Serial.println(STATE_NAMES[newState]);
}

void beep(int times, int ms) {
  for (int i = 0; i < times; i++) {
    digitalWrite(BUZZER_PIN, HIGH);
    delay(ms);
    digitalWrite(BUZZER_PIN, LOW);
    delay(ms);
  }
}

// Returns 0 = no card, 1 = your card, 2 = an unknown card
int readCard() {
  if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return 0;
  bool match = true;
  Serial.print(F("Card: "));
  for (byte i = 0; i < rfid.uid.size; i++) {
    Serial.print(F("0x"));
    if (rfid.uid.uidByte[i] < 0x10) Serial.print(F("0"));
    Serial.print(rfid.uid.uidByte[i], HEX);
    if (i < rfid.uid.size - 1) Serial.print(F(", "));
    if (i < 4 && rfid.uid.uidByte[i] != masterCard[i]) match = false;
  }
  Serial.println();
  rfid.PICC_HaltA();
  return match ? 1 : 2;
}

bool sensorTriggered() {
  bool motion = digitalRead(PIR_PIN) == HIGH;
  bool moved = digitalRead(TILT_PIN) != tiltRest;
  return motion || moved;
}

void lights(bool red, bool green) {
  digitalWrite(RED_LED, red);
  digitalWrite(GREEN_LED, green);
}

void setup() {
  Serial.begin(9600);
  SPI.begin();
  rfid.PCD_Init();
  pinMode(PIR_PIN, INPUT);
  pinMode(TILT_PIN, INPUT_PULLUP);
  pinMode(GREEN_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  enter(DISARMED);
}

void loop() {
  unsigned long now = millis();
  unsigned long inState = now - stateStart;        // time spent in this state
  bool slowBlink = (now / 500) % 2;                // changes twice per second
  bool fastBlink = (now / 125) % 2;                // changes eight times per second

  int card = readCard();
  if (card == 2) beep(3, 80);                      // unknown card
  if (card == 1 && state != DISARMED) {            // your card always disarms
    digitalWrite(BUZZER_PIN, LOW);
    beep(2, 100);
    enter(DISARMED);
    return;
  }

  switch (state) {
    case DISARMED:
      lights(false, true);
      digitalWrite(BUZZER_PIN, LOW);
      if (card == 1) {
        beep(1, 150);
        enter(EXIT_WAIT);
      }
      break;

    case EXIT_WAIT:
      lights(false, slowBlink);
      digitalWrite(BUZZER_PIN, now % 1000 < 60);   // one short tick per second
      if (inState >= EXIT_DELAY) {
        tiltRest = digitalRead(TILT_PIN);          // remember the resting position
        enter(ARMED);
      }
      break;

    case ARMED:
      lights(slowBlink, false);
      digitalWrite(BUZZER_PIN, LOW);
      if (sensorTriggered()) enter(ENTRY_WAIT);
      break;

    case ENTRY_WAIT:
      lights(fastBlink, false);
      digitalWrite(BUZZER_PIN, now % 250 < 60);    // four ticks per second
      if (inState >= ENTRY_DELAY) enter(ALARM);
      break;

    case ALARM:
      lights(fastBlink, false);
      digitalWrite(BUZZER_PIN, inState < SIREN_TIME ? fastBlink : LOW);
      break;
  }
}

How it works

The variable state holds a number from 0 to 4, and the switch statement runs only the block for the current state. Each block does two jobs: it sets the lights and the buzzer for that state, and it checks the few things that can end it. All changes go through enter(), which stores the new state and the time it started, so inState always tells you how long the alarm has been in the current state.

Nothing in loop() waits. Blinking comes from the clock: (now / 500) % 2 is 0 for half a second, then 1 for half a second. That is why the card reader and both sensors are checked hundreds of times per second, even while the lights blink.

The tilt switch can be mounted in any position, so the sketch doesn't assume which reading means "moved". At the moment the system arms, it stores the switch's resting value in tiltRest, and any change from that value counts as a trigger. The siren stops after one minute so that it doesn't annoy the neighbours, but the red light keeps flashing to show that something happened.

Try this next

  • Allow a second card by adding another array and checking both.
  • Count how many times the alarm was triggered and print it when you disarm.
  • Add a "panic" feature: an unknown card three times in a row starts the alarm.
  • Swap the tilt switch for a reed switch from the Sensor Kit and a small magnet to make a real door sensor.

Troubleshooting

  • No card line appears in the Serial Monitor: check the RC522 wiring, especially 3.3V, GND, SDA and RST. Hold the card flat on the reader.
  • The buzzer beeps three times for your own card: the values in masterCard don't match. Copy them again from the Serial Monitor.
  • The entry delay starts immediately after arming: the PIR sensor still sees you. Leave its view during the exit delay, and wait a minute after power-up before testing.
  • The buzzer is on when it should be off: some buzzer modules switch on with LOW. Swap HIGH and LOW in the buzzer lines, or use the bare active buzzer from the UNO kit between pin 8 and GND.
  • The tilt switch triggers by itself: tape it down firmly. It contains a small rolling ball that reacts to vibration.

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