MFRC522 RFID module interfacing with Host MCU
Table of Contents
Many of you may be familiar with the RFID module MFRC522, but I’m willing to bet that most of you have only interfaced this module with an Arduino and the Arduino IDE environment. However, if you need to use the RFID module with other MCUs, you may find yourself at a loss for how to proceed. If this is the case, you’ve come to the right place. In this blog, I will show you how to create a device driver for the RFID Module MFRC522, allowing you to interface it with any Host MCU. If you’re not sure what we mean by interfacing with a Host MCU or why it’s necessary, be sure to check out our blog for more information.
Continuing with the MFRC522 RFID reader interfacing to host MCU, Objective would be to interface this module with Host MCU’s like of NXP Semiconductors, STMicroelectronics or other vendors MCU’s. Will make the driver to interface the RFID Reader with any MCU, not unlike just with Arduino and Arduino IDE environment. You just need to change 2-3 low level API’s for running it on different MCU’s, would be telling about it in below sections. In this blog we are going to write the driver in c++ language. Before proceeding further would recommend viewers go through, the following set of blogs and videos to have a better understanding.
RFID Reader MFRC522: Overview and Datasheet Explanation( Highly recommended to go through at first)
MIFRAME RFID Tags: Overview and Datasheet Explanation
Hardware Connection of MFRC522 Reader module
MFRC522 Module has 8 pins exposed out, which can be categorised into 3 parts: Communication pins, Power Supply Pins and Additional Pins. As explained below
4 pins are communication pins, that would be connected to Host MCU either using SPI, I2C, or UART.
We would be making the connection using the SPI peripheral. Here I am referencing out hardware connection with 2 microcontrollers: NXP Semiconductors S32K144 MCU and STMicroelectronics STM32F103 MCU.
STMicroelectronics STM32F103 would be using SPI-1 Instance and S32K144 would be using LPSPI-0 Instance. 
2 pins are for Power Supply Connection, which would be used for powering the RFID reader. One can power the MFRC522 Reader via Host MCU. Connect the VCC and GND pins with the Host MCU Power pins. Make sure, you supply MFRC522 with 3.3 V, don’t power it with 5V.
There are 2 additional pins on MFRC522: IRQ and RST pins.
IRQ pin is an interrupt pin, that is used for alerting the HOST MCU when an RFID tag is in the vicinity. Read about the IRQ pin and interrupts in MFRC522 from here.
RST pin would be not used for this project.
These connections are for debugging and understanding purposes. By connecting the logic analyzers, we would be able to see how literally SPI communication and what commands we are sending, and what responses we are getting in bit and byte levels. Would recommend doing this step, as it makes the understanding clear at the root level. It would hardly take a couple of minutes to setup this.
For connecting the logic analyzer, connect the Channel1,3,5,7 of the logic analyzer with communication pins.
I am using the Salae logic analyzer, which is readily available. viewers can refer to this video on Gettobyte Youtube channel on how to set up logic analyzer connections, hardware, and software.
MFRC522 Functional Description API's
MFRC522 has a set of functional descriptions, on which the whole of the working of MFRC522 depends. To write the driver of MFRC522, it’s important to have an understanding of those functional blocks. Reading from the datasheet could be tiering, hence viewers can read and understand from here.
MFRC522 Host Interface API’s
We are going to connect MFRC522 via the SPI interface to the host MCU.
SPI configurations:
- MSB is sent first
- 8 Bits per transfer
- The clock is Low when inactive(CPOL=0)
- Data is valid on Clock Leading Edge(CPHA=0)
- Enable line is Active LOW
SPI Address Byte
These address bytes are of 6 bits. When sending the address byte, MSB should tell whether we have to perform a read/write operation on that address. LSB is always set to logic 0 when sending the address byte.
Thus you would find in the below driver that MFRC522 registers which are defined in GB_MFRC522.h have been left shifted 1 bit so that the MSB bit can be configured whether to perform a Read or write operation on that register address.
SPI Read Data
To read the data, Host MCU will send the register address at the MOSI line with LSB as 1 and then in MISO, it would get the data.
This would be performed via a low-level function, that is reading one single byte from the address GB_reg which is sent in its argument.
uint8_t GB_MFRC522_ReadRegister(PCD_Register GB_reg);
For e,g we have to read the version of the MFRC522, which can be done via VersionReg(ox37): uint8_t v = GB_MFRC522_ReadRegister(VersionReg);
- So it would be left shifted first: 0x37<<1 = 0x6E(So that can configure MSB for read and Write operation).
- And then as we want to perform a read operation at this register, so need to write MSB with 1. We will Or above value with 0x80( See definition of uint8_t GB_MFRC522_ReadRegister(PCD_Register GB_reg): 0x6E | 0x80 = 0xEE.
As per the datasheet, reading this register would give either 0x92 or 0x91.(Refer the datasheet for in-depth-description of this register). In logic analyzer reading from the MFRC522 would look like this:
SPI Write Data
To write the data, Host MCU will send the register address at the MOSI line with LSB as 0 and then will write bytes that have to be written at that address register.
This would be performed via a low-level function, that is writing one single byte value at the address GB_reg .
void GB_MFRC522_WriteRegister(PCD_Register GB_reg, uint8_t value)
For e.g, we have to write data on the CommandReg register(0x02), which is used to specify which command MFRC522 has to send to PICC(refer to MFRC522 Command set of previous blog): GB_MFRC522_WriteRegister(CommandReg, command);
- So it would be left shifted first: 0x02<<1 = 0x12(So that can configure MSB for read and Write operation).
- And then as we want to perform a write operation at this register, so need to write MSB with 0. That would be done by default. We are sending a Transcive command(0b1100/0xC)
In the logic analyzer writing on MFRC522 would look like this
MFRC522 FIFO API’s
MFRC522 has an Internal FIFO of 64 bytes. Broadly FIFO of MFRC522 can be controlled via 3 registers: FIFODataReg(0x09), FIFOLevelReg(0x0A), and WaterLevelReg(0x0B). These registers would be read and written directly using the Low-Level Host Interface API.
- FIFODataReg(0x09): Accessing this register, we can write or read data on the FIFO buffer.
- GB_MFRC522_WriteRegister(FIFODataReg, sendLen, sendData);
0x09 is left shifted by 1 byte, which becomes 0x12. Thus you can see below pic 0x12 is transmitted from the MOSI line and then in FIFO we are writing just 1 byte which is the REQA command(0x26)
- GB_MFRC522_ReadRegister(FIFODataReg, n, backdata, rxalign);
0x09 is left shifted by 1 byte, which becomes 0x012. And then further we OR it with Ox80 to read the FIFO buffer, which becomes 0x12 | 0x80 = 0x92. Thus you can see below pic HOST MCU is transmitting 0x92 and in return, it reads the FIFO buffer, which is basically ATQA( response of REQA which is transmitted above)
2. FIFOLevelReg(0x0A): Accessing this register, we can read how many bytes are there on the FIFO buffer.(0x0A<<1 ) | 0x80 = 0x94.
Reading the number of bytes received in FIFO after the REQA command is sent. Would be getting 2 bytes in FIFO. Hence on transmitting 0x94 on MOSI, we would be getting 0x02 at MISO.
MFRC522 CRC API’s
- gb_MFRC522_statusCodes PCD_CalculateCRC( byte *data, byte length, byte *result): Is the API that would be used for the calculation of CRC.( API defination see below driver source file)
- For using the CRC we have to configure its preset value. That can be done via the ModeReg register. In the ModeReg register, bits CRCPreset[1:0] are used for configuring the Preset value.
GB_MFRC522_WriteRegister(ModeReg, 0x3D); // Default 0x3F. Set the preset value for the CRC co processor for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4)
We would be setting the preset to 6363h. Thus would be setting the CRCPreset bits to 01.
gb_MFRC522_statusCodes PCD_CalculateCRC( byte *data, byte length, byte *result): Is the API that would be used for calculation of CRC
MFRC522 Interrupts API’s
MFRC522 can trigger the interrupts when a certain event occurs. There are 4 registers for configuring the interrupts of MFRC522: ComIrqReg(0x04) & DivIrqReg(0x03) for indicating which interrupt has been triggered. ComIEnReg (0x02) & DivIEnReg(0x03) for configuring the behavior of the IRQ pin. These registers would be read and written directly using the Low-Level Host Interface API.
MFRC522 Time Unit API’s
MFRC522 has a Timer Unit, that is used for configuring the internal clock and analog interface. The timer unit has the following registers: TModeReg(0x2A), TPrescalerReg(0x2B),TReloadReg(0x2C and 0x2D) and TCounterVAlReg(0x2E & 0x2F). These registers would be directly written and read by Low-Level Host Interface API.
- GB_MFRC522_WriteRegister(TModeReg, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds
- GB_MFRC522_WriteRegister(TPrescalerReg, 0xA9); // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs.
- GB_MFRC522_WriteRegister(TReloadRegH, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout.
- GB_MFRC522_WriteRegister(TReloadRegL, 0xE8);
MFRC522 Command Set API’s
MFRC522 operation is determined by certain commands. According to these commands, correspondingly MFRC522 would be performing some action. Host MCU will write the command code to the CommandReg Register(0x01). This Register would be read and written directly using the Low-Level Host Interface API.
Following is the table of the commands, that can be sent.
So say, we have to send Idle Command to MFRC522.
- GB_MFRC522_WriteRegister(CommandReg, PCD_Idle);
- GB_MFRC522_WriteRegister(CommandReg, PCD_Transceive);
- GB_MFRC522_WriteRegister(CommandReg, PCD_CalcCRC);
MFRC522 State Machines
State Machines for doing various operations with MFRC522.
In this would talk about different state machines for the working of MFRC522.
- MFRC522 init state machine
- MFRC522 scanning PICC state machine
- MFRC522 Get UID PICC state machine.
- MFRC522 CRC
MFRC522 PICC Scanning State Machine
For scanning PCD would emit the REQA Command, it will emit the REQA command via short frame format(specified in ISO14443-3 doc). So Host MCU will configure the PCD FIFO to send the REQA command and then FIFO buffer would be transmitted. If PICC is in energizing field, it will start listening for valid REQ command and transmits its ATQA (Answer to Request) as a response into the PCD FIFO. This response would be used to check whether some PICC is scanned or not. When PICC is scanned it will be in ready state, if PICC is not scanned by PCD then it would be in idle state. 
MFRC522 Get UID PICC State Machine
PICC that are compatible with MFRC522 are MF1S503x, these are easily avaible in online stores and normally comes along with the MFRC522 modules. Below is the state machine to get the UID of PICC. For its programming implementation refer the GB_PICC_ReadCardSerial API() and GB_PICC_Select API(), which are specified below. Also so as to understand in bit and byte level communication for UID refer to PICC Command Set section of previous blog.
MFRC522 Init State Machine
MFRC522 CRC State Machine
Driver creation of MFRC522 Reader
GB_MFRC522.h
#ifndef GB_MFRC522_H_
#define GB_MFRC522_H_
typedef uint8_t byte;
void yield(void)
{
while(1);
}
/*
MFRC522 can communicate via SPI protocol at data speed upto 10Mbit/s.
--> Data bytes on both MOSI and MISO lines are sent with the MSB first. Data on both MOSI
and MISO lines must be stable on the rising edge of the clock and can be changed on the
falling edge. Data is provided by the MFRC522 on the falling clock edge and is stable
during the rising clock edge.
--> The MSB of the first byte defines the mode used. To read data from the MFRC522 the
MSB is set to logic 1. To write data to the MFRC522 the MSB must be set to logic 0. Bits 6
to 1 define the address and the LSB is set to logic 0.
-->
*/
#define gb_MFRC522_CE PB0
#define gb_MFRC522_CE_pin_low gb_spi_port &= ~(1< The last 2 bytes of the response is assumed to be a CRC_A that must be validated
);
// A struct used for passing the UID of a PICC.
typedef struct {
byte size; // Number of bytes in the UID. 4, 7 or 10.
byte uidByte[10];
byte sak; // The SAK (Select acknowledge) byte returned from the PICC after successful selection.
} Uid;
// Member variables
Uid uid; // Used by PICC_ReadCardSerial().
gb_MFRC522_statusCodes GB_PICC_REQA_OR_WUPA(byte command, byte *gb_bufferATQA, byte *gb_buffersize);
gb_MFRC522_statusCodes GB_PICC_RequestA(byte * gb_bufferATQ, byte *gb_buffersize);
bool GB_PICC_IsNewCardPresent();
bool GB_PICC_ReadCardSerial();
void GB_PICCDetails(Uid *uid);
#include "GB_MFRC522.cpp"
#endif
GB_MFRC522.c
void GB_MFRC522_WriteRegister(PCD_Register GB_reg, uint8_t value)
{
gb_MFRC522_CE_pin_low;
GB_MA_SPI0_send_byte_conti(GB_reg);
GB_MA_SPI0_send_byte_conti(value);
gb_MFRC522_CE_pin_high;
}
void GB_MFRC522_WriteRegister(PCD_Register GB_reg, uint8_t count, uint8_t *value)
{
gb_MFRC522_CE_pin_low;
GB_MA_SPI0_send_byte_conti(GB_reg);
for (uint8_t i =0;i f_timer=40kHz, ie a timer period of 25μs.
GB_MFRC522_WriteRegister(TReloadRegH, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout.
GB_MFRC522_WriteRegister(TReloadRegL, 0xE8);
GB_MFRC522_WriteRegister(TxASKReg, 0x40);// Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting
GB_MFRC522_WriteRegister(ModeReg, 0x3D); // Default 0x3F. Set the preset value for the CRC co processor for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4) // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset)
GB_MFRC522_AnteenaOn(); //Enable the Antenna Driver pins TX1 & TX2(They are disabled by reset)
}
void GB_MFRC522_AnteenaOn()
{
uint8_t gb_x = GB_MFRC522_ReadRegister(TxControlReg);
if((gb_x & 0x03) != 0x03)
{
GB_MFRC522_WriteRegister(TxControlReg, (gb_x | 0x03));
}
}
gb_MFRC522_statusCodes PCD_CalculateCRC( byte *data, byte length, byte *result) ///< In: Pointer to the data to transfer to the FIFO for CRC calculation.\
)
{
GB_MFRC522_WriteRegister(CommandReg, PCD_Idle); // Stop any active command.
GB_MFRC522_WriteRegister(DivIrqReg, 0x04); // Clear the CRCIRq interrupt request bit
GB_MFRC522_WriteRegister(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization
GB_MFRC522_WriteRegister(FIFODataReg, length, data); // Write data to the FIFO
GB_MFRC522_WriteRegister(CommandReg, PCD_CalcCRC); // Start the calculation
// Wait for the CRC calculation to complete. Check for the register to
// indicate that the CRC calculation is complete in a loop. If the
// calculation is not indicated as complete in ~90ms, then time out
// the operation.
const uint32_t deadline = GB_millis() + 89;
do {
// DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved
byte n = GB_MFRC522_ReadRegister(DivIrqReg);
if (n & 0x04) { // CRCIRq bit set - calculation done
GB_MFRC522_WriteRegister(CommandReg, PCD_Idle); // Stop calculating CRC for new content in the FIFO.
// Transfer the result from the registers to the result buffer
result[0] = GB_MFRC522_ReadRegister(CRCResultRegL);
result[1] = GB_MFRC522_ReadRegister(CRCResultRegH);
return STATUS_OK;
}
yield();
}
while (static_cast (GB_millis()) < deadline);
// 89ms passed and nothing happened. Communication with the MFRC522 might be down.
return STATUS_TIMEOUT;
} // End PCD_CalculateCRC()
gb_MFRC522_statusCodes GB_MFRC522_CommunicateWithPICC(byte command, //The command to execute
byte waitIRQ,
byte *sendData,
byte sendLen,
byte *backdata,
byte *backlen,
byte *validbits,
byte rxalign,
bool checkCRC
)
{
byte txLastBits = validbits ? *validbits :0;
byte bitFraming = (rxalign <<4) + txLastBits; //RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0]
GB_MFRC522_WriteRegister(CommandReg, PCD_Idle); // Stop any active command.
GB_MFRC522_WriteRegister(ComIrqReg, 0x7F); // Clear all seven interrupt request bits
// For PCD to PICC Communication!!
GB_MFRC522_WriteRegister(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization
GB_MFRC522_WriteRegister(FIFODataReg, sendLen, sendData); // Write sendData to the FIFO
GB_MFRC522_WriteRegister(BitFramingReg, bitFraming); // Bit adjustments
GB_MFRC522_WriteRegister(CommandReg, command); // Execute the command
if(command = PCD_Transceive){
GB_MFRC522_SetRegisterBitmask(BitFramingReg, 0x80); //StartSend=1, transmission of data starts
}
const uint32_t deadline = GB_millis()+36;
bool completed = false;
do {
byte n = GB_MFRC522_ReadRegister(ComIrqReg); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq
if (n & waitIRQ) { // One of the interrupts that signal success has been set.
// GB_printString0("signal success\n");
completed = true;
break;
}
if (n & 0x01) { // Timer interrupt - nothing received in 25ms
// GB_printString0("Timeout\n");
return STATUS_TIMEOUT;
}
//yield();
}
while (static_cast (GB_millis()) < deadline);
// _delay_ms(30);
//36ms and nothing happened. Communication with the MFRC522 might be down.
if (!completed) {
return STATUS_TIMEOUT;
}
//Stop now if any errors except collisions were detected.
byte errorRegValue = GB_MFRC522_ReadRegister(ErrorReg); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr
if (errorRegValue & 0x13) { // BufferOvfl ParityErr ProtocolErr
GB_printString0("Some error \n");
return STATUS_ERROR;
}
byte _validbits = 0;
//If the caller wants data back, get it from the MFRC522.
if(backdata && backlen) {
byte n = GB_MFRC522_ReadRegister(FIFOLevelReg);
// GB_printString0("FIFO len");
//GB_decimel0(n);
//GB_printString0("\n");
if(n > *backlen){
return STATUS_NO_ROOM;
}
*backlen = n;
GB_MFRC522_ReadRegister(FIFODataReg, n, backdata, rxalign);
// GB_printString0("FIFO len");
// GB_decimel0(*backdata);
// GB_printString0("\n");
_validbits = GB_MFRC522_ReadRegister(ControlReg) & 0x07;
if(validbits){
*validbits = _validbits;
}
}
// Tell about collisions
if (errorRegValue & 0x08) { // CollErr
return STATUS_COLLISION;
}
// Perform CRC_A validation if requested.
if (backdata && backlen && checkCRC) {
// In this case a MIFARE Classic NAK is not OK.
if (*backlen == 1 && _validbits == 4) {
return STATUS_MIFARE_NACK;
}
// We need at least the CRC_A value and all 8 bits of the last byte must be received.
if (*backlen < 2 || _validbits != 0) {
return STATUS_CRC_WRONG;
}
// Verify CRC_A - do our own calculation and store the control in controlBuffer.
byte controlBuffer[2];
gb_MFRC522_statusCodes status = PCD_CalculateCRC(&backdata[0], *backlen - 2, &controlBuffer[0]);
if (status != STATUS_OK) {
return status;
}
if ((backdata[*backlen - 2] != controlBuffer[0]) || (backdata[*backlen - 1] != controlBuffer[1])) {
return STATUS_CRC_WRONG;
}
}
return STATUS_OK;
}//return STATUS_OK;
gb_MFRC522_statusCodes GB_MFRC522_TransceiveData( byte *senddata, //Pointer to the data to transfer to the FIFO
byte sendlen, //Number of bytes to transfer to FIFO
byte *backdata, //nullptr or pointer to buffer if data should be read back after executing the command
byte *backlen, //In: Max no of bytes to write to *backdata. Out: The no of bytes returned.
byte *validbits, //In/Out: The number of valid bits in the last byte
byte rxalign,
bool checkCRC //In: True => The last 2 bytes of the response is assumed to be a CRC_A that must be validated
)
{
byte waitIRQ = 0x30;
// GB_printString0("uy\n");
// GB_decimel0(rxalign);
// GB_printString0("\n");
// GB_decimel0(checkCRC);
// GB_printString0("\n");
_delay_ms(100);
return GB_MFRC522_CommunicateWithPICC(PCD_Transceive, waitIRQ, senddata, sendlen, backdata, backlen, validbits, rxalign, checkCRC);
}
gb_MFRC522_statusCodes GB_PICC_REQA_OR_WUPA(byte command, byte *gb_bufferATQA, byte *gb_buffersize)
{
gb_MFRC522_statusCodes status;
byte gb_validbits;
if(gb_bufferATQA == 0 || *gb_buffersize < 2) { // As gb_bufferATQA is a buffer to store the Answer to request, when command REQ is send by PCD.
return STATUS_NO_ROOM; //So this buffer should be pointing to some valid pointer and not to null pointer. And buffer
} //size should be greater then 2 bytes, ATQA is of 2 bytes.
GB_MFRC522_ClearRegisterBitMask(CollReg,0x80);
gb_validbits = 7;
status = GB_MFRC522_TransceiveData(&command, 1, gb_bufferATQA, gb_buffersize, &gb_validbits, 0, 0);
// GB_printString0("status -->");
// GB_decimel0(status);
// GB_printString0("\n");
if( status != STATUS_OK);
return status;
if(*gb_buffersize !=2 || gb_validbits != 0){
return STATUS_ERROR;
}
return STATUS_OK;
}
gb_MFRC522_statusCodes GB_PICC_RequestA(byte * bufferATQ, byte *buffersize)
{
return GB_PICC_REQA_OR_WUPA(PICC_CMD_REQA, bufferATQ, buffersize);
}
bool GB_PICC_IsNewCardPresent()
{
byte gb_bufferATQ[2]; // We will be sending the Request command. That is in order to detect the PICCs which are in the operating field
// PCD shall send repeated request commands. PCD will send REQ or WUP in any sequence to detect the PICCs.
//REQ commands are transmitted via short frame If PICC is in energizing field for PCD and gets powered up,
//it will start listening for valid REQ command. And transmits its ATQ(Answer to request).
//This answer to Request is stored in this buffer. ATQA is a 2 byte number that is returned by PICC.
byte gb_buffersize = sizeof(gb_bufferATQ);
// Reset baud rates
GB_MFRC522_WriteRegister(TxModeReg, 0x00);
GB_MFRC522_WriteRegister(RxModeReg, 0x00);
// Reset ModWidthReg
GB_MFRC522_WriteRegister(ModWidthReg, 0x26);//38 in decimal
gb_MFRC522_statusCodes result = GB_PICC_RequestA(gb_bufferATQ, &gb_buffersize);
// GB_decimel0(result);
// GB_printString0("\n");
return (result == STATUS_OK || result == STATUS_COLLISION);
}
/**
* 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?
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
gb_MFRC522_statusCodes GB_PICC_Select( Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID.
byte validBits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply uid->size.
) {
bool uidComplete;
bool selectDone;
bool useCascadeTag;
byte cascadeLevel = 1;
gb_MFRC522_statusCodes result;
byte count;
byte checkBit;
byte index;
byte uidIndex; // The first index in uid->uidByte[] that is used in the current Cascade Level.
int8_t currentLevelKnownBits; // The number of known UID bits in the current Cascade Level.
byte buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A
byte bufferUsed; // The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO.
byte rxAlign; // Used in BitFramingReg. Defines the bit position for the first bit received.
byte txLastBits; // Used in BitFramingReg. The number of valid bits in the last transmitted byte.
byte *responseBuffer;
byte responseLength;
// 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) {
return STATUS_INVALID;
}
// Prepare MFRC522
GB_MFRC522_ClearRegisterBitMask(CollReg, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared.
// Repeat Cascade Level loop until we have a complete UID.
uidComplete = false;
while (!uidComplete) {
// Set the Cascade Level in the SEL byte, find out if we need to use the Cascade Tag in byte 2.
switch (cascadeLevel) {
case 1:
buffer[0] = PICC_CMD_SEL_CL1;
uidIndex = 0;
useCascadeTag = validBits && uid->size > 4; // When we know that the UID has more than 4 bytes
break;
case 2:
buffer[0] = PICC_CMD_SEL_CL2;
uidIndex = 3;
useCascadeTag = validBits && uid->size > 7; // When we know that the UID has more than 7 bytes
break;
case 3:
buffer[0] = PICC_CMD_SEL_CL3;
uidIndex = 6;
useCascadeTag = false; // Never used in CL3.
break;
default:
return STATUS_INTERNAL_ERROR;
break;
}
// GB_decimel0(validBits);
// GB_printString0("\n");
// GB_decimel0(uidIndex);
// GB_printString0("\n");
// GB_decimel0(useCascadeTag);
// GB_printString0("\n");
// How many UID bits are known in this Cascade Level?
currentLevelKnownBits = validBits - (8 * uidIndex);
if (currentLevelKnownBits < 0) {
currentLevelKnownBits = 0;
}
// Copy the known bits from uid->uidByte[] to buffer[]
index = 2; // destination index in buffer[]
if (useCascadeTag) {
buffer[index++] = PICC_CMD_CT; // buffer[3]
}
byte bytesToCopy = currentLevelKnownBits / 8 + (currentLevelKnownBits % 8 ? 1 : 0); // The number of bytes needed to represent the known bits for this level.
// GB_decimel0(bytesToCopy);
// GB_printString0("\n");
if (bytesToCopy) {
byte maxBytes = useCascadeTag ? 3 : 4; // Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag
if (bytesToCopy > maxBytes) {
bytesToCopy = maxBytes;
}
for (count = 0; count < bytesToCopy; count++) {
buffer[index++] = uid->uidByte[uidIndex + count];
}
}
// Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits
if (useCascadeTag) {
currentLevelKnownBits += 8;
}
// Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations.
selectDone = false;
while (!selectDone) {
// Find out how many bits and bytes to send and receive.
if (currentLevelKnownBits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT.
//Serial.print(F("SELECT: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC);
// GB_decimel0(buffer[1]);
// GB_printString0("o\n");
buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole bytes
// GB_decimel0(buffer[1]);
// GB_printString0("\n");
// Calculate BCC - Block Check Character
buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5];
// GB_decimel0(buffer[6]);
// GB_printString0("o\n");
// Calculate CRC_A
result = PCD_CalculateCRC(buffer, 7, &buffer[7]);
// GB_decimel0(result);
// GB_printString0("\n");
if (result != STATUS_OK) {
return result;
}
// GB_printString0("z\n");
txLastBits = 0; // 0 => All 8 bits are valid.
bufferUsed = 9;
// Store response in the last 3 bytes of buffer (BCC and CRC_A - not needed after tx)
responseBuffer = &buffer[6];
responseLength = 3;
}
else { // This is an ANTICOLLISION/SELECT
//Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC);
txLastBits = currentLevelKnownBits % 8;
count = currentLevelKnownBits / 8; // Number of whole bytes in the UID part.
index = 2 + count; // Number of whole bytes: SEL + NVB + UIDs
buffer[1] = (index << 4) + txLastBits; // NVB - Number of Valid Bits
// GB_decimel0(buffer[1]);
// GB_printString0("\n");
bufferUsed = index + (txLastBits ? 1 : 0);
// Store response in the unused part of buffer
responseBuffer = &buffer[index];
responseLength = sizeof(buffer) - index;
}
// Set bit adjustments
rxAlign = txLastBits; // Having a separate variable is overkill. But it makes the next line easier to read.
GB_MFRC522_WriteRegister(BitFramingReg, (rxAlign << 4) + txLastBits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0]
// for (int i =0; i collision.
byte valueOfCollReg = GB_MFRC522_ReadRegister(CollReg); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0]
if (valueOfCollReg & 0x20) { // CollPosNotValid
return STATUS_COLLISION; // Without a valid collision position we cannot continue
}
byte collisionPos = valueOfCollReg & 0x1F; // Values 0-31, 0 means bit 32.
if (collisionPos == 0) {
collisionPos = 32;
}
if (collisionPos <= currentLevelKnownBits) { // No progress - should not happen
return STATUS_INTERNAL_ERROR;
}
// Choose the PICC with the bit set.
currentLevelKnownBits = collisionPos;
count = currentLevelKnownBits % 8; // The bit to modify
checkBit = (currentLevelKnownBits - 1) % 8;
index = 1 + (currentLevelKnownBits / 8) + (count ? 1 : 0); // First byte is index 0.
buffer[index] |= (1 << checkBit);
}
else if (result != STATUS_OK) {
return result;
}
else { // STATUS_OK
if (currentLevelKnownBits >= 32) { // This was a SELECT.
selectDone = true; // No more anticollision
// GB_decimel0(currentLevelKnownBits);
// GB_printString0("2)");
// We continue below outside the while.
}
else { // This was an ANTICOLLISION.
// We now have all 32 bits of the UID in this Cascade Level
// GB_printString0("\n");
// GB_decimel0(currentLevelKnownBits);
// GB_printString0("\n");
// GB_printString0("1)");
currentLevelKnownBits = 32;
// Run loop again to do the SELECT.
}
}
} // End of while (!selectDone)
// We do not check the CBB - it was constructed by us above.
// GB_printString0("3");
// Copy the found UID bytes from buffer[] to uid->uidByte[]
index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[]
bytesToCopy = (buffer[2] == PICC_CMD_CT) ? 3 : 4;
for (count = 0; count < bytesToCopy; count++) {
uid->uidByte[uidIndex + count] = buffer[index++];
}
// Check response SAK (Select Acknowledge)
if (responseLength != 3 || txLastBits != 0) { // SAK must be exactly 24 bits (1 byte + CRC_A).
return STATUS_ERROR;
}
// Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed anymore.
result = PCD_CalculateCRC(responseBuffer, 1, &buffer[2]);
if (result != STATUS_OK) {
return result;
}
if ((buffer[2] != responseBuffer[1]) || (buffer[3] != responseBuffer[2])) {
return STATUS_CRC_WRONG;
}
if (responseBuffer[0] & 0x04) { // Cascade bit set - UID not complete yes
cascadeLevel++;
}
else {
uidComplete = true;
uid->sak = responseBuffer[0];
}
} // End of while (!uidComplete)
// Set correct uid->size
uid->size = 3 * cascadeLevel + 1;
return STATUS_OK;
} // End PICC_Select()
bool GB_PICC_ReadCardSerial()
{
gb_MFRC522_statusCodes result = GB_PICC_Select(&uid, 0);
return (result == STATUS_OK);
}
void GB_PICCDetails(Uid *uid)
{
// UID
GB_printString0("Card UID:\n");
for (byte i = 0; i < uid->size; i++) {
if(uid->uidByte[i] < 0x10)
GB_printString0("0");
else
GB_printString0(" ");
GB_decimel0(uid->uidByte[i]);
}
GB_printString0("\n");
// SAK
GB_printString0("Card SAK: \n");
if(uid->sak < 0x10)
GB_printString0("0");
GB_decimel0(uid->sak);
// (suggested) PICC type
// PICC_Type piccType = PICC_GetType(uid->sak);
// Serial.print(F("PICC type: "));
// Serial.println(PICC_GetTypeName(piccType));
} // End PICC_DumpDetailsToSerial()
Proposed Demo
Demo Explained
Project Download
NXP-S32K144-MCU/S32K114_RFID_Module at main · gkunalupta/NXP-S32K144-MCU (github.com)
AVR_BareMetal_Firmwares/SPI/Examples/Atmega256_RFID_BareMetal at master · gkunalupta/AVR_BareMetal_Firmwares (github.com)
Prev
PREVIOUS
ISO14443-A: RFID Standard Protocol
NEXT
GPIO Peripheral in S32K144 MCU
Next
Leave a Reply
Logged in as Rohit Chauhan. Edit your profile. Log out? Required fields are marked *
Comment *
Proposed Demo
STM32F103
NXP S32K144
AVR MCU
Project Download
Author