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