Mifare RFID tags, like other RFID tags, contain a serial number that can be read using an RFID reader, but they also have a limited amount of memory space that you can write data to, and read back from. This can be handy if you want to do something like keep a user’s account balance or name directly on the RFID tag.
This tutorial shows a number of the functions of my sonMicroReader library for Processing, including how to write to the memory on tags. It uses the same circuit as the SM130 reader example. The entire sketch can be downloaded here:
The interface screen of this sketch is shown in Figure 1. It contains buttons that trigger the major functions of the SM130 module, including setting the antenna power, selecting tags, authenticating for reading from or writing to tags, reading a block of memory, seeking tags, writing to a block of memory, writng to a 4-byte block, and getting the firmware version.
How do I read a tag’s ID number?
There are two ways to read a tag’s ID number: you can select tag, or you can seek tag. Select tag assumes that there’s a tag in the field of the antenna. Seek tag, on the other hand, puts the reader in a continuous loop where it waits for a tag to come into range, then automatically gives you the results when it has a tag. This sketch doesn’t read continuously. It issues each command once when you click the appropriate button. For more on reading continuously, see the Reading Mifare Tags example.
To see reading in action, run the sketch and click the “Select Tag” button. With no tag in range, nothing will happen. By contrast, try clicking “Seek Tag” with no tag in range, then bring a tag into range. You’ll see that when the tag comes into range, the reader reads automatically.
How do I write to a tag’s memory?
To write to a tag, you first have to select the tag, then authenticate the block of memory using an authentication key. There are three basic authentication key types, called A, B, and FF. The latter uses a special transport key to authenticate, which you can set. The sequence goes like this:
- select tag
- authenticate block of memory you want to write to
- write data
You can write up to 16 bytes for every block of memory. Beyond that, you have to break your message up across multiple blocks.
The mifare ultralight tags have smaller blocks of memory, so there is also a writeFourByteBlock() command. This command writes only four-byte strings.
How do I read from a tag’s memory?
Reading works much like writing. First you select, then you authenticate, then you read. If a block of memory was authenticated with key type A for writing, then it has to be read with the same key type. Just like with writing, the operation goes like so:
- select tag
- authenticate block of memory you want to write to
- read data
To see reading and writing in action, do the following:
- Hit delete, then type a short message (16 characters or less).
- Bring a tag in range
- Click Select
- When you get a good result, click Authenticate
- When you get a good result, click Write Block
To read the same tag’s memory, here’s the sequence:
- Bring the tag in range
- Click Select
- When you get a good result, click Authenticate
- When you get a good result, click Read Block
- You should see your message in the response field
Now that you’ve got the basic functionality down, it’s time to look at the code in depth.
The Code
This sketch uses the serial and sonMicro libraries. The global variables are mostly used to keep track of the last received messages from the reader:
// import libraries: import processing.serial.*; import sonMicroReader.*; String tagID = ""; // the string for the tag ID Serial myPort; // serial port instance SonMicroReader myReader; // sonMicroReader instance int lastCommand = 0; // last command sent int lastTagType = 0; // last tag type received int lastPacketLength = 0; // last packet length received String lastTag = null; // last tag ID received int lastErrorCode = 0; // last error code received int[] lastResponse = null; // last response from the reader (raw data) int lastAntennaPower = 0; // last antenna power received int lastChecksum = 0; // last checksum received int fontHeight = 14; // font height for the text onscreen String message = null; // message read from tag String outputString = "Hello world!"; // string to write to tag
The setup() method initializes the libraries, the screen size, and the fonts. It also calls a method called makeButtons() that initializes a list of the button objects:
void setup() { // set window size: size(600,400); // list all the serial ports: println(Serial.list()); // based on the list of serial ports printed from the // previous command, change the 0 to your port's number: String portnum = Serial.list()[0]; // initialize the serial port. default data rate for // the SM130 reader is 19200: myPort = new Serial(this, portnum, 19200); // initialize the reader instance: myReader = new SonMicroReader(this,myPort); myReader.start(); // create a font with the second font available to the system: PFont myFont = createFont(PFont.list()[2], fontHeight); textFont(myFont); // create the command buttons: makeButtons(); }
The draw() method draws all the stuff on the screen. It calls drawButtons(), which iterates over the buttons and redraws them. Then it prints the status messages for the command, packet length, tag type, and so forth. If there’s a new response string, it draws that. Finally, it draws whatever you type in the output field.
void draw() { // clear the screen: background(0); // draw the command buttons: drawButtons(); // draw the output fields: textAlign(LEFT); text("Command: " + hex(lastCommand, 2), 10, 30); text("Packet length: " +lastPacketLength, 10, 50); text("Antenna power: " + lastAntennaPower, 10, 70); text("Tag type: " + lastTagType, 10, 90); text("Tag string: " + lastTag, 10, 110); text("Error code: " + hex(lastErrorCode, 2), 10, 130); // print the hex values for all the bytes in the response: String responseString = ""; if (lastResponse != null) { for (int b = 0; b < lastResponse.length; b++) { responseString += hex(lastResponse[b], 2) + " "; } // wrap the full text so it doesn't overflow the buttons // and make the screen all messy: text("Full response:\n" + responseString, 10, 150, 300, 200); } // print any error messages from the reader: text(myReader.getErrorMessage(), 10, 210); // if there's a message from the tag's memory, // print it: if (message != null) { text("message read from tag:\n" + message, 10, 230); } // print the output message: text("type your message to write to tag:\n",10, 300); fill(0,0,150); text(outputString, 10, 320); // show the firmware version: fill(0); text("SonMicroReader version: " + myReader.version(), width - 300, height - 30); }
The sonMicroEvent() is called automatically whenever a new packet of data comes in from the reader. It parses all the messages out from the packet, and if the command sent was a Read Block, it prints out the message received from the tag’s memory.
void sonMicroEvent(SonMicroReader myReader) { // get all the relevant data from the last data packet: lastCommand = myReader.getCommand(); lastTagType = myReader.getTagType(); lastPacketLength = myReader.getPacketLength(); lastTag = myReader.getTagString(); println(lastTag); lastErrorCode = myReader.getErrorCode(); lastAntennaPower = myReader.getAntennaPower(); lastResponse = myReader.getSonMicroReading(); lastChecksum = myReader.getCheckSum(); // if the last command sent was a read block command: if (lastCommand == 0x86) { int[] inputString = myReader.getPayload(); message = ""; for (int c = 0; c < inputString.length; c++) { message += char(inputString); } println(message); } else { message = null; } }
The buttonPressed() method scans the list of buttons to see which matches the one clicked. Then it chooses the appropriate command to execute for that button:
void buttonPressed(RectButton thisButton) { // figure out which button this is in the ArrayList: int buttonNumber = buttons.indexOf(thisButton); // do the right thing: switch (buttonNumber) { case 0: // set antenna power if (myReader.getAntennaPower() < 1) { myReader.setAntennaPower(0x01); } else { myReader.setAntennaPower(0x00); } break; case 1: // select tag myReader.selectTag(); break; case 2: // authenticate myReader.authenticate(0x10, 0xFF); break; case 3: // readblock myReader.readBlock(0x10); break; case 4: // seek tag myReader.seekTag(); break; case 5: // write tag - must be 16 bytes or less myReader.writeBlock(0x10, outputString); outputString = ""; break; case 6: // write 4-byte block - must be 4 bytes or less String shortString = outputString.substring(0,4); println(shortString); myReader.writeFourByteBlock(0x10, shortString); break; case 7: // get reader firmware version myReader.getFirmwareVersion(); break; } }
the keyTyped() method takes in whatever you typed and adds it to the output string. If you type DELETE, it clears the output string. If you type more than 16 characters, it warns you that you can’t fit that in one block.
void keyTyped() { switch (key) { case ENTER: outputString = "\0"; break; case BACKSPACE: // delete outputString = "\0"; break; default: if (outputString.length() < 16) { outputString += key; } else { outputString = "output string can't be more than 16 characters"; } } }
That’s the main code for the sketch. There is a second tab in the sketch that generates the buttons. It’s based on Processing‘s buttons example (in the File menu –> Examples –> Topics –> GUI –> Buttons). For more details, see that example.
Here’s the code in its entirety:
One Reply to “Writing to Mifare RFID tags”
Comments are closed.