Home / Projects / Raspberry Pi Projects / Raspberry Pi Pico Access Control System Using the Elecrow Pico Advanced Kit
pcbway

Raspberry Pi Pico Access Control System Using the Elecrow Pico Advanced Kit

Access control systems are commonly used in offices, schools, laboratories, apartments, and industrial facilities. At the center of these systems is a controller that decides whether a person is allowed to enter, controls the door lock, and monitors alarm inputs.

In this tutorial, I will build a small access control system using a Raspberry Pi Pico and several modules from the Elecrow Raspberry Pi Pico Advanced Kit. The system can read an RFID card, accept a PIN from a keypad, show messages on an LCD, control a solenoid lock through a relay, and respond to fire and tamper alarm inputs.

This is not meant to replace a real commercial access control panel, but it is a very good learning project if you want to understand how access control logic works at the microcontroller level.


Project Overview

The Raspberry Pi Pico will act as the main controller. It will accept access requests from two sources:

  1. An RFID card scanned through the RC522 RFID module
  2. A PIN entered through a membrane keypad

If the card UID or PIN is valid, the Pico activates a relay driver. The relay then powers a solenoid lock for a few seconds to simulate unlocking a door.

The system also monitors two alarm inputs:

  1. Fire alarm input
  2. Tamper alarm input

When the fire alarm is triggered, the door is released and the buzzer sounds. This simulates a real access control system where doors may be released during emergency conditions.

When the tamper alarm is triggered, the system keeps the door locked and sounds the buzzer. This simulates someone trying to force open or tamper with the door or panel.

About the Elecrow Raspberry Pi Pico Advanced Kit

raspberry pi pico

For this project, I used modules from the Elecrow Raspberry Pi Pico Advanced Kit. The kit includes a Raspberry Pi Pico, breadboards, LEDs, an RGB module, sound sensor, PIR motion sensor, photoresistor module, passive buzzer, vibration sensor, magnetic spring module, soil moisture sensor, potentiometer, servo, joystick, RC522 RFID module, 4-digit display, traffic light module, rotary encoder, 1602 LCD, temperature and humidity sensor, raindrops module, flame sensor, OLED module, membrane keypad, smart car parts, ultrasonic sensor, infrared remote, and several other accessories.

For this access control project, I will use the parts that make sense for a small door controller:

  • Raspberry Pi Pico
  • RC522 RFID module
  • Membrane keypad
  • 1602 LCD module
  •  Passive buzzer
  • LEDs (traffic light module)
  • Flame sensor or button for fire alarm simulation
  • Vibration sensor for tamper detection

I will also use an external solenoid lock and relay driver. These are not driven directly by the Pico because a solenoid needs more current than a GPIO pin can provide.


How the System Works

Here is the basic block diagram of the project:

The Pico continuously checks the system inputs. In normal standby mode, the LCD asks the user to scan a card or enter a PIN.

If access is granted, the relay turns on and the solenoid unlocks for a short time. If access is denied, the buzzer gives an error sound and the red LED turns on.

The fire alarm and tamper alarm have higher priority than normal card or PIN access.


Features of This Project

This project includes the following features:

  • RFID card access using the RC522 module
  • PIN access using a membrane keypad
  • LCD status messages
  • Solenoid lock control using a relay driver
  • Buzzer feedback
  • LED status indicators
  • Fire alarm input
  • Tamper alarm input
  • Optional door contact monitoring
  • MicroPython code for beginners

Required Components

Most of the components used in this project are included in the Elecrow Raspberry Pi Pico Advanced Kit. I also added a solenoid lock and relay driver to make the project closer to a real access control setup.

Component Purpose
Raspberry Pi Pico Main controller
RC522 RFID module Reads RFID cards
Membrane keypad PIN entry
1602 LCD module Displays system status
Passive buzzer Alarm and feedback sound
Red LED Access denied / alarm indicator
Green LED Access granted indicator
Yellow LED Standby / warning indicator
Flame sensor or button Fire alarm input
Vibration sensor Tamper alarm input
Relay driver module Controls the solenoid lock
Solenoid lock Door lock output
External power supply Powers the solenoid lock

You can also use a servo motor instead of a solenoid lock if you want a safer beginner version of the project. However, using a relay and solenoid makes the project feel closer to a real access control system.


Important Safety Note About Solenoid Locks

The Raspberry Pi Pico cannot directly drive a solenoid lock. A solenoid usually requires more voltage and current than a Pico GPIO pin can provide.

The correct setup is:

Pico GPIO → Relay Driver Input → Relay Contact → Solenoid Power

The Pico only controls the relay input. The solenoid must have its own power supply.

Also, make sure the Pico ground and relay module ground are connected together, unless you are using a fully isolated relay module.

If your relay board or solenoid driver does not already include flyback protection, you should add a diode across the solenoid coil to protect the circuit from voltage spikes.

For a real door access system, use proper certified hardware. This tutorial is only for learning and prototyping.


Pin Connections

The exact pins can be changed in the code, but I will use this pin assignment for the tutorial.

RC522 RFID Module to Raspberry Pi Pico

The RC522 uses SPI communication.

RC522 Pin Raspberry Pi Pico Pin
SDA / SS GP17
SCK GP18
MOSI GP19
MISO GP16
RST GP20
3.3V 3V3
GND GND

Do not connect the RC522 to 5V. The RC522 module works with 3.3V logic.


I2C LCD to Raspberry Pi Pico

This tutorial assumes that the 1602 LCD has an I2C backpack.

LCD Pin Raspberry Pi Pico Pin
VCC VBUS or 5V
GND GND
SDA GP4
SCL GP5

Most I2C LCD backpacks work at 5V, but the I2C pull-ups may also pull SDA and SCL to 5V. Many hobby modules work in practice, but the safer method is to use a level shifter or power the backpack in a way that keeps the I2C lines at 3.3V.

If your LCD has 16 pins instead of 4 pins, then it is probably a parallel LCD and will need different wiring and a different library.


Keypad to Raspberry Pi Pico

For a 4x4 membrane keypad:

Keypad Line Pico Pin
Row 1 GP6
Row 2 GP7
Row 3 GP8
Row 4 GP9
Column 1 GP10
Column 2 GP11
Column 3 GP12
Column 4 GP13

If you are using a 4x3 keypad, you can modify the keypad map and remove one column.


Relay Driver and Solenoid Lock

Relay Module Pin Pico / Supply Connection
IN GP14
VCC 5V or relay module required voltage
GND GND

The solenoid is connected through the relay contacts, not directly to the Pico.

A simple wiring idea is:

Solenoid Power +  → Relay COM
Relay NO          → Solenoid +
Solenoid -        → Solenoid Power -

When the relay turns on, the solenoid receives power and unlocks.

Some relay modules are active-low. This means the relay turns on when the Pico output is LOW instead of HIGH. I will make this configurable in the code.


Buzzer and LEDs

Part Pico Pin
Buzzer GP15
Green LED GP21
Red LED GP22
Yellow LED GP26

Use current-limiting resistors for the LEDs.


Alarm Inputs

Alarm Input Suggested Component Pico Pin
Fire alarm input Flame sensor DO or push button GP27
Tamper alarm input Vibration sensor or magnetic sensor GP28
Door contact, optional Magnetic spring module GP2

For beginner testing, I recommend using push buttons first. Once the logic works, you can replace the buttons with the flame sensor and tamper sensor.

This is my setup on a breadboard. I didn't use the relay + solenoid lock yet; the red LED on the middle of the breadboard will serve as the indicator that the lock released or not.

Raspberry Pi Pico Access Control Setup


Installing MicroPython on the Raspberry Pi Pico

Before uploading the code, install MicroPython on the Raspberry Pi Pico. You can download the latest UF2 file in the official MicroPython website.

  1. Hold the BOOTSEL button on the Pico.
  2. Connect the Pico to your computer using a USB cable.
  3. Release the BOOTSEL button.
  4. The Pico should appear as a USB drive.
  5. Copy the MicroPython UF2 file to the Pico.
  6. Open Thonny.
  7. Select the MicroPython interpreter for Raspberry Pi Pico.

Once this is done, you can run MicroPython code on the Pico using Thonny.


Required MicroPython Libraries

This project needs libraries for:

  • RC522 RFID module
  • I2C LCD

For the RC522, you need a file named:

mfrc522.py

For the I2C LCD, you need files such as:

lcd_api.py
pico_i2c_lcd.py

You can find the mfrc522.py in this library, while the I2C LCD files are in this library.

Upload these files to the Raspberry Pi Pico using Thonny. You can place them directly on the Pico or inside a /lib folder.

 


Testing the RFID Reader First

Before combining all parts, it is better to test the RFID reader alone. This helps you get the UID of your RFID card.

A card UID may look something like this:

12 AB 34 CD

You will later place this UID inside the list of authorized cards.

In the final code, the authorized cards are stored here:

AUTHORIZED_CARDS = [
    z">"12 AB 34 CD",
    z">"A1 B2 C3 D4"
]

Replace these values with your actual card UID.


System Behavior

The system has several operating conditions.

Idle Mode

In idle mode, the LCD shows:

PICO ACCESS
SCAN CARD/PIN

The system waits for an RFID card or keypad input.


Access Granted

If the card or PIN is valid:

ACCESS GRANTED
DOOR UNLOCKED

The green LED turns on, the buzzer gives a short beep, and the relay unlocks the solenoid for a few seconds.


Access Denied

If the card or PIN is invalid:

ACCESS DENIED
TRY AGAIN

The red LED turns on and the buzzer gives an error sound.


Fire Alarm

If the fire alarm input is triggered:

FIRE ALARM!
DOOR RELEASED

The door unlocks and the buzzer sounds repeatedly.

This is done because many access control systems release doors during emergency conditions. Again, this project is only a simulation.


Tamper Alarm

If the tamper input is triggered:

TAMPER ALARM!
SYSTEM LOCKED

The system sounds the buzzer and flashes the red LED. The door remains locked.


Event Priority

The Pico should not treat all events equally. Alarm inputs should have higher priority than normal access requests.

For this project, I use this priority:

Priority Event Action
1 Fire alarm Unlock door and sound alarm
2 Tamper alarm Keep locked and sound alarm
3 Valid RFID or PIN Unlock door temporarily
4 Invalid RFID or PIN Deny access
5 Idle Wait for input

Fire alarm has the highest priority because it is a safety-related condition.


Final MicroPython Code

Upload the required libraries first, then save the following code as

main.py

on the Raspberry Pi Pico.

This code assumes:

  • RC522 uses SPI0
  • LCD uses I2C0
  • Keypad is 4x4
  • Relay output is active-high by default
  • Fire and tamper inputs are active-low using pull-up resistors

If your relay module is active-low, change:

RELAY_ACTIVE_HIGH = True

to:

RELAY_ACTIVE_HIGH = False

Here is the complete code:

from machine import Pin, PWM, I2C, SPI
from time import sleep, ticks_ms, ticks_diff

# ------------------------------------------------------------
# Optional libraries
# ------------------------------------------------------------
# These imports depend on the library files you uploaded.
# If your library uses different class names, adjust these lines.

try:
    from mfrc522 import MFRC522
except ImportError:
    MFRC522 = None

try:
    from pico_i2c_lcd import I2cLcd
except ImportError:
    I2cLcd = None


# ------------------------------------------------------------
# User settings
# ------------------------------------------------------------

AUTHORIZED_CARDS = [
    "C3 A4 13 29",
    "12 AB 34 CD",
    "A1 B2 C3 D4"
]

AUTHORIZED_PIN = "1234"

ALARM_CHECK_INTERVAL_SECONDS = 5
UNLOCK_TIME_SECONDS = 3

RELAY_ACTIVE_HIGH = True

LCD_I2C_ADDR = 0x27
LCD_ROWS = 2
LCD_COLS = 16


# ------------------------------------------------------------
# Pin assignments
# ------------------------------------------------------------

# RFID RC522 SPI pins
RFID_SCK_PIN = 18
RFID_MOSI_PIN = 19
RFID_MISO_PIN = 16
RFID_CS_PIN = 17
RFID_RST_PIN = 20

# LCD I2C pins
LCD_SDA_PIN = 4
LCD_SCL_PIN = 5

# Keypad pins
ROW_PINS = [6, 7, 8, 9]
COL_PINS = [10, 11, 12, 13]

# Outputs
RELAY_PIN = 14
BUZZER_PIN = 15
GREEN_LED_PIN = 21
RED_LED_PIN = 22
YELLOW_LED_PIN = 26

# Inputs
FIRE_INPUT_PIN = 27
TAMPER_INPUT_PIN = 28
DOOR_CONTACT_PIN = 2


# ------------------------------------------------------------
# Hardware setup
# ------------------------------------------------------------

green_led = Pin(GREEN_LED_PIN, Pin.OUT)
red_led = Pin(RED_LED_PIN, Pin.OUT)
yellow_led = Pin(YELLOW_LED_PIN, Pin.OUT)

relay = Pin(RELAY_PIN, Pin.OUT)

buzzer = PWM(Pin(BUZZER_PIN))
buzzer.duty_u16(0)

fire_input = Pin(FIRE_INPUT_PIN, Pin.IN, Pin.PULL_UP)
tamper_input = Pin(TAMPER_INPUT_PIN, Pin.IN, Pin.PULL_UP)
door_contact = Pin(DOOR_CONTACT_PIN, Pin.IN, Pin.PULL_UP)

row_pins = [Pin(pin, Pin.OUT) for pin in ROW_PINS]
col_pins = [Pin(pin, Pin.IN, Pin.PULL_UP) for pin in COL_PINS]

key_map = [
    ["1", "2", "3", "A"],
    ["4", "5", "6", "B"],
    ["7", "8", "9", "C"],
    ["*", "0", "#", "D"]
]

# LCD setup
lcd = None

if I2cLcd is not None:
    try:
        i2c = I2C(0, sda=Pin(LCD_SDA_PIN), scl=Pin(LCD_SCL_PIN), freq=400000)
        lcd = I2cLcd(i2c, LCD_I2C_ADDR, LCD_ROWS, LCD_COLS)
    except Exception as e:
        print("LCD init failed:", e)
        lcd = None
else:
    print("LCD library not found. LCD output disabled.")

# RFID setup
rfid = None

if MFRC522 is not None:
    try:
        # This constructor may be different depending on your mfrc522.py library.
        # Some libraries use:
        # rfid = MFRC522(spi_id=0, sck=18, miso=16, mosi=19, cs=17, rst=20)
        #
        # If this line fails, check the documentation of your RC522 library.
        rfid = MFRC522(
            spi_id=0,
            sck=RFID_SCK_PIN,
            miso=RFID_MISO_PIN,
            mosi=RFID_MOSI_PIN,
            cs=RFID_CS_PIN,
            rst=RFID_RST_PIN
        )
    except Exception as e:
        print("RFID init failed:", e)
        rfid = None
else:
    print("MFRC522 library not found. RFID disabled.")


# ------------------------------------------------------------
# Helper functions
# ------------------------------------------------------------

def set_relay(active):
    if RELAY_ACTIVE_HIGH:
        relay.value(1 if active else 0)
    else:
        relay.value(0 if active else 1)


def lock_door():
    set_relay(False)


def unlock_door():
    set_relay(True)


def all_leds_off():
    green_led.off()
    red_led.off()
    yellow_led.off()


def lcd_message(line1, line2=""):
    print(line1, line2)

    if lcd is None:
        return

    lcd.clear()
    lcd.move_to(0, 0)
    lcd.putstr(line1[:16])

    lcd.move_to(0, 1)
    lcd.putstr(line2[:16])


def beep(freq=2000, duration=0.1):
    buzzer.freq(freq)
    buzzer.duty_u16(30000)
    sleep(duration)
    buzzer.duty_u16(0)


def beep_success():
    beep(1800, 0.08)
    sleep(0.05)
    beep(2200, 0.08)


def beep_error():
    for _ in range(3):
        beep(600, 0.12)
        sleep(0.08)


def beep_alarm():
    beep(1200, 0.15)
    sleep(0.08)


def is_fire_alarm_active():
    # Active-low input
    return fire_input.value() == 0


def is_tamper_active():
    # Active-low input
    return tamper_input.value() == 0


def is_door_open():
    # This depends on your magnetic switch module.
    # For this example, LOW means door open.
    return door_contact.value() == 0


# ------------------------------------------------------------
# Keypad functions
# ------------------------------------------------------------

def scan_keypad():
    for row_index, row in enumerate(row_pins):
        for r in row_pins:
            r.value(1)

        row.value(0)

        for col_index, col in enumerate(col_pins):
            if col.value() == 0:
                sleep(0.05)  # debounce

                if col.value() == 0:
                    key = key_map[row_index][col_index]

                    while col.value() == 0:
                        sleep(0.01)

                    return key

    return None


# ------------------------------------------------------------
# RFID functions
# ------------------------------------------------------------

def uid_to_string(uid):
    return " ".join("{:02X}".format(byte) for byte in uid)


def read_rfid_card():
    if rfid is None:
        return None

    try:
        (stat, tag_type) = rfid.request(rfid.REQIDL)

        if stat == rfid.OK:
            (stat, uid) = rfid.SelectTagSN()

            if stat == rfid.OK:
                return uid_to_string(uid)

    except Exception as e:
        print("RFID read error:", e)

    return None


# ------------------------------------------------------------
# Access control actions
# ------------------------------------------------------------

def grant_access(source):
    all_leds_off()
    green_led.on()

    lcd_message("ACCESS GRANTED", source)
    beep_success()

    unlock_door()
    sleep(UNLOCK_TIME_SECONDS)
    lock_door()

    green_led.off()
    lcd_message("DOOR LOCKED", "SCAN CARD/PIN")
    sleep(1)


def deny_access(reason):
    all_leds_off()
    red_led.on()

    lcd_message("ACCESS DENIED", reason)
    beep_error()

    red_led.off()
    sleep(1)

def sound_alarm_cycle(duration_seconds, fire_mode=False):
    start_time = ticks_ms()

    while ticks_diff(ticks_ms(), start_time) < duration_seconds * 1000:
        if fire_mode:
            red_led.toggle()
            yellow_led.toggle()
        else:
            red_led.toggle()
            yellow_led.off()

        beep_alarm()
        sleep(0.2)

    buzzer.duty_u16(0)

def handle_fire_alarm():
    all_leds_off()

    lcd_message("FIRE ALARM!", "DOOR RELEASED")
    unlock_door()

    while True:
        sound_alarm_cycle(ALARM_CHECK_INTERVAL_SECONDS, fire_mode=True)

        if not is_fire_alarm_active():
            break

        lcd_message("FIRE ALARM!", "STILL ACTIVE")

    all_leds_off()
    lock_door()
    lcd_message("FIRE CLEARED", "DOOR LOCKED")
    sleep(1)


def handle_tamper_alarm():
    all_leds_off()

    lcd_message("TAMPER ALARM!", "SYSTEM LOCKED")
    lock_door()

    while True:
        sound_alarm_cycle(ALARM_CHECK_INTERVAL_SECONDS, fire_mode=False)

        if not is_tamper_active():
            break

        lcd_message("TAMPER ALARM!", "STILL ACTIVE")

    all_leds_off()
    lcd_message("TAMPER CLEARED", "SCAN CARD/PIN")
    sleep(1)


def handle_forced_door():
    all_leds_off()
    red_led.on()

    lcd_message("DOOR FORCED!", "ALARM ACTIVE")
    lock_door()

    # Sound alarm for a few seconds
    start_time = ticks_ms()

    while ticks_diff(ticks_ms(), start_time) < 5000:
        red_led.toggle()
        beep_alarm()
        sleep(0.2)

    all_leds_off()
    lcd_message("SCAN CARD/PIN", "")
    sleep(1)


# ------------------------------------------------------------
# Main program
# ------------------------------------------------------------

def main():
    entered_pin = ""
    last_idle_update = 0

    lock_door()
    all_leds_off()

    lcd_message("PICO ACCESS", "SCAN CARD/PIN")
    yellow_led.on()

    door_was_unlocked_by_system = False

    while True:
        # Highest priority: fire alarm
        if is_fire_alarm_active():
            handle_fire_alarm()
            entered_pin = ""
            lcd_message("PICO ACCESS", "SCAN CARD/PIN")
            yellow_led.on()
            continue

        # Second priority: tamper alarm
        if is_tamper_active():
            handle_tamper_alarm()
            entered_pin = ""
            lcd_message("PICO ACCESS", "SCAN CARD/PIN")
            yellow_led.on()
            continue

        # Optional forced door detection
        # This is basic logic only. A real system needs better door timing rules.
        if is_door_open() and not door_was_unlocked_by_system:
            handle_forced_door()
            entered_pin = ""
            lcd_message("PICO ACCESS", "SCAN CARD/PIN")
            yellow_led.on()
            continue

        # RFID access
        uid = read_rfid_card()

        if uid is not None:
            print("Card UID:", uid)

            if uid in AUTHORIZED_CARDS:
                yellow_led.off()
                door_was_unlocked_by_system = True
                grant_access("RFID CARD")
                door_was_unlocked_by_system = False
            else:
                yellow_led.off()
                deny_access("INVALID CARD")

            lcd_message("PICO ACCESS", "SCAN CARD/PIN")
            yellow_led.on()
            entered_pin = ""
            sleep(0.5)
            continue

        # Keypad access
        key = scan_keypad()

        if key is not None:
            print("Key:", key)

            if key == "*":
                entered_pin = ""
                lcd_message("ENTER PIN:", "")
                beep(1200, 0.05)

            elif key == "#":
                if entered_pin == AUTHORIZED_PIN:
                    yellow_led.off()
                    door_was_unlocked_by_system = True
                    grant_access("PIN ENTRY")
                    door_was_unlocked_by_system = False
                else:
                    yellow_led.off()
                    deny_access("WRONG PIN")

                entered_pin = ""
                lcd_message("PICO ACCESS", "SCAN CARD/PIN")
                yellow_led.on()

            elif key in "0123456789":
                if len(entered_pin) < 8: entered_pin += key lcd_message("ENTER PIN:", "*" * len(entered_pin)) beep(1500, 0.04) # Refresh idle display occasionally if ticks_diff(ticks_ms(), last_idle_update) > 3000 and entered_pin == "":
            lcd_message("PICO ACCESS", "SCAN CARD/PIN")
            yellow_led.on()
            last_idle_update = ticks_ms()

        sleep(0.05)


main()

How the Code Works

The code starts by defining the authorized cards and PIN:

AUTHORIZED_CARDS = [
    "12 AB 34 CD",
    "A1 B2 C3 D4"
]

AUTHORIZED_PIN = "1234"

You need to replace the sample RFID UIDs with the actual UIDs of your cards.

The system also defines how long the door will unlock:

UNLOCK_TIME_SECONDS = 3

This means that when access is granted, the solenoid is released for 3 seconds.

There is also another delay for when the fire alarm or tamper alarm is triggered:

ALARM_CHECK_INTERVAL_SECONDS = 5

This means that the buzzer will stay in alarm mode for 5 seconds before checking the states of the sensor again.


Relay Control

The relay is controlled by this function:

def set_relay(active):
    if RELAY_ACTIVE_HIGH:
        relay.value(1 if active else 0)
    else:
        relay.value(0 if active else 1)

This is useful because some relay modules turn on when the input pin is HIGH, while others turn on when the input pin is LOW.

If your relay works backward, simply change this line:

RELAY_ACTIVE_HIGH = True

to:

RELAY_ACTIVE_HIGH = False

LCD Messages

The function below prints messages to both the LCD and the Thonny shell:

def lcd_message(line1, line2=z">""):
    print(line1, line2)

    if lcd is None:
        return

    lcd.clear()
    lcd.move_to(0, 0)
    lcd.putstr(line1[:16])

    lcd.move_to(0, 1)
    lcd.putstr(line2[:16])

The

[:16]

part makes sure that the message fits on a 16-column LCD.


Reading the Keypad

The keypad is scanned row by row. Each row is pulled LOW one at a time, and the Pico checks which column also becomes LOW.

def scan_keypad():
    for row_index, row in enumerate(row_pins):
        for r in row_pins:
            r.value(1)

        row.value(0)

        for col_index, col in enumerate(col_pins):
            if col.value() == 0:
                sleep(0.05)

                if col.value() == 0:
                    key = key_map[row_index][col_index]

                    while col.value() == 0:
                        sleep(0.01)

                    return key

    return None

The small delay is used for basic button debounce.


RFID Access Logic

The RFID function returns the UID of a scanned card:

uid = read_rfid_card()

Then the main loop checks whether the UID is in the list of authorized cards:

if uid in AUTHORIZED_CARDS:
    grant_access("RFID CARD")
else:
    deny_access("INVALID CARD")

This is the simplest way to handle RFID authorization in a beginner project.


PIN Access Logic

The keypad allows the user to enter a PIN. The

*

key clears the input, while the

#

key submits it.

if key == "*":
    entered_pin = ""

elif key == "#":
    if entered_pin == AUTHORIZED_PIN:
        grant_access("PIN ENTRY")
    else:
        deny_access("WRONG PIN")

This is similar to how many keypad-based access systems work.


Fire Alarm Logic

The fire alarm input has the highest priority. If it is active, the system immediately runs this function:

handle_fire_alarm()

Inside that function, the door is unlocked:

unlock_door()

Then the buzzer and LEDs continue until the fire alarm input clears.

This behavior simulates an emergency door release.


Tamper Alarm Logic

The tamper alarm works differently from the fire alarm. When tamper is detected, the system keeps the door locked:

lock_door()

Then it sounds the buzzer and flashes the red LED.

This simulates a forced access or panel tamper condition.


Door Contact Logic

The optional door contact can detect if the door was opened without a valid unlock command.

In this tutorial, the logic is simplified:

if is_door_open() and not door_was_unlocked_by_system:
    handle_forced_door()

In a real access control system, this part would usually include a delay timer. For example, after access is granted, the door is allowed to open for a few seconds. If it stays open too long, the controller may trigger a “door held open” alarm.

You can add this as an advanced improvement.


Video

Here's the project in action:

Again, I didn't include the relay and solenoid lock for this video. The red LED lighting up means the door is unlocked. Replacing the red LED with the relay module and solenoid lock combination opens the lock when the LED is on.

Testing the Project

Test the project in stages. Do not connect everything at once.

Step 1: Test the LCD

Upload a simple LCD test first and confirm that the LCD can display text.

Step 2: Test the Keypad

Print each key to the Thonny shell. Make sure every key is detected correctly.

Step 3: Test the RFID Reader

Scan your card and copy the UID from the shell.

Then place the UID inside:

AUTHORIZED_CARDS = [
    "YOUR CARD UID HERE"
]

Step 4: Test the Relay Without the Solenoid

Before connecting the solenoid, test the relay with only the relay module connected. You should hear the relay click when access is granted.

Step 5: Test the Solenoid Lock

Once the relay works, connect the solenoid to its external power supply through the relay contacts.

Do not power the solenoid from the Pico.

Step 6: Test Fire and Tamper Inputs

Use push buttons first. Once the logic works, replace the buttons with the flame sensor, magnetic sensor, or vibration sensor.


RFID Reader Not Detected

Check the SPI wiring carefully. The most common mistake is swapping MOSI and MISO.

Also make sure the RC522 is powered from 3.3V, not 5V.

LCD Shows Nothing

Try adjusting the contrast potentiometer on the I2C backpack.

If the LCD still does not work, scan for the I2C address. Some LCD backpacks use 0x27, while others use 0x3F.

Change this line if needed:

LCD_I2C_ADDR = 0x27

Relay Works Backward

Change:

RELAY_ACTIVE_HIGH = True

to:

RELAY_ACTIVE_HIGH = False

Buzzer Is Too Weak

The passive buzzer may need a transistor driver if you want it to be louder. For simple breadboard testing, direct GPIO control is usually enough.

Solenoid Resets the Pico

This usually means the solenoid is causing voltage drops or electrical noise.

Use a separate power supply for the solenoid, connect grounds properly, and add flyback protection if needed.


Improving the Project

There are many ways to improve this project.

You can add an enrollment mode where an admin card allows new cards to be registered. You can also save authorized cards in the Pico’s flash memory so they are not hardcoded in the program.

Another useful improvement is event logging. The Pico can record events such as:

ACCESS GRANTED - RFID
ACCESS DENIED - INVALID CARD
ACCESS GRANTED - PIN
FIRE ALARM
TAMPER ALARM
DOOR FORCED

If you use a Raspberry Pi Pico W instead of a regular Pico, you can add a simple web dashboard. This dashboard can show the latest access events and alarm status.

You can also add a real-time clock module to timestamp each event.


Final Thoughts

This Raspberry Pi Pico access control project combines several important embedded system concepts into one practical build. It uses SPI for RFID, GPIO scanning for the keypad, I2C for the LCD, PWM for the buzzer, and digital outputs for the relay and LEDs.

The best part is that the project behaves like a small version of a real access control controller. It accepts credentials, controls a lock, displays status messages, and responds to alarm inputs.

For beginners, MicroPython makes the project easier to understand because the code is readable and the system logic is clear. For a production access control device, I would normally use C or C++ for better structure, timing, and reliability. But for learning how the system works, MicroPython is a very good starting point.

The nice thing about using a kit like the Elecrow Raspberry Pi Pico Advanced Kit is that the project can be expanded easily. After getting the RFID and keypad access working, you can add more modules from the same kit, such as the OLED display, PIR motion sensor, vibration sensor, magnetic spring module, infrared receiver, or ultrasonic sensor.

This makes the project more than just a one-time RFID door lock demo. It becomes a small platform for learning how real embedded control systems handle inputs, outputs, alarms, and user feedback.

Check Also

raspberry pi pico hc-sr04 ultrasonic sensor wiring

How to Auto Hide Windows Apps Using Raspberry Pi Pico

Updated: October 18, 2024In this project, I will show you how to automatically switch between …

Index