MTG RFID Companion

A hand-held Magic: The Gathering companion built on an ESP32-WROOM-32. Tap an RFID/NFC-tagged card to a built-in reader and the device:

  • renders the card's cover art and metadata on a 2.0" ST7789 TFT (240x320),
  • flashes a WS2812B Neopixel in the card's mana color and plays a short buzzer chirp,
  • keeps a life counter (20/40) you adjust with a rotary encoder,
  • caches cards locally on LittleFS and refetches from the Scryfall API over Wi-Fi,
  • runs a SoftAP registration server (MTG-Companion) so you can pair an unknown tag to a card name from your phone.

All code is pure ESP-IDF v5.x (C) / FreeRTOS — no Arduino.


Features

🃏 RFID scanning MFRC522, NTAG21x / MIFARE tags, 4- and 7-byte UIDs
🖥️ Display ST7789 240x320 RGB565, 5x7 text, scaled digits, streaming JPEG decode
🌐 Network Wi-Fi STA auto-connect, Scryfall HTTPS with local cache
📡 Pairing SoftAP MTG-Companion (192.168.4.1) web SPA for UID ↔ card name
🔄 UX EC11 rotary encoder, backlit life counter, mana-colored LED, audio cues
💾 Storage LittleFS /spiffs (~1.9MB) seeded with card mapping + example art

Hardware Requirements

  • ESP32-WROOM-32 dev board (4MB flash, no PSRAM required)
  • MFRC522 RFID reader (13.56 MHz, SPI)
  • ST7789 SPI TFT, 240x320 (e.g. common 2.0-2.4" breakout)
  • EC11 rotary encoder with push switch
  • WS2812B / Neopixel (single LED)
  • Passive piezo buzzer
  • Antenna-friendly NTAG213/NTAG215 stickers or MIFARE cards, ~3.3V–5V supply

Wiring Guide

Do not change these GPIO assignments in code unless you update both main/hw_pins.h and this table. The ST7789 and MFRC522 share one SPI bus (SPI2_HOST).

Pin map

Peripheral Function ESP32 GPIO Notes
Shared SPI SCK 18 SPI2_HOST (VSPI), 1 MHz modules
MOSI 23
MISO 19
MFRC522 SDA (CS) 5 dedicated chip select
RST 22 hard reset
3.3V / GND — feed from board 3.3V rail
ST7789 CS 15 dedicated chip select
DC (A0) 2 data/command
RST 4 panel reset
LED 3.3V or 5V optional backlight pin
EC11 CLK 32 interrupt input, pull-up
DT 33 quadrature B
SW 25 button, active low, pull-up
WS2812B DIN 27 RMT Tx; supply 5V/VDD, GND common
Buzzer SIG 14 LEDC PWM

Suggested breadboard layout

        ESP32-WROOM-32
  ┌────────────────────────────┐
  │ 3V3  18 ──── SCK ───────┤ MFRC522  5 ── SDA(CS)  22 ── RST
  │ GND  23 ──── MOSI ──────┤ (SPI)    3V3/GND
  │       19 ──── MISO ─────┤
  │       15 ──── CS ───────┤ ST7789   2 ── DC  4 ── RST
  │       4 ─────────────────┤ (SPI)
  │       32 ── CLK ─────────┤ EC11     33 ── DT  25 ── SW/GND
  │       27 ── Din ─────────┤ WS2812B  (5V + GND)
  │       14 ── SIG ─────────┤ Buzzer   (GND)
  └────────────────────────────┘
  • Keep SPI signal runs short (< 10 cm) for reliable 20 MHz display transfers.
  • NTAG213/215 FPC stickers need to sit flat and close to the MFRC522 antenna.
  • The WS2812B draws up to ~60 mA; power it from the 5V rail, never from a GPIO.

Getting Started

1. Toolchain

Install ESP-IDF v5.5 (any 5.x works): https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/

Verify:

# Windows (Espressif "ESP-IDF 5.5 PowerShell" terminal)
. C:\Espressif\Initialize-Idf.ps1        # or: . $IDF_PATH\export.ps1
idf.py --version

2. Build

idf.py set-target esp32
idf.py build

The custom partition table (partitions.csv) reserves:

Offset Size Subtype Purpose
0x10000 2 MB app/factory firmware
0x210000 ~1.9 MB data/littlefs /spiffs cache (seeded from storage/)

3. Configure Wi-Fi

Two equivalent ways:

# a) interactive menu (recommended)
idf.py menuconfig        # -> "MTG RFID Companion" -> SSID + password

# b) edit the preset file directly
#    sdkconfig.defaults.esp32

Leaving the SSID empty (or keeping the YOUR_... placeholders) disables station mode — the SoftAP pairing server still runs, so you can use the device without a home network.

4. Flash & watch

idf.py -p COMx flash monitor      # COMx = your serial port

Expected boot log if everything is wired right:

I storage: LittleFS mounted at /spiffs: <size> bytes total ...
I storage: boot_count read back = 1 (persistence across reboots OK)
I rfid:    RC522 polling task pinned to Core 0
I display: ST7789 240x320 ready ...
I web:     SoftAP 'MTG-Companion' at 192.168.4.1, web server ready
I ui:      UI task on core 1

Usage

Scan a card

Place a tagged card on the reader:

  1. Mapped UID → chirp, mana-colored LED flash, then the card view (cover art + name/type/P·T/price/mana).
  2. Unknown UID → registration screen with the pairing address.

Life counter

Press the encoder button to toggle SCANNER ↔ LIFE_COUNTER. Rotate to adjust (20/40 preset or whatever you last set; press again to reset to the preset).

Web pairing (registration mode)

  1. Connect your phone to the MTG-Companion access point.
  2. Open http://192.168.4.1.
  3. Scan an unmapped card — the UID appears in the page.
  4. Type / pick a Scryfall-autocomplete name → Pair UID with card.
  5. The mapping persists to /spiffs/mappings.json and the card is fetched & cached.

Storage Layout (/spiffs)

/spiffs/
├── mappings.json              UID -> Card Name map
├── cards/
│   ├── sample.json            example metadata
│   ├── sample.jpg             example cover art (baked into flash image)
│   ├── <UID>.json             Scryfall metadata cache
│   └── <UID>.jpg              cached cover art

The storage/ folder is compiled into the LittleFS partition image at build time (littlefs_create_partition_image), so a fresh flash already contains mappings.json and the example art.


Troubleshooting

Symptom Fix
Display = black / garbage Check CS/DC/RST wiring; try toggling esp_lcd_panel_invert_color and LCD_RGB_ELEMENT_ORDER_* in display_manager.c
Colors inverted Same toggle as above — panel clones differ
No UID printed on scan Verify MFRC522 wiring, tag type (ISO14443A), antenna positioning; confirm rfid: RC522 polling task pinned to Core 0 in the log
Wi-Fi never connects Re-run idf.py menuconfig, confirm you replaced the YOUR_... placeholders
Can't reach 192.168.4.1 Ensure your phone joined MTG-Companion, not a cached network
Card renders but no art Card not cached yet — check scryfall log lines; art downloads are best-effort
App grows near 2MB mbedTLS + HTTP pulls are large; if needed, enable CONFIG_MBEDTLS_CERTIFICATE_BUNDLE (smaller)

Project Layout

├── CMakeLists.txt              top-level; bakes LittleFS storage image
├── partitions.csv              custom partition table
├── sdkconfig.defaults          shared ESP32 defaults
├── sdkconfig.defaults.esp32    Wi-Fi credential preset (edit me)
├── storage/                    files pre-seeded into /spiffs
├── tools/                      host-side helpers (sample JPEG generator)
└── main/
    ├── hw_pins.h               GPIO assignments (authoritative)
    ├── spi_bus_manager.c       shared SPI2_HOST bus
    ├── storage_manager.c       LittleFS mount + JSON helpers
    ├── rfid_manager.c          MFRC522 / Core 0 polling
    ├── display_manager.c       ST7789 + fonts + streaming JPEG
    ├── ui_task.c               state machine + screens (Core 1)
    ├── wifi_manager.c          STA connect/reconnect
    ├── scryfall_client.c       Scryfall HTTPS + cache (net_task)
    ├── web_server.c            SoftAP pairing server
    ├── encoder.c               EC11 ISR decode
    ├── led_manager.c           WS2812B RMT
    ├── buzzer.c                LEDC PWM tones
    └── app_state.c             shared app/life-counter state

See AGENTS.md for the detailed architecture, core split, and coding conventions used across this repository.

Description
No description provided
Readme 34 MiB
2026-09-14 09:31:48 +02:00
Languages
C 98.6%
CMake 0.9%
PowerShell 0.5%