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