com.diozero.devices.MFRC522 Maven / Gradle / Ivy
package com.diozero.devices;
/*
* #%L
* Organisation: diozero
* Project: diozero - Core
* Filename: MFRC522.java
*
* This file is part of the diozero project. More information about this project
* can be found at https://www.diozero.com/.
* %%
* Copyright (C) 2016 - 2024 diozero
* %%
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* #L%
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.tinylog.Logger;
import com.diozero.api.DeviceInterface;
import com.diozero.api.DigitalOutputDevice;
import com.diozero.api.SpiConstants;
import com.diozero.api.SpiDevice;
import com.diozero.util.Hex;
import com.diozero.util.SleepUtil;
/**
*
* Datasheet
* Example
* Python code
* MiFare Classic Universal toolKit
* (MFCUK)
* Work-in-progress!
*
*
* Wiring:
*
*
* - SDA: SPI0 CE0 (GPIO8)
* - SCK: SPI0 SCLK (GPIO11)
* - MOSI: SPI0 MOSI (GPIO10)
* - MISO: SPI0 MISO (GPIO9)
* - IRQ: Not connected
* - GND: GND
* - RST: Any free GPIO (GPIO25)
* - 3v3: 3v3
*
*
*
* Java port from MFRC522
* Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS SPI W AND R BY
* COOQROBOT that was created by Miguel Balboa (circuitito.com).
*
*
*
* There are three hardware components involved:
*
*
* - The controller, e.g. Arduino, Raspberry Pi
* - The PCD (Proximity C oupling Device), e.g. NXP MFRC522 Contactless
* Reader
* - The PICC (Proximity Integrated Circuit Card): A card or tag using the ISO
* 14443A interface, e.g. Mifare or NTAG203.
*
*
*
* The microcontroller and card reader uses SPI for communication. The protocol
* is described in the
* MFRC522
* datasheet.
*
*
*
* The card reader and the tags communicate using a 13.56MHz electromagnetic
* field. The protocol is defined in http://wg8.de/wg8n1496_17n3613_Ballot_FCD14443-3.pdf:
*
*
* - ISO/IEC 14443-3 Identification cards
* - Contactless integrated circuit cards
* - Proximity cards
* - Part 3: Initialization and anticollision"
* - Chapter 6, Type A ? Initialization and anticollision
*
*
*
* If only the PICC UID is wanted, the above documents have all the information
* needed.
* To read and write from MIFARE PICCs, the MIFARE protocol is used after the
* PICC has been selected.
* The MIFARE Classic and Ultralight chips and protocols are described in the
* datasheets:
*
*
* - 1K
* - 4K
* - Mini
* - Ultralight
* - Ultralight
* C
*
*
* MIFARE Classic 1K (MF1S503x)
*
* Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes.
* The blocks are numbered 0-63.
* Block 3 in each sector is the Sector Trailer. See
* sections 8.6 and
* 8.7:
*
*
*
* Bytes 0-5: Key A
* Bytes 6-8: Access Bits
* Bytes 9: User data
* Bytes 10-15: Key B (or user data)
*
*
* Block 0 is read-only manufacturer data, sometimes as follows:
*
*
*
* 0 1 2 3 |4 | 5 |6 |7 8 9 A B C D E F
* UID |BCC|SAK|ATAQ|Manufacturer data
*
*
* To access a block, an authentication using a key from the block's sector must
* be performed first.
* Example: To read from block 10, first authenticate using a key from sector 3
* (blocks 8-11).
* All keys are set to FFFFFFFFFFFFh at chip delivery.
* Warning: Please read section 8.7 "Memory Access". It includes this
* text: if the PICC detects a format violation the whole sector is irreversibly
* blocked.
* To use a block in "value block" mode (for Increment/Decrement operations) you
* need to change the sector trailer. Use setAccessBits() to calculate the bit
* patterns.
*
*
* MIFARE Classic 4K (MF1S703x)
*
* Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16
* bytes/block = 4096 bytes.
* The blocks are numbered 0-255.
* The last block in each sector is the Sector Trailer like above.
*
*
* MIFARE Classic Mini (MF1 IC S20)
*
* Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes.
* The blocks are numbered 0-19.
* The last block in each sector is the Sector Trailer like above.
*
*
* MIFARE Ultralight (MF0ICU1)
*
* Has 16 pages of 4 bytes = 64 bytes.
* Pages 0 + 1 is used for the 7-byte UID.
* Page 2 contains the last check digit for the UID, one byte manufacturer
* internal data, and the lock bytes (see
* http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf section 8.5.2).
* Page 3 is OTP, One Time Programmable bits. Once set to 1 they cannot revert
* to 0.
* Pages 4-15 are read/write unless blocked by the lock bytes in page 2.
*
*
* MIFARE Ultralight C (MF0ICU2)
*
* Has 48 pages of 4 bytes = 192 bytes.
* Pages 0 + 1 is used for the 7-byte UID.
* Page 2 contains the last check digit for the UID, one byte manufacturer
* internal data, and the lock bytes (see
* http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf section 8.5.2).
* Page 3 is OTP, One Time Programmable bits. Once set to 1 they cannot revert
* to 0.
* Pages 4-39 are read/write unless blocked by the lock bytes in page 2.
* Page 40 Lock bytes
* Page 41 16 bit one way counter
* Pages 42-43 Authentication configuration
* Pages 44-47 Authentication key
*
*
* Access conditions for sector trailer
*
*
* KeyA Bits KeyB
* R W R W R W
* 0 - A A - A A
* 1 - A A A A A Default
* 2 - - A - A -
* 3 - B AB B - B
* 4 - B AB - - B
* 5 - - AB B - -
* 6 - - AB - - -
* 7 - - AB - - -
*
*
* Access conditions for data blocks
*
*
* R W + -X
* 0 AB AB AB AB Data Default
* 1 AB -- -- AB Value
* 2 AB -- -- -- Data
* 3 B B -- -- Data
* 4 AB B -- -- Data
* 5 B -- -- -- Data
* 6 AB B B AB Value
* 7 -- -- -- -- Data
*
*
* Workflow
*
* - Call ISO_Request() to check to see if a tag is in range and if so get its
* ATQA.
* - Upon success call ISO_Anticollision() which returns the UID of the active
* tag in range.
* - Upon success call ISO_Select(UID) which selects the active tag in range
* by its UID and returns its SAK.
* - If the card needs authentication call ISO_Authenticate(blockAddr, keyId,
* key, UID) to authenticate access to a block.
* - Call ISO_StopCrypto() when you have finished talking to a card which
* requires authentication.
*
*/
public class MFRC522 implements DeviceInterface {
public static final byte[] DEFAULT_KEY = { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
(byte) 0xff };
// Firmware data for self-test
// Reference values based on firmware version
// Hint: if needed, you can remove unused self-test data to save flash memory
//
// Version 0.0 (0x90)
// Philips Semiconductors; Preliminary Specification Revision 2.0 - 01 August
// 2005; 16.1 self-test
public static final byte[] MFRC522_firmware_referenceV0_0 = { 0x00, (byte) 0x87, (byte) 0x98, 0x0f, 0x49,
(byte) 0xFF, 0x07, 0x19, (byte) 0xBF, 0x22, 0x30, 0x49, 0x59, 0x63, (byte) 0xAD, (byte) 0xCA, 0x7F,
(byte) 0xE3, 0x4E, 0x03, 0x5C, 0x4E, 0x49, 0x50, 0x47, (byte) 0x9A, 0x37, 0x61, (byte) 0xE7, (byte) 0xE2,
(byte) 0xC6, 0x2E, 0x75, 0x5A, (byte) 0xED, 0x04, 0x3D, 0x02, 0x4B, 0x78, 0x32, (byte) 0xFF, 0x58, 0x3B,
0x7C, (byte) 0xE9, 0x00, (byte) 0x94, (byte) 0xB4, 0x4A, 0x59, 0x5B, (byte) 0xFD, (byte) 0xC9, 0x29,
(byte) 0xDF, 0x35, (byte) 0x96, (byte) 0x98, (byte) 0x9E, 0x4F, 0x30, 0x32, (byte) 0x8D };
// Version 1.0 (0x91)
// NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test
public static final byte[] MFRC522_firmware_referenceV1_0 = { 0x00, (byte) 0xC6, 0x37, (byte) 0xD5, 0x32,
(byte) 0xB7, 0x57, 0x5C, (byte) 0xC2, (byte) 0xD8, 0x7C, 0x4D, (byte) 0xD9, 0x70, (byte) 0xC7, 0x73, 0x10,
(byte) 0xE6, (byte) 0xD2, (byte) 0xAA, 0x5E, (byte) 0xA1, 0x3E, 0x5A, 0x14, (byte) 0xAF, 0x30, 0x61,
(byte) 0xC9, 0x70, (byte) 0xDB, 0x2E, 0x64, 0x22, 0x72, (byte) 0xB5, (byte) 0xBD, 0x65, (byte) 0xF4,
(byte) 0xEC, 0x22, (byte) 0xBC, (byte) 0xD3, 0x72, 0x35, (byte) 0xCD, (byte) 0xAA, 0x41, 0x1F, (byte) 0xA7,
(byte) 0xF3, 0x53, 0x14, (byte) 0xDE, 0x7E, 0x02, (byte) 0xD9, 0x0F, (byte) 0xB5, 0x5E, 0x25, 0x1D, 0x29,
0x79 };
// Version 2.0 (0x92)
// NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test
public static final byte[] MFRC522_firmware_referenceV2_0 = { 0x00, (byte) 0xEB, (byte) 0x66, (byte) 0xBA, 0x57,
(byte) 0xBF, 0x23, (byte) 0x95, (byte) 0xD0, (byte) 0xE3, 0x0D, 0x3D, 0x27, (byte) 0x89, 0x5C, (byte) 0xDE,
(byte) 0x9D, 0x3B, (byte) 0xA7, 0x00, 0x21, 0x5B, (byte) 0x89, (byte) 0x82, 0x51, 0x3A, (byte) 0xEB, 0x02,
0x0C, (byte) 0xA5, 0x00, 0x49, 0x7C, (byte) 0x84, 0x4D, (byte) 0xB3, (byte) 0xCC, (byte) 0xD2, 0x1B,
(byte) 0x81, 0x5D, 0x48, 0x76, (byte) 0xD5, 0x71, 0x61, 0x21, (byte) 0xA9, (byte) 0x86, (byte) 0x96,
(byte) 0x83, 0x38, (byte) 0xCF, (byte) 0x9D, 0x5B, 0x6D, (byte) 0xDC, 0x15, (byte) 0xBA, 0x3E, 0x7D,
(byte) 0x95, 0x3B, 0x2F };
// Clone
// Fudan Semiconductor FM17522 (0x88)
public static final byte[] FM17522_firmware_reference = { 0x00, (byte) 0xD6, 0x78, (byte) 0x8C, (byte) 0xE2,
(byte) 0xAA, 0x0C, 0x18, 0x2A, (byte) 0xB8, 0x7A, 0x7F, (byte) 0xD3, (byte) 0x6A, (byte) 0xCF, 0x0B,
(byte) 0xB1, 0x37, 0x63, 0x4B, 0x69, (byte) 0xAE, (byte) 0x91, (byte) 0xC7, (byte) 0xC3, (byte) 0x97,
(byte) 0xAE, 0x77, (byte) 0xF4, 0x37, (byte) 0xD7, (byte) 0x9B, 0x7C, (byte) 0xF5, 0x3C, 0x11, (byte) 0x8F,
0x15, (byte) 0xC3, (byte) 0xD7, (byte) 0xC1, 0x5B, 0x00, 0x2A, (byte) 0xD0, 0x75, (byte) 0xDE, (byte) 0x9E,
0x51, 0x64, (byte) 0xAB, 0x3E, (byte) 0xE9, 0x15, (byte) 0xB5, (byte) 0xAB, 0x56, (byte) 0x9A, (byte) 0x98,
(byte) 0x82, 0x26, (byte) 0xEA, 0x2A, 0x62 };
// The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK.
public static final byte MF_ACK = 0xA;
// A Mifare Crypto1 key is 6 bytes.
public static final byte MF_KEY_SIZE = 6;
// AddicoreRFID error codes
public static final byte MI_OK = 0;
public static final byte MI_NOTAGERR = 1;
public static final byte MI_ERR = 2;
private static final int SPI_CLOCK_FREQUENCY = 1_000_000;
private final SpiDevice device;
private final DigitalOutputDevice resetPin;
private boolean logReadsAndWrites = false;
public MFRC522(int chipSelect, int resetGpio) {
this(SpiConstants.DEFAULT_SPI_CONTROLLER, chipSelect, resetGpio);
}
public MFRC522(int controller, int chipSelect, int resetGpio) {
this(controller, chipSelect, new DigitalOutputDevice(resetGpio, true, false), null);
}
public MFRC522(int controller, int chipSelect, DigitalOutputDevice resetPin) {
this(controller, chipSelect, resetPin, null);
}
public MFRC522(int controller, int chipSelect, int resetGpio, AntennaGain antennaGain) {
this(controller, chipSelect, new DigitalOutputDevice(resetGpio, true, false), antennaGain);
}
public MFRC522(int controller, int chipSelect, DigitalOutputDevice resetPin, AntennaGain antennaGain) {
device = SpiDevice.builder(chipSelect).setController(controller).setFrequency(SPI_CLOCK_FREQUENCY).build();
this.resetPin = resetPin;
init(antennaGain);
}
@Override
public void close() {
if (device != null) {
device.close();
}
if (resetPin != null) {
resetPin.close();
}
}
public void setLogReadsAndWrites(boolean logReadsAndWrites) {
this.logReadsAndWrites = logReadsAndWrites;
}
/////////////////////////////////////////////////////////////////////////////////////
// Basic interface functions for communicating with the MFRC522
/////////////////////////////////////////////////////////////////////////////////////
private void writeRegister(PcdRegister register, PcdCommand command) {
writeRegister(register, command.getValue());
}
private void writeRegister(PcdRegister register, byte value) {
if (logReadsAndWrites) {
Logger.debug("(0x{}, 0x{})", Integer.toHexString(register.getValue() & 0xff),
Integer.toHexString(value & 0xff));
}
byte[] tx = new byte[2];
// Address Format: 0XXXXXX0, the left most "0" indicates a write
tx[0] = register.getAddress();
tx[1] = value;
device.write(tx);
}
private void writeRegister(PcdRegister register, byte[] values) {
writeRegister(register, values, values.length);
}
private void writeRegister(PcdRegister register, byte[] values, int length) {
if (logReadsAndWrites) {
Logger.debug("(0x{}, 0x{}, {} bytes)", Integer.toHexString(register.getValue() & 0xff),
Hex.encodeHexString(values), Integer.valueOf(length));
}
byte[] tx = new byte[length + 1];
// Address Format: 0XXXXXX0, the left most "0" indicates a write
tx[0] = register.getAddress();
System.arraycopy(values, 0, tx, 1, length);
device.write(tx);
}
private byte readRegister(PcdRegister register) {
byte[] tx = new byte[2];
// Address Format: 1XXXXXX0, the first "1" indicates a read
tx[0] = (byte) (register.getAddress() | 0x80);
tx[1] = (byte) 0;
byte[] rx = device.writeAndRead(tx);
if (logReadsAndWrites) {
Logger.debug("(0x{}): 0x{}", Integer.toHexString(register.getValue() & 0xff),
Integer.toHexString(rx[1] & 0xff));
}
return rx[1];
}
private byte[] readRegister(PcdRegister register, int count, byte rxAlign) {
if (count == 0) {
return null;
}
// MSB == 1 is for reading. LSB is not used in address. Datasheet section
// 8.1.2.3.
byte address = (byte) (register.getAddress() | 0x80);
byte[] tx = new byte[count + 1];
int i;
for (i = 0; i < count; i++) {
tx[i] = address;
}
tx[i] = (byte) 0;
byte[] rx = device.writeAndRead(tx);
byte[] values = new byte[count];
// Index in values array.
int index = 0;
int length = count;
i = 1;
// One read is performed outside of the loop
count--;
// Only update bit positions rxAlign..7 in values[0]
if (rxAlign != 0) {
// Create bit mask for bit positions rxAlign..7
byte mask = (byte) ((0xFF << rxAlign) & 0xFF);
// Read value and tell that we want to read the same address again.
byte value = rx[i++];
// Apply mask to both current value of values[0] and the new data in value.
values[0] = (byte) ((values[0] & ~mask) | (value & mask));
index++;
}
while (index < count) {
// Read value and tell that we want to read the same address again.
values[index] = rx[i++];
index++;
}
// Read the final byte. Send 0 to stop reading.
values[index] = rx[i++];
if (logReadsAndWrites) {
Logger.debug("(0x{}, {} bytes, {}): 0x{}", Integer.toHexString(register.getValue() & 0xff),
Integer.valueOf(length), Integer.valueOf(rxAlign), Hex.encodeHexString(values));
}
return values;
}
private void setBitMask(PcdRegister register, byte mask) {
byte current = readRegister(register);
// Logger.debug("Current: 0x" + Integer.toHexString(current&0xff) + ", mask: 0x"
// + Integer.toHexString(mask&0xff));
// Already set?
// if ((current & mask) != mask) {
// Logger.debug("Setting bit mask 0x" + Integer.toHexString(mask&0xff));
writeRegister(register, (byte) (current | mask));
// }
}
private void clearBitMask(PcdRegister register, byte mask) {
byte current = readRegister(register);
// Logger.debug("Current: 0x" + Integer.toHexString(current&0xff) + ", mask: 0x"
// + Integer.toHexString(mask&0xff));
// Already clear?
// if ((current & mask) != 0) {
// Logger.debug("Clearing bit mask 0x" + Integer.toHexString(mask&0xff));
writeRegister(register, (byte) (current & ~mask));
// }
}
private byte[] calculateCRC(byte[] data) {
return calculateCRC(data, data.length);
}
private byte[] calculateCRC(byte[] data, int length) {
writeRegister(PcdRegister.COMMAND_REG, PcdCommand.IDLE); // Stop any active command.
writeRegister(PcdRegister.DIV_IRQ_REG, (byte) 0x04); // Clear the CRCIRq interrupt request bit
writeRegister(PcdRegister.FIFO_LEVEL_REG, (byte) 0x80); // FlushBuffer = 1, FIFO initialization
writeRegister(PcdRegister.FIFO_DATA_REG, data, length); // Write data to the FIFO
writeRegister(PcdRegister.COMMAND_REG, PcdCommand.CALC_CRC); // Start the calculation
// Wait for the CRC calculation to complete (up to 100ms)
long start_ms = System.currentTimeMillis();
do {
// DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq
// reserved reserved
byte n = readRegister(PcdRegister.DIV_IRQ_REG);
if ((n & 0x04) != 0) { // CRCIRq bit set - calculation done
writeRegister(PcdRegister.COMMAND_REG, PcdCommand.IDLE); // Stop calculating CRC for new content in the
// FIFO.
// Transfer the result from the registers to the result buffer
byte[] result = new byte[2];
result[0] = readRegister(PcdRegister.CRC_RESULT_REG_LSB);
result[1] = readRegister(PcdRegister.CRC_RESULT_REG_MSB);
return result;
}
} while ((System.currentTimeMillis() - start_ms) < 100);
// 100ms passed and nothing happend. Communication with the MFRC522 might be
// down.
Logger.error("*** Timed out waiting for CalcCRC to complete");
return null;
}
/////////////////////////////////////////////////////////////////////////////////////
// Functions for manipulating the MFRC522
/////////////////////////////////////////////////////////////////////////////////////
/**
* Initialises the MFRC522 chip.
*
* @param antennaGain new antenna gain value
*/
public void init(AntennaGain antennaGain) {
if (!resetPin.isOn()) {
Logger.debug("reset pin was off");
// Exit power down mode. This triggers a hard reset.
resetPin.on();
// Section 8.8.2 in the datasheet says the oscillator start-up time
// is the start up time of the crystal + 37,74us. Let us be
// generous: 50ms.
SleepUtil.sleepMillis(50);
} else {
// Perform a soft reset if we haven't triggered a hard reset above.
reset();
}
// Reset baud rates
writeRegister(PcdRegister.TX_MODE_REG, (byte) 0x00);
writeRegister(PcdRegister.RX_MODE_REG, (byte) 0x00);
// Reset ModWidthReg
writeRegister(PcdRegister.MOD_WIDTH_REG, (byte) 0x26);
if (antennaGain != null) {
setAntennaGain(antennaGain);
}
// OLD CODE - START
// The following formula is used to calculate the timer frequency if the
// DEMOD_REG register?s TPrescalEven bit is set to logic 0:
// fTimer = 13.56 MHz / (2*TPreScaler+1).
// The following formula is used to calculate the timer frequency if the
// DEMOD_REG register?s TPrescalEven bit inDemoReg is set to logic 1:
// fTimer = 13.56 MHz / (2*TPreScaler+2).
// 110100111110 = 3390; 13_560_000 / 6781 -> fTimer = 1999
// total time delay = ((TPrescaler * 2 + 1) * (TReloadVal + 1)) / 13.56 MHz
// 203430 / 13560000 = 0.015 = 0.015
// Timer: TPrescaler*TReloadVal/6.78MHz = 24ms
/*-
writeRegister(T_MODE_REG, (byte) 0x8D); // Tauto=1; f(Timer) = 6.78MHz/TPreScaler
writeRegister(T_PRESCALER_REG, (byte) 0x3E); // TModeReg[3..0] + TPrescalerReg
// 30
writeRegister(T_RELOAD_REG_MSB, (byte) 0);
writeRegister(T_RELOAD_REG_LSB, (byte) 0x1E);
writeRegister(TX_ASK_REG, (byte) 0x40); // 100%ASK
writeRegister(MODE_REG, (byte) 0x3D); // CRC Initial value 0x6363 ???
*/
// TPrescaler: 000010101001 = 169; 13_560_000 / 169 -> fTimer = 80236
// TReload: 11111101000 = 2024
// ((169 * 2 + 1) * (2024 + 1)) / 13.56 MHz = 0.050
// joan:
// self._PCDWrite(self._TModeReg, 0x80)
// self._PCDWrite(self._TPrescalerReg, 0xA9)
// self._PCDWrite(self._TReloadRegH, 0x03)
// self._PCDWrite(self._TReloadRegL, 0xe8)
// self._PCDWrite(self._TxASKReg, 0x40)
// #self._PCDWrite(self._ModeReg, 0x3D)
// self._PCDWrite(self._ModeReg, 0x29)
// OLD CODE - END
// When communicating with a PICC we need a timeout if something goes wrong.
// f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler =
// [TPrescaler_Hi:TPrescaler_Lo].
// TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is
// TPrescalerReg.
writeRegister(PcdRegister.T_MODE_REG, (byte) 0x80); // TAuto=1; timer starts automatically at the end of the
// transmission in all communication modes at all speeds
writeRegister(PcdRegister.T_PRESCALER_REG, (byte) 0xA9); // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9
// = 169 => f_timer=40kHz, ie a timer period of
// 25us.
writeRegister(PcdRegister.T_RELOAD_REG_MSB, (byte) 0x03); // Reload timer with 0x03E8 = 1000, ie 25ms before
// timeout.
writeRegister(PcdRegister.T_RELOAD_REG_LSB, (byte) 0xE8);
writeRegister(PcdRegister.TX_ASK_REG, (byte) 0x40); // Default 0x00. Force a 100 % ASK modulation independent of
// the ModGsPReg register setting
writeRegister(PcdRegister.MODE_REG, (byte) 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor
// for the CalcCRC command to 0x6363 (ISO 14443-3 part
// 6.2.4)
setAntennaOn(true); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset)
}
/**
* Perform soft reset of AddicoreRFID Module
*/
private void reset() {
writeRegister(PcdRegister.COMMAND_REG, PcdCommand.SOFT_RESET);
// The datasheet does not mention how long the SoftRest command takes to
// complete.
// But the MFRC522 might have been in soft power-down mode (triggered by bit 4
// of CommandReg)
// Section 8.8.2 in the datasheet says the oscillator start-up time is the start
// up time of the crystal + 37,74us. Let us be generous: 50ms.
SleepUtil.sleepMillis(50);
while ((readRegister(PcdRegister.COMMAND_REG) & (1 << 4)) != 0) {
// PCD still restarting - unlikely after waiting 50ms, but better safe than
// sorry.
}
}
/**
* Open antennas, each time you start or shut down the natural barrier between
* the transmitter should be at least 1ms interval
*
* @param on on/off value
*/
public void setAntennaOn(boolean on) {
if (on) {
byte value = readRegister(PcdRegister.TX_CONTROL_REG);
if ((value & 0x03) != 0x03) {
writeRegister(PcdRegister.TX_CONTROL_REG, (byte) (value | 0x03));
}
} else {
clearBitMask(PcdRegister.TX_CONTROL_REG, (byte) 0x03);
}
}
/**
* Get the current MFRC522 Receiver Gain (RxGain[2:0]) value. See 9.3.3.6 /
* table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf NOTE: Return
* value scrubbed with (0x07<<4)=01110000b as RCFfgReg may use reserved
* bits.
*
* @return Value of the RxGain, scrubbed to the 3 bits used.
*/
public AntennaGain getAntennaGain() {
return AntennaGain.forValue(readRegister(PcdRegister.RF_CONFIG_REG));
}
/**
* Set the MFRC522 Receiver Gain (RxGain) to value specified by given mask. See
* 9.3.3.6 / table 98 in https://www.nxp.com/docs/en/data-sheet/MFRC522.pdf
* NOTE: Given mask is scrubbed with (0x07<<4)=01110000b as RCFfgReg may
* use reserved bits.
*
* @param gain New antenna gain value
*/
public void setAntennaGain(AntennaGain gain) {
byte reg_val = readRegister(PcdRegister.RF_CONFIG_REG);
// Only bother if there is a change
if (AntennaGain.forValue(reg_val) != gain) {
// Clear needed to allow 000 pattern
// clearBitMask(PcdRegister.RF_CONFIG_REG, (byte) (0x07 << 4));
reg_val &= ~AntennaGain.BIT_MASK;
// Only set RxGain[2:0] bits (RFCfgReg[6:4]) - all other bits are reserved
// setBitMask(PcdRegister.RF_CONFIG_REG, gain.getValue());
writeRegister(PcdRegister.RF_CONFIG_REG, (byte) (reg_val | gain.getValue()));
}
}
/**
* Performs a self-test of the MFRC522 See 16.1.1 in
* http://www.nxp.com/documents/data_sheet/MFRC522.pdf
*
* @return Whether or not the test passed. Or false if no firmware reference is
* available.
*/
public boolean performSelfTest() {
Logger.debug("Self test - START");
// This follows directly the steps outlined in 16.1.1
// 1. Perform a soft reset.
reset();
// 2. Clear the internal buffer by writing 25 bytes of 00h
byte[] ZEROES = new byte[25];
writeRegister(PcdRegister.FIFO_LEVEL_REG, (byte) 0x80); // flush the FIFO buffer
writeRegister(PcdRegister.FIFO_DATA_REG, ZEROES); // write 25 bytes of 00h to FIFO
writeRegister(PcdRegister.COMMAND_REG, PcdCommand.MEM); // transfer to internal buffer
// 3. Enable self-test
writeRegister(PcdRegister.AUTO_TEST_REG, (byte) 0x09);
// 4. Write 00h to FIFO buffer
writeRegister(PcdRegister.FIFO_DATA_REG, (byte) 0x00);
// 5. Start self-test by issuing the CalcCRC command
writeRegister(PcdRegister.COMMAND_REG, PcdCommand.CALC_CRC);
// 6. Wait for self-test to complete
byte n;
for (int i = 0; i < 0xFF; i++) {
// The datasheet does not specify exact completion condition except
// that FIFO buffer should contain 64 bytes.
// While selftest is initiated by CalcCRC command
// it behaves differently from normal CRC computation,
// so one can't reliably use DivIrqReg to check for completion.
// It is reported that some devices does not trigger CRCIRq flag
// during selftest.
n = readRegister(PcdRegister.FIFO_LEVEL_REG);
if (n >= 64) {
break;
}
}
writeRegister(PcdRegister.COMMAND_REG, PcdCommand.IDLE); // Stop calculating CRC for new content in the FIFO.
// 7. Read out resulting 64 bytes from the FIFO buffer.
// byte[] result = readRegister(FIFO_DATA_REG, 64);
byte[] result = new byte[64];
for (int i = 0; i < result.length; i++) {
result[i] = readRegister(PcdRegister.FIFO_DATA_REG);
}
// Auto self-test done
// Reset AutoTestReg register to be 0 again. Required for normal operation.
writeRegister(PcdRegister.AUTO_TEST_REG, (byte) 0x00);
// Determine firmware version (see section 9.3.4.8 in spec)
int version = readRegister(PcdRegister.VERSION_REG) & 0xff;
Logger.debug("version: 0x" + Integer.toHexString(version));
// Pick the appropriate reference values
byte[] reference;
switch (version) {
case 0x88: // Fudan Semiconductor FM17522 clone
reference = FM17522_firmware_reference;
break;
case 0x90: // Version 0.0
reference = MFRC522_firmware_referenceV0_0;
break;
case 0x91: // Version 1.0
reference = MFRC522_firmware_referenceV1_0;
break;
case 0x92: // Version 2.0
reference = MFRC522_firmware_referenceV2_0;
break;
default: // Unknown version
Logger.debug("Self test - END - FAIL");
return false; // abort test
}
// Verify that the results match up to our expectations
for (int i = 0; i < 64; i++) {
if (result[i] != reference[i]) {
Logger.debug("Self test - END - FAIL");
return false;
}
}
Logger.debug("Self test - END - PASS");
// Test passed; all is good.
return true;
} // End PCD_PerformSelfTest()
public int getVersion() {
return readRegister(PcdRegister.VERSION_REG) & 0xff;
}
/////////////////////////////////////////////////////////////////////////////////////
// Functions for communicating with PICCs
/////////////////////////////////////////////////////////////////////////////////////
/**
* Executes the Transceive command. CRC validation can only be done if backData
* and backLen are specified.
*
* @param sendData Pointer to the data to transfer to the FIFO.
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
private Response transceiveData(byte[] sendData) {
return transceiveData(sendData, (byte) 0, (byte) 0, false);
}
/**
* Executes the Transceive command. CRC validation can only be done if backData
* and backLen are specified.
*
* @param sendData Pointer to the data to transfer to the FIFO.
* @param rxAlign Defines the bit position in backData[0] for the first bit
* received. Default 0.
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
private Response transceiveData(byte[] sendData, byte rxAlign) {
return transceiveData(sendData, rxAlign, (byte) 0, false);
}
/**
* Executes the Transceive command. CRC validation can only be done if backData
* and backLen are specified.
*
* @param sendData Pointer to the data to transfer to the FIFO.
* @param validBits The number of valid bits in the last byte. 0 for 8 valid
* bits
* @param rxAlign Defines the bit position in backData[0] for the first bit
* received. Default 0.
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
private Response transceiveData(byte[] sendData, byte validBits, byte rxAlign) {
return transceiveData(sendData, validBits, rxAlign, false);
}
/**
* Executes the Transceive command. CRC validation can only be done if backData
* and backLen are specified.
*
* @param sendData Pointer to the data to transfer to the FIFO.
* @param validBits The number of valid bits in the last byte. 0 for 8 valid
* bits
* @param rxAlign Defines the bit position in backData[0] for the first bit
* received. Default 0.
* @param checkCRC True => The last two bytes of the response is assumed to be
* a CRC_A that must be validated.
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
private Response transceiveData(byte[] sendData, byte validBits, byte rxAlign, boolean checkCRC) {
byte waitIRq = 0x30; // RxIRq and IdleIRq
return communicateWithPICC(PcdCommand.TRANSCEIVE, waitIRq, sendData, validBits, rxAlign, checkCRC);
}
private Response communicateWithPICC(PcdCommand command, byte waitIRq, byte[] sendData) {
return communicateWithPICC(command, waitIRq, sendData, (byte) 0, (byte) 0, false);
}
private Response communicateWithPICC(PcdCommand command, byte waitIRq, byte[] sendData, byte validBits) {
return communicateWithPICC(command, waitIRq, sendData, validBits, (byte) 0, false);
}
/**
* Transfers data to the MFRC522 FIFO, executes a command, waits for completion
* and transfers data back from the FIFO. CRC validation can only be done if
* backData and backLen are specified.
*
* @param command The command to execute. One of the PCD_Command enums.
* @param waitIRq The bits in the ComIrqReg register that signals successful
* completion of the command.
* @param sendData Data to transfer to the FIFO.
* @param validBits The number of valid bits in the last byte. 0 for 8 valid
* bits.
* @param rxAlign Defines the bit position in backData[0] for the first bit
* received. Default 0.
* @param checkCRC True => The last two bytes of the response is assumed to be
* a CRC_A that must be validated.
* @return response
*/
private Response communicateWithPICC(PcdCommand command, byte waitIRq, byte[] sendData, byte validBits,
byte rxAlign, boolean checkCRC) {
// Prepare values for BitFramingReg
byte tx_last_bits = validBits;
byte bit_framing = (byte) ((rxAlign << 4) + tx_last_bits); // RxAlign = BitFramingReg[6..4]. TxLastBits =
// BitFramingReg[2..0]
writeRegister(PcdRegister.COMMAND_REG, PcdCommand.IDLE); // Stop any active command.
writeRegister(PcdRegister.COM_IRQ_REG, (byte) 0x7f); // Clear all seven interrupt request bits
writeRegister(PcdRegister.FIFO_LEVEL_REG, (byte) 0x80); // FlushBuffer=1, FIFO Initialisation
writeRegister(PcdRegister.FIFO_DATA_REG, sendData); // Write sendData to the FIFO
writeRegister(PcdRegister.BIT_FRAMING_REG, bit_framing); // Bit adjustments
writeRegister(PcdRegister.COMMAND_REG, command); // Execute the command
if (command == PcdCommand.TRANSCEIVE) {
setBitMask(PcdRegister.BIT_FRAMING_REG, (byte) 0x80); // StartSend=1, transmission of data starts
}
// Wait for the command to complete.
// In PCD_Init() we set the TAuto flag in TModeReg. This means the timer
// automatically starts when the PCD stops transmitting.
long start_ms = System.currentTimeMillis();
boolean timeout = true;
do {
byte n = readRegister(PcdRegister.COM_IRQ_REG);
if ((n & waitIRq) != 0) {
// One of the interrupts that signal success has been set.
timeout = false;
break;
}
// Timer interrupt - nothing received in 25ms
if ((n & 0x01) != 0) {
Logger.debug("timer interrupt, n: 0x" + Integer.toHexString(n & 0xff));
break;
}
} while ((System.currentTimeMillis() - start_ms) < 200);
// 35.7ms and nothing happend. Communication with the MFRC522 might be down.
if (timeout) {
Logger.debug("Timed out waiting for interrupt");
return new Response(StatusCode.TIMEOUT);
}
// StartSend=0
// clearBitMask(PcdRegister.BIT_FRAMING_REG, (byte) 0x80);
// Stop now if any errors except collisions were detected.
// ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr
// ParityErr ProtocolErr
byte error_reg_val = readRegister(PcdRegister.ERROR_REG);
// BufferOvfl ParityErr ProtocolErr
if ((error_reg_val & 0x13) != 0) {
Logger.error("*** Error reg val: 0x" + Integer.toHexString(error_reg_val & 0xff));
return new Response(StatusCode.ERROR);
}
int back_len = 0;
byte valid_bits = 0;
// If the caller wants data back, get it from the MFRC522.
byte[] back_data = new byte[0];
// TODO Check this is equivalent
// if (backData && backLen) {
// FIXME Also TRANSCEIVE when called from haltA()
if (command != PcdCommand.MF_AUTHENT) {
// Number of bytes in the FIFO
back_len = readRegister(PcdRegister.FIFO_LEVEL_REG) & 0xff;
Logger.debug("FIFO Level: " + back_len);
// Get received data from FIFO
/*-
back_data = new byte[back_len];
for (int index=0; index 0 && checkCRC) {
Logger.debug("Checking CRC");
// In this case a MIFARE Classic NAK is not OK.
if (back_len == 1 && valid_bits == 4) {
Logger.error("*** MIFARE Classic NAK is not ok");
return new Response(StatusCode.MIFARE_NACK);
}
// We need at least the CRC_A value and all 8 bits of the last byte must be
// received.
if (back_len < 2 || valid_bits != 0) {
Logger.error("*** CRC was wrong");
return new Response(StatusCode.CRC_WRONG);
}
// Verify CRC_A - do our own calculation and store the control in controlBuffer.
byte[] control_buffer = calculateCRC(back_data, back_len - 2);
if (control_buffer == null) {
Logger.error("*** Control buffer from PCD_CalculateCRC was null");
return new Response(StatusCode.TIMEOUT);
}
if ((back_data[back_len - 2] != control_buffer[0]) || (back_data[back_len - 1] != control_buffer[1])) {
Logger.error("*** CRC was wrong");
return new Response(StatusCode.CRC_WRONG);
}
}
Logger.debug("ok");
return new Response(StatusCode.OK, back_data, back_len, valid_bits);
}
/**
* Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to
* READY and prepare for anticollision or selection. 7 bit frame. Beware: When
* two PICCs are in the field at the same time I often get STATUS_TIMEOUT -
* probably due do bad antenna design.
*
* @param bufferATQA The buffer to store the ATQA (Answer to request) in
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
public StatusCode requestA(byte[] bufferATQA) {
return requestAOrWakeUpA(PiccCommand.REQUEST_A, bufferATQA);
}
/**
* Transmits a Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to
* go to READY(*) and prepare for anticollision or selection. 7 bit frame.
* Beware: When two PICCs are in the field at the same time I often get
* STATUS_TIMEOUT - probably due do bad antenna design.
*
* @param bufferATQA The buffer to store the ATQA (Answer to request) in
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
public StatusCode wakeupA(byte[] bufferATQA) {
return requestAOrWakeUpA(PiccCommand.WAKE_UP_A, bufferATQA);
}
/**
* Transmits REQA or WUPA commands. Beware: When two PICCs are in the field at
* the same time I often get STATUS_TIMEOUT - probably due do bad antenna
* design.
*
* @param command The command to send - PICC_CMD_REQA or PICC_CMD_WUPA
* @param bufferATQA The buffer to store the ATQA (Answer to request) in
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
public StatusCode requestAOrWakeUpA(PiccCommand command, byte[] bufferATQA) {
// The ATQA response is 2 bytes long.
if (bufferATQA == null || bufferATQA.length != 2) {
return StatusCode.NO_ROOM;
}
// ValuesAfterColl=1 => Bits received after collision are cleared.
clearBitMask(PcdRegister.COLL_REG, (byte) 0x80);
// For REQA and WUPA we need the short frame format - transmit only 7 bits of
// the last (and only) byte. TxLastBits = BitFramingReg[2..0]
byte valid_bits = 7;
Response response = transceiveData(new byte[] { command.getValue() }, valid_bits);
valid_bits = response.getValidBits();
if (response.getStatus() != StatusCode.OK) {
return response.getStatus();
}
byte[] back_data = response.getBackData();
int back_len = response.getBackLen();
// ATQA must be exactly 16 bits.
if (back_len != 2 || response.getValidBits() != 0) {
return StatusCode.ERROR;
}
System.arraycopy(back_data, 0, bufferATQA, 0, back_len);
return StatusCode.OK;
}
public UID select() {
return select((byte) 0);
}
/**
* Transmits SELECT/ANTICOLLISION commands to select a single PICC. Before
* calling this function the PICCs must be placed in the READY(*) state by
* calling PICC_RequestA() or PICC_WakeupA(). On success: - The chosen PICC is
* in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT.
* (Figure 7 of the ISO/IEC 14443-3 draft.) - The UID size and value of the
* chosen PICC is returned in *uid along with the SAK.
*
* A PICC UID consists of 4, 7 or 10 bytes. Only 4 bytes can be specified in a
* SELECT command, so for the longer UIDs two or three iterations are used: UID
* size Number of UID bytes Cascade levels Example of PICC ========
* =================== ============== =============== single 4 1 MIFARE Classic
* double 7 2 MIFARE Ultralight triple 10 3 Not currently in use?
*
* @param validBits The number of known UID bits supplied in *uid. Normally 0.
* If set you must also supply uid->size.
* @return UID object or null if there was an error.
*/
public UID select(byte validBits) {
byte[] buffer = new byte[9]; // The SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes
// CRC_A
/*-
* Description of buffer structure:
* Byte 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3
* Byte 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete bytes, Low nibble: Extra bits.
* Byte 2: UID-data or CT See explanation below. CT means Cascade Tag.
* Byte 3: UID-data
* Byte 4: UID-data
* Byte 5: UID-data
* Byte 6: BCC Block Check Character - XOR of bytes 2-5
* Byte 7: CRC_A
* Byte 8: CRC_A
* The BCC and CRC_A are only transmitted if we know all the UID bits of the current Cascade Level.
*
* Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels)
* UID size Cascade level Byte2 Byte3 Byte4 Byte5
* ======== ============= ===== ===== ===== =====
* 4 bytes 1 uid0 uid1 uid2 uid3
* 7 bytes 1 CT uid0 uid1 uid2
* 2 uid3 uid4 uid5 uid6
* 10 bytes 1 CT uid0 uid1 uid2
* 2 CT uid3 uid4 uid5
* 3 uid6 uid7 uid8 uid9
*/
// Sanity checks
if (validBits > 80) {
Logger.error("*** Error: validBits ({}) was > 80", Byte.valueOf(validBits));
return null;
}
// Prepare MFRC522
clearBitMask(PcdRegister.COLL_REG, (byte) 0x80); // ValuesAfterColl=1 => Bits received after collision are
// cleared.
// Repeat Cascade Level loop until we have a complete UID.
boolean uid_complete = false;
boolean use_cascade_tag;
byte cascade_level = 1;
List uid_bytes = new ArrayList<>();
byte uid_sak = 0;
byte uid_index; // The first index in uid->uidByte[] that is used in the current Cascade Level.
int current_level_known_bits; // The number of known UID bits in the current Cascade Level.
int response_buffer_offset;
while (!uid_complete) {
Logger.debug("uid_complete: " + uid_complete + ", cascade_level: " + cascade_level);
// Set the Cascade Level in the SEL byte, find out if we need to use the Cascade
// Tag in byte 2.
switch (cascade_level) {
case 1:
buffer[0] = PiccCommand.SEL_CL1.getValue();
uid_index = 0;
use_cascade_tag = (validBits != 0) && (uid_bytes.size() > 4); // When we know that the UID has more than
// 4 bytes
break;
case 2:
buffer[0] = PiccCommand.SEL_CL2.getValue();
uid_index = 3;
use_cascade_tag = (validBits != 0) && (uid_bytes.size() > 7); // When we know that the UID has more than
// 7 bytes
break;
case 3:
buffer[0] = PiccCommand.SEL_CL3.getValue();
uid_index = 6;
use_cascade_tag = false; // Never used in CL3.
break;
default:
Logger.error("*** Error: invalid cascade_level ()", Byte.valueOf(cascade_level));
return null;
}
// How many UID bits are known in this Cascade Level?
current_level_known_bits = validBits - (8 * uid_index);
if (current_level_known_bits < 0) {
current_level_known_bits = 0;
}
// Copy the known bits from uid->uidByte[] to buffer[]
int index = 2; // destination index in buffer[]
if (use_cascade_tag) {
buffer[index++] = PiccCommand.CASCADE_TAG.getValue();
}
// The number of bytes needed to represent the known bits for this level.
int bytesToCopy = current_level_known_bits / 8 + (((current_level_known_bits % 8) != 0) ? 1 : 0);
if (bytesToCopy != 0) {
// Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag
int maxBytes = use_cascade_tag ? 3 : 4;
if (bytesToCopy > maxBytes) {
bytesToCopy = maxBytes;
}
for (int count = 0; count < bytesToCopy; count++) {
buffer[index++] = uid_bytes.get(uid_index + count).byteValue();
}
}
// Now that the data has been copied we need to include the 8 bits in CT in
// currentLevelKnownBits
if (use_cascade_tag) {
current_level_known_bits += 8;
}
// Repeat anti collision loop until we can transmit all UID bits + BCC and
// receive a SAK - max 32 iterations.
boolean select_done = false;
int buffer_used; // The number of bytes used in the buffer, ie the number of bytes to transfer to
// the FIFO.
int tx_last_bits = 0; // Used in BitFramingReg. The number of valid bits in the last transmitted byte.
int response_length = 0;
byte[] response_buffer = {};
while (!select_done) {
Logger.debug("select_done: " + select_done);
// Find out how many bits and bytes to send and receive.
if (current_level_known_bits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT.
Logger.debug("SELECT: current_level_known_bits={}", Integer.valueOf(current_level_known_bits));
buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole bytes
// Calculate BCC - Block Check Character
buffer[6] = (byte) (buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]);
// Calculate CRC_A
// result = PCD_CalculateCRC(buffer, 7, &buffer[7]);
byte[] crc = calculateCRC(buffer, 7);
if (crc == null) {
Logger.error("*** Error calculating CRC");
return null;
}
// Note C++ code stores CRC in the last 2 bytes of buffer
System.arraycopy(crc, 0, buffer, 7, crc.length);
tx_last_bits = 0; // 0 => All 8 bits are valid.
buffer_used = 9;
// Store response in the last 3 bytes of buffer (BCC and CRC_A - not needed
// after tx)
// responseBuffer = &buffer[6];
// responseLength = 3;
response_buffer_offset = 6;
response_length = crc.length + 1;
} else { // This is an ANTICOLLISION.
Logger.debug("ANTICOLLISION: current_level_known_bits={}",
Integer.valueOf(current_level_known_bits));
tx_last_bits = current_level_known_bits % 8;
int count = current_level_known_bits / 8; // Number of whole bytes in the UID part.
index = 2 + count; // Number of whole bytes: SEL + NVB + UIDs
buffer[1] = (byte) ((index << 4) + tx_last_bits); // NVB - Number of Valid Bits
buffer_used = index + ((tx_last_bits != 0) ? 1 : 0);
// Store response in the unused part of buffer
// responseBuffer = &buffer[index];
// responseLength = sizeof(buffer) - index;
response_buffer_offset = index;
response_length = buffer.length - index;
}
// Set bit adjustments
// Having a separate variable is overkill. But it makes the next line easier to
// read.
int rx_align = tx_last_bits;
// RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0]
writeRegister(PcdRegister.BIT_FRAMING_REG, (byte) ((rx_align << 4) + tx_last_bits));
Logger.debug("tx_last_bits: " + tx_last_bits);
// Transmit the buffer and receive the response.
byte[] tx_data = new byte[buffer_used];
System.arraycopy(buffer, 0, tx_data, 0, buffer_used);
Response response = transceiveData(tx_data, (byte) tx_last_bits, (byte) rx_align);
// result = PCD_TransceiveData(buffer, bufferUsed, responseBuffer,
// &responseLength, &txLastBits, rxAlign);
if (response.getBackData() == null) {
Logger.error("*** got no back data from transcieveData, aborting");
return null;
}
System.arraycopy(response.getBackData(), 0, buffer, response_buffer_offset, response.getBackLen());
response_buffer = response.getBackData();
response_length = response.getBackLen();
tx_last_bits = response.getValidBits();
if (response.getStatus() == StatusCode.COLLISION) { // More than one PICC in the field => collision.
byte valueOfCollReg = readRegister(PcdRegister.COLL_REG); // CollReg[7..0] bits are: ValuesAfterColl
// reserved CollPosNotValid CollPos[4:0]
if ((valueOfCollReg & 0x20) != 0) { // CollPosNotValid
Logger.error("*** valueOfCollReg ({}) has bit 0x20 set", Byte.valueOf(valueOfCollReg));
return null; // Without a valid collision position we cannot continue
}
int collisionPos = valueOfCollReg & 0x1F; // Values 0-31, 0 means bit 32.
if (collisionPos == 0) {
collisionPos = 32;
}
if (collisionPos <= current_level_known_bits) { // No progress - should not happen
Logger.error("*** collisionPos ({}) is <= current_level_known_bits ({})",
Integer.valueOf(collisionPos), Integer.valueOf(current_level_known_bits));
// return StatusCode.INTERNAL_ERROR;
return null;
}
// Choose the PICC with the bit set.
current_level_known_bits = collisionPos;
int count = (current_level_known_bits - 1) % 8; // The bit to modify
index = 1 + (current_level_known_bits / 8) + (count != 0 ? 1 : 0); // First byte is index 0.
buffer[index] |= (1 << count);
} else if (response.getStatus() != StatusCode.OK) {
Logger.error("*** Invalid response from PCD_TransceiveData: {}", response.getStatus());
// return response.getStatus();
return null;
} else { // STATUS_OK
if (current_level_known_bits >= 32) { // This was a SELECT.
select_done = true; // No more anticollision
// We continue below outside the while.
} else { // This was an ANTICOLLISION.
// We now have all 32 bits of the UID in this Cascade Level
current_level_known_bits = 32;
// Run loop again to do the SELECT.
}
}
} // End of while (!selectDone)
// We do not check the CBB - it was constructed by us above.
// Copy the found UID bytes from buffer[] to uid->uidByte[]
index = (buffer[2] == PiccCommand.CASCADE_TAG.getValue()) ? 3 : 2; // source index in buffer[]
bytesToCopy = (buffer[2] == PiccCommand.CASCADE_TAG.getValue()) ? 3 : 4;
for (int count = 0; count < bytesToCopy; count++) {
uid_bytes.add(Byte.valueOf(buffer[index++]));
if (uid_bytes.size() != uid_index + count + 1) {
Logger.error("*** Error, expected uid_bytes size to be " + (uid_index + count + 1) + ", but was "
+ uid_bytes.size());
}
}
// Check response SAK (Select Acknowledge)
if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 byte + CRC_A).
Logger.error("*** SAK must be exactly 24 bits (1 byte + CRC_A), response_length={}",
Integer.valueOf(response_length));
// return StatusCode.ERROR;
return null;
}
// Verify CRC_A - do our own calculation
byte[] crc = calculateCRC(response_buffer, 1);
if (crc == null) {
Logger.error("*** Error in PCD_CalculateCRC");
// return StatusCode.CRC_WRONG;
return null;
}
if ((crc[0] != response_buffer[1]) || (crc[1] != response_buffer[2])) {
Logger.error("*** CRC was wrong");
// return StatusCode.CRC_WRONG;
return null;
}
if ((response_buffer[0] & 0x04) != 0) { // Cascade bit set - UID not complete yes
cascade_level++;
} else {
uid_complete = true;
uid_sak = response_buffer[0];
}
} // End of while (!uidComplete)
UID uid = new UID(uid_bytes, uid_sak);
Logger.debug("End of while (!uidComplete) loop, uid: " + uid);
// Set correct uid->size
// uid.setSize(3 * cascade_level + 1);
if (uid.getSize() != (3 * cascade_level + 1)) {
Logger.error("*** Expected UID size to be " + (3 * cascade_level + 1) + " but was " + uid.getSize());
}
return uid;
}
/**
* Instructs a PICC in state ACTIVE(*) to go to state HALT.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
public StatusCode haltA() {
byte[] buffer = new byte[4];
// Build command buffer
buffer[0] = PiccCommand.HALT_A.getValue();
buffer[1] = 0;
// Calculate CRC_A
byte[] crc = calculateCRC(buffer);
if (crc == null) {
return StatusCode.TIMEOUT;
}
System.arraycopy(crc, 0, buffer, 2, crc.length);
// Send the command.
// The standard says:
// If the PICC responds with any modulation during a period of 1 ms after the
// end of the frame containing the
// HLTA command, this response shall be interpreted as 'not acknowledge'.
// We interpret that this way: Only STATUS_TIMEOUT is a success.
Response response = transceiveData(buffer);
StatusCode result = response.getStatus();
if (result == StatusCode.TIMEOUT) {
return StatusCode.OK;
}
if (result == StatusCode.OK) { // That is ironically NOT ok in this case ;-)
return StatusCode.ERROR;
}
return result;
}
/**
* Executes the MFRC522 MFAuthent command. This command manages MIFARE
* authentication to enable a secure communication to any MIFARE Mini, MIFARE 1K
* and MIFARE 4K card. The authentication is described in the MFRC522 datasheet
* section 10.3.1.9 and http://www.nxp.com/documents/data_sheet/MF1S503x.pdf
* section 10.1. For use with MIFARE Classic PICCs. The PICC must be selected -
* ie in state ACTIVE(*) - before calling this function. Remember to call
* PCD_StopCrypto1() after communicating with the authenticated PICC - otherwise
* no new communications can start.
*
* All keys are set to FFFFFFFFFFFFh at chip delivery.
*
* @param authKeyA PICC_CMD_MF_AUTH_KEY_A or PICC_CMD_MF_AUTH_KEY_B
* @param blockAddr The block number. See numbering in the comments in the .h
* file.
* @param key Crypto1 key to use (6 bytes)
* @param uid Pointer to Uid struct. The first 4 bytes of the UID is used.
* @return STATUS_OK on success, STATUS_??? otherwise. Probably STATUS_TIMEOUT
* if you supply the wrong key.
*/
public StatusCode authenticate(boolean authKeyA, byte blockAddr, byte[] key, UID uid) {
Logger.debug("blockAddr: " + blockAddr);
// Build command buffer
byte[] sendData = new byte[12];
sendData[0] = authKeyA ? PiccCommand.MF_AUTH_KEY_A.getValue() : PiccCommand.MF_AUTH_KEY_B.getValue();
sendData[1] = blockAddr;
System.arraycopy(key, 0, sendData, 2, key.length);
/*
* for (byte i=0; i> 8);
buffer[2] = (byte) ((data & 0xFF0000) >> 16);
buffer[3] = (byte) ((data & 0xFF000000) >> 24);
return mifareTransceive(buffer, true); // Adds CRC_A and accept timeout as success.
} // End MIFARE_TwoStepHelper()
/**
* MIFARE Transfer writes the value stored in the volatile memory into one
* MIFARE Classic block. For MIFARE Classic only. The sector containing the
* block must be authenticated before calling this function. Only for blocks in
* "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001].
*
* @param blockAddr The block (0-0xff) number.
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
public StatusCode mifareTransfer(byte blockAddr) {
// Tell the PICC we want to transfer the result into block blockAddr.
byte[] cmdBuffer = { PiccCommand.MF_TRANSFER.getValue(), blockAddr };
return mifareTransceive(cmdBuffer); // Adds CRC_A and checks that the response is MF_ACK.
} // End MIFARE_Transfer()
/**
* Helper routine to read the current value from a Value Block.
*
* Only for MIFARE Classic and only for blocks in "value block" mode, that is:
* with access bits [C1 C2 C3] = [110] or [001]. The sector containing the block
* must be authenticated before calling this function.
*
* @param blockAddr The block (0x00-0xff) number.
* @return Integer value or null if error.
*/
public Integer mifareGetValue(byte blockAddr) {
// Read the block
byte[] buffer = mifareRead(blockAddr);
if (buffer == null) {
return null;
}
// Extract the value
return Integer.valueOf(((buffer[3] & 0xff) << 24) | ((buffer[2] & 0xff) << 16) | ((buffer[1] & 0xff) << 8)
| (buffer[0] & 0xff));
} // End MIFARE_GetValue()
/**
* Helper routine to write a specific value into a Value Block.
*
* Only for MIFARE Classic and only for blocks in "value block" mode, that is:
* with access bits [C1 C2 C3] = [110] or [001]. The sector containing the block
* must be authenticated before calling this function.
*
* @param blockAddr The block (0x00-0xff) number.
* @param value New value of the Value Block.
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
public StatusCode mifareSetValue(byte blockAddr, int value) {
byte[] buffer = new byte[16];
// Translate the int32_t into 4 bytes; repeated 2x in value block
buffer[0] = buffer[8] = (byte) (value & 0xFF);
buffer[1] = buffer[9] = (byte) ((value & 0xFF00) >> 8);
buffer[2] = buffer[10] = (byte) ((value & 0xFF0000) >> 16);
buffer[3] = buffer[11] = (byte) ((value & 0xFF000000) >> 24);
// Inverse 4 bytes also found in value block
buffer[4] = (byte) ~buffer[0];
buffer[5] = (byte) ~buffer[1];
buffer[6] = (byte) ~buffer[2];
buffer[7] = (byte) ~buffer[3];
// Address 2x with inverse address 2x
buffer[12] = buffer[14] = blockAddr;
buffer[13] = buffer[15] = (byte) ~blockAddr;
// Write the whole data block
return mifareWrite(blockAddr, buffer);
} // End MIFARE_SetValue()
/*
* Authenticate with a NTAG216.
*
* Only for NTAG216. First implemented by Gargantuanman.
*
* @param passWord password.
*
* @param pACK result success???.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
/*-
public StatusCode ntag216Auth(byte[] passWord, byte pACK[]) //Authenticate with 32bit password
{
// TODO: Fix cmdBuffer length and rxlength. They really should match.
// (Better still, rxlength should not even be necessary.)
StatusCode result;
byte cmdBuffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A.
cmdBuffer[0] = 0x1B; //Comando de autentificacion
for (byte i = 0; i<4; i++)
cmdBuffer[i+1] = passWord[i];
result = PCD_CalculateCRC(cmdBuffer, 5, &cmdBuffer[5]);
if (result!=STATUS_OK) {
return result;
}
// Transceive the data, store the reply in cmdBuffer[]
byte waitIRq = 0x30; // RxIRq and IdleIRq
//byte cmdBufferSize = sizeof(cmdBuffer);
byte validBits = 0;
byte rxlength = 5;
result = PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, cmdBuffer, 7, cmdBuffer, &rxlength, &validBits);
pACK[0] = cmdBuffer[0];
pACK[1] = cmdBuffer[1];
if (result!=STATUS_OK) {
return result;
}
return STATUS_OK;
} // End PCD_NTAG216_AUTH()
*/
/////////////////////////////////////////////////////////////////////////////////////
// Support functions
/////////////////////////////////////////////////////////////////////////////////////
public StatusCode mifareTransceive(byte[] sendData) {
return mifareTransceive(sendData, false);
}
/**
* Wrapper for MIFARE protocol communication. Adds CRC_A, executes the
* Transceive command and checks that the response is MF_ACK or a timeout.
*
* @param sendData Data to transfer to the FIFO. Do NOT include the CRC_A.
* @param acceptTimeout A timeout is also success
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
public StatusCode mifareTransceive(byte[] sendData, boolean acceptTimeout) {
// Sanity check
//if (sendData == null || sendData.length != 16) {
// return StatusCode.INVALID;
//}
/*- C++ Code:
byte cmdBuffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A.
// Copy sendData[] to cmdBuffer[] and add CRC_A
memcpy(cmdBuffer, sendData, sendLen);
result = PCD_CalculateCRC(cmdBuffer, sendLen, &cmdBuffer[sendLen]);
if (result != STATUS_OK) {
return result;
}
sendLen += 2;
*/
byte[] crc = calculateCRC(sendData);
if (crc == null) {
return StatusCode.TIMEOUT;
}
// Transceive the data
byte waitIRq = 0x30; // RxIRq and IdleIRq
byte validBits = 0;
byte[] cmdBuffer = new byte[sendData.length + 2];
System.arraycopy(sendData, 0, cmdBuffer, 0, sendData.length);
System.arraycopy(crc, 0, cmdBuffer, sendData.length, crc.length);
// result = PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, cmdBuffer, sendLen,
// cmdBuffer, &cmdBufferSize, &validBits);
Response result = communicateWithPICC(PcdCommand.TRANSCEIVE, waitIRq, cmdBuffer, validBits);
validBits = result.getValidBits();
if (acceptTimeout && result.getStatus() == StatusCode.TIMEOUT) {
return StatusCode.OK;
}
if (result.getStatus() != StatusCode.OK) {
return result.getStatus();
}
// The PICC must reply with a 4 bit ACK
if (result.getBackData().length != 1 || validBits != 4) {
return StatusCode.ERROR;
}
if (result.getBackData()[0] != MF_ACK) {
return StatusCode.MIFARE_NACK;
}
return StatusCode.OK;
} // End PCD_MIFARE_Transceive()
/**
* Translates the SAK (Select Acknowledge) to a PICC type.
*
* @param sak The SAK byte returned from PICC_Select().
* @return PICC_Type
*/
public static PiccType getPiccType(byte sak) {
// http://www.nxp.com/documents/application_note/AN10833.pdf
// 3.2 Coding of Select Acknowledge (SAK)
// ignore 8-bit (iso14443 starts with LSBit = bit 1)
// fixes wrong type for manufacturer Infineon
// (http://nfc-tools.org/index.php?title=ISO14443A)
return PiccType.forId(sak &= 0x7F);
} // End PICC_GetType()
/**
* Calculates the bit pattern needed for the specified access bits. In the [C1
* C2 C3] tuples C1 is MSB (=4) and C3 is LSB (=1).
*/
/*-
public void mifareSetAccessBits( byte *accessBitBuffer, ///< Pointer to byte 6, 7 and 8 in the sector trailer. Bytes [0..2] will be set.
byte g0, ///< Access bits [C1 C2 C3] for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39)
byte g1, ///< Access bits C1 C2 C3] for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39)
byte g2, ///< Access bits C1 C2 C3] for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39)
byte g3 ///< Access bits C1 C2 C3] for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39)
) {
byte c1 = ((g3 & 4) << 1) | ((g2 & 4) << 0) | ((g1 & 4) >> 1) | ((g0 & 4) >> 2);
byte c2 = ((g3 & 2) << 2) | ((g2 & 2) << 1) | ((g1 & 2) << 0) | ((g0 & 2) >> 1);
byte c3 = ((g3 & 1) << 3) | ((g2 & 1) << 2) | ((g1 & 1) << 1) | ((g0 & 1) << 0);
accessBitBuffer[0] = (~c2 & 0xF) << 4 | (~c1 & 0xF);
accessBitBuffer[1] = c1 << 4 | (~c3 & 0xF);
accessBitBuffer[2] = c3 << 4 | c2;
} // End MIFARE_SetAccessBits()
*/
/**
* Performs the "magic sequence" needed to get Chinese UID changeable Mifare
* cards to allow writing to sector 0, where the card UID is stored.
*
* Note that you do not need to have selected the card through REQA or WUPA,
* this sequence works immediately when the card is in the reader vicinity. This
* means you can use this method even on "bricked" cards that your reader does
* not recognise anymore (see MFRC522::MIFARE_UnbrickUidSector).
*
* Of course with non-bricked devices, you're free to select them before calling
* this function.
*
* @return Status
*/
public boolean mifareOpenUidBackdoor() {
// Magic sequence:
// > 50 00 57 CD (HALT + CRC)
// > 40 (7 bits only)
// < A (4 bits only)
// > 43
// < A (4 bits only)
// Then you can write to sector 0 without authenticating
haltA(); // 50 00 57 CD
byte[] cmd = { 0x40 };
byte validBits = 7; /*
* Our command is only 7 bits. After receiving card response, this will contain
* amount of valid response bits.
*/
Response resp = transceiveData(cmd, validBits, (byte) 0, false); // 40
byte[] response = resp.getBackData();
int received = resp.getBackLen();
validBits = resp.getValidBits();
if (resp.getStatus() != StatusCode.OK) {
Logger.error(
"Card did not respond to 0x40 after HALT command. Are you sure it is a UID changeable one? Error: {}",
resp.getStatus());
return false;
}
if (received != 1 || response[0] != 0x0A) {
Logger.error("Got bad response on backdoor 0x40 command: 0x{} ({} valid bits)",
Integer.toHexString(response[0]), Integer.valueOf(validBits));
return false;
}
cmd = new byte[] { 0x43 };
validBits = 8;
resp = transceiveData(cmd, validBits, (byte) 0, false); // 43
response = resp.getBackData();
received = resp.getBackLen();
validBits = resp.getValidBits();
if (resp.getStatus() != StatusCode.OK) {
Logger.error("Error in communication at command 0x43, after successfully executing 0x40. Error: {}",
resp.getStatus());
return false;
}
if (received != 1 || response[0] != 0x0A) {
Logger.error("Got bad response on backdoor 0x43 command: 0x{} ({} valid bits)",
Integer.toHexString(response[0]), Integer.valueOf(validBits));
return false;
}
// You can now write to sector 0 without authenticating!
return true;
} // End MIFARE_OpenUidBackdoor()
/**
* Reads entire block 0, including all manufacturer data, and overwrites that
* block with the new UID, a freshly calculated BCC, and the original
* manufacturer data.
*
* Make sure to have selected the card before this function is called.
*
* @param newUid New UID.
* @param uid Current UID
* @param authKey Authentication key
* @return Status
*/
public boolean mifareSetUid(byte[] newUid, UID uid, byte[] authKey) {
// UID + BCC byte can not be larger than 16 together
if (newUid == null || newUid.length == 0 || newUid.length > 15) {
Logger.error("New UID buffer empty, size 0, or size > 15 given");
return false;
}
// Authenticate for reading
StatusCode status = authenticate(true, (byte) 1, authKey, uid);
if (status != StatusCode.OK) {
if (status == StatusCode.TIMEOUT) {
// We get a read timeout if no card is selected yet, so let's select one
// Wake the card up again if sleeping
// byte atqa_answer[2];
// byte atqa_size = 2;
// wakeupA(atqa_answer, &atqa_size);
if (!isNewCardPresent() || readCardSerial() == null) {
Logger.error("No card was previously selected, and none are available. Failed to set UID.");
return false;
}
status = authenticate(true, (byte) 1, authKey, uid);
if (status != StatusCode.OK) {
// We tried, time to give up
Logger.error("Failed to authenticate to card for reading, could not set UID: {}", status);
return false;
}
} else {
Logger.error("PCD_Authenticate() failed: {}", status);
return false;
}
}
// Read block 0
byte[] block0_buffer = mifareRead((byte) 0);
if (block0_buffer == null) {
Logger.error("MIFARE_Read() failed: {}. Are you sure your KEY A for sector 0 is 0x{}?", status,
Hex.encodeHexString(authKey));
return false;
}
block0_buffer = Arrays.copyOf(block0_buffer, 16); //Remove the CRC_A which is also returned by mifareRead
// Write new UID to the data we just read, and calculate BCC byte
byte bcc = 0;
for (int i = 0; i < uid.getSize(); i++) {
block0_buffer[i] = newUid[i];
bcc ^= newUid[i];
}
// Write BCC byte to buffer
block0_buffer[uid.getSize()] = bcc;
// Stop encrypted traffic so we can send raw bytes
stopCrypto1();
// Activate UID backdoor
if (!mifareOpenUidBackdoor()) {
Logger.error("Activating the UID backdoor failed.");
return false;
}
// Write modified block 0 back to card
status = mifareWrite((byte) 0, block0_buffer);
if (status != StatusCode.OK) {
Logger.error("MIFARE_Write() failed: {}", status);
return false;
}
// Wake the card up again
byte[] atqa_answer = new byte[2];
wakeupA(atqa_answer);
return true;
}
/**
* Resets entire sector 0 to zeroes, so the card can be read again by readers.
*
* @return Status
*/
public boolean mifareUnbrickUidSector() {
mifareOpenUidBackdoor();
byte[] block0_buffer = { 0x01, 0x02, 0x03, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00 };
// Write modified block 0 back to card
StatusCode status = mifareWrite((byte) 0, block0_buffer);
if (status != StatusCode.OK) {
Logger.error("MIFARE_Write() failed: {}", status);
return false;
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////////
// Convenience functions - does not add extra functionality
/////////////////////////////////////////////////////////////////////////////////////
/**
* Returns true if a PICC responds to PICC_CMD_REQA. Only "new" cards in state
* IDLE are invited. Sleeping cards in state HALT are ignored.
*
* @return bool
*/
public boolean isNewCardPresent() {
// Reset baud rates
writeRegister(PcdRegister.TX_MODE_REG, (byte) 0x00);
writeRegister(PcdRegister.RX_MODE_REG, (byte) 0x00);
// Reset ModWidthReg
writeRegister(PcdRegister.MOD_WIDTH_REG, (byte) 0x26);
byte[] bufferATQA = new byte[2];
StatusCode result = requestA(bufferATQA);
return (result == StatusCode.OK || result == StatusCode.COLLISION);
} // End PICC_IsNewCardPresent()
/**
* Simple wrapper around PICC_Select. Returns the UID of the card if present,
* otherwise null. Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or
* PICC_WakeupA() first.
*
* @return The UID is a card could be read, otherwise null
*/
public UID readCardSerial() {
return select();
}
// DEBUG METHODS
/**
* Dumps debug info about the connected PCD to Serial. Shows all known firmware
* versions
*/
public void dumpVersionToConsole() {
// Get the MFRC522 firmware version
int v = readRegister(PcdRegister.VERSION_REG) & 0xff;
System.out.print("Firmware Version: 0x");
System.out.print(Integer.toHexString(v & 0xff));
// Lookup which version
switch (v) {
case 0x88:
System.out.println(" = (clone)");
break;
case 0x90:
System.out.println(" = v0.0");
break;
case 0x91:
System.out.println(" = v1.0");
break;
case 0x92:
System.out.println(" = v2.0");
break;
default:
System.out.println(" = (unknown)");
}
// When 0x00 or 0xFF is returned, communication probably failed
if ((v == 0x00) || (v == 0xFF)) {
System.out.println("WARNING: Communication failure, is the MFRC522 properly connected?");
}
} // End PCD_DumpVersionToSerial()
/**
* Dumps debug info about the selected PICC to Serial. On success the PICC is
* halted after dumping the data. For MIFARE Classic the factory default key of
* 0xFFFFFFFFFFFF is tried.
*
* @param uid UID returned from a successful PICC_Select().
* @deprecated Kept for backward compatibility
*/
@Deprecated
public void dumpToConsole(UID uid) {
dumpToConsole(uid, DEFAULT_KEY);
}
@Deprecated
public void dumpToConsole(UID uid, byte[] key) {
// Dump UID, SAK and Type
dumpDetailsToConsole(uid);
// Dump contents
PiccType piccType = uid.getType();
switch (piccType) {
case MIFARE_MINI:
case MIFARE_1K:
case MIFARE_4K:
// All keys are set to FFFFFFFFFFFFh at chip delivery from the factory.
dumpMifareClassicToConsole(uid, key);
break;
case MIFARE_UL:
dumpMifareUltralightToConsole();
break;
case ISO_14443_4:
case MIFARE_DESFIRE:
case ISO_18092:
case MIFARE_PLUS:
case TNP3XXX:
Logger.warn("Dumping memory contents not implemented for that PICC type.");
break;
case UNKNOWN:
case NOT_COMPLETE:
default:
break; // No memory dump here
}
haltA(); // Already done if it was a MIFARE Classic PICC.
} // End PICC_DumpToSerial()
/**
* Dumps card info (UID,SAK,Type) about the selected PICC to Serial.
*
* @param uid UID struct returned from a successful PICC_Select().
* @deprecated kept for backward compatibility
*/
@Deprecated
public static void dumpDetailsToConsole(UID uid) {
// UID
System.out.println("Card UID: 0x" + Hex.encodeHexString(uid.getUidBytes()));
// SAK
System.out.println("Card SAK: 0x" + Integer.toHexString(uid.getSak() & 0xff));
// (suggested) PICC type
System.out.println("PICC type: " + uid.getType().getName());
} // End PICC_DumpDetailsToSerial()
/**
* Dumps memory contents of a MIFARE Classic PICC. On success the PICC is halted
* after dumping the data.
*
* @param uid UID returned from a successful PICC_Select().
* @param key Key A used for all sectors.
*/
public void dumpMifareClassicToConsole(UID uid, byte[] key) {
byte no_of_sectors = 0;
switch (uid.getType()) {
case MIFARE_MINI:
// Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes.
no_of_sectors = 5;
break;
case MIFARE_1K:
// Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes.
no_of_sectors = 16;
break;
case MIFARE_4K:
// Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16
// bytes/block = 4096 bytes.
no_of_sectors = 40;
break;
default: // Should not happen. Ignore.
break;
}
// Dump sectors, highest address first.
if (no_of_sectors != 0) {
System.out.println("Sector Block 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 AccessBits");
for (int i = no_of_sectors - 1; i >= 0; i--) {
dumpMifareClassicSectorToConsole(uid, key, (byte) i);
}
}
haltA(); // Halt the PICC before stopping the encrypted session.
stopCrypto1();
} // End PICC_DumpMifareClassicToSerial()
/**
* Dumps memory contents of a sector of a MIFARE Classic PICC. Uses
* PCD_Authenticate(), MIFARE_Read() and PCD_StopCrypto1. Always uses
* PICC_CMD_MF_AUTH_KEY_A because only Key A can always read the sector trailer
* access bits.
*
* @param uid UID returned from a successful select().
* @param key Key A for the sector.
* @param sector The sector to dump, 0..39.
*/
public void dumpMifareClassicSectorToConsole(UID uid, byte[] key, byte sector) {
int firstBlock; // Address of lowest address to dump actually last block dumped)
byte no_of_blocks; // Number of blocks in sector
boolean isSectorTrailer; // Set to true while handling the "last" (ie highest address) in the sector.
// The access bits are stored in a peculiar fashion.
// There are four groups:
/*-
// g[3] Access bits for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39)
// g[2] Access bits for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39)
// g[1] Access bits for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39)
// g[0] Access bits for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39)
*/
// Each group has access bits [C1 C2 C3]. In this code C1 is MSB and C3 is LSB.
// The four CX bits are stored together in a nible cx and an inverted nible cx_.
int c1, c2, c3; // Nibbles
int c1_, c2_, c3_; // Inverted nibbles
int[] g = new int[4]; // Access bits for each of the four groups.
byte group; // 0-3 - active group for access bits
boolean firstInGroup; // True for the first block dumped in the group
// Determine position and size of sector.
if (sector < 32) { // Sectors 0..31 has 4 blocks each
no_of_blocks = 4;
firstBlock = sector * no_of_blocks;
} else if (sector < 40) { // Sectors 32-39 has 16 blocks each
no_of_blocks = 16;
firstBlock = 128 + (sector - 32) * no_of_blocks;
} else { // Illegal input, no MIFARE Classic PICC has more than 40 sectors.
return;
}
// Dump blocks, highest address first.
byte[] buffer;
byte blockAddr;
isSectorTrailer = true;
// True if one of the inverted nibbles did not match
boolean invertedError = false; // Avoid "unassiged variable" warning.
for (byte blockOffset = (byte) (no_of_blocks - 1); blockOffset >= 0; blockOffset--) {
blockAddr = (byte) (firstBlock + blockOffset);
// Sector number - only on first line
if (isSectorTrailer) {
if (sector < 10) {
System.out.print(" "); // Pad with spaces
} else {
System.out.print(" "); // Pad with spaces
}
System.out.print(sector);
System.out.print(" ");
} else {
System.out.print(" ");
}
// Block number
if (blockAddr < 10) {
System.out.print(" "); // Pad with spaces
} else {
if (blockAddr < 100) {
System.out.print(" "); // Pad with spaces
} else {
System.out.print(" "); // Pad with spaces
}
}
System.out.print(blockAddr);
System.out.print(" ");
// Establish encrypted communications before reading the first block
if (isSectorTrailer) {
StatusCode status = authenticate(true, (byte) firstBlock, key, uid);
if (status != StatusCode.OK) {
System.out.println("PCD_Authenticate() failed: " + status);
return;
}
}
// Read block
buffer = mifareRead(blockAddr);
if (buffer == null) {
System.out.print("MIFARE_Read() failed");
continue;
}
// Dump data
for (byte index = 0; index < 16; index++) {
int val = buffer[index] & 0xff;
if (val < 0x10) {
System.out.print(" 0");
} else {
System.out.print(" ");
}
System.out.print(Integer.toHexString(val & 0xff));
if ((index % 4) == 3) {
System.out.print(" ");
}
}
// Parse sector trailer data
if (isSectorTrailer) {
c1 = (buffer[7] & 0xff) >> 4;
c2 = (buffer[8] & 0xF);
c3 = ((buffer[8] & 0xff) >> 4);
c1_ = (buffer[6] & 0xF);
c2_ = ((buffer[6] & 0xff) >> 4);
c3_ = (buffer[7] & 0xF);
invertedError = (c1 != (~c1_ & 0xF)) || (c2 != (~c2_ & 0xF)) || (c3 != (~c3_ & 0xF));
g[0] = ((c1 & 1) << 2) | ((c2 & 1) << 1) | ((c3 & 1) << 0);
g[1] = ((c1 & 2) << 1) | ((c2 & 2) << 0) | ((c3 & 2) >> 1);
g[2] = ((c1 & 4) << 0) | ((c2 & 4) >> 1) | ((c3 & 4) >> 2);
g[3] = ((c1 & 8) >> 1) | ((c2 & 8) >> 2) | ((c3 & 8) >> 3);
isSectorTrailer = false;
}
// Which access group is this block in?
if (no_of_blocks == 4) {
group = blockOffset;
firstInGroup = true;
} else {
group = (byte) (blockOffset / 5);
firstInGroup = (group == 3) || (group != (blockOffset + 1) / 5);
}
if (firstInGroup) {
// Print access bits
System.out.print(" [ ");
System.out.print((g[group] >> 2) & 1);
System.out.print(" ");
System.out.print((g[group] >> 1) & 1);
System.out.print(" ");
System.out.print((g[group] >> 0) & 1);
System.out.print(" ] ");
if (invertedError) {
System.out.print(" Inverted access bits did not match! ");
}
}
if (group != 3 && (g[group] == 1 || g[group] == 6)) { // Not a sector trailer, a value block
int value = ((buffer[3] & 0xff) << 24) | ((buffer[2] & 0xff) << 16) | ((buffer[1] & 0xff) << 8)
| (buffer[0] & 0xff);
System.out.print(
String.format(" Value=0x%02x Adr=0x%02x", Integer.valueOf(value), Byte.valueOf(buffer[12])));
}
System.out.println();
}
} // End PICC_DumpMifareClassicSectorToSerial()
/**
* Dumps memory contents of a MIFARE Ultralight PICC.
*/
public void dumpMifareUltralightToConsole() {
int i;
System.out.println("Page 0 1 2 3");
// Try the mpages of the original Ultralight. Ultralight C has more pages.
for (byte page = 0; page < 16; page += 4) { // Read returns data for 4 pages at a time.
// Read pages
byte[] buffer = mifareRead(page);
if (buffer == null) {
System.out.println("MIFARE_Read() failed: buffer was null");
break;
}
// Dump data
for (byte offset = 0; offset < 4; offset++) {
i = page + offset;
if (i < 10) {
System.out.print(" "); // Pad with spaces
} else {
System.out.print(" "); // Pad with spaces
}
System.out.print(i);
System.out.print(" ");
for (byte index = 0; index < 4; index++) {
i = 4 * offset + index;
if (buffer[i] < 0x10) {
System.out.print(" 0");
} else {
System.out.print(" ");
}
System.out.print(Integer.toHexString(buffer[i] & 0xff));
}
System.out.println();
}
}
} // End PICC_DumpMifareUltralightToSerial()
public static class Response {
private StatusCode status;
private byte[] backData;
private int backLen;
private byte validBits;
public Response(StatusCode status) {
this.status = status;
}
public Response(StatusCode status, byte[] backData) {
this.status = status;
this.backData = backData;
}
public Response(StatusCode status, byte[] backData, int backLen, byte validBits) {
this.status = status;
this.backData = backData;
this.backLen = backLen;
this.validBits = validBits;
}
public StatusCode getStatus() {
return status;
}
public byte[] getBackData() {
return backData;
}
public int getBackLen() {
return backLen;
}
public byte getValidBits() {
return validBits;
}
@Override
public String toString() {
return "Response [status=" + status + ", backData=" + Arrays.toString(backData) + ", backLen=" + backLen
+ ", validBits=" + validBits + "]";
}
}
public enum StatusCode {
OK(0), // Success
ERROR(1), // Error in communication
COLLISION(2), // Collission detected
TIMEOUT(3), // Timeout in communication.
NO_ROOM(4), // A buffer is not big enough.
INTERNAL_ERROR(5), // Internal error in the code. Should not happen ;-)
INVALID(6), // Invalid argument.
CRC_WRONG(7), // The CRC_A does not match
MIFARE_NACK(0xff); // A MIFARE PICC responded with NAK.
private byte code;
StatusCode(int code) {
this((byte) code);
}
StatusCode(byte code) {
this.code = code;
}
public byte getCode() {
return code;
}
}
private enum PcdRegister {
// Registers
// Reserved00(0x00),
COMMAND_REG(0x01), // starts and stops command execution
COM_INT_EN_REG(0x02), // enable and disable interrupt request control bits
DIV_INT_EN_REG(0x03), // enable and disable interrupt request control bits
COM_IRQ_REG(0x04), // interrupt request bits
DIV_IRQ_REG(0x05), // interrupt request bits
ERROR_REG(0x06), // error bits showing the error status of the last command executed
STATUS1_REG(0x07), // communication status bits
STATUS2_REG(0x08), // receiver and transmitter status bits
FIFO_DATA_REG(0x09), // input and output of 64 byte FIFO buffer
FIFO_LEVEL_REG(0x0A), // number of bytes stored in the FIFO buffer
WATER_LEVEL_REG(0x0B), // level for FIFO underflow and overflow warning
CONTROL_REG(0x0C), // miscellaneous control registers
BIT_FRAMING_REG(0x0D), // adjustments for bit-oriented frames
COLL_REG(0x0E), // bit position of the first bit-collision detected on the RF interface
// RESERVED_01(0x0F),
// RESERVED_10(0x10),
MODE_REG(0x11), // defines general modes for transmitting and receiving
TX_MODE_REG(0x12), // defines transmission data rate and framing
RX_MODE_REG(0x13), // defines reception data rate and framing
TX_CONTROL_REG(0x14), // controls the logical behavior of the antenna driver pins TX1 and TX2
TX_ASK_REG(0x15), // controls the setting of the transmission modulation
TX_SEL_REG(0x16), // selects the internal sources for the antenna driver
RX_SEL_REG(0x17), // selects internal receiver settings
RX_THRESHOLD_REG(0x18), // selects thresholds for the bit decoder
DEMOD_REG(0x19), // defines demodulator settings
// Reserved11(0x1A),
// Reserved12(0x1B),
MIFARE_TX_REG(0x1C), // controls some MIFARE communication transmit parameters
MIFARE_RX_REG(0x1D), // controls some MIFARE communication receive parameters
// Reserved14(0x1E),
SERIAL_SPEED_REG(0x1F), // selects the speed of the serial UART interface
// Reserved20(0x20),
CRC_RESULT_REG_MSB(0x21), // shows the MSB values of the CRC calculation
CRC_RESULT_REG_LSB(0x22), // shows the LSB values of the CRC calculation
// Reserved21(0x23),
MOD_WIDTH_REG(0x24), // controls the ModWidth setting
// Reserved22(0x25),
RF_CONFIG_REG(0x26), // configures the receiver gain
GS_N_REG(0x27), // selects the conductance of the antenna driver pins TX1 and TX2 for modulation
CWGsP_REG(0x28), // defines the conductance of the p-driver output during periods of no
// modulation
ModGsP_REG(0x29), // defines the conductance of the p-driver output during periods of modulation
T_MODE_REG(0x2A), // defines settings for the internal timer
T_PRESCALER_REG(0x2B), //
T_RELOAD_REG_MSB(0x2C), // defines the 16-bit timer reload value
T_RELOAD_REG_LSB(0x2D), T_COUNTER_VALUE_REG_MSB(0x2E), // shows the 16-bit timer value
T_COUNTER_VALUE_REG_LSB(0x2F),
// Reserved30(0x30),
TEST_SEL1_REG(0x31), // general test signal configuration
TEST_SEL2_REG(0x32), // general test signal configuration and PRBS control
TEST_PIN_EN_REG(0x33), // enables pin output driver on pins D1 to D7
TEST_PIN_VALUE_REG(0x34), // defines the values for D1 to D7 when it is used as an I/O bus
TEST_BUS_REG(0x35), // shows the status of the internal test bus
AUTO_TEST_REG(0x36), // controls the digital self test
VERSION_REG(0x37), // shows the software version
ANALOG_TEST_REG(0x38), // controls the pins AUX1 and AUX2
TEST_DAC1_REG(0x39), // defines the test value for TestDAC1
TEST_DAC2_REG(0x3A), // defines the test value for TestDAC2
TEST_ADC_REG(0x3B); // shows the value of ADC I and Q channels
// Reserved31(0x3C),
// Reserved32(0x3D),
// Reserved33(0x3E),
// Reserved34(0x3F);
private byte value;
private byte address;
PcdRegister(int value) {
this.value = (byte) value;
address = (byte) ((value << 1) & 0x7e);
}
public byte getAddress() {
return address;
}
public byte getValue() {
return value;
}
}
private enum PcdCommand {
IDLE(0b0000), // no action, cancels current command execution
MEM(0b0001), // stores 25 bytes into the internal buffer
GENERATE_RANDOM_ID(0b0010), // generates a 10-byte random ID number
CALC_CRC(0b0011), // activates the CRC coprocessor or performs a self test
TRANSMIT(0b0100), // transmits data from the FIFO buffer
NO_CMD_CHANGE(0b0111), // no command change, can be used to modify the
// CommandReg register bits without affecting the command,
// for example, the PowerDown bit
RECEIVE(0b1000), // activates the receiver circuits
TRANSCEIVE(0b1100), // transmits data from FIFO buffer to antenna and automatically
// activates the receiver after transmission
MF_AUTHENT(0b1110), // performs the MIFARE standard authentication as a reader
SOFT_RESET(0b1111); // resets the MFRC522
private byte value;
PcdCommand(byte value) {
this.value = value;
}
PcdCommand(int value) {
this.value = (byte) value;
}
public byte getValue() {
return value;
}
}
// AddicoreRFID Proximity Integrated Circuit Card (PICC) Commands
private enum PiccCommand {
// The commands used by the PCD to manage communication with several PICCs (ISO
// 14443-3, Type A, section 6.4)
REQUEST_A(0x26), // REQuest command, Type A. Invites PICCs in state IDLE to go to READY and
// prepare for anticollision or selection. 7 bit frame.
MF_READ(0x30), // Reads one 16 byte block from the authenticated sector of the PICC. Also used
// for MIFARE Ultralight.
HALT_A(0x50), // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT.
WAKE_UP_A(0x52), // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to
// READY(*) and prepare for anticollision or selection. 7 bit frame.
MF_AUTH_KEY_A(0x60), // Perform authentication with key A
MF_AUTH_KEY_B(0x61), // Perform authentication with key B
CASCADE_TAG(0x88), // Cascade Tag. Not really a command, but used during anti collision.
SEL_CL1(0x93), // Anti collision/Select, Cascade Level 1
SEL_CL2(0x95), // Anti collision/Select, Cascade Level 2
SEL_CL3(0x97), // Anti collision/Select, Cascade Level 3
MF_WRITE(0xA0), // Writes one 16 byte block to the authenticated sector of the PICC. Called
// "COMPATIBILITY WRITE" for MIFARE Ultralight.
UL_WRITE(0xA2), // Writes one 4 byte page to the PICC.
MF_TRANSFER(0xB0), // Writes the contents of the internal data register to a block.
MF_DECREMENT(0xC0), // Decrements the contents of a block and stores the result in the internal data
// register.
MF_INCREMENT(0xC1), // Increments the contents of a block and stores the result in the internal data
// register.
MF_RESTORE(0xC2); // Reads the contents of a block into the internal data register.
private byte value;
PiccCommand(int value) {
this((byte) value);
}
PiccCommand(byte value) {
this.value = value;
}
public byte getValue() {
return value;
}
}
public static class UID {
/** Number of bytes in the UID. 4, 7 or 10. */
private byte[] uidBytes;
/**
* The SAK (Select acknowledge) byte returned from the PICC after successful
* selection.
*/
private byte sak;
public UID(List bytes, byte sak) {
uidBytes = new byte[bytes.size()];
for (int i = 0; i < bytes.size(); i++) {
uidBytes[i] = bytes.get(i).byteValue();
}
this.sak = sak;
}
public int getSize() {
return uidBytes.length;
}
public byte getUidByte(int index) {
return uidBytes[index];
}
public byte[] getUidBytes() {
return uidBytes;
}
public byte getSak() {
return sak;
}
@Override
public String toString() {
return "UID [uidBytes=" + Hex.encodeHexString(uidBytes) + ", sak=" + sak + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(uidBytes);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
UID other = (UID) obj;
if (!Arrays.equals(uidBytes, other.uidBytes))
return false;
return true;
}
public PiccType getType() {
return PiccType.forId(sak);
}
}
public enum AntennaGain {
DB_18A(0b000, 18), DB_23A(0b001, 23), DB_18B(0b010, 18), DB_23B(0b011, 23), DB_33(0b100, 33), DB_38(0b101, 38),
DB_43(0b110, 43), DB_48(0b111, 48);
static final int SHIFT = 4;
static final int BIT_MASK = 0x07 << SHIFT;
private final byte value;
private final int gainDb;
AntennaGain(int value, int gainDb) {
this.value = (byte) (value << SHIFT);
this.gainDb = gainDb;
}
public byte getValue() {
return value;
}
public int getGainDb() {
return gainDb;
}
public static AntennaGain forValue(byte regValue) {
switch (regValue & BIT_MASK) {
case 0b000 << SHIFT:
return DB_18A;
case 0b001 << SHIFT:
return DB_23A;
case 0b010 << SHIFT:
return DB_18B;
case 0b011 << SHIFT:
return DB_23B;
case 0b100 << SHIFT:
return DB_33;
case 0b101 << SHIFT:
return DB_38;
case 0b110 << SHIFT:
return DB_43;
case 0b111 << SHIFT:
return DB_48;
default:
return null;
}
}
}
public enum PiccType {
UNKNOWN("Unknown type"), ISO_14443_4("PICC compliant with ISO/IEC 14443-4"),
ISO_18092("PICC compliant with ISO/IEC 18092 (NFC)"), MIFARE_MINI("MIFARE Mini, 320 bytes"),
MIFARE_1K("MIFARE 1KB"), MIFARE_4K("MIFARE 4KB"), MIFARE_UL("MIFARE Ultralight or Ultralight C"),
MIFARE_PLUS("MIFARE Plus"), MIFARE_DESFIRE("MIFARE DESFire"), TNP3XXX("MIFARE TNP3XXX"),
NOT_COMPLETE("SAK indicates UID is not complete.");
private String name;
PiccType(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static PiccType forId(byte id) {
switch (id) {
case 0x04:
return NOT_COMPLETE; // UID not complete
case 0x09:
return MIFARE_MINI;
case 0x08:
return MIFARE_1K;
case 0x18:
return MIFARE_4K;
case 0x00:
return MIFARE_UL;
case 0x10:
case 0x11:
return MIFARE_PLUS;
case 0x01:
return TNP3XXX;
case 0x20:
return ISO_14443_4;
case 0x40:
return ISO_18092;
default:
return UNKNOWN;
}
}
}
}