Breadboard
docs
Example project · Sensors

RFID door lock

An MFRC522 reader guarding a two-LED door. Toggle “Hold tag to reader” in the reader’s Inspector: the authorized card lights green and reads a note off block 1; edit the tag UID and tap again to get the red ACCESS DENIED path.

The RFID door lock circuit as rendered by the simulator

What's on the bench

  • 2× LED
  • 2× Resistor
  • Battery
  • ESP32-C6
  • RFID reader (MFRC522)

How it's wired

  • Battery · posESP32-C6 · vin
  • Battery · negESP32-C6 · gnd
  • ESP32-C6 · 3v3RFID reader (MFRC522) · vcc
  • ESP32-C6 · gnd2RFID reader (MFRC522) · gnd
  • ESP32-C6 · 3v3bRFID reader (MFRC522) · rst
  • ESP32-C6 · g6RFID reader (MFRC522) · sck
  • ESP32-C6 · g7RFID reader (MFRC522) · mosi
  • ESP32-C6 · g2RFID reader (MFRC522) · miso
  • ESP32-C6 · g20RFID reader (MFRC522) · cs
  • ESP32-C6 · g1Resistor · a
  • Resistor · bLED · a
  • LED · kESP32-C6 · gnd
  • ESP32-C6 · g0Resistor · a
  • Resistor · bLED · a
  • LED · kESP32-C6 · gnd

The code

This MicroPython script runs on the emulated board every boot; edit it in the Code tab.

from machine import Pin, SPI
import time

class MFRC522:
    OK, ERR = 0, 2
    def __init__(self, spi, cs):
        self.spi, self.cs = spi, cs
        self.cs.value(1)
        self._wreg(0x01, 0x0F)  # soft reset
        for r, v in ((0x2A, 0x8D), (0x2B, 0x3E), (0x2D, 30), (0x2C, 0),
                     (0x15, 0x40), (0x11, 0x3D)):
            self._wreg(r, v)
        self._wreg(0x14, self._rreg(0x14) | 0x03)  # antenna on
    def _wreg(self, reg, val):
        self.cs.value(0)
        self.spi.write(bytes([(reg << 1) & 0x7E, val]))
        self.cs.value(1)
    def _rreg(self, reg):
        self.cs.value(0)
        self.spi.write(bytes([((reg << 1) & 0x7E) | 0x80]))
        val = self.spi.read(1, 0)[0]
        self.cs.value(1)
        return val
    def _crc(self, data):
        self._wreg(0x05, 0x04)
        self._wreg(0x0A, 0x80)
        for b in data:
            self._wreg(0x09, b)
        self._wreg(0x01, 0x03)  # CalcCRC
        return [self._rreg(0x22), self._rreg(0x21)]
    def _tocard(self, cmd, send, bits=0):
        self._wreg(0x04, 0x7F)  # clear irqs
        self._wreg(0x0A, 0x80)  # flush FIFO
        self._wreg(0x0D, bits)
        for b in send:
            self._wreg(0x09, b)
        self._wreg(0x01, cmd)
        if cmd == 0x0C:
            self._wreg(0x0D, 0x80 | bits)  # StartSend
        wait = 0x10 if cmd == 0x0E else 0x30
        for _ in range(20):
            n = self._rreg(0x04)
            if n & 0x01:
                return self.ERR, []  # timer: nothing answered
            if n & wait:
                break
        else:
            return self.ERR, []
        return self.OK, [self._rreg(0x09) for _ in range(self._rreg(0x0A))]
    def request(self):
        return self._tocard(0x0C, [0x26], 7)[0]
    def anticoll(self):
        stat, r = self._tocard(0x0C, [0x93, 0x20])
        return r[:4] if stat == self.OK and len(r) == 5 else None
    def select(self, uid):
        buf = [0x93, 0x70] + uid + [uid[0] ^ uid[1] ^ uid[2] ^ uid[3]]
        return self._tocard(0x0C, buf + self._crc(buf))[0]
    def auth(self, block, key, uid):
        return self._tocard(0x0E, [0x60, block] + key + uid)[0]
    def read(self, block):
        buf = [0x30, block]
        stat, r = self._tocard(0x0C, buf + self._crc(buf))
        return bytes(r[:16]) if stat == self.OK and len(r) >= 16 else None
    def halt(self):
        buf = [0x50, 0x00]
        self._tocard(0x0C, buf + self._crc(buf))
        self._wreg(0x08, self._rreg(0x08) & 0xF7)  # stop crypto1

# C6 hardware SPI (GPSPI2) fixed pins: sck=6, mosi=7, miso=2.
rdr = MFRC522(SPI(1, baudrate=1000000), Pin(20, Pin.OUT))
print('MFRC522 version 0x%02x' % rdr._rreg(0x37))
green = Pin(1, Pin.OUT, value=0)
red = Pin(0, Pin.OUT, value=0)
KEY = [0xFF] * 6
AUTHORIZED = '9c5a1db3'
print('Hold a tag to the reader (toggle in its Inspector)...')
while True:
    if rdr.request() == rdr.OK:
        uid = rdr.anticoll()
        if uid and rdr.select(uid) == rdr.OK:
            s = ''.join('%02x' % b for b in uid)
            if s == AUTHORIZED and rdr.auth(1, KEY, uid) == rdr.OK:
                note = rdr.read(1)
                print('ACCESS GRANTED', s, note)
                green.value(1)
                red.value(0)
            else:
                print('ACCESS DENIED', s)
                red.value(1)
                green.value(0)
            rdr.halt()  # one report per tap; lift + re-hold to scan again
    time.sleep_ms(300)

Related projects