Reading MiFare Ultralight tags on Raspberry Pi with the RC522 and SimpleMFRC522
While building my son’s magic storybook, I had a bit of trouble getting my RC522 RFID/NFC module to work with MiFare Ultralight tags (the kind you’ll likely end up with if you buy cheap NFC stickers on eBay). There seemed to be a number of people online having similar problems and not much material to resolve them, perhaps because the real solution is ‘actually learn what you’re doing’. But around here we drink only as deep from the Pierian spring as is absolutely necessary to achieve our ends, so here we are.
I thought I’d share what I learned to help future solution-searchers. This is not even close to a comprehensive or expert guide, and may even be misleading in places, but hopefully it can help you figure things out for your own use case.
There is a library for using this module with a Raspberry Pi, which comes in two parts: MFRC522 and SimpleMFRC522. MFRC522 implements all the basic functions of the module; I believe if you know what you are doing you can do everything you need to do with it without modification. SimpleMFRC522 puts together these basics into a few essential functions for reading and writing tags, but with a couple of important limitations: it assumes you are using MiFare Classic tags (like the card and key fob that probably came with your RC522 module), and it reads and writes just a few pre-selected blocks of the tag’s memory.
Auth error
If you try to use an Ultralight tag, you will probably see the message “AUTH ERROR”. This is because MiFare Classic tags require authorisation to read and write, but Ultralight tags don’t. The library presses ahead with the authorization process, but it can’t work.
There are two crude, hacky ways around this. You can remove the authorization process altogether by deleting this line:
status = self.READER.MFRC522_Auth(self.READER.PICC_AUTHENT1A, 11, self.KEY, uid)
Or you can force it to carry on as if it worked, by removing the if
statement from the front of this (or replacing the condition, status == self.READER.MI_OK
, with True
):
if status == self.READER.MI_OK:
for block_num in self.BLOCK_ADDRS:
block = self.READER.MFRC522_Read(block_num)
if block:
data += block
Either will let you work with Ultralight tags. The former will (I think) mean Classic tags don’t work anymore; the latter will mean they work but you won’t get warned if something goes wrong with the authorization process.
Reading tags written from your phone
You have probably also tried to use the RFC522 module to read tags you wrote with your phone. This is unlikely to work if you use SimpleMFRC522, because as mentioned, it always looks for blocks 8 through 10:
BLOCK_ADDRS = [8, 9, 10]
for block_num in self.BLOCK_ADDRS:
block = self.READER.MFRC522_Read(block_num)
if block:
data += block
if data:
text_read = ''.join(chr(i) for i in data)
The data you write from your phone probably won’t end up in these blocks.
I simply chose to start at block 0 and read until the blocks ran out, then search through the output for my desired text (which I marked up so I could find it reliably). I imagine this wasn’t the best approach, but it was really simple and gave reliable results.
block_num = 0
while True:
block = self.READER.MFRC522_Read(block_num)
if block:
data += block
block_num = block_num + 4
else:
break
if data:
text_read = ''.join(chr(i) for i in data)
You may notice that this code adds 4 to block_num, which tells it which block to read, each time it loops. That’s because of another exciting quirk of using Ultralight tags:
Messed up text
If you have got this far, you might find that you can read and write text, but it comes out all messed up. This is because of a difference in how data is stored on the two types of tag. Both types are split up into ‘blocks’, but on the Classic tag a blog is 16 bytes, while on an Ultralight it is only 4.
However, when you ask the RC522 to read a block from an Ultralight tag, it will actually read four blocks, so that it can still give you 16 bytes. So if you naively ask for blocks 1 to 3, you actually get blocks 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5 and 6.
Instead, you need to request every fourth block, which is why the code above increments block_num by 4 each time.
Enough of this, I just want to make my janky code work
Here is my fork of the MFRC522 library with the above changes. It implements two new methods in SimpleMFRC522: dump_ul
and dump_ul_no_block
. Like the read
methods, these return the tag ID and a string containing the data, but they don’t attempt authorization, and they read all the data on the tag.
I haven’t touched the write methods as I wasn’t planning to use them. It’s my understanding that (unlike reading) writing to an Ultralight tag is done one 4-byte block at a time to avoid unintentionally overwriting data, so keep that in mind if you need to write.