This guide explains how to modify your own firmware to enable simultaneous operation of the onboard LoRa transceiver and an MFRC522 RFID reader using separate SPI buses on the Heltec V3. After struggling with this personally and discovering that many people were running into problems and resorting to removing the main chip on the MFRC522 in order to cut traces and modify the PCB for I2C and UART operation, I figured there had to be a better way. This was my solution.
This documentation walks you through how to integrate the necessary pieces into your existing project, define a second SPI bus, configure pin assignments, and build RFID-based messages that can be transmitted over Meshtastic.
-
Wire the MFRC522 module to the Heltec V3 using the default pins:
MFRC522 pin Heltec V3 pin SCK GPIO7 MOSI GPIO6 MISO GPIO5 SDA/SS GPIO26 RST GPIO4 3.3 V 3V3 GND GND (See
docs/wiring.pngfor a simple wiring diagram.) -
Install libraries – PlatformIO will automatically fetch the
MFRC522library and the Meshtastic bridge library as specified inplatformio.ini. -
Build and upload the firmware:
pio run # compile pio run -t upload # flash to the device
Default SPI Bus (VSPI): Reserved for the onboard LoRa radio (Heltec default).
Secondary SPI Bus (HSPI): Assigned to the MFRC522 RFID reader.
Custom SPI Bus Name: rfidSPI
Board: Heltec ESP32 LoRa V3
To use a custom SPI bus for the MFRC522 reader, define a new SPIClass instance in your source and assign it the HSPI hardware bus:
SPIClass rfidSPI(HSPI);In your RFID driver header (e.g. MFRC522.h), ensure this is respected:
#ifndef MFRC522_SPI
#define MFRC522_SPI SPI
#endif
extern SPIClass MFRC522_SPI;Override the SPI instance in your platformio.ini:
[env:heltec-v3]
build_flags =
-DMFRC522_SPI=rfidSPIDefault pins for HSPI operation can be defined in your variant or overridden per build:
[env:heltec-v3]
build_flags =
-DRC522_SCK_PIN=7
-DRC522_MOSI_PIN=6
-DRC522_MISO_PIN=5
-DRC522_SS_PIN=26
-DRC522_RST_PIN=4Or default to the values below if not overridden (my curent usage):
#define RC522_SCK_PIN 7
#define RC522_MOSI_PIN 6
#define RC522_MISO_PIN 5
#define RC522_SS_PIN 26
#define RC522_RST_PIN 4Declare the RFID reader and bus:
SPIClass rfidSPI(HSPI);
MFRC522 rfid(RC522_SS_PIN, RC522_RST_PIN);Set up the bus and initialize the reader:
rfidSPI.begin(RC522_SCK_PIN, RC522_MISO_PIN, RC522_MOSI_PIN, RC522_SS_PIN);
rfid.PCD_Init();In your loop or polling section, check for tags and construct a message:
if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) {
String uidStr = "";
for (byte i = 0; i < rfid.uid.size; i++) {
uidStr += String(rfid.uid.uidByte[i], HEX);
}
String payload = "{\"scanner_id\": \"RFID01\", \"tag_uid\": \"" + uidStr + "\", \"timestamp\": " + String(millis()) + "}";
sendToMeshtastic(payload);
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
}This sends a structured JSON payload like:
{
"scanner_id": "RFID01",
"tag_uid": "f41298ab",
"timestamp": 172344123
}Assuming Meshtastic is running and the serial port is active, you can send messages via Python or C++ API bindings. Here's an example C++ stub if you're bridging through serial:
void sendToMeshtastic(const String& msg) {
Serial2.println(msg); // or write to the Meshtastic port
}- MFRC522 library (or compatible fork)
- SPI and Arduino libraries
- Meshtastic Serial Bridge or native integration
platformio.inimacro overrides for pin and SPI assignment
The ESP32-S3 supports multiple SPI buses. By separating the MFRC522 onto its own bus (HSPI) while leaving LoRa on VSPI, you maintain performance and reliability without hardware modifications or mode switching. This keeps your firmware clean and modular.
After each successful tag read, the built-in LED is blinked three times to provide immediate user feedback. To avoid spamming the same UID, scans are ignored until a configurable delay has passed and the UID differs from the previous read.
const unsigned long SCAN_DELAY_MS = 2000;
unsigned long lastScanTime = 0;
String lastUid = "";
void loop() {
if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) {
String uid = "";
for (byte i = 0; i < rfid.uid.size; i++) {
uid += String(rfid.uid.uidByte[i], HEX);
}
if (millis() - lastScanTime > SCAN_DELAY_MS && uid != lastUid) {
// send payload here
for (int i = 0; i < 3; i++) {
digitalWrite(LED_PIN, HIGH);
delay(100);
digitalWrite(LED_PIN, LOW);
delay(100);
}
lastScanTime = millis();
lastUid = uid;
}
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
}
}