Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of RFID-RC522 by
MFRC522.cpp
00001 /* 00002 * MFRC522.cpp - Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS SPI W AND R BY COOQROBOT. 00003 * _Please_ see the comments in MFRC522.h - they give useful hints and background. 00004 * Released into the public domain. 00005 */ 00006 00007 #include "MFRC522.h" 00008 00009 static const char* const _TypeNamePICC[] = 00010 { 00011 "Unknown type", 00012 "PICC compliant with ISO/IEC 14443-4", 00013 "PICC compliant with ISO/IEC 18092 (NFC)", 00014 "MIFARE Mini, 320 bytes", 00015 "MIFARE 1KB", 00016 "MIFARE 4KB", 00017 "MIFARE Ultralight or Ultralight C", 00018 "MIFARE Plus", 00019 "MIFARE TNP3XXX", 00020 00021 /* not complete UID */ 00022 "SAK indicates UID is not complete" 00023 }; 00024 00025 static const char* const _ErrorMessage[] = 00026 { 00027 "Unknown error", 00028 "Success", 00029 "Error in communication", 00030 "Collision detected", 00031 "Timeout in communication", 00032 "A buffer is not big enough", 00033 "Internal error in the code, should not happen", 00034 "Invalid argument", 00035 "The CRC_A does not match", 00036 "A MIFARE PICC responded with NAK" 00037 }; 00038 00039 #define MFRC522_MaxPICCs (sizeof(_TypeNamePICC)/sizeof(_TypeNamePICC[0])) 00040 #define MFRC522_MaxError (sizeof(_ErrorMessage)/sizeof(_ErrorMessage[0])) 00041 00042 ///////////////////////////////////////////////////////////////////////////////////// 00043 // Functions for setting up the driver 00044 ///////////////////////////////////////////////////////////////////////////////////// 00045 00046 /** 00047 * Constructor. 00048 * Prepares the output pins. 00049 */ 00050 MFRC522::MFRC522(PinName mosi, 00051 PinName miso, 00052 PinName sclk, 00053 PinName cs, 00054 PinName reset) : m_SPI(mosi, miso, sclk), m_CS(cs), m_RESET(reset) 00055 { 00056 /* Configure SPI bus */ 00057 m_SPI.format(8, 0); 00058 m_SPI.frequency(8000000); 00059 00060 00061 /* Release SPI-CS pin */ 00062 m_CS = 1; 00063 00064 /* Release RESET pin */ 00065 m_RESET = 1; 00066 } // End constructor 00067 00068 00069 /** 00070 * Destructor. 00071 */ 00072 MFRC522::~MFRC522() 00073 { 00074 00075 } 00076 00077 00078 ///////////////////////////////////////////////////////////////////////////////////// 00079 // Basic interface functions for communicating with the MFRC522 00080 ///////////////////////////////////////////////////////////////////////////////////// 00081 00082 /** 00083 * Writes a byte to the specified register in the MFRC522 chip. 00084 * The interface is described in the datasheet section 8.1.2. 00085 */ 00086 void MFRC522::PCD_WriteRegister(uint8_t reg, uint8_t value) 00087 { 00088 m_CS = 0; /* Select SPI Chip MFRC522 */ 00089 00090 // MSB == 0 is for writing. LSB is not used in address. Datasheet section 8.1.2.3. 00091 (void) m_SPI.write(reg & 0x7E); 00092 (void) m_SPI.write(value); 00093 00094 m_CS = 1; /* Release SPI Chip MFRC522 */ 00095 } // End PCD_WriteRegister() 00096 00097 /** 00098 * Writes a number of bytes to the specified register in the MFRC522 chip. 00099 * The interface is described in the datasheet section 8.1.2. 00100 */ 00101 void MFRC522::PCD_WriteRegister(uint8_t reg, uint8_t count, uint8_t *values) 00102 { 00103 m_CS = 0; /* Select SPI Chip MFRC522 */ 00104 00105 // MSB == 0 is for writing. LSB is not used in address. Datasheet section 8.1.2.3. 00106 (void) m_SPI.write(reg & 0x7E); 00107 for (uint8_t index = 0; index < count; index++) 00108 { 00109 (void) m_SPI.write(values[index]); 00110 } 00111 00112 m_CS = 1; /* Release SPI Chip MFRC522 */ 00113 } // End PCD_WriteRegister() 00114 00115 /** 00116 * Reads a byte from the specified register in the MFRC522 chip. 00117 * The interface is described in the datasheet section 8.1.2. 00118 */ 00119 uint8_t MFRC522::PCD_ReadRegister(uint8_t reg) 00120 { 00121 uint8_t value; 00122 m_CS = 0; /* Select SPI Chip MFRC522 */ 00123 00124 // MSB == 1 is for reading. LSB is not used in address. Datasheet section 8.1.2.3. 00125 (void) m_SPI.write(0x80 | reg); 00126 00127 // Read the value back. Send 0 to stop reading. 00128 value = m_SPI.write(0); 00129 00130 m_CS = 1; /* Release SPI Chip MFRC522 */ 00131 00132 return value; 00133 } // End PCD_ReadRegister() 00134 00135 /** 00136 * Reads a number of bytes from the specified register in the MFRC522 chip. 00137 * The interface is described in the datasheet section 8.1.2. 00138 */ 00139 void MFRC522::PCD_ReadRegister(uint8_t reg, uint8_t count, uint8_t *values, uint8_t rxAlign) 00140 { 00141 if (count == 0) { return; } 00142 00143 uint8_t address = 0x80 | reg; // MSB == 1 is for reading. LSB is not used in address. Datasheet section 8.1.2.3. 00144 uint8_t index = 0; // Index in values array. 00145 00146 m_CS = 0; /* Select SPI Chip MFRC522 */ 00147 count--; // One read is performed outside of the loop 00148 (void) m_SPI.write(address); // Tell MFRC522 which address we want to read 00149 00150 while (index < count) 00151 { 00152 if ((index == 0) && rxAlign) // Only update bit positions rxAlign..7 in values[0] 00153 { 00154 // Create bit mask for bit positions rxAlign..7 00155 uint8_t mask = 0; 00156 for (uint8_t i = rxAlign; i <= 7; i++) 00157 { 00158 mask |= (1 << i); 00159 } 00160 00161 // Read value and tell that we want to read the same address again. 00162 uint8_t value = m_SPI.write(address); 00163 00164 // Apply mask to both current value of values[0] and the new data in value. 00165 values[0] = (values[index] & ~mask) | (value & mask); 00166 } 00167 else 00168 { 00169 // Read value and tell that we want to read the same address again. 00170 values[index] = m_SPI.write(address); 00171 } 00172 00173 index++; 00174 } 00175 00176 values[index] = m_SPI.write(0); // Read the final byte. Send 0 to stop reading. 00177 00178 m_CS = 1; /* Release SPI Chip MFRC522 */ 00179 } // End PCD_ReadRegister() 00180 00181 /** 00182 * Sets the bits given in mask in register reg. 00183 */ 00184 void MFRC522::PCD_SetRegisterBits(uint8_t reg, uint8_t mask) 00185 { 00186 uint8_t tmp = PCD_ReadRegister(reg); 00187 PCD_WriteRegister(reg, tmp | mask); // set bit mask 00188 } // End PCD_SetRegisterBitMask() 00189 00190 /** 00191 * Clears the bits given in mask from register reg. 00192 */ 00193 void MFRC522::PCD_ClrRegisterBits(uint8_t reg, uint8_t mask) 00194 { 00195 uint8_t tmp = PCD_ReadRegister(reg); 00196 PCD_WriteRegister(reg, tmp & (~mask)); // clear bit mask 00197 } // End PCD_ClearRegisterBitMask() 00198 00199 00200 /** 00201 * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. 00202 */ 00203 uint8_t MFRC522::PCD_CalculateCRC(uint8_t *data, uint8_t length, uint8_t *result) 00204 { 00205 PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. 00206 PCD_WriteRegister(DivIrqReg, 0x04); // Clear the CRCIRq interrupt request bit 00207 PCD_SetRegisterBits(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization 00208 PCD_WriteRegister(FIFODataReg, length, data); // Write data to the FIFO 00209 PCD_WriteRegister(CommandReg, PCD_CalcCRC); // Start the calculation 00210 00211 // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us. 00212 uint16_t i = 5000; 00213 uint8_t n; 00214 while (1) 00215 { 00216 n = PCD_ReadRegister(DivIrqReg); // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved 00217 if (n & 0x04) 00218 { 00219 // CRCIRq bit set - calculation done 00220 break; 00221 } 00222 00223 if (--i == 0) 00224 { 00225 // The emergency break. We will eventually terminate on this one after 89ms. 00226 // Communication with the MFRC522 might be down. 00227 return STATUS_TIMEOUT; 00228 } 00229 } 00230 00231 // Stop calculating CRC for new content in the FIFO. 00232 PCD_WriteRegister(CommandReg, PCD_Idle); 00233 00234 // Transfer the result from the registers to the result buffer 00235 result[0] = PCD_ReadRegister(CRCResultRegL); 00236 result[1] = PCD_ReadRegister(CRCResultRegH); 00237 return STATUS_OK; 00238 } // End PCD_CalculateCRC() 00239 00240 00241 ///////////////////////////////////////////////////////////////////////////////////// 00242 // Functions for manipulating the MFRC522 00243 ///////////////////////////////////////////////////////////////////////////////////// 00244 void MFRC522::PCD_Reset_On() 00245 { 00246 m_RESET = 0; 00247 } 00248 00249 void MFRC522::PCD_Reset_Off() 00250 { 00251 m_RESET = 1; 00252 } 00253 00254 00255 /** 00256 * Initializes the MFRC522 chip. 00257 */ 00258 void MFRC522::PCD_Init() 00259 { 00260 /* Reset MFRC522 */ 00261 m_RESET = 0; 00262 wait_ms(10); 00263 m_RESET = 1; 00264 00265 // 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. 00266 wait_ms(50); 00267 00268 // When communicating with a PICC we need a timeout if something goes wrong. 00269 // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo]. 00270 // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. 00271 PCD_WriteRegister(TModeReg, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds 00272 PCD_WriteRegister(TPrescalerReg, 0xA9); // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25us. 00273 PCD_WriteRegister(TReloadRegH, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. 00274 PCD_WriteRegister(TReloadRegL, 0xE8); 00275 00276 PCD_WriteRegister(TxASKReg, 0x40); // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting 00277 PCD_WriteRegister(ModeReg, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4) 00278 00279 PCD_WriteRegister(RFCfgReg, (0x07<<4)); // Set Rx Gain to max 00280 00281 PCD_AntennaOn(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) 00282 } // End PCD_Init() 00283 00284 /** 00285 * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. 00286 */ 00287 void MFRC522::PCD_Reset() 00288 { 00289 PCD_WriteRegister(CommandReg, PCD_SoftReset); // Issue the SoftReset command. 00290 // The datasheet does not mention how long the SoftRest command takes to complete. 00291 // But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg) 00292 // 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. 00293 wait_ms(50); 00294 00295 // Wait for the PowerDown bit in CommandReg to be cleared 00296 while (PCD_ReadRegister(CommandReg) & (1<<4)) 00297 { 00298 // PCD still restarting - unlikely after waiting 50ms, but better safe than sorry. 00299 } 00300 } // End PCD_Reset() 00301 00302 /** 00303 * Turns the antenna on by enabling pins TX1 and TX2. 00304 * After a reset these pins disabled. 00305 */ 00306 void MFRC522::PCD_AntennaOn() 00307 { 00308 uint8_t value = PCD_ReadRegister(TxControlReg); 00309 if ((value & 0x03) != 0x03) 00310 { 00311 PCD_WriteRegister(TxControlReg, value | 0x03); 00312 } 00313 } // End PCD_AntennaOn() 00314 00315 ///////////////////////////////////////////////////////////////////////////////////// 00316 // Functions for communicating with PICCs 00317 ///////////////////////////////////////////////////////////////////////////////////// 00318 00319 /** 00320 * Executes the Transceive command. 00321 * CRC validation can only be done if backData and backLen are specified. 00322 */ 00323 uint8_t MFRC522::PCD_TransceiveData(uint8_t *sendData, 00324 uint8_t sendLen, 00325 uint8_t *backData, 00326 uint8_t *backLen, 00327 uint8_t *validBits, 00328 uint8_t rxAlign, 00329 bool checkCRC) 00330 { 00331 uint8_t waitIRq = 0x30; // RxIRq and IdleIRq 00332 return PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, sendData, sendLen, backData, backLen, validBits, rxAlign, checkCRC); 00333 } // End PCD_TransceiveData() 00334 00335 /** 00336 * Transfers data to the MFRC522 FIFO, executes a commend, waits for completion and transfers data back from the FIFO. 00337 * CRC validation can only be done if backData and backLen are specified. 00338 */ 00339 uint8_t MFRC522::PCD_CommunicateWithPICC(uint8_t command, 00340 uint8_t waitIRq, 00341 uint8_t *sendData, 00342 uint8_t sendLen, 00343 uint8_t *backData, 00344 uint8_t *backLen, 00345 uint8_t *validBits, 00346 uint8_t rxAlign, 00347 bool checkCRC) 00348 { 00349 uint8_t n, _validBits = 0; 00350 uint32_t i; 00351 00352 // Prepare values for BitFramingReg 00353 uint8_t txLastBits = validBits ? *validBits : 0; 00354 uint8_t bitFraming = (rxAlign << 4) + txLastBits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] 00355 00356 PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. 00357 PCD_WriteRegister(ComIrqReg, 0x7F); // Clear all seven interrupt request bits 00358 PCD_SetRegisterBits(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization 00359 PCD_WriteRegister(FIFODataReg, sendLen, sendData); // Write sendData to the FIFO 00360 PCD_WriteRegister(BitFramingReg, bitFraming); // Bit adjustments 00361 PCD_WriteRegister(CommandReg, command); // Execute the command 00362 if (command == PCD_Transceive) 00363 { 00364 PCD_SetRegisterBits(BitFramingReg, 0x80); // StartSend=1, transmission of data starts 00365 } 00366 00367 // Wait for the command to complete. 00368 // In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops transmitting. 00369 // Each iteration of the do-while-loop takes 17.86us. 00370 i = 2000; 00371 while (1) 00372 { 00373 n = PCD_ReadRegister(ComIrqReg); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq 00374 if (n & waitIRq) 00375 { // One of the interrupts that signal success has been set. 00376 break; 00377 } 00378 00379 if (n & 0x01) 00380 { // Timer interrupt - nothing received in 25ms 00381 return STATUS_TIMEOUT; 00382 } 00383 00384 if (--i == 0) 00385 { // The emergency break. If all other condions fail we will eventually terminate on this one after 35.7ms. Communication with the MFRC522 might be down. 00386 return STATUS_TIMEOUT; 00387 } 00388 } 00389 00390 // Stop now if any errors except collisions were detected. 00391 uint8_t errorRegValue = PCD_ReadRegister(ErrorReg); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr 00392 if (errorRegValue & 0x13) 00393 { // BufferOvfl ParityErr ProtocolErr 00394 return STATUS_ERROR; 00395 } 00396 00397 // If the caller wants data back, get it from the MFRC522. 00398 if (backData && backLen) 00399 { 00400 n = PCD_ReadRegister(FIFOLevelReg); // Number of bytes in the FIFO 00401 if (n > *backLen) 00402 { 00403 return STATUS_NO_ROOM; 00404 } 00405 00406 *backLen = n; // Number of bytes returned 00407 PCD_ReadRegister(FIFODataReg, n, backData, rxAlign); // Get received data from FIFO 00408 _validBits = PCD_ReadRegister(ControlReg) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last received byte. If this value is 000b, the whole byte is valid. 00409 if (validBits) 00410 { 00411 *validBits = _validBits; 00412 } 00413 } 00414 00415 // Tell about collisions 00416 if (errorRegValue & 0x08) 00417 { // CollErr 00418 return STATUS_COLLISION; 00419 } 00420 00421 // Perform CRC_A validation if requested. 00422 if (backData && backLen && checkCRC) 00423 { 00424 // In this case a MIFARE Classic NAK is not OK. 00425 if ((*backLen == 1) && (_validBits == 4)) 00426 { 00427 return STATUS_MIFARE_NACK; 00428 } 00429 00430 // We need at least the CRC_A value and all 8 bits of the last byte must be received. 00431 if ((*backLen < 2) || (_validBits != 0)) 00432 { 00433 return STATUS_CRC_WRONG; 00434 } 00435 00436 // Verify CRC_A - do our own calculation and store the control in controlBuffer. 00437 uint8_t controlBuffer[2]; 00438 n = PCD_CalculateCRC(&backData[0], *backLen - 2, &controlBuffer[0]); 00439 if (n != STATUS_OK) 00440 { 00441 return n; 00442 } 00443 00444 if ((backData[*backLen - 2] != controlBuffer[0]) || (backData[*backLen - 1] != controlBuffer[1])) 00445 { 00446 return STATUS_CRC_WRONG; 00447 } 00448 } 00449 00450 return STATUS_OK; 00451 } // End PCD_CommunicateWithPICC() 00452 00453 /* 00454 * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. 00455 * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. 00456 */ 00457 uint8_t MFRC522::PICC_RequestA(uint8_t *bufferATQA, uint8_t *bufferSize) 00458 { 00459 return PICC_REQA_or_WUPA(PICC_CMD_REQA, bufferATQA, bufferSize); 00460 } // End PICC_RequestA() 00461 00462 /** 00463 * 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. 00464 * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. 00465 */ 00466 uint8_t MFRC522::PICC_WakeupA(uint8_t *bufferATQA, uint8_t *bufferSize) 00467 { 00468 return PICC_REQA_or_WUPA(PICC_CMD_WUPA, bufferATQA, bufferSize); 00469 } // End PICC_WakeupA() 00470 00471 /* 00472 * Transmits REQA or WUPA commands. 00473 * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. 00474 */ 00475 uint8_t MFRC522::PICC_REQA_or_WUPA(uint8_t command, uint8_t *bufferATQA, uint8_t *bufferSize) 00476 { 00477 uint8_t validBits; 00478 uint8_t status; 00479 00480 if (bufferATQA == NULL || *bufferSize < 2) 00481 { // The ATQA response is 2 bytes long. 00482 return STATUS_NO_ROOM; 00483 } 00484 00485 // ValuesAfterColl=1 => Bits received after collision are cleared. 00486 PCD_ClrRegisterBits(CollReg, 0x80); 00487 00488 // For REQA and WUPA we need the short frame format 00489 // - transmit only 7 bits of the last (and only) byte. TxLastBits = BitFramingReg[2..0] 00490 validBits = 7; 00491 00492 status = PCD_TransceiveData(&command, 1, bufferATQA, bufferSize, &validBits); 00493 if (status != STATUS_OK) 00494 { 00495 return status; 00496 } 00497 00498 if ((*bufferSize != 2) || (validBits != 0)) 00499 { // ATQA must be exactly 16 bits. 00500 return STATUS_ERROR; 00501 } 00502 00503 return STATUS_OK; 00504 } // End PICC_REQA_or_WUPA() 00505 00506 /* 00507 * Transmits SELECT/ANTICOLLISION commands to select a single PICC. 00508 */ 00509 uint8_t MFRC522::PICC_Select(Uid *uid, uint8_t validBits) 00510 { 00511 bool uidComplete; 00512 bool selectDone; 00513 bool useCascadeTag; 00514 uint8_t cascadeLevel = 1; 00515 uint8_t result; 00516 uint8_t count; 00517 uint8_t index; 00518 uint8_t uidIndex; // The first index in uid->uidByte[] that is used in the current Cascade Level. 00519 uint8_t currentLevelKnownBits; // The number of known UID bits in the current Cascade Level. 00520 uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A 00521 uint8_t bufferUsed; // The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO. 00522 uint8_t rxAlign; // Used in BitFramingReg. Defines the bit position for the first bit received. 00523 uint8_t txLastBits; // Used in BitFramingReg. The number of valid bits in the last transmitted byte. 00524 uint8_t *responseBuffer; 00525 uint8_t responseLength; 00526 00527 // Description of buffer structure: 00528 // Byte 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3 00529 // Byte 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete bytes, Low nibble: Extra bits. 00530 // Byte 2: UID-data or CT See explanation below. CT means Cascade Tag. 00531 // Byte 3: UID-data 00532 // Byte 4: UID-data 00533 // Byte 5: UID-data 00534 // Byte 6: BCC Block Check Character - XOR of bytes 2-5 00535 // Byte 7: CRC_A 00536 // Byte 8: CRC_A 00537 // The BCC and CRC_A is only transmitted if we know all the UID bits of the current Cascade Level. 00538 // 00539 // Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels) 00540 // UID size Cascade level Byte2 Byte3 Byte4 Byte5 00541 // ======== ============= ===== ===== ===== ===== 00542 // 4 bytes 1 uid0 uid1 uid2 uid3 00543 // 7 bytes 1 CT uid0 uid1 uid2 00544 // 2 uid3 uid4 uid5 uid6 00545 // 10 bytes 1 CT uid0 uid1 uid2 00546 // 2 CT uid3 uid4 uid5 00547 // 3 uid6 uid7 uid8 uid9 00548 00549 // Sanity checks 00550 if (validBits > 80) 00551 { 00552 return STATUS_INVALID; 00553 } 00554 00555 // Prepare MFRC522 00556 // ValuesAfterColl=1 => Bits received after collision are cleared. 00557 PCD_ClrRegisterBits(CollReg, 0x80); 00558 00559 // Repeat Cascade Level loop until we have a complete UID. 00560 uidComplete = false; 00561 while ( ! uidComplete) 00562 { 00563 // Set the Cascade Level in the SEL byte, find out if we need to use the Cascade Tag in byte 2. 00564 switch (cascadeLevel) 00565 { 00566 case 1: 00567 buffer[0] = PICC_CMD_SEL_CL1; 00568 uidIndex = 0; 00569 useCascadeTag = validBits && (uid->size > 4); // When we know that the UID has more than 4 bytes 00570 break; 00571 00572 case 2: 00573 buffer[0] = PICC_CMD_SEL_CL2; 00574 uidIndex = 3; 00575 useCascadeTag = validBits && (uid->size > 7); // When we know that the UID has more than 7 bytes 00576 break; 00577 00578 case 3: 00579 buffer[0] = PICC_CMD_SEL_CL3; 00580 uidIndex = 6; 00581 useCascadeTag = false; // Never used in CL3. 00582 break; 00583 00584 default: 00585 return STATUS_INTERNAL_ERROR; 00586 //break; 00587 } 00588 00589 // How many UID bits are known in this Cascade Level? 00590 if(validBits > (8 * uidIndex)) 00591 { 00592 currentLevelKnownBits = validBits - (8 * uidIndex); 00593 } 00594 else 00595 { 00596 currentLevelKnownBits = 0; 00597 } 00598 00599 // Copy the known bits from uid->uidByte[] to buffer[] 00600 index = 2; // destination index in buffer[] 00601 if (useCascadeTag) 00602 { 00603 buffer[index++] = PICC_CMD_CT; 00604 } 00605 00606 uint8_t bytesToCopy = currentLevelKnownBits / 8 + (currentLevelKnownBits % 8 ? 1 : 0); // The number of bytes needed to represent the known bits for this level. 00607 if (bytesToCopy) 00608 { 00609 // Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag 00610 uint8_t maxBytes = useCascadeTag ? 3 : 4; 00611 if (bytesToCopy > maxBytes) 00612 { 00613 bytesToCopy = maxBytes; 00614 } 00615 00616 for (count = 0; count < bytesToCopy; count++) 00617 { 00618 buffer[index++] = uid->uidByte[uidIndex + count]; 00619 } 00620 } 00621 00622 // Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits 00623 if (useCascadeTag) 00624 { 00625 currentLevelKnownBits += 8; 00626 } 00627 00628 // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations. 00629 selectDone = false; 00630 while ( ! selectDone) 00631 { 00632 // Find out how many bits and bytes to send and receive. 00633 if (currentLevelKnownBits >= 32) 00634 { // All UID bits in this Cascade Level are known. This is a SELECT. 00635 //Serial.print("SELECT: currentLevelKnownBits="); Serial.println(currentLevelKnownBits, DEC); 00636 buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole bytes 00637 00638 // Calulate BCC - Block Check Character 00639 buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; 00640 00641 // Calculate CRC_A 00642 result = PCD_CalculateCRC(buffer, 7, &buffer[7]); 00643 if (result != STATUS_OK) 00644 { 00645 return result; 00646 } 00647 00648 txLastBits = 0; // 0 => All 8 bits are valid. 00649 bufferUsed = 9; 00650 00651 // Store response in the last 3 bytes of buffer (BCC and CRC_A - not needed after tx) 00652 responseBuffer = &buffer[6]; 00653 responseLength = 3; 00654 } 00655 else 00656 { // This is an ANTICOLLISION. 00657 //Serial.print("ANTICOLLISION: currentLevelKnownBits="); Serial.println(currentLevelKnownBits, DEC); 00658 txLastBits = currentLevelKnownBits % 8; 00659 count = currentLevelKnownBits / 8; // Number of whole bytes in the UID part. 00660 index = 2 + count; // Number of whole bytes: SEL + NVB + UIDs 00661 buffer[1] = (index << 4) + txLastBits; // NVB - Number of Valid Bits 00662 bufferUsed = index + (txLastBits ? 1 : 0); 00663 00664 // Store response in the unused part of buffer 00665 responseBuffer = &buffer[index]; 00666 responseLength = sizeof(buffer) - index; 00667 } 00668 00669 // Set bit adjustments 00670 rxAlign = txLastBits; // Having a seperate variable is overkill. But it makes the next line easier to read. 00671 PCD_WriteRegister(BitFramingReg, (rxAlign << 4) + txLastBits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] 00672 00673 // Transmit the buffer and receive the response. 00674 result = PCD_TransceiveData(buffer, bufferUsed, responseBuffer, &responseLength, &txLastBits, rxAlign); 00675 if (result == STATUS_COLLISION) 00676 { // More than one PICC in the field => collision. 00677 result = PCD_ReadRegister(CollReg); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] 00678 if (result & 0x20) 00679 { // CollPosNotValid 00680 return STATUS_COLLISION; // Without a valid collision position we cannot continue 00681 } 00682 00683 uint8_t collisionPos = result & 0x1F; // Values 0-31, 0 means bit 32. 00684 if (collisionPos == 0) 00685 { 00686 collisionPos = 32; 00687 } 00688 00689 if (collisionPos <= currentLevelKnownBits) 00690 { // No progress - should not happen 00691 return STATUS_INTERNAL_ERROR; 00692 } 00693 00694 // Choose the PICC with the bit set. 00695 currentLevelKnownBits = collisionPos; 00696 count = (currentLevelKnownBits - 1) % 8; // The bit to modify 00697 index = 1 + (currentLevelKnownBits / 8) + (count ? 1 : 0); // First byte is index 0. 00698 buffer[index] |= (1 << count); 00699 } 00700 else if (result != STATUS_OK) 00701 { 00702 return result; 00703 } 00704 else 00705 { // STATUS_OK 00706 if (currentLevelKnownBits >= 32) 00707 { // This was a SELECT. 00708 selectDone = true; // No more anticollision 00709 // We continue below outside the while. 00710 } 00711 else 00712 { // This was an ANTICOLLISION. 00713 // We now have all 32 bits of the UID in this Cascade Level 00714 currentLevelKnownBits = 32; 00715 // Run loop again to do the SELECT. 00716 } 00717 } 00718 } // End of while ( ! selectDone) 00719 00720 // We do not check the CBB - it was constructed by us above. 00721 00722 // Copy the found UID bytes from buffer[] to uid->uidByte[] 00723 index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[] 00724 bytesToCopy = (buffer[2] == PICC_CMD_CT) ? 3 : 4; 00725 for (count = 0; count < bytesToCopy; count++) 00726 { 00727 uid->uidByte[uidIndex + count] = buffer[index++]; 00728 } 00729 00730 // Check response SAK (Select Acknowledge) 00731 if (responseLength != 3 || txLastBits != 0) 00732 { // SAK must be exactly 24 bits (1 byte + CRC_A). 00733 return STATUS_ERROR; 00734 } 00735 00736 // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed anymore. 00737 result = PCD_CalculateCRC(responseBuffer, 1, &buffer[2]); 00738 if (result != STATUS_OK) 00739 { 00740 return result; 00741 } 00742 00743 if ((buffer[2] != responseBuffer[1]) || (buffer[3] != responseBuffer[2])) 00744 { 00745 return STATUS_CRC_WRONG; 00746 } 00747 00748 if (responseBuffer[0] & 0x04) 00749 { // Cascade bit set - UID not complete yes 00750 cascadeLevel++; 00751 } 00752 else 00753 { 00754 uidComplete = true; 00755 uid->sak = responseBuffer[0]; 00756 } 00757 } // End of while ( ! uidComplete) 00758 00759 // Set correct uid->size 00760 uid->size = 3 * cascadeLevel + 1; 00761 00762 return STATUS_OK; 00763 } // End PICC_Select() 00764 00765 /* 00766 * Instructs a PICC in state ACTIVE(*) to go to state HALT. 00767 */ 00768 uint8_t MFRC522::PICC_HaltA() 00769 { 00770 uint8_t result; 00771 uint8_t buffer[4]; 00772 00773 // Build command buffer 00774 buffer[0] = PICC_CMD_HLTA; 00775 buffer[1] = 0; 00776 00777 // Calculate CRC_A 00778 result = PCD_CalculateCRC(buffer, 2, &buffer[2]); 00779 if (result == STATUS_OK) 00780 { 00781 // Send the command. 00782 // The standard says: 00783 // If the PICC responds with any modulation during a period of 1 ms after the end of the frame containing the 00784 // HLTA command, this response shall be interpreted as 'not acknowledge'. 00785 // We interpret that this way: Only STATUS_TIMEOUT is an success. 00786 result = PCD_TransceiveData(buffer, sizeof(buffer), NULL, 0); 00787 if (result == STATUS_TIMEOUT) 00788 { 00789 result = STATUS_OK; 00790 } 00791 else if (result == STATUS_OK) 00792 { // That is ironically NOT ok in this case ;-) 00793 result = STATUS_ERROR; 00794 } 00795 } 00796 00797 return result; 00798 } // End PICC_HaltA() 00799 00800 00801 ///////////////////////////////////////////////////////////////////////////////////// 00802 // Functions for communicating with MIFARE PICCs 00803 ///////////////////////////////////////////////////////////////////////////////////// 00804 00805 /* 00806 * Executes the MFRC522 MFAuthent command. 00807 */ 00808 uint8_t MFRC522::PCD_Authenticate(uint8_t command, uint8_t blockAddr, MIFARE_Key *key, Uid *uid) 00809 { 00810 uint8_t i, waitIRq = 0x10; // IdleIRq 00811 00812 // Build command buffer 00813 uint8_t sendData[12]; 00814 sendData[0] = command; 00815 sendData[1] = blockAddr; 00816 00817 for (i = 0; i < MF_KEY_SIZE; i++) 00818 { // 6 key bytes 00819 sendData[2+i] = key->keyByte[i]; 00820 } 00821 00822 for (i = 0; i < 4; i++) 00823 { // The first 4 bytes of the UID 00824 sendData[8+i] = uid->uidByte[i]; 00825 } 00826 00827 // Start the authentication. 00828 return PCD_CommunicateWithPICC(PCD_MFAuthent, waitIRq, &sendData[0], sizeof(sendData)); 00829 } // End PCD_Authenticate() 00830 00831 /* 00832 * Used to exit the PCD from its authenticated state. 00833 * Remember to call this function after communicating with an authenticated PICC - otherwise no new communications can start. 00834 */ 00835 void MFRC522::PCD_StopCrypto1() 00836 { 00837 // Clear MFCrypto1On bit 00838 PCD_ClrRegisterBits(Status2Reg, 0x08); // Status2Reg[7..0] bits are: TempSensClear I2CForceHS reserved reserved MFCrypto1On ModemState[2:0] 00839 } // End PCD_StopCrypto1() 00840 00841 /* 00842 * Reads 16 bytes (+ 2 bytes CRC_A) from the active PICC. 00843 */ 00844 uint8_t MFRC522::MIFARE_Read(uint8_t blockAddr, uint8_t *buffer, uint8_t *bufferSize) 00845 { 00846 uint8_t result = STATUS_NO_ROOM; 00847 00848 // Sanity check 00849 if ((buffer == NULL) || (*bufferSize < 18)) 00850 { 00851 return result; 00852 } 00853 00854 // Build command buffer 00855 buffer[0] = PICC_CMD_MF_READ; 00856 buffer[1] = blockAddr; 00857 00858 // Calculate CRC_A 00859 result = PCD_CalculateCRC(buffer, 2, &buffer[2]); 00860 if (result != STATUS_OK) 00861 { 00862 return result; 00863 } 00864 00865 // Transmit the buffer and receive the response, validate CRC_A. 00866 return PCD_TransceiveData(buffer, 4, buffer, bufferSize, NULL, 0, true); 00867 } // End MIFARE_Read() 00868 00869 /* 00870 * Writes 16 bytes to the active PICC. 00871 */ 00872 uint8_t MFRC522::MIFARE_Write(uint8_t blockAddr, uint8_t *buffer, uint8_t bufferSize) 00873 { 00874 uint8_t result; 00875 00876 // Sanity check 00877 if (buffer == NULL || bufferSize < 16) 00878 { 00879 return STATUS_INVALID; 00880 } 00881 00882 // Mifare Classic protocol requires two communications to perform a write. 00883 // Step 1: Tell the PICC we want to write to block blockAddr. 00884 uint8_t cmdBuffer[2]; 00885 cmdBuffer[0] = PICC_CMD_MF_WRITE; 00886 cmdBuffer[1] = blockAddr; 00887 // Adds CRC_A and checks that the response is MF_ACK. 00888 result = PCD_MIFARE_Transceive(cmdBuffer, 2); 00889 if (result != STATUS_OK) 00890 { 00891 return result; 00892 } 00893 00894 // Step 2: Transfer the data 00895 // Adds CRC_A and checks that the response is MF_ACK. 00896 result = PCD_MIFARE_Transceive(buffer, bufferSize); 00897 if (result != STATUS_OK) 00898 { 00899 return result; 00900 } 00901 00902 return STATUS_OK; 00903 } // End MIFARE_Write() 00904 00905 /* 00906 * Writes a 4 byte page to the active MIFARE Ultralight PICC. 00907 */ 00908 uint8_t MFRC522::MIFARE_UltralightWrite(uint8_t page, uint8_t *buffer, uint8_t bufferSize) 00909 { 00910 uint8_t result; 00911 00912 // Sanity check 00913 if (buffer == NULL || bufferSize < 4) 00914 { 00915 return STATUS_INVALID; 00916 } 00917 00918 // Build commmand buffer 00919 uint8_t cmdBuffer[6]; 00920 cmdBuffer[0] = PICC_CMD_UL_WRITE; 00921 cmdBuffer[1] = page; 00922 memcpy(&cmdBuffer[2], buffer, 4); 00923 00924 // Perform the write 00925 result = PCD_MIFARE_Transceive(cmdBuffer, 6); // Adds CRC_A and checks that the response is MF_ACK. 00926 if (result != STATUS_OK) 00927 { 00928 return result; 00929 } 00930 00931 return STATUS_OK; 00932 } // End MIFARE_Ultralight_Write() 00933 00934 /* 00935 * MIFARE Decrement subtracts the delta from the value of the addressed block, and stores the result in a volatile memory. 00936 */ 00937 uint8_t MFRC522::MIFARE_Decrement(uint8_t blockAddr, uint32_t delta) 00938 { 00939 return MIFARE_TwoStepHelper(PICC_CMD_MF_DECREMENT, blockAddr, delta); 00940 } // End MIFARE_Decrement() 00941 00942 /* 00943 * MIFARE Increment adds the delta to the value of the addressed block, and stores the result in a volatile memory. 00944 */ 00945 uint8_t MFRC522::MIFARE_Increment(uint8_t blockAddr, uint32_t delta) 00946 { 00947 return MIFARE_TwoStepHelper(PICC_CMD_MF_INCREMENT, blockAddr, delta); 00948 } // End MIFARE_Increment() 00949 00950 /** 00951 * MIFARE Restore copies the value of the addressed block into a volatile memory. 00952 */ 00953 uint8_t MFRC522::MIFARE_Restore(uint8_t blockAddr) 00954 { 00955 // The datasheet describes Restore as a two step operation, but does not explain what data to transfer in step 2. 00956 // Doing only a single step does not work, so I chose to transfer 0L in step two. 00957 return MIFARE_TwoStepHelper(PICC_CMD_MF_RESTORE, blockAddr, 0L); 00958 } // End MIFARE_Restore() 00959 00960 /* 00961 * Helper function for the two-step MIFARE Classic protocol operations Decrement, Increment and Restore. 00962 */ 00963 uint8_t MFRC522::MIFARE_TwoStepHelper(uint8_t command, uint8_t blockAddr, uint32_t data) 00964 { 00965 uint8_t result; 00966 uint8_t cmdBuffer[2]; // We only need room for 2 bytes. 00967 00968 // Step 1: Tell the PICC the command and block address 00969 cmdBuffer[0] = command; 00970 cmdBuffer[1] = blockAddr; 00971 00972 // Adds CRC_A and checks that the response is MF_ACK. 00973 result = PCD_MIFARE_Transceive(cmdBuffer, 2); 00974 if (result != STATUS_OK) 00975 { 00976 return result; 00977 } 00978 00979 // Step 2: Transfer the data 00980 // Adds CRC_A and accept timeout as success. 00981 result = PCD_MIFARE_Transceive((uint8_t *) &data, 4, true); 00982 if (result != STATUS_OK) 00983 { 00984 return result; 00985 } 00986 00987 return STATUS_OK; 00988 } // End MIFARE_TwoStepHelper() 00989 00990 /* 00991 * MIFARE Transfer writes the value stored in the volatile memory into one MIFARE Classic block. 00992 */ 00993 uint8_t MFRC522::MIFARE_Transfer(uint8_t blockAddr) 00994 { 00995 uint8_t cmdBuffer[2]; // We only need room for 2 bytes. 00996 00997 // Tell the PICC we want to transfer the result into block blockAddr. 00998 cmdBuffer[0] = PICC_CMD_MF_TRANSFER; 00999 cmdBuffer[1] = blockAddr; 01000 01001 // Adds CRC_A and checks that the response is MF_ACK. 01002 return PCD_MIFARE_Transceive(cmdBuffer, 2); 01003 } // End MIFARE_Transfer() 01004 01005 01006 ///////////////////////////////////////////////////////////////////////////////////// 01007 // Support functions 01008 ///////////////////////////////////////////////////////////////////////////////////// 01009 01010 /* 01011 * Wrapper for MIFARE protocol communication. 01012 * Adds CRC_A, executes the Transceive command and checks that the response is MF_ACK or a timeout. 01013 */ 01014 uint8_t MFRC522::PCD_MIFARE_Transceive(uint8_t *sendData, uint8_t sendLen, bool acceptTimeout) 01015 { 01016 uint8_t result; 01017 uint8_t cmdBuffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A. 01018 01019 // Sanity check 01020 if (sendData == NULL || sendLen > 16) 01021 { 01022 return STATUS_INVALID; 01023 } 01024 01025 // Copy sendData[] to cmdBuffer[] and add CRC_A 01026 memcpy(cmdBuffer, sendData, sendLen); 01027 result = PCD_CalculateCRC(cmdBuffer, sendLen, &cmdBuffer[sendLen]); 01028 if (result != STATUS_OK) 01029 { 01030 return result; 01031 } 01032 01033 sendLen += 2; 01034 01035 // Transceive the data, store the reply in cmdBuffer[] 01036 uint8_t waitIRq = 0x30; // RxIRq and IdleIRq 01037 uint8_t cmdBufferSize = sizeof(cmdBuffer); 01038 uint8_t validBits = 0; 01039 result = PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, cmdBuffer, sendLen, cmdBuffer, &cmdBufferSize, &validBits); 01040 if (acceptTimeout && result == STATUS_TIMEOUT) 01041 { 01042 return STATUS_OK; 01043 } 01044 01045 if (result != STATUS_OK) 01046 { 01047 return result; 01048 } 01049 01050 // The PICC must reply with a 4 bit ACK 01051 if (cmdBufferSize != 1 || validBits != 4) 01052 { 01053 return STATUS_ERROR; 01054 } 01055 01056 if (cmdBuffer[0] != MF_ACK) 01057 { 01058 return STATUS_MIFARE_NACK; 01059 } 01060 01061 return STATUS_OK; 01062 } // End PCD_MIFARE_Transceive() 01063 01064 01065 /* 01066 * Translates the SAK (Select Acknowledge) to a PICC type. 01067 */ 01068 uint8_t MFRC522::PICC_GetType(uint8_t sak) 01069 { 01070 uint8_t retType = PICC_TYPE_UNKNOWN; 01071 01072 if (sak & 0x04) 01073 { // UID not complete 01074 retType = PICC_TYPE_NOT_COMPLETE; 01075 } 01076 else 01077 { 01078 switch (sak) 01079 { 01080 case 0x09: retType = PICC_TYPE_MIFARE_MINI; break; 01081 case 0x08: retType = PICC_TYPE_MIFARE_1K; break; 01082 case 0x18: retType = PICC_TYPE_MIFARE_4K; break; 01083 case 0x00: retType = PICC_TYPE_MIFARE_UL; break; 01084 case 0x10: 01085 case 0x11: retType = PICC_TYPE_MIFARE_PLUS; break; 01086 case 0x01: retType = PICC_TYPE_TNP3XXX; break; 01087 default: 01088 if (sak & 0x20) 01089 { 01090 retType = PICC_TYPE_ISO_14443_4; 01091 } 01092 else if (sak & 0x40) 01093 { 01094 retType = PICC_TYPE_ISO_18092; 01095 } 01096 break; 01097 } 01098 } 01099 01100 return (retType); 01101 } // End PICC_GetType() 01102 01103 /* 01104 * Returns a string pointer to the PICC type name. 01105 */ 01106 char* MFRC522::PICC_GetTypeName(uint8_t piccType) 01107 { 01108 if(piccType == PICC_TYPE_NOT_COMPLETE) 01109 { 01110 piccType = MFRC522_MaxPICCs - 1; 01111 } 01112 01113 return((char *) _TypeNamePICC[piccType]); 01114 } // End PICC_GetTypeName() 01115 01116 /* 01117 * Returns a string pointer to a status code name. 01118 */ 01119 char* MFRC522::GetStatusCodeName(uint8_t code) 01120 { 01121 return((char *) _ErrorMessage[code]); 01122 } // End GetStatusCodeName() 01123 01124 /* 01125 * Calculates the bit pattern needed for the specified access bits. In the [C1 C2 C3] tupples C1 is MSB (=4) and C3 is LSB (=1). 01126 */ 01127 void MFRC522::MIFARE_SetAccessBits(uint8_t *accessBitBuffer, 01128 uint8_t g0, 01129 uint8_t g1, 01130 uint8_t g2, 01131 uint8_t g3) 01132 { 01133 uint8_t c1 = ((g3 & 4) << 1) | ((g2 & 4) << 0) | ((g1 & 4) >> 1) | ((g0 & 4) >> 2); 01134 uint8_t c2 = ((g3 & 2) << 2) | ((g2 & 2) << 1) | ((g1 & 2) << 0) | ((g0 & 2) >> 1); 01135 uint8_t c3 = ((g3 & 1) << 3) | ((g2 & 1) << 2) | ((g1 & 1) << 1) | ((g0 & 1) << 0); 01136 01137 accessBitBuffer[0] = (~c2 & 0xF) << 4 | (~c1 & 0xF); 01138 accessBitBuffer[1] = c1 << 4 | (~c3 & 0xF); 01139 accessBitBuffer[2] = c3 << 4 | c2; 01140 } // End MIFARE_SetAccessBits() 01141 01142 ///////////////////////////////////////////////////////////////////////////////////// 01143 // Convenience functions - does not add extra functionality 01144 ///////////////////////////////////////////////////////////////////////////////////// 01145 01146 /* 01147 * Returns true if a PICC responds to PICC_CMD_REQA. 01148 * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. 01149 */ 01150 bool MFRC522::PICC_IsNewCardPresent(void) 01151 { 01152 uint8_t bufferATQA[2]; 01153 uint8_t bufferSize = sizeof(bufferATQA); 01154 uint8_t result = PICC_RequestA(bufferATQA, &bufferSize); 01155 return ((result == STATUS_OK) || (result == STATUS_COLLISION)); 01156 } // End PICC_IsNewCardPresent() 01157 01158 /* 01159 * Simple wrapper around PICC_Select. 01160 */ 01161 bool MFRC522::PICC_ReadCardSerial(void) 01162 { 01163 uint8_t result = PICC_Select(&uid); 01164 return (result == STATUS_OK); 01165 } // End PICC_ReadCardSerial()
Generated on Tue Jul 12 2022 20:55:24 by
1.7.2
